linux必先利其器篇之--GDB详解

gdb调试完整示例(教程):ncurses

我对在网上找到显示命令而不显示其输出的“ gdb示例”感到有些沮丧。 gdb是GNU调试器,Linux上的标准调试器。在观看《给我15分钟》链接: link.时,我被提醒缺乏示例输出,我将改变Greg Law在CppCon 2015上对GDB演讲的看法,所幸其中包括输出! 15分钟是值得的。

这也启发了我分享一个完整的gdb调试示例,其中包括输出和涉及的每个步骤,包括死胡同。这不是一个特别有趣或陌生的问题,它只是例行的gdb调试会话。但是它涵盖了基础知识,并且可以用作各种教程,请记住,gdb的功能比我在这里使用的要多得多。

由于我正在调试需要root访问权限的工具,因此我将以root用户身份运行以下命令。根据需要替换非root用户和sudo。您也不应该通读所有这些内容:我已经列举了每个步骤,因此您可以浏览它们并找到感兴趣的步骤。

1. The Problem

BPF工具的密件抄送集合有一个对缓存顶部的拉取请求,该请求使用类似顶部的显示来按进程显示页面缓存统计信息。大!但是,当我对其进行测试时,它遇到了段错误:

# ./cachetop.py
Segmentation fault

请注意,它显示的是“分段故障”,而不是“分段故障(核心已转储)”。我想要一个核心转储来调试它。 (核心转储是进程内存的副本–名称来自磁核心内存的时代–可以使用调试器进行调查。)

核心转储分析是一种调试方法,但不是唯一的一种。我可以在gdb中实时运行该程序以检查问题。我还可以使用外部跟踪器来捕获数据并在segfault事件上跟踪堆栈。我们将从核心转储开始。

2. Fixing Core Dumps

我将检查核心转储设置:

# ulimit -c
0
# cat /proc/sys/kernel/core_pattern
core

ulimit -c 显示核心转储最大的创建数,现在被设置成0:关闭核心转储(对当前进程和他的子进程)

/proc/…/core_pattern设置为“ core”,它将在当前目录中删除一个名为“ core”的核心转储文件。暂时可以,但是我将展示如何在全球位置进行设置:

# ulimit -c unlimited
# mkdir /var/cores
# echo "/var/cores/core.%e.%p" > /proc/sys/kernel/core_pattern

您可以进一步自定义该core_pattern;例如,%h表示主机名,%t表示转储时间。这些选项记录在Linux内核源文件的Documentation / sysctl / kernel.txt下。

要使core_pattern永久且在重启后仍然有效,可以通过/etc/sysctl.conf中的“ kernel.core_pattern”进行设置。

再试一次:

# ./cachetop.py
Segmentation fault (core dumped)
# ls -lh /var/cores
total 19M
-rw------- 1 root root 20M Aug  7 22:15 core.python.30520
# file /var/cores/core.python.30520 
/var/cores/core.python.30520: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from 'python ./cachetop.py'

我们有核心转储文件了。

3. Starting GDB

现在,我将使用目标程序位置运行gdb(使用shell替换,“`”,尽管您应该指定完整路径,除非您确定可以使用),并使用核心转储文件:

# gdb `which python` /var/cores/core.python.30520
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/bin/python...(no debugging symbols found)...done.

warning: core file may not match specified executable file.
[New LWP 30520]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

warning: JITed object file architecture unknown is not compatible with target architecture i386:x86-64.
Core was generated by `python ./cachetop.py'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007f0a37aac40d in doupdate () from /lib/x86_64-linux-gnu/libncursesw.so.5

最后两行特别有趣:它告诉我们这是libncursesw库的doupdate()函数中的分段错误。如果这是一个众所周知的问题,那么值得快速进行网络搜索。我快速浏览了一下,但没有找到一个常见原因。

我已经可以猜出libncursesw是干什么用的,但是如果您不熟悉libncursesw,那么在“ / lib”下并以“ .so。*”结尾表示这是一个共享库,其中可能包含手册页,网站,软件包说明等

# dpkg -l | grep libncursesw
ii  libncursesw5:amd64                  6.0+20160213-1ubuntu1                    amd64
     shared libraries for terminal handling (wide character support)

我碰巧正在Ubuntu上调试它,但是Linux发行版对于gdb的使用并不重要。

4. Back Trace

堆栈跟踪显示了我们如何到达故障点,并且通常足以帮助识别常见问题。通常这是我在gdb会话中使用的第一个命令:bt(回溯的缩写):

gdb) bt
#0  0x00007f0a37aac40d in doupdate () from /lib/x86_64-linux-gnu/libncursesw.so.5
#1  0x00007f0a37aa07e6 in wrefresh () from /lib/x86_64-linux-gnu/libncursesw.so.5
#2  0x00007f0a37a99616 in ?? () from /lib/x86_64-linux-gnu/libncursesw.so.5
#3  0x00007f0a37a9a325 in wgetch () from /lib/x86_64-linux-gnu/libncursesw.so.5
#4  0x00007f0a37cc6ec3 in ?? () from /usr/lib/python2.7/lib-dynload/_curses.x86_64-linux-gnu.so
#5  0x00000000004c4d5a in PyEval_EvalFrameEx ()
#6  0x00000000004c2e05 in PyEval_EvalCodeEx ()
#7  0x00000000004def08 in ?? ()
#8  0x00000000004b1153 in PyObject_Call ()
#9  0x00000000004c73ec in PyEval_EvalFrameEx ()
#10 0x00000000004c2e05 in PyEval_EvalCodeEx ()
#11 0x00000000004caf42 in PyEval_EvalFrameEx ()
#12 0x00000000004c2e05 in PyEval_EvalCodeEx ()
#13 0x00000000004c2ba9 in PyEval_EvalCode ()
#14 0x00000000004f20ef in ?? ()
#15 0x00000000004eca72 in PyRun_FileExFlags ()
#16 0x00000000004eb1f1 in PyRun_SimpleFileExFlags ()
#17 0x000000000049e18a in Py_Main ()
#18 0x00007f0a3be10830 in __libc_start_main (main=0x49daf0 <main>, argc=2, argv=0x7ffd33d94838, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, 
    stack_end=0x7ffd33d94828) at ../csu/libc-start.c:291
#19 0x000000000049da19 in _start ()

从下至上阅读,从父母到孩子。 “?”条目是符号转换失败的地方。产生堆栈跟踪的堆栈遍历也可能失败。在这种情况下,您可能会看到一个有效的帧,然后是少量的虚假地址。如果符号或堆栈损坏得太严重而无法理解堆栈跟踪,则通常可以通过以下方法来解决:安装调试信息包(为gdb提供更多符号,并让其执行基于DWARF的堆栈遍历),或重新编译软件从源中获取具有帧指针和调试信息的(-fno-omit-frame-pointer -g)。上面的许多“ ??”可以通过添加python-dbg软件包来修复条目。

这个特定的堆栈看起来不太有用:第5帧到第17帧(在左侧索引)是Python的内部结构,尽管我们尚未看到Python方法。然后第4帧是_curses库,然后在libncursesw中。看起来像wgetch()-> wrefresh()-> doupdate()。仅根据名称,我想会刷新窗口。为什么要进行核心转储?

5. Disassembly

我将从反汇编我们的段错误的函数doupdate()开始:

(gdb) disas doupdate
Dump of assembler code for function doupdate:
   0x00007f0a37aac2e0 <+0>:   push   %r15
   0x00007f0a37aac2e2 <+2>:   push   %r14
   0x00007f0a37aac2e4 <+4>:   push   %r13
   0x00007f0a37aac2e6 <+6>:   push   %r12
   0x00007f0a37aac2e8 <+8>:   push   %rbp
   0x00007f0a37aac2e9 <+9>:   push   %rbx
   0x00007f0a37aac2ea <+10>:  sub    $0xc8,%rsp
[...]
---Type <return> to continue, or q <return> to quit---
[...]
   0x00007f0a37aac3f7 <+279>: cmpb   $0x0,0x21(%rcx)
   0x00007f0a37aac3fb <+283>: je     0x7f0a37aacc3b <doupdate+2395>
   0x00007f0a37aac401 <+289>: mov    0x20cb68(%rip),%rax        # 0x7f0a37cb8f70
   0x00007f0a37aac408 <+296>: mov    (%rax),%rsi
   0x00007f0a37aac40b <+299>: xor    %eax,%eax
=> 0x00007f0a37aac40d <+301>: mov    0x10(%rsi),%rdi
   0x00007f0a37aac411 <+305>: cmpb   $0x0,0x1c(%rdi)
   0x00007f0a37aac415 <+309>: jne    0x7f0a37aac6f7 <doupdate+1047>
   0x00007f0a37aac41b <+315>: movswl 0x4(%rcx),%ecx
   0x00007f0a37aac41f <+319>: movswl 0x74(%rdx),%edi
   0x00007f0a37aac423 <+323>: mov    %rax,0x40(%rsp)
[...]

输出被截断。 (我也可以只键入“ disas”,并且默认情况下为doupdate。)

箭头“ =>”指向我们的段错误地址,它正在执行mov 0x10(%rsi),%r​​di:从%rsi寄存器中指向的内存加上偏移量0x10的移动到%rdi寄存器。接下来,我将检查寄存器的状态。

6. Check Registers

使用i r打印寄存器状态(short for info registers):

(gdb) i r
rax            0x0  0
rbx            0x1993060    26816608
rcx            0x19902a0    26804896
rdx            0x19ce7d0    27060176
rsi            0x0  0
rdi            0x19ce7d0    27060176
rbp            0x7f0a3848eb10   0x7f0a3848eb10 <SP>
rsp            0x7ffd33d93c00   0x7ffd33d93c00
r8             0x7f0a37cb93e0   139681862489056
r9             0x0  0
r10            0x8  8
r11            0x202    514
r12            0x0  0
r13            0x0  0
r14            0x7f0a3848eb10   139681870703376
r15            0x19ce7d0    27060176
rip            0x7f0a37aac40d   0x7f0a37aac40d <doupdate+301>
eflags         0x10246  [ PF ZF IF RF ]
cs             0x33 51
ss             0x2b 43
ds             0x0  0
es             0x0  0
fs             0x0  0
gs             0x0  0

好吧,%rsi为零。这是我们的问题!零不太可能是有效地址,这种类型的段错误是常见的软件错误:取消引用未初始化或NULL指针。

7. Memory Mappings

您可以使用i proc m再次检查零是否有效(short for info proc mappings):

(gdb) i proc m
Mapped address spaces:

      Start Addr           End Addr       Size     Offset objfile
        0x400000           0x6e7000   0x2e7000        0x0 /usr/bin/python2.7
        0x8e6000           0x8e8000     0x2000   0x2e6000 /usr/bin/python2.7
        0x8e8000           0x95f000    0x77000   0x2e8000 /usr/bin/python2.7
  0x7f0a37a8b000     0x7f0a37ab8000    0x2d000        0x0 /lib/x86_64-linux-gnu/libncursesw.so.5.9
  0x7f0a37ab8000     0x7f0a37cb8000   0x200000    0x2d000 /lib/x86_64-linux-gnu/libncursesw.so.5.9
  0x7f0a37cb8000     0x7f0a37cb9000     0x1000    0x2d000 /lib/x86_64-linux-gnu/libncursesw.so.5.9
  0x7f0a37cb9000     0x7f0a37cba000     0x1000    0x2e000 /lib/x86_64-linux-gnu/libncursesw.so.5.9
  0x7f0a37cba000     0x7f0a37ccd000    0x13000        0x0 /usr/lib/python2.7/lib-dynload/_curses.x86_64-linux-gnu.so
  0x7f0a37ccd000     0x7f0a37ecc000   0x1ff000    0x13000 /usr/lib/python2.7/lib-dynload/_curses.x86_64-linux-gnu.so
  0x7f0a37ecc000     0x7f0a37ecd000     0x1000    0x12000 /usr/lib/python2.7/lib-dynload/_curses.x86_64-linux-gnu.so
  0x7f0a37ecd000     0x7f0a37ecf000     0x2000    0x13000 /usr/lib/python2.7/lib-dynload/_curses.x86_64-linux-gnu.so
  0x7f0a38050000     0x7f0a38066000    0x16000        0x0 /lib/x86_64-linux-gnu/libgcc_s.so.1
  0x7f0a38066000     0x7f0a38265000   0x1ff000    0x16000 /lib/x86_64-linux-gnu/libgcc_s.so.1
  0x7f0a38265000     0x7f0a38266000     0x1000    0x15000 /lib/x86_64-linux-gnu/libgcc_s.so.1
  0x7f0a38266000     0x7f0a3828b000    0x25000        0x0 /lib/x86_64-linux-gnu/libtinfo.so.5.9
  0x7f0a3828b000     0x7f0a3848a000   0x1ff000    0x25000 /lib/x86_64-linux-gnu/libtinfo.so.5.9
[...]

第一个有效的虚拟地址是0x400000。低于该值的任何内容(如果被引用)将触发分段错误。

此时,有几种不同的方法可以进一步挖掘。我将从一些说明步骤开始。

8. Breakpoints

Back to the disassembly:

   0x00007f0a37aac401 <+289>:   mov    0x20cb68(%rip),%rax        # 0x7f0a37cb8f70
   0x00007f0a37aac408 <+296>:   mov    (%rax),%rsi
   0x00007f0a37aac40b <+299>:   xor    %eax,%eax
=> 0x00007f0a37aac40d <+301>:   mov    0x10(%rsi),%rdi

阅读这四条指令:似乎是从栈中将某些东西拉到%rax中,然后将%rax引用到%rsi中,将%eax设置为零(xor是一种优化,而不是移动$ 0),然后尽管我们知道%rsi为零,但我们用偏移量取消了对%rsi的引用。此序列用于遍历数据结构。也许%rax很有趣,但是先前的指令已将其设置为零,因此我们无法在核心转储寄存器状态下看到它。

我可以在doupdate + 289上设置一个断点,然后单步执行每条指令以了解如何设置和更改寄存器。首先,我需要启动gdb以便我们实时执行程序:

# gdb `which python`
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86\_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/bin/python...(no debugging symbols found)...done.
(gdb) b *doupdate + 289
No symbol table is loaded.  Use the "file" command.

哎呀。我想显示此错误,以解释为什么我们通常以main上的断点开始,然后在该点上加载符号,然后设置感兴趣的实际断点。我将直接进入doupdate函数条目,运行问题,然后在碰到函数时设置偏移断点:

(gdb) b doupdate
Function "doupdate" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (doupdate) pending.
(gdb) r cachetop.py
Starting program: /usr/bin/python cachetop.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
warning: JITed object file architecture unknown is not compatible with target architecture i386:x86-64.

Breakpoint 1, 0x00007ffff34ad2e0 in doupdate () from /lib/x86_64-linux-gnu/libncursesw.so.5
(gdb) b *doupdate + 289
Breakpoint 2 at 0x7ffff34ad401
(gdb) c
Continuing.

Breakpoint 2, 0x00007ffff34ad401 in doupdate () from /lib/x86_64-linux-gnu/libncursesw.so.5

我们到了断点。

如果您以前没有做过,r(运行)命令将使用将传递给我们先前在命令行(python)上指定的gdb目标的参数。因此,这最终运行了“ python cachetop.py”。

原文链接地址:http://www.brendangregg.com/blog/2016-08-09/gdb-example-ncurses.html

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值