Debuggen mit dem GDB

H. Högl, 2017-10-22

Inhalt

etc/gdbtui.png

Das GDB Text User Interface. Man sieht die drei Fenster (von oben): Register, Quelltext und Kommandoeingabe.

1   Quelltext

# main.s
# H. Hoegl, 2017-10-23

# --- BSS section ---
          .section .bss

          .lcomm   buffer,  256


# --- DATA section ---
          .section .data

array:    .long             5, 9, 1, 3, 8 ,7, 10, 6, 12, 11, 0
var1:     .long             42
str1:     .asciz            "ABC"


# --- TEXT section ---
          .section .text

          .globl _start

_start:   movl  var1, %eax
          call  f1

_L1:      incl %eax
          jmp  _L1

f1:       movl  $1, %eax
          ret


# vim: expandtab ts=4 sw=4

2   Lokale .gdbinit Datei im Programm-Verzeichnis

3   Text User Interface (TUI)

Man sollte das TUI einschalten, um übersichtlich Debuggen zu können. Wir bevorzugen das Layout auf dem man von oben nach unten sieht: Register, Quellcode und Kommandoeingabe.

Es gibt zwei Möglichkeiten:

  1. Kommandozeilenoption: --tui

    Auf dem Eingabeprompt anschliessen mehrmals C-x 2 eingeben, damit schaltet man nacheinander die verschiedenen Layouts durch bis das Gewünschte erreicht ist.

  2. .gdbinit

Zum Aktivieren des bei Systemnaher Programmierung verwendeten TUI Layouts kann man in die Datei .gdbinit schreiben:

tui enable
layout regs

(bei moderneren GDB Versionen) oder

tui reg general

(bei älteren Versionen).

Mit C-x o schaltet den Fokus auf ein anderes TUI Fenster um.

4   Ablaufkontrolle

  1. Programm starten und unterbrechen

    # Programm von Anfang an starten
    (gdb) run [args]
    
    # Programm nach breakpoint wieder weiterlaufen lassen
    (gdb) continue
    
    # Laufendes Programm über Tastatur abbrechen
    (gdb) Strg-C
    
  2. Breakpoints

    (gdb) br _start
    (gdb) br 26
    (gdb) br main.s:26
    (gdb) br *<addr>
    (gdb) br f1
    (gdb) br _L1 if $eax == 1000
    
    # Führe Kommandos bei breakpoints aus
    (gdb) br <label>
          commands
          ...
          end
    
    # gibt aktuelle breakpoints aus
    (gdb) info br
    Num     Type           Disp Enb Address    What
    1       breakpoint     keep y   0x0804807a main.s:28
            breakpoint already hit 1 time
    2       breakpoint     keep y   0x08048084 main.s:33
    
    # enable/disable breakpoint
    (gdb) disable 2
    (gdb) enable 2
    
    # breakpoint loeschen
    (gdb) del 2
    
  3. Einzelschritte

    Beachte den Unterschied zwischen step und next:

    • Step bleibt bei einem Funktionsaufruf in der Funktion an der ersten Zeile stehen ("step into function").
    • Bei next wird die komplette Funktion sofort ausgeführt und dann erst bleibt die Ausführung stehen.
    # s(step) - Eine Zeile in einer Hochsprache
    (gdb) s
    
    # s(tep) i(nstruction) - Ein Maschinenbefehl
    (gdb) si
    
    # n(ext)
    (gdb) n
    
    # n(ext) i(nstruction)
    (gdb) ni
    

    Um das Programm wieder mit voller Geschwindigkeit auszuführen, gibt man c(ontinue) ein.

5   Daten anzeigen und ändern

  1. Register

    (gdb) p $eax
    
    (gdb) set $eax = 42
    

    An das $-Zeichen vor dem Registernamen im Debugger denken. Im Assembler muss es hingegen ein %-Zeichen sein.

  2. Variablen

    Die Variable var1 ist in der data section definiert:

             .section  .data
    var1:    .int      42
    

    Hier sind ein paar nützliche gdb Kommandos:

    # var1 ausgeben
    (gdb) p var1
    $1 = 42
    
    # var1 in hex ausgeben
    (gdb) p/x var1
    $1 = 0x2a
    
    # var1 auf Wert setzen
    (gdb) set variable var1 = 43
    set var1 = 43
    
    # short
    (gdb) set var1 = 43
    set var1 = 43
    
    # Adresse von var1 im Speicher ausgeben
    (gdb) p &var1
    $1 = (<data variable, no debug info> *) 0x80490a7
    
    (gdb) set {int}0x80490a7 = 0xAFFE
    (gdb)
    
    (gdb) whatis var1
    type = <data variable, no debug info>
    
    (gdb) ptype var1
    type = <data variable, no debug info>
    
  3. Strings

    Definition:

          .section .data
    str1: .asciz  "ABC"
    

    Kommandos:

    (gdb) p str1
    $6 = 4407873      (was ist das?)
    
    (gdb) p/x str1
    #$8 = 0x434241
    
    (gdb) p/x &str1
    $5 = 0x80490a7
    
    (gdb) x/s &str1
    0x80490a7:      "ABC"
    
    (gdb) x/4c &str1
    0x80490a7:      65 'A'  66 'B'  67 'C'  0 '\000'
    
    (gdb) set (0x80490a7 + 1) = 'm
    (gdb) x/s &str1
    0x80490a7:      "AmC"
    
  4. Arrays

    Ausgeben der Elemente

    (gdb) p array@5
    $2 = {3, 67, 34, 12, 17}
    
    (gdb) p array@6
    $3 = {3, 67, 34, 12, 17, 0}
    
    (gdb) p array@7
    $4 = {3, 67, 34, 12, 17, 0, 1835008}
    

    Ausgeben der Elemente (alternative Syntax)

    (gdb) p {int[6]} &array
    $5 = {3, 67, 34, 12, 17, 0}
    

    Elemente verändern

    (gdb) set *(unsigned int *)0x804909e = 18
    (gdb) p array@5
    $7 = {18, 67, 34, 12, 17}
    
  5. Buffer

    Definiert eine Variable buffer mit 256 Bytes im bss Abschnitt (alle 256 Bytes werden auf 0 initialisiert):

    .section .bss
    .lcomm buffer, 256
    

    GDB Kommandos:

    (gdb) p buffer
    $1 = 0
    
    (gdb) p buffer@256
    $2 = {0 <repeats 256 times>}
    
    (gdb) p (char[256]) buffer
    $1 = '\000' <repeats 255 times>
    
    (gdb) p {char[256]} &buffer
    $1 = '\000' <repeats 255 times>
    
    (gdb) p &buffer
    $3 = (<data variable, no debug info> *) 0x80490d8 <buffer>
    
    (gdb) set {int} (&buffer+3) = 2017
    
    (gdb) x/8xw &buffer
    0x80490d8 <buffer>:     0x00000000      0x00000000      0x00000000      0x000007e1
    0x80490e8 <buffer+16>:  0x00000000      0x00000000      0x00000000      0x00000000
    
    # Vorsicht: 5 Worte offset
    (gdb) set {char}(&buffer+5) = 'A'
    (gdb) x/16xw &buffer
    0x80490e0 <buffer>:     0x00000000      0x00000000      0x00000000      0x00000000
    0x80490f0 <buffer+16>:  0x00000000      0x00000041      0x00000000      0x00000000
    
    # Adresse statt &buffer (5 byte offset)
    set {char[3]}(0x80490e0+5) = {'a', 'b', 'c'}
    
    # Setze drittes Zeichen in Buffer auf 'y'
    set {char}(&(char[1])buffer+3) = 'y'
    
    # Setze ab dem fuenften Buchstaben den String "DEF\0"
    set {char[4]}(&(char[1])buffer+5) = "DEF"
    
    set {char[4]} &buffer = "ABC"
    set {char[4]} (&buffer+4) = "DEF"
    
    # Buffer "dump" (cb = char/byte; xw = hex/word)
    x/32cb &buffer
    x/8xw &buffer
    
    # Vorsicht: malloc
    (gdb) set &buffer+11 = {'a', 'b', 'c'}
    evaluation of this expression requires the program to have a function "malloc".
    
  6. Stack (Kommandozeilenargumente)

    Auf dem gdb Prompt startet man ein Programm mit Argumenten arg1, arg2 und arg3 wie folgt (breakpoint auf _start setzen):

    (gdb) run arg1 arg2 arg3
    
    (gdb) p $esp
    $6 = (void *) 0xbffff4d0
    
    # Anzahl Argumente
    (gdb) p/x *(int*)$esp
    $8 = 0x4
    # Alternative: (gdb) x/1xw $esp
    
    (gdb) x/s *(char**)($esp+4)
    0xbffff637:     "/home/hhoegl/Sysprog/gdb-uebersicht/main"
    
    # Alternative:
    (gdb) x/s {char*}($esp+4)
    0xbffff637:     "/home/hhoegl/Sysprog/gdb-uebersicht/main"
    
    (gdb) x/s *(char**)($esp+8)
    0xbffff660:     "arg1"
    
    (gdb) x/s *(char**)($esp+12)
    0xbffff665:     "arg2"
    
    (gdb) x/s *(char**)($esp+16)
    0xbffff66a:     "arg3"
    

    Anleitung zu type-casts:

    • char* : ist ein Zeiger auf ein oder mehrere Zeichen.
    • char** : ist ein Zeiger der wiederum auf einen Zeiger auf Zeichen zeigt.
    • (char*) : der Zeiger auf Zeiger ist einmal dereferenziert worden, d.h. man moechte den Zeiger auf die Zeichen haben.

6   Sonstiges