我感兴趣的是一个信号处理程序,它可以识别导致问题的指令的地址。
我知道siginfo_t
和__builtin_return_address
,似乎都没有工作:
#include <iostream> #include <signal.h> void handler (int, siginfo_t *, void *); int main () { begin: std :: cerr << &&begin << " ~ " << &&before << " ~ " << &&after << "\n"; struct sigaction s; s .sa_flags = SA_SIGINFO; sigemptyset (& s .sa_mask); s .sa_sigaction = handler; sigaction (SIGSEGV, &s, NULL); int * i = NULL; before: *i = 0; after: std :: cout << "End.\n"; } void handler (int, siginfo_t *si, void *) { std :: cerr << "si:" << si -> si_addr << "\n"; std :: cerr << "At: " << __builtin_return_address (0) << "\n"; std :: cerr << "At: " << __builtin_return_address (1) << "\n"; std :: cerr << "At: " << __builtin_return_address (2) << "\n"; std :: cerr << "At: " << __builtin_return_address (3) << "\n"; std :: cerr << "At: " << __builtin_return_address (4) << "\n"; std :: cerr << "At: " << __builtin_return_address (5) << "\n"; }
这个输出如下所示:
0x10978 ~ 0x10a4c ~ 0x10a54 si:0 At: 0xfb945364 At: 0xfb939e64 At: 0x10a40 At: 0x10740 At: 0 At: Segmentation Fault
所以siginfo_t
是NULL,并且__builtin_return_address
是在指定标签之间的某处产生值。
我期待这两个返回&&before
的价值&&before
。 我是否正确使用这些function?
在Linux 2.6.9-89.0.9.Elsmp和SunOS上进行testing。
用SA_SIGINFO
(声明为void *
的一个)安装的处理程序的第三个参数是指向ucontext_t
结构的指针。 这个结构的内容是体系结构和操作系统特定的,不是任何标准的一部分,但是它们包含了你所需要的信息。 下面是一个适合使用它的程序版本(特定于Linux / x86-64;对于每个感兴趣的体系结构和操作系统,您将需要#ifdef
):
#define _GNU_SOURCE 1 #include <iostream> #include <iomanip> #include <signal.h> #include <ucontext.h> using std::cout; static volatile int *causecrash; static void handler(int, siginfo_t *si, void *ptr) { ucontext_t *uc = (ucontext_t *)ptr; cout << "si:" << si->si_addr << '\n'; cout << "ip:" << std::hex << uc->uc_mcontext.gregs[REG_RIP] << '\n'; } int main() { begin: cout.setf(std::ios::unitbuf); cout << &&begin << " ~ " << &&before << " ~ " << &&after << '\n'; struct sigaction s; s.sa_flags = SA_SIGINFO|SA_RESETHAND; s.sa_sigaction = handler; sigemptyset(&s.sa_mask); sigaction(SIGSEGV, &s, 0); before: *causecrash = 0; after: cout << "End.\n"; }
顺便说一句,海湾合作委员会有这样一个恶劣的习惯,移动标签的地址是采取,但没有用于控制转移操作(据它可以告诉)。 比较:
$ g++ -O0 -W -Wall test.cc && ./a.out 0x400a30 ~ 0x400acd ~ 0x400ada si:0 ip:400ad4 Segmentation fault $ g++ -O2 -W -Wall test.cc && ./a.out 0x4009f0 ~ 0x4009f0 ~ 0x4009f0 si:0 ip:400ab4 Segmentation fault
看看所有的标签如何在优化版本的相同地址? 这将绊倒任何企图说,通过调整个人电脑从故障中恢复。 IIRC有办法使GCC不这样做,但我不知道它是什么,也无法在手册中找到它。
siginfo_t
不会工作,因为它包含被访问的内存地址,而不是指令的地址。
现在, __builtin_return_address
很有意思。 在我的机器上,它返回一些废话:
0x40089f ~ 0x400935 ~ 0x40093f si:0 At: 0x7fe22916fc20 At: 0x7fe22915ad8e
我不知道为什么。 但后来我检查了核心转储:
(gdb) bt #0 0x00000000004009ff in handler(int, siginfo*, void*) () #1 <signal handler called> #2 0x0000000000400939 in main ()
正如您所看到的,就像您的情况一样,违规地址位于标签位置之间的某处。 虽然这很容易解释。 只要看看main()的反汇编:
(gdb) disas Dump of assembler code for function main: ... ; the label is here: 0x0000000000400935 <+161>: mov -0x8(%rbp),%rax => 0x0000000000400939 <+165>: movl $0x0,(%rax) 0x000000000040093f <+171>: mov $0x400c32,%esi
带标签的声明由几条指令组成。 第一个将地址加载到RAX寄存器中。 它成功完成,因为它没有任何问题。 这是第二个访问地址和中断。 这就解释了为什么跟踪中的地址与标签的地址有些不同,尽管代码可能与我的例子不同。 这一切都不能解释为什么__builtin_return_address
在我的情况下给废话。