使用 Strace 和 GDB 调试工具的乐趣
编写 UNIX® 系统程序充满乐趣,并且具有教育意义。使用 UNIX strace 工具和 GDB(GNU 项目调试工具),您可以真正地深入研究系统的功能,并了解组成这些功能的各种各样的程序。同时使用这两种工具,能够在查看 UNIX 计算机底层信息 的时候,给您带来更好的体验。
UNIX 家族总是为用户提供了丰富的工具。UNIX 是一个工具财宝箱,有了这些工具,您不仅可以完成具有创造性的工作,还可以在深入研究该操作系统的同时得到教育和娱乐。strace(用来跟踪任何程序的系统调用)和 GDB 调试工具(用来在受控的环境中运行程序的功能齐全的调试工具)是实现这个目标的两个有价值的工具。
UNIX 的设计由大量的函数调用(称为系统调用)组成,其中包括一些简单的任务,如在屏幕上显示字符串来设置任务优先级。所有的 UNIX 程序都是通过调用操作系统提供的这些底层服务来完成它们的任务,使用 strace 工具,您可以清楚地看到这些调用过程及其使用的参数。通过这种方式,您可以操作这些程序,以了解它们与操作系统之间的底层交互。
开始游戏
让我们以一个简单的 UNIX 命令 pwd
作为开始,然后更深入地研究该命令在完成其任务的过程中进行了哪些工作。启动 xterm 以创建一个进行实验的受控环境,然后输入下面的命令:
$ pwd
这个 pwd
命令显示了当前的工作目录。在我的计算机上,当时的输出是:
/home/bill/
一个如此简单的函数掩饰了该命令底层的复杂性(顺便说一下,所有的计算机程序都是这样的)。要真正地了解其复杂性,请使用 strace 工具再次运行 pwd
命令:
$ strace pwd
通过该命令,您可以看到,在显示和列举当前工作目录的过程中,UNIX 计算机执行了相当多的操作(请参见清单 1)。
清单 1:strace pwd 命令的输出
execve("/bin/pwd", ["pwd"], [/* 39 vars */]) = 0
uname({sys="Linux", node="sammy", ...}) = 0
brk(0) = 0x804c000
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4001...
.
.
.
fstat64(3, {st_mode=S_IFREG|0644, st_size=115031, ...}) = 0
old_mmap(NULL, 115031, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40017000
close(3) = 0
open("/lib/tls/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360U\1"..., 1024) = 1024
fstat64(3, {st_mode=S_IFREG|0755, st_size=1547996, ...}) = 0
old_mmap(0x42000000, 1257224, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x42000000
mprotect(0x4212e000, 20232, PROT_NONE) = 0
old_mmap(0x4212e000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x12e000)...
old_mmap(0x42131000, 7944, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,...
close(3) = 0
set_thread_area({entry_number:-1 -> 6, base_addr:0x40016ac0, limit:1048575, seg_32bit...
munmap(0x40017000, 115031) = 0
brk(0) = 0x804c000
brk(0x804d000) = 0x804d000
brk(0) = 0x804d000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=30301680, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40017000
close(3) = 0
brk(0) = 0x804d000
brk(0x804e000) = 0x804e000
getcwd("/home/bill", 4096) = 11
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 6), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4021700...
write(1, "/home/bill\n", 11/home/bill
) = 11
munmap(0x40217000, 4096) = 0
exit_group(0) = ?
UNIX 系统调用的具体细节
关于检索和显示当前工作目录所需的所有系统调用的详细细节,已经超出了本文的讨论范围,但我会介绍如何获取这些信息。清单 1 中的每一行都以类似 C
的格式,清楚地说明了一项系统调用及其参数,这也是 C
程序员希望看到的。Strace 同样以这种方式显示这些调用,不管在实际创建该程序时使用的是何种编程语言。
如果要了解清单 1 中的所有细节信息,对于所有这些系统调用,UNIX 为您提供了大量的文档。清单 1 中“最重要”的函数是 getcwd()
函数,它表示获取当前工作目录。当前的 xterm 显示了 strace pwd
的输出,同时再启动另一个 xterm 并输入下面的命令以查看 UNIX 对该函数的显示:
$ man getcwd
您所看到的应该是 getcwd()
函数完整的清单以及这个重要的 C
函数需要的和返回的参数清单。同样地,您可以输入 man brk
或 man fstat64
等等。通常,UNIX 系统通过文档对这些系统函数进行了详细的说明,如果花些时间仔细地研究它们,您将逐渐地了解到 UNIX 的功能是多么的强大,以及学习这些底层系统细节是多么的容易。在所有的操作系统中,UNIX 最善于帮助您理解其底层的处理过程。
观察 nweb
对于下面几个步骤,您需要使用更庞大且更复杂的程序,而不是像 pwd
这样简单的 UNIX 命令。简单的超文本传输协议 (HTTP) 服务器,如 nweb,是非常适合的。当您在 Internet 上冲浪 的时候,HTTP 服务器侦听浏览器请求,然后通过发送所请求的对象,如 Web 页面和图形文件,以此响应浏览器的请求。
下载并安装 nweb,该软件由 IBM developerWorks 投稿作家 Nigel Griffiths 编写。 (请参阅参考资料部分提供的 Nigel 的文章“nweb: ?"a tiny, safe Web server (static pages only)”(developerWorks,2004 年 6 月)的链接。)
下载 es-nweb.zip 到 $HOME/downloads 目录,然后输入清单 2 中所示的简单命令,以提取、编译并启动该程序:
注意:我假设您需要为 Linux® 工作站编译这个程序。如果实际情况并非如此,那么有关在其他的 UNIX 操作系统上对该程序进行编译的详细信息,请阅读这篇 nweb 文章。
清单 2. 用于提取、编译和启动 nweb 的命令
$ cd src
$ mkdir nweb
$ cd nweb
$ unzip $HOME/downloads/es-nweb.zip
$ gcc -ggdb -O -DLINUX nweb.c -o nweb
$ ./nweb 9090 $HOME/src/nweb &
注意:清单 2 中的 -ggdb
选项和 Nigel 的文章中的内容有些不同,该选项用于告诉 GCC 编译器对该程序进行优化,以便使用 GDB 调试工具对其进行调试,您将在以后用到该调试工具。
接下来,要确认 nweb 服务器已经运行,可以使用清单 3 中所示的 ps
命令来对它进行检查。
清单 3. ps 命令
$ ps
PID TTY TIME CMD
2913 pts/5 00:00:00 bash
4009 pts/5 00:00:00 nweb
4011 pts/5 00:00:00 ps
最后,要确认 nweb 确实正在运行并且状态正常,可以在您的计算机上启动一个 Web 浏览器并在地址栏中输入 http://localhost:9090
。
针对 nweb 使用 strace
现在,让我们来进行一些有趣的工作。启动另一个 xterm,然后使用 strace 来跟踪正在运行的 nweb 服务器。要完成该任务,您必须清楚该程序的进程 ID,并且必须具有适当的权限。您仅仅可以看到一组特定的系统调用,即那些与网络相关的系统调用。输入清单 4 第一行所示的命令作为开始,其中使用了前面显示的 nweb 的进程 ID。您应该看到如下的输出(清单 4 中的第二行)。
清单 4. 开始对 nweb 进行跟踪
$ strace -e trace=network -p 4009
accept(0,
请注意,在调用网络 accept()
函数的过程中停止了跟踪操作。在浏览器中刷新几次 http://localhost:9090
页面,请注意每次刷新该页面时 strace 的显示。这是不是很棒呢?您所看到的是,当 Web 浏览器调用 HTTP 服务器 (nweb
) 时,服务器所进行的底层网络调用。简单地说,nweb
正在接受 来自您的浏览器的调用。
您可以在运行 strace 的具有窗口焦点的 xterm 中按下 Ctrl+C 以停止对网络调用的跟踪。
再来研究 GDB 调试工具
正如您所看到的,strace 可以作为了解用户程序如何通过某些系统调用与操作系统进行交互的一个很好的程序。GDB 调试工具本身也可以附加于一个正在运行的进程,并帮助您进行更深入的研究。
GDB 调试工具非常有用,Internet 上提供了大量的有关该工具的可用信息。通常,调试工具是很有价值的工具,并且任何负责开发和维护计算机系统的人员应该了解如何使用它们。因此,在 nweb 运行于另一个 xterm 会话的同时,按下 Ctrl+C 停止 strace,然后输入清单 5 中所示的命令启动 GDB 调试工具。
清单 5. 启动 GDB 调试工具
$ gdb --quiet
(gdb) attach 4009
Attaching to process 4009
Reading symbols from /home/bill/src/nweb/nweb...done.
Reading symbols from /lib/tls/libc.so.6...done.
Loaded symbols for /lib/tls/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0xffffe410 in ?? ()
(gdb)
-quiet
选项告诉 GDB 调试工具仅显示其提示符,而不要显示所有其他的启动信息。如果需要显示额外的文本信息,可以去掉 -quiet
选项。
attach 4009
命令启动对当前正在运行的 nweb 服务器的调试工作,并且 GDB 调试工具通过读取有关该进程的所有的符号信息来做出同样方式的响应。下一步,使用 info
命令来列举您所研究的程序的相关信息(请参见清单 6)。
清单 6. info 命令列出程序信息
(gdb) info proc
process 4009
cmdline = './nweb'
cwd = '/home/bill/src/nweb'
exe = '/home/bill/src/nweb/nweb'
(gdb)
info
命令(请参见清单 7)的另一个有用的变种是 info functions
,然而,函数的列表可能很长。
清单 7. info functions 命令得到的函数列表
(gdb) info functions
All defined functions: File nweb.c:
void log(int, char *, char *, int);
int main(int, char **);
void web(int, int); File __finite:
int __finite();
.
.
.
(gdb)
因为使用 -ggdb
选项对 nweb 程序进行了编译,所以可执行文件中包含了大量的调试信息,允许该调试工具查看文件中列举的已定义的函数,如清单 7 所示。
list 和 disassemble 命令
有两个重要的 GDB 调试工具命令,它们分别是 list
和 disassemble
。通过使用清单 8 中所示的代码尝试使用这些命令。
清单 8. list 命令
(gdb) list main
121 exit(1);
122 }
123
124
125 main(int argc, char **argv)
126 {
127 int i, port, pid, listenfd, socketfd, hit;
128 size_t length;
129 char *str;
130 static struct sockaddr_in cli_addr; /* static = initialised to zeros */
(gdb)
131 static struct sockaddr_in serv_addr; /* static = initialised to zeros */
132
133 if( argc < 3 || argc > 3 || !strcmp(argv[1], "-?") ) {
134 (void)printf("hint: nweb Port-Number Top-Directory\n\n"
135 "\tnweb is a small and very safe mini web server\n"
136 "\tnweb only servers out file/web pages with extensions named below\n"
137 "\t and only from the named directory or its sub-directories.\n"
138 "\tThere is no fancy features = safe and secure.\n\n"
139 "\tExample: nweb 8181 /home/nwebdir &\n\n"
140 "\tOnly Supports:");
正如您所看到的,list
命令以源文件的形式列出了正在运行的程序,并标注了相应的行号。按下 Return 键(如第 130 行和第 131 行之间所示)以接着上次的列表继续进行列举。现在,尝试使用 disassemble
命令,可以缩写为 disass
(请参见清单 9)。
清单 9. disassemble 命令
(gdb) disass main
Dump of assembler code for function main:
0x08048ba2 <main+0>: push ebp
0x08048ba3 <main+1>: mov ebp,esp
0x08048ba5 <main+3>: push edi
0x08048ba6 <main+4>: push esi
0x08048ba7 <main+5>: push ebx
0x08048ba8 <main+6>: sub esp,0xc
0x08048bab <main+9>: mov ebx,DWORD PTR [ebp+12]
0x08048bae <main+12>: and esp,0xfffffff0
.
.
.
0x08048c01 <main+95>: call 0x8048664 <printf>
0x08048c06 <main+100>: add esp,0x10
0x08048c09 <main+103>: inc esi
0x08048c0a <main+104>: cmp DWORD PTR [ebx+esi],0x0
---Type <return> to continue, or q <return> to quit---
反汇编清单显示了该 main
函数的汇编语言清单。在本示例中,汇编代码指示出运行该代码的计算机使用的是 Intel® Pentium® 处理器。如果该程序运行于不同类型的处理器上,如基于 IBM Power PC® 的计算机,那么您的代码看上去将有很大的区别。
在其运行的过程中进行监视
因为您所监视的是一个正在运行的程序,所以可以设置相应的断点,然后在它响应浏览器请求并向提出请求的浏览器传输 .html 和 .jpg 文件的同时,对该程序进行监视。清单 10 介绍了如何完成该任务。
清单 10. 设置断点
(gdb) break 188
Breakpoint 1 at 0x8048e70: file nweb.c, line 188.
(gdb) commands 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>continue
>end
(gdb) c
Continuing.
此时,GDB 调试工具已设置为在 nweb 服务器接受 浏览器请求处进行中断,该调试工具将仅仅显示相应的请求并继续处理其他的请求,而不会中断正在运行的程序。刷新几次浏览器中的 http://localhost:9090/
页面,可以观察到,GDB 调试工具显示了断点并继续运行。
在刷新浏览器页面的同时,您应该看到如清单 11 所示的断点信息,在 GDB 调试工具 xterm 中滚动输出。与 strace 相同,您可以按下 Ctrl+C 来停止对 nweb 服务器的调试。在停止了跟踪操作之后,您可以输入 quit
命令以退出 GDB 调试工具。
清单 11. GDB 调试工具 xterm 中的断点信息
Breakpoint 1, main (argc=3, argv=0x1) at nweb.c:188
188 if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0)
Breakpoint 1, main (argc=3, argv=0x1) at nweb.c:188
188 if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0)
Breakpoint 1, main (argc=3, argv=0x1) at nweb.c:188
188 if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0)
Breakpoint 1, main (argc=3, argv=0x1) at nweb.c:188
188 if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0)
Program received signal SIGINT, Interrupt.
0xffffe410 in ?? ()
(gdb) quit
The program is running. Quit anyway (and detach it)? (y or n) y
Detaching from program: /home/bill/src/nweb/nweb, process 4009
$
请注意,您正告诉 GDB 调试工具停止对一个仍在内存中活动的程序的调试。即使是在退出了调试工具之后,您还可以刷新浏览器页面,并将看到 nweb 仍在运行。可以输入 kill 4009
命令来停止该程序,或者在您退出会话时,该页面将会消失。
和平常一样,您可以通过其 man 和 info 页面来了解各种各样的工具,如 strace 和 GDB 调试工具。请确保使用 UNIX 为您提供的这些工具!
了解尽可能多的信息
了解关于您所使用的计算机的尽可能多的信息,绝不是件坏事,并且还可以从这个过程中获得乐趣。实际上,UNIX 通过提供各种工具,如 strace 和 GDB 调试工具以及包含在相应的 man 和 info 页面中的大量的信息,鼓励您对系统进行研究和学习。计算机是人类智慧的延伸,并且我们对其了解得越多,它们将变得越有用。
使用 Strace 和 GDB 调试工具的乐趣的更多相关文章
- 在php中使用strace、gdb、tcpdump调试工具
[转] http://www.syyong.com/php/Using-strace-GDB-and-tcpdump-debugging-tools-in-PHP.html 在php中我们最常使用调试 ...
- Linux调试工具strace和gdb常用命令小结
strace和gdb是Linux环境下的两个常用调试工具,这里是个人在使用过程中对这两个工具常用参数的总结,留作日后查看使用. strace调试工具 strace工具用于跟踪进程执行时的系统调用和所接 ...
- 在 php 中使用 strace、gdb、tcpdump 调试工具
转自:http://www.syyong.com/php/Using-strace-GDB-and-tcpdump-debugging-tools-in-PHP.html 在 php 中我们最常使用调 ...
- 使用单进程、strace、gdb调试PHP错误
使用单进程.strace.gdb调试PHP错误 PHP一般是在FPM的呵护下运行的,但是某些情况下进程异常崩溃会导致502.下面是解决思想: 1. 单进程运行: php -d display_erro ...
- gdb调试工具使用方法分享
刚才看了一个CSDN上分享gdb调试工具使用的教程,讲得非常好,推荐到这里: http://blog.csdn.net/liigo/article/details/582231
- Linux环境下的GCC编译器与GDB调试工具介绍
假如现在我们有如下代码需要编译运行和调试.文件名为:test.c #include <stdio.h> int main() { int day, month, year, sum, le ...
- GDB调试工具总结
程序调试的基本思想是“分析现象->假设错误原因->产生新的现象去验证假设”这样一个循环过程,根据现象如何假设错误原因,以及如何设计新的现象去验证假设,需要非常严密的分析和思考.程序中除了一 ...
- Gdb调试工具/ Makfile项目管理
gdb调试工具 gcc -g main.c -o main 常用命令 命令 简写 作用 help h 按模块列 ...
- 【Linux】GDB调试工具
GDB调试工具 Linux中包含一个很强大的调试工具GDB(GNU Debuger),可以用它来调试C和C++程序. 一. GDB的主要功能有: 设置断点,当程序运行到断点处暂停 显示变量的值,可以打 ...
随机推荐
- GDKOI2015
problems http://gdoi.sysu.edu.cn/wp-content/uploads/2015/03/GDKOI-2015-day1.pdf http://gdoi.sysu.edu ...
- JVM基础和调优(一)
最近的项目中,出现了内存和性能的问题,需要优化,所以趁着这个机会,把自己关于java虚拟机的东整理一下,不对的地方,欢迎指出. 数据类型,因为在java的优化的过程中,检测到的数据类型一般比较的基础, ...
- 算法导论(第三版)Exercises4.2(第四章二节)
4.2-1(计算结果) 18 14 62 66 4.2-2(Strassen算法计算矩阵乘法) void multiplyMatrix(int a[], int b[], int n, int r ...
- Codeforce 219 div1
B 4D"部分和"问题,相当于2D部分和的拓展,我是分解成2D部分和做的: f[x1][y1][x2][y2]=true/false 表示 左上(x1,y1) 右下(x2,y2)的 ...
- ShellSort Shell排序
希尔排序(Shell Sort)又称为“缩小增量排序”.是1959年由D.L.Shell提出来的.该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直 ...
- 关于ognl.OgnlException: target is null for setProperty(null的解决方案
在跑struts2的时候有时候会出现上面的错,特别是新手, 这种情况是在struts2高级的POJO访问时候出现的s 警告: Error setting expression 'user.passwo ...
- Unity 对象池的使用
在游戏开发过程中,我们经常会遇到游戏发布后,测试时玩着玩着明显的感觉到有卡顿现象.出现这种现象的有两个原因:一是游戏优化的不够好或者游戏逻辑本身设计的就有问题,二是手机硬件不行.好吧,对于作为程序员的 ...
- c#中关于virtual,override和new的理解
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Cons ...
- mongodb 详解 error:10061 由于目标计算机积极拒绝,无法连接解决方法
mongodb下载地址(32位):下载地址 自己选择版本 建立如下与mongodb并行的两个文件夹data和log. 然后建立mongo.config. 在mongo.config配置文件中输入: # ...
- “文件XXX正由另一进程使用,因此该进程无法访问此文件”
文件xxx正在由另一进城使用,这种问题出现有一种原因: 就是同一个线程重打开文件,但是没有关闭的情况下,再次读取的时候抛出异常. 如下的代码为错误代码: