在我们日常开发中,经常会遇见程序崩溃退出的情况,一般在linux下我们可以通过生成core文件来进行调试,定位出现异常位置,但是如果我们忘记了生成core文件或者程序在用户机器上运行没有生成core,就无法通过调试core文件定位异常,这时就可以使用dmesg中的信息帮助调试。
ELF(Executable and Linkable Format)是一种通用的二进制文件格式,主要用于在Unix和Linux系统中存储可执行程序、共享库、目标文件等。下面是对ELF文件的详细说明:
文件头(File Header):
程序头表(Program Header Table):
节头表(Section Header Table):
.init(_init)
:init
节通常指的是程序的初始化代码段,它是程序执行的起点,init
节的入口点是程序的起始地址,也就是操作系统将开始执行的地方。这个入口点通常是可执行文件的文件头中指定的地址。段数据(Section Data):
符号表(Symbol Table):
重定位表(Relocation Table):
动态链接信息(Dynamic Linking Information):
字符串表(String Table):
版本信息(Version Information):
其他信息:
测试程序:C++、Qt
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
QWidget* w = nullptr;
// w->show(); // 异常发生在动态库中
int* a = nullptr;
*a = 10; // 异常发生在可执行程序中
}
执行程序并出现崩溃;
使用dmesg命令查看程序异常日志(有时需要使用sudo dmesg);
[44741.877559] untitled4[14899]: segfault at 0 ip 00005598c5d3b8e4 sp 00007ffd7505a2b8 error 6 in untitled4[5598c5d3b000+1000]
[44741.877582] Code: ff ff ff 48 89 ef be 38 00 00 00 5d e9 c5 f9 ff ff 0f 1f 44 00 00 f3 0f 1e fa 48 83 ef 10 eb d6 66 0f 1f 44 00 00 f3 0f 1e fa <c7> 04 25 00 00 00 00 00 00 00 00 0f 0b 66 2e 0f 1f 84 00 00 00 00
这里会用到两类地址;
所以要使用dmesg的地址定位异常位置就需要将内存地址转换为可执行文件中的地址;
由于init
节的入口点是程序的起始地址,所以文件中的地址 = 指令指针地址 - 文件加载进内存首地址 + init节地址
;
可通过nm命令或者 objdump -p命令查看init节地址 ;
00005598c5d3b8e4 - 5598c5d3b000 + 3000 = 38E4
使用addr2line -e 可执行程序 地址 -Cfi
就可如下图所示得到出现异常的函数和所在文件、在文件中的行号。
注意:如果是使用Release模式编译的不包含调试符号则只能看到出现异常的函数,不能具体到文件和行号。
当异常出现在动态库中是dmesg日志信息如下所示;
[47072.125853] untitled4[15938]: segfault at 28 ip 00007f885dca8f7e sp 00007ffe035bc640 error 4 in libQt5Widgets.so.5.12.5[7f885db0e000+61d000]
[47072.125882] Code: 48 89 df be 01 00 00 00 5b 48 8b 40 68 ff e0 90 66 90 66 2e 0f 1f 84 00 00 00 00 00 48 8b 05 b1 ec 6a 00 53 48 89 fb 48 8b 38 <48> 8b 43 28 8b 70 0c 48 8b 07 ff 90 a8 00 00 00 83 f8 04 74 2d 83
由于可执行程序和动态库的堆栈是相互独立的,并不是在同一个堆栈,所以当异常出现在动态库中时dmesg日志信息中的地址是动态库在内存中的地址;
所以文件中的地址 = 指令指针地址 - 文件加载进内存首地址
;
00007f885dca8f7e - 7f885db0e000 = 19AF7E
使用ldd
查看动态库路径;
使用addr2line -e 动态库 地址 -Cfi
就可如下图所示得到出现异常的函数和所在文件、在文件中的行号。
{__/}
(̷ ̷´̷ ̷^̷ ̷`̷)̷◞~❤
| ⫘ |