C ++ отладка / печать пользовательского типа с помощью GDB: случай библиотеки nlohmann json - PullRequest
15 голосов
/ 23 марта 2019

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

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

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

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

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 Ответ

21 голосов
/ 23 марта 2019

Я нашел свой собственный ответ, читая далее возможности GDB и вопросы переполнения стека, касающиеся print 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's 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's 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 и, что еще важнее, будет эффективным, даже если нет низшего процесса вызова методов из (спасибо Занятый русский для точности). Отладчику не нужно будет изучать новую команду (например, pjson, определенный в кратком ответе).

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


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

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

Вот пример, показывающий, как можно написать принтер std::string. См. Pretty Printing API , для подробностей об 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 (см. Ниже пример кода)

Примечание: он опирается на трюк , предоставленный здесь , который должен разрешать вызов метода класса 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:
        # https://stackoverflow.com/a/22798055/7237062
        # "(*("+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: добавить точность, заданную используемый русский язык.

...