gnu2和gnu3区别
GNU调试器 (gdb)是一种宝贵的工具,可用于在开发程序时检查正在运行的进程并解决问题。
您可以在特定位置设置断点(按函数名称,行号等),启用和禁用这些断点,显示和更改变量值,并执行所有调试器希望执行的所有标准操作。 但是它还具有您可能没有尝试过的许多其他功能。 这里有五个供您尝试。
设置断点是您将要学习的使用GNU调试器的第一步。 程序在达到断点时停止,您可以运行gdb命令对其进行检查或更改变量,然后再允许该程序继续运行。
例如,您可能知道经常调用的函数有时会崩溃,但仅当它获得某个参数值时才会崩溃。 您可以在该函数的开始处设置一个断点并运行程序。 每次遇到断点时都会显示函数参数,如果未提供触发崩溃的参数值,则可以继续操作,直到再次调用该函数为止。 当麻烦的参数触发崩溃时,您可以单步执行代码以查看问题所在。
(
gdb
)
break sometimes_crashes
Breakpoint
1 at 0x40110e:
file prog.c, line
5 .
(
gdb
) run
[ ...
]
Breakpoint
1 , sometimes_crashes
(
f =0x7fffffffd1bc
) at prog.c:
5
5 fprintf
( stderr,
(
gdb
)
continue
Breakpoint
1 , sometimes_crashes
(
f =0x7fffffffd1bc
) at prog.c:
5
5 fprintf
( stderr,
(
gdb
)
continue
为了使此方法更具可重复性,您可以在您感兴趣的特定调用之前计算该函数被调用的次数,并在该断点处设置一个计数器(例如,“ continue 30”以使其在到达下一个29次时忽略它)断点)。
但是,断点真正强大的地方在于它们在运行时评估表达式的能力,这使您可以自动化这种测试。 输入:条件断点。
break
[ LOCATION
]
if CONDITION
(
gdb
)
break sometimes_crashes
if
! f
Breakpoint
1 at 0x401132:
file prog.c, line
5 .
(
gdb
) run
[ ...
]
Breakpoint
1 , sometimes_crashes
(
f =0x0
) at prog.c:
5
5 fprintf
( stderr,
(
gdb
)
有条件断点使您不必让gdb每次调用该函数时都去做,而是让条件断点仅在特定表达式的值为true时才使gdb停止在该位置。 如果执行到达条件断点位置,但表达式的计算结果为false,则执行
GNU调试器中断点的一个甚至更复杂的功能是能够编写对到达断点的响应的脚本。 断点命令使您可以编写GNU调试器命令列表,以在到达断点时运行。
我们可以使用它来解决有时已经出现的bug,并在它提供空指针的情况下无害地从该函数返回,而该臭虫有时会在功能中返回。
我们可以使用silent作为第一行来更好地控制输出。 否则,每次碰到断点时,即使在运行断点命令之前,也会显示堆栈帧。
(
gdb
)
break sometimes_crashes
Breakpoint
1 at 0x401132:
file prog.c, line
5 .
(
gdb
) commands
1
Type commands
for breakpoint
( s
)
1 , one per line.
End with a line saying just
"end" .
> silent
>
if
! f
> frame
>
printf
"Skipping call \n "
>
return
0
>
continue
> end
>
printf
"Continuing \n "
>
continue
> end
(
gdb
) run
Starting program:
/ home
/ twaugh
/ Documents
/ GDB
/ prog
warning: Loadable section
".note.gnu.property" outside of ELF segments
Continuing
Continuing
Continuing
#0 sometimes_crashes (f=0x0) at prog.c:5
5 fprintf
( stderr,
Skipping call
[ Inferior
1
( process
9373
) exited normally
]
(
gdb
)
GNU Debugger内置支持使用x命令以各种格式检查内存,包括八进制,十六进制等。 但是我喜欢并排看到两种格式:左侧为十六进制字节,右侧为相同字节表示的ASCII字符。
当我想逐字节查看文件的内容时,经常使用hexdump -C (hexdump来自util-linux软件包)。 这是gdb的x命令,显示十六进制字节:
(
gdb
) x
/ 33xb mydata
0x404040
< mydata
> : 0x02 0x01 0x00 0x02 0x00 0x00 0x00 0x01
0x404048
< mydata+
8
> : 0x01 0x47 0x00 0x12 0x61 0x74 0x74 0x72
0x404050
< mydata+
16
> : 0x69 0x62 0x75 0x74 0x65 0x73 0x2d 0x63
0x404058
< mydata+
24
> : 0x68 0x61 0x72 0x73 0x65 0x75 0x00 0x05
0x404060
< mydata+
32
> : 0x00
如果您可以教gdb像hexdump一样显示内存怎么办? 您可以并且实际上可以将这种方法用于您喜欢的任何格式。
通过结合使用dump命令将字节存储在文件中,将shell命令在文件上运行hexdump以及define命令结合在一起,我们可以使自己的新hexdump命令使用hexdump显示内存内容。
(
gdb
) define
hexdump
Type commands
for definition of
"hexdump" .
End with a line saying just
"end" .
> dump binary memory
/ tmp
/ dump.bin
$arg0
$arg0 +
$arg1
> shell
hexdump
-C
/ tmp
/ dump.bin
> end
这些命令甚至可以放在〜/ .gdbinit文件中,以永久定义hexdump命令。 它在起作用:
(
gdb
)
hexdump mydata sizeof
( mydata
)
00000000 02 01 00 02 00 00 00 01 01
47 00
12
61
74
74
72
| .........G..attr
|
00000010
69
62
75
74
65
73 2d
63
68
61
72
73
65
75 00 05
| ibutes-charseu..
|
00000020 00
| .
|
00000021
有时您想更多地了解导致崩溃的原因,而源代码还不够。 您想查看在CPU指令级别发生了什么。
使用disassemble命令可以查看实现功能的CPU指令。 但是有时输出可能很难遵循。 通常,我想查看哪些指令对应于函数中源代码的特定部分。 为此,请使用/ s修饰符在反汇编中包含源代码行。
(
gdb
) disassemble
/ s main
Dump of assembler code
for
function main:
prog.c:
11
{
0x0000000000401158
< +
0
> : push
% rbp
0x0000000000401159
< +
1
> : mov
% rsp,
% rbp
0x000000000040115c
< +
4
> : sub
$0 x10,
% rsp
12 int n =
0 ;
0x0000000000401160
< +
8
> : movl
$0 x0,-0x4
(
% rbp
)
13 sometimes_crashes
(
& n
) ;
0x0000000000401167
< +
15
> : lea -0x4
(
% rbp
) ,
% rax
0x000000000040116b
< +
19
> : mov
% rax,
% rdi
0x000000000040116e
< +
22
> : callq 0x401126
< sometimes_crashes
>
[ ...snipped...
]
这与信息寄存器一起查看所有CPU寄存器的当前值以及命令stepi一次执行一条指令之类的命令,可以使您对程序有更详细的了解。
有时,您希望自己可以倒转时间。 想象一下,您已经达到了变量的监视点。 监视点就像一个断点,但不是在程序中的某个位置设置,而是在表达式上设置(使用watch命令)。 每当表达式的值更改时,执行就会停止,调试器将接管控制权。
想象一下您已经达到了这个观察点,并且变量使用的内存已更改值。 事实证明,这可能是由更早发生的事情引起的。 例如,内存已释放,现在正在重新使用。 但是何时,为什么释放了它?
GNU调试器甚至可以解决此问题,因为您可以反向运行程序!
它通过在每个步骤中仔细记录程序的状态来实现此目的,以便可以恢复以前记录的状态,从而产生时间倒流的错觉。
要启用此状态记录,请使用target record-full命令。 然后,您可以使用听起来不太可行的命令,例如:
这是一个实际的反向调试示例:
(
gdb
) b main
Breakpoint
1 at 0x401160:
file prog.c, line
12 .
(
gdb
) r
Starting program:
/ home
/ twaugh
/ Documents
/ GDB
/ prog
[ ...
]
Breakpoint
1 , main
(
) at prog.c:
12
12 int n =
0 ;
(
gdb
) target record-full
(
gdb
) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401154
in sometimes_crashes
(
f =0x0
) at prog.c:
7
7
return
* f;
(
gdb
) reverse-finish
Run back to call of
#0 0x0000000000401154 in sometimes_crashes (f=0x0)
at prog.c:
7
0x0000000000401190
in main
(
) at prog.c:
16
16 sometimes_crashes
(
0
) ;
这些只是GNU调试器可以做的一些有用的事情。 还有更多发现。 您最喜欢gdb的哪个隐藏,鲜为人知或仅是惊人的惊人功能? 请在评论中分享。
gnu2和gnu3区别