一些杂七杂八的调试技巧整理
Table of Contents
最后更新:2018-10-04
大多数时候去做调试,无非是几个情况:
1、程序、库遇到 bug
2、遇到靠思考也难以解决的故障
3、学习原理
4、分析漏洞
调试的目的不同,方法就有好多,但是我认为最重要的还是具备对系统调用和网络协议的熟悉,其次就是掌握各种工具。
1. 跟踪函数调用
好多高级语言都能找到可以跟踪函数调用的工具,借助这些工具可以快速摸清函数调用流程。如 PHP 有 Xdebug,只用在 PHP 源码中调用 Xdebug 提供的函数即可:
xdebug_start_trace('输出的日志路径'); .... xdebug_stop_trace();
1.1. strace
对于操作系统层面,strace 命令可以帮我们跟踪程序的系统调用情况。时常会遇到下载的软件没有在文档里说清楚配置文件路径,导致启动时找不到配置文件,借助 strace 命令,我们只用分析程序启动时读取了哪些文件。
1.1.1. 例 1,运行 snort 时遇到加载库的问题
某台 CentOS 上运行 snort 时:
$ sudo snort -v snort: error while loading shared libraries: libdnet.1: cannot open shared object file: No such file or directory
但是系统已经安装了 libdnet:
$ sudo yum list installed libdnet Installed Packages libdnet.x86_64 1.12-13.1.el7 @anaconda
通过 strace,看看发生了什么问题:
$ sudo strace snort -vd execve("/sbin/snort", ["snort", "-vd"], [/* 19 vars */]) = 0 ... open("/usr/lib64/libdnet.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) ... exit_group(127) = ? +++ exited with 127 +++
上面的输出显示,启动时找不到 /usr/lib64/libdnet.1 这个文件,因为系统里已经安装了这个包,所以我只用手动链接下即可解决:
$ sudo ln -s /usr/lib64/libdnet.so.1 /usr/lib64/libdnet.1
要看程序启动时加载了哪些文件,还通过以下表达式就可以捕获所有的 open 调用:
$ sudo strace -e 'trace=open' snort -v
1.1.2. 例 2,Common Lisp 安装 clsql 库遇到无法加载 SQLite3 库
执行 (ql:quickload 'clsql-sqlite3)
后提示如下:
Couldn't load foreign libraries "libsqlite3", "sqlite3"
因为提示的包名和 Fedora 中包命名不同,不知道要加载哪个库,所以用 strace 跟踪下。先另启个 sbcl,然后用 strace 附到进程上去:
strace -p 16845 -o /tmp/install.log
16845 是 sbcl 的进程 PID,将捕获到的系统调用结果输出到 /tmp/install.log中。再执行 (ql:quickload 'clsql-sqlite3)
,提示错误后中断 strace,分析 install.log:
$ fgrep '.so' install.log openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 5 openat(AT_FDCWD, "/lib64/tls/libsqlite3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib64/libsqlite3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib64/tls/libsqlite3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib64/libsqlite3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 5 openat(AT_FDCWD, "/lib64/tls/sqlite3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib64/sqlite3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib64/tls/sqlite3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib64/sqlite3.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 5 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 5 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 5 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 5
可以看到试图从 /lib64、/usr/lib64 等目录中加载 sqlite3.so 和 libsqlite3.so。dnf 的 provides 可以根据文件名查询对应的包名,将这些文件拿去搜下发现 sqlite-devel 提供了/usr/lib64/libsqlite3.so:
$ sudo dnf provides /usr/lib64/libsqlite3.so sqlite-devel-3.26.0-1.fc29.x86_64 : Development tools for the sqlite3 embeddable SQL database : engine 仓库 :@System 匹配来源: 文件名 :/usr/lib64/libsqlite3.so
安装后即可。
1.1.3. 例 3,跟踪子进程的系统调用
加上 -f 参数即可,-ff 可以把每个子进程的系统调用单独输出到一个文件中。
1.2. Ktrace
Ktrace 是 BSD 下类似 Linux 中 strace 的工具,用于跟踪进程的系统调用。ktrace 输出的是二进制文件,需要 kdump 辅助解析。
例 1,解决找不到文件问题
比如我修改 MySQL 的配置文件 /etc/my.cnf 里数据存储路径后,依旧提示找不到某文件。用 ktrace 命令执行:
# ktrace mysqld_safe
然后会在当前目录下生成 ktrace.out 的二进制文件,用 kdump(参数 -f 指定 dump 文件)即可看到调用过程,类似:
22866 sh CALL munmap(0x32be7206000,0xff0) 22866 sh RET munmap 0 22866 sh CALL write(2,0x32c3dc7b410,0x6a) 22866 sh GIO fd 2 wrote 106 bytes "/usr/local/bin/mysqld_safe[956]: cannot create /var/mysql/host.err: No such file or directory " 22866 sh RET write 106/0x6a 22866 sh CALL read(10,0x32c3dc7bc58,0x200) 22866 sh RET read 0 22866 sh CALL close(10) 22866 sh RET close 0
其中 CALL 表示调用某个系统函数,RET 是调用的返回值,NAMI 是访问的文件路径。比如我想找出命令调用操作了哪些外部文件,以及它的返回值:
# kdump | fgrep -A 2 NAMI
类似结果如下:
22866 sh CALL sigprocmask(SIG_BLOCK,0x80000<SIGCHLD>) 22866 sh NAMI "/var/mysql/host.pid" 22866 sh RET stat -1 errno 2 No such file or directory 22866 sh CALL pipe(0x7f7ffffda880) 22866 sh NAMI "/var/mysql/host.err" 22866 sh RET open -1 errno 2 No such file or directory 22866 sh CALL issetugid() 22866 sh NAMI "/usr/share/nls/C/libc.cat"
或者查看系统调用返回失败的:
kdump | fgrep errno -B 2
例 2,寻找配置文件
运行 Nginx 时,想知道加载的哪个目录配置文件:
# ktrace -t n nginx
参数 -t 指定要跟踪的系统调用类型,n 表示跟踪 namei。
# kdump -f ktrace.out | fgrep .conf