Hypervisor-assisted Debugging

Die Arbeit an einem Hypervisor bringt eine Reihe von Herausforderungen mit sich. Eine besondere Herausforderung ist dabei die Geräteemulation. Das mag übertrieben sein, wenn man in simulierten Umgebungen wie der Fixed Virtual Platform von ARM arbeitet. Sobald man jedoch zu echter Hardware wechselt, können die Dinge aus dem Ruder laufen.

Verlassen der simulierten Umgebung

Eine häufige Komponente in allen eingebetteten Geräten sind Power Management Integrated Circuit (PMIC) Regler. Ihre Grundfunktion ist relativ einfach: Sie verwalten den Fluss und die Richtung des Stroms. Dazu regeln sie die Eingangsspannungen und -ströme und schalten die verfügbaren Geräte ein. Aber die Dinge sind komplexer. In Wirklichkeit verwendet jeder Hersteller unzählige verschiedene Regler mit mindestens ebenso vielen Funktionen, die er noch zusätzlich anhäuft. So kann z. B. ein bestimmter Gerätetreiber eine Teilmenge der Funktionen des Leistungsreglertreibers implementieren und ihn direkt über syscon direkt abfragen, um den Zustand eines zugehörigen Subsystems zu ermitteln. Bei diesem Teilsystem könnte es sich um einen Frequenzteiler handeln, der vor dem Zurücksetzen des Geräts aktiv sein muss.

Typische Probleme mit Power Regulators

All dies wird zu einem Problem, wenn die Integration in einem SoC zu unflexibel ist. Leistungsregler (oder deren Treiber) müssen mit Blick auf die Virtualisierung entwickelt werden. Nehmen wir zum Beispiel an, es sollen Geräte zwischen mehreren VMs aufgeteilen werden, wobei jede VM exklusiven Zugriff auf das ihr zugewiesene Gerät hat. Da auf Arm-Architekturen Device Trees anstelle von Mechanismen wie ACPI Device Enumerationverwendet werden, ist der erste logische Schritt, die nicht benötigten Geräteknoten aus dem Gerätebaum der einzelnen VMs zu löschen. Gesagt und getan. Aber jetzt stellt man fest, dass der Treiber der einen VM das Gerät eingeschaltet hat, während der andere Treiber der zweiten VM es wieder ausgeschaltet hat. Und warum? Weil dieser Treiber erwartet hat, den Geräteknoten der ersten VM im Device Tree zu finden und davon ausgeht, dass sein Fehlen bedeutet, dass das Gerät deaktiviert wurde. Warum also nicht sicherstellen, dass seine Stromversorgung ebenfalls deaktiviert ist? Es kann sehr lange dauern bis solche Probleme aufgespürt werden. Darüber hinaus sind Hardware-Debugging-Lösungen aufgrund mangelnder Unterstützung für bestimmte Plattformen manchmal keine Option (normalerweise hat OpenOCD immerhin noch eine bessere Schnittstelle zu Hardware-Debuggern zu haben als OEM-Software). Und damit sind wir wieder beim eigentlichen Thema.

Vorteile des Hypervisor-assisted Debugging

Wenn man an einem Punkt angelangt ist, an dem nicht einmal ein "printk"-Debugging möglich ist, scheinen die Möglichkeiten begrenzt zu sein. Tatsächlich bleibt ohne Hardware-Debugger nur die Möglichkeit, den ausführenden Kernel in EL2 (Hypervisor-Space) zu trappen, vorausgesetzt, es gibt einen Hypervisor. Dies kann auf verschiedene Weise geschehen:

  • Speicherzugriffe trappen , indem die physischen Adressbereiche des Hosts nicht auf den physischen Adressraum des Gastes abgebildet werden. Durch die Verwendung der Stage-2-Page-Table-Funktion in Arm-Prozessoren (auch bekannt als Extended Page Tables - Intel, Nested Page Tables - AMD) konfigurieren wir die MMU im Wesentlichen so, dass sie zwei Übersetzungen vornimmt: eine vom virtuellen Adressraum des Gastes in den physischen Adressraum des Gastes und eine weitere zwischen letzterem und dem physischen Adressraum des Hosts (d. h. der tatsächlichen physischen Adresse). Ein Fehler bei der ersten Übersetzung wird in EL1 (Kernel-Space) behandelt, während ein Fehler bei der zweiten Übersetzung vom Hypervisor behoben werden muss. Diese Lösung umfasst die Durchführung der beabsichtigten Operation (gemäß dem Exception Syndrome Register), die Inkrementierung des Programmzählers und die Rückkehr zu EL1. Zwischen diesen Schritten können wir jede beliebige Debugging-bezogene Operation durchführen, die wir wünschen.

  • Trap-Instruktionen einfügen, was entweder programmatisch erfolgen kann (wie bei Software-Breakpoints, d. h. Ersetzen des ersten Bytes der Zielanweisung durch ein int 3 oder brk abhängig von der Architektur) oder manuell, durch Einfügen von hvc oder smc Instruktionen im Kernel Code. Der Vorteil gegenüber der vorherigen Methode ist, dass sie eine gezieltere Fehlersuche mit weniger Aufwand ermöglicht.

  • Hardware Debug Registers erzielen mehr oder weniger den gleichen Effekt wie die erste Methode. Je nach Anforderungen können diese jedoch nützlich sein, um Single Stepping zu implementieren und Speicherzugriff-Traps auf Instruktion-Fetches zu beschränken, wodurch der Prozess erheblich beschleunigt wird.

Während diese Bausteine immerhin zu einem rudimentären Debugging führen, sind Funktionen wie VM Introspection und DWARF-Symbolparsing deutlich schwerer zu integrieren. Nichtsdestotrotz: anspruchsvollere Werkzeuge werden gebraucht. HyperDbg ist ein Beispiel für einen Hypervisor-gestützten Debugger, der Intels VT-x- und TSX-Erweiterungen nutzt, um eine robustere Debugging-Infrastruktur bereitzustellen, ähnlich der, die in einem Arm-Ökosystem erforderlich wäre. Nichtsdestotrotz wird die Forschung zu diesem Thema durch die anhaltenden Herausforderungen beim Reverse Engineering und der Malware-Analyse erschwert. Die Verbesserung der Portabilität und der Benutzerfreundlichkeit treten angesichts der modernen Code-Packer und -Schutzmechanismen in den Hintergrund.
Kontaktieren Sie uns, um mehr darüber zu erfahren, wie wir Ihre Embedded-Projekte unterstützen können!

Haben Sie Fragen?

Kontaktieren Sie uns, um mehr darüber zu erfahren, wie wir Ihre Embedded-Projekte unterstützen können!