x86-64 어셈블리로 X11을 활용한 최소 GUI 구현: fasmg 최적화
저수준 코드를 작성하는 개발자들은 종종 실행 파일 크기를 최소화하면서도 기능을 유지하는 과제에 직면합니다. 이 글에서는 X11 프로토콜을 사용하여 x86-64 어셈블리로 최소 GUI를 만드는 방법을 살펴봅니다. 링커 없이 fasmg를 사용하여 컴팩트한 바이너리를 생성하고, 동적 메모리 할당을 위해 mmap을 활용하며, 시스템 호출을 단순화하기 위해 매크로를 사용합니다.
목표는 텍스트가 포함된 GUI 창을 원래 구현보다 더 작게 만드는 것입니다. 중급 이상의 개발자를 위한 코드 가독성에 초점을 맞춥니다: X11 요청 구조, ABI 호환성, 데이터 복사 최적화.
개발 환경 설정
VSCode에서 릴리스 및 디버그 모드 작업으로 빌드합니다. 릴리스: DEBUG=0, 디버그: DEBUG=1 및 DWARF 심볼 생성.
{
"label": "릴리스 실행 파일 생성",
"type": "shell",
"command": "chmod",
"args": ["+x", "${fileBasenameNoExtension}"],
"dependsOn": "fasmg로 릴리스 빌드"
}
Cutter에서 디버깅을 위한 launch.json:
{
"name": "Cutter 실행",
"type": "node",
"request": "launch",
"preLaunchTask": "fasmg로 디버그 빌드",
"runtimeExecutable": "Cutter.AppImage",
"runtimeArgs": [
"${fileDirname}/${fileBasenameNoExtension}",
"--analysis", "2",
"--arch", "x86",
"--bits", "64"
]
}
디버그 빌드(.asm 파일)는 Ctrl+Shift+T를 누르세요.
디버깅 및 로깅을 위한 매크로
디버그 모드에서만 libc를 동적으로 링크:
include 'dynamic/import64.inc'
if DEBUG
interpreter '/lib64/ld-linux-x86-64.so.2'
needed 'libc.so.6'
import printf
end if
%d, %f, %e, %g, %a를 지원하는 가변 인수 printf 매크로. COUNT_ARGS_TO는 인수를 세고, GET_VARARG_TO는 인덱스로 인수를 검색합니다.
fasm 가상 메모리를 통해 float/double을 IEEE-754로 변환:
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
주요 VARG_FUNC 매크로는 형식 문자열을 파싱하고, System V ABI(xmm0-xmm7는 부동소수점, rsi/rdx/...는 정수)에 따라 레지스터에 인수를 분배합니다. DEBUG_MSG는 로그를 위한 래퍼입니다.
X11 요청 구조
통합된 FULL_LAYOUT 구조는 모든 필요한 요청을 포함합니다:
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
필드는 복사 코드를 최소화하도록 정렬됩니다. 레이아웃은 r12, 소켓은 r13+에 위치합니다.
범용 시스템 호출 매크로
MAKE_SYSCALL은 ptr [c], [c], 0 지원과 함께 레지스터(rdi, rsi, rdx, r10, r8, r9) 설정을 자동화합니다:
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
; rsi, rdx, r10...에 대해 유사하게 처리
end iterate
; syscall scall
end macro
크기 및 성능 최적화
- 정적 데이터 대신 mmap 사용: 모든 X11 구조를 위한 4KB 페이지.
- 복사를 위한 movsb: 필드 그룹화로 호출 최소화.
- 상수를 위한 레지스터: r12-r15는 변경되지 않음.
- 바이너리의 기본값: 매크로는 사용된 필드만 생성.
- 고정 폰트: X11이 자동으로 "fixed" 선택.
fasmg의 장점: 컴팩트한 코드, 매크로, 링커 불필요.
핵심 요약
- X11 요청 구조는 최적의 movsb 복사를 위해 정렬됩니다.
- 가상 메모리를 통한 IEEE-754 변환을 포함한 가변 인수 printf 매크로.
- 범용 시스템 호출 매크로로 상용구 코드 70% 감소.
- mmap 4KB로 바이너리의 정적 데이터 없이 모든 구조 처리.
- VSCode + Cutter에서 디버그/릴리스 빌드로 리버스 엔지니어링.
— Editorial Team
아직 댓글이 없습니다.