我必须实现基于SEH的异常处理程序。 首先,我编写了以下示例代码,其中我尝试使用 fs 寄存器注册异常处理程序。
#include <iostream>
#include <exception>
#include <windows.h>
using namespace std;
EXCEPTION_DISPOSITION myHandler(
_EXCEPTION_RECORD *ExcRecord,
void * EstablisherFrame,
_CONTEXT *ContextRecord,
void * DispatcherContext)
{
cout << "In the exception handler" << endl;
cout << "Just a demo. exiting..." << endl;
return ExceptionContinueExecution;
}
int main()
{
cout << "!!!Hello World!!!" << endl; // prints !!!Hello World!!!
EXCEPTION_REGISTRATION myExceptReg;
EXCEPTION_REGISTRATION *pReg = &myExceptReg;
myExceptReg.handler = myHandler;
DWORD prev;
asm("movl %fs:0 , %eax");
asm("movl %%eax , %0": "=r" (prev));
myExceptReg.prev = (EXCEPTION_REGISTRATION*) prev;
asm ("movl %0, %%eax" : "=m" (pReg));
asm("movl %eax , %fs:0");
// int* ptr = 0;
// exception e;
return 0;
}
当我调试代码时,我发现 fs 寄存器的值零。并且程序在执行 asm(“movl%fs:0,%eax”)后崩溃;
以下是与此代码等效的程序集示例。
000000000000401626: mov %rax,%rcx
000000000000401629: callq 0x44d7a0 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))>
32 EXCEPTION_REGISTRATION *pReg = &myExceptReg;
00000000000040162e: lea 0x20(%rbp),%rax
000000000000401632: mov %rax,0x18(%rbp)
33 myExceptReg.handler = myHandler;
000000000000401636: lea -0x13d(%rip),%rax # 0x401500 <myHandler(_EXCEPTION_RECORD*, void*, _CONTEXT*, void*)>
00000000000040163d: mov %rax,0x28(%rbp)
36 asm("movl %fs:0 , %eax");
000000000000401641: mov %fs:0x0,%eax
37 asm("movl %%eax , %0": "=r" (prev));
000000000000401649: mov %eax,%ebx
00000000000040164b: mov %ebx,0x3c(%rbp)
39 myExceptReg.prev = (EXCEPTION_REGISTRATION*) prev;
00000000000040164e: mov 0x3c(%rbp),%eax
000000000000401651: mov %rax,0x20(%rbp)
41 asm ("movl %0, %%eax" : "=m" (pReg));
000000000000401655: mov 0x18(%rbp),%eax
42 asm("movl %eax , %fs:0");
000000000000401658: mov %eax,%fs:0x0
50 return 0;
可能是什么问题?
答案 0 :(得分:1)
总结一下:
调试输出显示代码正在为64位编译,并且(正如Hans指出的那样)正在使用的异常处理样式仅对32位有效。确保代码编译为32位可以解决问题。
如果这回答了您的问题,请点击左侧的复选标记,以便获得业力奖励。
答案 1 :(得分:0)
除了64位问题之外,你无意中破坏了%eax
(没有告诉编译器)。
我很惊讶它与您的原始资源一起工作。你很幸运,编译器无论如何都会立即覆盖%eax
,可能是因为你使用-O0
制作了令人讨厌的代码,这些代码永远不会在寄存器中保留任何东西。因此,只要您使用优化编译,您的代码就会崩溃。
你也很幸运,编译器没有在两个asm语句之间插入任何指令,这些语句破坏了%eax
。永远不要指望在两个asm块之间存活的寄存器或标志:在一个块中使用多个指令。
除此之外,asm ("movl %0, %%eax" : "=m" (pReg));
告诉编译器asm语句在内存中覆盖pReg
,而不读取旧值。同样,只有-O0
使您免于此错误,因为它无法优化pReg = &myExceptReg;
,如果pReg
无论如何都会被覆盖,则不需要计算-O1
。使用pReg
或更高版本,您应该期望void* prev;
asm volatile("mov %%fs:0, %0": "=r" (prev));
...
asm volatile("movl %0, %%fs:0" : : "re" (pReg) : "memory");
// reg or immediate source, but not memory, are encodable with a memory destination
未初始化。
prev
当被问到时,编译器将负责将变量置于寄存器中。如果它想要在之后将"=r" (prev)
存储回内存,它会这样做。没有必要强制它这样做,%eax
只是从prev
生成了一个冗余的reg-reg移动到其他reg gcc决定保留movl
的内容。
因为(根据Hans的说法),这段代码仅在32位有用,我将"e"
后缀保留为存储到内存,因此当没有涉及寄存器来确定时,编译器可以确定操作数大小它。 (即,当值是全局或静态符号的编译时常量地址时。)
除此之外,这个源代码可以编译成适当的64位代码,因为它没有明确地命名任何寄存器。
您可以删除约束的l
部分和mov r/m64, imm32
后缀,以便在第二个asm语句的32位和64位之间轻松移植,但代价是强制编译器浪费一条指令,首先将地址放入寄存器,即使它不需要。在64位模式下,"i"
(英特尔语法)就是您所需要的。我使用an "e"
constraint而不是if(sizeof(void*) == 4) { asm("movl ..."); } else { asm("movq..."); }
约束,因为任意64位常量都不能编码,只能符号扩展32位。幸运的是,在默认代码模型中,符号地址可以安全地假设为低2GB。链接器可以将地址填入32位重定位。
为了使其在32位和64位之间可以使用立即操作数进行移植,您需要将其输出为64位的“movq”。我认为你必须使用预处理器来测试i386和amd64。 -O0
也可能有用。这会使mov r/m32, imm32
的代码变得非常丑陋,但是分支的伪造方面仍会汇集(到=0
)并且永远不会运行。