3 Punkte von GN⁺ 2025-12-10 | Noch keine Kommentare. | Auf WhatsApp teilen
  • 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.

Noch keine Kommentare.