10

你还在用GDB调试程序吗?

 4 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.
neoserver,ios ssh client

你还在用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/


Recommend

  • 62
    • blog.51cto.com 7 years ago
    • Cache

    调试利器GDB(下)-在路上

    本文讲了GDB的数据断点使用和栈帧信息的查看以及其他使用技巧

  • 73
    • Github github.com 6 years ago
    • Cache

    GDB 单步调试汇编

    之前在看汇编的时候一直是肉眼看GCC -S的结果,缺点是很不直观,无法实时的看到寄存器的值,所以研究了下如何用GDB调试汇编。当然,写这篇文章更重要的一个目的是半年没有写博客了,博客要长草了。^_^ 我调试汇编的需求有几点:

  • 47
    • www.tuicool.com 5 years ago
    • Cache

    GDB 调试指南

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev) ,专注于干货分享,号内有 10T 书籍和视频资源,后台回复 「1024」 即可领取,欢迎大家关注,二维码文末可以扫。

  • 28
    • zhuanlan.zhihu.com 4 years ago
    • Cache

    你还在用GDB调试程序吗? - 知乎

  • 12
    • blog.studygolang.com 4 years ago
    • Cache

    GDB调试Go程序

    GDB调试Go程序 GDB调试Go程序 说明:作为一门静态语言,似乎支持调试是必须的,而且,Go初学者喜欢问的问题也是:大家都用什么IDE?怎么调试? 其实,Go是为多核和并发而生,真正的项目,你用单步调试,原本没问题的,可能...

  • 10
    • blog.csdn.net 4 years ago
    • Cache

    GDB 调试程序 详解 使用实例

    GDB 调试程序 详解 使用实例 用GDB调试程序

  • 7
    • jiajunhuang.com 4 years ago
    • Cache

    Go使用gdb调试

    Go使用gdb调试 其实我一般调试都是直接打log的,不过gdb调试还是很有用处,尤其是当碰到一些底层错误的需要单步跟踪的时候,比如,想研究一下 Go的runtime是如何实现的的时候。 首先在编译Go程序的时候,要让Go带上编译信息:

  • 16
    • zhuanlan.zhihu.com 4 years ago
    • Cache

    使用VS 2017进行Linux C/C++远程GDB调试

    使用VS 2017进行Linux C/C++远程GDB调试同济大学 计算机科学与技术硕士在读既然你已经知道了makefile是咋回事了,又嫌麻烦每次都要用ftp传代码+开个终端测试那就不妨...

  • 11
    • taowusheng.cn 4 years ago
    • Cache

    gdb和qemu调试Linux内核

    gdb和qemu调试Linux内核 之前学习了利用KGDB双机调试内核,这种...

  • 8
    • blog.ponder.work 3 years ago
    • Cache

    使用gdb调试Python程序

    由于Python解释器是由C语言编写,我们可以使用GDB来调试Python进程,对于程序卡死等异常情况调试比较有帮助。 用gdb调试Python程序,主要有两个部分 原生的gdb命令,调试的的Python解释器的C代码 py-bt等以py-为前缀...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK