[软件设计/软件工程] C++与gdB调试/打印自定义类型:以NLHMANN JSON库为例

[复制链接]
发表于 2022-5-5 09:27:46
问题
我正在使用 NLHHMN 进行 JSON C++ 实现项目。

如何在 GDB 中轻松探索 nlohmann 的 JSON 键/值?

我尝试使用这个 STL GDB 包装器,因为它提供了帮助来探索 nlohmann JSON-LIB 使用的标准 C++ 库结构。

但是我觉得不方便。

这是一个简单的用例:
  1. json foo;
  2. foo["flex"] = 0.2;
  3. foo["awesome_str"] = "bleh";
  4. foo["nested"] = {{"bar", "barz"}};
复制代码

我想在 GDB 中拥有的东西:

  1. (gdb) p foo
  2. {
  3.     "flex" : 0.2,
  4.     "awesome_str": "bleh",
  5.     "nested": etc.
  6. }
复制代码

当前行为
  1. (gdb) p foo
  2. $1 = {
  3.   m_type = nlohmann::detail::value_t::object,
  4.   m_value = {
  5.     object = 0x129ccdd0,
  6.     array = 0x129ccdd0,
  7.     string = 0x129ccdd0,
  8.     boolean = 208,
  9.     number_integer = 312266192,
  10.     number_unsigned = 312266192,
  11.     number_float = 1.5427999782486669e-315
  12.   }
  13. }
  14. (gdb) p foo.at("flex")
  15. Cannot evaluate function -- may be inlined // I suppose it depends on my compilation process. But I guess it does not invalidate the question.
  16. (gdb) p *foo.m_value.object
  17. $2 = {
  18.   _M_t = {
  19.     _M_impl = {
  20.       <std::allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long long, unsigned long long, double, std::allocator, nlohmann::adl_serializer> > > >> = {
  21.         <__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long long, unsigned long long, double, std::allocator, nlohmann::adl_serializer> > > >> = {<No data fields>}, <No data fields>},
  22.       <std::_Rb_tree_key_compare<std::less<void> >> = {
  23.         _M_key_compare = {<No data fields>}
  24.       },
  25.       <std::_Rb_tree_header> = {
  26.         _M_header = {
  27.           _M_color = std::_S_red,
  28.           _M_parent = 0x4d72d0,
  29.           _M_left = 0x4d7210,
  30.           _M_right = 0x4d7270
  31.         },
  32.         _M_node_count = 5
  33.       }, <No data fields>}
  34.   }
  35. }
复制代码

回答
我找到了自己的答案,进一步阅读了有关 std::string 打印的 GDB 函数和堆栈溢出问题。

捷径是最简单的。

另一种方式很艰难,但我很高兴我能做到。还有很大的改进空间。

短路径 v3.1.2

我简单地定义了一个 gdb 命令如下:
  1. # this is a gdb script
  2. # can be loaded from gdb using
  3. # source my_script.txt (or. gdb or whatever you like)
  4. define pjson
  5. # use the lohmann's builtin dump method, ident 4 and use space separator
  6. printf "%s\n", $arg0.dump(4, ' ', true).c_str()
  7. end
  8. # configure command helper (text displayed when typing 'help pjson' in gdb)
  9. document pjson
  10. Prints a lohmann's JSON C++ variable as a human-readable JSON string
  11. end
复制代码

在 gdb 中使用它:
  1. (gdb) source my_custom_script.gdb
  2. (gdb) pjson foo
  3. {
  4.     "flex" : 0.2,
  5.     "awesome_str": "bleh",
  6.     "nested": {
  7.         "bar": "barz"
  8.     }
  9. }
复制代码

短路径 v3.7.0 [编辑] 2019-onv-06

也可以使用新的 to_string() 方法,但我无法让它与 GDB 一起使用,以实现实时伪劣进程。下面的方法仍然有效。
  1. # this is a gdb script
  2. # can be loaded from gdb using
  3. # source my_script.txt (or. gdb or whatever you like)
  4. define pjson
  5. # use the lohmann's builtin dump method, ident 4 and use space separator
  6. printf "%s\n", $arg0.dump(4, ' ', true, json::error_handler_t::strict).c_str()
  7. end
  8. # configure command helper (text displayed when typing 'help pjson' in gdb)
  9. document pjson
  10. Prints a lohmann's JSON C++ variable as a human-readable JSON string
  11. end
复制代码
  1. GNU gdb (GDB) 8.3 for GNAT Community 2019 [rev=gdb-8.3-ref-194-g3fc1095]
  2. c++ project built with
  3. GPRBUILD/ GNAT Community 2019 (20190517) (x86_64-pc-mingw32)The following python code shall be loaded within gdb. I use a .gdbinit file sourced in gdb.Github repo: https://github.com/LoneWanderer-GH/nlohmann-json-gdbGDB scriptFeel free to adopt the loading method of your choice (auto, or not, or IDE plugin, whatever)set print pretty
  4. # source stl_parser.gdb # if you like the good work done with those STL containers GDB parsers

  5. source printer.py # the python file is given below
  6. python gdb.printing.register_pretty_printer(gdb.current_objfile(), build_pretty_printer())
  7. Python scriptimport gdb

  8. import platform
  9. import sys
  10. import traceback

  11. # adapted from https://github.com/hugsy/gef/blob/dev/gef.py
  12. # their rights are theirs
  13. HORIZONTAL_LINE = "_"  # u"\u2500"
  14. LEFT_ARROW = "<-"  # "\u2190 "
  15. RIGHT_ARROW = "->"  # " \u2192 "
  16. DOWN_ARROW = "|"  # "\u21b3"

  17. nlohmann_json_type_namespace = \
  18.     r"nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, " \
  19.     r"std::allocator<char> >, bool, long long, unsigned long long, double, std::allocator, nlohmann::adl_serializer>"

  20. # STD black magic
  21. MAGIC_STD_VECTOR_OFFSET = 16  # win 10 x64 values, beware on your platform
  22. MAGIC_OFFSET_STD_MAP = 32  # win 10 x64 values, beware on your platform

  23. """"""
  24. # GDB black magic
  25. """"""
  26. nlohmann_json_type = gdb.lookup_type(nlohmann_json_type_namespace).pointer()
  27. # for in memory direct jumps. cast to type is still necessary yet to obtain values, but this could be changed by chaning the types to simpler ones ?
  28. std_rb_tree_node_type = gdb.lookup_type("std::_Rb_tree_node_base::_Base_ptr").pointer()
  29. std_rb_tree_size_type = gdb.lookup_type("std::size_t").pointer()

  30. """"""
  31. # nlohmann_json reminder. any interface change should be reflected here
  32. # enum class value_t : std::uint8_t
  33. # {
  34. #     null,             ///< null value
  35. #     object,           ///< object (unordered set of name/value pairs)
  36. #     array,            ///< array (ordered collection of values)
  37. #     string,           ///< string value
  38. #     boolean,          ///< boolean value
  39. #     number_integer,   ///< number value (signed integer)
  40. #     number_unsigned,  ///< number value (unsigned integer)
  41. #     number_float,     ///< number value (floating-point)
  42. #     discarded         ///< discarded by the the parser callback function
  43. # };

  44. """"""
  45. enum_literals_namespace = ["nlohmann::detail::value_t::null",
  46.                             "nlohmann::detail::value_t::object",
  47.                             "nlohmann::detail::value_t::array",
  48.                             "nlohmann::detail::value_t::string",
  49.                             "nlohmann::detail::value_t::boolean",
  50.                             "nlohmann::detail::value_t::number_integer",
  51.                             "nlohmann::detail::value_t::number_unsigned",
  52.                             "nlohmann::detail::value_t::number_float",
  53.                             "nlohmann::detail::value_t::discarded"]

  54. enum_literal_namespace_to_literal = dict([(e, e.split("::")[-1]) for e in enum_literals_namespace])

  55. INDENT = 4 # beautiful isn't it ?

  56. def std_stl_item_to_int_address(node):
  57.     return int(str(node), 0)


  58. def parse_std_str_from_hexa_address(hexa_str):
  59.     # https://stackoverflow.com/questions/6776961/how-to-inspect-stdstring-in-gdb-with-no-source-code
  60.     return '"{}"'.format(gdb.parse_and_eval("*(char**){}".format(hexa_str)).string())


  61. class LohmannJSONPrinter(object):
  62.     """Print a nlohmann::json in GDB python
  63.     BEWARE :
  64.      - Contains shitty string formatting (defining lists and playing with ",".join(...) could be better; ident management is stoneage style)
  65.      - Parsing barely tested only with a live inferior process.
  66.      - It could possibly work with a core dump + debug symbols. TODO: read that stuff
  67.      https://doc.ecoscentric.com/gnutools/doc/gdb/Core-File-Generation.html
  68.      - Not idea what happens with no symbols available, lots of fields are retrieved by name and should be changed to offsets if possible
  69.      - NO LIB VERSION MANAGEMENT. TODO: determine if there are serious variants in nlohmann data structures that would justify working with strucutres
  70.      - PLATFORM DEPENDANT TODO: remove the black magic offsets or handle them in a nicer way
  71.     NB: If you are python-kaizer-style-guru, please consider helping or teaching how to improve all that mess
  72.     """

  73.     def __init__(self, val, indent_level=0):
  74.         self.val = val
  75.         self.field_type_full_namespace = None
  76.         self.field_type_short = None
  77.         self.indent_level = indent_level

  78.         self.function_map = {"nlohmann::detail::value_t::null": self.parse_as_leaf,
  79.                             "nlohmann::detail::value_t::object": self.parse_as_object,
  80.                             "nlohmann::detail::value_t::array": self.parse_as_array,
  81.                             "nlohmann::detail::value_t::string": self.parse_as_str,
  82.                             "nlohmann::detail::value_t::boolean": self.parse_as_leaf,
  83.                             "nlohmann::detail::value_t::number_integer": self.parse_as_leaf,
  84.                             "nlohmann::detail::value_t::number_unsigned": self.parse_as_leaf,
  85.                             "nlohmann::detail::value_t::number_float": self.parse_as_leaf,
  86.                             "nlohmann::detail::value_t::discarded": self.parse_as_leaf}

  87.     def parse_as_object(self):
  88.         assert (self.field_type_short == "object")

  89.         o = self.val["m_value"][self.field_type_short]

  90.         # traversing tree is a an adapted copy pasta from STL gdb parser
  91.         # (http://www.yolinux.com/TUTORIALS/src/dbinit_stl_views-1.03.txt and similar links)

  92.         #   Simple GDB Macros writen by Dan Marinescu (H-PhD) - License GPL
  93.         #   Inspired by intial work of Tom Malnar,
  94.         #     Tony Novac (PhD) / Cornell / Stanford,
  95.         #     Gilad Mishne (PhD) and Many Many Others.
  96.         #   Contact: dan_c_marinescu@yahoo.com (Subject: STL)
  97.         #
  98.         #   Modified to work with g++ 4.3 by Anders Elton
  99.         #   Also added _member functions, that instead of printing the entire class in map, prints a member.

  100.         node = o["_M_t"]["_M_impl"]["_M_header"]["_M_left"]
  101.         # end = o["_M_t"]["_M_impl"]["_M_header"]
  102.         tree_size = o["_M_t"]["_M_impl"]["_M_node_count"]


  103.         # in memory alternatives:

  104.         _M_t = std_stl_item_to_int_address(o.referenced_value().address)
  105.         _M_t_M_impl_M_header_M_left = _M_t + 8 + 16 # adding bits
  106.         _M_t_M_impl_M_node_count    = _M_t + 8 + 16 + 16 # adding bits

  107.         node = gdb.Value(long(_M_t_M_impl_M_header_M_left)).cast(std_rb_tree_node_type).referenced_value()
  108.         tree_size = gdb.Value(long(_M_t_M_impl_M_node_count)).cast(std_rb_tree_size_type).referenced_value()

  109.         i = 0

  110.         if tree_size == 0:
  111.             return "{}"
  112.         else:
  113.             s = "{\n"
  114.             self.indent_level += 1
  115.             while i < tree_size:
  116.                 # STL GDB scripts write "+1" which in my w10 x64 GDB makes a +32 bits move ...
  117.                 # may be platform dependant and should be taken with caution
  118.                 key_address = std_stl_item_to_int_address(node) + MAGIC_OFFSET_STD_MAP

  119.                 # print(key_object['_M_dataplus']['_M_p'])

  120.                 k_str = parse_std_str_from_hexa_address(hex(key_address))

  121.                 # offset = MAGIC_OFFSET_STD_MAP
  122.                 value_address = key_address + MAGIC_OFFSET_STD_MAP
  123.                 value_object = gdb.Value(long(value_address)).cast(nlohmann_json_type)

  124.                 v_str = LohmannJSONPrinter(value_object, self.indent_level + 1).to_string()

  125.                 k_v_str = "{} : {}".format(k_str, v_str)
  126.                 end_of_line = "\n" if tree_size <= 1 or i == tree_size else ",\n"

  127.                 s = s + (" " * (self.indent_level * INDENT)) + k_v_str + end_of_line  # ",\n"

  128.                 if std_stl_item_to_int_address(node["_M_right"]) != 0:
  129.                     node = node["_M_right"]
  130.                     while std_stl_item_to_int_address(node["_M_left"]) != 0:
  131.                         node = node["_M_left"]
  132.                 else:
  133.                     tmp_node = node["_M_parent"]
  134.                     while std_stl_item_to_int_address(node) == std_stl_item_to_int_address(tmp_node["_M_right"]):
  135.                         node = tmp_node
  136.                         tmp_node = tmp_node["_M_parent"]

  137.                     if std_stl_item_to_int_address(node["_M_right"]) != std_stl_item_to_int_address(tmp_node):
  138.                         node = tmp_node
  139.                 i += 1
  140.             self.indent_level -= 2
  141.             s = s + (" " * (self.indent_level * INDENT)) + "}"
  142.             return s

  143.     def parse_as_str(self):
  144.         return parse_std_str_from_hexa_address(str(self.val["m_value"][self.field_type_short]))

  145.     def parse_as_leaf(self):
  146.         s = "WTFBBQ !"
  147.         if self.field_type_short == "null" or self.field_type_short == "discarded":
  148.             s = self.field_type_short
  149.         elif self.field_type_short == "string":
  150.             s = self.parse_as_str()
  151.         else:
  152.             s = str(self.val["m_value"][self.field_type_short])
  153.         return s

  154.     def parse_as_array(self):
  155.         assert (self.field_type_short == "array")
  156.         o = self.val["m_value"][self.field_type_short]
  157.         start = o["_M_impl"]["_M_start"]
  158.         size = o["_M_impl"]["_M_finish"] - start
  159.         # capacity = o["_M_impl"]["_M_end_of_storage"] - start
  160.         # size_max = size - 1
  161.         i = 0
  162.         start_address = std_stl_item_to_int_address(start)
  163.         if size == 0:
  164.             s = "[]"
  165.         else:
  166.             self.indent_level += 1
  167.             s = "[\n"
  168.             while i < size:
  169.                 # STL GDB scripts write "+1" which in my w10 x64 GDB makes a +16 bits move ...
  170.                 offset = i * MAGIC_STD_VECTOR_OFFSET
  171.                 i_address = start_address + offset
  172.                 value_object = gdb.Value(long(i_address)).cast(nlohmann_json_type)
  173.                 v_str = LohmannJSONPrinter(value_object, self.indent_level + 1).to_string()
  174.                 end_of_line = "\n" if size <= 1 or i == size else ",\n"
  175.                 s = s + (" " * (self.indent_level * INDENT)) + v_str + end_of_line
  176.                 i += 1
  177.             self.indent_level -= 2
  178.             s = s + (" " * (self.indent_level * INDENT)) + "]"
  179.         return s

  180.     def is_leaf(self):
  181.         return self.field_type_short != "object" and self.field_type_short != "array"

  182.     def parse_as_aggregate(self):
  183.         if self.field_type_short == "object":
  184.             s = self.parse_as_object()
  185.         elif self.field_type_short == "array":
  186.             s = self.parse_as_array()
  187.         else:
  188.             s = "WTFBBQ !"
  189.         return s

  190.     def parse(self):
  191.         # s = "WTFBBQ !"
  192.         if self.is_leaf():
  193.             s = self.parse_as_leaf()
  194.         else:
  195.             s = self.parse_as_aggregate()
  196.         return s

  197.     def to_string(self):
  198.         try:
  199.             self.field_type_full_namespace = self.val["m_type"]
  200.             str_val = str(self.field_type_full_namespace)
  201.             if not str_val in enum_literal_namespace_to_literal:
  202.                 return "TIMMY !"
  203.             self.field_type_short = enum_literal_namespace_to_literal[str_val]
  204.             return self.function_map[str_val]()
  205.             # return self.parse()
  206.         except:
  207.             show_last_exception()
  208.             return "NOT A JSON OBJECT // CORRUPTED ?"

  209.     def display_hint(self):
  210.         return self.val.type


  211. # adapted from https://github.com/hugsy/gef/blob/dev/gef.py
  212. # inspired by https://stackoverflow.com/questions/44733195/gdb-python-api-getting-the-python-api-of-gdb-to-print-the-offending-line-numbe
  213. def show_last_exception():
  214.     """Display the last Python exception."""
  215.     print("")
  216.     exc_type, exc_value, exc_traceback = sys.exc_info()

  217.     print(" Exception raised ".center(80, HORIZONTAL_LINE))
  218.     print("{}: {}".format(exc_type.__name__, exc_value))
  219.     print(" Detailed stacktrace ".center(80, HORIZONTAL_LINE))
  220.     for (filename, lineno, method, code) in traceback.extract_tb(exc_traceback)[::-1]:
  221.         print("""{} File "{}", line {:d}, in {}()""".format(DOWN_ARROW, filename, lineno, method))
  222.         print("   {}    {}".format(RIGHT_ARROW, code))
  223.     print(" Last 10 GDB commands ".center(80, HORIZONTAL_LINE))
  224.     gdb.execute("show commands")
  225.     print(" Runtime environment ".center(80, HORIZONTAL_LINE))
  226.     print("* GDB: {}".format(gdb.VERSION))
  227.     print("* Python: {:d}.{:d}.{:d} - {:s}".format(sys.version_info.major, sys.version_info.minor,
  228.                                                    sys.version_info.micro, sys.version_info.releaselevel))
  229.     print("* OS: {:s} - {:s} ({:s}) on {:s}".format(platform.system(), platform.release(),
  230.                                                     platform.architecture()[0],
  231.                                                     " ".join(platform.dist())))
  232.     print(horizontal_line * 80)
  233.     print("")
  234.     exit(-6000)


  235. def build_pretty_printer():
  236.     pp = gdb.printing.RegexpCollectionPrettyPrinter("nlohmann_json")
  237.     pp.add_printer(nlohmann_json_type_namespace, "^{}$".format(nlohmann_json_type_namespace), LohmannJSONPrinter)
  238.     return pp


  239. ######
  240. # executed at autoload (or to be executed by in GDB)
  241. # gdb.printing.register_pretty_printer(gdb.current_objfile(),build_pretty_printer())
  242. BEWARE :
  243. - Contains shitty string formatting (defining lists and playing with ",".join(...) could be better; ident management is stoneage style)
  244. - Parsing barely tested only with a live inferior process.
  245. - It could possibly work with a core dump + debug symbols. TODO: read that stuff
  246. https://doc.ecoscentric.com/gnutools/doc/gdb/Core-File-Generation.html
  247. - Not idea what happens with no symbols available, lots of fields are retrieved by name and should be changed to offsets if possible
  248. - NO LIB VERSION MANAGEMENT. TODO: determine if there are serious variants in nlohmann data structures that would justify working with structures
  249. - PLATFORM DEPENDANT TODO: remove the black magic offsets or handle them in a nicer way
  250. NB: If you are python-kaizer-style-guru, please consider helping or teaching how to improve all that mess
  251. some (light tests):gpr file:project Debug_Printer is

  252.    for Source_Dirs use ("src", "include");
  253.    for Object_Dir use "obj";
  254.    for Main use ("main.cpp");
  255.    for Languages use ("C++");

  256.    package Naming is
  257.       for Spec_Suffix ("c++") use ".hpp";
  258.    end Naming;

  259.    package Compiler is
  260.       for Switches ("c++") use ("-O3", "-Wall", "-Woverloaded-virtual", "-g");
  261.    end Compiler;

  262.    package Linker is
  263.       for Switches ("c++") use ("-g");
  264.    end Linker;

  265. end Debug_Printer;
  266. main.cpp#include  // i am using the standalone json.hpp from the repo release
  267.     #include using json = nlohmann::json;

  268. int main() {
  269.   json fooz;
  270.   fooz = 0.7;

  271.   json arr = {3, "25", 0.5};

  272.   json one;
  273.   one["first"] = "second";

  274.   json foo;
  275.   foo["flex"] = 0.2;
  276.   foo["bool"] = true;
  277.   foo["int"] = 5;
  278.   foo["float"] = 5.22;
  279.   foo["trap "] = "you fell";
  280.   foo["awesome_str"] = "bleh";
  281.   foo["nested"] = {{"bar", "barz"}};
  282.   foo["array"] = { 1, 0, 2 };

  283.   std::cout << "fooz" << std::endl;
  284.   std::cout << fooz.dump(4) << std::endl << std::endl;

  285.   std::cout << "arr" << std::endl;
  286.   std::cout << arr.dump(4) << std::endl << std::endl;

  287.   std::cout << "one" << std::endl;
  288.   std::cout << one.dump(4) << std::endl << std::endl;

  289.   std::cout << "foo" << std::endl;
  290.   std::cout << foo.dump(4) << std::endl << std::endl;

  291.   json mixed_nested;

  292.   mixed_nested["Jean"] = fooz;
  293.   mixed_nested["Baptiste"] = one;
  294.   mixed_nested["Emmanuel"] = arr;
  295.   mixed_nested["Zorg"] = foo;

  296.   std::cout << "5th element" << std::endl;
  297.   std::cout << mixed_nested.dump(4) << std::endl << std::endl;

  298.   return 0;
  299. }
  300. outputs:
  301. (gdb) source .gdbinit

  302. Breakpoint 1, main () at F:\DEV\Projets\nlohmann.json\src\main.cpp:45
  303. (gdb) p mixed_nested
  304. $1 = {
  305.     "Baptiste" : {
  306.             "first" : "second"
  307.     },
  308.     "Emmanuel" : [
  309.             3,
  310.             "25",
  311.             0.5,
  312.     ],
  313.     "Jean" : 0.69999999999999996,
  314.     "Zorg" : {
  315.             "array" : [
  316.                     1,
  317.                     0,
  318.                     2,
  319.             ],
  320.             "awesome_str" : "bleh",
  321.             "bool" : true,
  322.             "flex" : 0.20000000000000001,
  323.             "float" : 5.2199999999999998,
  324.             "int" : 5,
  325.             "nested" : {
  326.                     "bar" : "barz"
  327.             },
  328.             "trap " : "you fell",
  329.     },
  330. }
  331. Edit 2019-march-24 : add precision given by employed russian.Edit 2020-april-18 : after a long night of struggling with python/gdb/stl I had something working by the ways of the GDB documentation for python pretty printers. Please forgive any mistakes or misconceptions, I banged my head a whole night on this and everything is flurry-blurry now.Edit 2020-april-18 (2): rb tree node and tree_size could be traversed in a more "in-memory" way (see above)
复制代码






上一篇:使用 NAudio 改变左右声道的声音平衡
下一篇:TStringList.IndexOf:IndexOf中的通配符?

使用道具 举报

Archiver|手机版|小黑屋|吾爱开源 |网站地图

Copyright 2011 - 2012 Lnqq.NET.All Rights Reserved( ICP备案粤ICP备14042591号-1粤ICP14042591号 )

关于本站 - 版权申明 - 侵删联系 - Ln Studio! - 广告联系

本站资源来自互联网,仅供用户测试使用,相关版权归原作者所有

快速回复 返回顶部 返回列表