Minimale GUI in x86-64-Assembler mit X11: Optimierung für fasmg
Entwickler von Low-Level-Code stehen oft vor der Herausforderung, die Größe der ausführbaren Datei zu minimieren, ohne die Funktionalität einzuschränken. Dieser Artikel zeigt, wie man eine minimale GUI in x86-64-Assembler mit dem X11-Protokoll erstellt. Wir nutzen fasmg, um eine kompakte Binärdatei ohne Linker zu generieren, mmap für dynamische Speicherzuweisung und Makros zur Vereinfachung von Systemaufrufen.
Das Ziel ist ein GUI-Fenster mit Text, das kleiner ist als ursprüngliche Implementierungen. Der Fokus liegt auf der Lesbarkeit des Codes für mittlere bis erfahrene Entwickler: X11-Anforderungsstrukturen, ABI-Kompatibilität und Optimierung der Datenkopie.
Einrichtung der Entwicklungsumgebung
Erstellen Sie in VSCode mit Tasks für Release- und Debug-Modi. Release: DEBUG=0, Debug: DEBUG=1 mit DWARF-Symbolgenerierung.
{
"label": "Release-Executable erstellen",
"type": "shell",
"command": "chmod",
"args": ["+x", "${fileBasenameNoExtension}"],
"dependsOn": "Release-Build mit fasmg"
}
Debugging in Cutter mit launch.json:
{
"name": "Cutter starten",
"type": "node",
"request": "launch",
"preLaunchTask": "Debug-Build mit fasmg",
"runtimeExecutable": "Cutter.AppImage",
"runtimeArgs": [
"${fileDirname}/${fileBasenameNoExtension}",
"--analysis", "2",
"--arch", "x86",
"--bits", "64"
]
}
Drücken Sie Strg+Umschalt+T für Debug-Builds (.asm-Dateien).
Makros für Debugging und Protokollierung
Dynamisches Verknüpfen von libc nur im Debug-Modus:
include 'dynamic/import64.inc'
if DEBUG
interpreter '/lib64/ld-linux-x86-64.so.2'
needed 'libc.so.6'
import printf
end if
Makros für vararg printf mit Unterstützung für %d, %f, %e, %g, %a. COUNT_ARGS_TO zählt Argumente, GET_VARARG_TO ruft sie nach Index ab.
Konvertieren von Float/Double zu IEEE-754 über fasm virtuellen Speicher:
macro MOVQ_IMM reg, val
local bits
virtual at 0
dq val
load bits qword from 0
end virtual
mov rax, bits
movq reg, rax
end macro
Das Haupt-VARG_FUNC-Makro analysiert den Formatstring und verteilt Argumente auf Register gemäß System V ABI (xmm0-xmm7 für Float, rsi/rdx/... für Integer). DEBUG_MSG ist ein Wrapper für Protokolle.
X11-Anforderungsstrukturen
Eine vereinheitlichte FULL_LAYOUT-Struktur enthält alle notwendigen Anforderungen:
struct FULL_LAYOUT
ridBase dd 0
ridMask dd 0
windowRootId dd 0
rootVisualId dd 0
gcId dd 0
exposed db 0
create_window_req X11_CREATE_WINDOW
draw_string_req STRING_TO_DRAW
handshake X11_HANDSHAKE
gc_request X11_CREATE_GC
addr_un SOCKADDR_UN
map_window_req X11_MAP_WINDOW
union
free rb HEAP_SIZE - ($ - create_window_req)
setup X11_SETUP
pollfd POLLFD
endu
ends
Felder sind geordnet, um Kopiercode zu minimieren. Layout in r12, Sockets in r13+.
Universelles Systemaufruf-Makro
MAKE_SYSCALL automatisiert das Setzen von Registern (rdi, rsi, rdx, r10, r8, r9) mit Unterstützung für ptr [c], [c], 0:
macro MAKE_SYSCALL scall, args&
iterate arg, args
if % = 1
match =ptr [c], arg
lea rdi, [c]
else match [c], arg
mov rdi, [c]
else match =0, arg
xor rdi, rdi
else
mov rdi, arg
end match
; ähnlich für rsi, rdx, r10...
end iterate
; syscall scall
end macro
Größen- und Leistungsoptimierungen
- mmap statt statischer Daten: 4KB-Seite für alle X11-Strukturen.
- movsb zum Kopieren: Gruppieren von Feldern minimiert Aufrufe.
- Register für Konstanten: r12-r15 bleiben unverändert.
- Standardwerte in Binärdatei: Makros generieren nur genutzte Felder.
- Feste Schriftart: X11 wählt automatisch "fixed".
Vorteile von fasmg: kompakter Code, Makros, kein Linker.
Wichtige Erkenntnisse
- X11-Anforderungsstrukturen sind für optimale movsb-Kopie geordnet.
- Vararg printf-Makro mit IEEE-754-Konvertierung über virtuellen Speicher.
- Universelles Syscall-Makro reduziert Boilerplate um 70 %.
- mmap 4KB deckt alle Strukturen ohne statische Daten in der Binärdatei ab.
- Debug/Release-Builds in VSCode + Cutter für Reverse Engineering.
— Editorial Team
Noch keine Kommentare.