天天看點

你還在用GDB調試程式嗎?

↑ 點選藍字 關注視學算法

作者丨薛定谔的喵@知乎

來源丨https://zhuanlan.zhihu.com/p/152274203

編輯丨極市平台

你還在用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實作同樣的效果。具體步驟如下:

下載下傳pretty printer: svn co svn://http://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python

在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

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

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

你還在用GDB調試程式嗎?

覺得有用麻煩給個在看啦~  

你還在用GDB調試程式嗎?