Ich versuche gerade ein Image eines Flash-Speichers (32MB) für einen
ARM9 Prozessor (ARM926EJ-S) in QEMU unter Windows zum "laufen" zu
bringen. Konkreter gesagt möchte ich da drin debuggen. Dabei hoffe ich
auf etwas Hilfe von erfahrenen QEMU-Anwendern hier :-)
Mir ist schon klar das dies nicht einfach wird, da die ARM-Umgebungen ja
nicht genormt sind und jeder da sein eigenes Süppchen kocht.
Soweit ich das in der Doku verstanden habe ist die "qemu-system-arm.exe"
der zu verwendende Emulator. Dieser kennt zahlreiche "machine" (-machine
help) und "cpu" (-cpu help) typen.
Wie gehe ich an die Sache ran, auch im Hinblick eines Debuggings? Kommt
dafür gdb unter Windows (mingw) in Frage? Und wie "verheirate" ich das
alles, das ich breakpoints setzen kann, etc. Laut QEMU-Doku soll man die
Programmausführung mit "-s" verhindern können, sodass man quasi vom
RESET-Zeitpunkt an debuggen kann.
Danke für die Hilfe
Das ist im Prinzip gar nicht soooo schwierig. Es gibt nur leider recht
wenige Beispiele im Netz dazu. Ich hab vor kurzem mal ein Cortex M3
Projekt in QEMU gedebugged und dabei folgende Aufrufe genutzt:
Starten von QEMU, ohne graphische Ausgabe, Semihosting ein und GDB auf
1234:
Dankeschön :-)
Mein image ist jedoch nicht im ELF-format und der GDB meckert mir das an
("... is not a core dump: file format not recognized").
Wenn ich ich QEMU so starte (in einer CMD-Shell) dann passiert einfach
NICHTS. Sprich das Kommando geht zurück und nichts wurde gestartet.
Hmmm...
c:\Programme\qemu\qemu-system-arm.exe -machine n800 -nographic
-semihosting-config enable=on,target=native -gdb tcp:1234 -S -kernel
"D:\Temp\FX_8M5T-18K931-HD_7612300566_MBFLASH_PIN-8075.bin"
Dieses File hat auch keine Symbole, zumindest nicht das ich wüsste
(müsste man doch auch irgendwie rauskriegen? Ein einfacher "readelf"
bring nur "readelf: Error: Not an ELF file - it has the wrong magic
bytes at the start"). Es startet einfach ab 0x0000 mit ARM-Code.
Olli Z. schrieb:> Es startet einfach ab 0x0000 mit ARM-Code.
Im Zweifelsfall kann man daraus ein ELF machen, indem man es via objcopy
konvertiert und dann linkt; im Linkerscript muss man die Startadresse
einstellen.
Olli Z. schrieb:> Dieses File hat auch keine Symbole, zumindest nicht das ich wüsste
Kann es auch nicht haben, wenn es kein ELF/COFF/PE o.ä. ist, was nicht
der Fall ist, wenn es einfach nur ARM-Code ist!
Olli Z. schrieb:> Es startet einfach ab 0x0000 mit ARM-Code.
Niklas G. schrieb:> Im Zweifelsfall kann man daraus ein ELF machen, indem man es via objcopy> konvertiert und dann linkt; im Linkerscript muss man die Startadresse> einstellen.
Ok, ich habe nun mit
Vincent H. schrieb:> qemu-system-arm> -cpu cortex-m3> -machine lm3s6965evb> -nographic> -semihosting-config enable=on,target=native> -gdb tcp::1234> -S -kernel test.elf
Das führt bei mir dazu das offenbar nichts gestartet wird. Der Aufruf
erfolgt, aber anschließend wird kein QEMU geöffnet, bzw. ist gleich
wieder beendet (kann ich nicht erkennen).
Ich rufe das so auf (den n800 habe ich nur genommen weil da ein ARM926EJ
Prozessor drin ist):
Eigentlich müsste nun ja QEMU starten, das ELF lesen und einen Listener
auf dem Port 1234 starten auf den sich dann im Nachgang der GDB
connecten würde, richtig?
Olli Z. schrieb:> ein ELF erzeugt.
Das ist dann aber vermutlich ein x86 ELF :) Mache das so um ein ARM-ELF
zu erhalten, und die Section-Flags zu setzen:
Olli Z. schrieb:> Sieht ja eigentlich gut aus, oder?
Ja, aber das ist noch ein relokierbares Image ohne Adressen, d.h. QEMU
wüsste nicht wo das in den Speicher hin soll. Lege daher ein simples
Linker-Script an, z.B. in der Datei "image.ld":
1
ENTRY(Reset_Handler)
2
3
SECTIONS
4
{
5
.text 0x80000000 : {
6
Reset_Handler = .;
7
*.(text)
8
}
9
}
Wichtig ist hier, die Adresse des RAM zu setzen, gegen welche
hoffentlich der Code im Binary gelinkt wurde (sonst funktionieren keine
Sprünge). Als Entry-Point wird hier die allererste Adresse im Code
gesetzt. Ist das korrekt? Wenn am Anfang des Images noch eine
Exception-Tabelle oder so steht wäre es ggf. sinnvoll "Reset_Handler =
(. + 0x20);" oder so zu setzen. Weiß aber nicht ob QEMU das überhaupt
auswertet.
Danach kannst du das so linken:
1
arm-none-eabi-ld -T image.ld image.o -o image.elf
Das sollte dann ein korrektes ELF mit einer einzelnen binären Section
ergeben.
Ich habe bei mir testweise als image.bin eine Textdatei mit "Hallo!"
angelegt. Das ergibt:
1
$ readelf -S image.o
2
There are 5 section headers, starting at offset 0xf4:
Niklas G. schrieb:> wüsste nicht wo das in den Speicher hin soll. Lege daher ein simples
Linker-Script an
Ok, verstanden! :-)
> Wichtig ist hier, die Adresse des RAM zu setzen, gegen welche> hoffentlich der Code im Binary gelinkt wurde (sonst funktionieren keine
Sprünge).
Ah, ok, klar. Ja das System hat RAM und zwar einmal 64 Kbyte internes
SRAM ab Adresse 0x2000 0000 - 0x2000 FFFF und einmal 128 MByte externes
SD-RAM ab Adresse 0x8000 0000.
> Wenn am Anfang des Images noch eine Exception-Tabelle oder so steht
Ja, da ist der ARM Typische Block mit 0x20 Bytes, von denen aber nur der
Reset-Vector sinnvoll belegt ist, alle anderen führen zu einer
Endlosschleife. Der Reset springt dann zu 0xD1C. Dort beginnt eine
Initialisierung. Genau da möchte ich mit dem Debugging beginnen :-)
Also müsste ich dann im Linkerfile den "Reset_Handler = (. + 0xD1C);"
setzen?
> Danach kannst du das so linken:
Ok, teste ich.
> Ich habe bei mir testweise als image.bin eine Textdatei mit "Hallo!"
Ich kann auch gern die Daten hier bereitstellen, stehen in einem anderen
Thread von mir eh zum Download
https://www.mikrocontroller.net/attachment/423482/8S7T-18K931-AD_7612300524_PIN-8367.zip
Die ARM-spezifischen tools wie "arm-none-eabi-ld" habe ich in meiner
MinGW-Umgebung nicht drin. Mit welchem Paket erhalte ich die?
Olli Z. schrieb:> Ah, ok, klar. Ja das System hat RAM und zwar einmal 64 Kbyte internes> SRAM ab Adresse 0x2000 0000 - 0x2000 FFFF und einmal 128 MByte externes> SD-RAM ab Adresse 0x8000 0000.
Ok, muss man die beide belegen? Dann müsste man das Image auftrennen...
Olli Z. schrieb:> Also müsste ich dann im Linkerfile den "Reset_Handler = (. + 0xD1C);"> setzen?
Ist gar nicht nötig, die Einträge sind bei den ARM926 genau wie beim
ARMv7A (und im Unterschied zum Cortex-M) einfach nur
Sprung-Instruktionen ("B" oder "LDR"). Dann kannst du es so lassen, dann
springt es ja automatisch ans richtige Ziel. Ist sogar besser, dann
funktioniert es auch wenn sich die Adresse mal ändert.
Olli Z. schrieb:> Mit welchem Paket erhalte ich die?
Da z.B.:
https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-eabi/
Die für Cortex-M sollten aber auch gehen:
https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm
Vielleicht wertet QEMU die Architektur-Header auch gar nicht aus, dann
wäre es egal... Habe dein Image mal entsprechend zu einem ELF umgebaut
und angehängt.
Wenn du das Image aber selbst erstellt hast - wieso hast du dann nicht
sowieso schon eine ELF-Datei die beim Kompilieren & Linken hinten raus
fällt? Das wäre doch deutlich netter, dann hast du Funktionsnamen und
Debug-Symbole...
OKAY! Das klappt nun. Was etwas fizzelig an dieses linaro-Paket zu
kommen, bzw. es zu installieren. Letztlich musste ich erst über
"mingw-get" das "xz"-Tool runterladen, dann das linaro-Archiv (.xz
Datei), dieses in den C:\mingw\ Ordner werfen und mit "xz -xvf
gcc-linaro-7.4.1-2019.02-i686-mingw32_arm-eabi.tar.xz" entpacken damit
die Dateien an die richtigen Stellen kommen.
Nun habe ich das korrekte ELF und damit startet dann auch der QEMU wie
es Vincent oben geschrieben hat. Habe den Befehl eine eine Windows-Batch
geschrieben:
C:\Programme\qemu\qemu-system-arm.exe -machine n800 -gdb tcp::1234
-nographic -S -semihosting-config enable=on,target=native -kernel
"C:\temp\image.elf"
Anschließend kann ich in einer CMD-Shell den GDB starten:
gdb C:\temp\image.elf
(das "file" zwischen gdb und Dateiname musste ich weglassen damit es
funktioniert)
Und dann konnte ich im gdb mit "target remote :1234" verbinden.
1
C:\temp>gdb image.elf
2
GNU gdb (GDB) 7.6.1
3
Copyright (C) 2013 Free Software Foundation, Inc.
4
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
5
This is free software: you are free to change and redistribute it.
6
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
7
and "show warranty" for details.
8
This GDB was configured as "mingw32".
9
For bug reporting instructions, please see:
10
<http://www.gnu.org/software/gdb/bugs/>...
11
Reading symbols from C:\temp\image.elf...(no debugging symbols found)...done.
12
(gdb) target remote :1234
13
Remote debugging using :1234
14
warning: while parsing target description (at line 1): Target description specified unknown architecture "arm"
15
warning: Could not load XML target description; ignoring
16
0x00000000 in ?? ()
17
(gdb)
Allerdings kennt das ELF kein "main":
(gdb) thb main
Function "main" not defined.
Olli Z. schrieb:> Allerdings kennt das ELF kein "main"
Natürlich nicht. In der bin Datei sind keine Symbole, und das einbetten
in eine ELF Datei fügt die nicht hinzu. Du musst alles auf Basis von
Adressen und Assembler machen. Wie gesagt - beim selbst kompilieren
müsste eine ELF Datei mit Symbolen herauskommen. Man muss nur den Umweg
über die bin Datei vermeiden!
Das Debuggen mit gdb wird ziemlich mühsam werden, da eine Menge Dinge
fehlen die man normalerweise hat:
- keine Funktionsnamen, nur Adressen
- kein Stack, mit etwas Glück einen Level (die aufrufende Funktion, bei
ARM im LR register)
- keine Variablen
Vielleicht wäre ein Tool wie radare2, ghidra or IDA Pro eine bessere
Wahl?
Danke, das ist mir schon alles klar. Leider habe ich nichts und ich
wollte bewusst auf Assembler Ebene debuggen.
IDA Pro hätte ich zur Verfügung, aber damit habe ich bislang nur etwas
"offline" disassembliert. Es gibt aber Dinge die man nur mit breakpoints
entdecken kann.
Wo kommt das .bin Image denn her? Wenn du die Adressen von Funktionen
und Variablen kennst, kannst du die ins Linker Script schreiben:
1
main = 0x80001234;
2
buttonpressed = 0x80002000;
Dann sieht der Debugger die.
Also wenn man z.B. den Linux Kernel kompiliert, erhält man ein binäres
Abbild des Speichers ("zImage"), wie er beim starten aussehen sollte. Da
sind dann normalerweise keine Debug Infos und Symbole drin. Zuvor wird
allerdings eine ELF Datei angelegt ("vmlinux"), in welcher alle Symbole
drin sind. Diese kann man zwar nicht mit jedem Bootloader starten, aber
man kann sie in den GDB und vermutlich auch QEMU laden, um dann
komfortabel Debuggen zu können. Geht sowas bei dir nicht?
Niklas Gürtler schrieb:> Wo kommt das .bin Image denn her? Wenn du die Adressen von Funktionen
Das ist ein JTAG-Dump eines Flash-Speichers. Und ich gehe mal davon aus
das die Ursprünglichen Entwickler (einen könnte ich fragen, aber der
darf nix sagen, NDA) im Jahre 2005 oder wann das war, auch schon so
schlau waren keine Debug-Symbole oder sonstige Infos ins BIN zu packen.
Im Image (welches ich ja oben verlinkt habe, kann sich jeder selbst gern
anschauen) finden sich zwar zahlreiche Debug-Meldungen, aber das ist ja
eine andere "Ebene".
> und Variablen kennst, kannst du die ins Linker Script schreiben:>
1
main = 0x80001234;
2
> buttonpressed = 0x80002000;
> Dann sieht der Debugger die.
Interessant, das wäre eine Option. Derzeit mache ich das über IDA Pro im
disassemblierten ARM code. An einigen Stellen komme ich aber durch
reines lesen nicht weiter und müsste mal sehen wie sich das im
LIVE-Umfeld verhält.
Eine Idee war eben das Image über einen QEMU schrittweise ausführen zu
lassen. So könnten mir auch Leute helfen die selbst kein solches Gerät
zur Verfügung haben :-)
Eine andere wäre es das im Gerät zu tun, mit dem Segger JTAG Debugger.
> Also wenn man z.B. den Linux Kernel kompiliert, erhält man ein binäres> Abbild des Speichers ("zImage"), wie er beim starten aussehen sollte. Da> sind dann normalerweise keine Debug Infos und Symbole drin. Zuvor wird> allerdings eine ELF Datei angelegt ("vmlinux"), in welcher alle Symbole> drin sind. Diese kann man zwar nicht mit jedem Bootloader starten, aber> man kann sie in den GDB und vermutlich auch QEMU laden, um dann> komfortabel Debuggen zu können. Geht sowas bei dir nicht?
Danke für die Erklärung, so langsam verstehe ich das dadurch. Äh, nein,
bei meinem BIN image kann ich im moment noch garnichts machen, nichtmal
schrittweise ausführen lassen, keine Speicher auslesen, keine Register
anzeigen. Irgendwas stimmt also noch nicht...
Ich habe noch dieses interessante Dokument gefunden:
https://www.hex-rays.com/products/ida/support/tutorials/debugging_qemu.pdf
Darin geht es um die Instrumentation von QEMU von IDA aus, um
"Codesnippets" ausführen zu können. Also genau was ich suche. Das
probiere ich nun mal aus, ob ich da weiter komme.
So wie ich es sehe kann man mit IDA durchaus direkt debuggen. Eine
möglichkeit ist es wohl dies über einen Debug-Server zu tun. Helft mir
mal ob ich das richtig verstanden hab:
----
Der Debug-Server kann z.B. ein im Netzwerk befindliches "J-Link ARM"
Interface sein. Der Adapter spricht dann auf der Hardware-Seite via
JTAG/SWD mit dem ICE des Chips und auf der Softwareseite mit dem
Debugger in IDA.
Oder der Debug-Server wird als Prozess auf dem Remote-System gestartet
in dem auch die zu debuggende Software läuft, bzw. ausgeführt werden
kann. Das kommt wohl gerne bei Linux oder Windows-Betriebssystemen zum
Einsatz.
Die Schwierigkeit in einer proprietären Umgebung wie meinem Board ist es
wohl das man wenig Möglichkeit hat darin einen Debugger zu starten,
daher wäre eine externe Hardware-Lösung schon das Mittel der Wahl für
ein "In-Circuit-Debugging".
----
Überhaupt muss man immer aufpassen das man beim Thema ARM-Debugging
nicht in einer Linux-Welt landet, sondern schön in seiner SoC-Umgebung
bleibt.
Olli Z. schrieb:> Das ist ein JTAG-Dump eines Flash-Speichers. Und ich gehe mal davon aus> das die Ursprünglichen Entwickler (einen könnte ich fragen, aber der> darf nix sagen, NDA)
Sag das doch früher...
Olli Z. schrieb:> keine Debug-Symbole oder sonstige Infos ins BIN zu packen.
Wie denn auch; der ausführende Prozessor kann mit Debug-Symbolen nichts
anfangen, daher werden die im Speicher nicht gebraucht, daher gibt es
kein Format mit dem die Symbole in einem Speicherabbild abgelegt werden
können. Theoretisch könnte man die entsprechenden ELF-Sections in einen
nicht genutzten Bereich legen, aber das ist mehr als unüblich...
Olli Z. schrieb:> Derzeit mache ich das über IDA Pro im> disassemblierten ARM code.
Und IDA merkt sich die von dir annotierten Funktionsnamen? Das ist doch
auch gut, wenn du das ins Linkerscript exportierst wäre es im ELF
"persistent" auch für andere Tools.
Olli Z. schrieb:> nichtmal> schrittweise ausführen lassen, keine Speicher auslesen, keine Register> anzeigen. Irgendwas stimmt also noch nicht...
Kannst du es nicht mal mit einem Beispiel-Image für QEMU ausprobieren?
Eines das nur aus NOPs besteht? Oder besser noch - ein "normal" via GCC
kompiliertes & gelinktes ELF, mit allen (Debug-)Symbolen.
Olli Z. schrieb:> Der Debug-Server kann z.B. ein im Netzwerk befindliches "J-Link ARM"> Interface sein
Oder per USB angeschlossen, und auf dem PC läuft der JLinkGDBServer...
Olli Z. schrieb:> Überhaupt muss man immer aufpassen das man beim Thema ARM-Debugging> nicht in einer Linux-Welt landet, sondern schön in seiner SoC-Umgebung> bleibt.
Na, was für ein OS ist das denn? Linux ist hier ja praktisch
De-Facto-Standard.
Also, ich habe die grundlegende Funktionalität gefunden. In der
cfg/dbg_arch.cfg von IDA wird der Pfad zum QEMU über eine Variable
gesetzt. Default ist %ProgramFiles%/QEMU, was bei mir passte.
Dann geht man in IDA in die "Debugger options...", dort auf "Set
Specific Options", "Choose a configuration" und wählt "ARM_versatilepb".
Die restlichen Settings sind interessant, aber für den Anfang reicht
das.
Nun startet man den Debugger, dieser startet QEMU und man kann
schrittweise ausführen, Breakpoints setzen, etc. :-)
Also nächstes würde ich versuchen die Memory Map des Gerätes dort
abzubilden, damit die Speicherzugriffe grundsätzlich funktionieren.
Was natürlich nicht geht ist die Abbildung der SoC memory mapped
ports...
Olli Z. schrieb:> Was natürlich nicht geht ist die Abbildung der SoC memory mapped> ports...
Hat QEMU gar keine Unterstützung für den spezifischen SoC? Hoffentlich
greift dein Kernel nicht gleich in den ersten 7 Instruktionen auf
SoC-spezifische Hardware zu - selbst der ARM-Kern ist ja konfigurierbar,
und je nachdem ob der Code eine bestimmte Konfiguration annimmt oder
generisch ist (wie Linux) kann der auch ziemlichen Unsinn machen...
Ist letztlich eine Frage wie früh der Code crasht :)
Na klar ist der SoC anders als ein reiner ARM. Der OMAP5912 hat ja noch
nen DSP und internes SRAM und ROM und jede Menge IO-Kram.
Ich wüsste garnicht wie man das "in software" nachbilden sollte.
Aber ich würde einfach mal sehen wie weit ich komme.
In der Init-Sequenz werden bestimmte über Speicheradressen verfügbare
Peripheriebausteine initialisiert und teilweise auch abgefragt. Hier
gibt es schon erste Endlosschleifen weil dort auf einen bestimmten
Zustand gewartet wird. Diese Adresse liegen irgendwo bei 0xFFFE 0000
aufwärts bis 0xFFFF FFFF.
D.H. wenn ich diesen Teil als RAM definiere dann müsste ich doch
innerhalb der Debugger-Sitzung die abgefragte Speicherstelle ändern
können sodass es weiter geht, oder ich überspringe die Loop einfach.
Naja "in-circuit" wäre definitiv besser :-) Aber dazu brauche ich einen
J-Link ARM.
IDA baut beim o.g. Setup selbst eine temporäre ELF-Datei. Tja, die
kochen halt auch nur mit Wasser ;-)
Ich habe versucht noch etwas mehr über die gdb_arch.cfg herauszufinden.
Der per Default vorhandene ARM-Eintrag sieht ja so aus:
1
.ARM_versatilepb
2
name QEMU: ARM Versatile/PB
3
cpu arm
4
range DATA RAM 0x00000000:0x08000000
5
range IO SYSREGS 0x10000000:0x10200000
6
initial_sp 0x08000000
7
8
; %i - input file from "Process Options" dialog
9
; %e - temporary ELF file created from database contents
Die MACHINE "versatilepb" ist ja gut (ARM Versatile/PB (ARM926EJ-S)),
nur die CPU stimmt doch nicht. Der OMAP5912 hat eine ARM926EJ-S CPU (so
wie in der "machine" eigentlich vorgesehen) und da sollte dann doch
besser "-cpu arm926" stehen, oder?
Und dann kommen die IDA-Parameter zur Memory-Map. Die passt auch nicht
zu meinem OMAP. Wo finde ich denn eine Übersicht der für "range"
gültigen Schlüsselwörter und wie geht IDA damit um?
Die Memory-Map die ich bislang ermittelt habe müsste so aussehen:
Olli Z. schrieb:> D.H. wenn ich diesen Teil als RAM definiere dann müsste ich doch> innerhalb der Debugger-Sitzung die abgefragte Speicherstelle ändern> können sodass es weiter geht, oder ich überspringe die Loop einfach.
Theoretisch ja... Aber dann z.B. DMA-Transfers oder Timer-Interrupts mit
dem richtigen Timing zu simulieren dürfte spannend werden.
Olli Z. schrieb:> Aber dazu brauche ich einen> J-Link ARM.
Die Investition lohnt sich. Die 50€ für private Nutzung wären es mir
wert die vielen grauen Haare mit QEMU zu sparen! Wenn man sich schon IDA
leistet sind das doch Peanuts :)
Olli Z. schrieb:> und da sollte dann doch> besser "-cpu arm926" stehen, oder?
Vermutlich. Der Cortex-A8 ist ja schon ziemlich anders.