调试工具记录

一、运行阶段调试

1、GDB调试

1)显示代码:list 1,100list 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:用来查看编译后目标文件的组成

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值