使用gdb调试的时候,人们往往会希望有一些命令能实现自己特殊的需求,但实际上gdb本身并没有提供这样的命令。
对这些用户需求,其实常可以使用gdb的Python API(或其他语言的API)来实现。
首先,Python API的总帮助页在这里:
GDB Python API
其中常用的有关于Breakpoint和Command相关的API:
Break Point API
Command API
下面举几个用户自定义命令的例子。在文章的最后,把上篇博客中的gdb命令脚本翻译成Python脚本来做。
方法:
定义一个类,继承 gdb.Command 类; 然后重写 init 和 invoke 方法。
import gdb
# For Command "step-before-next-call"
class StepBeforeNextCall (gdb.Command):
def __init__ (self):
# "step-before-next-call" is user-defined command's name
super (StepBeforeNextCall, self).__init__ ("step-before-next-call",
gdb.COMMAND_OBSCURE)
def invoke (self, arg, from_tty):
def addr2num(addr):
try:
return int(addr) # Python 3
except:
return long(addr) # Python 2
arch = gdb.selected_frame().architecture()
while True:
current_pc = addr2num(gdb.selected_frame().read_register("pc"))
disa = arch.disassemble(current_pc)[0]
if "call" in disa["asm"]: # or startswith ?
break
SILENT=True
gdb.execute("stepi", to_string=SILENT)
print("step-before-next-call: next instruction is a call.")
print("{}: {}".format(hex(int(disa["addr"])), disa["asm"]))
# Declare user-defined command
StepBeforeNextCall()
方法:
计算当前栈的深度,然后一直step,直到深度变深一层为止。
import gdb
# For Command "step-to-next-call"
class StepToNextCall (gdb.Command):
def __init__ (self):
# "step-to-next-call" is user-defined command's name
super (StepToNextCall, self).__init__ ("step-to-next-call",
gdb.COMMAND_OBSCURE)
def invoke (self, arg, from_tty):
def callstack_depth():
depth = 1
frame = gdb.newest_frame()
while frame is not None:
frame = frame.older()
depth += 1
return depth
start_depth = current_depth = callstack_depth()
# step until we're one step deeper
while current_depth == start_depth:
SILENT=True
gdb.execute("step", to_string=SILENT)
current_depth = callstack_depth()
# display information about the new frame
gdb.execute("frame 0")
# Declare user-defined command
StepToNextCall()
方法:
一直执行 step, 如果新的frame是以"/usr"开头的,那么执行 finish 命令以结束当前frame
import gdb
# For Command "step-no-library"
class StepNoLibrary (gdb.Command):
def __init__ (self):
# "step-no-library" is user-defined command's name
super (StepNoLibrary, self).__init__ ("step-no-library",
gdb.COMMAND_OBSCURE)
def invoke (self, arg, from_tty):
step_msg = gdb.execute("step", to_string=True)
fname = gdb.newest_frame().function().symtab.objfile.filename
if fname.startswith("/usr"):
# inside a library
SILENT=False
gdb.execute("finish", to_string=SILENT)
else:
# inside the application
print(step_msg[:-1])
# Declare user-defined command
StepNoLibrary()
首先,将上述3个自定义命令集成到一个python脚本中,如pycmd.py
import gdb
# For Command "step-before-next-call"
class StepBeforeNextCall (gdb.Command):
def __init__ (self):
# "step-before-next-call" is user-defined command's name
super (StepBeforeNextCall, self).__init__ ("step-before-next-call",
gdb.COMMAND_OBSCURE)
def invoke (self, arg, from_tty):
def addr2num(addr):
try:
return int(addr) # Python 3
except:
return long(addr) # Python 2
arch = gdb.selected_frame().architecture()
while True:
current_pc = addr2num(gdb.selected_frame().read_register("pc"))
disa = arch.disassemble(current_pc)[0]
if "call" in disa["asm"]: # or startswith ?
break
SILENT=True
gdb.execute("stepi", to_string=SILENT)
print("step-before-next-call: next instruction is a call.")
print("{}: {}".format(hex(int(disa["addr"])), disa["asm"]))
# For Command "step-to-next-call"
class StepToNextCall (gdb.Command):
def __init__ (self):
# "step-to-next-call" is user-defined command's name
super (StepToNextCall, self).__init__ ("step-to-next-call",
gdb.COMMAND_OBSCURE)
def invoke (self, arg, from_tty):
def callstack_depth():
depth = 1
frame = gdb.newest_frame()
while frame is not None:
frame = frame.older()
depth += 1
return depth
start_depth = current_depth =callstack_depth()
# step until we're one step deeper
while current_depth == start_depth:
SILENT=True
gdb.execute("step", to_string=SILENT)
current_depth = callstack_depth()
# display information about the new frame
gdb.execute("frame 0")
# For Command "step-no-library"
class StepNoLibrary (gdb.Command):
def __init__ (self):
# "step-no-library" is user-defined command's name
super (StepNoLibrary, self).__init__ ("step-no-library",
gdb.COMMAND_OBSCURE)
def invoke (self, arg, from_tty):
step_msg = gdb.execute("step", to_string=True)
fname = gdb.newest_frame().function().symtab.objfile.filename
if fname.startswith("/usr"):
# inside a library
SILENT=False
gdb.execute("finish", to_string=SILENT)
else:
# inside the application
print(step_msg[:-1])
# The following 3 lines are necessary for declaring user-defined commands.
StepBeforeNextCall()
StepToNextCall()
StepNoLibrary()
在gdb中source这个 pycmd.py 之后,可以使用 help user-defined 命令查看用户自定义的命令:
(gdb) source pycmd.py
(gdb) help user-defined
User-defined commands.
The commands in this class are those defined by the user.
Use the "define" command to define a command.
List of commands:
step-before-next-call -- This command is not documented
step-no-library -- This command is not documented
step-to-next-call -- This command is not documented
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.
接下来,看C语言源文件test2.c:
#include
unsigned int myadd(unsigned int i)
{
int result = 0;
if (i > 0) {
result = i + myadd(i-1);
}
return result;
}
int main()
{
int a = 5;
int sum = 0;
sum = myadd(a);
printf("sum = %d\n", sum);
return 0;
}
build一下:
gcc -g test2.c -o test2.exe
调试过程示例(省略了一些冗余的打印):
(gdb) source pycmd.py
(gdb) file test2.exe
(gdb) b main
Breakpoint 8 at 0x6ae: file test2.c, line 13.
# stop before "printf" library call
(gdb) b 18
Breakpoint 9 at 0x6d1: file test2.c, line 18.
(gdb) r
Breakpoint 8, main () at test2.c:14
14 int a = 5;
(gdb) step-to-next-call
15 int sum = 0;
17 sum = myadd(a);
myadd (i=5) at test2.c:5
5 int result = 0;
#0 myadd (i=5) at test2.c:5
5 int result = 0;
(gdb) step-to-next-call
6 if (i > 0) {
7 result = i + myadd(i-1);
myadd (i=4) at test2.c:5
5 int result = 0;
#0 myadd (i=4) at test2.c:5
5 int result = 0;
(gdb) step-to-next-call
6 if (i > 0) {
7 result = i + myadd(i-1);
myadd (i=3) at test2.c:5
5 int result = 0;
#0 myadd (i=3) at test2.c:5
5 int result = 0;
(gdb) c
Continuing.
Breakpoint 9, main () at test2.c:18
18 printf("sum = %d\n", sum);
(gdb) step-no-library # Don't step into "printf" library call.
sum = 15
20 return 0;
(gdb)
import gdb
class FunctionFinishBreakpoint (gdb.FinishBreakpoint):
def __init__ (self):
gdb.FinishBreakpoint.__init__(self, gdb.newest_frame(),
internal=True)
self.silent = True
def stop(self):
#print("after: {}".format(int(self.return_value)))
return not int(self.return_value)
class FunctionBreakpoint(gdb.Breakpoint):
def __init__ (self, spec):
gdb.Breakpoint.__init__(self, spec)
self.silent = True
def stop (self):
#print("before")
FunctionFinishBreakpoint() # set breakpoint on function return
return False # do not stop at function entry
FunctionBreakpoint("function_name")
首先,C++代码如下:
// test_ret.cpp
// g++ -g test_ret.cpp -o test3.exe
#include
using namespace std;
bool is_even_num(int num)
{
return num % 2 == 0;
}
int main()
{
int data = 6;
int i;
bool result;
for (i=0; i<data; i++) {
result = is_even_num(i);
if (!result) {
cout << i << " ";
}
}
cout << endl;
return 0;
}
该程序将打印 1 3 5
调试的时候,每次 is_even_num() 函数返回false的时候,gdb都会stop. 详情见以下的调试过程,其中的 pycmd2.py 就是上一节中的python代码。
(gdb) file test3.exe
(gdb) source pycmd2.py
Breakpoint 1 at 0x961: file test_ret.cpp, line 6.
(gdb) b main
Breakpoint 2 at 0x976: file test_ret.cpp, line 11.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000000961 in is_even_num(int) at test_ret.cpp:6
2 breakpoint keep y 0x0000000000000976 in main() at test_ret.cpp:11
(gdb) r
Breakpoint 2, main () at test_ret.cpp:11
11 int data = 6;
(gdb) c
Continuing.
(gdb) p i
$1 = 1
(gdb) c
Continuing.
(gdb) p i
$2 = 3
(gdb) c
Continuing.
(gdb) p i
$3 = 5
(gdb) c
Continuing.
1 3 5
[Inferior 1 (process 11476) exited normally]
在前一篇博客中,我们谈到了使用gdb脚本来实现调试的自动化。实际上,也可以通过Python脚本来实现。
下面的Python脚本就是用来调试前一篇博客中的test.c编译出的test.exe的。它的功能与前一篇博客中的gdb脚本基本相同。
为维护文章的完整性,下面先将C代码再贴一遍,然后再贴Python的gdb脚本。
C代码:
// test.c
// gcc -g test.c -o test.exe
#include
#include
int icount = 1; // default value
int main(int argc, char *argv[])
{
int i, b;
if (argc == 2) {
icount = atoi(argv[1]);
}
i = icount;
while (i > -1) {
b = 5 / i;
printf(" 5 / %d = %d \n", i, b );
i--;
}
printf("Finished\n");
return 0;
}
Python的gdb脚本如下:
import os
import sys
LOG_FILE='gdb.log'
class MyBreakPoint1(gdb.Breakpoint):
def __init__(self, *args, **kwargs):
super(MyBreakPoint1, self).__init__(*args, **kwargs)
def stop(self):
gdb.execute("p i")
gdb.execute("p b")
class MyBreakPoint2(gdb.Breakpoint):
def __init__(self, *args, **kwargs):
super(MyBreakPoint2, self).__init__(*args, **kwargs)
def stop(self):
gdb.execute("p i")
gdb.execute("p b")
def setup():
print('Running GDB from: %s\n'%(gdb.PYTHONDIR))
gdb.execute("set pagination off")
gdb.execute("set print pretty")
# if connecting to remote target
# gdb.execute('target remote :')
gdb.execute('set logging file %s'%(LOG_FILE))
gdb.execute('set logging on')
print('\nReading gdb env...\n')
gdb.execute('show script-extension')
gdb.execute('show sysroot')
gdb.execute('show solib-search-path')
print('\nSetup complete !!\n')
def main():
setup()
MyBreakPoint1('test.c:20')
MyBreakPoint2('test.c:19')
gdb.execute("r 3")
main()
关于使用方法,与前类似,只要在gdb中source一下这个Python脚本即可。具体调试trace从略。
(完)