6

你还在用GDB调试程序吗?

 3 years ago
source link: https://zhuanlan.zhihu.com/p/152274203
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

你还在用GDB调试程序吗?

一个人NB的不是标签

你还在用GDB调试程序吗?

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

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

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

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

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

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

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

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

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

将难看的数据变得好看

以下面的代码为例

#include <map>
#include <iostream>
#include <string>
using namespace  std;

int main() {
    std::map<string, string> lm;
    lm["good"] = "heart";
    // 查看map 里面内容
    std::cout<<lm["good"];
}

当代码运行到std<<cout时, 你想查看map里面的内容,如果没有python和自定义的脚本,print lm看到的是

$2 = {_M_t = {
    _M_impl = {<std::allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >> = {<__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >> = {<No data fields>}, <No data fields>}, <std::_Rb_tree_key_compare<std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >> = {
        _M_key_compare = {<std::binary_function<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool>> = {<No data fields>}, <No data fields>}}, <std::_Rb_tree_header> = {_M_header = {
          _M_color = std::_S_red, _M_parent = 0x55555556eeb0, 
          _M_left = 0x55555556eeb0, _M_right = 0x55555556eeb0}, 
        _M_node_count = 1}, <No data fields>}}}

但是当你在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实现同样的效果。具体步骤如下:

  1. 下载pretty printer: svn co svn://http://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python
  2. 在gdb里面输入(将路径改成你下载的路径):
python
import sys
sys.path.insert(0, '/home/maude/gdb_printers/python')
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end

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

详细请看:

https://sourceware.org/gdb/wiki/STLSupport

https://codeyarns.com/2014/07/17/how-to-enable-pretty-printing-for-stl-in-gdb/

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

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

#####################################################
# Usage: to load this to gdb run:
# (gdb) source ..../path/to/<script_file>.py

import gdb

class StackWalk(gdb.Command):
    def __init__(self):
        # This registers our class as "StackWalk"
        super(StackWalk, self).__init__("stackwalk", gdb.COMMAND_DATA)

    def invoke(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 < rbp:
            try:
                print('pointer is {}'.format(ptr))
                print(gdb.execute('wc_print {}'.format(ptr.cast(ppwc).dereference())))
                print('===')
            except:
                pass
            ptr += 8
        

# This registers our class to the gdb runtime at "source" time.
StackWalk()

Note: wc_print是我写的另外一个简单Python命令,用于打印给定地址的宽字符串,具体实现留作习题~

更快的调试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/ <PATH TO BINARY>
# /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.
#

import gdb


class StackFold(gdb.Command):
    def __init__(self):
        super(StackFold, self).__init__("stackfold", gdb.COMMAND_DATA)

    def invoke(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 in inferiors:
            for thread in inferior.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 not None:
                        if cur_frame.name() is not None:
                            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 in stack_maps:
                        stack_maps[s_backtrace] = []
                    # Now lets add this thread to the map.
                    stack_maps[s_backtrace].append({'gtid': gtid, 'tpid' : tpid, 'bt': o} )
                except Exception 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 in stack_maps:
            # Get our human readable form out.
            o = stack_maps[smap][0]['bt']
            for t in stack_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:

1 https://undo.io/resources/gdb-watchpoint/python-gdb/

2 https://codeyarns.com/2014/07/17/how-to-enable-pretty-printing-for-stl-in-gdb/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK