Assembler Module mit CMake

So wie es in Star Trek heißt

Space, the final frontier.

so heißt es dann auch gerne in der Software-Entwicklung:

Assembler, the final frontier.

Bleibt nur die Frage offen, wie man diese letzte Grenze dann auch in CMake überwindet.


Das GATE Projekt soll eigentlich frei von Hardware-Spezialitäten sein, lediglich die “notwendigen” OS APIs der wichtigsten Hersteller sollen gesammelt und abstrahiert werden.
Assembler Codes haben da keinen Platz und sind auch in anderen Libs wie OpenSSL / libreSSL usw. bewusst abgeschaltet.

Eine kleine Ausnahme gibt es, wenn es um die CPU-ID geht, die man mit Inline-Assembler auslesen kann … aber schon da gibt es das typische MSVC X64 Problem, dass dort nämlich kein Inline-Assembler unterstützt wird.

Mit der EFI Plattform gibt es nun aber einen Sonderfall, wo es um den Nachbau von C-APIs geht, namentlich setjmp() und longjmp().

Diese Funktionen lassen sich nicht mit reinem C nachbauen. Doch diese sind in der gnuefi Bibliothek als .s Assembler Dateien beigelegt.

Blöd ist nur, dass ich primär unter Windows mit dem MSVC arbeite, wo mir erstens die .s Dateien nichts bringen und zweitens CMake diese Dateien nicht automatisch mitbaut.

GNU Assembler Support für CMake

Im Netz findet man schnell die Info, dass man ASM als Sprache im CMake Projekt angeben soll, also z.B.
project(my_project C ASM)

Dann werden noch *.s Dateien hinzugefügt und man erhält:

 1 project(my_project C ASM)
 2
 3 file(GLOB MY_ASM_SOURCES
 4   "path/to/*.s"
 5 )
 6 file(GLOB MY_C_SOURCES
 7   "path/to/*.c"
 8 )
 9 
10 add_library(my_project
11   ${MY_ASM_SOURCES}
12   ${MY_C_SOURCES}
13 )

CMake generiert dann Make-Files, die die .s Assembler Dateien kompilieren und zur Zielbibliothek (oder Executable) hinzufügen.

MSVC Assembler Support für CMake

In Visual Studio kann man manuell den MASM Support aktivieren, in dem man das Projekt auswählt, und dann unter Project - Build Customizations klickt und dort die masm Zeile anhakt. Ab dann werden alle .asm Dateien an den Assembler Compiler weitergereicht.

Für CMake gibt es beim MSVC einen eigenen MASM Token, der da lautet:
ASM_MASM.

Somit muss man nur noch die unter DOS und Windows üblichen .asm Dateien in CMakeLists.txt aufnehmen:

 1 project(my_project C ASM_MASM)
 2
 3 file(GLOB MY_ASM_SOURCES
 4   "path/to/*.asm"
 5 )
 6 file(GLOB MY_C_SOURCES
 7   "path/to/*.c"
 8 )
 9 
10 add_library(my_project
11   ${MY_ASM_SOURCES}
12   ${MY_C_SOURCES}
13 )

MASM Funktionen für C schreiben

Nun beginnt für mich der harte Kampf die AT&T Assemblercodes nach MASM zu portieren. Denn da der AT&T Syntax bei GNU und GCC vorherrscht und auch die setjmp() und longjmp() Codes der gnu-efi Bibliothek so geschrieben sind, müssen fast alle Codezeilen “umgedreht” werden, damit sie MASM-tauglich werden.

Warum MASM ein Operator Ziel, Quell Schema verfolgt und AT&T umgekehrt Operator Quelle, Ziel nutzt und es da keinen Standard gibt, stört mich damals wie heute.

So sehen jedenfalls meine vorläufigen Bemühungen aus:

 1.CODE
 2
 3PUBLIC _setjmp
 4
 5_setjmp PROC
 6  ; RCX hold pointer to jmp_buf 
 7  pop rdx   ; RET address
 8  push rdx
 9  mov rax, 0
10  mov [rcx + 00h], rsp  ; Frame
11  mov [rcx + 08h], rbx                
12  mov [rcx + 10h], rsp
13  mov [rcx + 18h], rbp
14  mov [rcx + 20h], rsi
15  mov [rcx + 28h], rdi
16  mov [rcx + 30h], r12
17  mov [rcx + 38h], r13
18  mov [rcx + 40h], r14
19  mov [rcx + 48h], r15
20  mov [rcx + 50h], rdx ; RIP
21  ; currently, skip following fields in jmp_buf
22  ret
23_setjmp ENDP
24
25PUBLIC longjmp
26
27longjmp PROC
28  mov rbx, [rcx + 08h]
29  mov rsp, [rcx + 10h]
30  mov rbp, [rcx + 18h]
31  mov rsi, [rcx + 20h]
32  mov rdi, [rcx + 28h]
33  mov r12, [rcx + 30h]
34  mov r13, [rcx + 38h]
35  mov r14, [rcx + 40h]
36  mov r15, [rcx + 48h]
37
38  mov r8, [rcx + 50h] ; RIP
39
40  mov eax, edx
41  and rax, 0ffffffffh
42  cmp eax, 0
43  jne longjmp_exit
44  mov eax, 1
45longjmp_exit:
46  jmp r8
47longjmp ENDP

Fazit

Der erste Schritt ist also getan. Die in Assembler implementierten setjmp() und longjmp() Symbole erlauben mir nun weitere Bibliotheken wie LUA oder libjpeg auch für native EFI-Apps zu nutzen.

Das Potential ist aber weit größer.
Denn mit Assembler-Fragmenten kann ich nun auch zu anderen Stacks springen und z.B. Koroutinen ohne OS-Support implementieren.

Aber … bis das lauffähig sein wird, vergeht vermutlich noch einige Zeit.