1P by GN⁺ | ★ favorite | 댓글 1개
  • UEFIRC는 운영체제가 뜨기 전 메인보드 펌웨어의 UEFI 사전 부팅 환경에서 실행되는 그래픽 IRC 클라이언트로, 부트로더용 환경에서도 일반 앱에 가까운 UI와 네트워크 기능을 구현할 수 있음을 보여줌
  • 구현은 UEFI가 네트워크 부팅을 위해 제공하는 NIC 드라이버와 TCP 스택을 활용하며, QEMU용 vmnet 네트워크 백엔드가 개발을 가능하게 함
  • 가장 까다로운 지점은 Rust에서 UEFI TCP 프로토콜을 다루는 일이었고, 전역 상태, 재진입 콜백, scatter-gather 버퍼, 이벤트·토큰·핸들·프로토콜이 복잡하게 얽힘
  • GUI는 axle의 Rust GUI 툴킷과 TrueType 렌더러를 UEFI로 가져온 형태이며, 마우스 입력·스크롤바·스크롤 뷰 텍스트 렌더링을 위해 libgui 개선도 함께 이뤄짐
  • 결과물은 실용적 IRC 클라이언트라기보다 정교한 농담성 프로젝트에 가깝지만, UEFI TCP/IP 스택에 대한 불만을 UEFI 안에서 IRC로 말할 수 있는 도구가 됨

UEFIRC가 하는 일

  • UEFIRCUEFI에서 실행되는 그래픽 IRC 클라이언트
  • Rust로 작성됐고, axle 사용자 공간용으로 만든 GUI 툴킷과 TrueType 렌더러를 활용함
  • IRC 서버에 접속해 채팅하고 메시지를 읽을 수 있음
  • 개발에는 QEMU용 vmnet 네트워크 백엔드가 사용됨

UEFI라는 실행 무대

  • 운영체제의 부트로더는 메인보드 ROM에 저장된 펌웨어의 도움으로 로드됨
  • 과거 BIOS는 여러 제약을 가졌고, 이를 대체하기 위해 UEFI 표준이 만들어짐
    • BIOS는 부트로더가 16-bit 모드에서 시작하도록 요구함
    • 첫 단계 로더가 512바이트 안에 들어가야 하는 요구도 있음
  • UEFI는 부트로더를 처음부터 64-bit 환경에 두고, VESA 디스플레이 해상도 전환, 메모리 할당, EFI 파일시스템 접근 같은 API를 제공함
  • BIOS보다 큰 진전이지만, 과도하게 설계됐다는 평가도 함께 받음

네트워크 부팅 기능을 IRC에 재사용

  • 일부 부트로더는 로컬 블록 디바이스가 아니라 네트워크를 통해 운영체제를 로드할 수 있음
  • 이 사용 사례를 지원하려면 UEFI 펌웨어가 네트워크 스택을 포함해야 함
    • NIC 드라이버
    • TCP 구현
    • 사전 부팅 환경에서 실행되는 애플리케이션이 이 스택에 접근하는 API
  • 부트로더가 반드시 운영체제를 로드해야 하는 것은 아니므로, 같은 환경에서 IRC 클라이언트도 실행할 수 있음

Rust에서 UEFI TCP를 다루는 어려움

  • 프로젝트에서 가장 까다로운 부분은 UEFI TCP 프로토콜 클라이언트를 Rust로 구현하는 일이었음
  • UEFI TCP 프로토콜은 Rust에 설명하기 어려운 데이터 수명과 상호작용을 요구함
    • 전역 상태
    • 재진입 콜백
    • scatter-gather 버퍼
    • 이벤트, 토큰, 핸들, 프로토콜
  • 메모리 누수와 TCP 수신 버퍼의 use-after-free를 없애기 위해 Rust 코드를 여러 날 테스트함

NOTIFY_SIGNALNOTIFY_WAIT의 혼란

  • UEFI 이벤트 API는 이름만 보고 동작을 예측하기 어려움
  • NOTIFY_SIGNAL을 지정하면 이벤트 발생 시 콜백이 호출되고, wait() 사용은 오류가 됨
  • NOTIFY_WAIT을 지정하고 wait()를 호출하면, UEFI는 이벤트가 발생하기 전까지 콜백을 여러 번 호출할 수 있고 이벤트가 발생하면 wait()가 풀림
  • 두 모드는 같은 콜백이라도 완전히 다른 의미를 갖게 함
    • NOTIFY_SIGNAL: 이벤트가 발생했으니 다음 작업을 할 시점
    • NOTIFY_WAIT: 이벤트가 아직 발생하지 않았으니 진행을 유도할 시점
  • 수신 패킷 데이터를 비동기 버퍼링하기 위해 최종적으로 NOTIFY_WAIT 루프와 짧은 타임아웃 타이머를 함께 사용함

마우스와 커서 지원

  • IRC 클라이언트에 마우스가 필수는 아니지만, 앱을 더 상호작용적으로 느끼게 함
  • UEFI의 Simple Pointer Protocol을 사용해 마우스 이동과 버튼 입력을 읽고, GUI에 커서 위치 피드백을 넣음
  • Simple Pointer Protocol은 스크롤 휠을 지원하지 않음
    • UEFIRC에서는 화살표 키를 쓰거나 커서로 스크롤바를 드래그해야 함
  • 표준 OVMF UEFI 펌웨어에서는 마우스 이벤트를 얻을 수 없어, UsbMouseDxe 등 필요한 드라이버와 프로토콜을 포함한 커스텀 UEFI 펌웨어를 빌드함
  • QEMU에서 UEFIRC를 시험할 수 있도록 해당 UEFI 펌웨어도 릴리스에 업로드

마우스 이동 스케일링

  • 마우스 드라이버는 절대 위치가 아니라 위치 변화량을 보고함
  • 단순히 delta_x, delta_y를 그대로 더하는 선형 스케일링은 둔하게 느껴짐
  • 운영체제는 빠른 이동과 세밀한 조정을 함께 가능하게 하는 방식에 가까운 스케일링을 사용함
  • 예시 구현에서는 이동량의 절댓값 합에 log2()를 적용한 값을 곱해 커서 이동을 키움
  • 선형 이동 커서는 전체 환경이 느리고 반응이 나쁘다고 느끼게 만들기 쉬움

IRC 메시지 모델링

  • IRC 메시지 모델링은 비교적 간단하고 쾌적했음
  • IRC는 텍스트 기반 라인 포맷을 사용해 파싱하기 쉬움
  • 다만 수십 년에 걸친 확장으로 인해 일부만 표준화된 부담도 있음

UEFI에서 libgui 사용

  • axle의 Rust GUI 툴킷은 이미 axle 외부 컨텍스트에서도 쓸 수 있도록 상당 부분 작업돼 있어, UEFI에서 실행하는 일이 크게 어렵지는 않았음
  • 핵심 작업은 UEFI 안에서 사용할 수 있는 AwmWindow 구현을 제공하는 일이었음
  • 이후 libgui의 여러 기능을 그대로 활용함
    • 이벤트 관리
    • 폰트 렌더링
    • 레이어 합성
    • 뷰 장식
    • 스크롤 뷰 같은 복잡한 컴포넌트

스크롤바와 스크롤 뷰 텍스트 렌더링

  • axle의 C 기반 libgui에는 스크롤바 기능이 있었지만, Rust 버전에는 아직 일부 기능이 부족했음
  • UEFIRC의 주요 상호작용은 텍스트로 채워진 스크롤 뷰에서 이뤄지므로, Rust libgui에 스크롤바 기능을 다시 구현함
  • 스크롤 뷰는 고정 크기 뷰보다 픽셀 렌더링 비용이 큼
    • 고정 크기 뷰는 width * height 크기의 RGB 버퍼를 생각하면 됨
    • 스크롤 뷰는 무한히 확장 가능한 캔버스를 다뤄야 함
  • axle의 Rust GUI 툴킷은 스크롤 뷰를 타일 기반으로 처리함
    • 각 타일은 수백 픽셀 너비의 정사각형 픽셀 버퍼
    • 콘텐츠가 실제로 렌더링되는 영역에 필요한 타일만 할당함
    • 보이는 타일을 계산해 최종 이미지로 이어 붙임
  • TrueType 렌더러가 글리프의 픽셀마다 putpixel()을 호출하면, 스크롤 뷰가 전체 렌더링 영역을 미리 알 수 없어 비효율적임
  • 이를 해결하기 위해 polygon stack을 선, 원, 사각형 같은 기본 드로잉 단위에 추가함
    • 스크롤 뷰가 큰 폴리곤을 그린다는 사실을 알고 필요한 타일을 미리 할당할 수 있음
    • 임의 폴리곤 채우기를 기본 프리미티브로 둔 점은 마음에 들지 않지만, 실용적으로 잘 동작함

UEFIRC를 만들며 개선된 libgui

완전히 불필요한 결과물

  • IRC 클라이언트 자체는 정교한 농담 프로젝트라 실사용성이 높지 않음
  • UEFI TCP/IP 스택에 화가 날 때, 그 불만을 이야기할 도구로 쓸 수 있음
  • 마지막으로 UEFI 안에서 UEFI #edk2 개발 IRC 채널에 접속해 인사를 남김

댓글과 토론

Hacker News 의견들
  • 장난삼아 UEFI 부팅 전 환경에서만 돌아가는 그래픽 IRC 클라이언트를 만들었고, TrueType 글꼴, 커서, GUI 장식 같은 과한 기능도 넣었음
    원래는 처음부터 만드는 GPS 수신기에 지쳐서 빠르고 가벼운 걸 해보려던 프로젝트였는데, 늘 그렇듯 예상보다 훨씬 오래 걸렸음
    글에서 스크롤 뷰를 모델링하고 정적 뷰포트에 렌더링하는 방식을 보여주는 시각화에도 꽤 많은 시간을 썼고, 즐겁게 봐주면 좋겠음
    처음엔 “UEFI에 들어가면 안 될 걸 우겨 넣자”는 생각으로 Twitter 클라이언트를 떠올렸지만, 이미 누군가 UEFI의 HTTP 프로토콜로 잘 만들어둔 게 있어서 HTTP는 피하기로 했음
    그래서 TCP 위에서 동작하고, 부팅 전 환경과 전혀 어울리지 않는 소셜 미디어 느낌도 있는 IRC를 골랐음

    • “부팅 전 환경 근처에도 있으면 안 되는 느낌”이라지만, 부팅 문제를 도와달라고 하려면 오히려 딱 맞는 장소처럼 보임
    • 쓸데없이 거대한 운영체제와 잡다한 기능을 버리고 더 작고 단순한 UEFI로 가고 싶음. 시작도 더 빠르고 “임베디드” 개발도 쉬워질 테니 말임
      물론 농담임. 어느 정도는
      미니멀리스트라 GUI나 마우스도 필요 없고, UEFI도 이미 내겐 필요한 것보다 많은 듯함
      언급된 Twitter 클라이언트는 여기 있음: https://github.com/arata-nvm/mitnal
    • UEFI에 쑤셔 넣기엔 너무 큰 소프트웨어라면 애초에 전부 불필요한 비대한 소프트웨어라고 봐야 함. 예전엔 360KB 플로피 두 장이면 충분했음
    • 정말 멋짐. 예전부터 VPN 자격 증명을 UEFI에 저장하고, 시스템이 서버에 접속해 PXE 네트워크 부팅을 하게 만들 수 있을지 궁금했음
      설치가 완전히 망가져 정상 부팅이 안 되는 원격 시스템을 자동 복구하도록, 어쩌면 안전하게 허용하는 꽤 괜찮은 방식일 수 있어 보임
    • 처음부터 만든 GPS 수신기 쪽 이야기가 더 궁금함
  • 정말 좋음. 대부분이 생각하는 시스템 아래에는 생각보다 복잡하고 강력한 소프트웨어가 깔려 있다는 점도 잘 드러남
    운영체제가 소프트웨어 스택의 “최저 계층”이라고 흔히 오해하지만, 실제로는 시스템을 진짜로 소유하는 펌웨어성 코드가 있음
    때로는 할 일을 마치고 사라지고, 때로는 운영체제조차 투명하게 느끼는 상태로 시스템이 켜져 있는 내내 남아 있음
    “그냥 장치 구동용 저수준 코드일 뿐이고, 거기서 심각한 일은 안 생긴다”는 태도가 있지만, 그 아래에 IRC 클라이언트까지 넣을 수 있다면 다른 악의적인 일들도 충분히 상상 가능해짐

  • “왜?”라니, 대체 “왜”가 무슨 질문임? 이런 정신을 보려고 HN에 옴
    “가장 무서운 깨달음이 찾아왔다. 내가 한 일에는 아무 이유도 없었다. 왜 했는지는 알았다. 그냥 재미있을 것 같아서 했다. 하지만 그들이 ‘대체 왜 이런 짓을 했냐’고 물을 테고, 충분히 그럴듯한 이유가 없으면 나를 정신병원에 처넣을 것 같았다.” — Boyd Rice

  • 스스로를 낮게 보지 않아도 됨. 여기엔 봇넷 명령·제어 클라이언트 프로젝트가 있음
    UI가 좀 웃기긴 함

  • 정말 멋짐. UEFI API가 이렇게 쉽게 접근 가능하고 문서화도 잘 되어 있는 줄 몰랐음
    개발 주기가 어땠는지 궁금함. VM에서 실행했을 것 같은데, 클라이언트를 돌릴 때마다 매번 “부팅”해야 했는지 궁금함

    • 보통 작업 루프는 UEFI 애플리케이션을 실어 QEMU 인스턴스를 부팅하는 식이었음
      메인 실행 스크립트가 UEFIRC의 새 빌드를 담은 EFI 파일시스템을 다시 만들고, 그걸 QEMU에 넘김
      다만 GUI를 만들 때는 이 오버헤드가 꽤 번거로워져서, 앱이 순수 UEFI와 Mac에서 돌아가는 호스트 환경 둘 다 대상으로 빌드되게 설정했음
      빌드 플래그를 바꾸면 GUI 툴킷이 UEFI가 제공하는 프레임버퍼에 직접 그리거나, Mac의 윈도 시스템에 연결되어 이벤트를 주고받게 됨
      이중 대상 접근의 오버헤드는 엔트리 포인트에서도 볼 수 있음: https://github.com/codyd51/uefirc/blob/main/src/main.rs
      IRC 메시지 파싱은 별도 장식이 필요 없어서 Mac에서 바로 도는 단위 테스트 묶음으로 개발했고, 일부는 여기 있음: https://github.com/codyd51/uefirc/blob/main/src/irc/response...
    • QEMU는 UEFI 앱을 실행할 수 있음
  • 언젠가 아직도 돌고 있는 내 IRC 봇용 운영체제 작성을 끝내고 싶음
    어쩌면 가장 쓸모없는 말이겠지만, 비선형 마우스 이동, 즉 가속은 새 운영체제를 부팅하면 가장 먼저 끄는 설정임. 이상하게도 손이 실제로 아픔
    예를 들어 Mac에는 무료인 linearmouse가 있고, Windows에서는 그냥 가속을 끄면 됨. Linux에서는 당연히 쉬움
    마우스 가속을 쓰면 마우스가 움직인 거리와 화면상의 이동 거리를 매핑하는 감각을 익히기 어렵고, 장기적으로는 가속 없이 쓰는 편이 더 효율적이라고 봄
    게이머들에게서 배운 방식이고, 게이머들이 여전히 그럴 만한 이유가 있다고 생각함

    • 마우스 설정을 바꾸지 않아서 기본값이 뭔지는 모르겠지만, 가려진 화면 영역도 여전히 정확히 클릭할 수 있음
      어느 쪽이든 감각은 익숙해질 것 같음. 자동차의 가속 페달도 보통 속도에 직접 매핑되지는 않는 것처럼
  • “왜?”라고 묻는다면, UEFI가 처음 소개될 때 이런 저수준 애플리케이션이 약속됐기 때문임
    UEFI를 만든 쪽은 일부 벤더가 부팅 중 특정 키를 눌러 접근하게 했던 Linux 기반 인터넷 전용 미니 운영체제까지 대체하겠다는 꿈도 꿨음. 이름은 기억나지 않음

    • 그건 예전 Dell 같은 곳에 있던 Quick View / Quick Boot 기능이었음. 대개 생산성 앱 몇 개로 바로 부팅했음
      YouTube에서 이걸 깊게 다룬 영상을 봤는데, 처음엔 축소된 Linux나 다른 맞춤 운영체제였고, 나중에는 UEFI 앱으로 옮겨갔다가 결국 유행이 식은 것으로 기억함
  • 글이 좋음. 2년 전 barebox 부트로더의 만우절 장난이 떠오름. 다른 부팅 대상이 모두 실패하면 #barebox로 연결해 주는 기능이었음[1]
    그쪽의 초점은 barebox에 TCP 지원을 추가하는 데 있었고, 여기처럼 멋진 GUI 요소는 없었음
    인터페이스는 명령줄뿐이었고, barebox를 EFI 페이로드로 빌드하면 EFI GOP 위에 그릴 수 있었음
    [1]: https://lore.barebox.org/barebox/20220401145902.GF4351@telli...

  • Cathode Ray Dude의 최근 영상이 바로 떠오름. HP의 “이메일 클라이언트”, 실제로는 Outlook 플러그인인 QuickLook을 다뤘는데, 이것도 이런 방식으로 구현되어 출시된 제품이었음: https://www.youtube.com/watch?v=ssob-7sGVWs
    영상에는 HP가 했던 더 이상한 일들도 나옴. 다만 이 프로젝트는 QuickLook이 피했던 어려운 부분인 네트워킹까지 해냄

  • 글의 시각화가 놀라울 정도로 훌륭하고 인상적임