你还在用GDB调试程序吗?

如果是,那么我们是同道中人。但是你知道GDB有一个很强大的功能,Python scripting嘛?

如果是的,那么恭喜你,你是一个大牛。

本文主要讲述如何使用Python来提高你的gdb调试技能, 让你从繁重的重复的工作里面挣脱出来呼吸新鲜空气。

首先,第一件事,使用gdb7.x以上的版本,最好9.x的。因为Python的支持是从gdb7.0(2009年?)开始的。

进入正题

gdb本来就支持自定义脚本辅助调试,为什么还要用Python脚本呢?因为自定义脚本的语法比较老,不如写Python欢快。如果你喜欢用原来的自定义脚本方法,那也是可以的。

借助Python,你可以将难看的数据变得好看,

借助Python,你可以将重复的工作变成一个命令,

借助Python,你可以更快的调试bug,

借助Python,你可以装逼,哈哈哈

……

将难看的数据变得好看

以下面的代码为例

#include #include #include 
using namespace std;
int main() {
std::maplm;
lm["good"] = "heart";//查看map 里面内容
std::cout<
}
当代码运行到std<
$2 = {_M_t ={
_M_impl= {<:allocator std::char_traits>, std::allocator > const, std::__cxx11::basic_string, std::allocator > > > >> = {<:new_allocator std::char_traits>, std::allocator > const, std::__cxx11::basic_string, std::allocator > > > >> = {}, }, <:_rb_tree_key_compare std::char_traits>, std::allocator > > >> ={
_M_key_compare= {<:binary_function std::char_traits>, std::allocator >, std::__cxx11::basic_string, std::allocator >, bool>> = {}, }}, <:_rb_tree_header> = {_M_header ={
_M_color= std::_S_red, _M_parent = 0x55555556eeb0,
_M_left= 0x55555556eeb0, _M_right = 0x55555556eeb0},
_M_node_count= 1}, }}}
但是当你在gdb9.2里面输入print lm的时候,你看到的将是
(gdb) p lm
$3 = std::map with 1 element = {["good"] = "heart"}

map里面有什么一清二楚。这是因为gdb9.x自带了一系列标准库的Python pretty priniter。 如果你使用的是gdb7.x,那么你可以手动的导入这些pretty printer实现同样的效果。具体步骤如下:

在gdb里面输入(将路径改成你下载的路径):

pythonimportsys
sys.path.insert(0,'/home/maude/gdb_printers/python')from libstdcxx.v6.printers importregister_libstdcxx_printers
register_libstdcxx_printers (None)
end

这样你就可以放心使用了~

详细请看:

将重复的工作变成一个命令

比如在调试的时候,你知道当前栈指向一个字符串,但是你不知道具体在哪里,你想遍历这个栈将它找出来,那么你可以借助Python自定义一个命令"stackwalk",这个命令可以直接Python代码遍历栈,将字符串找出来。

######################################################Usage: to load this to gdb run:#(gdb) source ..../path/to/.py
importgdbclassStackWalk(gdb.Command):def __init__(self):#This registers our class as "StackWalk"
super(StackWalk, self).__init__("stackwalk", gdb.COMMAND_DATA)definvoke(self, arg, from_tty):#When we call "StackWalk" from gdb, this is the method
#that will be called.
print("Hello from StackWalk!")#get the register
rbp = gdb.parse_and_eval('$rbp')
rsp= gdb.parse_and_eval('$rsp')
ptr=rsp
ppwc= gdb.lookup_type('wchar_t').pointer().pointer()while ptr 
#This registers our class to the gdb runtime at "source" time.
StackWalk()
更快的调试bug
当你调试多线程的时候,你发现callstack 一堆,而且好多都是重复的,如果它们可以自动去重或者折叠多好,这样你只需要关注一小部分。好消息!Python可以让你用一个命令就可以轻松搞定。而且已经有人写好了相应的代码,你只需要导入即可。详细介绍请看https://fy.blackhats.net.au/blog/html/2017/08/04/so_you_want_to_script_gdb_with_python.html#From https://fy.blackhats.net.au/blog/html/2017/08/04/so_you_want_to_script_gdb_with_python.html######################################################
#Usage: to load this to gdb run:#(gdb) source ..../path/to/debug_naughty.py#
#To have this automatically load, you need to put the script#in a path related to your binary. If you make /usr/sbin/foo,#You can ship this script as:#/usr/share/gdb/auto-load/ #/usr/share/gdb/auto-load/usr/sbin/foo#
#This will trigger gdb to autoload the script when you start#to acces a core or the live binary from this location.#
importgdbclassStackFold(gdb.Command):def __init__(self):
super(StackFold, self).__init__("stackfold", gdb.COMMAND_DATA)definvoke(self, arg, from_tty):#An inferior is the 'currently running applications'. In this case we only
#have one.
stack_maps ={}#This creates a dict where each element is keyed by backtrace.
#Then each backtrace contains an array of "frames"
# inferiors =gdb.inferiors()for inferior ininferiors:for thread ininferior.threads():try:#Change to our threads context
thread.switch()#Get the thread IDS
(tpid, lwpid, tid) =thread.ptid
gtid=thread.num#Take a human readable copy of the backtrace, we'll need this for display later.
o = gdb.execute('bt', to_string=True)#Build the backtrace for comparison
backtrace =[]
gdb.newest_frame()
cur_frame=gdb.selected_frame()while cur_frame is notNone:if cur_frame.name() is notNone:
backtrace.append(cur_frame.name())
cur_frame=cur_frame.older()#Now we have a backtrace like ['pthread_cond_wait@@GLIBC_2.3.2', 'lazy_thread', 'start_thread', 'clone']
#dicts can't use lists as keys because they are non-hashable, so we turn this into a string.
#Remember, C functions can't have spaces in them ...
s_backtrace = ' '.join(backtrace)#Let's see if it exists in the stack_maps
if s_backtrace not instack_maps:
stack_maps[s_backtrace]=[]#Now lets add this thread to the map.
stack_maps[s_backtrace].append({'gtid': gtid, 'tpid' : tpid, 'bt': o} )exceptException as e:print(e)#Now at this point we have a dict of traces, and each trace has a "list" of pids that match. Let's display them
for smap instack_maps:#Get our human readable form out.
o = stack_maps[smap][0]['bt']for t instack_maps[smap]:#For each thread we recorded
print("Thread %s (LWP %s))" % (t['gtid'], t['tpid']))print(o)#This registers our class to the gdb runtime at "source" time.
StackFold()

等等!还有好多,毕竟Python图灵完备,只要GDB提供相应的API,你想要啥都能实现。

会了这些,你就可以向新手装逼去了

References: