跳转到内容

Linux 应用程序调试技巧/调试器

来自 Wikibooks,开放世界中的开放书籍

准备工作

[编辑 | 编辑源代码]

总有一天,生产机器上会出现一些难以重现的问题。通常,此类机器难以访问,没有开发环境,并且无法在其上安装任何内容。最多它将具有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 TUI

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.

C++ 支持

[编辑 | 编辑源代码]
预置 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 *

参考文献

[编辑 | 编辑源代码]
华夏公益教科书