Minimalny GUI w asemblerze x86-64 z X11: optymalizacja dla fasmg
Programiści piszący kod niskiego poziomu często mierzą się z zadaniem minimalizacji rozmiaru plików wykonywalnych przy zachowaniu funkcjonalności. W tym artykule omawiamy tworzenie minimalnego GUI w asemblerze x86-64 z wykorzystaniem protokołu X11. Używamy fasmg do generowania kompaktowego pliku binarnego bez linkera, mmap do dynamicznej alokacji pamięci oraz makr do uproszczenia wywołań systemowych.
Celem jest okno GUI z tekstem o rozmiarze mniejszym niż w oryginalnych implementacjach. Skupiamy się na czytelności kodu dla programistów średniego/zaawansowanego poziomu: struktury żądań X11, zgodność ABI, optymalizacja kopiowania danych.
Przygotowanie środowiska programistycznego
Kompilacja w VSCode z zadaniami dla trybów release i debug. Release: DEBUG=0, debug: DEBUG=1 z generowaniem symboli DWARF.
{
"label": "Utwórz plik wykonywalny release",
"type": "shell",
"command": "chmod",
"args": ["+x", "${fileBasenameNoExtension}"],
"dependsOn": "Kompilacja release z fasmg"
}
Debugowanie w Cutter z launch.json:
{
"name": "Uruchom Cutter",
"type": "node",
"request": "launch",
"preLaunchTask": "Kompilacja debug z fasmg",
"runtimeExecutable": "Cutter.AppImage",
"runtimeArgs": [
"${fileDirname}/${fileBasenameNoExtension}",
"--analysis", "2",
"--arch", "x86",
"--bits", "64"
]
}
Skrót klawiszowy Ctrl+Shift+T dla kompilacji debug (pliki .asm).
Makra do debugowania i logowania
Dołączamy libc dynamicznie tylko w trybie debug:
include 'dynamic/import64.inc'
if DEBUG
interpreter '/lib64/ld-linux-x86-64.so.2'
needed 'libc.so.6'
import printf
end if
Makra dla vararg printf z obsługą %d, %f, %e, %g, %a. COUNT_ARGS_TO zlicza argumenty, GET_VARARG_TO pobiera według indeksu.
Konwersja float/double do IEEE-754 przez wirtualną pamięć fasm:
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
Główne makro VARG_FUNC parsuje ciąg formatujący, rozdziela argumenty do rejestrów zgodnie z System V ABI (xmm0-xmm7 dla float, rsi/rdx/... dla integer). DEBUG_MSG — nakładka na logi.
Struktury żądań X11
Jednolita struktura FULL_LAYOUT zawiera wszystkie potrzebne żądania:
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
Pola uporządkowane dla minimalizacji kodu kopiowania. Układ w r12, gniazda w r13+.
Uniwersalne makro wywołań systemowych
MAKE_SYSCALL automatyzuje ustawianie rejestrów (rdi, rsi, rdx, r10, r8, r9) z obsługą 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
; analogicznie dla rsi, rdx, r10...
end iterate
; syscall scall
end macro
Optymalizacje rozmiaru i wydajności
- mmap zamiast danych statycznych: strona 4KB dla wszystkich struktur X11.
- movsb do kopiowania: grupowanie pól minimalizuje wywołania.
- Rejestry dla stałych: r12-r15 niezmienne.
- Wartości domyślne w pliku binarnym: makra generują tylko używane pola.
- Stała czcionka: X11 automatycznie wybiera "fixed".
Zalety fasmg: kompaktowy kod, makra, brak linkera.
Co jest ważne
- Struktury żądań X11 uporządkowane dla optymalnego kopiowania movsb.
- Makro vararg printf z konwersją IEEE-754 przez wirtualną pamięć.
- Uniwersalne makro syscall redukuje boilerplate o 70%.
- mmap 4KB obejmuje wszystkie struktury bez danych statycznych w pliku binarnym.
- Kompilacje debug/release w VSCode + Cutter do inżynierii wstecznej.
— Editorial Team
Brak komentarzy.