Linux内核调试方法总结之coredump
什么是core dump?
分析core dump是Linux应用程序调试的一种有效方式,core dump又称为“核心转储”,是该进程实际使用的物理内存的“快照”。分析core dump文件可以获取应用程序崩溃时的现场信息,如程序运行时的CPU寄存器值、堆栈指针、栈数据、函数调用栈等信息。
Core dump是Linux基于信号实现的。Linux中信号是一种异步事件处理机制,每种信号都对应有默认的异常处理操作,默认操作包括忽略该信号(Ignore)、暂停进程(Stop)、终止进程(Terminate)、终止并产生core dump(Core)等。
Signal | Value | Action | Comment |
SIGHUP | 1 | Term | Hangup detected on controlling terminal or death of controlling process |
SIGINT | 2 | Term | Interrupt from keyboard |
SIGQUIT | 3 | Core | Quit from keyboard |
SIGILL | 4 | Core | Illegal Instruction |
SIGTRAP | 5 | Core | Trace/breakpoint trap |
SIGABRT | 6 | Core | Abort signal from abort(3) |
SIGIOT | 6 | Core | IOT trap. A synonym for SIGABRT |
SIGEMT | 7 | Term | |
SIGFPE | 8 | Core | Floating point exception |
SIGKILL | 9 | Term | Kill signal, cannot be caught, blocked or ignored. |
SIGBUS | 10,7,10 | Core | Bus error (bad memory access) |
SIGSEGV | 11 | Core | Invalid memory reference |
SIGPIPE | 13 | Term | Broken pipe: write to pipe with no readers |
SIGALRM | 14 | Term | Timer signal from alarm(2) |
SIGTERM | 15 | Term | Termination signal |
SIGUSR1 | 30,10,16 | Term | User-defined signal 1 |
SIGUSR2 | 31,12,17 | Term | User-defined signal 2 |
SIGCHLD | 20,17,18 | Ign | Child stopped or terminated |
SIGCONT | 19,18,25 | Cont | Continue if stopped |
SIGSTOP | 17,19,23 | Stop | Stop process, cannot be caught, blocked or ignored. |
SIGTSTP | 18,20,24 | Stop | Stop typed at terminal |
SIGTTIN | 21,21,26 | Stop | Terminal input for background process |
SIGTTOU | 22,22,27 | Stop | Terminal output for background process |
SIGIO | 23,29,22 | Term | I/O now possible (4.2BSD) |
SIGPOLL | Term | Pollable event (Sys V). Synonym for SIGIO | |
SIGPROF | 27,27,29 | Term | Profiling timer expired |
SIGSYS | 12,31,12 | Core | Bad argument to routine (SVr4) |
SIGURG | 16,23,21 | Ign | Urgent condition on socket (4.2BSD) |
SIGVTALRM | 26,26,28 | Term | Virtual alarm clock (4.2BSD) |
SIGXCPU | 24,24,30 | Core | CPU time limit exceeded (4.2BSD) |
SIGXFSZ | 25,25,31 | Core | File size limit exceeded (4.2BSD) |
SIGSTKFLT | 16 | Term | Stack fault on coprocessor (unused) |
SIGCLD | 18 | Ign | A synonym for SIGCHLD |
SIGPWR | 29,30,19 | Term | Power failure (System V) |
SIGINFO | 29 | A synonym for SIGPWR, on an alpha | |
SIGLOST | 29 | Term | File lock lost (unused), on a sparc |
SIGWINCH | 28,28,20 | Ign | Window resize signal (4.3BSD, Sun) |
SIGUNUSED | 31 | Core | Synonymous with SIGSYS |
什么情况下会产生core dump呢?
以下情况会出现应用程序崩溃导致产生core dump:
- 内存访问越界 (数组越界、字符串无\n结束符、字符串读写越界)
- 多线程程序中使用了线程不安全的函数,如不可重入函数
- 多线程读写的数据未加锁保护(临界区资源需要互斥访问)
- 非法指针(如空指针异常或者非法地址访问)
- 堆栈溢出
怎么获取core dump呢?
Linux提供了一组命令来配置core dump行为:
1. ulimit –c 查看core dump机制是否使能,若为0则默认不产生core dump,可以使用ulimit –c unlimited使能core dump
2. cat /proc/sys/kernel/core_pattern 查看core文件默认保存路径,默认情况下是保存在应用程序当前目录下,但是如果应用程序中调用chdir()函数切换了当前工作目录,则会保存在对应的工作目录
3. echo “/data/xxx/<core_file>” > /proc/sys/kernel/core_pattern 指定core文件保存路径和文件名,其中core_file可以使用以下通配符:
%% 单个%字符
%p 所dump进程的进程ID
%u 所dump进程的实际用户ID
%g 所dump进程的实际组ID
%s 导致本次core dump的信号
%t core dump的时间 (由1970年1月1日计起的秒数)
%h 主机名
%e 程序文件名
4. ulimit –c [size] 指定core文件大小,默认是不限制大小的,如果自定义的话,size值必须大于4,单位是block(1block = 512bytes)
怎么分析core dump?
我们首先编写一个程序,人为地产生core dump并获取core dump文件。
程序如上图,我们通过除零操作产生core dump
编译运行产生了浮点数异常,从而引发core dump (注:编译时必须添加-g参数,表示添加调试信息,这样才可以使用gdb进行调试)
当前目录下产生了core文件,使用file命令查看core文件类型
发现core文件类型为ELF格式,使用readelf查看ELF文件头部信息如下
通过Type字段可以看到,该文件为core文件
前面我们讲到core dump可以查看应用程序崩溃时的现场信息,这里,我们需要gdb命令辅助实现,使用gdb test core(即test可执行文件和core文件)
“Program terminated with signal 8, Arithmetic exception”表示应用程序是因为接收到Linux内核发出的Signal 8信号量而终止执行,Signal 8是SIGFPE,即浮点数异常。同时打印出了出问题的代码行result = a/b。
通过bt –n (backtrace)命令可以显示函数调用栈信息,n表示显示的调用栈层数,不指定则打印完整调用栈。因为test.c调试程序不涉及函数调用,所以我们只能看到main函数的栈信息,如果程序是在main函数的字函数中出错,则可以打印更多的调用栈信息。
通过disassemble命令可以打印出错时的汇编代码片段,其中箭头指向的是出错的指令,即PC寄存器指向的地址,PC寄存器存放的是下一条执行指令。很多人会很困惑,因为通常程序执行的时候,PC寄存器指向的指令是待执行指令,就会怀疑gdb定位到的出错指令的准确性。其实,CPU确实是执行过这一条指令,但是CPU发现这条指令发生的异常,这个时候就会进入异常处理流程,gdb通过回溯调用栈准确地回到这一条指令执行前的状态,所以PC寄存器的值是完全可信的。
可以看到调用了div指令做除法操作,被除数是-0x8(%ebp),指当前栈基址向下偏移8个字节所在内存单元的数值,EBP是栈基址寄存器。同时我们可以看到前面通过movl $0x0, -0x8(%ebp)将0保存到该内存单元,证明被除数为0。
如上所示,gdb默认使用AT&T汇编语言格式打印汇编语句,可以通过set disassembly-flavor intel设置为intel汇编语言格式。
通过list命令可以查看当前指令附近的代码,前提是gdb工具可以找到源代码