你如何阅读segfault内核日志消息
这可能是一个非常简单的问题,我正试图debugging一个应用程序,在kern.log
生成以下segfault错误
kernel: myapp[15514]: segfault at 794ef0 ip 080513b sp 794ef0 error 6 in myapp[8048000+24000]
这是我的问题:
-
是否有任何关于segfault的diff错误数字的文档,在这个例子中是错误6,但我已经看到错误4,5
-
at bf794ef0 ip 0805130b sp bf794ef0 and myapp[8048000+24000]
的信息是什么意思?
到目前为止,我能够用符号进行编译,而当我做一个x 0x8048000+24000
它会返回一个符号,这是否是正确的方法呢? 我的假设到目前为止如下:
- sp =堆栈指针?
- ip =指令指针
- at = ????
- myapp [8048000 + 24000] =符号地址?
当报告指向一个程序,而不是一个共享库
运行addr2line -e myapp 080513b
(并重复给出的其他指令指针值)以查看错误发生的位置。 更好的办法是获得一个debug-instrumented的构build,并在像gdb这样的debugging器下重现问题。
如果它是共享库
在libfoo.so[NNNNNN+YYYY]
部分中, NNNNNN
是加载库的位置。 从指令指针( ip
)中减去这个值,就会得到违规指令的.so
的偏移量。 然后你可以使用objdump -DCgl libfoo.so
并search那个偏移量的指令。 你应该能够很容易地从asm标签中找出它的function。 如果.so
没有优化,你也可以尝试使用addr2line -e libfoo.so <offset>
。
错误意味着什么
以下是这些字段的细目:
-
address
– 代码尝试访问的内存中的位置(很可能是10
和11
是来自指针的偏移量,我们希望将其设置为有效值,但指向0
) -
ip
– 指令指针,即。 正在尝试这样做的代码在哪里生活 -
sp
– 堆栈指针 -
error
– 架构特定的标志; 请参阅arch/*/mm/fault.c
于您的平台的arch/*/mm/fault.c
。
根据我有限的知识,你的假设是正确的。
-
sp
=堆栈指针 -
ip
=指令指针 -
myapp[8048000+24000]
=地址
如果我正在debugging的问题,我会修改代码产生一个核心转储或崩溃时logging一个堆栈回溯 。 您也可以在GDB下运行程序(或附加)。
错误代码只是页面错误的体系结构错误代码,似乎是特定于体系结构的。 它们通常logging在内核源码的arch/*/mm/fault.c
中。 我的Linux/arch/i386/mm/fault.c
对于error_code具有以下定义:
- 位0 == 0表示没有find页面,1表示保护错误
- 位1 == 0表示读取,1表示写入
- 位2 == 0表示内核,1表示用户模式
我的Linux/arch/x86_64/mm/fault.c
增加了以下内容:
- 位3 == 1表示故障是取指令
如果它是共享库
不幸的是, 事后不可能知道dynamic链接器将库放置在内存中的什么位置 。
那么,仍然有可能检索信息,而不是从二进制,但从对象。 但是你需要对象的基地址。 而且这些信息仍然位于link_map结构中的coredump内。
所以首先你要把struct link_map导入到GDB中。 因此,让我们用debugging符号编译一个程序,并将其添加到GDB。
link.c
#include <link.h> toto(){struct link_map * s = 0x400;}
get_baseaddr_from_coredump.sh
#!/bin/bash BINARY=$(which myapplication) IsBinPIE () { readelf -h $1|grep 'Type' |grep "EXEC">/dev/null || return 0 return 1 } Hex2Decimal () { export number="`echo "$1" | sed -e 's:^0[xX]::' | tr '[af]' '[AF]'`" export number=`echo "ibase=16; $number" | bc` } GetBinaryLength () { if [ $# != 1 ]; then echo "Error, no argument provided" fi IsBinPIE $1 || (echo "ET_EXEC file, need a base_address"; exit 0) export totalsize=0 # Get PT_LOAD's size segment out of Program Header Table (ELF format) export sizes="$(readelf -l $1 |grep LOAD |awk '{print $6}'|tr '\n' ' ')" for size in $sizes do Hex2Decimal "$size"; export totalsize=$(expr $number + $totalsize); export totalsize=$(expr $number + $totalsize) done return $totalsize } if [ $# = 1 ]; then echo "Using binary $1" IsBinPIE $1 && (echo "NOT ET_EXEC, need a base_address..."; exit 0) BINARY=$1 fi gcc -g3 -fPIC -shared link.c -o link.so GOTADDR=$(readelf -S $BINARY|grep -E '\.got.plt[ \t]'|awk '{print $4}') echo "First do the following command :" echo file $BINARY echo add-symbol-file ./link.so 0x0 read echo "Now copy/paste the following into your gdb session with attached coredump" cat <<EOF set \$linkmapaddr = *(0x$GOTADDR + 4) set \$mylinkmap = (struct link_map *) \$linkmapaddr while (\$mylinkmap != 0) if (\$mylinkmap->l_addr) printf "add-symbol-file .%s %#.08x\n", \$mylinkmap->l_name, \$mylinkmap->l_addr end set \$mylinkmap = \$mylinkmap->l_next end
它将在一组GDB命令中打印出整个link_map内容。
它本身看起来可能是无用的,但是对于我们所使用的共享对象的base_addr,通过直接在另一个GDB实例中debugging涉及的共享对象,可以从地址中获得更多的信息。 保持第一个gdb有符号的idee。
注:脚本是相当不完整的,我怀疑你可以添加到第二个参数添加符号文件打印与此值的总和:
readelf -S $SO_PATH|grep -E '\.text[ \t]'|awk '{print $5}'
其中$ SO_PATH是添加符号文件的第一个参数
希望能帮助到你