UEFI로 시작하는 베어메탈 프로그래밍 첫걸음

First steps in bare-metal programming using UEFI

kevinboone.me ▲ 246 댓글 51 LaSombra

요약

리눅스에서 C와 gnu-efi로 운영체제 없이 부팅되는 UEFI 프로그램을 만드는 법을 다룬 연재입니다.

개발자 케빈 분이 리눅스와 C에 익숙한 개발자를 위해 쓴 다섯 편짜리 연재입니다. UEFI(PC 메인보드의 펌웨어 규격)가 주는 기본 기능만으로 도는 베어메탈 프로그램을 'Hello, World' 수준까지 만들어 봅니다.

왜 중요한가

  • 부트로더나 커널이 아니어도 전원을 켜고 1~2초 만에 뜨는 프로그램을 만들 수 있다는 점을 보여 줍니다.
  • 저자는 1980년대 컴퓨터처럼 켜자마자 BASIC이 뜨는 기계나 정전 뒤 곧바로 재시작해야 하는 장비를 쓰임새로 들었습니다.
  • UEFI를 몰라도 익숙한 리눅스 도구만으로 컴파일부터 부팅 디스크 제작까지 따라 할 수 있게 엮었습니다.

핵심 내용

  • gnu-efi(EFI API를 얇게 감싼 라이브러리)와 GCC로 만든 ELF 파일을 objcopy로 펌웨어가 읽는 PE32+ 형식으로 바꿉니다.
  • C 표준 라이브러리가 없습니다. 없는 함수를 불러도 링커가 잡지 못해 실행 중에 멈추는 점을 가장 까다로운 문제로 꼽았습니다.
  • 문자가 2바이트이고 경로를 역슬래시로 나누는 등 윈도식 관례를 따라서 리눅스 코드를 옮기기 어렵습니다.
  • 완성한 파일은 FAT 형식 EFI 시스템 파티션의 /EFI/BOOT/BOOTX64.EFI에 두거나 ISO 이미지에 담아 VirtualBox나 USB로 부팅합니다.
  • realloc이 없고 파일 이어쓰기도 안 되는 등 제약이 많아서 운영체제가 하던 일을 상당 부분 직접 만들어야 한다고 했습니다.

HN 반응

  • EFI 개발에 참여했다는 이용자가 윈도와 닮은 코딩 방식은 의도한 것이라고 밝히자, UTF-16 문자열과 VFAT 채택은 두고두고 남을 실수라는 비판이 이어졌습니다.
  • 표준 라이브러리 대안으로 edk2-libc, posix-uefi, uefi-rs가 소개됐고, 로드 스미스와 OSDev Wiki가 이미 다룬 내용이라는 지적도 나왔습니다.

댓글

30개 표시 · 전체 51개
  1. the_panopticon HN

    https://kevinboone.me/efitest4.html 에서 언급한 "C 표준 라이브러리가 없는 상황에 익숙해져라"에 대해서는 https://github.com/tianocore/edk2-libc 가 늘 있습니다.

    그리고 "EFI의 윈도스러움을 염두에 두라"는 대목에서, 윈도 코딩 스타일 https://computernewb.com/~lily/files/Documents/NTDesignWorkbook/coding.pdf 과 닮은 것은 우연이 아닙니다. 저희는 원래의 Intel Boot Initiative에서 EFI, 그리고 UEFI의 코드와 사양으로 발전시켜 나가면서 이 코딩 방법론을 활용했습니다. https://www.intel.com/content/dam/www/public/us/en/documents/research/2011-vol15-iss-1-intel-technology-journal.pdf

  2. PerryStyle HN

    https://libc.llvm.org/uefi/index.html 도 있는데, 아직도 업데이트되고 있는지는 잘 모르겠습니다.

  3. FuriouslyAdrift HN

    EDK II에 최근 발견된 심각한 CVE가 있으니 주의하시기 바랍니다.

    https://app.opencve.io/cve/CVE-2026-97631

  4. ktm5j HN

    제가 알기론 PXE 드라이버에서만 그런 거라, 여기서는 문제 안 될 거예요.

  5. kragen HN

    "이 코딩 방법론을 활용했다"는 건 그 과정의 결과물을 부당하게 긍정적으로 표현한 거라고 생각해요. "병을 퍼뜨렸다"가 훨씬 정확하겠죠. UTF-16 문자열이라니, 정말요? VFAT요? PE32+는 그나마 정당화할 수 있을지도 모르죠(저는 ELF가 더 좋은데, 그건 더 낫기 때문이 아니라 더 익숙해서예요). 그런데 VFAT는요? 게다가 01998년에도 UTF-16이 문자열 포맷으로 제정신이 아니라는 건 분명했어야 하는데, EFI에서 UEFI로 넘어간 02005년에는 말할 것도 없고요.

    저한테 UEFI는 독성 폐기물을 호수에 내다 버리면서 "폐기물 처리 방법론을 활용했다"고 하는 것과 같아요. 네, 버리는 쪽이야 더 편하겠죠. 하지만 그 때문에 오래가는 피해가 생기고 수백만 명이 몇 년씩 고통받아요. 결국 다른 누군가가 그 뒤처리를 해야 하고요. 처음부터 제대로 된 방법을 택했을 때보다 비용은 훨씬 크죠.

    X-Windows에 대해서도 아마 같은 말을 할 수 있을 거예요.

  6. doublepg23 HN

    파일시스템을 직접 구현해 본 적은 없지만, OS 개발을 조사해 보니 FAT32가 최소한으로 구현할 법한 파일시스템에 꽤 가까우면서도 아주 잘 알려진 대상이더라고요.

  7. kragen HN

    네, 공정한 평가라고 생각해요. 다만 FAT32에도 멍청함이 프랙털처럼 깊이 박혀 있긴 해요. 분 안의 초를 따로 5비트 필드로 둔다고요? 파일 이름과 확장자를 고정 폭 필드로 따로 둔다고요? 파일 크기 4GiB 제한은요?

    그래도 FAT32가 비교적 단순하다는 장점이 있는 건 맞지만, 그게 VFAT의 변명이 될 수는 없어요.

  8. doublepg23 HN

    제가 용어에 익숙하지 않은 걸 수도 있는데, FAT32와 VFAT는 구분이 되나요?

  9. fredoralive HN

    Windows 95가 FAT에 긴 파일 이름을 도입했고, 그 파일시스템을 VFAT.VXD 드라이버로 구현했습니다. 그래서 "VFAT"는 "윈도 방식의 긴 파일 이름"이 붙은 FAT을 가리키는 약칭으로 쓰이기도 합니다. FAT16과 FAT32 같은 FAT의 모든 변형에 적용할 수 있습니다.

    FAT32는 90년대 중반 Windows 95 OSR2와 함께 도입된 FAT의 변형으로, 큰 하드 드라이브를 더 잘 지원합니다(크기 제한 문제 말고도 FAT16 드라이브에서는 클러스터 크기가 너무 커지고 있었습니다).

  10. duskwuff HN

    VFAT는 일부 운영체제가 FAT에서 긴 파일 이름(PROGRA~1 같은 것들)을 구현하는 방식입니다. 꽤 지저분합니다.

  11. JdeBP HN

    사실상 구분이 없습니다. 리눅스는 같은 디스크 위 파일시스템 형식을 보는 여러 방식을 서로 다른 파일시스템 유형으로 취급합니다. msdos, umsdos, vfat이 그렇습니다. 아직도 굴러다니는 1990년대 마이크로소프트의 옛 문서는 구조가 아니라 역사적으로 발전해 온 순서대로 설명하고, VFAT를 FAT32의 확장이라고 설명합니다.

    현실은, 적어도 이 글의 주제에 관해서는, FAT의 비트 수와 루트 디렉터리 방식(고정 위치인지 가변 길이인지)만이 의미 있는 변형이라는 겁니다. 2026년에 FAT에서 긴 파일 이름을 지원하지 않으려는 사람은 없고, EFI는 긴 파일 이름을 기본으로 이해합니다. EFI는 세 가지 비트 수를 모두 처리합니다. 그리고 32비트에서는 가변 길이 루트 디렉터리, 16비트와 12비트에서는 고정 길이 루트 디렉터리라는 관례대로 동작합니다.

    FAT은 아주 형편없는 파일시스템 형식이고, 자기 운영체제를 만드는 사람에게 최소한의 파일시스템 형식도 아닙니다. 크로스 개발 도구가 존재한다는 점에서 게으른 파일시스템 형식일 뿐입니다. 자기 운영체제를 위한 최소한의 파일시스템을 만든다면 훨씬 나은 출발점이 있습니다. 자기 운영체제를 만들지 않는 경우에도 어떤 합리적인 정의로 봐도 FAT은 최소가 아니며, 크로스 개발 도구와 수십 년에 걸친 역사적 변경 등과 호환되어야 하기 때문에 거쳐야 하는 관문이 엄청나게 많습니다.

    예를 들어 오리지널 UFS를 쓸 수 있습니다. LEAN도 있습니다. 20여 년 전에 저는 HPFS2를 고안했습니다.

  12. kragen HN

    HPFS2 괜찮아 보이네요. 직접 구현해 보셨나요? 그리고 각주에서 굳이 다루지 않기로 하신 LEAN의 설계상 실수는 뭐였나요?

    UEFI는 보통 파일시스템에 쓰지는 않고 읽기만 하면 되니까, 더 적절한 파일시스템은 ZIP 같은 거라고 생각해요.

  13. hmry HN

    Microsoft Game Development Kit 문서[0]에 실린 영원한 명언을 빌리자면, "Windows는 기본적으로 UTF-16으로 동작합니다 [...]. 이는 여러 플랫폼을 대상으로 하는 코드에 Windows가 지우는 독특한 부담입니다."

    마이크로소프트가 새 프로젝트에 UTF-16을 추가하는 "UTF-16 회사" 노릇을 언제쯤 그만둘지 궁금해요. LSP에서도 또 그랬잖아요, 그게 2016년인데요. 어쩌면 2045년쯤엔 마이크로소프트가 새 프로젝트에 UTF-8을 쓰고, 2100년이면 우리도 기본값을 UTF-8로 바꿀 수 있겠죠.

    [0] https://learn.microsoft.com/en-us/gaming/gdk/docs/gdk-dev/overviews/utf-8?view=gdk-2610

  14. tredre3 HN

    LSP에서도 또 그랬잖아요, 그게 2016년인데요.

    그건 VS Code용으로 개발됐다는 사실로 설명이 됩니다. VS Code는 타입스크립트/자바스크립트 프로젝트이고, 자바스크립트는 문자열에 UTF-16을 씁니다(마이크로소프트와 무관한 역사적 이유 때문이지만, 같은 시기에 MS가 UCS-2를 쓰게 된 이유와 같은 이유입니다).

  15. genxy HN

    https://en.wikipedia.org/wiki/UTF-16

    UTF-16은 Windows API와 Java, Qt 같은 많은 프로그래밍 환경에서 쓰입니다. UTF-16은 가변 길이 문자 방식인데 대부분의 문자는 가변 길이가 아니어서(그래서 가변 길이는 거의 테스트되지 않습니다), Windows 자체를 포함해 많은 소프트웨어에서 버그가 발생했습니다.

  16. tredre3 HN

    물론입니다. UTF-16은 대부분의 맥락에서 나쁘고 용납할 수 없습니다. 제 댓글을 잘못 이해하셨거나 제가 표현을 제대로 못 한 것 같습니다.

    제가 하려던 말은, 스택의 모든 구성 요소가 자바스크립트 기반이라면 RPC API도 UTF-16을 쓰는 게 이해가 된다는 겁니다. 상위 댓글이 암시한 것처럼 마이크로소프트가 Windows의 역사적 이유로 밀어붙인 게 아니라, 마이크로소프트가 선택했지만 만들지는 않은 JS 스택 때문에 정해진 것입니다.

  17. genxy HN

    알아들었어요, 그냥 맥락을 덧붙인 거예요. Windows, JavaScript, Java 전부 UTF-16이죠. 6만 5천 자면 누구에게나 충분하잖아요.

  18. noosphr HN

    자바스크립트 라이브러리들이 이모지를 여기저기 던져 넣는 게 멍청하다고 늘 생각했어요.

    그러다 LLM이 쏟아내는 슬롭을 다루려고 마침내 JS를 제대로 이해해 보니, 사방에 로켓이 있는 건 그냥 민망한 게 아니라 UTF-16을 제대로 처리했다는 걸 보여 주는 가장 빠른 방법이더라고요.

  19. Someone HN

    자바스크립트는 문자열에 UTF-16을 쓴다

    명세가 그걸 요구한다고는 생각하지 않습니다. charAt은 UTF-16 코드 포인트를 기준으로 인덱싱하지만, 그걸 O(1) 시간에 해야 한다는 말은 본 기억이 없으니, 구현이 내부적으로 UTF-16을 써야 한다는 뜻은 아닙니다.

    또 최신 자바스크립트에 관해서는 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt 에 이렇게 나와 있습니다.

    “참고: 위의 해법을 charAt()으로 다시 구현하지 마세요. 외톨이 서로게이트(lone surrogate)를 감지하고 짝을 맞추는 일은 복잡하며, 내장 API는 문자열의 내부 표현을 직접 사용하므로 성능이 더 좋을 수 있습니다. 필요하다면 위에 언급한 API의 폴리필을 설치하세요.”

    그래서 최신 구현 대부분은 적어도 UTF-8과 비슷한 변형 내부 표현을 갖추고 있기를 바랍니다.

  20. GrayShade HN
  21. skrellm HN

    C 표준 라이브러리가 없는 문제의 간단한 해결책입니다.

    https://gitlab.com/bztsrc/posix-uefi

    특징:

    • POSIX 시스템에서 네이티브 도구만으로 동작합니다(별도 빌드 환경이 필요 없습니다)

    • UEFI 앱에 (제한적인) POSIX 인터페이스를 제공합니다

    • 앱과 펌웨어 사이에서 UTF-8과 UCS-16 문자열을 투명하게 변환합니다

    • GNU-EFI와 달리 "uefi_call_wrapper" 같은 꼼수가 필요 없습니다

    • 부팅 가능한 디스크 이미지(GPT, ESP 포함)를 만드는 도구가 있습니다

    • "Hello World"부터 ELF 실행 파일 부팅까지 예제 코드가 있습니다

    • AI를 전혀 쓰지 않았습니다(100% AI-free)

  22. bemtevi HN

    멋지네요! 100% AI-free인지는 어떻게 아세요? 이런 프로젝트를 눈여겨보고 있어서 알고 싶어요. 개발자 본인이신가요?

  23. jasomill HN

    지난 3년 동안 사소한 코드 변경이 딱 한 번뿐이라는 사실이면 저한테는 충분한 증거예요.

  24. JdeBP HN

    Rod Smith와 OSDev Wiki가 몇 년 전에 이미 이 길을 갔고, 어떤 대목은 글자 그대로 같습니다.

    물론 둘 다 리눅스 위의 GCC를 전제로 합니다. 둘 중 하나라도 바꾸면 완전히 다른 얘기가 됩니다. 예를 들어 NetBSD와 FreeBSD는 둘 다 기본 시스템에 EFI 개발 환경이 들어 있고, FreeBSD의 경우 /usr/src/boot 안에 숨어 있습니다. 자체 EFI 부트로더를 빌드해야 하니 그럴 수밖에 없습니다. 게다가 NetBSD는 EFI를 지원하는 대상이 인텔 프로세서 아키텍처만이 아닙니다. (예컨대 NetBSD는 ARM CPU에서도 EFI로 부팅할 수 있습니다.)

  25. kevin_thibedeau HN

    링커는 존재하지 않는 함수를 코드가 호출해도 불평하지 않습니다. 그냥 외부 심볼로 표시해 두고 다른 코드가 제공해 주리라 기대할 뿐입니다. 하지만 제공되지 않을 것이고, 프로그램은 이런 빠진 함수 중 하나를 호출하는 순간 그냥 죽어 버립니다.

    링크 후에 외부 심볼이 남아 있지 않은지는 nm -D --undefined-only로 확인할 수 있습니다. 이걸 빌드 스크립트에 넣어 두면 바이너리가 유효한지 검증할 수 있습니다.

  26. edoceo HN

    이 가이드는 UEFI 분야를 시작하기에 훌륭합니다. 저는 Zig로 부트로더를 만들 때 참고 자료 중 하나로 썼습니다. 나름의 문제도 있는데, 특히 16비트 문자 문제가 그렇습니다.

    그 부분을 파고들면 날카로운 모서리가 한둘이 아닌데, 어쩌면 EFI 환경 자체가 일반적인 리눅스 시스템이나 애플리케이션 개발에 비해 워낙 이상해서일 수도 있습니다.

  27. hocusPokeUs HN

    UEFI 프로그래밍에 도움이 되는 다른 멋진 자료들이에요.

    UEFI에서 돌아가는 IRC 클라이언트: https://github.com/codyd51/uefirc

    그 IRC 클라이언트가 의존하는 UEFI 라이브러리: https://github.com/rust-osdev/uefi-rs

    더는 GRUB과 부트로더 코드 경로를 따로 둘 필요가 없어요. 커널을 로드해서 UEFI에서 바로 리눅스를 부팅하세요: https://pretalx.com/devconf-cz-2024/talk/W3AVCT/

    부트로더부터 시스템 전체를 LLM으로 생성하는 사이드 프로젝트를 이것저것 만지작거려 왔는데, 기력이 나지 않았어요. 본업이 같은 사고 영역이라 번아웃을 피하려면 가끔은 머리에서 비워내야 하거든요. 궁극적으로는 제 리눅스 사용 방식을 모델로 만들어 부팅하고 싶어요(가능한 모든 걸 bpf로 계획하고 모델링해서 sched_ext용 연산 스케줄로 변환하고 있어요). 그래서 그냥 커널 <> 모델 구조만 갖고 싶어요.

    보안을 위해서는, 웹캠과 키 같은 걸로 확인하기 전에는 특정 메모리 블록에 접근하지 못하도록 막는 여러 인증 의무를 모델에 어떻게 내장할지 고민하고 있어요.

  28. mrlonglong HN

    이런. UEFI는 생각보다 훨씬 나쁘네요.

    제일 좋은 방법은 부트로더로만 취급하고, UEFI보다 훨씬 제정신인 다른 걸 쓰는 거죠.

    새 개정판에서는 8비트 UTF와 더 나은 관례를 쓰게 될 거라는 희망을 감히 품어도 될까요.

  29. kragen HN

    coreboot이 찾으시는 게 아닐까요? 아니면 DeviceTree? 아니면 Open Firmware?

    아니면 특정 보드용 무언가도 있고요. UEFI나 BIOS는 그냥 호환성 계층일 뿐이고, 더 제정신인 대안들에 비해 유일한 장점은 쉽게 살 수 있는 값싸고 빠른 특정 하드웨어에 이미 설치되어 동작한다는 거예요.

    그래도 그 장점이 28년 전만큼 크진 않을지도 모르겠네요.

  30. mlfreeman HN

    C#도 쓸 수 있어요.

    https://github.com/MichalStrehovsky/zerosharp

    https://github.com/MichalStrehovsky/uefimaze

    제가 만든 건 아니지만, 조만간 한번 만져 볼 목록에 올려 뒀어요.

Hacker News에서 보기 ↗