web-dev-qa-db-fra.com

Type personnalisé de débogage / impression C ++ avec GDB: le cas de la bibliothèque jlo de nlohmann

Je travaille sur un projet utilisant implémentation json C++ de nlohmann .

Comment peut-on explorer facilement les clés/valeurs JSON de nlohmann dans GDB?

J'ai essayé d'utiliser ceci habillage STL gdb car il fournit des aides pour explorer les structures de bibliothèque C++ standard que la bibliothèque JSON de nlohmann utilise. Mais je ne le trouve pas pratique.

Voici un cas d'utilisation simple:

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

Ce que j'aimerais avoir dans GDB:

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

Comportement actuel

(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>}
  }
}
18
LoneWanderer

J'ai trouvé ma propre réponse en lisant davantage les capacités GDB et les questions de débordement de pile concernant impression de std :: string . Le chemin court est la meilleure option pour l'instant.

Chemin court v3.1.2

J'ai simplement défini une commande gdb comme suit:

# 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

L'utiliser dans gdb:

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

Chemin court v3.7.0 [EDIT] 2019-onv-06 On peut également utiliser la nouvelle méthode to_string (), mais je n'ai pas pu le faire fonctionner avec GDB avec un processus inférieur en direct. La méthode ci-dessous fonctionne toujours.

# 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, json::error_handler_t::strict).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

Sur le dessus (mais ne fonctionne pas pour moi)

L'autre façon est de définir une jolie imprimante GDB dans python et de la rendre étroitement associée à votre projet (autochargement) Voir ce lien pour une approche approfondie.

Fondamentalement, lorsque dans gdb vous tapez:

(gdb) p foo

et GDB va automagiquement tester le type de foo et appeler la jolie imprimante associée le cas échéant. Cela aboutirait au même résultat. La principale différence est que cela se fait à l'aide de la commande bien connue print et plus important encore, soyez efficace même s'il n'y a pas de processus inférieur pour appeler des méthodes à partir de (merci Employé russe pour la précision). La personne qui débogue n'aurait pas à apprendre une nouvelle commande (comme la pjson définie dans la réponse courte).

Ci-dessous, certains extraits de document GDB + une python qui ne fonctionne pas.


Citant:

Une jolie imprimante se compose de deux parties: une fonction de recherche pour détecter si le type est pris en charge et l'imprimante elle-même.

Voici un exemple montrant comment un std::string l'imprimante peut être écrite. Voir Pretty Printing API , pour plus de détails sur l'API que cette classe doit fournir.

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'

Citant toujours par souci d'exhaustivité:

Et voici un exemple montrant comment une fonction de recherche pour l'exemple d'imprimante ci-dessus pourrait être écrite.

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

J'ai essayé de l'implémenter de cette façon. Cependant, j'ai un taux d'échec de 100% avec le code suivant, avec des messages d'erreur GDB cryptiques (voir ci-dessous l'exemple de code)

Nota: il s'appuie sur le astuce fourni ici qui est censé autoriser un appel de méthode de classe C++ dans GDB, en contournant le Value.Type check (les méthodes objet peuvent être trouvées et leur value.Type serait gdb.TYPE_CODE_METHOD, mais gdb python ne les considérera pas comme appelables. Seulement gdb.TYPE_CODE_FUNC sont appelables. Alors, parse_and_evalagit comme un hack pour effectuer un appel de méthode réel).

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())

Les erreurs:

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.

ou

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

Edit 2019-mars-24: ajoutez la précision donnée par employé russe.

24
LoneWanderer

Ma solution a été de modifier le fichier ~/.gdbinit.

define jsontostring 
   printf "%s\n", $arg0.dump(2, ' ', true, nlohmann::detail::error_handler_t::strict).c_str()
end

Cela rend la commande "jsontostring" disponible sur chaque session gdb sans avoir besoin de sourcer aucun fichier.

(gdb) jsontostring object

0
Lexusminds