通用 gdb python 帮助程序来遍历 C++ STL 容器并调用元素的回调

问题描述 投票:0回答:1

我想调试 C++ 应用程序核心和 gcore 文件。
这包括为二进制数据存储库中的相关数据创建报告。
这些存储库基于 STL 容器(std::map<>、std::set<>、std::vector<>、...)
为此,我使用自己的“通用”Python 帮助程序自动遍历 STL 容器,调用回调函数来处理数据,并创建数据报告。

这是一个例子:

测试c++程序:

#include <string>
#include <map>
#include <iostream>

struct DemoStruct
{
        DemoStruct(int arg1, double arg2, std::string arg3, std::string arg4, char arg5, double arg6)
        : wantSeeThis_1(arg1),
          wantIgnoreThis_1(arg2),
          wantSeeThis_2(arg3),
          wantIgnoreThis_2(arg4),
          wantIgnoreThis_3(arg5),
          wantSeeThis_3(arg6)
        { };

        ~DemoStruct() = default;

        int             wantSeeThis_1;
        double          wantIgnoreThis_1;
        std::string     wantSeeThis_2;
        std::string     wantIgnoreThis_2;
        char            wantIgnoreThis_3;
        double          wantSeeThis_3;
};

std::map<int, DemoStruct> staticData = {
                {       1, {1, 2.0, "hello", "world", 'a', 3.0 } },
                {       2, {10, 3.2, "hi", "world", 'b', 0.3 } },
                {       3, {50, 4.7, "salute", "world", 'c', 11.5 } },
};

int main(int, char**)
{
        // set breakpoint here and inspect data
        return 0;
}

编译它:

> g++ -std=c++11 -o main -g3 -O0 main.cpp

使用 #2 python 脚本进行调试:

文件#1:

> cat stlhelper.py
# -*- coding: utf-8 -*-

import gdb
import sys
import re
import itertools
import six


#
# base class for PrintGenricXY
#
class PrintGenericCommon (gdb.Function):
        """print generic common"""

        def __init__ (self):
                super (PrintGenericCommon, self).__init__ ("PrintGenericList")

        #
        # data is hosted by the subject and we don't know their representation
        # subject.ElemItemString() is responsible to format properly (might be complex data)
        #
        def PrintResult(self, subject):
                # calculate the max sizes per element of all lines for alignment
                sizes = subject.sizesDef
                for x in subject.result:
                        for key, value in six.iteritems(sizes):
                                valStr = subject.ElemItemString(x[key])
                                if len(valStr) > value:
                                        sizes[key] = len(valStr)

                totalSize = 0
                for key, value in six.iteritems(sizes):
                        sizes[key] += 2
                        totalSize += sizes[key]

                print("-" * totalSize)
                for i in range(len(subject.result)):
                        myStr = ''
                        for j in range(len(subject.elements)):
                                setSize=sizes[subject.elements[j]]
                                if (j == len(subject.elements) - 1):
                                        setSize=0 # don't auto-align the last element, which often can have some large elements in the list
                                myStr  += "%-*s" % (setSize, subject.ElemItemString(subject.result[i][subject.elements[j]]))
                        print (myStr)
                        # print optional __VERBOSE data in a next line
                        if '__VERBOSE' in subject.result[i]:
                                str = subject.ElemItemString(subject.result[i]['__VERBOSE'])
                                lines = str.splitlines()
                                for line in lines:
                                        print ('\t' + line)
                                if len(str) > 0 and str[-1] == '\n':
                                        print('')
                        if i == 0:
                                print ("-" * totalSize)


#
# generic printer for std::map<> and std::set<>
#
class PrintGenericMapOrSet (PrintGenericCommon):
        """print generic map/set"""

        regex = re.compile('\$count')

        maxNoResults = -1

        def __init__ (self):
                super (PrintGenericCommon, self).__init__ ("PrintGenericMapOrSet")

        def setMaxNoResults(self, maxNoResults):
                self.maxNoResults = maxNoResults


        def invokeCore(self, headlines, map, valueType, isConst, isPointer, valueHandler):
                collectedLines = []
                map = map['_M_t']['_M_impl']
                count = map['_M_node_count']
                for x in headlines:
                        collectedLines.append(self.regex.sub(str(count), x))
                node = map['_M_header']['_M_left']
                valuetype = gdb.lookup_type (valueType)
                if isConst:
                        valuetype = valuetype.const()
                if isPointer:
                        valuetype = valuetype.pointer()
                nodetype = gdb.lookup_type('std::_Rb_tree_node < %s >' % valuetype)
                nodetype = nodetype.pointer()
                i = 0
                while i < count:
                        collectedLines.append(valueHandler(i, node.cast (nodetype).dereference()['_M_value_field'], node))
                        if node.dereference()['_M_right']:
                                node = node.dereference()['_M_right']
                                while node.dereference()['_M_left']:
                                        node = node.dereference()['_M_left']
                        else:
                                parent = node.dereference()['_M_parent']
                                while node == parent.dereference()['_M_right']:
                                        node = parent
                                        parent = parent.dereference()['_M_parent']
                                if node.dereference()['_M_right'] != parent:
                                        node = parent
                        i += 1
                        if self.maxNoResults > -1 and i >= self.maxNoResults:
                                break;
                return "\n".join(collectedLines)

        def invoke (self, headlines, mapLoc, valueType, valueHandler, typeWrapper):
                map = gdb.parse_and_eval (mapLoc)
                return self.invokeCore(headlines, map, valueType, valueHandler, typeWrapper);

#
# generic printer for std::map<>
#
class PrintGenericMap (gdb.Function):
        """print generic map"""

        handler = PrintGenericMapOrSet()

        def __init__ (self):
                super (PrintGenericMap, self).__init__ ("PrintGenericMap")

        def setMaxNoResults(self, maxNoResults):
                return self.handler.setMaxNoResults(maxNoResults)

        def invokeCore(self, headlines, map, valueType, valueHandler):
                return self.handler.invokeCore(headlines, map, 'std::pair' + valueType, False, False, valueHandler)

        def invoke (self, headlines, mapLoc, valueType, valueHandler, maxNoResults):
                return self.handler.invoke(headlines, mapLoc, 'std::pair' + valueType, False, False, valueHandler);

        def PrintResult(self, subject):
                return self.handler.PrintResult(subject)


#
# generic printer for std::set<>
#
class PrintGenericSet (gdb.Function):
        """print generic set"""

        handler = PrintGenericMapOrSet()

        def __init__ (self):
                super (PrintGenericSet, self).__init__ ("PrintGenericSet")

        def setMaxNoResults(self, maxNoResults):
                return self.handler.setMaxNoResults(maxNoResults)

        def invokeCore(self, headlines, map, valueType, isConst, isPointer, valueHandler):
                return self.handler.invokeCore(headlines, map, valueType, isConst, isPointer, valueHandler)

        def invoke (self, headlines, mapLoc, valueType, isConst, isPointer, valueHandler, maxNoResults):
                return self.handler.invoke(headlines, mapLoc, valueType, isConst, isPointer, valueHandler);

        def PrintResult(self, subject):
                return self.handler.PrintResult(subject)

文件#2:

> cat main.py
# -*- coding: utf-8 -*-

import gdb, platform, six, copy

from stlhelper import PrintGenericMap, PrintGenericSet

class PrintStaticData (gdb.Command):
        """Print STL data demo.\n"""

        printer = None
        result = []
        headline = {}
        sizesDef = {}

        elements = [
                'key',
                'see_1',
                'see_2',
                'see_3',
        ]

        errorEntry = {
                'key'   : '?',
                'see_1' : '?',
                'see_2' : '?',
                'see_3' : '?',
        }

        def __init__ (self):
                super (PrintStaticData, self).__init__ ("PrintStaticData", gdb.COMMAND_USER)
                self.headline = {
                        'key'   : 'Key',
                        'see_1' : 'Value #1',
                        'see_2' : 'Value #2',
                        'see_3' : 'Value #3',
                }

        def ReInit(self):
                if(platform.python_version().startswith("2")):
                        for key, value in six.iteritems(self.headline):
                                self.sizesDef[key] = 0
                else:
                        for key in self.headline.keys():
                                self.sizesDef[key] = 0
                self.result = []
                self.result.append(self.headline)

        def ElemItemString(self, elem):
                return elem

        def Traverse(self, index, pair, node = None):
                first = pair['first']
                second = pair['second']
                newEntry = copy.deepcopy(self.errorEntry)
                newEntry['key'] = str(first)
                newEntry['see_1'] = str(second['wantSeeThis_1'])
                newEntry['see_2'] = str(second['wantSeeThis_2'])
                newEntry['see_3'] = str(second['wantSeeThis_3'])
                self.result.append(newEntry)
                return ''

        def invoke (self, arg, from_tty):
                self.ReInit()

                self.printer = PrintGenericMap()

                data = gdb.parse_and_eval("staticData")
                result = self.printer.invokeCore(
                        [],
                        data,
                        '<int const, DemoStruct>',
                        self.Traverse)

                self.printer.PrintResult(self)

PrintStaticData()

.gdbinit 文件(根据您自己的路径进行调整):

cat $HOME/.gdbinit
set print object
set print pretty
set print static off
set pagination off
set auto-load safe-path /

python
sys.path.insert(0, "/home/me/TEST")
import main
end

set history filename ~/.gdb_history
set history save

在“gdb”中执行演示,例如海湾合作委员会 4.8.5

> gdb main
(gdb) break 35
Breakpoint 1 at 0x400c18: file main.cpp, line 35.

(gdb) r
Starting program: /home/me/TEST/main

Breakpoint 1, main () at main.cpp:35
35              return 0;

(gdb) PrintStaticData
----------------------------------------------
Key  Value #1  Value #2  Value #3
----------------------------------------------
1    1         "hello"   3
2    10        "hi"      0.29999999999999999
3    50        "salute"  11.5

到目前为止一切顺利。

现在的问题是,它不再适用于 gcc 9.4.0。 :-(
原因是 STL 类型 std::map<> 的内部数据结构发生了变化。
我的 python 助手并不是真正的“通用”——它们依赖于具体的 STL 实现。
使用 gcc 9.4.0 我打了

(gdb) PrintStaticData 
Python Exception <class 'gdb.error'> There is no member or method named _M_value_field.: 
Error occurred in Python: There is no member or method named _M_value_field.

不再有 _M_value_field - 我在 stdlhelper.py 中使用它:

                   collectedLines.append(valueHandler(i, node.cast (nodetype).dereference()['_M_value_field'], node))

所以我可以尝试更新Python代码以处理新的STL实现。
但我认为(希望)gcc python STL 工具箱中已经有一些通用工具可以执行相同的操作:
遍历任何 STL 容器 - 无需了解其实现 - 并调用回调函数来处理存储在容器中的每个元素。

gcc python helpers 可以提供这样的功能吗?
也许有一些文档/指南的链接?

python c++ stl gdb
1个回答
0
投票

非常感谢用户ssbssa!
此替换 main.py 用于“PrintStaticDataV2”,打印与“PrintStaticData”相同的结果。
但它不再需要在我的 stlhelper.py 中自行编写代码。
相反,它基于 https://sourceware.org/gdb/current/onlinedocs/gdb.html/Pretty-Printing-API.html

# -*- coding: utf-8 -*-

import gdb, platform, six, copy

from stlhelper import PrintGenericMap, PrintGenericSet

class PrintStaticData (gdb.Command):
        """Print STL data demo.\n"""

        printer = None
        result = []
        headline = {}
        sizesDef = {}

        elements = [
                'key',
                'see_1',
                'see_2',
                'see_3',
        ]
        
        errorEntry = {
                'key'   : '?',
                'see_1' : '?',
                'see_2' : '?',
                'see_3' : '?',
        }

        def __init__ (self):
                super (PrintStaticData, self).__init__ ("PrintStaticData", gdb.COMMAND_USER)        
                self.headline = {
                        'key'   : 'Key', 
                        'see_1' : 'Value #1',
                        'see_2' : 'Value #2',
                        'see_3' : 'Value #3',
                }

        def ReInit(self):
                if(platform.python_version().startswith("2")):
                        for key, value in six.iteritems(self.headline):
                                self.sizesDef[key] = 0
                else:
                        for key in self.headline.keys():
                                self.sizesDef[key] = 0
                self.result = []
                self.result.append(self.headline)

        def ElemItemString(self, elem):
                return elem

        def Traverse(self, index, pair, node = None):
                first = pair['first']
                second = pair['second']
                newEntry = copy.deepcopy(self.errorEntry)
                newEntry['key'] = str(first)
                newEntry['see_1'] = str(second['wantSeeThis_1'])
                newEntry['see_2'] = str(second['wantSeeThis_2'])
                newEntry['see_3'] = str(second['wantSeeThis_3'])
                self.result.append(newEntry)
                return ''

        def invoke (self, arg, from_tty):
                self.ReInit()
                
                self.printer = PrintGenericMap()
                
                data = gdb.parse_and_eval("staticData")
                result = self.printer.invokeCore( 
                        [], 
                        data, 
                        '<int const, DemoStruct>', 
                        self.Traverse) 

                self.printer.PrintResult(self)
                
PrintStaticData()

class PrintStaticDataV2 (gdb.Command):
        """Print STL data demo version #2.\n"""

        printer = None
        result = []
        headline = {}
        sizesDef = {}

        elements = [
                'key',
                'see_1',
                'see_2',
                'see_3',
        ]
        
        errorEntry = {
                'key'   : '?',
                'see_1' : '?',
                'see_2' : '?',
                'see_3' : '?',
        }

        def __init__ (self):
                super (PrintStaticDataV2, self).__init__ ("PrintStaticDataV2", gdb.COMMAND_USER)        
                self.headline = {
                        'key'   : 'Key', 
                        'see_1' : 'Value #1',
                        'see_2' : 'Value #2',
                        'see_3' : 'Value #3',
                }

        def ReInit(self):
                if(platform.python_version().startswith("2")):
                        for key, value in six.iteritems(self.headline):
                                self.sizesDef[key] = 0
                else:
                        for key in self.headline.keys():
                                self.sizesDef[key] = 0
                self.result = []
                self.result.append(self.headline)

        def ElemItemString(self, elem):
                return elem

        def Traverse(self, pair):
                first = pair[0]
                second = pair[1]
                newEntry = copy.deepcopy(self.errorEntry)
                newEntry['key'] = str(first)
                newEntry['see_1'] = str(second['wantSeeThis_1'])
                newEntry['see_2'] = str(second['wantSeeThis_2'])
                newEntry['see_3'] = str(second['wantSeeThis_3'])
                self.result.append(newEntry)
                return ''

        def invoke (self, arg, from_tty):
                self.ReInit()
                self.printer = PrintGenericMap()
                
                data = gdb.parse_and_eval("staticData")
                pp = gdb.default_visualizer(data)
                no = 0
                key = None
                for element in pp.children():
                        if (no % 2) == 0:
                                key = element[1]
                        else:
                                self.Traverse((key, element[1]))
                        no += 1
                print('container has #' + str(no) + ' children')          
                
                self.printer.PrintResult(self)
                
PrintStaticDataV2()

结果:

(gdb) PrintStaticData
----------------------------------------------
Key  Value #1  Value #2  Value #3
----------------------------------------------
1    1         "hello"   3
2    10        "hi"      0.29999999999999999
3    50        "salute"  11.5
(gdb) PrintStaticDataV2
container has #6 children
----------------------------------------------
Key  Value #1  Value #2  Value #3
----------------------------------------------
1    1         "hello"   3
2    10        "hi"      0.29999999999999999
3    50        "salute"  11.5
© www.soinside.com 2019 - 2024. All rights reserved.