LINUX 段错误查找记录 -- segfault at fffffffffffffff9 ip 0000003c97e7b81c sp 00007fffad7c0638 error 4 in lib

来源:互联网 发布:淘宝代上活动 编辑:程序博客网 时间:2024/05/17 23:56
119.*.*.45 app 总是段错误, 且不产生core文件(ulimit -c unlimited 设置)

grep segfault /var/log/messages
Oct 31 17:39:40 -45 kernel: *Serve[9909]: segfault at 3946 ip 0000000000003946 sp 00007f8de69a9e18 error 14 in *Server[400000+13000]
Oct 31 18:32:41 r-45 kernel: *Serve[17038]: segfault at 7de6 ip 0000000000007de6 sp 00007f09cacf9128 error 14 in *Server[400000+13000]
Oct 31 18:53:43 r-45 kernel: *Serve[17281]: segfault at 13a4 ip 0000003c0ac0920b sp 00007f1ebdd64bc0 error 4 in ld-2.12.so[3c0ac00000+20000]

dmesg | tail -30
*Info[5879]: segfault at 7d1 ip 0000003c0b0480ac sp 00007fff0ef9d540 error 4 in libc-2.12.so[3c0b000000+18a000]
*Serve[17038]: segfault at 7de6 ip 0000000000007de6 sp 00007f09cacf9128 error 14 in *Server[400000+13000]
*Serve[17281]: segfault at 13a4 ip 0000003c0ac0920b sp 00007f1ebdd64bc0 error 4 in ld-2.12.so[3c0ac00000+20000]

glibc 库版本查看
ldd 
(GNU libc) 2.12  对malloc 是非线程安全版本  至少需要>= 2.3.6

以“*Serve[17038]: segfault at 7de6 ip 0000000000007de6 sp 00007f09cacf9128 error 14 in *Server[400000+13000]”为例
IP is the Instruction Pointer aka Program Counter. SP is the stack pointer. 
The 'error 4' is a low-level error code which is irrelevant for us.


objdump -D | grep 7de6
 407de6:       bb ff ff ff ff          mov    $0xffffffff,%ebx
 17de6:       01 03                   add    %eax,(%rbx)

 400000 - 7de6(ip) = 3F821A

 whith gdb:
 info symbol *3F821A

 errcode 详见linux 内核源码arch/*/mm/fault.c 描述, 
  * Page fault error code bits:
 *
 *   bit 0 == 0: no page found1: protection fault
 *   bit 1 == 0: read access1: write access
 *   bit 2 == 0: kernel-mode access1: user-mode access
 *   bit 3 == 1: use of reserved bit detected
 *   bit 4 == 1: fault was an instruction fetch


address - the location in memory the code is trying to access (it's likely that 10 and 11 are offsets from a pointer we expect to be set to a valid value but which is instead pointing to 0)
ip - instruction pointer, ie. where the code which is trying to do this lives
sp - stack pointer
error - Architecture-specific flags; see arch/*/mm/fault.c for your platform.

Event for a shared lib, the "[3c0ac00000+20000]" part should give a hint where the crashing segment of the lib was mapped in memory. "readelf --segments mylib.so" lists these segments, and then you can calculate the EIP offset into the crashing segment and feed that to addr2line (or view it in "objdump -dgS").


backtrace、backtrace_symbols、backtrace_symbols_fd 可以打印函数调用的堆栈
通过捕捉SIGSEGV信号, 在信号处理函数中调用堆栈函数, 打印函数down挡掉时的堆栈
具体见man 手册


gcc 自带内置的函数可以打印堆栈地址:
__builtin_return_address() 返回一个地址(指针形式)


eg:// get current address
void* p = __builtin_return_address(0);
printf("0x%x\n", p);
// get callee address
p = __builtin_return_address(1); 
// we cannot get more addresses as we don't have any
// information about how many leves of calls we have

libunwind库可以打印详细的堆栈调用, 需下载安装, 再查看man手册使用

sigaction函数sa_sigaction => siginfo_t => si_addr引起错误的内存地址


段错误:
Nov  3 17:03:54 haier-45 kernel: haierUpassServe[22708]: segfault at 3946 ip 0000000000003946 sp 00007f56edf20e18 error 14 in haierUpassServer[400000+13000]
以上信息说明:
开始是系统当前时间
进程名字及pid
segfault at 引起故障的地址
ip 指令的内存地址
sp 堆栈指针地址, 及栈顶指针
err is not an errno nor a signal numbe, but page fault error code
[400000+13000] 对象崩溃时映射的虚拟内存起始地址和大小


addr2line -e app ip


demon[3702]:segfault at b771c488 ip b771c4b3 sp bfc8b1a0 error 7 in libmodule.so[b771c000+1000]
上面的b771c000是libmodule.so的载入地址,所以运行
addr2line -e libmodule.so ip地址-模块载入地址(b771c4b3-b771c000)
此处也可根据程序ip指令地址 - 模块载入地址, 然后反汇编相应的动态库, 在反汇编中查找相应的差值(.so文件中指令地址为相对地址, 满足共享需要)
如果指令为程序中, 可直接反汇编程序, 在反汇编中查找相应指令地址 


或者gdb重新运行程序,break在相应的指令地址




如果关注的backtrace位于一个动态链接库中,那么麻烦一些,因为动态链接库的基地址不是固定的。
程序每次运行加载动态库的地址是不一样的
这个时候,首先要把进程的memory map找来。在Linux下,进程的memory map可以在/proc/<pid>/maps文件中得到。然后在这个文件中找到动态链接库的基地址,然后将backtrace中的地址 – 动态链接库的基地址,得到偏移地址offset address, 最后addr2line -C -f -e <shared library> <offset address>。(此方法,进程一结束
proc目下下相应的pid就不存在了) 调用pmap命令也可 、 /proc/pid/maps


dladdr可在程序运行时得到本程序加载的动态库信息
dl_iterate_phdr功能同上, 但感觉更好用些, 具体见man手册


objdump -DCl ./app 反汇编:
08048544 <main>:
main():
/mnt/hgfs/D/code/vm_proj_saving/myproj/c/segfault.c:6
 8048544: 55                   push   %ebp
 8048545: 89 e5                mov    %esp,%ebp
 8048547: 83 e4 f0             and    $0xfffffff0,%esp
 804854a: 83 ec 20             sub    $0x20,%esp
/mnt/hgfs/D/code/vm_proj_saving/myproj/c/segfault.c:7
 804854d: c7 44 24 1c 00 00 00 movl   $0x0,0x1c(%esp)
 8048554: 00 
/mnt/hgfs/D/code/vm_proj_saving/myproj/c/segfault.c:9
 8048555: b8 44 86 04 08       mov    $0x8048644,%eax
注意没有加$的数表示内存地址, 而不表示立即数(立即数即为数字, 而非变量)

.o 文件所有指令中用到的符号地址都是相对地址, 下一步链接器要修改这些指令,把其中的地址都改成 加载时的内存地址,这些指令才能正确执行。


总结:可以在程序中打印动态库加载地址(dl_iterate_phdr, dladdr)信息, 打印动态库加载地址信息主要是为了算crash发生在动态库中的具体编译地址, 至于段错误信号, 可以通过信号捕捉, 在信号处理函数中打印进程crash时堆栈, 打印堆栈有上边三个函数(__builtin_return_address()、backtrace()、libunwind库需安装,再man libunwind查看使用方法)均可打印, 

0 0