Linux 应用程序调试技巧/调试器
总有一天,生产机器上会出现一些难以重现的问题。通常,此类机器难以访问,没有开发环境,并且无法在其上安装任何内容。最多它将具有gdb,但很可能没有。通常,使用最频繁的用户也是第一个发现问题的人,而且通常,使用最频繁的用户也是花钱最多的用户。并且必须对该问题进行根本原因诊断和修复。
除非应用程序已为这一取证收集时刻做好准备,否则无法做太多事情来诊断问题所在。因此,准备工作应从编译开始
- 拥有一个“符号服务器”并确保其可访问。在符号服务器上编译。
- 使用调试符号编译发行版。如果您不想发布它们,请删除它们,但请保留它们。
- 随应用程序一起发布 gdbserver 以进行远程调试。
这些准备工作将允许您
- 调试在任何机器上运行的应用程序,包括没有gdb安装的机器。
- 从任何可以网络访问符号服务器的机器进行调试。
此外,事先考虑如何在机器上进行调试
- 在代码中嵌入断点,在感兴趣的难以到达的位置,然后
- 启动应用程序
- 使用调试器附加到它
- 等待断点被命中
一种轻松访问调试器中正确代码的方法是在自动挂载的文件夹中构建二进制文件,每个构建都在其自己的子文件夹中。您正在调试的机器也应该可以访问相同的自动挂载共享。
- 安装 autofs
- 在 /etc/auto.master 中取消注释该行
/net -hosts
- 在/etc/exports导出文件夹
# Build directory: rw for localhost, read-only for any other host
/home/amelinte/projects/lpt/lpt localhost(rw,sync,no_subtree_check) *(ro,root_squash,no_subtree_check)
- 作为 root:重新启动 autofs 和 nfs 并导出构建共享
/etc/init.d/autofs restart
/etc/init.d/nfs-kernel-server restart
/usr/sbin/exportfs -a
- 导出文件夹:编辑/etc/exports
- 作为 root(RedHat)service autofs start
最后,在构建机器上的自动挂载目录中构建二进制文件(此处构建机器为bear):
cd /net/bear/home/amelinte/projects/lpt/lpt.destructor
make clean
make
注意使用符号信息解析的文件名路径
[0x413464] lpt::stack::call_stack<40ul>::call_stack(bool)+0x52
At /net/bear/home/amelinte/projects/lpt/lpt.destructor/lpt/include/lpt/call_stack.hpp:74
In binaries/bin/stacktest
如果符号已从二进制文件中剥离,则指向gdb包含符号的文件夹,并使用debug-file-directory指令。
要将调试器指向源文件
(gdb) set substitute-path /from/path1 /to/path1
(gdb) set substitute-path /from/path2 /to/path2
- 在应用程序运行的机器上(appmachine):
- 如果 gdbserver 不存在,请将其复制过来。
- 启动应用程序。
- 启动 gdbservergdbserver gdbmachine:2345 --attach program
- 在gdbmachine:
- 在 gdb 提示符下,输入target remote appmachine:2345
有时您可能需要通过 ssh 建立隧道
- 在 gdbmachine 上
- ssh -L 5432:appmachine:2345 user@appmachine
- 在 gdb 提示符下target remote localhost:5432
找出进程的 PID,然后
(gdb) attach 1045
Attaching to process 1045
Reading symbols from /usr/lib64/firefox-3.0.18/firefox...(no debugging symbols found)...done.
Reading symbols from /lib64/libpthread.so.0...(no debugging symbols found)...done.
[Thread debugging using libthread_db enabled]
[New Thread 0x448b4940 (LWP 1063)]
[New Thread 0x428b0940 (LWP 1054)]
....
(gdb) detach
- set detach-on-fork off
- 请参阅 GDB 文档中“all-stop”与“non-stop”模式及其相关设置
在 x86 平台上
#define EMBEDDED_BREAKPOINT asm volatile ("int3;")
或更复杂的
#define EMBEDDED_BREAKPOINT \
asm("0:" \
".pushsection embed-breakpoints;" \
".quad 0b;" \
".popsection;")
这将在难以到达的条件下中断到调试器
if (cpuload > 3.1416 && started > 111*MINS*AGO && whatever-dynamic-condition)
{
EMBEDDED_BREAKPOINT;
}
b bigFoot
commands
bt
continue
end
set logging on
set confirm off
rbreak .
监视点可以在软件中实现,如果 CPU 支持也可以在硬件中实现。通常在英特尔处理器上,有 8 个调试寄存器,其中只有 4 个可以用于硬件断点,这限制了系统范围内的监视点数量。
在硬件监视点上安装一个条件,以便仅停止增加变量值的访问。
watch -location i
set var $prev = i
command $bpnum
if $prev < i
printf "increase prev %d %d \n", $prev, i
set var $prev = i
else
printf "decrease prev %d %d \n", $prev, i
set var $prev = i
continue
end
end
这需要 gdb 7.9 或更高版本,并配置了 python 支持。
(gdb) break funcA if $_caller_is("funcB")
GDB 提供了一个用于代码、反汇编程序和寄存器的文本用户界面。例如
- Ctrl-x 1 将显示代码窗格
- Ctrl-x a 将隐藏 TUI 窗格
没有一个 GUI 接口可以gdb(Qt Creator 因其直观易用而脱颖而出)可以访问所有gdb功能。
curses gdb 提供了改进的 TUI。可在此处找到调试器 GUI 的完整列表 此处。
例如,反向调试是任何 GUI 都无法访问的功能。
(gdb) l
1 /* 1 */ int var;
2 /* 2 */ int main (void) {
3 /* 3 */ int i; for (i=0;i<100;i++)
4 /* 4 */ var=i/10;
5 /* 5 */ return 0; }
(gdb) start
(gdb) record
(gdb) adv 5
main () at history.c:5
5 /* 5 */ return 0; }
(gdb) watch var
Hardware watchpoint 2: var
(gdb) reverse-continue
Continuing.
Hardware watchpoint 2: var
Old value = 9
New value = 8
0x00000000004004c3 in main () at history.c:4
4 /* 4 */ var=i/10;
(gdb) p i
$1 = 90
(gdb) reverse-continue
Continuing.
Hardware watchpoint 2: var
Old value = 8
New value = 7
0x00000000004004c3 in main () at history.c:4
4 /* 4 */ var=i/10;
(gdb) p i
$2 = 80
另请参阅
您可以监视寄存器。请注意,这将强制调试器单步执行被调试程序,并且程序运行速度会非常慢。
(gdb) watch $ebp
需要注意的是,在即将发布的 gdb 版本中,.gdbinit将被替换为gdb-gdb.gdb:
gdb-gdb.gdb ^ ^ ^ | | |---- It's a gdb script. | | If it were Python this would be .py. | | | --------- "-gdb" is a gdb convention, it's the suffix added to a file | for auxiliary support. | E.g., gdb will auto-load libstdc++.so-gdb.py (version elided) | which contains the std c++ pretty-printers. | ------------- This init script is for the program named "gdb". If this were for readelf the script would be named readelf-gdb.gdb.
gdb可能需要一点指导才能使用 C++11 二进制文件。
(gdb) set cp-abi gnu-v3
(gdb) set language c++
(gdb) maintenance demangle _ZNSt16nested_exceptionD2Ev
std::nested_exception::~nested_exception()
最终可以使用 templight 来调试和分析模板。
(gdb) python print(x.type)
SuperClass *
(gdb) python print(x.dynamic_type)
SubClass *