为什么在64位系统中,使用int 3会生成SIGSEGV信号而不是停止调试器?

20
在32位模式编程中,我经常在程序中使用int 3来使程序停留在调试器指定的位置(在源代码中嵌入该指令)。现在在64位模式下,它似乎无法工作,在gdb下产生了一个非常普通的SIGSEGV,并且毁掉了程序("Program terminated with signal SIGSEGV, Segmentation fault. The program no longer exists.")。我想知道64位模式是否有另一种机制,或者我是否需要做一些缓存刷新(在这种情况下int 3是动态生成的操作码(0xcc),类似jit的代码)。


请注意,对于某些汇编器(如NASM),int 3CD 03,您需要写 int3(无空格)才能获得 0xCC 单字节操作码。根据Intel的int n / int3手册,实际上在vm86模式下差异很大。但是,是的,Intel的手册确实确认它在64位模式下的行为相同,因此任何行为上的差异都取决于内核和/或调试器。 - Peter Cordes
4个回答

20

__debugbreak()

今天,一位同事来问如何在64位平台上获得“int 3”功能。什么是“int 3”?它是用于创建断点的汇编指令。至少对于x86处理器而言,正是这个指令,而且正如您所想象的那样,它非常特定于平台。

在64位平台上没有内联汇编,所以就没有了"__asm int 3"。现在该怎么办?好吧,有一个不太知名的构造,实际上在所有平台(x64、Itanium和x86)上都可以使用,那就是__debugbreak()。这是Visual C++编译器内部函数(在Visual C++ 2005中定义在vc\include\intrin.h下,还有许多其他很酷的内部函数),它将在所有平台上有效地执行“int 3”。

DebugBreak,Win32函数调用仍然存在,但通常我更喜欢使用__debugbreak(),即使仅因为它不是一个函数调用(它是一个编译器内部函数),并且您不需要调试符号来获取可读的调用堆栈。

如果您正在编写C++,您可能不希望编写不可移植的汇编代码,这只是您无需编写这种代码的一个地方。

http://blogs.msdn.com/b/kangsu/archive/2005/09/07/462232.aspx


13
这是一个非常有用的回答。但是,正如我在问题中指定的那样,我正在动态生成代码,并且这就排除了编译器内部函数。更不用说我的通常编译器不是Visual C++,我的平台也不是Windows。此外,这个回答是不准确的,因为int 3确实存在,在64位Linux上至少表现得像往常一样。此外,根据编译器,在64位模式下您确实可以拥有内联汇编。 - dsign
1
这对于微软的集成开发环境来说没问题,但如果我使用Eclipse和GCC呢? - Mawg says reinstate Monica

15
以下代码适用于 amd64 UNIX 平台:

breakpoint.c

int main() {
    int i;     
    for(i=0; i<3;i++) {
        __asm__("int3");
    }
}

简单编译:gcc -c breakpoint.c,然后启动gdb a.out

(gdb) run
Starting program: /tmp/a.out 

Program received signal SIGTRAP, Trace/breakpoint trap.
0x00000000004004fb in main ()

你看,gdb在断点处停止。


2
你应该说内核版本而不仅仅是日期 :-) - Ciro Santilli OurBigBook.com
1
你的第一段是错误的:BarsMonster的答案是关于Win64的,而你展示的功能是在类UNIX系统上(或者至少是非MS工具链,如果在Win64上)。 - Ruslan
@Ruslan 谢谢您的反馈! - hek2mgl

13

啊,我明白了,抱歉。我必须取消对执行页面的保护。Int 3仍然是一个有效的调试陷阱。


这个问题是七年前的,所以很难记得我当时做了什么细节。我记得那很简单,我想我是在调用mmap(2)并添加了PROT_EXEC标志。或者是mprotect(2)……? - dsign
可能是因为代码所在的页面是读/写/不可执行页面,导致执行从未到达int3。因此,仅仅跳转到那里就会导致段错误,无论内容如何。是的,对于mmap,PROT_READ|PROT_EXEC|PROT_WRITE应该是正确的方法。或者你可以先使用读/写页面,然后在完成后使用mprotect将其翻转为读/执行,这样你就永远不会映射写+执行页面(出于安全原因)。 - Peter Cordes

0
我建议您不要使用 asm int 3 ,因为它适用于所有构建类型。你可能会忘记代码中的某一行,这可能会带来很大的麻烦。而可以选择使用 __debugbreak,它只在调试模式下有效。

虽然我也会建议在可用的情况下使用__debugbreak而不是asm int 3,但我认为你的假设是错误的。__debugbreak似乎总是有效的,不仅在调试模式下,而且在发布模式下也是如此。 - Deniz Bahadir

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接