ARM Assembler in MSVC++

Mit meinem neuen ARM-PC und dem ebenso neuen Visual Studio 2022 Preview für ARM64 eröffnet sich mir wieder ein neues Kapitel in der Softwareentwicklung:

ARM32 und ARM64 Assembly Language.

Denn nun kann ich beides live debuggen.


Einrichtung in Visual Studio

Wenn man *.asm Dateien einem Visual Studio Projekt hinzufügt und die Build Dependencies - Build Customizations - masm Targets aktiviert hat, ist lediglich X86 und X64 Support (ml.exe) verfügbar.

Für die ARM Entwicklung braucht man aber den ARM Assembler (armasm.exe) und dieser kann über den marmasm Eintrag der Visual C++ Build Customization Files aktiviert werden.

Hat man in seinem MSVC Projekt parallel auch masm aktiviert, dann sollte man für jede Zielplattform die korrekten *.asm Dateien einschließen und ihren Item Type in den Properties korrekt setzen.
Dieser lautet dann nämlich Microsoft ARM Assembler (und nicht wie sonst Microsoft Macro Assembler)

In älteren Varianten von Visual Studio fehlt ein solches Custom-Target. Hier kann man sich entweder das masm Target-File hernehmen und anpassen oder von Anfang an neu erstellen.
Oder man definiert einen generischen Kommandoaufruf. Am Ende muss mindestens ein:
armasm64.exe "path\to\my_code.asm" -o "ARM64\Debug\my_code.obj"
dabei herauskommen.

Noch kein direkter CMake Support

CMake unterstützt den MARMASM leider noch nicht.
Es gibt einige Diskussions-Hinweise im Netz darauf, dass im Modul CMakeDetermineASM_MASMCompiler.cmake ein Patch notwendig wäre, weil dort nur ML64 und ML erkannt werden.

Ich vermute aber, dass Microsoft das in der nächsten Preview, aller-spätestens aber mit dem nächsten Visual Studio Produkt nachliefern wird.

Vorläufig muss ich alle Assembler-Tests manuell einrichten.

ARM Syntax: Ja nicht auf das Leerzeichen vergessen!

Meine ersten Versuche mit kopierten Zeilen aus dem Netz scheiterten alle mit seltsamen Fehlern wie:

error A2034: unknown opcode: .text

wenn die erste Zeile AREA |.text|,CODE,READONLY übersetzt wird.

Eine ARM Assembly Language Zeile ist immer nach dem gleichen Schema aufgebaut, nämlich:
{symbol} {instruction|directive|pseudo-instruction} {;comment}
Und damit muss jede Zeile entweder mit einer Zeilenmarke anfangen (Funktionsnamen oder Sprung-Label), oder eben mit einem Leerzeichen oder TAB.

 1;demo.asm: Wrong! No space before directives/instructions
 2AREA |.text|,CODE,READONLY
 3
 4EXPORT my_add_func
 5my_add_func PROC
 6  add x0, x0, x1
 7  ret
 8ENDP
 9
10END

Direktiven wie AREA, EXPORT oder ENDP und alle CPU Anweisungen müssen also immer eingerückt werden:

 1  ;demo.asm: Correct! With space before directives/instructions
 2  AREA |.text|,CODE,READONLY  ; location + type of generated code
 3
 4  EXPORT my_add_func ;declare "my_add_func" as public function
 5my_add_func PROC  ;"my_add_func" entry-point symbol
 6  add x0, x0, x1  ; ret = param_1 + param2
 7  ret             ;ARM return instruction
 8  ENDP            ;end of procedure "my_add_func"
 9
10  END            ;end of assembly file

Kaum hat man es richtig gemacht, lässt sich die Assemblerzeile mit dem ARM64 Assembler übersetzen und aus C++ mit dem Prototyp

1#include <stdint.h>
2extern "C" intptr_t my_add_func(intptr_t a, intptr_t b);

aufrufen.

Fazit

Nun kann ich also meine Stack-Switching-Routinen auch auf ARM Plattformen portieren und das wiederrum eröffnet ein weiteres spannendes Feld:

EFI-Apps auf ARM Hardware

Doch bis ich mich ausreichend gut mit ARM Assembler Codes auskenne, wird wohl noch der eine oder andere Test notwendig sein.
Schließlich habe ich bisher immer nur mit X86 gearbeitet und kenne die weit gestreuten Möglichkeiten der ARM Architektur noch nicht.

Zumindest freue ich mich, dass ich jetzt wenigstens alle Windows-Plattformen mit Assemblercode beglücken kann.
Doch auch das wird nicht das Ende sein … denn mein Raspberry PI verdient eine mindestens genau so gute Linux Abdeckung.