段错误调试
Linux程序在运行时出现段错误,对于程序员来说是非常头疼的。这里列举了几种能够快速定位到段错误的方法。
一 .printf大法输出日志
1 .在应用程序中使用printf,直接打印出当前数据的值,观测值是否正确
2.将日志打印到文件中,且增加打印级别,可在编译或者运行程序时根据需要选择打印级别。该方法需要实现自己的my_printf函数
3.查看打印日志文件的方法: taif -f /path/to/log
二.GDB及远程GDB
本地调试使用gdb,远程调试使用gdbserver。
1.在编译时,需要选择-g参数,以使用gdb
2.在本地运行程序,可直接使用gdb运行程序,常用命令如下:
r #运行程序
b file:line #file文件的line行处设置断点,程序运行到这里会暂停
b line #在当前文件的line行设置断点
c #继续运行程序,直到下一个断点,
n #单步运行程序,只执行一行代码
l #查看程序源码,只显示10行
p /x val #查看内存变量val的值,/x表示以16进制显示
bt #栈帧回溯,可查看当前栈的调用过程
3.运行在嵌入式设备端的程序,无法直接使用gdb,可使用gdb+gdbserver,
a)首先需要交叉编译gdbserver工具,以在目标板上运行
b)确保主机能够和目标板正常通信
c)在目标板上执行 gdbserver hostIp:port a.out
d)在主机上执行 arm-linux-gdb a.out #注意,此处需要用交叉编译工具链中的gdb执行程序
进入gdb环境后,执行
(gdb) target remote boardIp:port a.out #建立通信
若出现无法读取库文件的警告,使用如下命令进行设置库文件路径
(gdb) set solib-search-path /my/path/to/compile/gcc/libe) 经过以上步骤,可以执行目标板上的程序了,同样在主机上执行
(gdb) c
即可启动程序,其余指令可按照本地调试的方法执行参考链接: https://blog.csdn.net/zhaoxd200808501/article/details/77838933?spm=1001.2014.3001.5506
gdb调试的示例代码如下,core.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char * argv [ ])
{
int *p = NULL;
p = malloc(4);
if (p == NULL)
{
perror("malloc failed");
}
printf("address [0x%p]\r\n", p);
free(p);
free(p); /* 重复释放*/
return 0;
}
编译命令为: gcc core.c -g #gdb加入-g参数表示保留调试信息
gdb启动命令为: gdb ./a.out
该指令的执行结果如下,进入gdb环境后,执行r,可以看到程序发生段错误,可以看到释放了两次,但是无法知道具体发生在哪个函数的哪一行。使用bt回溯栈信息,根据最后一行可知段错误发生在core.c的第17行,对照源码,就能够找到段错误的地方。
三.栈帧回溯
使用backstrace进行栈帧回溯,在 发生段错误后,程序能够把当前的栈帧信息打印出来,便于我们查找错误。需要注意的是, 在编译二进制文件时,可以使用-g参数,保留调试信息,在生成可执行文件时,使用stip去掉调试信息;在编译二进制文件为可执行文件时,使用链接选项-rdynamic参数,用于动态加载动态符号表,在打印栈信息时才能打印出函数名。
在发生段错误后,需要用到命令objdump和addr2line来查找发生错误的地方。常见 的命令有:
objdump -S a.out #反汇编可执行程序
objdump -S main.o #反汇编二进制文件
addr2line -e backtrace 0x400a3e #读取行号
演示demo文件backstrace.c源代码如下:
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
/* Obtain a backtrace and print it to stdout. */
void
print_trace (void)
{
void *array[10];
char **strings;
int size, i;
size = backtrace (array, 10);
strings = backtrace_symbols (array, size);
if (strings != NULL)
{
//打印栈帧信息
printf ("Obtained %d stack frames.\n", size);
for (i = 0; i < size; i++)
printf ("%s\n", strings[i]);
}
free (strings);
}
void sigHandler(int sig, siginfo_t *info, void *arg)
{
print_trace ();
exit(0);
}
int main (void)
{
struct sigaction sa={0};
//用于注册段错误回调
sa.sa_handler = sigHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK;
sigaction(11, &sa, NULL);
*((int*)0x0) = 123456;
while(1){
sleep(1);
}
return 0;
}
对于以上文件,我们需要编译为可执行文件。编译命令如下:
gcc backstrace.c -c -g #输出二进制文件backstrace.o
gcc backstrace.o -rdynamic #使用多台加载符号表的方式输出可执行文件a.out
stip a.out #缩减调试信息
· 执行结果如下图所示。
根据以上信息,我们可以看到,段错误发生在main函数中。在main函数入口偏移0x7b的位置处。现在我们使用objdump来找到这一行代码。使用命令objdump -S backstrace.o可查看反汇编代码,main函数的反汇编代码如下:
main函数的入口地址为0xde,根据前面的错误偏移地址为0x7b,将入口地址加上错误偏移地址,0xde+0x7b = 0x159,正好对应发生段错误的位置:*((int*)0x0) = 123456;
四.coredump分析
开启coredump后,应用程序在发生段错误后,会生成coredump文件,我们可以根据coredump进行分析,找出错误的地方。步骤如下:
1.打开coredump
ulimit -c 0 ## 不产生core文件. 如果结果为0,说明当程序崩溃时,系统并不能生成coredump。
ulimit -c 1024 ## 设置core文件最大为1024k
ulimit -c unlimited ## 不限制core文件大小
2.设置coredump生成路径
# 例如:将所有的core文件生成到/corefile目录下,文件名的格式为core-命令名-pid-时间戳.
echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
3.生成coredump文件后,使用gdb调试coredump文件,命令
gdb exe-file core-file
进入gdb环境后,使用bt命令,查看栈帧
(gdb) bt
栈帧回溯可参考gdb调试小节。
网络程序调试
对应网络应用程序,可以使用网络抓包工具抓包进行分析查看网络数据通信情况。嵌入式环境下,没有可视化的界面进行抓包,可使用命令行抓包工具tcpdump进行抓包,然后将抓包文件放到windows下分析。步骤如下:
1.交叉编译tcpdump工具,将生成的库文件和可执行文件拷贝到目标板中
2.在目标板执行
tcpdump -n -i eth0 -w /path/to/test.cap
该命令表示抓取eth0网口的网络数据并保存到test.cap文件中
tcpdump更多命令参考
https://blog.csdn.net/ljbcharles/article/details/122256796
3.将生成的test.cap文件,拷贝到windows中,可用wireshark进行分析,能够更详细的展示数据帧的信息备注:熟悉网络通信协议对于调试网络应用程序很有帮助。