一、运行阶段调试
1、GDB调试
1)显示代码:list 1,100
或list GdbTest.c:1,100
2)设置断点:
break 文件名 行号,b file.c:33
,在文件file.c的33行设置断点
break 行号,b 44
,在当前文件的44行设置断点
break 函数名,b fun1
,在函数的入口设置断点
break break_args if cond,b fun1 if argc > 1
,有cond为true时的断点
3)打印变量:print 文件名/函数名 变量名,p fun1::a1
,如果要打印某个线程的变量,要先切到上下文,见第7点。
4)监控变量:
watch 文件名/函数名 变量名,watch fun1::a1
char *p; watch *p,监控指针p指向的内容,watch p,监控指针p变量本身;
cha a[16]; watch a,监控a的16个数据
5)查看线程信息,info thread
6)查看断点信息,info break
7)切换线程:thread threadId,如thread 2
8)运行:run
,r
9)单步运行:next
,n
10)继续运行:continue
,c
11)指定源码路径:dir /mnt/xxx/yyy/zzz/
,当执行文件和源码不在同一路径时,gdb查看代码会出现找不到文件,指定源码路径可解决;多个源码路径用“:”分隔;清空源码路径命令:dir
12)指定加载失败的so的搜索路径:set solib-search-path /var:/home
,solib-search-path可以指定多个路径。在linux上,路径之间用冒号分隔
13)查看源码路径:show dir
14)查看内存 x/nfu addr
n是要显示的内存单元个数
f是显示方式:x16进制、d 10进制、u 6进制无符号、c 字符格式、f 浮点数格式
u是一种地址的单元长度:b 单字节、h 双字节、w 四字节、g 八字节;
eg.x/128xw 0xffff1234
,从0xffff1234开始以16进制显示128个word
举例:
Reading symbols from /data1/hongmiao/work/Code/MyCode/GDB/a.out...done.
(gdb) l 1,100 #--->显示代码
1 #include <stdio.h>
2 #include <time.h>
3 #include <pthread.h>
4 #include <sys/prctl.h>
5
6 int g_val = 0;
7 void *fun1(void *arg)
8 {
9 prctl(PR_SET_NAME, __func__);
10 int a1 = 0;
11 while(1)
12 {
13 a1++;
14 printf("---%d, %d\n", a1, g_val);
15 sleep(1);
16 }
17 }
18
19 void *fun2(void *arg)
20 {
21 prctl(PR_SET_NAME, __func__);
22 int a2 = 0;
23 while(1)
24 {
25 a2++;
26 g_val++;
27 sleep(5);
28 }
29 }
30
31 int main(int argc, char *argv[])
32 {
33 prctl(PR_SET_NAME, __func__);
34 int m = 0;
35 pthread_t th1;
36 pthread_t th2;
37 pthread_create(&th1, NULL, fun1, NULL);
38 pthread_create(&th2, NULL, fun2, NULL);
39 while(1)
40 {
41 m++;
42 sleep(1);
43 }
44 return 0;
45 }
(gdb) b 10 #--->设置断点
Breakpoint 1 at 0x400654: file GdbTest.c, line 10.
(gdb) r #--->运行程序
Starting program: /data1/hongmiao/work/Code/MyCode/GDB/./GdbTest
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff77fe700 (LWP 2990)]
[Switching to Thread 0x7ffff77fe700 (LWP 2990)]
Breakpoint 1, fun1 (arg=0x0) at GdbTest.c:10
10 int a1 = 0;
(gdb) watch g_val #--->观察变量,继续运行后,变量变化才会在断点停下
Hardware watchpoint 2: g_val
(gdb) c #--->继续运行
Continuing.
[New Thread 0x7ffff6ffd700 (LWP 2991)]
---1, 0
[Switching to Thread 0x7ffff6ffd700 (LWP 2991)]
Hardware watchpoint 2: g_val
Old value = 0
New value = 1
fun2 (arg=0x0) at GdbTest.c:27 # 变量变化,停在g_val修改的位置
27 sleep(5);
(gdb) c #--->继续运行
Continuing.
---2, 1
---3, 1
---4, 1
---5, 1
---6, 1
Hardware watchpoint 2: g_val
Old value = 1
New value = 2
fun2 (arg=0x0) at GdbTest.c:27 # 变量变化,停在g_val修改的位置
27 sleep(5);
(gdb)
2、top、mpstat、vmstat
mpstat 可以显示每个处理器的统计,而 vmstat 显示所有处理器的统计,但可以看到内存。
top - 14:41:26 up 4 days, 5:01, 69 users, load average: 0.36, 1.06, 1.70
Tasks: 509 total, 1 running, 498 sleeping, 10 stopped, 0 zombie
Cpu(s): 0.7%us, 0.6%sy, 0.0%ni, 98.4%id, 0.1%wa, 0.0%hi, 0.3%si, 0.0%st
Mem: 16434584k total, 15672516k used, 762068k free, 682876k buffers
Swap: 4192252k total, 686288k used, 3505964k free, 11550948k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4327 xxx 20 0 73864 23m 17m S 13 0.1 40:48.02 smbd
1994 xxx 20 0 71064 20m 17m D 3 0.1 13:39.96 smbd
3092 xxx 20 0 69580 19m 17m S 2 0.1 37:37.00 smbd
10 root 20 0 0 0 0 S 0 0.0 12:17.32 ksoftirqd/1
1172 root 20 0 386m 1388 1044 S 0 0.0 1:04.45 samba
12911 xxx 20 0 17600 1664 976 R 0 0.0 0:00.02 top
22338 root 20 0 201m 5796 1388 S 0 0.0 5:19.85 Xorg
1 root 20 0 24552 2128 1112 S 0 0.0 0:01.10 init
2 root 20 0 0 0 0 S 0 0.0 0:00.08 kthreadd
3 root 20 0 0 0 0 S 0 0.0 1:38.59 ksoftirqd/0
tomato-pc:~/work/Code/MyCode/valgrind-pmem-3.19$ mpstat
Linux 3.5.0-23-generic (Cpl-AVI-General-74-178) 2022年11月19日 _x86_64_ (12 CPU)
14时38分56秒 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
14时38分56秒 all 2.45 0.00 0.77 0.62 0.00 0.14 0.00 0.00 96.01
tomato-pc:~/work/Code/MyCode/valgrind-pmem-3.19$ vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
1 0 686288 795908 668428 11530436 0 0 55 208 1 3 2 1 96 1
3、strace
Linux下,进程不能直接访问硬件设备。当进程需要访问硬件设备时(读取磁盘文件、接收网络数据等),则必须由用户态切换为内核态,然后通过系统调用来访问硬件设备。strace是跟踪进程执行时的系统调用和所接收的信号(即它跟踪到一个进程产生的系统调用,包括参数、返回值、执行消耗的时间)。strace最简单的用法是执行一个指定的命令(过程中,starce会记录和解析命令进程的所有系统调用及这个进程的所有的信号值),在指定命令结束后立即退出。参考[strace的简单用法]。(https://blog.csdn.net/mijichui2153/article/details/85229307)
参数:
- -c 统计每一系统调用的所执行的时间,次数和出错的次数等.
- -d 输出strace关于标准错误的调试信息.
- -f 跟踪由fork调用所产生的子进程.
- -ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.
- -F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.
- -h 输出简要的帮助信息.
- -i 输出系统调用的入口指针.
- -q 禁止输出关于脱离的消息.
- -r 打印出相对时间关于,每一个系统调用.
- -t 在输出中的每一行前加上时间信息.
- -tt 在输出中的每一行前加上时间信息,微秒级.
- -ttt 微秒级输出.
- -T 显示每一调用所耗的时间.
- -v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.
- -V 输出strace的版本信息.
- -x 以十六进制形式输出非标准字符串
- -xx 所有字符串以十六进制形式输出.
- -a column 设置返回值的输出位置.默认 为40.
- -e expr 指定一个表达式,用来控制如何跟踪.格式:[qualifier=][!]value1[,value2]… qualifier只能是trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的qualifier是 trace.感叹号是否定符号.例如:-eopen等价于 -e trace=open,表示只跟踪open调用.而-etrace!=open 表示跟踪除了open以外的其他调用.有两个特殊的符号 all和none. 注意有些shell使用!来执行历史记录里的命令,所以要使用\.
- -e trace=set 只跟踪指定的系统 调用.例如:-e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.
- -e trace=file 只跟踪有关文件操作的系统调用.
- -e trace=process 只跟踪有关进程控制的系统调用.
- -e trace=network 跟踪与网络有关的所有系统调用.
- -e strace=signal 跟踪所有与系统信号有关的系统调用.
- -e trace=ipc 跟踪所有与进程通讯有关的系统调用.
- -e abbrev=set 设定strace输出的系统调用的结果集.-v 等于abbrev=none.默认为abbrev=all.
- -e raw=set 将指定的系统调用的参数以十六进制显示.
- -e signal=set 指定跟踪的系统信号.默认为all.如 signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.
- -e read=set 输出从指定文件中读出 的数据.例如: -e read=3,5 -e write=set 输出写入到指定文件中的数据.
- -o filename 将strace的输出写入文件filename
- -p pid 跟踪指定的进程pid.
- -s strsize 指定输出的字符串的最大长度.默认为32.文件名一直全部输出.
- -u username 以username的UID和GID执行被跟踪的命令
4、coredump
coredump是指当程序出错而异常中断时,OS会把程序工作的当前状态存储成一个coredunmp文件。
通常情况下coredump文件包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息等。
- ulimit -a 查看core文件生成是否开启
- ulimit -c 0 关闭core文件生成
- ulimit -c 4096 打开core文件生成,大小限制4096blocks,一般1block=512bytes
- ulimit -c unlimited 打开core文件生成且不限制大小
- echo “/data/coredump/core” > /proc/sys/kernel/core_pattern 更改coredump文件的存储位置为/data/coredump/core
- echo “/data/coredump/core.%e.%p.%t” > /proc/sys/kernel/core_pattern 指定内核所生成的coredump文件的文件名,产生的core文件中将带有崩溃的程序名、以及它的进程ID,上面的%e和%p会被替换成程序文件名、进程ID和时间。
- echo 1 > /proc/sys/kernel/core_uses_pid 如果这个文件的内容被配置成1,那么即使core_pattern中没有设置%p,最后生成的core dump文件名仍会加上进程ID。
- gdb ./main ./coredump 使用GDB调试coredump
5、 valgrind
Valgrind是一款用于内存调试、内存泄漏检测以及性能分析、检测线程错误的软件开发工具。主要包括Memcheck、Callgrind、Cachegrind 等工具,每个工具都能完成一项任务调试、检测或分析。可以检测内存泄露、线程违例和Cache 的使用等。在Valgrind下运行的程序运行速度要慢得多,而且使用的内存要多得多。例如,Memcheck工具下的程序是正常情况的两倍多。因此,最好在性能好的机器上使用Valgrind。参考valgrind简介与使用。
例子:
tomato-pc:~/work/Code/MyCode/GDB$ cat valgrindTest.c -n
1 #include <stdio.h>
2
3 int main()
4 {
5 int x = 10;
6 int y = 0;
7 char a[10] = {0};
8 a[10]=1;//栈内存越界
9 char *b = malloc(10);
10 b[10]=1;//栈内存越界
11 memset(b, 0, 11);//栈内存越界
12 while(1);
13 return 0;
14 }
15
tomato-pc:~/work/Code/MyCode/GDB$ valgrind ./valgrindTest
==4743== Memcheck, a memory error detector
==4743== Copyright (C) 2002-2015, and GNU GPL''d, by Julian Seward et al.
==4743== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==4743== Command: ./valgrindTest
==4743==
==4743== Invalid write of size 1
==4743== at 0x400541: main (valgrindTest.c:10)
==4743== Address 0x51f404a is 0 bytes after a block of size 10 alloc''d
==4743== at 0x4C2D027: malloc (vg_replace_malloc.c:299)
==4743== by 0x400534: main (valgrindTest.c:9)
==4743==
==4743== Invalid write of size 1
==4743== at 0x400555: main (valgrindTest.c:11)
==4743== Address 0x51f404a is 0 bytes after a block of size 10 alloc'd
==4743== at 0x4C2D027: malloc (vg_replace_malloc.c:299)
==4743== by 0x400534: main (valgrindTest.c:9)
==4743==
==4743==
==4743== Process terminating with default action of signal 2 (SIGINT)
==4743== at 0x400559: main (valgrindTest.c:12)
==4743==
==4743== HEAP SUMMARY:
==4743== in use at exit: 10 bytes in 1 blocks
==4743== total heap usage: 1 allocs, 0 frees, 10 bytes allocated
==4743==
==4743== LEAK SUMMARY:
==4743== definitely lost: 0 bytes in 0 blocks
==4743== indirectly lost: 0 bytes in 0 blocks
==4743== possibly lost: 0 bytes in 0 blocks
==4743== still reachable: 10 bytes in 1 blocks
==4743== suppressed: 0 bytes in 0 blocks
==4743== Rerun with --leak-check=full to see details of leaked memory
==4743==
==4743== For counts of detected and suppressed errors, rerun with: -v
==4743== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
6、mtrace
mtrace(memory trace),是 GNU Glibc 自带的内存问题检测工具,它可以用来协助定位内存泄露问题。它的实现源码在glibc源码的malloc目录下,其基本设计原理为设计一个函数 void mtrace (),函数对 libc 库中的 malloc/free 等函数的调用进行追踪,由此来检测内存是否存在泄漏的情况。
在使用之前,需要修改我们的源码,用来跟踪内存分配和释放,其中需要使用两个函数
#include <mcheck.h>
/* 开启内存分配跟踪 */
void mtrace(void);
/* 取消内存分配跟踪 */
void muntrace(void);
mtrace() 函数中会为那些和动态内存分配有关的函数(譬如 malloc()、realloc()、memalign() 以及 free())安装 “钩子(hook)” 函数,这些 hook 函数会为我们记录所有有关内存分配和释放的跟踪信息,而 muntrace() 则会卸载相应的 hook 函数。基于这些 hook 函数生成的调试跟踪信息,我们就可以分析是否存在 “内存泄露” 这类问题了。
例子:
tomato-pc:~/work/Code/MyCode/GDB$ export MALLOC_TRACE=./test.log
tomato-pc:~/work/Code/MyCode/GDB$ cat -n mtraceTest.c
1 #include <stdio.h>
2 #include <mcheck.h>
3
4 int main()
5 {
6 mtrace();
7 setenv("MALLOC_TRACE", "/data1/hongmiao/work/Code/MyCode/GDB/mem.txt", 1);//实际没有生效
8 system("export MALLOC_TRACE=./test.log");
9 int x = 10;
10 int y = 0;
11 char a[10] = {0};
12 a[10]=1;
13 char *b = malloc(10);
14 b[10]=1;
15 memset(b, 0, 11);
16 muntrace();
17 //while(1);
18 return 0;
19 }
20
tomato-pc:~/work/Code/MyCode/GDB$ gcc -g -o mtraceTest mtraceTest.c
mtraceTest.c: 在函数‘main’中:
mtraceTest.c:13:12: 警告: 隐式声明与内建函数‘malloc’不兼容 [默认启用]
mtraceTest.c:15:2: 警告: 隐式声明与内建函数‘memset’不兼容 [默认启用]
tomato-pc:~/work/Code/MyCode/GDB$ ./mtraceTest
tomato-pc:~/work/Code/MyCode/GDB$ ls
addr2lineTest addr2lineTest.c a.out core corefile GdbTest.c mtraceTest mtraceTest.c test.log valgrindTest valgrindTest.c
tomato-pc:~/work/Code/MyCode/GDB$ cat test.log
= Start
@ /lib/x86_64-linux-gnu/libc.so.6:[0x7f0dffe04099] + 0x2477460 0x3a
@ /lib/x86_64-linux-gnu/libc.so.6:(tsearch+0xbb)[0x7f0dffeb94bb] + 0x24774b0 0x20
@ ./mtraceTest:[0x4006a3] + 0x24774e0 0xa
= End
tomato-pc:~/work/Code/MyCode/GDB$ addr2line -e ./mtraceTest 0x4006a3
/data1/hongmiao/work/Code/MyCode/GDB/mtraceTest.c:13
tomato-pc:~/work/Code/MyCode/GDB$
解读:
export MALLOC_TRACE=./test.log设置生成日志文件的路径。
./mtraceTest指的是我们执行的程序名字,[0x4006a3] 是第一次调用 malloc 函数机器码中的地址信息,+ 表示申请内存( - 表示释放),0x24774e0 是 malloc 函数申请到的地址信息,0xa 表示的是申请的内存大小。由此分析第一次申请已经释放,第二次申请没有释放,存在内存泄漏的问题。
7、dump_stack函数
要知道某个函数由谁调用,比较困难。可以通过内核提供的接口dump_stack()来跟踪,回溯打印调用栈信息。当内核发生panic时候,也会主动调用该接口,所以我们可以在调试过程中主动调用该接口来进行测试。
8、dladdr函数
获取某个地址的符号信息概要。
9、SP、LR、PC寄存器
FP:帧指针r11
IP:内部暂用寄存器r12
SP:堆栈指针r13,指向栈当前的位置。
LR:连接寄存器r14,保存函数返回的地址,当异常发生时,异常模式的r14用来保存异常返回地址。
PC:程序计数器r15
二、编译阶段调试
1、addr2line根据地址查找代码行
参数:
- -a --addresses:在函数名、文件和行号信息之前,显示地址,以十六进制形式。
- -b --target=:指定目标文件的格式为bfdname。
- -e --exe=:指定需要转换地址的可执行文件名。
- -i --inlines : 如果需要转换的地址是一个内联函数,则输出的信息包括其最近范围内的一个非内联函数的信息。
- -j --section=:给出的地址代表指定section的偏移,而非绝对地址。
- -p --pretty-print:使得该函数的输出信息更加人性化:每一个地址的信息占一行。
- -s --basenames:仅仅显示每个文件名的基址(即不显示文件的具体路径,只显示文件名)。
- -f --functions:在显示文件名、行号输出信息的同时显示函数名信息。
- -C --demangle[=style]:将低级别的符号名解码为用户级别的名字。
- -h --help:输出帮助信息。
- -v --version:输出版本号。
如果没有coredump,可以通过dmesg
查看出错时的内核日志,然后通过addr2line将地址转换成出错程序的位置,如:addr2line -e addr2lineTest 000000000040052d
例子:
tomato@pc:~/work/Code/MyCode/GDB$ cat -n addr2lineTest.c
1 #include <stdio.h>
2
3 int func(int a, int b)
4 {
5 return a / b;
6 }
7
8 int main()
9 {
10 int x = 10;
11 int y = 0;
12 char *p = NULL;
13 memset(p, 0, 4); //空指针错误
14 printf("%d / %d = %d\n", x, y, func(x, y));//除0错误
15 return 0;
16 }
17
tomato@pc:~/work/Code/MyCode/GDB$ gcc -g -o addr2lineTest addr2lineTest.c
addr2lineTest.c: 在函数‘main’中:
addr2lineTest.c:13:4: 警告: 隐式声明与内建函数‘memset’不兼容 [默认启用]
tomato@pc:~/work/Code/MyCode/GDB$ ./addr2lineTest
段错误 (核心已转储)
tomato@pc:~/work/Code/MyCode/GDB$ dmesg | tail -1
[125201.960808] addr2lineTest[2739]: segfault at 0 ip 000000000040052d sp 00007fff0b5d9370 error 6 in addr2lineTest[400000+1000]
tomato@pc:~/work/Code/MyCode/GDB$
tomato@pc:~/work/Code/MyCode/GDB$ addr2line -e addr2lineTest 000000000040052d
/data1/hongmiao/work/Code/MyCode/GDB/addr2lineTest.c:13
tomato@pc:~/work/Code/MyCode/GDB$
2、其他
1)nm:linux下自带的特定文件分析工具,一般用来检查分析二进制文件、库文件、可执行文件中的符号表,返回二进制文件中各段的信息。
2)readelf:一般用于查看ELF格式的文件信息,常见的文件如在Linux上的可执行文件,动态库(.so)或者静态库(.a) 等包含ELF格式的文件。
3)objdump:用来查看编译后目标文件的组成