In meinen früheren EJTAG- Veröffentlichungen : Eine Attraktion für Hacker und Black Swift: Mit EJTAG wurde der einfachste Weg zur Verwendung von EJTAG in Betracht gezogen - Laden in den Arbeitsspeicher und Starten eines Benutzerprogramms. Die Fähigkeiten von EJTAG sind jedoch nicht darauf beschränkt. In der Veröffentlichung wird beschrieben, wie Sie das einfache Debuggen von Code mithilfe von EJTAG mithilfe der Freeware-Tools openocd und GDB organisieren.Ich wurde aufgefordert, diese Veröffentlichung durch einen Brief eines Lesers zu schreiben, der mich bat, anonym zu bleiben, und der mich um Hilfe bat - ein AR9344-basiertes Gerät bootet nicht (es hängt in der U-Boot-Initialisierungsphase) - wie kann ich herausfinden, wo das Problem mit EJTAG liegt?Da ich kein Gerät zur Hand hatte, das auf dem AR9344 basiert, stellte sich heraus, dass das Black Swift Pro-Board, das auf dem zugehörigen AR9331-Chip basiert, die Erzählung mit Blick darauf war. Ich denke, dass die Änderungen, die für den AR9344 vorgenommen werden müssen, nicht signifikant sind. Fahren wir mitder Problemstellung fort: Esgibt eine Black Swift Pro-Karte, an die wir über openocd über EJTAG angeschlossen und den Prozessor in den Stoppmodus versetzt haben.Es ist erforderlich, mehrere zehn Prozessorbefehle nacheinander auszuführen, nach Ausführung jedes Befehls anzuhalten und gegebenenfalls den Inhalt von RAM, Boot-ROM, Peripherie-Controller-Registern oder Prozessor-Registern zu überprüfen.Um das Problem zu lösen, sehe ich mindestens zwei Ansätze:- einfach - nur mit openocd - die grundlegende Funktionalität zum Ausführen der erforderlichen Aktionen ist bereits in openocd enthalten. Es ist nur notwendig, es benutzen zu können;
- Komplex - Mit dem openocd + GDB-Bundle steuert der Benutzer den Prozess der Ausführung von Prozessoranweisungen über GDB, und openocd konvertiert GDB-Anforderungen in EJTAG-Befehle.
Betrachten Sie nun beide Lösungen genauer., Black Swift: EJTAG.
1: openocd
Diejenigen, die meine früheren Veröffentlichungen über EJTAG gelesen haben, sollten sich daran erinnern, dass openocd in ihnen als dummer Skript-Executor (Konfigurationsdateien) angezeigt wird, der im Batch-Modus zu funktionieren scheint und keine Benutzerinteraktion ermöglicht. Dies ist jedoch nicht der Fall. Während die openocd-Software ausgeführt wird, kann sie tatsächlich aufgefordert werden, einen Befehl über die Befehlszeilenschnittstelle auszuführen. Um auf die Befehlszeilenschnittstelle zuzugreifen, startet openocd einen Telnet-Server.Standardmäßig wird der TCP-Port 4444 für den Telnet-Server verwendet. Bei Bedarf kann die TCP-Portnummer mithilfe der Option geändert werden telnet_port
(siehe Beispiel unten).Versuchen wir, den Bootloader des Black Swift-Boards mit openocd zu verfolgen.Beispiel für eine Konfigurationsdateiblack-swift-trace.cfg
für openocd, wodurch openocd für den Telnet-Server gezwungen wird, Port 4455 zu verwenden: Quelle [find interface / ftdi / tumpa.cfg]
adapter_khz 6000
Quelle [find black-swift.cfg]
telnet_port 4455
drin
Halt
Das Ausführen von openocd 0.9.0 als root sieht folgendermaßen aus: # openocd -f black-swift-trace.cfg
Open On-Chip Debugger 0.9.0 (2015-05-28-17: 08)
Lizenziert unter GNU GPL v2
Lesen Sie für Fehlerberichte
http://openocd.org/doc/doxygen/bugs.html
keine getrennt
Adaptergeschwindigkeit: 6000 kHz
Info: Automatische Auswahl des ersten verfügbaren Sitzungstransports "jtag". Verwenden Sie zum Überschreiben 'transport select <transport>'.
Fehler: kein Gerät gefunden
Fehler: ftdi-Gerät mit vid 0403, pid 8a98, Beschreibung '*' und seriellem '*' kann nicht geöffnet werden
Info: Taktrate 6000 kHz
Info: JTAG-Tipp: ar9331.cpu-Tipp / Gerät gefunden: 0x00000001 (Hersteller: 0x000, Teil: 0x0000, Version: 0x0)
Zielzustand: angehalten
Ziel im MIPS32-Modus aufgrund einer Debug-Anforderung angehalten, pc: 0xbfc00000
Zielzustand: angehalten
Ziel im MIPS32-Modus aufgrund von Einzelschritt angehalten, pc: 0xbfc00404
Jetzt können wir ein weiteres Terminalfenster öffnen und mit dem Programm eine Verbindung zum openocd-Telnet-Server herstellen telnet
: $ telnet localhost 4455
Versuch :: 1 ...
Versuch 127.0.0.1 ...
Verbunden mit localhost.
Escape-Zeichen ist '^]'.
Öffnen Sie den On-Chip-Debugger
>
Eine Liste aller openocd-Befehle ist mit dem Befehl leicht zu erhalten help
.Für die schrittweise Ausführung von Prozessoranweisungen ist der step
folgende Befehl hilfreich : Schritt [Adresse]
Führen Sie einen Befehl an der vom Register angegebenen Adresse aus
Befehlszähler (PC). Wenn der Adressparameter angegeben ist, dann
Der Befehl wird beginnend mit der Adressadresse ausgeführt.
Die schrittweise Ausführung von Prozessoranweisungen in der Konsole sieht folgendermaßen aus: > Schritt 0xbfc00400
Zielzustand: angehalten
Ziel im MIPS32-Modus aufgrund von Einzelschritt angehalten, pc: 0xbfc00404
> Schritt
Zielzustand: angehalten
Ziel im MIPS32-Modus aufgrund eines Einzelschritts angehalten, pc: 0xbfc00408
> Schritt
Zielzustand: angehalten
Ziel im MIPS32-Modus aufgrund von Einzelschritt angehalten, pc: 0xbfc0040c
Die folgenden openocd-Befehle können ebenfalls nützlich sein: reg [(register_number | register_name) [(value | 'force')]]
Prozessorregisterwert lesen oder schreiben.
Das Aufrufen von reg ohne Parameter führt zur Ausgabe aller Register.
Wenn der Parameter 'force' verwendet wird, wird erzwungen
Subtrahieren des Registers vom Prozessor (anstatt ein zwischengespeichertes auszugeben
Werte).
mwb ['phys'] address value [count]
address , value.
phys, address — ,
— .
count address
count,
value.
mwh ['phys'] address value [count]
mwb, 16- .
mww ['phys'] address value [count]
mwb, 32- .
mdb ['phys'] address [count]
address.
phys, address — ,
— .
count
Array an Adresse Adresslänge Anzahl Bytes.
mdh ['phys'] Adresse [Anzahl]
Der Befehl ähnelt mdb, es wird jedoch anstelle eines Bytes ein 16-Bit-Wort gelesen
mdw ['phys'] address [count]
Der Befehl ähnelt mdb, es wird jedoch ein 32-Bit-Wort anstelle eines Bytes gelesen.
Wie Sie leider sehen können, kann die neueste Version (zum Zeitpunkt dieses Schreibens) von openocd 0.9.0 Anweisungen von Prozessoren mit MIPS-Architektur nicht zerlegen, obwohl es einen solchen Disassembler für Prozessoren mit ARM-Architektur gibt .Das Fehlen eines Disassemblers macht die schrittweise Ausführung von Prozessoranweisungen direkt mit openocd nicht sehr komfortabel. Sie können den Komfort erhöhen, wenn Sie GDB verwenden.Lösung 2: Verwenden Sie das openocd + GDB-Bundle
Im openocd + GDB-Bundle sind die Rollen wie folgt verteilt: Der Benutzer kommuniziert mit GDB, das eine bequeme Schnittstelle zum Debuggen bietet, von dem Mechanismus abstrahiert, mit dem die Ausführung von Anweisungen gesteuert wird, und openocd übernimmt die Aufgabe, den Prozessor gemäß den Anweisungen von GDB direkt zu steuern.Die Verwendung von GDB zur Steuerung der Ausführung von Anweisungen auf einem MIPS-Prozessor über EJTAG bietet gegenüber openocd mehrere Vorteile:- Wie oben erwähnt, ist in GDB ein Disassembler für die MIPS-Architektur integriert.
- Es ist möglich, Debugging-Informationen aus der Quelle zu verwenden. Wenn Sie beispielsweise Ihr eigenes C-Programm debuggen, kann GDB anzeigen, welche C-Code-Zeile gerade ausgeführt wird, und den Status genau der Programmvariablen detaillieren, nicht der Speicherzellen mit mysteriösen Adressen.
- openocd GDB GDB Remote Serial Protocol; qemu , , — GDB;
- , GDB TAB.
Es sollte bedacht werden, dass GDB mit hochrangigen Konzepten arbeitet und openocd gezwungen ist, mit den Geräten zu arbeiten, die GDB Wishlist nicht immer effektiv mit EJTAG implementieren kann.Beispielsweise weist der Benutzer GDB an, einen Haltepunkt an der angegebenen Adresse festzulegen. Diese Anweisung geht an openocd. Bei MIPS-Prozessoren hat openocd jedoch mindestens zwei Möglichkeiten, einen Haltepunkt festzulegen:- sdbbp, , openocd sdbbp , sdbbp , . software breakpoint. , .
- . . , hardware breakpoint, .
Obwohl innerhalb des GDB Remote Serial Protocol zwischen Hardware-Haltepunkt und Software-Haltepunkt unterschieden wird (siehe Pakete z und z0 in der Protokollbeschreibung ) und GDB geeignete Optionen für die Auswahl des Haltepunkttyps bietet, kann sich herausstellen, dass die Verwendung von Punkten auf einem bestimmten Prozessor Einschränkungen unterliegt Aufschlüsselung des einen oder anderen Typs. Dementsprechend verfügt openocd über eine Option gdb_breakpoint_override
, mit der Sie eine der beiden beschriebenen Methoden zum Organisieren von Haltepunkten erzwingen können.Um den GDB-Debugger zu verbinden, implementiert openocd einen GDB-Server, der standardmäßig den TCP-Port 3333 verwendet. Bei Bedarf kann die TCP-Portnummer mithilfe der Option geändert werden gdb_port
.Um eine Verbindung zum openocd GDB-Server herzustellen, der den MIPS-Prozessor steuert, benötigen wir eine spezielle Version von GDB mit MIPS-Unterstützung. Ich gehe davon aus, dass der Reader x86 / amd64-basierte Computer verwendet, auf denen Debian Linux ausgeführt wird, um openocd und GDB unter Verwendung von mips-linux-gnu-gdb aus dem Sourcery CodeBench-Paket auszuführen. Informationen zur Installation dieses Pakets finden Sie hier . Sie müssen lediglich die Änderung einführen, dass zum Zeitpunkt des Schreibens dieser Zeilen die letzte Version von Sourcery CodeBench mips-2015.05-18 ist, die im Mai 2015 veröffentlicht wurde. Sie können das Archiv hier über diesen Link herunterladen .Versuchen wir, das openocd + GDB-Bundle in der Praxis zu verwenden. Führen Sie openocd aus: # openocd -f run-u-boot_mod-trace.cfg \
> -c "gdb_breakpoint_override hard" -c "Schritt 0xbfc00400"
Die letzten beiden Befehle für openocd bieten Folgendes:- Hardware-Haltepunkte werden verwendet, unabhängig davon, was GDB sich dort vorstellt (Adressen 0xbfc0xxxx entsprechen dem ROM, sodass Software-Haltepunkte nicht funktionieren).
- Ein Befehl wird von der Adresse 0xbfc00400 ausgeführt, wonach der Prozessor erneut stoppt.
Öffnen Sie ein anderes Terminalfenster und starten Sie GDB: $ /opt/mips-2015.05/bin/mips-linux-gnu-gdb
GNU gdb (Sourcery CodeBench Lite 2015.05-18) 7.7.50.20140217-cvs
Copyright (C) 2014 Freie Software Foundation, Inc.
Lizenz GPLv3 +: GNU GPL Version 3 oder höher <http://gnu.org/licenses/gpl.html>
Dies ist freie Software: Sie können sie ändern und weitergeben.
Es besteht KEINE GARANTIE, soweit dies gesetzlich zulässig ist. Geben Sie "Kopieren anzeigen" ein
und "Garantie anzeigen" für Details.
Diese GDB wurde als "--host = i686-pc-linux-gnu --target = mips-linux-gnu" konfiguriert.
Geben Sie "show configuration" für Konfigurationsdetails ein.
Anweisungen zur Fehlerberichterstattung finden Sie unter:
<https://sourcery.mentor.com/GNUToolchain/>.
Das GDB-Handbuch und andere Dokumentationsressourcen finden Sie online unter:
<http://www.gnu.org/software/gdb/documentation/>.
Um Hilfe zu erhalten, geben Sie "help" ein.
Geben Sie "apropos word" ein, um nach Befehlen zu suchen, die sich auf "word" beziehen.
(gdb)
Jetzt erklären wir GDB den Prozessortyp, mit dem wir arbeiten werden. Wir bitten Sie, die nächste ausführbare Anweisung, den Prozessor, zu zerlegen und schließlich eine Verbindung zum openocd GDB-Server herzustellen: (gdb) setze Architektur-Mips: isa32r2
Es wird angenommen, dass die Zielarchitektur mips ist: isa32r2
(gdb) setze endian groß
Es wird angenommen, dass das Ziel Big Endian ist
(gdb) disassemble-next-line aktivieren
(gdb) Zielfernbedienung: 3333
Remote-Debugging mit: 3333
0xbfc00404 in ?? ()
=> 0xbfc00404: 40 80 08 00 mtc0 Null, c0_random
Um einen Prozessorbefehl in GDB auszuführen, wird ein Befehl verwendet stepi
. Versuchen wir, ein paar Anweisungen des Prozessors zu befolgen, und lassen Sie sich nicht durch die Warnungen von GDB über das Fehlen von Debugging-Informationen verwirren (der Start der Funktion kann nicht gefunden werden). In dieser Situation gibt es keinen Platz, um diese Informationen abzurufen. (gdb) stepi
Warnung: GDB kann den Start der Funktion bei 0xbfc00408 nicht finden.
GDB kann den Start der Funktion bei 0xbfc00408 nicht finden
und kann daher die Größe des Stapelrahmens dieser Funktion nicht bestimmen.
Dies bedeutet, dass GDB möglicherweise nicht auf diesen Stapelrahmen zugreifen kann, oder
die Rahmen darunter.
Dieses Problem wird höchstwahrscheinlich durch einen ungültigen Programmzähler oder verursacht
Stapelzeiger.
Wenn Sie jedoch der Meinung sind, dass GDB einfach weiter zurück suchen sollte
von 0xbfc00408 für Code, der wie der Anfang von a aussieht
Funktion können Sie den Bereich der Suche mit dem `Set erhöhen
heuristischer Zaunpfosten 'Befehl.
0xbfc00408 in ?? ()
=> 0xbfc00408: 40 80 10 00 mtc0 Null, c0_entrylo0
(gdb) stepi
Warnung: GDB kann den Start der Funktion bei 0xbfc0040c nicht finden.
0xbfc0040c in ?? ()
=> 0xbfc0040c: 40 80 18 00 mtc0 Null, c0_entrylo1
(gdb) stepi
Warnung: GDB kann den Start der Funktion bei 0xbfc00410 nicht finden.
0xbfc00410 in ?? ()
=> 0xbfc00410: 40 80 20 00 mtc0 Null, c0_context
(gdb)
Lesen Sie nun das 32-Bit-Wort unter 0xbfc00408: (gdb) p / x * 0xbfc00408
$ 1 = 0x40801000
Verwenden Sie den folgenden Befehl, um den Status der Prozessorregister zu drucken info registers
: (gdb) Info-Register
Null bei v0 v1 a0 a1 a2 a3
R0 00000000 37c688e2 22b15a00 28252198 0c12d319 4193c014 84e49102 06193640
t0 t1 t2 t3 t4 t5 t6 t7
R8 00000002 9f003bc0 92061301 1201c163 31d004a0 92944911 ac031248 b806001c
s0 s1 s2 s3 s4 s5 s6 s7
R16 8bc81985 402da011 c94d2454 88d5a554 81808e0d cc445151 4401a826 50020402
t8 t9 k0 k1 gp sp s8 ra
R24 01c06b30 01000000 10000004 fffffffe 9f003bc0 54854eab 329d626b bfc004b4
Status lo hi badvaddr Ursache PC
00400004 00244309 b9ca872c ed6a1f00 60808350 bfc00410
fcsr fir
00000000 00000000
GDB ist ein erweitertes Tool mit einer Vielzahl von Befehlen und Optionen. Für eine nähere Bekanntschaft mit GDB verweise ich den Leser auf die offizielle GDB-Dokumentation .Initialisierung des AR9331 RAM Controllers
Mal sehen, wie GDB ein bestimmtes Problem lösen kann: Indem wir die U-Boot-Ausführung auf der Black Swift-Karte verfolgen, identifizieren wir eine Folge von Einträgen in den Registern des RAM-Controllers, die zu seiner Initialisierung führen. Das Erkennen einer solchen Sequenz ist äußerst nützlich, wenn wir Programme auf Black Swift unter Verwendung von openocd unter Umgehung des U-Boot ausführen möchten. Diese Initialisierungssequenz ist auch nützlich, wenn Sie einen alternativen Bootloader für Black Swift erstellen.Führen Sie openocd aus (genau wie im vorherigen Abschnitt): # openocd -f run-u-boot_mod-trace.cfg \
> -c "gdb_breakpoint_override hard" -c "Schritt 0xbfc00400"
Erstellen Sie ein Skript, um GDB auszuführen bs-u-boot-trace-gdb.conf
: setze Architektur-Mips: isa32r2
setze endian groß
Setzen Sie Disassemble-Next-Line auf
Zielfernbedienung: 3333
Paginierung auslösen
Legen Sie die Protokolldatei bs_gdb.log fest
Anmeldung einstellen
während $ pc! = (void (*) ()) 0x9f002ab0
stepi
Info-Register
Ende
ablösen
Verlassen
Im Vergleich zum Beispiel im vorherigen Abschnitt bewirkt dieses Skript, dass GDB die Ausgabe in eine Datei dupliziert bs_gdb.log
, die Paginierung deaktiviert (Bestätigung des Umblätterns durch den Benutzer) und dann beginnt, Prozessoranweisungen zyklisch auszuführen und den Status der Prozessorregister nach jeder Anweisung anzuzeigen. Wenn das PC-Register (Adressregister der folgenden Anweisung) den Wert 0x9f002ab0 erreicht, trennt GDB die Verbindung zu openocd und funktioniert nicht mehr. Somit wird am Ende von GDB eine Datei bs_gdb.log
mit einer vollständigen Spur der Ausführung von Prozessoranweisungen erstellt .Der Start von GDB wird wie folgt sein: $ /opt/mips-2015.05/bin/mips-linux-gnu-gdb -x bs-u-boot-trace-gdb.conf
Hinweis: Das Skript bs-u-boot-trace-gdb.conf
funktioniert höchstwahrscheinlich nicht sofort nach dem Einschalten der Karte, da u-boot_mod zusätzlich die Anti-Mystery-Fehler AR9331 zurücksetzt, wodurch das Skript nicht mehr ausgeführt wird. Stoppen Sie in diesem Fall openocd und GDB und führen Sie openocd und GDB erneut aus.
Jetzt dreht sich alles um das Kleine - Sie müssen bs_gdb.log
alle Schreibanweisungen aus der Datei auswählen sw
(Wort speichern, dh einen 32-Bit-Wert schreiben). Die Register des AR9331-Speichercontrollers sind 32-Bit-Register, sodass andere Anweisungen aus der Speicherfamilie sicher ignoriert werden können.Da der Disassembler nur die Namen der Argumentregister der Anweisung erzeugtsw
=> 0xbfc004ec: ad f9 00 00 sw t9,0 (t7)
Es reicht jedoch nicht aus bs_gdb.log
, alle Zeilen mit der sw-Anweisung aus der Datei auszuwählen. Um festzustellen, welcher Wert mit sw an welche Adresse geschrieben wurde, muss die Datei einer bs_gdb.log
zusätzlichen Verarbeitung unterzogen werden. Die Verarbeitung kann mit dem Skript parse_gdb_output.pl erfolgen:
my %r;
foreach $i (qw(zero at v0 v1 a0 a1 a2 a3 t0 t1 t2 t3 t4 t5 t6 t7
s0 s1 s2 s3 s4 s5 s6 s7 t8 t9 k0 k1 gp sp s8 ra)) {
$r{$i} = "none";
}
sub parse_reg($)
{
$_ = $_[0];
if (/^ R/) {
my @fields = split m'\s+';
my $f = 2;
my @rgs;
@rgs = qw(zero at v0 v1 a0 a1 a2 a3) if (/^ R0/);
@rgs = qw(t0 t1 t2 t3 t4 t5 t6 t7) if (/^ R8/);
@rgs = qw(s0 s1 s2 s3 s4 s5 s6 s7) if (/^ R1/);
@rgs = qw(t8 t9 k0 k1 gp sp s8 ra) if (/^ R2/);
foreach $i (@rgs) {
$r{$i} = $fields[$f];
$f = $f + 1;
}
}
}
while (<>) {
if (/^=>([^s]*)\tsw\t([^,]*),(\d+)\(([^)]*)\)/) {
my $rs = $2;
my $offset = $3;
my $rd = $4;
parse_reg(<>);
parse_reg(<>);
parse_reg(<>);
parse_reg(<>);
print("$1 sw $rs={0x$r{$rs}}, $offset($rd={0x$r{$rd}})\n");
}
}
Der Start parse_gdb_output.pl
ist wie folgt:$ grep "^ = \ | ^ R" bs_gdb.log | ./parse_gdb_output.pl
Hier ist ein Ausschnitt der Ausgabe parse_gdb_output.pl
(die Markierungen ' <<< PLL
' und ' <<< DDR
' wurden später manuell eingegeben): ...
0x9f002700: ad cf 00 00 sw t7 = {0x00dbd860}, 0 (t6 = {0xb8116248})
0x9f00271c: ad f9 00 00 sw t9 = {0x000fffff}, 0 (t7 = {0xb800009c})
0x9f0027a0: ad f9 00 00 sw t9 = {0x00018004}, 0 (t7 = {0xb8050008}) <<< PLL
0x9f0027dc: ad f9 00 00 sw t9={0x00000352}, 0(t7={0xb8050004}) <<<
0x9f002840: ad f9 00 00 sw t9={0x40818000}, 0(t7={0xb8050000}) <<<
0x9f002898: ad f9 00 00 sw t9={0x001003e8}, 0(t7={0xb8050010}) <<<
0x9f0028f4: ad f9 00 00 sw t9={0x00818000}, 0(t7={0xb8050000}) <<<
0x9f002970: ad cf 00 00 sw t7={0x00800000}, 0(t6={0xb8116248})
...
0x9f002994: ad cf 00 00 sw t7={0x40800700}, 0(t6={0xb8116248})
0x9f002a54: ad f9 00 00 sw t9={0x00008000}, 0(t7={0xb8050008})
0x9f00309c: af 38 00 00 sw t8={0x7fbc8cd0}, 0(t9={0xb8000000}) <<< DDR
0x9f0030b0: af 38 00 00 sw t8={0x9dd0e6a8}, 0(t9={0xb8000004}) <<<
0x9f0030dc: af 38 00 00 sw t8={0x00000a59}, 0(t9={0xb800008c}) <<<
0x9f0030ec: af 38 00 00 sw t8 = {0x00000008}, 0 (t9 = {0xb8000010}) <<<
...
Da die Adressen der Register des Taktsignalgenerators (PLL) und die Adressen der Register des Speichercontrollers vom DDR-Typ bekannt sind, ist es leicht herauszufinden, welche Nummern an welche Adressen geschrieben werden sollten, um den RAM-Controller korrekt zu initialisieren.Zusammenfassung
Wie Sie sehen, ist das Debuggen über JTAG mit openocd und GDB überhaupt nicht schwierig, und die beschriebenen Arbeitsmethoden eignen sich nicht nur für AR9331, sondern nach einiger Anpassung auch für Prozessoren mit einer anderen Architektur, für die openocd und GDB unterstützt werden.