Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
am335xbootrom — Reverse Engineering des TI AM3358 Boot-ROMs | Kitploit
Tools/GitHubGitHub/sjgallagher2/am335xbootrom
Embedded-System-SicherheitReverse EngineeringDebuggerHardware-HackingBinäranalyseLernen & BildungFirmware-Analyse
GitHubsjgallagher2/am335xbootrom

am335xbootrom

Reverse Engineering des TI AM3358 Boot-ROMs

Repository anzeigen
6156vor 2 JahrenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

Reverse Engineering des AM335x Boot-ROM

Es ist wohl achtzehn Monate her, seit ich ein paar Beaglebone-Black-Boards in die Hände bekam, die aus dem Mülleimer gerettet wurden. Leider funktionierten die Boards nicht auf Anhieb. Nun war es mein erstes Mal, mit einem dieser Boards zu arbeiten – oder überhaupt mit einem Single-Board-Computer –, also war ich mir nicht sicher, ob das Problem an etwas lag, das ich tat, oder an den Boards selbst (vielleicht der Grund, warum sie überhaupt im Mülleimer gelandet waren). Es hat ziemlich lange gedauert und eine Menge Mühe gekostet, diese Boards tatsächlich zum Booten zu bringen, aber verdammt, ich habe es geschafft. Und hier ist, was ich dabei gelernt habe.

HINWEIS: So verwendest du die Ghidra-XML-Datei

Ich habe ein paar Hilfsprogramme in dieses Repo gepackt, zusammen mit einer aus Ghidra exportierten XML-Datei, die alle Symbole enthält, die ich bisher durch das Reversing gewinnen konnte. Ich habe diesen Beitrag verwendet, um ohne die eigentliche Firmware zu exportieren, um Urheberrechtsprobleme zu vermeiden, nur für den Fall. Wenn du das Boot-ROM selbst debuggen willst, hast du den JTAG ohnehin angeschlossen und kannst das Boot-ROM (von 0x20000 bis 0x2BFFF) selbst auslesen.

So lädst du die Symbole:

  1. Erstelle ein neues Ghidra-Projekt. Importiere die Binärdatei (nicht die XML) in Ghidra: Verwende ARMv7 Little Endian und stelle sicher, dass du unter Options die Basisadresse auf 0x20000 setzt; den Blocknamen kannst du auf bootrom setzen.
  2. Öffne diese Binärdatei in CodeBrowser. NICHT ANALYSIEREN.
  3. Gehe auf File > Add program und wähle die XML-Datei aus. Die Standardeinstellungen sollten passen. Du kannst nun den Reset-Handler durchgehen oder direkt zu main() oder zum MMC/SD-Karten-Boot-Handler springen.

Das Problem

Zunächst wusste ich, dass es sich um kundenspezifische Versionen des Standard-Beaglebone-Black handelte, also habe ich früh festgestellt, dass auf der Platine selbst etwas fehlen könnte, etwa eine Board-Kennung. Als ich eine standardmäßige, mit balenaEtcher formatierte SD-Karte bootete, sah ich einfach nichts. Ich erwartete, dass die LEDs auf dem Board zu blinken beginnen, und ich erwartete, dass das Anschließen eines UART-zu-USB-Kabels mir erlauben würde, den U-Boot-Prozess zu sehen. Doch der UART blieb stumm. Wenn ich die SD-Karte entfernte, gab das Board immer wieder den Buchstaben C aus, was für einen UART/Seriell-Boot erwartetes Verhalten ist. Es hat definitiv versucht zu booten, und die SD-Karte veränderte dieses Verhalten, aber ich hatte keinen weiteren Einblick. Der Großteil der Fehlersuche im Internet nahm die U-Boot-Ausgabe als Ausgangspunkt, um Probleme zu diagnostizieren. Ich schätze, diesen Luxus würde ich nicht haben.

An diesem Punkt dachte ich, dass es sich lohnen würde, eine Debug-Probe anzuschließen. Leider hatte ich keinen passenden Header für den vorhandenen Footprint, also habe ich mir selbst einen gebaut.

Das Beaglebone-Board besitzt einen Header mit der Bezeichnung P2, über den die JTAG-Verbindungen herausgeführt werden. Ich habe ein paar Drähte von diesem Header zu einer Buchsenleiste verbunden, sodass ich über meinen J-Link darauf zugreifen kann.

Als ich Ozone (den Segger-Debugger) startete, konfigurierte ich den J-Link und begann damit, zunächst nur den Entry Point zu finden. Ich hatte gedacht, ein Reset-Halt würde mich an die gewünschte Stelle bringen; so kam ich zu der (falschen) Annahme, der Entry Point sei 0x2148a, obwohl mir durchaus auffiel, dass das nicht konsistent war. Später merkte ich, dass die AM335x-Boards nicht wirklich gut mit dem Reset-Halt des J-Links zusammenarbeiten, sodass es tatsächlich eine Verzögerung von vielleicht ein paar hundert Taktzyklen gab, die mich indeterministisch irgendwo in einem Boot-Handler landen ließ. (Ich habe das schließlich umgangen, indem ich eine GEL-Datei für TI Code Composer Studio geschrieben habe, das J-Link-Debugging unterstützt – beim Reset wird das PC-Register auf den Reset-Handler gesetzt, die Register werden gelöscht und der Befehlsmodus wird auf ARM erzwungen.)

Aus einem Thread in den TI-Foren (AM335x: TI-Mitarbeiter, wo bekomme ich den ROM-Bootloader-Quellcode/die Symbole?) habe ich ein paar Debug-Symbole mitgenommen: SPI Initialize bei 0x231e0, SPI ReadSectors bei 0x23230, und 0x24bfa ist eine Routine, die einen UART-Read durchführt. Das ist wohl eine ganz nette Hilfe, schätze ich. Mir fiel auf, dass der Boot fehlschlug, indem er in einer Endlosschleife bei 0x402f0440 landete, einer toten Schleife. Hmm, ziemlich weit entfernt vom Rest des Boot-ROMs, muss in RAM oder so sein. Es ist wohl an der Zeit, zum Technical Reference Manual (TRM) zu wechseln!

Kapitel 26 des TRM enthält jede Menge Informationen zum Booten. Wir erhalten die folgende Ansicht des Boot-ROMs:

Beschreibung:

Die Architektur des Public ROM Code ist in Abbildung 26-1 dargestellt. Sie ist in drei Hauptschichten unterteilt, im Top-down-Ansatz: High-Level, Treiber und Hardware-Abstraktionsschicht (HAL). Eine Schicht kommuniziert über eine einheitliche Schnittstelle mit einer darunterliegenden Schicht. Die High-Level-Schicht ist für die Hauptaufgaben des Public ROM Code zuständig: Watchdog- und Taktkonfiguration sowie die Haupt-Boot-Routine. Die Treiberschicht implementiert die logischen und Kommunikationsprotokolle für jedes bootende Gerät gemäß der Schnittstellenspezifikation. Schließlich implementiert das HAL den Code der untersten Ebene für die Interaktion mit den Hardware-Infrastruktur-IPs. Die bootenden Endgeräte sind an die Geräte-IO-Pads angeschlossen.

Abbildung 26-2 veranschaulicht den High-Level-Ablauf des Bootvorgangs des Public ROM Code. Auf diesem Gerät startet der Public ROM Code nach Abschluss des sicheren Startvorgangs (durchgeführt vom Secure ROM Code). Der ROM Code führt dann die Plattformkonfiguration und -initialisierung als Teil des öffentlichen Startvorgangs durch. Die Liste der Boot-Geräte wird basierend auf den SYSBOOT-Pins erstellt. Ein Boot-Gerät kann ein Speicher-Boot-Gerät sein (gelöteter Flash-Speicher oder ein vorübergehendes Boot-Gerät wie eine Speicherkarte) oder eine Peripherieschnittstelle, die mit einem Host verbunden ist. Die Hauptschleife des Bootvorgangs durchläuft die Liste der Boot-Geräte und versucht, ein Image vom derzeit ausgewählten Boot-Gerät zu finden. Diese Schleife wird verlassen, wenn ein gültiges Boot-Image gefunden und erfolgreich ausgeführt wird oder wenn der Watchdog abläuft. Die Image-Authentifizierung wird vor der Image-Ausführung auf einem HS-Device durchgeführt. Ein Fehler bei der Authentifizierung führt zu einer Verzweigung in eine „Dead Loop“ im Secure ROM (Warten auf einen Watchdog-Reset).

Memory Map! Ausnahmevektoren! Flussdiagramme! Jede Menge Informationen in diesem Abschnitt. Meine Aufgabe wurde viel einfacher.

An diesem Punkt nutzte ich die JTAG-Sonde, um die Firmware in ein paar verschiedene Dateien herunterzuladen, und begann, Dinge in Ghidra zu laden. Es schien keine SVD-Dateien oder anderen Register-Mappings in einem praktischen Format zu geben, was wirklich ärgerlich ist, denn das bedeutet, dass ich die Speicherbereiche und Register und alles manuell definieren muss. Das war ein mühsamer Prozess, aber nach einer Weile hatte ich ein Python-Skript, mit dem ich Symbole für den AM3358 in Ghidra laden konnte. Eine Sorge weniger!

Reverse Engineering

Es scheint, als lassen sich die Dateien, die ich habe, wie folgt zuordnen:

Es ist interessant, dass sich die Endlosschleife bei 0x402f_0440 am Anfang des „heruntergeladenen Images“ im internen SRAM befindet, während die Ausnahmevektoren woanders gespeichert sind. Vielleicht ist das später ein wichtiger Hinweis ...

Beim Reset kümmert sich das private Boot-ROM um den Sicherheitskram und verzweigt zu 0x2 0000, wo die Reset-Vektoren liegen. Die erste Anweisung ist ein Sprung zu 0x2 08d0, was der Entry Point sein muss. Es ist keine BX-Anweisung, also befinden wir uns zu diesem Zeitpunkt vermutlich noch im ARM-Modus.

Reset-Handler

Dies ist der erste ausgeführte Code, was bedeutet, dass es sich nicht wirklich um eine „Funktion“ mit Parametern handelt, sondern eher um ein compilergeneriertes Startskript. Der erste Basisblock:```arm ldr r4,[->Peripherals::CM_PER] mov r0,#0x2c ldr r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL mov r6,#0x2 str r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL mov r0,#0x2c poll: ldr r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL cmp r6,#0x2 bne poll

root@kitploit:~
Dieser Block setzt den OCMC-RAM-Takt auf aktiviert:
1. Setze `CM_PER_OCMCRAM_CLKCTRL=0x2`
2. Prüfe, ob das Register gesetzt wurde; wenn nicht, weiter pollen
Das Register `CM_PER_OCMCRAM_CLKCTRL` verwendet die Bits 0 und 1 für das Feld `MODULEMODE`; das Setzen auf `=0x2` aktiviert den Takt für den OCMC-RAM.

Der nächste Basisblock:```arm
	    ldr        r0,[PTR_control_status] 
	    ldr        r0,[r0,#0x0]=>control_status 
	    and        r0,r0,#0x700
	    mov        r0,r0, lsr #0x8
	    cmp        r0,#0x3
	    bne        skip
		ldr        r0,[PTR_control_status] 
	    ldr        r0,[r0,#0x0]=>control_status
	    cpy        r6,r0
	    and        r0,r0,#0x1f
	    cmp        r0,#0x1f
	    bleq       GPMIC_init 
skip:   ...

Dieser Block macht Folgendes:

  1. Prüfen, ob (control_status & 0x700) >> 8 == 0x3 ist; wenn nicht, überspringen
  2. Prüfen, ob control_status & 0x1f == 0x1f ist; wenn ja, nach dem Laden von control_status in r6 die Funktion GPMC_init aufrufen

Der nächste Block richtet den Koprozessor ein:```arm msr cpsr_c,#0xd3 ldr r4,[->Exceptions::ROM_RESET_VECTOR] mcr p15,0x0,r4,cr12,cr0,0x0 bl LAB_00020934 bl LAB_00020938 bl LAB_0002093c bl LAB_00020940 bl LAB_00020944 bl LAB_00020948 bl LAB_0002094c bl LAB_00020950 mrc p15,0x0,r0,cr1,cr0,0x0 orr r0,r0,#0x800 mcr p15,0x0,r0,cr1,cr0,0x0 b LAB_000207f0

root@kitploit:~
Operationen in diesem Block:
1. Verschiebe `11010011b` in das CPSR-Kontrollfeld (`I=1`,`F=1`,`T=0`,`MODE=10011`)
	1. `I` ist Interrupt-Deaktivierung, `F` ist Fast-Interrupt-Deaktivierung (also bedeutet `I=F=1`, dass Interrupts deaktiviert sind)
	2. `T` ist der Thumb-Modus, auf `0` gesetzt
	3. `MODE=10011` setzt den Prozessormodus auf den Supervisor-Modus ([ref](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/The-System-Level-Programmers--Model/ARM-processor-modes-and-core-registers/ARM-processor-modes?lang=en#CIHGHDGI))
	4. Siehe [hier](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/The-System-Level-Programmers--Model/ARM-processor-modes-and-core-registers/Program-Status-Registers--PSRs-) für weitere Informationen
2. Lade die Adresse zum ROM-Reset-Vektor
3. Greife auf das Security-Extensions-Register `c12` von [Coprocessor 15](https://developer.arm.com/documentation/den0013/d/ARM-Processor-Modes-and-Registers/Registers/Coprocessor-15) (Systemkontroll-Coprozessor) zu und lade den ROM-Reset-Vektor in das `VBAR` (Vector Base Address Register)
![](https://assets.kitploit.com/production/public/readmes/48883/6c4ef51fde89883fcdb3a9497004830d928d6f6c716668acd86dce3b9a68c93b.png)
4. Sieht aus wie `nop`-Sprünge? Warum `bl` statt `b`?
5. Aktiviere die Sprungvorhersage (setze Bit 11 des Systemkontrollregisters `SCTLR`)
![](https://assets.kitploit.com/production/public/readmes/48883/7a99ff8bd5285136c05b9d91bdf5057a6512c614cacc2726ce740254e4c240fa.png)
*CP15-`c1`-Register (Systemkontrollregister) in der VMSA-Implementierung*

Beschreibung des `SCTLR`-Registers:
> Das SCTLR bietet die Kontrolle auf oberster Ebene über das System, einschließlich seines Speichersystems.
> Dieses Register gehört zur Funktionsgruppe der Steuerregister für den virtuellen Speicher.

Siehe TRM Seite B4-1687. Bit 11 ist das *Branch-Prediction-Enable*-Bit; wenn es gesetzt ist, ist die [Sprungvorhersage](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/Common-Memory-System-Architecture-Features/Caches/Branch-predictors) aktiviert.

6. Rufe eine Funktion auf (über einen Sprung zu einer Aufrufanweisung)

### Funktion bei `0x20894` (`__main`)

Diese Funktion wird von einer anderen frühen Routine angesprungen. Ich glaube, sie initialisiert den Stack und möglicherweise Timer oder den Watchdog, bevor sie `FUN_0002889c` aufruft (die sich später als `main()` herausstellt!).```arm
		 ldr    sp,[->RESERVED_EXCEPTION_BRANCH]   ; 0x4030ce00
		 blx    load_stack_1
		 ldr    r12,[DWORD_1]
		 add    r12,r12,pc
		 tst    r12,#0x1
		 adrne  lr,0x208bd
		 cpyeq  lr,pc
		 bx     r12 ;=>init_timers_maybe
		 adr    r12,0x208bd
		 bx     r12 ;=>LAB_000208bc
000208bc bl     FUN_0002889c
000208c0 ddw    0x109
000208c4 addr   RESERVED_EXCEPTION_BRANCH
...
RESERVED_EXCEPTION_BRANCH:
         ldr    pc=>LAB_00020090,[PTR_LAB_4030ce20]
         ; 20090 is a dead loop

Die Vorgänge hier sind:

  1. Den Anfang der RAM-Ausnahmetabelle in den Stack-Pointer laden
  2. Zu einer Funktion verzweigen, die r0,r1,r2,r3,r4,lr auf den Stack legt (öffentlicher Stack in der Speicherkarte)
    1. Diese Funktion load_stack_1 verzweigt zu einer leeren Funktion bx lr und holt dann r0,r1,r2,r3,r4,pc vom Stack (setzt die Daten im Wesentlichen zurück in die Register und legt das, was lr enthält, in den pc, um zurückzukehren)
  3. Prüfen, ob pc + 0x109 ungerade ist; wenn gerade, 0x208bd in lr laden, andernfalls pc in lr kopieren
  4. Hauptfunktion aufrufen
  5. FUN_0002889c aufrufen

Dies sollte die Funktion __main() sein, auf die im Boot-Ablaufdiagramm Bezug genommen wird:

Das würde die nächste Funktion zur main-Funktion machen.

Wie oben in Abbildung 26-8 gezeigt, springt die CPU zum Public-ROM-Code-Reset-Vektor, sobald sie die sichere Boot-Initialisierung abgeschlossen hat. Im öffentlichen Modus führt die CPU beim Systemstart die öffentliche Initialisierung und das Stack-Setup durch (vom Compiler automatisch erzeugte C-Initialisierung oder „Scatter Loading"). Dann konfiguriert sie den Watchdog-Timer 1 (auf drei Minuten eingestellt) und führt die Systemtakt-Konfiguration durch. Schließlich springt sie zur Boot-Routine.

Hauptfunktion (0x209b0)

Wenn main aufgerufen wird, zeigt das SP-Register auf 0x4030ce00. Hier beginnt der Stack, und er wächst nach unten in Richtung 0x4030 b800; und da wir nach dem Pushen von 4 Registern (eine Differenz von 16 Bytes oder 4 Wörtern) auf die Adresse 0x4030 cdf0 zeigen, verwenden wir einen voll absteigenden Stack, wie im AAPCS. Das heißt, SP zeigt auf das zuletzt abgelegte Wort im Stack und wächst nach unten.

Hier ist die dekompilierte main()-Funktion:```c int main() { uint local_10; uint local_c;

local_c = 0; local_10 = 0; check_stack_prm(&local_10); update_coldreset_tracing_vector(local_10); update_current_tracing_vector(1); main_clock_init(6,0); watchdog_softreset(); watchdog_write_disable_seq_data2(); set_watchdog(300000); if ((local_10 & 1) != 0) { update_current_tracing_vector(2); local_c = local_c & 0xffff | 1; } timer_func_1(); clock_init_func_4(&local_c); run_booting_loop(&local_c,local_10 & 0xff); return 0; }

root@kitploit:~
Most interesting for my purposes is the `run_booting_loop` function at `0x20a10`. 

### Hinweise zum X-Loader

Nachdem ich den Startvorgang durchgegangen bin und zu diesem Teil mit Strings wie "ISSW" und "CHSETTINGS" und "X-LOADER" gelangt bin, habe ich mich auf die Suche nach anderen Stellen gemacht, an denen diese Strings in U-Boot-bezogenen Kontexten auftauchen könnten. Ich bin auf [diesen Thread](https://forum.xda-developers.com/t/discussion-on-the-boot-loader-cracked.1378886/) gestoßen, in dem Leute die Nook-Firmware reversen oder knacken, und die [x-loader-Quelle](https://github.com/joelagnel/x-loader/blob/f3c74bc9b01dac58e553393d6ec1041353f2f1f7/scripts/signGP.c) enthält Verweise auf Dinge wie `CHSETTINGS`. Nach meinen Recherchen scheint sich "ISSW" [auf das Booten von Nicht-Speichergeräten zu beziehen](https://github.com/u-boot/u-boot/blob/master/doc/README.ti-secure). 

Erinnern wir uns an den High-Level-Code aus der Initialisierungsdokumentation:
![](https://assets.kitploit.com/production/public/readmes/48883/3b7ecd05acbf1f0aa5497009b4df65d17a7b0e133b9b711bebaf9f5bebf546cf.png)

Von Interesse:
- RNDIS
- FAR
- XMODEM
- BOOTP
- TFTP
- DFT

Vielleicht ist es an der Zeit, wieder etwas Live-Debugging zu versuchen. Den Versuch, all diese Structs zu reversen, wäre wahrscheinlich schmerzhaft, angesichts der großen Menge an Daten, die ich nicht verstehen kann...

Hurra! Live-Debugging funktioniert, wenn man PC und SP manuell mit den Vektoren setzt, die ich gefunden habe:
![](https://assets.kitploit.com/production/public/readmes/48883/3886242947cd1cef1093f3cb02dbbcc02faf27f1298708dd849e69eae7b75f97.png)

Anhand der Quelle und der Tracing-Vektoren konnte ich die verschiedenen Boot-Optionen und die ihnen zugewiesenen Gerätenummern ermitteln. Das erwies sich später als sehr nützlich, da ich beim Setzen von Breakpoints im SD/MMC-Boot-Handler zwischen MMC0 (8) und MMCSD1 (9) unterscheiden musste. 

| Type       | Device            | Device ID   |
| ---------- | ----------------- | ----------- |
| Speicher     | XIP (MUX2)        | 1           |
| Speicher     | XIP w/WAIT (MUX2) | 2           |
| Speicher     | XIP (MUX1)        | 3           |
| Speicher     | XIP w/WAIT (MUX1) | 4           |
| Speicher     | NAND              | 5           |
| Speicher     | MMCSD1            | 7, 9 (eMMC) |
| Speicher     | NAND_I2C          | 10          |
| Speicher     | MMC0              | 8, 12 (SD)  |
| Peripherie | UART0             | 16          |
| Peripherie | USB               | 20          |
| Peripherie | GPGMAC0           | 22          |

Informationen darüber, wie der Prozessor bootet, finden Sie im TRM und in [dieser Antwort auf Stack Exchange](https://stackoverflow.com/a/31252989/8565545). Zur Zusammenfassung: 
1. Das Boot-ROM hat die MLO-Datei (Mmc LOader) auf der SD-Karte identifiziert und in den SRAM kopiert
2. Dies ist der sekundäre Programmloader, ein kleinerer Bootloader, der den gesamten RAM initialisiert und die vollständige U-Boot-Binärdatei zur Ausführung dorthin kopiert
3. Nachdem die U-Boot-Binärdatei ausgeführt wurde, booten wir (bzw. U-Boot) schließlich den Kernel

### `run_booting_loop()`

Dies ist die Haupt-Boot-Schleife. Sie läuft unendlich oder bis die Ausführung zu einem anderen Bootloader verzweigt, der in den RAM geladen wird. 

Beginn der Prozedur, ohne Tracing-Vektor-Updates:
- Gerätetyp nachschlagen
	- Wenn der Gerätetyp 5 (sicheres Gerät) ist, eine andere Initialisierung durchführen
- `build_boot_list(int,buffer[],data[],int)` ausführen  
	- `buffer[]` wird mit `0xff` initialisiert und `data[]` enthält den Gerätetyp (wahrscheinlich)```c
void run_booting_loop(uint32_t *r0_config,undefined4 param_2,undefined4 param_3,
                     undefined4 default_list)

{
  int iVar1;
  uint j;
  uint i;
  int device_type;
  byte alt_list [12];
  undefined4 boot_status;
  byte boot_list [8];
  uint8_t local_buffer [8];
  
  update_current_tracing_vector(3);
                    /* Device type is 3 */
  lookup_device_type(&device_type);
  if ((device_type == AM335X_HIGH_SECURITY) && (iVar1 = return_zero_4(), iVar1 != 0)) {
    init_something_1_small(&STATIC_DATA_1);
  }
                    /* param1 = 1
                       param2 = 4030 ebc4
                       param3 = 4030 ebb4 */
  build_boot_list(*(ushort *)r0_config,boot_list,alt_list,default_list);
  do {
    i = 0;
    local_buffer[0] = 0xff;
    local_buffer[1] = 0xff;
    local_buffer[2] = 0xff;
    local_buffer[3] = 0xff;
    do {
      if (boot_list[i] - 1 < 12) {
        update_current_tracing_vector(4);
                    /* No return unless there is an error */
        boot_device_1(r0_config,boot_list[i],local_buffer);
      }
      else if (boot_list[i] - 65 < 8) {
        update_current_tracing_vector(5);
        watchdog_write_disable_seq_data2();
        boot_status = 0xffffffff;
        boot_device_2((uint32_t)r0_config,boot_list[i],&boot_status,local_buffer);
        watchdog_write_enable_seq_data2();
        if (boot_status != 0xffffffff) {
          local_buffer[0] = (undefined)boot_status;
          local_buffer[1] = boot_status._1_1_;
          local_buffer[2] = boot_status._2_1_;
          local_buffer[3] = boot_status._3_1_;
          if ((boot_status & 0xffff00ff) == 0xf0030006) {
            update_current_tracing_vector(9);
            boot_list[i + 1] = (byte)(boot_status >> 8);
          }
          else if (boot_status != 0xf0030002) {
            update_current_tracing_vector(8);
            j = 0;
            do {
              if (63 < boot_list[j]) {
                boot_list[j] = 0;
              }
              j = j + 1 & 0xff;
            } while (j < 8);
          }
        }
      }
      i = i + 1 & 0xff;
    } while (i < 8);
    update_current_tracing_vector(6);
  } while( true );
}

Booten in den SRAM

Ich fand heraus, dass es eine Funktion bei 0x23d7a gab, die ich boot_into_SRAM() genannt habe, die die letzte Funktion war, die aufgerufen wurde, bevor in den SRAM verzweigt wurde, und von dort dann in den Exception-Handler ging. Zuvor hatte ich einen anderen RAM-Zustand gespeichert, aber während eines weiteren Live-Debuggings (jetzt über ein Jahr später, Juli 2024) wurde mir klar, was vor sich gehen musste. Das Board las erfolgreich Daten von der SD-Karte und führte Code aus, den es von der Karte geladen hatte! Um das zu verifizieren, musste ich Bytes im SRAM finden, die mit denen auf der SD-Karte übereinstimmten. Es stellte sich heraus, dass es innerhalb von am335x-evm-linux-sdk-bin-.../board-support/prebuilt-images/ eine Binärdatei namens u-boot-spl.bin-am335x-evm gibt, und der Code in dieser Binärdatei stimmt mit dem überein, was im SRAM erscheint. Wir haben das U-Boot-SPL erreicht!

UART-Terminal

Wir haben erfolgreich in den SRAM gebootet. Jetzt frage ich mich, was das UART-Terminal angeht, das Informationen über U-Boot anzeigen sollte. Die Hardware-Verbindung ist unten dargestellt.

Beim Verbinden des Geräts mit CuteCom, 115200 @ 8-N-1, gibt es ohne eingelegte SD-Karte einfach wiederholt C aus.

Aber wenn die SD-Karte eingelegt ist, gibt der UART nichts aus. Keine Meldungen, keine Zeichen. Der Fehler muss zu früh im Boot-Prozess auftreten? Aber jetzt, wo ich auch weiß, welchen Code es ausführt (und ich dessen Quellcode habe), sollte ich in der Lage sein, Debugging-Symbole dafür zu bauen und eine ordentliche Debug-Sitzung zu starten. Das könnte nicht trivial sein; ich muss sicherstellen, dass ich den Code auf dieselbe Weise kompiliere. Es könnte einfach einige Zeit dauern, bis ich genau lerne, was das SDK auf meine SD-Karte geladen hat und wie man es baut.

Da U-Boot Text an den UART ausgeben würde und ich nichts sehe, vermute ich, dass wir irgendwo im SPL eine Exception abfangen.

Zu diesem Zeitpunkt habe ich viele, viele Stunden damit verbracht, den dekompilierten Quellcode des Boot-ROMs in Ghidra zu reverse-engineeren und zu bereinigen, mir Structs anzusehen und wie jedes Datenelement über Funktionen hinweg verwendet wurde, manchmal verschachtelt, was mir allerlei Chaos bescherte. Während das auf der Sparflamme kochte, dachte ich, es sei auch an der Zeit, mit dem Debuggen und Kompilieren meines eigenen Codes zu beginnen. Wir sind schließlich im RAM, warum also nicht die SPL-Symbole laden und sehen, was los ist?

SDK-Entwicklung

Debugging

Man kann mit Ozone debuggen, dem Segger-Debugger für J-Link. Man kann auch TIs eigenes Code Composer Studio (CCS) verwenden oder vielleicht auch dessen VSCode-artige „Light“-Version CCS Theia. Ich konnte alles im SDK bauen, indem ich diesem Video folgte: Sitara Linux Board Porting Series: Modul 6. Es gibt drei Komponenten zu bauen:

  • Prozessorkonfiguration
  • U-Boot-Binärdatei
  • U-Boot Secondary Program Loader (SPL)

Ich bin dem Modul-7-Video aus der obigen Serie gefolgt und habe es geschafft, die Dinge zum Laufen zu bringen, mit ein paar Anmerkungen:

  1. s_init() ist nicht mehr vorhanden
  2. Hardware-Breakpoints mit dem J-Link müssen über das J-Link-Bedienfeld gesetzt werden (siehe Tray-Symbol). Ich bin mir nicht sicher, wie man damit Code lädt. Vielleicht über Ozone.

Symbole zu haben? Wunderbar. Jetzt kann ich sehen, was bei der Ausführung passiert, beginnend mit einem Reset-Handler reset(), und ich kann sehen, wo wir mit unserer Exception landen. Um sie aufzuspüren, habe ich einen Breakpoint am Exception-Handler bei 0x402f 0440 gesetzt und das Link-Register geprüft, das noch die Adresse der zuletzt aufgerufenen Funktion gespeichert hatte. Das erwies sich als Adresse 0x402f 76ce, auch wenn es nicht konsistent zu sein scheint. Was verursacht den Fehler?

Hinweis: Zum Debuggen, dem Modul-7-Video folgend, bis 0x402f 0400 ausführen, dann den Load-Memory()-Teil durchführen. Das muss bei jedem Neustart gemacht werden.

Wir gehen in die Funktion device_probe() (0x402f 74c4), dann ein paar andere Funktionen? Dann kommen wir aus do_setup_dpll(), das bei 0x402f 07fc verzweigt, nicht heraus, also gehen wir weiter hinein. Beim Durchschreiten gelangen wir zurück zu _main() in crt0.S, bei 0x402f 14e0. Es scheint, als kämen wir aus board_init_f() und gingen weiter zu spl_relocate_stack_gd(). Dieser Aufruf wird nicht verlassen. Wir erreichen dm_fixup_for_gd_move(). Diese enthält eine Anweisung, die bei 0x402f 76c2 fehlschlägt. Ich denke, das ist es: Sie versucht, auf 0x81ff ff20 zuzugreifen. Offenbar nicht gut. Ich habe eine Vermutung, dass es ein Problem mit der SDRAM-Konfiguration gibt. Ich habe alle Threads in den TI-Foren zu ähnlichen Problemen aufgespürt und ein halbes Dutzend gefunden, die Hinweise enthielten, die mir halfen. Ich kam zu dem Schluss, dass es wahrscheinlich entweder mit (a) EMIF-Abgleich oder (b) Software-Leveling zusammenhing.

DDR3-RAM-Konfiguration

Mein Board ist das unten gezeigte.

Der Speicher stammt von Micron, während der Schaltplan des BeagleBone Black, den ich habe (Rev C3), Kingston-DDR3-Speicher verwendet, genauer gesagt den D2516EC4BXGGB. Der DDR3 ist U12. Wir können die Micron-Markierungs-Dekodierseite verwenden, um das Bauteil zu finden:

  • MT41K256M16TW-107 XIT:P
    • 256 Meg x 16
    • 96-ball 8mm x 14mm FBGA, rev P
    • $t_{CK}$=1.07ns, CL = 13

Um sicherzugehen, dass das Ding lebt, begann ich damit, einfach zu prüfen, ob die Spannungsversorgung anliegt. Das Datenblatt legt fest, dass sie 1.5V +/- 0.075V betragen muss. Ich messe 1.506V über R6 auf der Unterseite des Boards. Wir haben zwei Testpunkte, TP1 und TP2.

Falls es jemals hilfreich ist, hier sind ein paar der Testpunkte.

  • Speicherbaustein

Prüfen wir die Clock-Enable-Leitung. Wir können beide Seiten von R96 prüfen; eine Seite sollte auf Masse liegen, die andere sollte auf High gehalten werden. Bestätigt, 1.5V an CKE.

Der nächste Schritt ist, das Taktsignal zu prüfen. Ich habe hier getan, was ich konnte, mit dem tinySA, an dem die Antenne angeschlossen war und die ungefähr in Richtung des RAM-Chips zeigte. Bei dieser Art von „Schnüffeln“ bin ich ziemlich sicher, dass der Takt vorhanden ist, zumindest genug für jetzt.

Weiter geht's: Es ist Zeit, sich die externe Speicheranbindung anzusehen. Etwas, das häufig vorkommt, ist das Konzept einer GEL-Datei. Dies ist eine interpretierte Sprache, die von Texas Instruments für Code Composer Studio entwickelt wurde; das Akronym steht für General Extension Language.

  • Erstellen von GEL-Dateien zur Geräteinitialisierung

Eine GEL-Datei ist im DDR-Speicherkonfigurationstool enthalten.

Alles klar! Ich bin dem Abgleichverfahren gefolgt (so gut ich konnte) und habe es geschafft, optimale Werte für die GEL-Datei zu finden.```


root@kitploit:~
The Slave Ratio Search Program Values are... 

PARAMETER MAX | MIN | OPTIMUM | RANGE


DATA_PHY_RD_DQS_SLAVE_RATIO 0x071 | 0x005 | 0x03b | 0x06c DATA_PHY_FIFO_WE_SLAVE_RATIO 0x1b3 | 0x046 | 0x0fc | 0x16d DATA_PHY_WR_DQS_SLAVE_RATIO 0x0f7 | 0x01a | 0x088 | 0x0dd DATA_PHY_WR_DATA_SLAVE_RATIO 0x137 | 0x05a | 0x0c8 | 0x0dd


root@kitploit:~
Beim Versuch, RAM-Einstellungen im Memory Browser anzupassen ... Hui! Es funktioniert! 

Der Speicher scheint also definitiv zu funktionieren, aber das SPL schlägt immer noch fehl. Vielleicht gibt es hier etwas bezüglich der Art, wie das SPL versucht, das SDRAM zu initialisieren? Ach ja, natürlich gibt es noch mehr zur Tuning-Prozedur! Du musst das SPL tatsächlich aktualisieren ...

Wir kommen jetzt näher. Die Datei `board.c` initialisiert DDR, indem sie prüft, welchen Board-Typ wir haben. Aber für dieses Board geben alle Funktionen (`board_is_evm_sk()`, `board_is_icev2()`, `board_is_bone_lt()`, usw.) `false` zurück, also wird standardmäßig `config_ddr(266, ...)` verwendet, wobei 266 die Taktfrequenz in MHz ist – und sie *sollte* 400 MHz betragen. Das wäre sicherlich ein Problem.

`board_is_bone_lt` sollte so umgangen werden, dass es immer `true` zurückgibt. Das habe ich getan, und ich komme ein Stück weiter, aber etwas stört mich. Das Laden der neuen Datei MLO auf die SD-Karte funktioniert nicht, obwohl das direkte Laden des Programms einwandfrei funktioniert. Was ist hier los? Ich kann erkennen, dass der Code, der in den SRAM geladen wird, nicht derselbe Code ist, den ich kompiliert habe. Tatsächlich habe ich die SD-Karte sogar formatiert, und trotzdem scheint ein Standard-SPL in den sram geladen zu werden! Ich habe verifiziert, dass der Bootprozess bei Verwendung einer anderen SD-Karte nicht versucht, fortzufahren. Daher sucht der Bootloader definitiv nach der Boot-Partition auf der SD-Karte, dann verschiebt er die Ausführung in den SRAM, aber er hat die Daten noch nicht kopiert? Ugh. Woher kommt das??

Dieses Problem hat mir nicht wenig Ärger bereitet. Ich habe alle Partitionen gelöscht, den MBR überschrieben, die Boot-Partition überschrieben und verschiedene SD-Karten ausprobiert, und nur meine Karte konnte immer noch booten, also musste es noch *irgendwelche* bootfähigen Daten darauf geben, die irgendwo gespeichert waren. Schließlich konnte ich dem Wahnsinn ein Ende bereiten, indem ich die *gesamte* Karte mit Nullen überschrieben habe. 

An diesem Punkt habe ich auch die **Tracing-Vektoren** kennengelernt, auf die du beim Troubleshooting des Boot-ROMs zugreifen kannst. Das würde sich auch als äußerst hilfreich für das Reversing erweisen, da ich wusste, woher alle Tracing-Aufrufe kamen, und dann basierend auf diesen Aufrufen Funktionsnamen usw. zuordnen konnte. Ich habe eine Tabellenkalkulation erstellt, um diese Tracing-Vektoren zu interpretieren, und sie genutzt, um schnell zu verstehen, wie sich der Bootvorgang änderte, als ich die Kartenparameter änderte. Und tatsächlich behauptete es, die CHSETTINGS immer wieder zu finden, während ich versuchte, die SD-Karte zu formatieren und neu zu formatieren, bevor ich verzweifelt das ganze Ding mit Nullen überschrieben habe. 

Um zu versuchen, die Karte so zu lesen, wie es der TI-Prozessor tun würde, kannst du `dd` verwenden. Verwende Blockgröße=512 und gib den ersten Sektor an (versuche mit GParted herauszufinden, welcher es ist), indem du die ersten `n` überspringst. Beispiel: Erster Sektor ist 2048, Gerät ist `sda`, wir lesen nur den ersten Sektor:```
sudo dd if=/dev/sda1 of=/home/sam/sector2048 bs=512 skip=2048 count=1

Ich habe diese verwendet, um Bilder des MBR und des Anfangs der Boot-Partition direkt von der SD-Karte herunterzuladen, die mir beide später noch nützlich sein würden.

Die SD-Karte unter die Lupe nehmen

Meine Reverse-Engineering-Bemühungen in Ghidra hatten mich zu den Boot-Handler-Funktionen der SD-Karte geführt, und ich konnte nun Funktionen sehen, die SD-Befehle an die Karte senden, und ich konnte Schritt für Schritt durchgehen und sehen, was die Karte antwortete. Ich dachte, ich wäre an der richtigen Stelle und die Karte würde alle Nullen zurückgeben. Später erfuhr ich, dass ich möglicherweise durch den eMMC-Handler (denselben Handler, aber mit einer anderen Geräte-ID) gestiegen war oder etwas anderes nicht stimmte, denn mit der SD-Karte war nichts falsch. Dennoch dachte ich, es sei an der Zeit zu lernen, wie diese Karten funktionieren.

Ich war neugierig zu sehen, warum die Karte bei jeder Blockanfrage weiterhin alle Nullen zurückgab. Offensichtlich funktionieren die Kartenfunktionen und die Software kann von ihr lesen, denn das hat sie in der Vergangenheit bereits getan. Aber dennoch war es an der Zeit, alles zu verdrahten und mit einem Logikanalysator einen Blick darauf zu werfen. Ein wenig Mikrolöten, die 30awg-Drähte mit UV-härtendem Epoxid fixieren, und mit meinem Saleae aufklemmen – und schon haben wir etwas, das funktioniert.

Ich habe diesen Analyzer verwendet, um die Daten zu analysieren. Zuerst habe ich es ohne eingelegte Karte versucht.

Bei den ersten paar Befehlen beträgt die Taktfrequenz 120 kHz. Offensichtlich antwortet die Karte nicht (sie ist nicht da).``` CMD0, arg=\0 GO_IDLE_STATE CMD8, arg=\x01\xAA SEND_EXT_CSD CMD55, arg=\0 APP_CMD CMD1, arg=\0 SEND_OP_COND ... CMD0, arg=\0 GO_IDLE_STATE CMD8, arg=\x01\xAA SEND_EXT_CSD CMD55, arg=\0 APP_CMD CMD1, arg=\0 SEND_OP_COND

root@kitploit:~
Wenn die Karte tatsächlich eingelegt ist, springt die Frequenz nach der Konfiguration auf etwa 6 MHz.

![](https://assets.kitploit.com/production/public/readmes/48883/80a4aca50543bd6766805e8491ba504f07a660b61804ec7a673f738dac0b03cc.png)

Nachdem ich einige Abstürze durch die Verwendung des SD-Modus überwunden hatte (Tipp: Selbst für diese SD-Karte solltest du den MMC-Modus verwenden) und ich verifizieren konnte, dass die Karte vernünftige Daten lieferte, legte ich das danach beiseite, weil ich meine Bemühungen verdoppelte, den SD-Karten-Boot-Handler zu verstehen, und erkannte, dass die korrekten Daten *gelesen* wurden – und es dieselben Daten waren, die ich manuell mit `dd` von der Karte geholt hatte! Na ja, es war ein unterhaltsamer Umweg und half mir, Vertrauen zu fassen, dass die Karte funktionierte. 

### Das Problem finden

Die Daten stammen von Adresse `0x4030c928` (eine Stack-Variable, 512-Byte-Array), nachdem der Zweig bei `0x25c2e` für Adresse `0x0000` und Gerät `8` ausgeführt wurde (siehe statische Daten bei `0x4030d00c`). Beim Sprung in die Methode, die ich `MBR_detection` genannt habe, prüft das Programm auf die magischen Bytes `0xaa55`. Zuerst lädt es die zweiten beiden Bytes `0xaa`, dann das erste `0xaa`.```
r0 = data[0x1ff]
r1 = data[0x1fe]
orr r0,r1,r0,lsl #8
sub r1,r0,#0xaa00
subs r1,#0x55
bne <return FAIL>

Dies gelingt. Die nächste Prüfung schlägt jedoch fehl:``` r0 = data[0xc] => 0 r1 = data[0xb] => 0 orr r0,r1,r0, lsl #8 cmp r0,#0x200 bne

root@kitploit:~
Decompiler-Pseudocode:```c
if (
	 data[0x1fe] != 0x55aa || 
	 data[0xb]   != 0x200  ||
	(data[0xd] != 1 &&     // bit 0
	 data[0xd] != 2 &&     // bit 1
	 data[0xd] != 4 &&     // bit 2
	 data[0xd] != 8 &&     // bit 3
	 data[0xd] != 0x10 &&  // bit 4
	 data[0xd] != 0x20 &&  // bit 5
	 data[0xd] != 0x40 &&  // bit 6
	 data[0xd] != 0x80)    // bit 7
	) 
{
	return 1;
}

Dies prüft, ob das Byte bei 0xb = 11 gleich 0x200 ist, und prüft, ob das Byte bei 0xd gleich einem Einzelbit-Wert ist. Beide Bedingungen müssen erfüllt sein, andernfalls wird ein Fehler zurückgegeben.

Nachdem die Erkennungsfunktion eine 1 zurückgibt, versucht der Boot-Handler als Nächstes, es als MBR zu lesen, und lädt den Offset der ersten Partition, um zu prüfen, ob diese eine bootfähige Partition ist. Hier ist das Verfahren:```C // Call block read function // mmc_block_read_something(boot_device *dev,blk_read_struct blk) ret = ((code *)blockread_struct->block_read_func)(blockread_struct->device_ptr,&block_read_info); if (ret != 0) { return 1; } // Check if device doesn't use MBR ret = MBR_check_bootable_partition((partition_struct *)block_data,blockread_struct); if (ret != 0) { // It uses MBR, try each partition in the partition entries for a bootable // partition ret = MBR_check_entries(block_data,blockread_struct); if (ret != 0) { return 1; } ret = MBR_parse_entries(block_data,&blockread_struct->part_entry); if (ret != 0) { return 1; } // Get bootable partition offset block_read_info = (blk_read_struct *)(blockread_struct->part_entry).first_sect_pos; uStack_220 = 1; pbStack_21c = block_data; // Call block read function // mmc_block_read_something(boot_device *dev,blk_read_struct blk) ret = ((code *)blockread_struct->block_read_func) (blockread_struct->device_ptr,&block_read_info); if (ret != 0) { return 1; } // Try and verify bootable partition again ret = MBR_check_bootable_partition((partition_struct *)block_data,blockread_struct); if (ret != 0) { return 1; } }

root@kitploit:~
Nun springe ich zu dem Punkt, an dem die Daten bei `0x800` gelesen werden, und bekomme den korrekten Dump. Aber die MBR-Erkennungsmethode gibt immer noch 1 zurück, selbst mit den richtigen Daten (und es gibt dort ein paar Ebenen der Indirektion, denen ich folgen musste, grrr), also muss dort das Problem liegen.

Jetzt kommt die Zielgerade. Der erste Lesevorgang von der SD-Karte ist der MBR, der bis zu vier Partitionstabelleneinträge enthält, siehe TRM-Tabellen 26-20, 26-21. Der Partitionstabelleneintrag für die Boot-Partition besagt, dass die Partition `0x40000` Sektoren enthält. Aber das Dateisystem der Partition (siehe TRM-Tabelle 26-23) besagt, dass es aus irgendeinem Grund nur `0x3fff8` sind, und das Boot-ROM erkennt das und schlägt fehl.

Als Experiment habe ich den Sprung umgangen, der die Probleme verursacht hat (was schlecht ausgehen könnte ... Daumen drücken ...), und das Programm lief auf jeden Fall weiter, obwohl ich nicht sicher bin, wo ich gelandet bin - es sieht nach Müll aus. Aber abgesehen davon bootet das Gerät tatsächlich! Einen Haltepunkt bei `0x402f0400` (Start des geladenen Images) gesetzt, und alles läuft einwandfrei. Vielleicht ist es Zeit, den UART anzuschließen? UART ist gut!

Was das SD-Karten-Problem angeht? Ich habe eine [Frage auf Unix SE](https://unix.stackexchange.com/questions/781715/why-do-the-mbr-partition-entry-and-partition-filesystem-disagree-on-the-number-o/781755) gestellt, aber das hat beim eigentlichen Problem nicht viel geholfen (obwohl ich dabei trotzdem einige gute Informationen bekommen habe). Von da an: Ich habe es endlich behoben! Beim Durchsehen der `mkfs`-Befehle zum Erstellen des FAT16-Dateisystems ist mir aufgefallen, dass [eine andere Referenz](https://blog.billvanleeuwen.ca/porting-u-boot-onto-the-beaglebone) das Flag -a verwendet, das die Ausrichtung deaktiviert. Das war der Schlüssel. Nach dem Hinzufügen dieses Flags und dem Neuerstellen stimmen die Sektoranzahlen überein (`0x40000`), und das System bootet. Ich schätze, das Boot-ROM unterstützt diese Art der Ausrichtung nicht.

Jetzt bekomme ich die untenstehenden Meldungen in einer Boot-Schleife, mit eingesteckter Karte. Hurra! Ich muss nur noch herausfinden, warum der Kernel nicht startet, und dann sind wir am Ziel! All diese Arbeit könnte sich endlich mit ein paar brauchbaren BeagleBone-Boards auszahlen. War es das wert? Wer kann das beurteilen.```
U-Boot SPL 2021.01-00001-gc59bf25a382-dirty (Jul 24 2024 - 20:38:49 -0400)
Trying to boot from MMC1


U-Boot 2021.01-00001-gc59bf25a382-dirty (Jul 28 2024 - 20:36:46 -0400)

CPU  : AM335X-GP rev 2.1
Model: TI AM335x BeagleBone Black
DRAM:  512 MiB
WDT:   Started with servicing (60s timeout)
NAND:  0 MiB
MMC:   OMAP SD/MMC: 0, OMAP SD/MMC: 1
Loading Environment from FAT... *** Warning - bad CRC, using default environment

<ethaddr> not set. Validating first E-fuse MAC
Net:   eth2: ethernet@4a100000, eth3: usb_ether
Hit any key to stop autoboot:  2 <0x08><0x08><0x08> 1 <0x08><0x08><0x08> 0 
WARNING: Could not determine device tree to use
switch to partitions #0, OK
mmc0 is current device
SD/MMC found on device 0
Failed to load 'boot.scr'
Failed to load 'uEnv.txt'
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
<0x1b>7<0x1b>[r<0x1b>[999;999H<0x1b>[6n<0x1b>8Scanning disk [email protected]...
Scanning disk [email protected]...
** Unrecognized filesystem type **
Found 4 disks
No EFI system partition
BootOrder not defined
EFI boot manager: Cannot load any image
switch to partitions #0, OK
mmc0 is current device
SD/MMC found on device 0
4997632 bytes read in 353 ms (13.5 MiB/s)
Failed to load '/boot/undefined'

Starting kernel ...

Es gibt also ein Problem mit dem Device Tree ("WARNING: Could not determine device tree to use"). Das ist meine erste Begegnung mit dem Kernel und dem Booten des Kernels, daher habe ich keine Ahnung, was das bedeutet.

"Booten des Kernels" umfasst Folgendes, soweit ich es verstehe:

  1. U-Boot lädt und sucht nach einem Kernel-Image (zImage); das Kernel-Image ist ein komprimiertes Kernel-Binary, zImage ist selbstentpackend
  2. Das Kernel-Image wird in den Speicher geladen und dann entpackt, entweder von sich selbst (zImage) oder von U-Boot (uImage)
  3. Der Kernel führt seine üblichen Low-Level-Abläufe aus und startet dann die Init-Programme/Daemons

Ein wichtiger Aspekt des Kernel-Boot-Prozesses ist der Device Tree. Dieser wird in der .dtb-Datei (Device-Tree-Binary; vgl. .dts-Device-Tree-Quelldateien) für das Board gespeichert.

Mein Problem ist nun, dass U-Boot den Device Tree des Boards nicht lädt, da im Log keine Meldung reading /am335x-boneblack.dtb erscheint. Stattdessen erhalten wir WARNING: Could not determine device tree to use. Das ist also ein guter Hinweis! Ich vermute, es liegt an der fehlenden EEPROM-Board-ID.

Einige Details darüber, wie es das Board ermittelt, finden sich in diesem Thread im TI-Forum.

Wie weiß U-Boot also, wie es sich konfigurieren und ordnungsgemäß booten muss? Im U-Boot-Quellcode, den wir gebaut haben, gibt es einen Ordner namens configs/, der defconfig-Dateien für verschiedene Boards speichert. Diese Dateien definieren die verschiedenen Konfigurationsparameter für U-Boot, einschließlich des Boot-Befehls, der wie folgt aussehen könnte:``` if test ${boot_fit} -eq 1; then run update_to_fit; fi; run findfdt; run init_console; run envboot; run finduuid; run distro_bootcmd

root@kitploit:~
Wir definieren, welche Konfiguration verwendet wird, wenn wir das Ziel `make <boardname>_config` ausführen. Die Funktion `findfdt` wird verwendet, um das Board zu identifizieren, auf dem wir arbeiten, und konfiguriert den Device Tree entsprechend. Sie sieht folgendermaßen aus (definiert in `am335x_evm.h`):```
"findfdt="\
		"if test $board_name = A335BONE; then " \
			"setenv fdtfile am335x-bone.dtb; fi; " \
		"if test $board_name = A335BNLT; then " \
			"setenv fdtfile am335x-boneblack.dtb; fi; " \
		"if test $board_name = A335PBGL; then " \
			"setenv fdtfile am335x-pocketbeagle.dtb; fi; " \
		"if test $board_name = BBBW; then " \
			"setenv fdtfile am335x-boneblack-wireless.dtb; fi; " \
		"if test $board_name = BBG1; then " \
			"setenv fdtfile am335x-bonegreen.dtb; fi; " \
		"if test $board_name = BBGW; then " \
			"setenv fdtfile am335x-bonegreen-wireless.dtb; fi; " \
		"if test $board_name = BBBL; then " \
			"setenv fdtfile am335x-boneblue.dtb; fi; " \
		"if test $board_name = BBEN; then " \
			"setenv fdtfile am335x-sancloud-bbe.dtb; fi; " \
		"if test $board_name = A33515BB; then " \
			"setenv fdtfile am335x-evm.dtb; fi; " \
		"if test $board_name = A335X_SK; then " \
			"setenv fdtfile am335x-evmsk.dtb; fi; " \
		"if test $board_name = A335_ICE && test $ice_mii = rmii; then " \
			"setenv fdtfile am335x-icev2.dtb; fi; " \
		"if test $board_name = A335_ICE && test $ice_mii = mii; then " \
			"setenv fdtfile am335x-icev2-prueth.dtb; fi; " \
		"if test $fdtfile = undefined; then " \
			"echo WARNING: Could not determine device tree to use; fi; \0" \

Wenn wir das Standardverhalten wollen, können wir einfach die Variable board_name ändern, oder? Naja, vielleicht nicht, oder zumindest weiß ich nicht, wo der beste Ort ist, um sie zu ändern. Aber obwohl das Setzen von board_name an sich nicht funktionierte, habe ich tatsächlich auch die Standard-.dtb-Datei aktualisiert, und das hat funktioniert!``` _____                    _____           _         _    
|  _  |___ ___ ___ ___   |  _  |___ ___  ||__ | |  
|     |  _| .'| . | . |  |   |  | . | | | -|  _|  _|
|
|
|| |__,|  ||  ||  || ||| ||||   
             |
|                    |___|             

Arago Project http://arago-project.org am335x-evm ttyS0

Arago 2021.09 am335x-evm ttyS0

am335x-evm login: root
root@am335x-evm:~#

root@kitploit:~
Endlich sind wir an einem Terminal. Meine Schrott-Boards leben!

### Beheben der fehlenden EEPROM-ID

Als letzten Schritt schreibe ich die korrekte Board-ID in das EEPROM, was aus dem Linux-Benutzerbereich einfach zu bewerkstelligen ist. Aus dem SPL-Quellcode ergeben sich die verschiedenen Board-IDs:
- `A335BONE` - Beaglebone-Board
- `A335BNLT` - Beaglebone-Black-Board
- `A335PBGL`
- `A335X_SK`
- `A33515BB`
- `A335_ICE`

Außerdem gibt es eine optionale Board-Revision. Laut Schaltplan liegt das EEPROM auf I2C0, und der Chip selbst (auf meinem Schaltplan eine 24LC32A, obwohl sie als '256Kx8 beschriftet ist) liefert die I2C-Geräteadresse `0x50` (binär `b1010` gefolgt von der Chipadresse `000`, da das 5-Pin-Gehäuse keine zusätzlichen Adress-Pins besitzt). Ein letzter wichtiger Punkt: Der WP-Pin wird über einen 10k-Pull-up auf HIGH gezogen, sodass der Schreibschutz standardmäßig aktiviert ist; er muss auf LOW gelegt werden, bevor geschrieben werden kann, andernfalls bestätigt er zwar, schreibt aber einfach nichts.

Auf das EEPROM kann über den Kernel unter `/sys/bus/i2c/devices/0-0050` zugegriffen werden, wo sich eine Datei namens `eeprom` befindet. Daher genügen ein paar `echo`-Aufrufe, wenn der WP-Pin auf LOW gelegt wird (TP4 oben in der Nähe der DC-Buchse mit Masse verbinden). Das Beaglebone Black System Reference Manual enthält das Format. Ich habe dies [von hier](https://groups.google.com/g/beagleboard/c/di5O5JCl4yw) übernommen.```sh
root@am335x-evm:~# cat fix_eeprom.sh 
#!/bin/bash
# Fix board ID EEPROM

EEPROM_FILE=/tmp/eeprom.tmp
EEPROM=/sys/bus/i2c/devices/0-0050/eeprom

# header bytes
echo -ne "\xaa\x55\x33\xee" > ${EEPROM_FILE}
# Board ID
echo -n "A335BNLT" >> ${EEPROM_FILE}
# serial number (I left this basically as the template)
echo -n "000C24wwBBoxxxx" >> ${EEPROM_FILE}

dd if=${EEPROM_FILE} of=${EEPROM}

Mit less zur Bestätigung, und wir sollten ab sofort keine Probleme haben, eine Standard-SD-Karte zu booten. Änderungen an der SPL- und der U-Boot-Konfiguration können zurückgerollt werden, sobald auf allen Boards die EEPROMs beschrieben wurden.

Tool herunterladen
RegionStartadresseLänge
Boot ROM (Public)0x4002_00000xBFFF
Boot ROM (Public, alias)0x0002_00000xBFFF
SRAM Internal0x402F_04000xFC00
L3 OCM00x4030_00000x10000
TestpunktVerbindungSchaltplanblattPlatinenseite
TP1DGND2 (D1)Oben
TP2VDD_MPUON (VDD_MPU_MON)5 (C4)Oben
TP3TESTOUT5 (B2)Oben
TP4Board ID WP11 (B1)Oben
Die Speicherschaltung ist auf der Hardware-Design-Seite ausführlich beschrieben.