$30 off During Our Annual Pro Sale. View Details »

Va debugger ton Python!

Va debugger ton Python!

Cette presentation vous explique les bases de Pdb ainsi que GDB, afin de debugger plus facilement vos scripts Python.

Stéphane Wirtel

September 27, 2017
Tweet

More Decks by Stéphane Wirtel

Other Decks in Programming

Transcript

  1. Va debugger ton
    par
    Stéphane Wirtel
    PyCon FR 2017 - Toulouse - 23/09/2017
    1 / 107

    View Slide

  2. Salut, je suis Stéphane
    par ma fille
    1 / 107

    View Slide

  3. Qui aime developper... ?
    2 / 107

    View Slide

  4. Debugger c'est Hard!
    3 / 107

    View Slide

  5. Que fait ce code ?
    # Example 1
    def main():
    for i in range(1, 4):
    i /= 4
    if i == 4:
    print('Salut PyConFR 2017')
    if __name__ == '__main__':
    main()
    4 / 107

    View Slide

  6. Que fait ce code ?
    # Example 1
    def main():
    for i in range(1, 4):
    i /= 4
    if i == 4:
    print('Salut PyConFR 2017')
    if __name__ == '__main__':
    main()
    $ python example1.py
    $
    5 / 107

    View Slide

  7. La sacro-sainte fonction print()
    6 / 107

    View Slide

  8. La sacro-sainte fonction print()
    # Example 2
    def main():
    for i in range(1, 4):
    i /= 4
    print(i)
    if i == 4:
    print('Salut PyConFR 2017')
    7 / 107

    View Slide

  9. La sacro-sainte fonction print()
    # Example 2
    def main():
    for i in range(1, 4):
    i /= 4
    print(i)
    if i == 4:
    print('Salut PyConFR 2017')
    $ python example2.py
    0
    0
    0
    $
    8 / 107

    View Slide

  10. Le module logging
    9 / 107

    View Slide

  11. Utilisons logging
    # Example 3
    import logging
    def main():
    logging.basicConfig(level=logging.INFO)
    logging.info('start program')
    logging.debug("before loop")
    for i in range(1, 4):
    i /= 4
    logging.debug(i)
    logging.debug("after loop")
    if i == 4:
    print("Hello PyConFR 2017")
    logging.debug("Hello PyConFR 2017")
    logging.info('end program')
    if __name__ == '__main__':
    main()
    10 / 107

    View Slide

  12. Utilisons logging
    # Example 3
    import logging
    def main():
    logging.basicConfig(level=logging.INFO)
    logging.info('start program')
    logging.debug("before loop")
    for i in range(1, 4):
    i /= 4
    logging.debug(i)
    logging.debug("after loop")
    if i == 4:
    print("Hello PyConFR 2017")
    logging.debug("Hello PyConFR 2017")
    logging.info('end program')
    if __name__ == '__main__':
    main()
    INFO:root:start program
    INFO:root:end program
    11 / 107

    View Slide

  13. Utilisons logging
    # Example 4
    import logging
    def main():
    logging.basicConfig(level=logging.DEBUG)
    logging.info('start program')
    logging.debug("before loop")
    for i in range(1, 4):
    i /= 4
    logging.debug(i)
    logging.debug("after loop")
    if i == 4:
    print("Hello PyConFR 2017")
    logging.debug("Hello PyConFR 2017")
    logging.info('end program')
    if __name__ == '__main__':
    main()
    12 / 107

    View Slide

  14. Utilisons logging
    INFO:root:start program
    DEBUG:root:before loop
    DEBUG:root:0
    DEBUG:root:0
    DEBUG:root:0
    DEBUG:root:after loop
    INFO:root:end program
    13 / 107

    View Slide

  15. Utilisons logging
    INFO:root:start program
    DEBUG:root:before loop
    DEBUG:root:0
    DEBUG:root:0
    DEBUG:root:0
    DEBUG:root:after loop
    INFO:root:end program
    Obligé de modifier le niveau de log pour voir ce qu'il se passe.
    14 / 107

    View Slide

  16. Cool
    mais
    Modification du code
    Execution
    Oubli de supprimer le print en production...
    15 / 107

    View Slide

  17. que faire, quoi utiliser...
    16 / 107

    View Slide

  18. Un debugger!
    17 / 107

    View Slide

  19. PDB
    Python Debugger est l'outil natif pour aider les développeurs à trouver et
    corriger les bugs de leur logiciel fétiche (ou celui que l'on récupère d'un ancien
    collègue ou prestataire...)
    18 / 107

    View Slide

  20. Eh oui, Python vient avec un debugger!
    19 / 107

    View Slide

  21. Comment faire
    import pdb; pdb.set_trace()
    20 / 107

    View Slide

  22. Comment faire
    import pdb; pdb.set_trace()
    # Example 5
    import pdb
    def main():
    for i in range(1, 4):
    i /= 4
    if i == 4:
    print("Hello PyConFR 2017")
    if __name__ == '__main__':
    pdb.set_trace()
    main()
    21 / 107

    View Slide

  23. Comment faire
    import pdb; pdb.set_trace()
    # Example 5
    import pdb
    def main():
    for i in range(1, 4):
    i /= 4
    if i == 4:
    print("Hello PyConFR 2017")
    if __name__ == '__main__':
    pdb.set_trace()
    main()
    $ python example5.py
    > example5.py(7)main()
    -> for i in range(1, 4):
    22 / 107

    View Slide

  24. Comment faire
    Example 6
    import pdb
    def do_something():
    raise Exception("bug")
    def main():
    try:
    do_something()
    except Exception:
    pdb.post_mortem()
    if __name__ == '__main__':
    main()
    23 / 107

    View Slide

  25. Comment faire
    Example 6
    import pdb
    def do_something():
    raise Exception("bug")
    def main():
    try:
    do_something()
    except Exception:
    pdb.post_mortem()
    if __name__ == '__main__':
    main()
    > example6.py(4)do_something()
    -> raise Exception("bug")
    (Pdb) bt
    example6.py(8)main()
    -> do_something()
    > example6.py(4)do_something()
    -> raise Exception("bug")
    (Pdb) ll
    3 def do_something():
    4 -> raise Exception("bug")
    (Pdb)
    24 / 107

    View Slide

  26. Comment faire
    On peut aussi charger pdb comme un module à la ligne de commande.
    $ python -m pdb
    $ python -m pdb -c "command"
    25 / 107

    View Slide

  27. Prenons un exemple lisant un .csv
    26 / 107

    View Slide

  28. Prenons un exemple lisant un .csv
    import pendulum
    import csv
    class Event:
    def __init__(self, name, date):
    self.name = name
    self.date = pendulum.parse(date)
    def get_events(stream):
    for row in csv.reader(stream):
    yield Event(*row)
    def main():
    with open('events.csv') as stream:
    for event in get_events(stream):
    print(event.name, event.date)
    if __name__ == '__main__':
    main()
    27 / 107

    View Slide

  29. Prenons un exemple lisant un .csv
    import pendulum
    import csv
    class Event:
    def __init__(self, name, date):
    self.name = name
    self.date = pendulum.parse(date)
    def get_events(stream):
    for row in csv.reader(stream):
    yield Event(*row)
    def main():
    with open('events.csv') as stream:
    for event in get_events(stream):
    print(event.name, event.date)
    if __name__ == '__main__':
    main()
    PyCon France,2017-09-21
    PyCon Canada,2017-11-18
    PyCon US,2017-05-17
    Python FOSDEM,2018-02-03
    Python TOTO,x
    28 / 107

    View Slide

  30. Prenons un exemple lisant un .csv
    $ python events.py
    Non d'un petit Python, CRASH avec deux exceptions :/
    PyCon France 2017-09-21T00:00:00+00:00
    PyCon Canada 2017-11-18T00:00:00+00:00
    PyCon US 2017-05-17T00:00:00+00:00
    Python FOSDEM 2018-02-03T00:00:00+00:00
    File "/home/stephane/.virtualenvs/flask/lib/python3.6/site-packages/dateutil/par
    raise ValueError("Unknown string format")
    ValueError: Unknown string format
    During handling of the above exception, another exception occurred:
    File "/home/stephane/.virtualenvs/flask/lib/python3.6/site-packages/pendulum/par
    raise ParserError('Invalid date string: {}'.format(text))
    pendulum.parsing.exceptions.ParserError: Invalid date string: x
    29 / 107

    View Slide

  31. Je débugge
    $ python -m pdb events.py
    > events.py(1)()
    -> import pendulum
    (Pdb)
    30 / 107

    View Slide

  32. Je liste mon code avec l ou ll
    (Pdb) ll
    1 -> import pendulum
    2 import csv
    3
    4 class Event:
    5 def __init__(self, name, date):
    6 self.name = name
    7 self.date = pendulum.parse(date)
    8
    9 def get_events(stream):
    10 for row in csv.reader(stream):
    11 yield Event(*row)
    12
    13 def main():
    14 with open('events.csv') as stream:
    15 for event in get_events(stream):
    16 print(event.name, event.date)
    17
    18 if __name__ == '__main__':
    19 main()
    31 / 107

    View Slide

  33. J'avance d'une ligne
    (Pdb) next
    > events.py(2)()
    -> import csv
    32 / 107

    View Slide

  34. J'avance d'une ligne
    (Pdb) next
    > events.py(2)()
    -> import csv
    (Pdb) ll
    1 import pendulum
    2 -> import csv
    3
    4 class Event:
    5 def __init__(self, name, date):
    6 self.name = name
    7 self.date = pendulum.parse(date)
    8
    9 def get_events(stream):
    10 for row in csv.reader(stream):
    11 yield Event(*row)
    12
    13 def main():
    14 with open('events.csv') as stream:
    15 for event in get_events(stream):
    16 print(event.name, event.date)
    17
    18 if __name__ == '__main__':
    19 main()
    33 / 107

    View Slide

  35. On saute dans le code
    -> def main():
    (Pdb) ll
    15 -> def main():
    16 with open('events.csv') as stream:
    17 for event in get_events(stream):
    18 print(event.name, event.date)
    19 print(Event.id)
    (Pdb) step
    > events.py(16)main()
    -> with open('events.csv') as stream:
    (Pdb) ll
    15 def main():
    16 -> with open('events.csv') as stream:
    17 for event in get_events(stream):
    18 print(event.name, event.date)
    19 print(Event.id)
    34 / 107

    View Slide

  36. On saute dans le code
    (Pdb) next
    > events.py(17)main()
    -> for event in get_events(stream):
    (Pdb) ll
    15 def main():
    16 with open('events.csv') as stream:
    17 -> for event in get_events(stream):
    18 print(event.name, event.date)
    19 print(Event.id)
    (Pdb) step
    --Call--
    > events.py(11)get_events()
    -> def get_events(stream):
    (Pdb) ll
    11 -> def get_events(stream):
    12 for row in csv.reader(stream):
    13 yield Event(*row)
    (Pdb) next
    > events.py(12)get_events()
    -> for row in csv.reader(stream):
    (Pdb) ll
    11 def get_events(stream):
    12 -> for row in csv.reader(stream):
    13 yield Event(*row)
    (Pdb)
    35 / 107

    View Slide

  37. Breakpoints
    Je reprends le code de base
    36 / 107

    View Slide

  38. Breakpoints
    (Pdb) ll
    1 import pendulum
    2 import csv
    3
    4 class Event:
    5 def __init__(self, name, date):
    6 self.name = name
    7 self.date = pendulum.parse(date)
    8
    9 def get_events(stream):
    10 for row in csv.reader(stream):
    11 yield Event(*row)
    12
    13 def main():
    14 with open('events.csv') as stream:
    15 for event in get_events(stream):
    16 print(event.name, event.date)
    17
    18 if __name__ == '__main__':
    19 main()
    37 / 107

    View Slide

  39. Breakpoints
    Lister
    > events.py(1)()
    -> import pendulum
    (Pdb) break
    (Pdb)
    38 / 107

    View Slide

  40. Breakpoints
    Ajouter
    > events.py(1)()
    -> import pendulum
    (Pdb) break 9
    Breakpoint 1 at events.py:9
    (Pdb) l
    4 class Event:
    5 id = 0
    6 def __init__(self, name, date):
    7 self.id += 1
    8 self.name = name
    9 B-> self.date = pendulum.parse(date)
    10
    11 def get_events(stream):
    12 for row in csv.reader(stream):
    13 yield Event(*row)
    39 / 107

    View Slide

  41. Breakpoints
    Ajouter
    > events.py(1)()
    -> import pendulum
    (Pdb) break 9
    Breakpoint 1 at events.py:9
    (Pdb) l
    4 class Event:
    5 id = 0
    6 def __init__(self, name, date):
    7 self.id += 1
    8 self.name = name
    9 B-> self.date = pendulum.parse(date)
    10
    11 def get_events(stream):
    12 for row in csv.reader(stream):
    13 yield Event(*row)
    (Pdb) break
    Num Type Disp Enb Where
    1 breakpoint keep yes at events.py:9
    40 / 107

    View Slide

  42. Breakpoints
    Désactiver un breakpoint
    (Pdb) disable 1
    Disabled breakpoint 1 at events.py:9
    41 / 107

    View Slide

  43. Breakpoints
    Désactiver un breakpoint
    (Pdb) disable 1
    Disabled breakpoint 1 at events.py:9
    (Pdb) break
    Num Type Disp Enb Where
    1 breakpoint keep no at events.py:9
    42 / 107

    View Slide

  44. Breakpoints
    Activer un breakpoint
    (Pdb) enable 1
    Enabled breakpoint 1 at events.py:9
    43 / 107

    View Slide

  45. Breakpoints
    Activer un breakpoint
    (Pdb) enable 1
    Enabled breakpoint 1 at events.py:9
    (Pdb) break
    Num Type Disp Enb Where
    1 breakpoint keep yes at events.py:9
    44 / 107

    View Slide

  46. Je continue
    (Pdb) continue
    > events.py(9)__init__()
    -> self.date = pendulum.parse(date)
    45 / 107

    View Slide

  47. J'examine
    (Pdb) args
    self = <__main__.Event object at 0x7f23cf8cc908>
    name = 'PyCon France'
    date = '2017-09-21'
    46 / 107

    View Slide

  48. J'examine
    (Pdb) args
    self = <__main__.Event object at 0x7f23cf8cc908>
    name = 'PyCon France'
    date = '2017-09-21'
    (Pdb) whatis name

    (Pdb) whatis self

    47 / 107

    View Slide

  49. Nouvelles commandes avec alias
    alias d pp dir(%1)
    48 / 107

    View Slide

  50. Nouvelles commandes avec alias
    alias d pp dir(%1)
    (Pdb) b 9
    Breakpoint 1 at events.py:9
    (Pdb) c
    > events.py(9)__init__()
    -> self.date = pendulum.parse(date)
    49 / 107

    View Slide

  51. Nouvelles commandes avec alias
    alias d pp dir(%1)
    (Pdb) b 9
    Breakpoint 1 at events.py:9
    (Pdb) c
    > events.py(9)__init__()
    -> self.date = pendulum.parse(date)
    (Pdb) d self.__dict__
    ['__class__',
    '__contains__',
    '__delattr__',
    ...
    ]
    50 / 107

    View Slide

  52. Nouvelles commandes avec alias
    alias loc !list(locals().keys())
    51 / 107

    View Slide

  53. Nouvelles commandes avec alias
    alias loc !list(locals().keys())
    (Pdb) loc
    dict_keys(['date', 'name', 'self'])
    52 / 107

    View Slide

  54. Nouvelles commandes avec alias
    alias printdict for key, value in %1.items(): print(f"{key}:{value}")
    53 / 107

    View Slide

  55. Nouvelles commandes avec alias
    alias printdict for key, value in %1.items(): print(f"{key}:{value}")
    (Pdb) printdict self.__dict__
    id:1
    name:PyCon France
    date:2017-09-21T00:00:00+00:00
    54 / 107

    View Slide

  56. Réutilisation d'alias
    alias pd printdict %1
    55 / 107

    View Slide

  57. Réutilisation d'alias
    alias pd printdict %1
    (Pdb) pd self.__dict__
    id:1
    name:PyCon France
    date:2017-09-21T00:00:00+00:00
    56 / 107

    View Slide

  58. Breakpoint et une action
    Possibilité de définir une action lors d'un breakpoint
    commands [bpnumber]
    p self.__dict__
    printdict self.__dict__
    end
    57 / 107

    View Slide

  59. Démonstration
    58 / 107

    View Slide

  60. PDB
    Récapitulatif des commandes
    59 / 107

    View Slide

  61. Les importantes
    Commande Description
    h(elp) Affiche l'aide
    h(elp) command Affiche l'aide d'une commande
    q(uit) Quitte le debuggeur
    60 / 107

    View Slide

  62. Examiner
    Commande Description
    p(rint) expr Affiche la valeur de l'expression
    pp expr Pretty-print de la valeur de l'expression
    l(ist) Affiche une partie du code
    ll Affiche toute le code de la fonction
    a(rgs) Affiche la liste des arguments de la fonction
    whatis arg Affiche le type de l'argument
    where Mais ou se trouve l'exception
    source Affiche la source d'une fonction
    61 / 107

    View Slide

  63. Naviguer
    Commande Description
    n(ext) Exécute l'instruction
    s(tep) Exécute et rentre dans la fonction/méthode
    r(eturn) Continue l'exécution jusqu'au return
    c(ontinue) Continue l'exécution jusqu'à un breakpoint
    u(p) Remonte d'un niveau dans la stack trace
    d(own) Descend d'un niveau dans la stack trace
    j(ump) lineno Saute a la ligne
    62 / 107

    View Slide

  64. Breakpoints
    Commande Description
    b(reak) Montre tous les breakpoints
    63 / 107

    View Slide

  65. Breakpoints
    Commande Description
    b(reak) lineno Place un breakpoint à lineno
    b(reak) lineno,
    cond
    Place un breakpoint à lineo si cond est True
    b(reak)
    file:lineno
    Place un breakpoint dans le fichier file à la ligne
    lineno
    b(reak) func
    Place un breakpoint à la première ligne de la
    fonction func
    tbreak lineno Place un breakpoint et l'enleve dès que l'on y est
    64 / 107

    View Slide

  66. Breakpoints
    Commande Description
    clear bpnumber Supprime un breakpoint
    disable bpnumber Désactive un breakpoint
    enable bpnumber Active un breakpoint
    ignore bpnumber count Ignore le breakpoint X fois
    condition bpnumber condition Permet de modifer le breakpoint
    65 / 107

    View Slide

  67. Celles que vous ne connaissez pas ;-)
    Commande Description
    !expr
    Execute une expression dans l'interpreteur
    Python
    alias Créer une nouvelle commande PDB
    unalias Supprime un alias
    commands
    [bpnumber]
    Assigne des commandes à un breakpoint
    restart or run Redémarre le script
    66 / 107

    View Slide

  68. Fichier de con guration
    alias d pp dir(%1)
    alias ps d self.__dict__
    alias loc !list(locals().keys())
    alias printdict for key, value in %1.items(): print(f"{key}:{value}")
    Voir $HOME/.pdbrc ou ./.pdbrc
    67 / 107

    View Slide

  69. Il y a un truc amusant
    68 / 107

    View Slide

  70. Ça fonctionne avec ipdb
    69 / 107

    View Slide

  71. Les alternatives - ipdb
    Auto-completion
    Coloration syntaxique
    Meilleurs tracebacks
    Meilleur introspection
    Commandes supplémentaires
    https://github.com/gotcha/ipdb
    70 / 107

    View Slide

  72. Les alternatives - pudb
    https://github.com/inducer/pudb
    71 / 107

    View Slide

  73. Les alternatives - rdb
    import pdb, socket, sys # noqa --> Pas PEP8
    class Rdb(pdb.Pdb):
    def __init__(self, port=4444):
    self.old_stdout = sys.stdout
    self.old_stdin = sys.stdin
    self.skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.skt.bind(('localhost', port))
    self.skt.listen(1)
    (clientsocket, address) = self.skt.accept()
    handle = clientsocket.makefile('rw')
    pdb.Pdb.__init__(self, completekey='tab', stdin=handle, stdout=handle)
    sys.stdout = sys.stdin = handle
    def do_continue(self, arg):
    sys.stdout = self.old_stdout
    sys.stdin = self.old_stdin
    self.skt.close()
    self.set_continue()
    return 1
    do_c = do_cont = do_continue
    https://dzone.com/articles/remote-debugging-python-using
    72 / 107

    View Slide

  74. Les alternatives - rdb (2)
    # Example usage - connect with 'telnet 4444'
    if __name__=='__main__':
    def buggy_method():
    x = 3
    remote_debug = Rdb()
    remote_debug.set_trace()
    print(x)
    buggy_method()
    https://dzone.com/articles/remote-debugging-python-using
    73 / 107

    View Slide

  75. Les alternatives - rdb (3)
    telnet localhost 4444
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    > rdb.py(32)buggy_method()
    -> print(x)
    (Pdb) ll
    28 def buggy_method():
    29 x = 3
    30 remote_debug = Rdb()
    31 remote_debug.set_trace()
    32 -> print(x)
    (Pdb) p x
    3
    (Pdb) quit
    Connection closed by foreign host.
    Meilleure implémentation dans
    https://github.com/celery/celery/blob/master/celery/contrib/rdb.py
    https://dzone.com/articles/remote-debugging-python-using
    74 / 107

    View Slide

  76. Les alternatives - wdb
    https://github.com/Kozea/wdb
    75 / 107

    View Slide

  77. Les alternatives - pdbpp
    https://github.com/antocuni/pdb
    76 / 107

    View Slide

  78. pdb ou ipdb avec du dev Web
    Par exemple, Django
    django-pdb avec --ipdb ou --pdb
    ou simplement
    python -m pdb ./manage.py test
    python -m ipdb ./manage.py test
    python -m pudb ./manage.py test
    77 / 107

    View Slide

  79. GNU Debugger
    Également nommé gdb, GNU Debugger, est le débuggeur standard du projet
    GNU, portable et fonctionne pour plusieurs langages, comme le C, le C++ et aussi
    le Python ;-)
    78 / 107

    View Slide

  80. GNU Debugger
    gdb >= 7 , support natif de Python
    79 / 107

    View Slide

  81. GNU Debugger
    Avoir les symboles de debug de Python
    $ dnf install python3-debug
    80 / 107

    View Slide

  82. GNU Debugger
    Avoir les symboles de debug de Python
    $ dnf install python3-debug
    ou
    $ ./configure --prefix=$PWD-build --with-pydebug
    $ make
    $ make install
    81 / 107

    View Slide

  83. GNU Debugger
    Python + gdb = ?
    82 / 107

    View Slide

  84. GNU Debugger
    Python + gdb = ?
    1. il y a une librairie pour debugger votre script Python depuis gdb
    83 / 107

    View Slide

  85. GNU Debugger
    Python + gdb = ?
    1. il y a une librairie pour debugger votre script Python depuis gdb
    2. gdb est extensible via Python
    84 / 107

    View Slide

  86. Que faire avec gdb ?
    Commande Description
    py-bt Montre la stack trace Python
    py-bt-full py-bt mais en plus verbeux
    py-list Liste le code Python
    py-print Affiche le contenu d'une variable Python
    py-locals Affiche les locals
    py-up Remonte d'une frame
    py-down Descend d'une frame
    85 / 107

    View Slide

  87. Le code
    import time
    import os
    def next(i):
    print(f"{os.getpid()} {time.strftime('%H:%M:%S')}")
    time.sleep(10)
    i = 1 - i
    i = 1
    while True:
    next(i)
    86 / 107

    View Slide

  88. Le code
    import time
    import os
    def next(i):
    print(f"{os.getpid()} {time.strftime('%H:%M:%S')}")
    time.sleep(10)
    i = 1 - i
    i = 1
    while True:
    next(i)
    $ python ex_loop1.py
    10619 06:14:24
    10619 06:14:34
    10619 06:14:44
    10619 06:14:54
    10619 06:15:04
    10619 06:15:14
    PID 10619
    87 / 107

    View Slide

  89. Comment faire ?
    $ gdb -p 10619
    GNU gdb (GDB) Fedora 8.0.1-26.fc26
    ...
    For help, type "help".
    Type "apropos word" to search for commands related to "word".
    Attaching to process 10619
    Reading symbols from /usr/bin/python3.6dm...Reading symbols from /usr/lib/debug/us
    Reading symbols from /lib64/libpython3.6dm.so.1.0...Reading symbols from /usr/lib/d
    Reading symbols from /lib64/libpthread.so.0...Reading symbols from /usr/lib/debug/
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib64/libthread_db.so.1".
    Reading symbols from /lib64/libdl.so.2...Reading symbols from /usr/lib/debug/usr/li
    Reading symbols from /lib64/libutil.so.1...Reading symbols from /usr/lib/debug/usr
    Reading symbols from /lib64/libm.so.6...Reading symbols from /usr/lib/debug/usr/li
    Reading symbols from /lib64/libc.so.6...Reading symbols from /usr/lib/debug/usr/li
    Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/de
    0x00007f40dc696ce3 in __select_nocancel () at ../sysdeps/unix/syscall-template.S:84
    84 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
    (gdb)
    88 / 107

    View Slide

  90. A cher le code
    (gdb) py-list
    1 import time
    2 import os
    3
    4 def next(i):
    5 print(f"{os.getpid()} {time.strftime('%H:%M:%S')}")
    >6 time.sleep(10)
    7 i = 1 - i
    8
    9 i = 1
    10 while True:
    11 next(i)
    (gdb)
    89 / 107

    View Slide

  91. A cher la traceback
    (gdb) py-bt
    Traceback (most recent call first):

    File "ex_loop1.py", line 6, in next
    time.sleep(10)
    File "ex_loop1.py", line 11, in
    next(i)
    90 / 107

    View Slide

  92. A cher la traceback avec plus d'infos
    (gdb) py-bt-full
    #3
    #7 Frame 0x55c890804748, for file ex_loop1.py, line 6, in next (i=1)
    time.sleep(10)
    #12 Frame 0x55c890804458, for file ex_loop1.py, line 11, in ()
    next(i)
    91 / 107

    View Slide

  93. A cher la traceback avec plus d'infos
    (gdb) py-bt-full
    #3
    #7 Frame 0x55c890804748, for file ex_loop1.py, line 6, in next (i=1)
    time.sleep(10)
    #12 Frame 0x55c890804458, for file ex_loop1.py, line 11, in ()
    next(i)
    (gdb) select-frame 7
    (gdb) info locals
    tstate = 0x55c8907a3cf0
    (gdb) select-frame 3
    (gdb) info locals
    func = 0x7f40d5974418
    meth = 0x7f40dd57c6c6
    self =
    result = 'sleep'
    flags = 8
    __PRETTY_FUNCTION__ = "_PyCFunction_FastCallDict"
    92 / 107

    View Slide

  94. Examiner une frame
    (gdb) py-bt-full
    #3
    #7 Frame 0x55706036f748, for file ex_loop1.py, line 6, in next (i=1)
    time.sleep(10)
    #12 Frame 0x55706036f458, for file ex_loop1.py, line 11, in ()
    next(i)
    (gdb) select-frame 7
    (gdb) py-list
    1 import time
    2 import os
    3
    4 def next(i):
    5 print(f"{os.getpid()} {time.strftime('%H:%M:%S')}")
    >6 time.sleep(10)
    7 i = 1 - i
    8
    9 i = 1
    10 while True:
    11 next(i)
    (gdb) py-print i
    local 'i' = 1
    (gdb) py-locals
    i = 1
    93 / 107

    View Slide

  95. Navigation
    (gdb) py-down
    Unable to find a newer python frame
    (gdb) py-up
    #7 Frame 0x55c890804748, for file ex_loop1.py, line 6, in next (i=1)
    time.sleep(10)
    (gdb) py-down
    #3
    94 / 107

    View Slide

  96. Backtrace de CPython
    (gdb) bt
    #0 0x00007f40dc696ce3 in __select_nocancel () at ../sysdeps/unix/syscall-template
    #1 0x00007f40dd57e7f8 in pysleep (secs=10000000000)
    at /usr/src/debug/Python-3.6.2/Modules/timemodule.c:1438
    #2 0x00007f40dd57c73d in time_sleep (self=, obj=
    at /usr/src/debug/Python-3.6.2/Modules/timemodule.c:235
    #3 0x00007f40dd39a9af in _PyCFunction_FastCallDict (
    func_obj=,
    args=0x55c8908048e0, nargs=1, kwargs=0x0)
    at /usr/src/debug/Python-3.6.2/Objects/methodobject.c:209
    #4 0x00007f40dd39ada6 in _PyCFunction_FastCallKeywords (
    func=,
    stack=0x55c8908048e0, nargs=1, kwnames=0x0)
    at /usr/src/debug/Python-3.6.2/Objects/methodobject.c:294
    #5 0x00007f40dd4b046e in call_function (pp_stack=0x7ffd7bcab7b8, oparg=1, kwnames=
    at /usr/src/debug/Python-3.6.2/Python/ceval.c:4809
    #6 0x00007f40dd4a8046 in _PyEval_EvalFrameDefault (
    f=Frame 0x55c890804748, for file ex_loop1.py, line 6, in next (i=1), throwflag=
    at /usr/src/debug/Python-3.6.2/Python/ceval.c:3295
    #7 0x00007f40dd494118 in PyEval_EvalFrameEx (
    f=Frame 0x55c890804748, for file ex_loop1.py, line 6, in next (i=1), throwflag=
    at /usr/src/debug/Python-3.6.2/Python/ceval.c:718
    95 / 107

    View Slide

  97. Threads
    (gdb) info threads
    Id Target Id Frame
    1 Thread 0x7f40ddaef700 (LWP 10619) "python3-debug" 0x00007f40dd39a9af in _PyC
    args=0x55c8908048e0, nargs=1, kwargs=0x0)
    at /usr/src/debug/Python-3.6.2/Objects/methodobject.c:209
    96 / 107

    View Slide

  98. Dumper les stack traces des threads
    (gdb) thread apply all py-bt
    Thread 1 (Thread 0x7f40ddaef700 (LWP 10619)):
    Traceback (most recent call first):

    File "ex_loop1.py", line 6, in next
    time.sleep(10)
    File "ex_loop1.py", line 11, in
    next(i)
    (gdb)
    97 / 107

    View Slide

  99. Débugger à distance
    98 / 107

    View Slide

  100. Mode Remote - gdbserver
    $ gdbserver --multi host:2345
    #
    # ou ces modes-ci
    #
    $ gdbserver host:2345 process_name
    $ gdbserver host:2345 --attach PID
    99 / 107

    View Slide

  101. Mode Remote - gdbserver
    $ gdbserver --multi host:2345
    #
    # ou ces modes-ci
    #
    $ gdbserver host:2345 process_name
    $ gdbserver host:2345 --attach PID
    $ gdb
    (gdb) target extended-remote host:2345
    (gdb) attach PID
    100 / 107

    View Slide

  102. Mode Remote - gdbserver
    $ gdbserver --multi host:2345
    #
    # ou ces modes-ci
    #
    $ gdbserver host:2345 process_name
    $ gdbserver host:2345 --attach PID
    $ gdb
    (gdb) target extended-remote host:2345
    (gdb) attach PID
    (gdb) py-list
    1 import time
    2 import os
    3
    4 def next(i):
    5 print(f"{os.getpid()} {time.strftime('%H:%M:%S')}")
    >6 time.sleep(10)
    7 i = 1 - i
    8
    9 i = 1
    10 while True:
    11 next(i)
    (gdb)
    101 / 107

    View Slide

  103. Extensible ?
    102 / 107

    View Slide

  104. Extensible ? - Oui
    https://github.com/cyrus-and/gdb-dashboardOui
    103 / 107

    View Slide

  105. Alternatives
    104 / 107

    View Slide

  106. gdbgui
    pip install gdbgui
    105 / 107

    View Slide

  107. Prochaines talks pour 2018
    SystemTap et DTrace pour mon Python
    Que fait mon débuggeur Python?
    GDB en profondeur
    106 / 107

    View Slide

  108. Questions ?
    [email protected]
    107 / 107

    View Slide