- Ausgehend vom Fehlen eines Debuggers, der die GPU-Ausführung stoppen und den Zustand untersuchen kann, beschreibt der Beitrag, wie diese Funktion direkt auf AMD-GPUs implementiert wird
- Über die DRM-Schnittstelle und libdrm erfolgt die direkte Kommunikation mit der GPU, wobei Kontext-Erstellung, Pufferzuweisung und Befehlsübermittlung schrittweise aufgebaut werden
- Mit den TBA/TMA-Registern und dem Trap-Handler wird die GPU-Ausführung angehalten und über die Synchronisierung mit der CPU wird das Lesen sowie die Wiederherstellung des Zustands realisiert
- Durch SPIR-V-Codekompilierung und RADV-Integration wird die reale Shader-Debugging-Umgebung erweitert und die Implementierung von breakpoint·stepping·watchpoint ermöglicht
- Dieser Ansatz, der die interne Struktur der GPU direkt steuert, zeigt die Machbarkeit eines vollständigen Debuggers für AMD-GPUs und lässt Raum für eine Weiterentwicklung hin zur Vulkan-Integration
Notwendigkeit und Ansatz beim GPU-Debugging
- Ausgangspunkt ist der Mangel an einem Werkzeug, mit dem sich die GPU-Ausführung wie bei der CPU anhalten und den Zustand prüfen lässt
- Das parallele Ausführungsmodell der GPU macht das Debugging deutlich komplexer
- In der AMD ROCm-Umgebung existiert
rocgdb, unterstützt jedoch nur einen auf ROCm begrenzten Bereich
- Basierend auf der Blog-Serie von Marcell Kiss wurde der Versuch unternommen, einen Debugger zu implementieren, der direkt mit der GPU kommuniziert
Direkte Kommunikation mit der GPU
- Die Funktionsweise des RADV-Treibers wurde analysiert, um zu lernen, wie man direkt mit der GPU kommuniziert
- Nach dem Öffnen von
/dev/dri/cardX wird mit dem KMD (Kernel Mode Driver) verbunden und anschließend amdgpu_device_initialize aufgerufen
- Mit
libdrm werden Kontext-Erstellung (amdgpu_cs_ctx_create) und Pufferzuweisung durchgeführt
- Es werden zwei Puffer erzeugt: ein Codepuffer und ein Befehls-Puffer
- Die Puffer werden in den virtuellen Adressraum von GPU und CPU gemappt
- Das Mapping wird nicht mehr über
amdgpu_bo_va_op, sondern über direkte IOCTL-Aufrufe abgewickelt
- Mit
clang und objdump wird Shader-Assembliercode kompiliert und das Binary extrahiert
- Die GPU-Befehle werden im
PM4 Packet-Format aufgebaut, um den Shader-Ausführungsbefehl zu senden
GPU-Traps und Debugfs-Nutzung
- Der Trap-Handler wird über die RDNA3-ISA-TBA/TMA-Register eingerichtet
TBA: Adresse des Trap-Handers
TMA: temporäre Speicheradresse für den Trap-Handler
- Da aus dem Userspace kein direkter Zugriff möglich ist, wird die debugfs-Schnittstelle verwendet
- Registerzugriff erfolgt über die Datei
/sys/kernel/debug/dri/{PCI address}/regs2
- Register-Schreibzugriffe mit
amdgpu_debugfs_regs2_write
- Mit je einem VMID werden TBA/TMA gesetzt, um den Trap-Handler zu aktivieren
- Jede VMID unterscheidet einen GPU-Prozesskontext
Implementierung des Trap-Handlers
- Der Trap-Handler ist ein privilegiertes Shader-Programm, das ausgeführt wird, wenn die GPU auf eine Ausnahme trifft
- Über das TTMP-Register werden GPU-Zustände (STATUS, EXEC, VCC usw.) gespeichert
- Mit dem Befehl
global_store_addtid_b32 werden Thread-spezifische Registerwerte im Speicher gespeichert
- Erkennt die CPU, dass die GPU Daten geschrieben hat, wird die GPU mit dem
SQ_CMD-Register kurz angehalten
- Danach analysiert die CPU die Daten und setzt anschließend mit
SQ_CMD die GPU-Ausführung wieder in Gang
- Beim Verlassen des Handlers werden Programmzähler und Registerzustand wiederhergestellt
SPIR-V-Codeausführung und RADV-Integration
- Statt manueller Assembly wird die Kompilierung von SPIR-V-Code unterstützt
- Das
ACO-Modul von RADV konvertiert SPIR-V in ein GPU-Binary
- Mit der Umgebungsvariable
RADV_FORCE_FAMILY wird ein virtuelles Gerät erzeugt
- Im
null_winsys-Modus von RADV wird nur kompiliert, ohne physischen Hardwarezugriff
- Aus dem Kompilat werden Shader-Code, Ressourcenkonfiguration und Debug-Informationen extrahiert
Erweiterung der Debugger-Funktionen
- Stepping: Mit den Bits
RSRC1.DEBUG_MODE und RSRC3.TRAP_ON_START wird die Befehl für Befehl-Ausführung gesteuert
- Breakpoints: Nach der Berechnung der Programmzählerposition auf Basis der Codepufferadresse erfolgt die Trap-Verarbeitung
- Source Mapping: Mithilfe der Debug-Informationen des ACO-Compilers erfolgt das Mapping der Source-Code-Zeilen
- Watchpoints: Mittels GPU-Seiten-Schutz oder des
SQ_WATCH-Registers ist eine Adressüberwachung möglich
- Variablennamen- und Typ-Nachverfolgung: In der NIR-Optimierungsstufe von Mesa ist eine verbesserte Weitergabe von Debug-Informationen erforderlich
- Vulkan-Integration: Auf Basis von RADV ist Frame-basiertes Debugging mit Puffer-, Textur- und Konstanteninformationen möglich
Bonus: Page-Walking-Code im User Mode
- Ein Beispielcode für Page-Table-Walk-Code für RDNA3 (gfx11)-GPUs wird bereitgestellt
- Mit enthaltenen PDE/PTE-Strukturdefinitionen und Dekodierfunktionen
- mit implementiertem Prozess der Umwandlung einer virtuellen in eine physische Adresse
- Durch das Lesen der Seitentabellenregister pro VMID ist eine Analyse der GPU-Memory-Mapping-Struktur möglich
Fazit
- Es wird die Machbarkeit einer vollständigen Debugger-Implementierung über Zugriff auf Kernel- und Hardwareebene auf AMD GPUs nachgewiesen
- Durch den Aufbau einer bidirektionalen Kommunikationsschleife zwischen CPU und GPU wird das Unterbrechen, die Zustandsanalyse und das Fortsetzen während der Ausführung realisiert
- Mit zukünftiger RADV- und Vulkan-Integration besteht Potenzial für den weiteren Ausbau zu einer entwicklerfreundlichen GPU-Debugging-Umgebung
Noch keine Kommentare.