С++ отладка/печать пользовательского типа с помощью GDB: случай библиотеки nlohmann json

Я работаю над проектом, использующим реализацию nlohmann json C++.

Как можно легко изучить ключи/значения JSON nlohmann в GDB?

Я попытался использовать это STL gdb wrapping, поскольку он предоставляет помощников для изучения стандартных библиотечных структур C++, которые использует nlohmann JSON lib. Но я не нахожу это удобным.

Вот простой пример использования:

json foo;
foo["flex"] = 0.2;
foo["awesome_str"] = "bleh";
foo["nested"] = {{"bar", "barz"}}; 

Что бы я хотел иметь в GDB:

(gdb) p foo
{
    "flex" : 0.2,
    "awesome_str": "bleh",
    "nested": etc.
}

Текущее поведение

(gdb) p foo
$1 = {
  m_type = nlohmann::detail::value_t::object, 
  m_value = {
    object = 0x129ccdd0, 
    array = 0x129ccdd0, 
    string = 0x129ccdd0, 
    boolean = 208, 
    number_integer = 312266192, 
    number_unsigned = 312266192, 
    number_float = 1.5427999782486669e-315
  }
}
(gdb) p foo.at("flex")
Cannot evaluate function -- may be inlined // I suppose it depends on my compilation process. But I guess it does not invalidate the question.
(gdb) p *foo.m_value.object
$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, 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> > > >> = {
        <__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>}, 
      <std::_Rb_tree_key_compare<std::less<void> >> = {
        _M_key_compare = {<No data fields>}
      }, 
      <std::_Rb_tree_header> = {
        _M_header = {
          _M_color = std::_S_red, 
          _M_parent = 0x4d72d0, 
          _M_left = 0x4d7210, 
          _M_right = 0x4d7270
        }, 
        _M_node_count = 5
      }, <No data fields>}
  }
}

Ответ 1

Я нашел свой собственный ответ, читая далее возможности GDB и вопросы, касающиеся печати std::string. Короткий путь - лучший вариант на данный момент.

Короткий путь

Я просто определил команду GDB следующим образом:

# this is a gdb script
# can be loaded from gdb using
# source my_script.txt (or. gdb or whatever you like)
define pjson
# use the lohmann builtin dump method, ident 4 and use space separator
printf "%s\n", $arg0.dump(4, ' ', true).c_str()
end
# configure command helper (text displayed when typing 'help pjson' in gdb)
document pjson
Prints a lohmann JSON C++ variable as a human-readable JSON string
end

Используя его в GDB:

(gdb) source my_custom_script.gdb
(gdb) pjson foo
{
    "flex" : 0.2,
    "awesome_str": "bleh",
    "nested": {
        "bar": "barz"
    }
}

Сверху (но у меня не работает)

Другой способ - определить симпатичный принтер GDB в python и сделать его тесно связанным с вашим проектом (активирована функция автозагрузки). Смотрите эту ссылку для более глубокого подхода.

В основном, когда в gdb вы наберете:

(gdb) p foo

и GDB автоматически проверит тип foo и вызовет соответствующий симпатичный принтер, если таковой имеется. Это привело бы к тому же результату. Основное отличие состоит в том, что это делается с помощью хорошо известной команды print и, что более важно, будет эффективным, даже если не существует низшего процесса для вызова методов из (спасибо Employed Russian за точность). Отладчику не нужно будет изучать новую команду (например, pjson, определенный в кратком ответе).

Ниже приведено извлечение некоторых документов из GDB + попытка кода Python, которая не работает.


Цитирование:

Pretty-printer состоит из двух частей: функция поиска для определения, поддерживается ли тип, и сам принтер.

Вот пример, показывающий, как можно написать принтер std::string. Подробнее о API, который должен предоставлять этот класс, см. в разделе Pretty Printing API.

class StdStringPrinter(object):
    "Print a std::string"

    def __init__(self, val):
        self.val = val

    def to_string(self):
        return self.val['_M_dataplus']['_M_p']

    def display_hint(self):
        return 'string'

Все еще цитирую ради полноты:

А вот пример, показывающий, как можно написать функцию поиска для приведенного выше примера принтера.

def str_lookup_function(val):
    lookup_tag = val.type.tag
    if lookup_tag == None:
        return None
    regex = re.compile("^std::basic_string<char,.*>$")
    if regex.match(lookup_tag):
        return StdStringPrinter(val)
    return None

Я пытался реализовать это таким образом. Тем не менее, у меня 100% отказов со следующим кодом, с загадочными сообщениями об ошибках GDB (см. ниже пример кода)

Примечание: он опирается на предоставленный здесь трюк trick provided here, который должен разрешать вызов метода класса C++ в GDB, минуя проверку Value.Type (методы объекта можно найти, и их value.Type будет gdb.TYPE_CODE_METHOD, но GDB Python не будет считать их вызываемыми. Только gdb.TYPE_CODE_FUNC могут быть вызваны. Таким образом, parse_and_eval действует как хак для выполнения фактического вызова метода).

import gdb
import re

class StdStringPrinter(object):
    """Print a std::string"""
    def __init__(self, val):
        self.val = val
    def to_string(self):
        eval_string = "(*("+str(self.val.type)+"*)("+str(self.val.address)+")).c_str()" # works 50% of the time ...
        return gdb.parse_and_eval(eval_string)
    def display_hint(self):
        return 'string'

class LohmannJSONPrinter(object):
    """Print a nlohmann::json"""
    def __init__(self, val):
        self.val = val
    def to_string(self):

        # workaround from here:
        # /info/4449405/gdb-python-api-is-it-possible-to-make-a-call-to-a-classstruct-method/10038039#10038039
        # "(*("+str(self.val.type)+"*)("+str(self.val.address)+")).method()"
        eval_string = '(*('+str(self.val.type)+'*)('+str(self.val.address)+')).dump(4, " ", true)'
        return gdb.parse_and_eval(eval_string) # fails 100% of the time
    def display_hint(self):
        return self.val.type

def build_pretty_printer():
    pp = gdb.printing.RegexpCollectionPrettyPrinter("foo")
    json = r"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>"
    pp.add_printer('nlohmann::json', json, LohmannJSONPrinter)
    return pp

# executed at autoload gdb.printing.register_pretty_printer(gdb.current_objfile(),
                                     build_pretty_printer())

Ошибки:

Cannot insert breakpoint -18. // or any negative value
Cannot access memory at address 0x111a2180 // appears to be a fixed value at each execution
Python Exception <class 'gdb.error'> Command aborted.

или

$2 = Python Exception <class 'gdb.error'> Attempt to take address of value not located in memory.:

Изменить 2019-март-24: добавить точность, заданную занятым русским языком.