关于linux:Linux-后台开发常用调试工具

35次阅读

共计 7997 个字符,预计需要花费 20 分钟才能阅读完成。

起源:李海彬
https://urlify.cn/VF7V7v

01 总览

编译阶段

  • nm                 获取二进制文件蕴含的符号信息
  • strings           获取二进制文件蕴含的字符串常量
  • strip               去除二进制文件蕴含的符号
  • readelf           显示指标文件详细信息
  • objdump         尽可能反汇编出源代码
  • addr2line        依据地址查找代码行

运行阶段

  • gdb                弱小的调试工具
  • ldd                 显示程序须要应用的动静库和理论应用的动静库
  • strace            跟踪程序以后的零碎调用
  • ltrace             跟踪程序以后的库函数
  • time               查看程序执行工夫、用户态工夫、内核态工夫
  • gprof              显示用户态各函数执行工夫
  • valgrind          查看内存谬误
  • mtrace           查看内存谬误

其余

  • proc 文件系统
  • 系统日志

02 编译阶段

nm(获取二进制文件外面蕴含的符号)

符号:函数、变量

参数:

  • -C           把 C ++ 函数签名转为可读模式
  • -A           列出符号名的时候同时显示来自于哪个文件。
  • -a           列出所有符号(这将会把调试符号也列出来。默认状态下调试符号不会被列出)
  • -l            列出符号在源代码中对应的行号(指定这个参数后,nm 将利用调试信息找出文件名以及符号的行号。对于一个已定义符号,将会找出这个符号定义的行号,对于未定义符号, 显示为空)
  • -n           依据符号的地址来排序(默认是按符号名称的字母程序排序的)
  • -u           只列出未定义符号

strings(获取二进制文件外面的字符串常量)

性能:

获取二进制文件外面的字符串常量

用处:

比拟重要的是查看 KEY 泄露

eg:strings <your_proc> | grep '^.\{16\}$'

查找 <your_proc> 中是否存在一行有 16 个字符的行,并显示进去。

选项:

  • -a 不只是扫描指标文件初始化和装载段, 而是扫描整个文件。
  • -f 在显示字符串之前先显示文件名。
  • -n min-len 打印至多 min-len 字符长的字符串. 默认的是 4。
#strings /lib/tls/libc.so.6 | grep GLIBC
GLIBC_2.0
GLIBC_2.1
GLIBC_2.1.1……

这样就能看到 glibc 反对的版本。

strip(去除二进制文件外面蕴含的符号)

用处:

可执行程序减肥(通常只在曾经调试和测试过的生成模块上,因为不能调试了)

反编译、反跟踪

readelf(显示指标文件详细信息)

nm 程序可用于列举符号及其类型和值,然而,要更认真地钻研指标文件中这些命名段的内容,须要应用性能更弱小的工具。其中两种功能强大的工具是 objdump 和 readelf。

readelf 工具应用来显示一个或多个 ELF 格式文件信息的 GNU 工具。应用不同的参数能够查看 ELF 文件不同的的信息。

readelf <option> <elffile>

  • -a           显示所有 ELF 文件的信息
  • -h           显示 ELF 文件的文件头
  • -l            显示程序头(program-header)和程序段(segment)和段上面的节
  • -S           显示较为具体的节信息(section)
  • -s           显示符号信息,
  • -n           显示标识信息(如果有)
  • -r            显示重定位信息(如果有)
  • -u           显示开展函数信息(如果有)
  • -d           显示动静节信息,个别是动静库的信息

objdump(尽可能反汇编出源代码)objdump –S <exe>

尽可能反汇编出源代码,尤其当编译的时候指定了 - g 参数时,成果比拟显著。

addr2line(依据地址查找代码行)

当某个过程解体时,日志文件(/var/log/messages)中就会给出附加的信息,包含程序终止起因、故障地址,以及蕴含程序状态字(PSW)、通用寄存器和拜访寄存器的简要寄存器转储。

eg:Mar 31 11:34:28 l02 kernel: failing address: 0

如果可执行文件包含调试符号(带 - g 编译的),应用 addr2line,能够确定哪一行代码导致了问题。

eg:addr2line –e exe addr

其实 gdb 也有这个性能,不过 addr2line 的益处是,很多时候,bug 很难重现,咱们手上只有一份 crash log。这样就能够利用 addr2line 找到对应的代码行,很不便。

留神:

  1. 该可执行程序用 - g 编译,使之带调试信息。
  2. 如果 crash 在一个 so 外面,那 addr2line 不能间接给出代码行。

参数:

  • -a     在显示函数名或文件行号前显示地址
  • -b     指定二进制文件格式
  • -C     解析 C ++ 符号为用户级的名称, 可指定解析款式
  • -e     指定二进制文件
  • -f      同时显示函数名称
  • -s     仅显示文件的根本名,而不是残缺门路
  • -i      开展内联函数
  • -j      读取绝对于指定节的偏移而不是相对地址
  • -p     每个地位都在一行显示

03 运行阶段

调试程序的常见步骤:

1、确定运行工夫次要花在用户态还是内核态(比拟土的一个办法:程序临时屏蔽 daemon()调用,hardcode 收到 n 个申请后 exit(0),time 一下程序……)。

2、如果是用户态,则应用 gprof 进行性能剖析。

3、如果是内核态,则应用 strace 进行性能剖析,另外能够应用其余工具(比方 ltrace 等)辅助。

ldd(显示程序须要应用的动静库和理论应用的动静库)

# ldd /bin/lslinux-gate.so.1 => (0xbfffe000)librt.so.1 => /lib/librt.so.1 (0xb7f0a000)libacl.so.1 => /lib/libacl.so.1 (0xb7f04000)libc.so.6 => /lib/libc.so.6 (0xb7dc3000)libpthread.so.0 => /lib/libpthread.so.0 (0xb7dab000)/lib/ld-linux.so.2 (0xb7f1d000)libattr.so.1 => /lib/libattr.so.1 (0xb7da6000)

第一栏:须要用什么库;第二栏:理论用哪个库文件;第三栏:库文件装载地址。

如果短少动静库,就会没有第二栏。

strace(跟踪以后零碎调用)

后果默认输入到 2。

  • -p <pid> attach 到一个过程
  • -c 最初统计各个 system call 的调用状况
  • -T 打印 system call 的调用工夫
  • -t/-tt/-ttt 工夫格局
  • -f/-F 跟踪由 fork/vfork 调用所产生的子过程
  • -o <file>,将 strace 的输入定向到 file 中。

如:strace -f -o ~/<result_file> <your_proc>

  • -e expr 指定一个表达式,用来管制如何跟踪,格局如下:
  • -e open 等价于 -e trace=open,示意只跟踪 open 调用

应用 strace –e open ./prg 来看程序应用了哪些配置文件或日志文件,很不便。

  • -e trace=<set> 只跟踪指定的零碎调用

例如:-e trace=open,close,rean,write 示意只跟踪这四个零碎调用.

  • -e trace=file 只跟踪无关文件操作的零碎调用
  • -e trace=process 只跟踪无关过程管制的零碎调用
  • -e trace=network 跟踪与网络无关的所有零碎调用
  • -e strace=signal 跟踪所有与零碎信号无关的零碎调用
  • -e trace=ipc 跟踪所有与过程通信无关的零碎调用

ltrace(跟踪以后库函数)

参数和 strace 很靠近

time(查看程序执行工夫、用户态工夫、内核态工夫)

# time ps aux | grep 'hi'
1020 21804 0.0 0.0 1888 664 pts/6 S+ 17:46 0:00 grep hi
real 0m0.009s
user 0m0.000s
sys 0m0.004s

留神:

time 只跟踪父过程,所以不能 fork

gprof(显示用户态各函数执行工夫)

gprof 原理:

在编译和链接程序的时候(应用 -pg 编译和链接选项),gcc 在你应用程序的每个函数中都退出了一个名为 mcount(or“_mcount”, or“__mcount”)的函数,也就是说 -pg 编译的应用程序里的每一个函数都会调用 mcount, 而 mcount 会在内存中保留一张函数调用图,并通过函数调用堆栈的模式查找子函数和父函数的地址。这张调用图也保留了所有与函数相干的调用工夫,调用次数等等的所有信息。

应用步骤:

1、应用 -pg 编译和链接应用程序

gcc -pg -o exec exec.c

如果须要库函数调用状况:

gcc -lc_p -gp -o exec exec.c

2、执行应用程序使之生成供 gprof 剖析的数据 gmon.out

3、应用 gprof 程序剖析应用程序生成的数据

gprof exec gmon.out > profile.txt

留神:

程序必须通过失常路径退出(exit()、main 返回),kill 有效。对后盾常驻程序的调试——我的比拟土办法是,屏蔽 daemon()调用,程序 hardcode 收到 n 个申请后 exit(0)。

有时不太准。

只管了用户态工夫耗费,没有管内核态耗费。

gdb core exec(gdb 查看 core 文件)筹备生成 core:

启动程序前,ulimit -c unlimited,设置 core 文件不限度大小。(相同,ulimit -c 0,能够阻止生成 core 文件)

默认在可执行程序的门路,生成的是名字为 core 的文件,新的 core 会笼罩旧的。

设置 core 文件名字:

/proc/sys/kernel/core_uses_pid 能够管制产生的 core 文件的文件名中是否增加 pid 作为扩大,1 为扩大,否则为 0。

proc/sys/kernel/core_pattern 能够设置格式化的 core 文件保留地位或文件名,比方原来文件内容是 core,能够批改为:

echo "/data/core/core-%e-%p-%t" > core_pattern

以下是参数列表:

  • %p – insert pid into filename 增加 pid
  • %u – insert current uid into filename 增加以后 uid
  • %g – insert current gid into filename 增加以后 gid
  • %s – insert signal that caused the coredump into the filename 增加导致产生 core 的信号
  • %t – insert UNIX time that the coredump occurred into filename 增加 core 文件生成时的 unix 工夫
  • %h – insert hostname where the coredump happened into filename 增加主机名
  • %e – insert coredumping executable name into filename 增加命令名

应用 gdb 查看 core:

gdb <program> <core 文件 >

opprofile(查看 CPU 耗在哪)

常用命令

应用 oprofile 进行 cpu 应用状况检测,须要通过初始化、启动检测、导出检测数据、查看检测后果等步骤,以下为罕用的 oprofile 命令。

初始化

  • opcontrol –no-vmlinux : 批示 oprofile 启动检测后,不记录内核模块、内核代码相干统计数据
  • opcontrol –init : 加载 oprofile 模块、oprofile 驱动程序

检测管制

  • opcontrol –start : 批示 oprofile 启动检测
  • opcontrol –dump : 批示将 oprofile 检测到的数据写入文件
  • opcontrol –reset : 清空之前检测的数据记录
  • opcontrol -h : 敞开 oprofile 过程

查看检测后果

  • opreport : 以镜像 (image) 的角度显示检测后果,过程、动静库、内核模块属于镜像领域
  • opreport -l : 以函数的角度显示检测后果
  • opreport -l test : 以函数的角度,针对 test 过程显示检测后果
  • opannotate -s test : 以代码的角度,针对 test 过程显示检测后果
  • opannotate -s /lib64/libc-2.4.so : 以代码的角度,针对 libc-2.4.so 库显示检测后果
linux # opreport
CPU: Core 2, speed 2128.07 MHz (estimated) Counted CPU_CLK_UNHALTED events (Clock cycles when not halted) with a unit mask of 0x00 (Unhalted core cycles) count 100000CPU_CLK_UNHAL
T.........| samples | %| ------------------------ 31645719     87.6453      no-vmlinux 4361113     10.3592      libend.so 7683      0.1367      libpython2.4.so.1.0        7046      0.1253      op_test

valgrind(查看内存谬误)

应用步骤:

1、官网下载并装置 valgrind。

2、- g 编译的程序都能够应用。

官网的示例代码 test.c

#include <stdlib.h>  
void f(void)  
{int* x = malloc(10 * sizeof(int));  
 x[10] = 0; // problem 1: heap block overrun  
} // problem 2: memory leak -- x not freed  
  
int main(void)  
{f();  
 return 0;  
}

编译程序gcc -Wall -g -o test test.c

3、valgrind 启动程序,屏幕输入后果。

valgrind --tool=memcheck --leak-check=full ./test

留神:

valgrind 只能查找堆内存的拜访谬误,对栈上的对象和动态对象没方法。

valgrind 会影响过程性能,据说可能慢 20 倍,所以在性能要求高的状况下,只能应用 mtrace 这种轻量级的工具了(然而 mtrace 只能辨认简略的内存谬误)。

如果程序生成的 core 的堆栈是错乱的,那么基本上是 stackoverflow 了。这种状况,能够通过在编译的时候,加上 –fstack-protector-all-D_FORTIFY_SOURCE=2 来检测。Stack-protector-all 会在每个函数里加上堆栈爱护的代码,并在堆栈上留上指纹。(记录下,没用过)

因为 valgrind 查不了栈和动态对象的内存拜访越界,这类问题,能够通过应用 gcc 的-fmudflap –lmudflap 来检测。(记录下,没用过)

全局变量的类型不统一的问题,当初还找到比拟好的办法,这从另一个方面阐明全局对象不是个好的设计,这给调试带来了麻烦。

mtrace(查看内存谬误)

mtrace 是 glibc 內提供的工具,原理很简略,就是把你程序中 malloc()和 free()的地位全副下來,最初两辆配对,沒有配对到的就是 memory leak。

应用的步骤如下:

1、代码中增加 mtrace()

#include <stdio.h>  
#include <stdlib.h>  
int main(void)  
{  
 int *p;  
 int i;  
#ifdef DEBUG  
 setenv("MALLOC_TRACE", "./memleak.log", 1);  
 mtrace();  
#endif  
 p=(int *)malloc(1000);  
 return 0;  
}

这段代码 malloc 了一个空间,却沒有 free 掉。咱们增加 9 -12 行的 mtrace 调用。

2、编译gcc -g -DDEBUG -o test1 test1.c

3、执行./test1,在目录里会发现./memleak.log

4、应用mtrace <your_proc> memleak.log 查看信息。

# mtrace test1 memleak.log  
- 0x0804a008 Free 3 was never alloc'd 0xb7e31cbe  
- 0x0804a100 Free 4 was never alloc'd 0xb7ec3e3f  
- 0x0804a120 Free 5 was never alloc'd 0xb7ec3e47  
  
Memory not freed:  
-----------------  
Address Size Caller  
0x0804a4a8 0x3e8 at /home/illidanliu/test1.c:14

能够看到 test1.c 没有对应的 free()。

04 其余

proc 文件系统

内核的窗口。

proc 文件系统是一个伪文件系统,它存在内存当中,而不占用外存空间。

用户和应用程序能够通过 proc 失去零碎的信息,并能够扭转内核的某些参数。

proc/ 目录构造(局部):

  • cmdline                 内核命令行
  • cpuinfo                  对于 Cpu 信息
  • devices                 能够用到的设施(块设施 / 字符设施)
  • filesystems           反对的文件系统
  • interrupts               中断的应用
  • ioports                  I/ O 端口的应用
  • kcore                    内核外围映像
  • kmsg                    内核音讯
  • meminfo 内存信息
  • mounts                 加载的文件系统
  • stat                       全面统计状态表
  • swaps                   对换空间的利用状况
  • version                  内核版本
  • uptime                  零碎失常运行工夫
  • net                        网络信息
  • sys                       可写,能够通过它来拜访或批改内核的参数

proc/<pid>/目录构造(局部):

  • cmdline                 命令行参数
  • environ                  环境变量值
  • fd                         一个蕴含所有文件描述符的目录
  • mem                     过程的内存被利用状况
  • stat                       过程状态
  • status                   Process status in human readable form
  • cwd                      当前工作目录的链接
  • exe                       Link to the executable of this process
  • maps                    内存映像
  • statm                    过程内存状态信息
  • root                       链接此过程的 root 目录

系统日志

/var/log/下的日志文件:

  • /var/log/messages     整体零碎信息,其中也蕴含系统启动期间的日志。此外,mail、cron、daemon、kern 和 auth 等内容也记录在 var/log/messages 日志中。
  • /var/log/auth.log           零碎受权信息,包含用户登录和应用的权限机制等。
  • /var/log/boot.log           系统启动时的日志。
  • /var/log/daemon.log      各种零碎后盾守护过程日志信息。
  • /var/log/lastlog          记录所有用户的最近信息。这不是一个 ASCII 文件,因而须要用 lastlog 命令查看内容。
  • /var/log/user.log           记录所有等级用户信息的日志。
  • /var/log/cron                每当 cron 过程开始一个工作时,就会将相干信息记录在这个文件中。
  • /var/log/wtmp 或 utmp    登录信息。
  • /var/log/faillog           用户登录失败信息。此外,谬误登录命令也会记录在本文件中。

正文完
 0