2. 程序调试
2.1. 进程调试
gdb 程序交互调试
GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。
对于一名Linux下工作的c++程序员,gdb是必不可少的工具;
GDB中的命令固然很多,但我们只需掌握其中十个左右的命令,就大致可以完成日常的基本的程序调试工作。
以下从一个完整的调试过程简单说明最基本的几个命令;
- gdb programmer # 启动gdb
当你完成了第一个程序调试之后,你当然会需要更多的命令:关于gdb常用命令及各种调试方法详见 gdb 调试利器 ;
同时,你需要更高效的调试:常用的调试命令都会有单字符的缩写,使用缩写更方便;同时,直接敲回车表示重复执行上一步命令;这在单步调试时非常有用;
pstack 跟踪栈空间
pstack是一个脚本工具,可显示每个进程的栈跟踪。pstack 命令必须由相应进程的属主或 root 运行。其核心实现就是使用了gdb以及thread apply all bt命令;
语法:
- pstrack <program-pid>
示例:
- pstack 4551
strace 分析系统调用
strace常用来跟踪进程执行时的系统调用和所接收的信号。在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。
完整程序:
strace -o output.txt -T -tt -e trace=all -p 28979
跟踪28979进程的所有系统调用(-e trace=all),并统计系统调用的花费时间,以及开始时间(以可视化的时分秒格式显示),最后将记录结果存在output.txt文件里面。
查看进程正在做什幺(实时输出进程执行系统调用的情况):
- strace -p <process-pid>
关于strace的详细介绍,详见 strace 跟踪进程中的系统调用 ;
2.2. 目标文件分析
nm
nm用来列出目标文件的符号清单。
- nm myProgrammer
这些包含可执行代码的段称为正文段。同样地,数据段包含了不可执行的信息或数据。另一种类型的段,称为 BSS 段,它包含以符号数据开头的块。对于 nm 命令列出的每个符号,它们的值使用十六进制来表示(缺省行为),并且在该符号前面加上了一个表示符号类型的编码字符。
常见的各种编码包括:
- A 表示绝对 (absolute),这意味着不能将该值更改为其他的连接;
- B 表示 BSS 段中的符号;
- C 表示引用未初始化的数据的一般符号。 可以将目标文件中所包含的不同的部分划分为段。段可以包含可执行代码、符号名称、初始数据值和许多其他类型的数据。有关这些类型的数据的详细信息,可以阅读 UNIX 中 nm 的 man 页面,其中按照该命令输出中的字符编码分别对每种类型进行了描述。
在目标文件阶段,即使是一个简单的 Hello World 程序,其中也包含了大量的细节信息。nm 程序可用于列举符号及其类型和值,但是,要更仔细地研究目标文件中这些命名段的内容,需要使用功能更强大的工具。
其中两种功能强大的工具是 objdump 和 readelf 程序。
注解
关于nm工具的参数说明及更多示例详见 nm 目标文件格式分析 ;
objdump
ogjdump工具用来显示二进制文件的信息,就是以一种可阅读的格式让你更多地了解二进制文件可能带有的附加信息。
- objdump -d myprogrammer
每个可执行代码段将在需要特定的事件时执行,这些事件包括库的初始化和该程序本身主入口点。
对于那些着迷于底层编程细节的程序员来说,这是一个功能非常强大的工具,可用于研究编译器和汇编器的输出。细节信息,比如这段代码中所显示的这些信息,可以揭示有关本地处理器本身运行方式的很多内容。对该处理器制造商提供的技术文档进行深入的研究,您可以收集关于一些有价值的信息,通过这些信息可以深入地了解内部的运行机制,因为功能程序提供了清晰的输出。
注解
关于objdump工具的参数说明及更多示例详见 objdump 二进制文件分析 ;
readelf
这个工具和objdump命令提供的功能类似,但是它显示的信息更为具体,并且它不依赖BFD库(BFD库是一个GNU项目,它的目标就是希望通过一种统一的接口来处理不同的目标文件);
- readelf -all a.out
ELF Header 为该文件中所有段入口显示了详细的摘要。在列举出这些 Header 中的内容之前,您可以看到 Header 的具体数目。在研究一个较大的目标文件时,该信息可能非常有用。
除了所有这些段之外,编译器可以将调试信息放入到目标文件中,并且还可以显示这些信息。输入下面的命令,仔细分析编译器的输出(假设您扮演了调试程序的角色):
- readelf --debug-dump a.out | more
调试工具,如 GDB,可以读取这些调试信息,并且当程序在调试器中运行的同时,您可以使用该工具显示更具描述性的标记,而不是对代码进行反汇编时的原始地址值。
注解
关于readelf工具的参数说明及更多示例详见 readelf elf文件格式分析 ;
size 查看程序内存占用
size这个工具用来查看程序运行时各个段的实际内存占用:
- size a.out
file 文件类型查询
这个工具用于查看文件的类型;
比如我们在64位机器上发现了一个32位的库,链接不上,这就有问题了:
- file a.out
也可以查看Core文件是由哪个程序生成:
- file core.22355
strings 查询数据中的文本信息
一个文件中包含二进制数据和文本数据,如果只需要查看其文本信息,使用这个命令就很方便;过滤掉非字符数据,将文本信息输出:
- strings <objfile>
fuser 显示文件使用者
显示所有正在使用着指定的file, file system 或者 sockets的进程信息;
- fuser -m -u redis-server
使用了-m和-u选项,用来查找所有正在使用redis-server的所有进程的PID以及该进程的OWNER;
fuser通常被用在诊断系统的”resource busy”问题。如果你希望kill所有正在使用某一指定的file, file system or sockets的进程的时候,你可以使用-k选项:
- fuser –k /path/to/your/filename
xxd 十六进制显示数据
以十六进制方式显示文件,只显示文本信息:
- xxd a.out
od
通常使用od命令查看特殊格式的文件内容。通过指定该命令的不同选项可以以十进制、八进制、十六进制和ASCII码来显示文件。
参数说明:
-A 指定地址基数,包括:
- d 十进制
- o 八进制(系统默认值)
- x 十六进制
n 不打印位移值 -t 指定数据的显示格式,主要的参数有:
c ASCII字符或反斜杠序列
- d 有符号十进制数
- f 浮点数
- o 八进制(系统默认值为02)
- u 无符号十进制数
- x 十六进制数 除了选项c以外的其他选项后面都可以跟一个十进制数n,指定每个显示值所包含的字节数。
说明:od命令系统默认的显示方式是八进制,这也是该命令的名称由来(Octal Dump)。但这不是最有用的显示方式,用ASCII码和十六进制组合的方式能提供更有价值的信息输出。
以十六进制和字符同时显示:
- od -Ax -tcx4 a.c
以字符方式显示:
- od -c a.c
注:类似命令还有hexdump(十六进制输出)