Assembler für Windows on ARM

ARM64 (AArch64) und ARM32 (Thumb-2) schreiben, bauen und debuggen – ohne Visual Studio.

Mit .arch arm64 erzeugt Asmonite eine Windows-Datei für ARM64 (AArch64, PE32+), mit .arch thumb eine für 32-Bit-ARM (ARM32, Thumb-2, PE32). Der Assembler schreibt DOS-Kopf, PE-Kopf, Abschnitte und die Import-Tabelle selbst; ein Programm ist eine einzige Textdatei. Zum Prüfen liest Dismonite das Ergebnis zurück, und der Debugger läuft auf einem echten Windows-on-ARM-Rechner.

Die Beispiele sind im Ordner examples\windows_arm des ZIPs. Zum Bauen läuft Asmonite auf einem gewöhnlichen Windows-Rechner; ausgeführt wird das Ergebnis auf einem Windows-on-ARM-Rechner.

ARM64: Hello World

Ein Programm für Windows on ARM64: printf aus der C-Laufzeit schreibt eine Zeile, exit beendet. Datei: examples\windows_arm\hello_arm64.asm.

Du schreibst
.format pe console
.arch arm64
.import msvcrt.dll printf, exit

.code
start:
  stp x29, x30, [sp, #-16]!
  adrp x0, msg
  add x0, x0, :lo12:msg   ; first argument in x0
  adrp x16, printf
  ldr x16, [x16, :lo12:printf]
  blr x16
  mov x0, #0
  adrp x16, exit
  ldr x16, [x16, :lo12:exit]
  blr x16

.data
msg db 'Hello, World!', 10, 0
Dismonite zeigt (aus der fertigen .exe)
start:
  stp x29, x30, [sp, #-0x10]!
  adrp x0, section_data
  add x0, x0, #0
  adrp x16, import_table
  ldr x16, [x16, #0x40]
  blr x16
  mov x0, #0
  adrp x16, import_table
  ldr x16, [x16, #0x48]
  blr x16

Das Programm ist nicht an eine feste Adresse gebunden: Windows legt ein ARM64-Programm an eine beliebige Stelle. Deshalb gibt es keine absoluten Adressen. adrp lädt die Seite (4 KB) eines Labels relativ zum pc, :lo12: ist der Rest innerhalb der Seite. Dasselbe Paar holt die Adresse einer importierten Funktion aus der Import-Tabelle (ldr x16, [x16, :lo12:printf]) und blr x16 ruft sie auf. Das erste Argument steht in x0, die weiteren in x1 bis x7; das Ergebnis kommt in x0 zurück. Rechts siehst du, wie Dismonite dieselbe Datei wieder liest: die Namen der Imports sind zu Positionen in import_table geworden, und Dismonite kann daraus wieder eine .asm machen, die dieselben Bytes ergibt.

ARM32: Thumb-2

Windows on ARM führt 32-Bit-Programme als Thumb-2 aus (PE32, Maschine ARMNT). Dasselbe Programm in 32 Bit. Datei: examples\windows_arm\hello_arm32.asm.

Du schreibst
.format pe console
.arch thumb
.import msvcrt.dll printf, exit

.code
start:
  push {r4, lr}
  adr r0, msg             ; first argument in r0
  ldr r3, printf          ; the address of printf
  blx r3
  movs r0, #0
  ldr r3, exit
  blx r3

.data
msg db 'Hello, World!', 10, 0
Dismonite zeigt (aus der fertigen .exe)
start:
  push {r4, lr}
  adr r0, msg
  ldr.w r3, [pc, #0x50]
  blx r3
  movs r0, #0
  ldr.w r3, [pc, #0x4c]
  blx r3

Hier gibt es keine Seitenpaare: adr und ldr mit einem Label rechnen vom pc aus und reichen nur 4 KB weit. Deshalb liegen Code, Daten und Imports in einem Abschnitt, und der Assembler legt die Import-Adresse so, dass sie in Reichweite ist. Die Argumente stehen in r0 bis r3, der Rest auf dem Stack. Der Zusatz .w im Listing von Dismonite kennzeichnet die lange Thumb-2-Form eines Befehls (4 Byte).

Ein Fenster: MessageBox

Mit .format pe gui entsteht ein Fensterprogramm. Dieses öffnet ein Meldungsfenster mit MessageBoxA (examples\windows_arm\messagebox_arm64.asm).

.format pe gui
.arch arm64
.import user32.dll MessageBoxA
.import kernel32.dll ExitProcess

.code
start:
  stp x29, x30, [sp, #-16]!
  mov x0, #0              ; no owner window
  adrp x1, text
  add x1, x1, :lo12:text
  adrp x2, caption
  add x2, x2, :lo12:caption
  mov w3, #0              ; type: MB_OK
  adrp x16, MessageBoxA
  ldr x16, [x16, :lo12:MessageBoxA]
  blr x16
  mov x0, #0
  adrp x16, ExitProcess
  ldr x16, [x16, :lo12:ExitProcess]
  blr x16

.data
text db 'Hello from a message box!', 0
caption db 'messagebox_arm64', 0

Die vier Argumente von MessageBoxA stehen in x0 bis x3 (Besitzer, Text, Titel, Art). Das Fenster öffnet sich auf dem Desktop, auf dem das Programm läuft: unter dem Debugger auf demselben Rechner, bei einem entfernten Rechner mit Debug-Server auf diesem.

ARM64 und Thumb-2 im Vergleich

ARM64ARM32 (Thumb-2)
Direktive.arch arm64.arch thumb
DateiartPE32+, Maschine ARM64PE32, Maschine ARMNT
Argumentex0 bis x7r0 bis r3, dann Stack
Adresse eines Labelsadrp + add :lo12: (4-KB-Seiten, beliebig weit)adr / ldr vom pc (4 KB Reichweite)
Import aufrufenadrp + ldr + blrldr r3, name + blx r3
Befehlslängeimmer 4 Byte2 oder 4 Byte
Debug-Serverasmodbg64asmodbg

Welche ARM-Dateien es gibt

Im PE-Kopf jeder Windows-Datei steht, für welchen Prozessor sie ist. Für ARM sind es mehr als zwei Arten; so ordnen Asmonite und Dismonite sie ein.

KennungArtLäuft aufAsmoniteDismonite
0xAA64ARM64 (AArch64), PE32+Windows 10 und 11 auf ARM64assemblieren, debuggen (.arch arm64)zerlegen, debuggen
0x01C4ARM32 mit Thumb-2 (ARMNT), PE32Windows RT (8, 8.1) und Windows 10 auf ARM; Windows 11 auf ARM64 führt keine 32-Bit-ARM-Programme mehr ausassemblieren, debuggen (.arch thumb)zerlegen, debuggen
0x01C0, 0x01C2das alte ARM (ARM-Modus und Thumb von vor Thumb-2)Windows CE, Pocket PC, Windows Mobile, Embedded Compact; auf keinem Windows-PCnein: kein Assembler dafürnur 0x01C0: zerlegt (der Export schreibt den Code als Bytes mit dem Befehl als Kommentar), kein Debuggen
0xA641, 0xA64EARM64EC und ARM64X (Windows 11)Windows 11 auf ARM64neinnein

Zum Vergleich: Auf einem Windows-10-Rechner mit ARM64 (rund 47.000 PE-Dateien) kamen ARM64, ARM32 mit Thumb-2, x86 und x64 vor. Die x86-Dateien enthalten zum Teil hybriden Code (ein x86-Kopf, aber ARM64-Befehle, den Windows für die Emulation mitliefert) und reine Sprachdateien (.mui). Das alte ARM fand sich dort nur in selbst mitgebrachten Dateien.

Bauen und debuggen

  1. Schreiben und bauen. Eine Datei aus examples\windows_arm in Asmonite öffnen und F9 drücken, oder in der Eingabeaufforderung asmoc hello_arm64.asm. Das Ergebnis ist eine .exe, die jeder Windows-on-ARM-Rechner ausführt.
  2. Debug-Server auf den ARM-Rechner. asmodbg (32 Bit) und asmodbg64 (ARM64) liegen im ZIP im Ordner debugserver. Sie laufen dort, wo das Programm laufen soll. Verbunden wird über ssh oder direkt im Netz mit einem Kopplungscode; auf einem Windows-on-ARM-Rechner selbst geht es auch lokal.
  3. Debuggen. Unter Debug > Debug target das Ziel Windows on ARM wählen. Haltepunkte, einzelne Schritte, Register, Stack und Speicher zeigt Asmonite an den Zeilen des Quelltextes. Register lassen sich bei angehaltenem Programm ändern.
  4. Fremde Programme untersuchen. Dismonite öffnet jede ARM- oder ARM64-.exe, zeigt den Code mit Labels und Kommentaren und exportiert eine .asm, die Asmonite wieder in dieselbe Datei übersetzt.

Die Befehlsinfo (F1) kennt die ARM- und ARM64-Befehle: Syntax, erlaubte Operanden, Flags und ein Beispiel.

Weiter

Alle Beispiele

Die kleinen Programme für Windows, DOS und ARM mit Ergebnis.

Die Beispiele-Seite →

Dismonite

Disassembler und Debugger für x86, x64, ARM, ARM64 und Thumb-2.

dismonite.com ↗

Herunterladen

Ein ZIP mit allem; entpacken, starten, fertig.

Zum Download →