Wave는 인라인 어셈블리를 지원하는 언어입니다. 현재 pre-beta 버전에서는 LLVM을 통해 컴파일이 가능하죠.
그렇다면, Wave로 부트 섹터를 만들고 QEMU로 실행하는 것도 가능하지 않을까요?
만약 이게 성공한다면, 아마도 Wave의 역사에 한 줄을 쓰게 될지도 모릅니다.
오늘은 바로 그 시도, Wave만으로 부트 섹터를 만들어보는 실험을 해보겠습니다.
현재 Wave는 LLVM을 임시로 사용하고 있습니다.
LLVM은 C/C++, Rust, Zig 등 다양한 언어들이 기반으로 삼고 있는 범용 컴파일 툴체인이죠.
하지만 Wave는 향후 LLVM을 벗어나, Whale이라는 독자적인 컴파일러 툴체인을 개발할 계획입니다.
Whale은 LLVM보다 Wave에 더 최적화된 구조를 가질 예정이며, 진정한 의미의 Wave 전용 컴파일러가 될 것입니다.
물론 그 전에 Wave의 프론트엔드를 완성해야 하겠죠.
이전 글에서는 Wave만으로 Hello World를 출력해봤습니다.
하지만 이번엔 한 단계 더 나아가, OS 위에서 실행되는 프로그램이 아닌 OS 밑에서 실행되는 부트 섹터를 작성해보겠습니다.
일반적으로 부트 섹터나 부트로더는 어셈블리로 작성해야 합니다.
하지만 Wave는 asm {} 구문을 통해 인라인 어셈블리를 지원하므로, 이를 통해 부트 섹터를 작성할 수 있습니다.
Wave의 인라인 어셈블리 문법은 제가 이전에 쓴 글
Wave로 아무것도 없이 Hello World 출력하기
에서도 간단히 소개한 바 있습니다.
다음은 실제로 사용할 Wave 코드입니다:
fun main() {
asm {
"mov ah, 0x0e"
"mov al, 0x48"
"int 0x10"
}
}
다음으로 넘어가기 전에 문법을 좀 살펴보도록 하겠습니다.
mov ah, 0x0eint 0x10은 비디오 서비스이며, AH = 0x0E일 경우 "TTY 문자 출력" 기능을 사용합니다.mov al, 0x48'H')를 저장합니다.int 0x100x0E → 문자 출력 모드'H' 한 글자를 출력하게 됩니다.이제 코드는 다 작성했으니 컴파일을 해볼 차례입니다.
Wave는 현재 리눅스 실행 파일로만 컴파일되기 때문에 .img나 .exe 포맷은 지원하지 않습니다.
하지만 wavec run main.wave 명령으로 생성되는 중간 결과인 temp.ll 파일(LLVM IR)을 활용해 부트 섹터 이미지를 만들 수 있습니다.
이 과정을 쉽게 하기 위해, build.sh라는 쉘 스크립트를 작성했습니다:
#!/bin/bash
set -e
LL_FILE=target/temp.ll
OBJ_FILE=boot.o
BIN_FILE=boot.bin
IMG_FILE=os.img
wavec run main.wave
llc -march=x86 -mattr=+16bit-mode -filetype=obj $LL_FILE -o $OBJ_FILE
ld -m elf_i386 -Ttext 0x7c00 --oformat binary $OBJ_FILE -o $BIN_FILE
echo -ne '\x55\xAA' | dd of=$BIN_FILE bs=1 seek=510 count=2 conv=notrunc
dd if=$BIN_FILE of=$IMG_FILE bs=512 count=1 conv=notrunc
echo "[+] 이미지 생성 완료: $IMG_FILE"
참고: LLVM 툴체인이 필요하며,
clang 14버전을 사용하는 걸 추천합니다.
터미널에서 ./build.sh를 실행하면 아래와 같은 결과가 나옵니다:

os.img 파일이 생성된 걸 확인할 수 있습니다.
이제 QEMU로 실행해볼까요?
qemu-system-i386 -drive format=raw,file=os.img
실행하면 이렇게 'H'가 출력되는 모습을 확인할 수 있습니다:

Wave는 아직 개발 중인 언어이지만, 이렇게 인라인 어셈블리를 활용하면 부트 섹터까지도 작성이 가능합니다.
앞으로 더 많은 기능과 문법이 추가된다면, 더 복잡한 OS 구성도 가능해질지 모릅니다.