Commit 53970bc6 authored by Jonas Hundseder's avatar Jonas Hundseder
Browse files

Kapitel Debugging erlaeutert

parent 0cfaeb45
# Technische Vorraussetzungen
* Linux basierendes Host-System mit 60 GB freiem Speicherplatz
* Buildroot 2020.02.9 LTS release
* Yocto 3.1 (Dunfell) LTS release
* Etcher für Linux
* MicroSD Card Reader und Karte
* BeagleBone Black
* 5V 1A DC Spannungsversorgung
* Ethernet Kabel
# Der GNU debugger
Der GDB ist ein Debugger für kompilierte Sprachen, vorallem für C und C++.
Die [Projektwebseite](https://www.gnu.org/software/gdb/) enthält viele nützliche Informationen, sowie ein User Manual.
GDB besitzt ein command-line User Interface. Zudem gibt es weitere Frontend User Interfaces mit graphischer Benutzeroberfläche.
# Vorbereitungen für Debugging
Der Source Code muss mit `-g` oder `-ggdb` kompiliert werden. Letzteres fügt allgemeine Debug Information spezifisch für GDB hinzu, während erstes Informationen generiert, welche Betriebssystem unabhängig sind und somit dies mehr portabel ist.
Zudem kann bei beiden ein Debug-Level angefügt werden von 0 bis 3:
0. keine Debug Information. Gleiches Verhalten, wie wenn der Switch weggelassen wird
1. minimale Debug Information. Inkludiert Funktionsnamen und externe Variablen, genug um ein Backtrace zu generieren
2. Standardeinstellung. Inkludiert Informationen über lokale Variablen und Zeilennummern, damit Source-Level Debugging und Single-Step Debugging durch den Code ermöglicht wird
3. Inkludiert extra Informationen über andere Dinge, womit GDB Macro Expressions richtig behandeln kann
Zumeist ist die Standardeinstellung genug, wenn jedoch Probleme beim Steppen durch den Code entstehen, vorallem bei Makros, erweitere es auf `-g3` bzw. `-ggdb3`.
Auch muss die Kompiler Optimierung betrachtet werden. Kompiler Optimierungen tendieren dazu, die Relation zwischen Source Code und Maschinencode zu zerstören, wodurch ein schrittweises Debugging erschwert wird. Tritt sowas auf, muss ohne Optimierung kompiliert werden (`-O`weglassen, bzw. `-Og` benutzen, womit Optimierungen veranlasst werden, die nicht mit Debugging interferieren).
Auch werden für das Debugging mit GDB *stack-frame pointer* benötigt, um ein backtrace der aufgerufenen Funktionen darzustellen. Diese sind bei bestimmten Architekturen und höhereren Grad der Optimierung nicht mehr enthalten (`-O2` oder höher).
# Debugging von Applikationen
Wenn die Aplikationen auf einem Server bzw. Desktop System laufen, können diese nativ gedebugged werden. Jedoch ist meistens in embedded Development ein Cross-Toolchain verwendet und man möchte bestehenden Code auf dem Zielsystem verwenden.
## Remote Debugging mit gdbserver
Dieser Server läuft auf dem Zielsystem und kontrolliert das Programm, welches gedebugged werden muss. `gdbserver` verbindet sich mit einer Kopie von GDB, welches auf dem Host-System läuft, entweder über eine Netzwerkverbindung oder über ein serielles Interface.
# Konfigurieren des Yocto Projectes für Remote debugging
Dazu muss `gdbserver` zum Target-Image hinzugefügt werden. zudem muss eine `SDK` generiert werden, welches Debug Symbole für die Executables generiert.
Inkludieren von `gdbserver` in der `conf/local.conf`:
```bash
IMAGE_INSTALL_append = "gdbserver"
# Ein SSH daemon muss hinzugefuegt werden
EXTRA_IMAGE_FEATURES ?= "ssh-server-openssh"
# Als Alternative kann auch gdbserver, native gdb und strace angefuegt werden
EXTRA_IMAGE_FEATURES ?= "tools-debug ssh-server-openssh"
# SDK und Image bauen
bitbake -c populate_sdk <image>
```
# Konfigurieren von Buildroot für remote debugging
Folgende Optionen müssen dabei aktiviert werden:
* **Toolchain | Build cross gdb for the host**: `BR2_PACKAGE_HOST_GDB`
* **Target packages | Debugging, profiling and benchmark | gdb**: `BR"_PACKAGE_GDB`
* **Target packages | Debugging, profiling and benchmark | gdbserver**: `BR2_PACKAGE_GDB_SERVER`
* **Build options | build packages with debugging symbols**: `BR2_PACKAGE_DEBUG`
# Start to debug
## Verbinden von GDB und gdbserver
Starte `gdbserver` mit einem TCP Port Nummer, auf welche gehört werden soll und optional eine IP-Adresse, mit welcher man Verbindungen erlaubt. Meistens ist jedoch nur der Port wichtig, die IP-Adresse egal. Damit wartet der `gdbserver` auf einem gewissen Port auf die Verbindung zum Host.
```bash
# Starten gdbserver auf Target System
gdbserver :1000 ./helloworld
# Starten der Kopie von GDB auf Host-System
aarch64-poky-linux-gdb helloworld
# Innerhalb von gdb benutze target remote command mit der spezifischen IP-Adresse des Target Systems und gegeben Port
(gdb) target remote 192.168.1.101:1000
#Ausgabe von gdbserver, wenn erfolgreich
Remote debugging from host 192.168.1.1
```
Bei einer seriellen Verbindung:
```bash
# Starte gdbserver und uebergebe seriellen Port
gdbserver /dev/ttyO0 ./helloworld
# setze Baudrate fuer serielle Verbindung auf Target System
ssty -F /dev/ttyO0 115200
# Auf dem Host starte GDB
(gdb) set serial baud 115200
(gsb) target remote /dev/ttyUSB0
```
## Setzen von sysroot
GDB muss nun wissen wo der Source-Code zu finden ist und wo sich Debug Informationen aufhalten. Dafür muss die Richtung von Sysroot und der Source Codes auf dem Host-System übergeben werden.
Mit Yocto-Project (sysroot):
```bash
(gdb) set sysroot /opt/poky/3.1.5/sysroots/arch64-poky-linux
```
Mit Buildroot (sysroot):
```bash
set sysroot /home/hundseder/buildroot/output/staging
```
Verzeichnis des benutzten Source-Codes:
```bash
(gdb) show directories
Source directories searched: $cdir:$cwd
```
`$cdir` ist das aktuelle Arbeitsverzeichnis. `$cwd` ist das Verzeichnis, in dem das Binary gebaut wurde. Dies kann so angezeigt werden:
```bash
aarch64-poky-linux-objdump --dwarf ./helloworld | grep DW_AT_comp_dir
```
Dies ist meist ausreichend. Jedoch können weitere Richtungen hinzugefügt werden:
```bash
# Wenn mehrere Kompile-Verzeichnisse angegeben sind, bsp. Yocto
(gdb) set substitute path /usr/src/debug/opt/poky/3.1.5/sysroot/aarch64-poky-linux/usr/src/debug
# Angabe von dynamischen Bibliotheken (beispielhafter Pfad, wird nur dann gesucht, wenn Binary in default PFad nicht gefunden)
(gdb) set solib-search-path /home/hundseder/src/lib_mylib
# Dritte Art und Weise. Dieses Verzeichnis wird vor allen anderen Verzeichnissen durchsucht !
(gdb) directory /home/hundseder/src/lib_mylib
```
## GDB command files
Beim Starten von GDB wird nach einem command File gesucht namens `~/.gdbinit`. Darin enthaltene Befehle werden anschließend automatisch ausgeführt, sodass Befehle, welche immer beim Starten von GDB ausgeführt werden müssen, automatisiert werden. Möchte man Befehle nicht in einem allgemeinen command File einfügen, kann auch im aktuellen Arbeitsverzeichnis eins eigefügt werden. Dazu muss im globalen File folgende Zeile eingefügt werden:
```bash
set auto-load safe-path /
# Oder nicht global sondern nur aktuelles Verzeichnis
add-auto-load-safe-path /home/hundseder/myprog
# Buildroot erzeugt automatisch ein Init-File fuer GDB
add-auto-load-safe-path /home/hundseder/Dokumente/Embedded_Linux/buildroot/staging/usr/share/buildroot/gdbinit
```
## Mehr GDB Kommandos / User Manuell
[Hier](https://www.sourceware.org/gdb/documentation) können alle Kommandos für GDB sowie das User Manuell gefunden werden.
Die wichtigsten Funktionen zusammengefasst:
![GDB Kommandos](../images/gdb-commands.png)
Das Kommando `run` funktioniert nicht bei remote debugging ! Stattdessen muss folgendes gemacht werden:
```bash
# Setzen des ersten Breakpoint zu Beginn der Hauptfunktion
(gdb) break main
# Continue zum nächsten Breakpoint
(gdb) continue
```
# Natives Debugging
Auch kann direkt auf dem Zielsystem das Programm gedebugged werden, wobei dies, aufgrund des benötigten Speicherplatz, nicht Standard ist.
Das Yocto-Project muss folgend in der `conf/local.conf` geändert werden:
```bash
# Alle Debug Packages werden geladen
EXTRA_IMAGE_FEATURES ?= "tools-debug dbg-pkgs"
# Nur ein bestimmtes Package mit Debug Information laden mit anfügen von dpg
<packagename>-dbg
# Dabei werden die Source Dateien geladen, dies kann wie folgt verhindert werden:
PACKAGE_DEBUG_SPLIT_STYLE = "debug-without-src"
```
Mit Buildroot kann eine Kopie von GDB auf dem Ziel Image installiert werden:
* **Target Packages | Debugging, profiling and benchmark | Full debugger**: `BR2_PACKAGE_GDB_DEBUGGER
Um Binaries mit Debug Informationen zu bauen und diese auf dem Ziel zu installieren:
* aktivieren: **Build Options | Build packages with debugging symbols**: `BR2_ENABLE_DEBUG`
* deaktivieren: **Bild Options | Strip target binaries**
# Just-in-time Debugging
Wenn Programme werden der Laufzeit fehlschlagen, bzw. sich nicht korrekt verhalten, kann direkt mit GDB darauf geschaut werden, um zu begutachten, was genau gerade passiert. Dazu ist die PID des Prozesses wichtig (bsp. 109). Mit folgendem Kommando kann dann auf dem Ziel System der `gdbserver` gestartet werden:
```bash
# Starten
gdbserver --attach :1000 109
# Beenden des Debugging, Programm läuft ohne GDB weiter
(gdb) detach
```
Dadurch wird der Prozess gestoppt, wenn er an einem gewissen Breakpoint angekommen ist.
# Debugging forks und Threads
Wenn das Programm einen `fork()` ausführt, kann entweder dem *parent* oder *child* Prozess gefolgt werden. Es wird standardmäßig dem *parent* Prozess gefolgt. Dies kann mit der `follow-fork-mode` Option geändert werden. Dies wird zum jetztigen Stand nicht von `gdbserver` unterstützt und funktioniert nur **nativ** !
Wenn ein Thread auf einen Breakpoint trifft, halten alle Threads an! Dies ist auch gut so um auf statische Variablen zu blicken. Wird nun der Thread wieder gestartet (`continue` oder `step`) starten alle Threads wieder. Mit `scheduler-locking = on (default = off)` wird nur der Thread wieder ausgeführt, der den Breakpoint erreicht hat.
# Core Files
Core Files beinhalten Informationen über gecrashte Programme, von dem Punkt, an dem sie beendet wurden. Dabei muss nicht währenddessen der Debugger ausgeführt werden.
Mit folgenden Kommando wird das erzeugen von Core Files aktiviert:
```bash
ulimit -c unlimited
```
Der Name des Files ist dabei `core` und wird in `/proc/<PID>/cwd` abgelegt.
Mit zwei Files kann die Namensgebung verändert werden:
* `/proc/sys/kernel/core_uses_pid`: Wenn eine `1` in das File geschrieben wird, erzeugt es einen File Namen, an dem die PID des Prozesses angefügt ist
* `/proc/sys/kernel/core_pattern`: Mithilfe von Meta-Characteren kann der Filename verändert werden
Zudem kann in letzterem auch ein Verzeichnis mitgegeben werden, in dem die Files gespeichert werden:
```bash
# Schreibt Files in /corefiles mit Programmname %e und Zeitpunkt des Crashes %t
echo /corefiles/core.%e.%t > /proc/sys/kernel/core_pattern
```
## Lesen von Core-Files
```bash
# Beispielhafter Aufruf
gdb sort-debug /home/hundseder/rootfs/corefiles/core.exampleProg.1431425613
# Anschliessend kann mit backtrace der Call Stack ueberpreuft werden
(gdb) backtrace
```
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment