UEFI (Unified Extensible Firmware Interface)
UEFI: 부팅 단계(SEC/PEI/DXE/BDS), Boot Services/Runtime Services, EFI System Partition, Secure Boot, Linux EFI Stub, efivars, 커널 UEFI 구현을 다룹니다.
핵심 요약
- 단계 분리 — 펌웨어, 부트로더, 커널 초기화 경계를 구분합니다.
- 하드웨어 기술 — ACPI/DT 등 기술 정보가 어디서 소비되는지 확인합니다.
- 신뢰 체인(Chain of Trust) — Secure Boot 등 검증 체인을 흐름으로 이해합니다.
- 실패 지점 — 부팅 로그에서 단계별 실패 단서를 빠르게 찾습니다.
- 호환성 관점 — 플랫폼 차이에 따른 초기화 분기를 함께 점검합니다.
단계별 이해
- 부팅 단계 식별
현재 이슈가 어느 단계에서 발생하는지 먼저 고정합니다. - 입력 데이터 확인
펌웨어/테이블/이미지 메타데이터를 점검합니다. - 전환 경계 검증
단계 간 인자 전달과 상태 인계를 추적합니다. - 플랫폼별 재검증
다른 하드웨어 조건에서도 동일하게 동작하는지 확인합니다.
UEFI 부팅 단계, Boot/Runtime Services, Secure Boot, Linux EFI Stub, 커널 UEFI 구현까지 — 펌웨어와 커널의 인터페이스를 소스 코드 수준에서 분석합니다.
UEFI 개요
UEFI(Unified Extensible Firmware Interface)는 운영체제와 플랫폼 펌웨어 사이의 소프트웨어 인터페이스를 정의하는 규격입니다. 전통적인 BIOS를 대체하며, GPT 파티션, 64-bit 지원, Secure Boot, 네트워크 부팅 등 현대 시스템에서 널리 사용되는 핵심 기능을 제공합니다.
| 특성 | Legacy BIOS | UEFI |
|---|---|---|
| 주소 공간(Address Space) | 16-bit 리얼 모드 (1MB) | 32/64-bit 보호/롱 모드 |
| 파티션 테이블 | MBR (최대 2TB, 4개 primary) | GPT (최대 9.4ZB, 128개 파티션) |
| 부트 엔트리 | 고정 LBA 0 (MBR 512B) | ESP의 EFI 바이너리 (파일 경로) |
| 드라이버 모델 | INT 13h (디스크), INT 10h (비디오) | UEFI 프로토콜 기반 드라이버 |
| 보안 | 없음 | Secure Boot (디지털 서명 검증(Signature Verification)) |
| 네트워크 | PXE (제한적) | IPv4/IPv6 네트워크 스택(Network Stack) 내장 |
| 셸 | 없음 | UEFI Shell (스크립팅 가능) |
| OS 인터페이스 | 부팅 후 사라짐 | Runtime Services 지속 제공 |
UEFI 부팅 흐름 다이어그램
UEFI 부팅 단계 (PI 아키텍처)
UEFI 펌웨어는 PI(Platform Initialization) 규격에 따라 여러 단계를 거쳐 실행됩니다. 각 단계는 이전 단계가 설정한 환경 위에서 동작합니다.
SEC (Security Phase)
파워 온 직후 가장 먼저 실행되는 단계입니다. CPU 리셋 벡터에서 시작하며, 메인 메모리(DRAM)가 아직 초기화되지 않은 상태입니다.
- SEC 단계 주요 동작:
- CPU 리셋 벡터 (0xFFFFFFF0) 에서 실행 시작
- CPU 캐시(Cache)를 RAM으로 사용 (CAR: Cache-As-RAM)
- → L2/L3 캐시를 임시 스택/데이터 영역으로 활용
- → No-Eviction Mode 설정
- 최소한의 CPU 초기화 (마이크로코드 로드 등)
- 보안 검증: 펌웨어 볼륨(FV)의 무결성(Integrity) 확인
- PEI Foundation으로 핸드오프
- CAR 메모리 레이아웃 (예시): 아래 SVG 참고
PEI (Pre-EFI Initialization)
PEI는 메인 메모리(DRAM)를 초기화하는 핵심 단계입니다. PEIM(PEI Module)이라는 작은 모듈들이 실행됩니다.
/*
* PEI 단계 주요 동작:
*
* 1. PEI Foundation (PEI Core) 초기화
* 2. PPI (PEI-to-PEI Interface) 서비스 등록
* 3. PEIM(PEI Module) 디스패치:
* - CPU PEIM: CPU 기능 설정, 캐시 구성
* - 메모리 PEIM: DRAM 컨트롤러 초기화 (SPD 읽기, 트레이닝)
* - 칩셋 PEIM: PCH/SoC 초기 설정
* 4. 메모리 초기화 완료 → CAR에서 DRAM으로 전환
* 5. HOB(Hand-Off Block) 생성 → DXE에 정보 전달
* - 메모리 맵, CPU 정보, 플랫폼 데이터
* 6. DXE Foundation으로 핸드오프
*/
/* HOB에 포함되는 주요 정보 */
- PHIT (Phase Handoff Information Table)
- 메모리 할당 HOB (사용 가능/예약 영역)
- 리소스 서술자 HOB (MMIO, I/O 포트 영역)
- 펌웨어 볼륨 HOB
- CPU HOB (코어 수, 기능)
DXE (Driver Execution Environment)
DXE는 UEFI의 핵심 단계로, 대부분의 하드웨어 초기화와 서비스 등록이 이루어집니다.
- DXE 단계 주요 동작:
- DXE Foundation (DXE Core) 초기화
- Boot Services Table 생성
- Runtime Services Table 생성
- System Table 생성
- DXE 드라이버 디스패치(Dispatch):
- PCI 버스(Bus) 열거 (PCI Host Bridge Driver)
- USB 컨트롤러 초기화
- 그래픽 출력 (GOP: Graphics Output Protocol)
- 디스크 I/O (Block I/O Protocol)
- 네트워크 (SNP: Simple Network Protocol)
- 콘솔 입출력 (Simple Text I/O)
- UEFI 프로토콜 등록:
- 각 드라이버가 프로토콜(인터페이스)을 핸들에 설치
- → 다른 드라이버나 애플리케이션이 프로토콜을 통해 접근
- SMM(System Management Mode) 초기화 (해당 시)
- BDS(Boot Device Selection)로 전환
BDS (Boot Device Selection)
BDS는 부팅 장치를 선택하고 부트로더를 실행하는 단계입니다.
- BDS 단계:
- UEFI 변수에서 부팅 순서 읽기:
- BootOrder: 부팅 시도 순서 (예: 0001,0003,0002)
- Boot0001: 첫 번째 부팅 옵션 (경로, 설명 등)
- 부팅 장치 연결 (ConnectController):
- 장치 경로(Device Path)에 따라 드라이버 바인딩
- 파일시스템(Filesystem) 드라이버 로드 (FAT32)
- EFI 바이너리 로드 및 실행:
- ESP에서 부트로더 로드
- 기본 경로: \EFI\BOOT\BOOTX64.EFI (x86_64)
- 또는 Boot 변수에 지정된 경로
- 부팅 실패 시 다음 Boot 항목으로 이동
- 모든 항목 실패 시 UEFI Shell 또는 에러 화면
Boot Services & Runtime Services
UEFI는 두 종류의 서비스를 OS에 제공합니다. Boot Services는 부팅 중에만 사용 가능하고, Runtime Services는 OS 실행 중에도 사용할 수 있습니다.
Boot Services
OS 부트로더나 EFI Stub이 ExitBootServices()를 호출하기 전까지 사용 가능한 서비스입니다:
| 카테고리 | 주요 함수 | 설명 |
|---|---|---|
| 메모리 | AllocatePages(), FreePages() | 물리 메모리(Physical Memory) 페이지(Page) 할당/해제 |
| 메모리 | AllocatePool(), FreePool() | 메모리 풀 할당/해제 |
| 메모리 | GetMemoryMap() | 물리 메모리 맵 조회 (커널 초기화에 필수) |
| 프로토콜 | HandleProtocol() | 핸들에서 프로토콜 인터페이스 획득 |
| 프로토콜 | LocateProtocol() | 특정 프로토콜을 제공하는 핸들 검색 |
| 이미지 | LoadImage(), StartImage() | EFI 바이너리 로드/실행 |
| 이벤트 | CreateEvent(), SetTimer() | 이벤트/타이머(Timer) 관리 |
| 종료 | ExitBootServices() | Boot Services 종료, OS에 제어권 이전 |
ExitBootServices() — 핵심 전환점
ExitBootServices()는 UEFI와 OS 간 제어권 전환의 핵심입니다. 호출 후에는 Boot Services를 사용할 수 없으며, 펌웨어의 하드웨어 접근이 중단됩니다.
/* drivers/firmware/efi/libstub/efi-stub-helper.c */
/*
* ExitBootServices() 호출 전후 상태 변화:
*
* [호출 전]
* - 펌웨어가 인터럽트, 타이머, 디바이스 제어
* - Boot Services + Runtime Services 모두 사용 가능
* - 펌웨어 메모리 맵이 변경될 수 있음
*
* [호출 후]
* - 모든 Boot Services 함수 포인터 무효화
* - 펌웨어 인터럽트/타이머 비활성화
* - 커널이 모든 하드웨어를 직접 제어해야 함
* - Runtime Services만 사용 가능
* - 메모리 맵 고정 (더 이상 변경 없음)
*
* 주의: GetMemoryMap()의 map_key가 일치해야 함
* → 메모리 맵을 가져온 후 다른 Boot Services 호출 금지
*/
/* EFI Stub에서의 ExitBootServices 호출 흐름 */
efi_status_t efi_exit_boot_services(...)
{
/* 1. GetMemoryMap()으로 최종 메모리 맵 획득 */
status = efi_bs_call(GetMemoryMap,
&map_size, map, &map_key,
&desc_size, &desc_ver);
/* 2. ExitBootServices() 호출 (map_key 전달) */
status = efi_bs_call(ExitBootServices,
handle, map_key);
if (status == EFI_INVALID_PARAMETER) {
/* map_key 불일치 → 메모리 맵 재획득 후 재시도 */
status = efi_bs_call(GetMemoryMap, ...);
status = efi_bs_call(ExitBootServices,
handle, map_key);
}
/* 이후 Boot Services 호출 불가 */
}
Runtime Services
OS 실행 중에도 사용할 수 있는 서비스입니다. 리눅스 커널은 이를 통해 UEFI 변수, 시간, 리셋 등을 처리합니다:
| 카테고리 | 주요 함수 | Linux 활용 |
|---|---|---|
| 변수 | GetVariable() | /sys/firmware/efi/efivars/ 읽기 |
| 변수 | SetVariable() | UEFI 변수 쓰기 (부팅 순서 변경 등) |
| 변수 | GetNextVariableName() | UEFI 변수 열거 |
| 시간 | GetTime(), SetTime() | RTC 접근 (hwclock) |
| 시간 | GetWakeupTime() | 알람 시간 조회/설정 |
| 리셋 | ResetSystem() | reboot 명령 처리 |
| 캡슐 | UpdateCapsule() | 펌웨어 업데이트 (fwupd) |
| 기타 | QueryVariableInfo() | 변수 저장소 용량 확인 |
/* include/linux/efi.h — Linux에서 Runtime Services 호출 */
extern struct efi {
efi_runtime_services_t *runtime; /* RT 서비스 테이블 */
unsigned long runtime_version; /* EFI 규격 버전 */
unsigned long mps; /* MPS 테이블 주소 */
unsigned long acpi; /* ACPI 1.0 RSDP */
unsigned long acpi20; /* ACPI 2.0+ XSDP */
unsigned long smbios; /* SMBIOS 테이블 */
unsigned long smbios3; /* SMBIOS 3.0+ */
/* ... */
} efi;
/* Runtime Services 호출 시 주의사항:
* - 가상 주소 매핑 필요 (SetVirtualAddressMap)
* - 일부 RT 호출은 non-preemptible
* - RT 서비스는 동시 호출 불가 (직렬화 필요)
* - efi_runtime_lock 으로 보호됨
*/
EFI System Partition (ESP)
ESP는 UEFI 부트로더, 드라이버, 유틸리티가 저장되는 특수 파티션입니다. FAT32 파일시스템을 사용하며, GPT 파티션 타입 GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B로 식별됩니다.
ESP 디렉터리 구조 예시
아래는 일반적인 Linux 배포판에서 자주 보이는 ESP 구조입니다. 실제 경로는 배포판과 설치 방식에 따라 다를 수 있습니다.
# 마운트 지점: /boot/efi (또는 /efi)
EFI/
BOOT/
BOOTX64.EFI # 폴백 부트로더 (기본 경로)
fedora/ # 배포판별 디렉터리 예시
shimx64.efi # Secure Boot shim
grubx64.efi # GRUB2 EFI 바이너리
grub.cfg # GRUB 설정 (또는 /boot/grub2/)
ubuntu/
shimx64.efi
grubx64.efi
mmx64.efi # MOK Manager
tools/
memtest86.efi # 메모리 테스트 유틸리티
vmlinuz-6.x # EFI Stub 부팅 시 커널 이미지
initramfs-6.x.img # EFI Stub 부팅 시 initramfs
EFI/BOOT/BOOTX64.EFI 같은 기본 경로를 확인하고, 그다음 배포판 디렉터리(EFI/fedora/, EFI/ubuntu/)와
실제 부팅 엔트리(efibootmgr -v)가 서로 일치하는지 점검하세요.
ESP 점검 명령
# ESP 파티션 타입 GUID, 파일시스템, 마운트 지점 확인
$ lsblk -o NAME,PARTTYPE,FSTYPE,MOUNTPOINT | grep -i efi
# 현재 ESP 마운트 상태 확인
$ findmnt /boot/efi
# UEFI 부팅 항목(경로 포함) 확인
$ efibootmgr -v
EFI 바이너리 형식
UEFI 애플리케이션과 드라이버는 PE32+ (Portable Executable) 형식을 사용합니다:
# EFI 바이너리 확인
$ file /boot/efi/EFI/fedora/grubx64.efi
grubx64.efi: PE32+ executable (EFI application) x86-64, for MS Windows
# PE 헤더 분석
$ objdump -x /boot/efi/EFI/fedora/shimx64.efi | head -30
# Subsystem: EFI Application (10)
# Machine: Advanced Micro Devices X86-64 (8664)
# 아키텍처별 기본 부팅 경로
x86_64 : \EFI\BOOT\BOOTX64.EFI
ARM64 : \EFI\BOOT\BOOTAA64.EFI
IA-32 : \EFI\BOOT\BOOTIA32.EFI
RISC-V : \EFI\BOOT\BOOTRISCV64.EFI
UEFI 변수 (efivars)
UEFI 변수는 NVRAM(비휘발성 메모리)에 저장되는 키-값 쌍으로, 부팅 설정, OS 데이터, Secure Boot 키 등을 포함합니다. Linux에서는 efivarfs를 통해 접근합니다.
# efivarfs 마운트 및 변수 목록
$ mount -t efivarfs efivarfs /sys/firmware/efi/efivars/
$ ls /sys/firmware/efi/efivars/ | head
Boot0000-8be4df61-93ca-11d2-aa0d-00e098032b8c
Boot0001-8be4df61-93ca-11d2-aa0d-00e098032b8c
BootCurrent-8be4df61-93ca-11d2-aa0d-00e098032b8c
BootOrder-8be4df61-93ca-11d2-aa0d-00e098032b8c
SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c
# 변수 이름 형식: 이름-벤더GUID
# 8be4df61-93ca-11d2-aa0d-00e098032b8c = EFI_GLOBAL_VARIABLE
efibootmgr — 부팅 관리
# 현재 부팅 항목 확인
$ efibootmgr -v
BootCurrent: 0001
Timeout: 3 seconds
BootOrder: 0001,0003,0002,0000
Boot0000* EFI Network PciRoot(0x0)/Pci(0x1f,0x6)/MAC(...)
Boot0001* Fedora HD(1,GPT,...)/File(\EFI\fedora\shimx64.efi)
Boot0002* Windows HD(1,GPT,...)/File(\EFI\Microsoft\Boot\bootmgfw.efi)
Boot0003* UEFI Shell FvVol(...)
# 새 부팅 항목 추가
$ efibootmgr -c -d /dev/sda -p 1 \
-L "Linux Direct" \
-l '\vmlinuz-6.x' \
-u 'root=/dev/sda2 ro quiet'
# 부팅 순서 변경
$ efibootmgr -o 0001,0002,0003
# 다음 부팅만 특정 항목으로
$ efibootmgr -n 0003 # 한 번만 UEFI Shell로 부팅
# 부팅 항목 삭제
$ efibootmgr -b 0003 -B
UEFI 변수 구조
/* include/linux/efi.h — UEFI 변수 속성 */
#define EFI_VARIABLE_NON_VOLATILE 0x00000001
#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002
#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004
#define EFI_VARIABLE_HARDWARE_ERROR_RECORD 0x00000008
#define EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS 0x00000010
#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS 0x00000020
#define EFI_VARIABLE_APPEND_WRITE 0x00000040
/*
* efivarfs에서 변수 파일 형식:
* [4 bytes: attributes (little-endian)] + [가변 길이: 데이터]
*
* 예시: BootOrder 변수 읽기
* $ hexdump -C /sys/firmware/efi/efivars/BootOrder-8be4df61-...
* 00000000 07 00 00 00 01 00 03 00 02 00 00 00
* ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* attributes Boot0001, Boot0003, Boot0002, Boot0000
* (0x07=NV|BS|RT)
*/
/sys/firmware/efi/efivars/에서 변수를 삭제하면 NVRAM이 영구적으로 변경됩니다. 잘못된 변수 삭제는 시스템 부팅 불가를 초래할 수 있습니다. 특히 rm -rf /sys/firmware/efi/efivars/는 절대 실행하지 마세요 — 일부 시스템이 벽돌이 될 수 있습니다.
Secure Boot
Secure Boot는 UEFI 규격의 보안 기능으로, 펌웨어가 신뢰할 수 있는 키로 서명된 EFI 바이너리만 실행하도록 합니다. 부트킷과 루트킷을 방지하는 신뢰 체인(Chain of Trust)을 구축합니다.
키 계층 구조
Secure Boot 키 저장소는 역할이 다른 4개 축으로 이해하면 빠릅니다.
| 키/DB | 역할 | 실무 포인트 |
|---|---|---|
PK (Platform Key) | 플랫폼 소유자 키. Secure Boot 신뢰 체인의 최상위 | 일반적으로 1개만 존재하며 KEK 갱신 권한의 루트 |
KEK (Key Exchange Key) | db/dbx 업데이트 권한을 가진 키 | OEM/OS 벤더 키가 여기에 등록됨 |
db (Signature Database) | 허용된 서명/해시(Hash) 목록 | Windows CA, 3rd Party CA, 사용자 인증서 등이 위치 |
dbx (Forbidden Database) | 차단된 서명/해시 목록 | 취소된 인증서, 알려진 취약 바이너리 해시 저장 |
- EFI 바이너리의 서명/해시를 추출합니다.
dbx에 있으면 즉시 거부합니다.db에 있으면 허용합니다.- 둘 다 없으면 거부합니다.
Shim 부트로더
Shim은 Microsoft의 3rd Party UEFI CA로 서명된 미니 부트로더로, Linux 배포판의 Secure Boot를 가능하게 합니다:
- UEFI 펌웨어가
shimx64.efi서명을 검증합니다. shimx64.efi가 배포판 키 또는 MOK로grubx64.efi를 검증합니다.grubx64.efi가 커널 이미지를 로드하고, 정책에 따라 서명을 검증합니다.- 커널 부팅 후 lockdown 정책이 연동되며 모듈 서명 강제가 적용됩니다.
# Secure Boot 상태 확인
$ mokutil --sb-state
SecureBoot enabled
# Secure Boot 키 확인
$ mokutil --pk # Platform Key
$ mokutil --kek # Key Exchange Key
$ mokutil --db # Allowed Signature Database
$ mokutil --dbx # Forbidden Signature Database
MOK (Machine Owner Key)
MOK는 shim이 관리하는 추가 키 저장소로, 사용자가 자체 서명한 커널이나 모듈을 Secure Boot 환경에서 사용할 수 있게 합니다:
- 사용자 키 쌍을 생성합니다.
mokutil --import로 등록 요청을 넣습니다.- 재부팅 후 MokManager에서 등록을 승인합니다.
- 등록된 키로 모듈을 서명하고 로드합니다.
# MOK 키 생성 및 등록
# 1. 키 쌍 생성
$ openssl req -new -x509 -newkey rsa:2048 \
-keyout MOK.priv -outform DER -out MOK.der \
-nodes -days 36500 \
-subj "/CN=My Module Signing Key/"
# 2. MOK 등록 요청 (재부팅 시 MokManager에서 확인)
$ mokutil --import MOK.der
# 비밀번호 입력 (재부팅 시 동일 비밀번호 필요)
# 3. 재부팅 → MokManager 화면에서 키 등록 확인
# 4. 커널 모듈 서명
$ /usr/src/kernels/$(uname -r)/scripts/sign-file \
sha256 MOK.priv MOK.der my_module.ko
# MOK 목록 확인
$ mokutil --list-enrolled
# MOK 삭제
$ mokutil --delete MOK.der
Kernel Lockdown 모드
Secure Boot가 활성화되면 커널은 자동으로 lockdown 모드에 진입하여, 커널 무결성을 우회할 수 있는 기능을 제한합니다:
| 모드 | 주요 제한 |
|---|---|
integrity (기본) | /dev/mem, /dev/kmem, /dev/port 쓰기/접근 제한, 비서명 kexec_load() 금지, 모듈 서명 강제, ACPI override 차단 |
confidentiality | integrity의 제한 + /proc/kcore, perf, tracefs, BPF 접근 추가 제한 |
# lockdown 상태 확인
$ cat /sys/kernel/security/lockdown
[none] integrity confidentiality
# dmesg에서 lockdown 메시지 확인
$ dmesg | grep -i lockdown
Lockdown: Kernel is locked down from EFI Secure Boot;
see man kernel_lockdown.7
Linux EFI Stub
EFI Stub은 리눅스 커널이 직접 UEFI 애플리케이션으로 동작할 수 있게 하는 기능입니다. GRUB 같은 별도 부트로더 없이 UEFI가 직접 커널을 실행합니다.
- CONFIG_EFI_STUB=y 활성화 시:
- bzImage/vmlinuz에 PE32+ 헤더가 포함됨
- → UEFI 펌웨어가 EFI 애플리케이션으로 인식/실행 가능
- 장점:
- GRUB 없이 직접 부팅 → 부팅 시간 단축
- 공격 표면 감소 (부트로더 취약점(Vulnerability) 제거)
- 간결한 부팅 체인
- 진입점(Entry Point): drivers/firmware/efi/libstub/
EFI Stub 부팅 흐름
/* drivers/firmware/efi/libstub/ — EFI Stub 실행 순서 */
/* 1. UEFI가 커널 PE 이미지를 메모리에 로드 */
/* 2. efi_pe_entry() 진입 (x86) 또는 efi_entry() (ARM64) */
efi_status_t __efiapi efi_pe_entry(
efi_handle_t handle,
efi_system_table_t *sys_table_arg)
{
/* 3. System Table 포인터 저장 */
efi_system_table = sys_table_arg;
/* 4. 커널 커맨드 라인 처리 */
efi_handle_cmdline(image, &cmdline_ptr);
/* 5. initrd 로드 (LINUX_EFI_INITRD_MEDIA_GUID 또는 파일) */
efi_load_initrd(image, ...);
/* 6. 커널 이미지 재배치 (KASLR 적용) */
efi_relocate_kernel(...);
/* 7. GOP(Graphics Output Protocol)에서 프레임버퍼 정보 획득 */
efi_setup_gop(...);
/* 8. EFI Memory Map 획득 */
status = efi_get_memory_map(&map);
/* 9. ExitBootServices() 호출 */
efi_exit_boot_services(handle, &map);
/* 10. 일반 커널 부팅 경로로 진입 */
/* → startup_32/startup_64 (x86) */
/* → primary_entry (ARM64) */
}
EFI Stub 직접 부팅 설정
# EFI Stub 직접 부팅 설정
# 1. 커널에 CONFIG_EFI_STUB=y 확인
$ grep EFI_STUB /boot/config-$(uname -r)
CONFIG_EFI_STUB=y
# 2. 커널과 initramfs를 ESP에 복사
$ cp /boot/vmlinuz-6.x /boot/efi/
$ cp /boot/initramfs-6.x.img /boot/efi/
# 3. efibootmgr로 부팅 항목 등록
$ efibootmgr -c -d /dev/nvme0n1 -p 1 \
-L "Linux EFI Stub" \
-l '\vmlinuz-6.x' \
-u 'root=UUID=xxxx-xxxx ro quiet \
initrd=\initramfs-6.x.img'
# 또는 systemd-boot 사용 (loader.conf + entries/)
# /boot/efi/loader/entries/linux.conf:
title Linux 6.x
linux /vmlinuz-6.x
initrd /initramfs-6.x.img
options root=UUID=xxxx-xxxx ro quiet
EFI Memory Map
UEFI 펌웨어가 GetMemoryMap()을 통해 제공하는 물리 메모리 맵은 커널의 메모리 관리(Memory Management) 초기화에 핵심입니다. Legacy BIOS의 E820 맵을 대체합니다.
EFI 메모리 타입
| 타입 | 값 | 설명 | OS 사용 |
|---|---|---|---|
EfiReservedMemoryType | 0 | 사용 불가 (펌웨어 예약) | 접근 금지 |
EfiLoaderCode | 1 | 부트로더 코드 | ExitBS 후 사용 가능 |
EfiLoaderData | 2 | 부트로더 데이터 | ExitBS 후 사용 가능 |
EfiBootServicesCode | 3 | Boot Services 코드 | ExitBS 후 사용 가능 |
EfiBootServicesData | 4 | Boot Services 데이터 | ExitBS 후 사용 가능 |
EfiRuntimeServicesCode | 5 | Runtime Services 코드 | 보존 필수 |
EfiRuntimeServicesData | 6 | Runtime Services 데이터 | 보존 필수 |
EfiConventionalMemory | 7 | 사용 가능한 일반 메모리 | 자유 사용 |
EfiUnusableMemory | 8 | 오류 있는 메모리 | 접근 금지 |
EfiACPIReclaimMemory | 9 | ACPI 테이블 메모리 | ACPI 파싱 후 회수 가능 |
EfiACPINVS | 10 | ACPI NVS 메모리 | 보존 필수 |
EfiMemoryMappedIO | 11 | MMIO 영역 | 디바이스 I/O용 |
EfiPersistentMemory | 14 | 비휘발성 메모리 (NVDIMM) | DAX 사용 가능 |
# dmesg에서 EFI 메모리 맵 확인
$ dmesg | grep "efi: mem"
efi: mem00: [Conventional Memory| | | | | | | |WB|WT|WC|UC] range=[0x0000000000000000-0x000000000009ffff] (0MB)
efi: mem01: [Reserved | | | | | | | |WB|WT|WC|UC] range=[0x000000000009f000-0x00000000000fffff] (0MB)
efi: mem02: [Loader Data | | | | | | | |WB|WT|WC|UC] range=[0x0000000000100000-0x0000000001ffffff] (31MB)
efi: mem10: [Runtime Data |RUN| | | | | | |WB|WT|WC|UC] range=[0x000000007b000000-0x000000007b0fffff] (1MB)
# RUN = Runtime Services 영역 (커널이 보존해야 함)
# 속성 플래그:
# WB = Write-Back 캐싱
# WT = Write-Through
# WC = Write-Combining
# UC = Uncacheable
# WP = Write-Protected
# RP = Read-Protected
# XP = Execute-Protected
# RUN = Runtime (SetVirtualAddressMap 매핑 필요)
SetVirtualAddressMap()
/* arch/x86/platform/efi/efi.c */
/*
* ExitBootServices() 후, Runtime Services 코드/데이터는
* 물리 주소에 매핑된 상태입니다.
*
* 커널은 SetVirtualAddressMap()을 호출하여
* Runtime Services 영역을 커널 가상 주소 공간에 매핑합니다.
* 이후 펌웨어의 RT 함수들은 가상 주소를 사용합니다.
*
* 이 호출은 부팅 중 한 번만 가능하며,
* 호출 후에는 되돌릴 수 없습니다.
*/
static void __init efi_map_regions(void)
{
/* EFI 메모리 맵의 Runtime 영역만 가상 주소 매핑 */
for_each_efi_memory_desc(md) {
if (!(md->attribute & EFI_MEMORY_RUNTIME))
continue;
/* ioremap 또는 memremap으로 매핑 */
efi_map_region(md);
}
}
/* Runtime Services 가상 주소 설정 */
efi.runtime->SetVirtualAddressMap(
map_size, desc_size, desc_ver, virtual_map);
커널 UEFI 구현
소스 트리
| 경로 | 설명 |
|---|---|
drivers/firmware/efi/ | UEFI 코어 — Runtime Services, efivars, 시스템 테이블 |
drivers/firmware/efi/libstub/ | EFI Stub 부트 코드 (아키텍처 독립) |
arch/x86/boot/compressed/efi_mixed.S | x86 32/64-bit UEFI mixed 모드 진입 |
arch/x86/platform/efi/ | x86 UEFI 플랫폼 코드 (메모리 맵(Memory Map), 초기화) |
arch/arm64/kernel/efi.c | ARM64 UEFI 지원 |
include/linux/efi.h | UEFI 자료구조, 프로토콜 GUID, 매크로(Macro) |
fs/efivarfs/ | efivarfs 파일시스템 구현 |
security/integrity/platform_certs/ | Secure Boot 인증서 처리 |
관련 CONFIG 옵션
# 일반적인 x86_64 UEFI Linux 환경에서 권장
CONFIG_EFI=y # UEFI 런타임 서비스 지원
CONFIG_EFI_STUB=y # EFI Stub (직접 부팅)
CONFIG_EFI_VARS=y # UEFI 변수 접근 (/sys/firmware/efi/)
CONFIG_EFIVAR_FS=y # efivarfs 파일시스템
# Secure Boot
CONFIG_EFI_SECURE_BOOT_SIG_ENFORCE=y # Secure Boot 서명 강제
CONFIG_MODULE_SIG=y # 모듈 서명 지원
CONFIG_MODULE_SIG_FORCE=y # 서명 없는 모듈 거부
CONFIG_SECURITY_LOCKDOWN_LSM=y # Lockdown LSM
# 기타
CONFIG_EFI_MIXED=y # 32-bit UEFI + 64-bit 커널 (x86)
CONFIG_EFI_CAPSULE_LOADER=m # 펌웨어 업데이트 캡슐
CONFIG_EFI_RCI2_TABLE=y # RCI2 테이블 지원
CONFIG_EFI_ESRT=y # EFI System Resource Table
CONFIG_FB_EFI=y # EFI 프레임버퍼
CONFIG_EFI_EARLYCON=y # EFI 초기 콘솔
캡슐 업데이트 (Firmware Update)
UEFI 캡슐 업데이트는 OS에서 펌웨어(BIOS)를 업데이트하는 표준 메커니즘입니다. Linux에서는 fwupd 서비스가 이를 활용합니다.
/*
* 캡슐 업데이트 흐름:
*
* 1. OS가 UpdateCapsule() Runtime Service 호출
* 또는 EFI 변수에 캡슐 위치 기록
* 2. 시스템 재부팅
* 3. 펌웨어가 캡슐을 감지하고 업데이트 수행
* 4. 업데이트 결과를 EFI 변수에 기록
* 5. OS 부팅 후 결과 확인
*/
# fwupd를 통한 펌웨어 업데이트
$ fwupdmgr get-devices # 업데이트 가능 장치 목록
$ fwupdmgr get-updates # 사용 가능한 업데이트 확인
$ fwupdmgr update # 업데이트 적용 (재부팅 필요)
# ESRT (EFI System Resource Table) 확인
$ cat /sys/firmware/efi/esrt/entries/entry0/fw_version
$ cat /sys/firmware/efi/esrt/entries/entry0/last_attempt_status
# 0 = 성공, 1 = 리소스 부족, 2 = 잘못된 형식 ...
# 수동 캡슐 업데이트 (커널 인터페이스)
# CONFIG_EFI_CAPSULE_LOADER=m 필요
$ modprobe efi_capsule_loader
$ cat firmware.cap > /dev/efi_capsule_loader
# 재부팅 시 펌웨어가 캡슐 적용
ACPI와 UEFI의 관계
ACPI(Advanced Configuration and Power Interface) 테이블은 UEFI 시스템에서 EFI Configuration Table을 통해 커널에 전달됩니다.
/* UEFI System Table → Configuration Table에서 ACPI 테이블 위치 */
/*
* UEFI System Table
* └── ConfigurationTable[]
* ├── {EFI_ACPI_20_TABLE_GUID, XSDP 주소} ← ACPI 2.0+
* ├── {EFI_ACPI_TABLE_GUID, RSDP 주소} ← ACPI 1.0
* ├── {SMBIOS_TABLE_GUID, SMBIOS 주소}
* ├── {EFI_MEMATTR_TABLE_GUID, 주소}
* └── ...
*
* Legacy BIOS에서는:
* RSDP를 0xE0000-0xFFFFF 범위에서 검색
*
* UEFI에서는:
* Configuration Table에서 GUID로 정확히 찾음
*/
# ACPI 테이블 확인
$ dmesg | grep ACPI
ACPI: RSDP 0x000000007BF7E014 000024 (v02 DELL )
ACPI: XSDT 0x000000007BF7D0E8 0000CC (v01 DELL ...)
ACPI: FACP 0x000000007BF49000 00010C (v06 DELL ...)
ACPI: DSDT 0x000000007BF0A000 03D2B3 (v02 DELL ...)
# EFI 시스템 테이블 정보
$ dmesg | grep "efi:"
efi: EFI v2.70 by American Megatrends
efi: ACPI 2.0=0x7bf7e014 ACPI=0x7bf7e000 SMBIOS=0x7bf9f000
SMBIOS 3.0=0x7bf9e000 ESRT=0x7ac4c018 MOKvar=0x7ac1e000
UEFI 디버깅(Debugging)
dmesg 확인
# EFI 관련 커널 메시지
$ dmesg | grep -i efi
# EFI 부팅 여부 확인
$ [ -d /sys/firmware/efi ] && echo "UEFI" || echo "BIOS"
# EFI 런타임 서비스 상태
$ dmesg | grep "EFI runtime"
efi: EFI Runtime Services are enabled
# EFI 런타임 서비스 비활성화 시 (efi=noruntime)
efi: EFI Runtime Services are disabled!
# Secure Boot 상태
$ dmesg | grep -i "secure boot"
secureboot: Secure boot enabled
# EFI Stub 메시지 (직접 부팅 시)
$ dmesg | grep "EFI stub"
EFI stub: Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID
EFI stub: Measured initrd data into PCR 9
EFI stub: KASLR enabled
EFI stub: Using DTB from configuration table
EFI stub: Exiting boot services...
디버깅 도구
# efivar — UEFI 변수 직접 조작
$ efivar -l # 모든 변수 나열
$ efivar -p -n 8be4df61-93ca-11d2-aa0d-00e098032b8c-SecureBoot
# OVMF — QEMU용 UEFI 펌웨어 (개발/디버깅)
# 경로와 파일명은 배포판마다 다를 수 있습니다.
$ OVMF_CODE=/path/to/OVMF_CODE.fd
$ OVMF_VARS_TEMPLATE=/path/to/OVMF_VARS.fd
$ cp "${OVMF_VARS_TEMPLATE}" ./OVMF_VARS.debug.fd
$ qemu-system-x86_64 \
-machine q35,smm=on \
-drive if=pflash,format=raw,readonly=on,file=${OVMF_CODE} \
-drive if=pflash,format=raw,file=./OVMF_VARS.debug.fd \
-drive file=disk.img,format=qcow2 \
-debugcon file:debug.log -global isa-debugcon.iobase=0x402
# OVMF 디버그 출력이 debug.log에 기록됨
# EFI Shell에서 디버깅
Shell> map -b # 블록 디바이스 매핑
Shell> bcfg boot dump # 부팅 항목 덤프
Shell> dmpstore BootOrder # 변수 내용 덤프
Shell> memmap # EFI 메모리 맵
Shell> drivers # 로드된 드라이버 목록
Shell> devtree # 디바이스 트리
# 커널 EFI 디버그 파라미터
# efi=debug → EFI 관련 상세 로그
# efi=noruntime → Runtime Services 비활성화
# efi=old_map → 이전 방식 메모리 매핑 (호환성)
# efi=nochunk → 메모리 맵 분할 비활성화
VARS.fd는 VM마다 별도 사본을 써야 하며, Secure Boot나 TPM 2.0까지 포함한 재현 가능한 실습은 QEMU의 OVMF + Secure Boot + swtpm 실습과 libvirt의 UEFI Secure Boot + TPM 2.0 XML 예제를 함께 보는 편이 안전하다.
자주 발생하는 UEFI 문제
| 증상 | 원인 | 해결 |
|---|---|---|
| 부팅 시 "Secure Boot violation" | 서명되지 않은 부트로더/커널 | MOK 등록 또는 Secure Boot 비활성화 |
| efibootmgr 명령 실패 | efivarfs 미마운트 또는 권한 부족 | mount -t efivarfs efivarfs /sys/firmware/efi/efivars/ |
| Runtime Services 크래시 | 펌웨어 버그, 메모리 매핑(Mapping) 문제 | efi=noruntime 또는 펌웨어 업데이트 |
| ESP를 찾지 못함 | GPT 파티션 타입 잘못 설정 | gdisk로 파티션 타입 EF00 확인 |
| UEFI Shell로 빠짐 | 부팅 항목 경로 오류 | \EFI\BOOT\BOOTX64.EFI 존재 확인 |
| 32-bit UEFI + 64-bit 커널 | Mixed mode 미지원 | CONFIG_EFI_MIXED=y 또는 32-bit 커널 |
| NVRAM 가득 참 | 변수 축적 (오류 로그 등) | 불필요 변수 삭제, 펌웨어 NVRAM 초기화 |
-DDEBUG_ON_SERIAL_PORT)를 사용하면 펌웨어 내부 동작을 시리얼 콘솔로 추적할 수 있습니다.
UEFI 프로토콜 모델
UEFI의 핵심 설계 철학은 프로토콜(Protocol) 기반 추상화입니다. 모든 하드웨어 접근과 서비스는 GUID로 식별되는 프로토콜 인터페이스를 통해 이루어집니다. 이는 객체지향의 인터페이스 패턴과 유사합니다.
프로토콜 아키텍처
/* UEFI 프로토콜 = GUID + 함수 포인터 테이블 (C 인터페이스)
*
* 핵심 개념:
* Handle — 프로토콜 인스턴스를 담는 컨테이너 (불투명 포인터)
* Protocol — GUID로 식별되는 함수 포인터 구조체
* Handle Database — 모든 핸들-프로토콜 매핑을 관리하는 전역 데이터베이스
*
* Handle ─┬── Protocol A (GUID_A → 함수 테이블 A)
* ├── Protocol B (GUID_B → 함수 테이블 B)
* └── Protocol C (GUID_C → 함수 테이블 C)
*/
/* 예: Block I/O Protocol — 디스크 섹터 읽기/쓰기 */
typedef struct _EFI_BLOCK_IO_PROTOCOL {
UINT64 Revision;
EFI_BLOCK_IO_MEDIA *Media; /* 디스크 정보 */
EFI_BLOCK_RESET Reset; /* 디바이스 리셋 */
EFI_BLOCK_READ ReadBlocks; /* 섹터 읽기 */
EFI_BLOCK_WRITE WriteBlocks; /* 섹터 쓰기 */
EFI_BLOCK_FLUSH FlushBlocks; /* 캐시 플러시 */
} EFI_BLOCK_IO_PROTOCOL;
/* GUID: 964E5B21-6459-11D2-8E39-00A0C969723B */
/* 프로토콜 사용 흐름 */
EFI_BLOCK_IO_PROTOCOL *BlockIo;
EFI_HANDLE *HandleBuffer;
UINTN HandleCount;
/* 1. Block I/O 프로토콜을 제공하는 모든 핸들 검색 */
gBS->LocateHandleBuffer(
ByProtocol,
&gEfiBlockIoProtocolGuid, /* 찾을 프로토콜 GUID */
NULL,
&HandleCount, /* 발견된 핸들 수 */
&HandleBuffer /* 핸들 배열 */
);
/* 2. 핸들에서 프로토콜 인터페이스 획득 */
gBS->HandleProtocol(
HandleBuffer[0], /* 첫 번째 디스크 핸들 */
&gEfiBlockIoProtocolGuid,
(VOID **)&BlockIo /* 프로토콜 인터페이스 반환 */
);
/* 3. 프로토콜 함수 호출 */
BlockIo->ReadBlocks(BlockIo, MediaId, Lba, BufferSize, Buffer);
Handle Database와 프로토콜 검색
/* Handle Database 구조 (EDK II 구현)
*
* gHandleList (이중 연결 리스트)
* │
* ├── IHANDLE {
* │ Signature: 'hndl'
* │ AllHandles: (리스트 링크)
* │ Protocols: ─── PROTOCOL_INTERFACE {
* │ Key: uint64 Protocol: → PROTOCOL_ENTRY (GUID)
* │ } Interface: → 프로토콜 함수 테이블
* │ OpenList: → 열린 프로토콜 추적
* │ }
* ├── IHANDLE { ... }
* └── IHANDLE { ... }
*
* 검색 방법 3가지:
* ByProtocol — 특정 GUID를 제공하는 모든 핸들
* AllHandles — 모든 핸들
* ByRegisterNotify — 프로토콜 등록 이벤트 통지
*/
/* 주요 Boot Services 프로토콜 조작 함수 */
gBS->InstallProtocolInterface( /* 핸들에 프로토콜 설치 */
&Handle, &ProtocolGuid, EFI_NATIVE_INTERFACE, Interface);
gBS->OpenProtocol( /* 프로토콜 열기 (참조 추적) */
Handle, &ProtocolGuid, &Interface,
AgentHandle, ControllerHandle, Attributes);
/* Attributes:
* BY_HANDLE_PROTOCOL — 단순 조회
* GET_PROTOCOL — 독점 접근
* BY_DRIVER — 드라이버 바인딩
* BY_CHILD_CONTROLLER — 자식 컨트롤러 관리
* EXCLUSIVE — 배타적 접근 (기존 BY_DRIVER 해제) */
gBS->InstallMultipleProtocolInterfaces( /* 여러 프로토콜 원자적 설치 */
&Handle,
&gEfiBlockIoProtocolGuid, &BlockIo,
&gEfiDiskInfoProtocolGuid, &DiskInfo,
NULL);
gBS->LocateProtocol( /* 첫 번째 일치 프로토콜 반환 */
&ProtocolGuid, Registration, &Interface);
UEFI Driver Binding Protocol
/* UEFI Driver Model — 핫플러그/드라이버 재사용 지원
*
* 모든 UEFI 드라이버는 Driver Binding Protocol을 구현:
* Supported() — 이 컨트롤러를 지원하는지 검사
* Start() — 드라이버 바인딩 (프로토콜 설치)
* Stop() — 드라이버 분리 (프로토콜 제거)
*/
typedef struct _EFI_DRIVER_BINDING_PROTOCOL {
EFI_DRIVER_BINDING_SUPPORTED Supported;
EFI_DRIVER_BINDING_START Start;
EFI_DRIVER_BINDING_STOP Stop;
UINT32 Version;
EFI_HANDLE ImageHandle;
EFI_HANDLE DriverBindingHandle;
} EFI_DRIVER_BINDING_PROTOCOL;
/* 드라이버 디스패치 흐름:
*
* 1. DXE Dispatcher가 FV(Firmware Volume)에서 드라이버 이미지 로드
* 2. 드라이버 EntryPoint() 실행 → Driver Binding Protocol 등록
* 3. BDS 또는 ConnectController()가 컨트롤러 연결 시도:
* a. 등록된 모든 Driver Binding의 Supported() 호출
* b. TRUE 반환한 드라이버의 Start() 호출
* c. Start()에서 하위 프로토콜 소비 + 상위 프로토콜 생산
*
* 예: AHCI 드라이버
* Supported(): PCI I/O Protocol → Class Code 0x0106(AHCI) 확인
* Start(): PCI I/O 소비 → Block I/O Protocol 생산
*
* 드라이버 스택:
* PCI Host Bridge → PCI Bus → AHCI Controller → AHCI Bus → Disk
* 각 계층이 하위 프로토콜을 소비하고 상위 프로토콜을 생산 */
핵심 UEFI 프로토콜 목록
| 프로토콜 | 용도 | 커널 관련성 |
|---|---|---|
EFI_BLOCK_IO_PROTOCOL | 블록 디바이스 섹터 I/O | EFI Stub 커널/initrd 로드 |
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL | FAT32 파일 접근 | ESP에서 파일 읽기 |
EFI_GRAPHICS_OUTPUT_PROTOCOL | 프레임버퍼/해상도 제어 | efifb/simplefb 드라이버 |
EFI_PCI_IO_PROTOCOL | PCI 장치 접근 | 부팅 전 PCI 장치 초기화 |
EFI_SIMPLE_NETWORK_PROTOCOL | 네트워크 인터페이스 | PXE/HTTP Boot |
EFI_LOAD_FILE2_PROTOCOL | initrd 로드 (커널 5.7+) | EFI Stub initrd 메커니즘 |
EFI_DEVICE_PATH_PROTOCOL | 장치 경로 표현 | 부팅 장치 식별 |
EFI_RNG_PROTOCOL | 하드웨어 난수 생성 | KASLR 엔트로피 소스 |
EFI_TCG2_PROTOCOL | TPM 2.0 접근 | Measured Boot PCR 측정 |
EFI_MEMORY_ATTRIBUTE_PROTOCOL | 메모리 속성 조회/설정 | W^X 메모리 보호 |
Device Path 프로토콜
Device Path는 UEFI에서 장치의 물리적/논리적 위치를 표현하는 데이터 구조입니다. 부팅 항목, 드라이버 바인딩, 파일 경로 등에 광범위하게 사용됩니다.
/* Device Path = 노드 체인 (연결 리스트처럼 순차적 노드 나열)
*
* 각 노드: Type(1B) + SubType(1B) + Length(2B) + Data(가변)
* 체인 끝: End Device Path 노드 (Type=0x7F, SubType=0xFF)
*/
typedef struct {
UINT8 Type; /* 노드 타입 */
UINT8 SubType; /* 하위 타입 */
UINT16 Length; /* 노드 전체 길이 (헤더 포함) */
} EFI_DEVICE_PATH_PROTOCOL;
/* 주요 노드 타입 */
0x01 Hardware Device Path
0x01 PCI /* Function, Device 번호 */
0x04 Memory Mapped
0x02 ACPI Device Path
0x01 ACPI /* HID, UID (예: PNP0A03=PCI Host Bridge) */
0x03 Messaging Device Path
0x01 ATAPI /* Primary/Secondary, Master/Slave */
0x02 SCSI /* Target, LUN */
0x05 USB /* Port, Interface */
0x0C MAC Address /* 네트워크 장치 */
0x12 SATA /* Port, Multiplier, LUN */
0x17 NVMe /* Namespace ID, EUI-64 */
0x18 URI /* HTTP Boot 대상 URI */
0x04 Media Device Path
0x01 Hard Drive /* 파티션 번호, GPT/MBR, 파티션 GUID */
0x04 File Path /* \EFI\fedora\shimx64.efi */
0x7F End Device Path
0xFF End Entire /* 체인 종료 */
/* 전형적인 NVMe 디스크 부팅 경로:
*
* PciRoot(0x0)/Pci(0x1D,0x0)/Pci(0x0,0x0)/NVMe(0x1,...)/
* HD(1,GPT,C12A7328-...)/File(\EFI\fedora\shimx64.efi)
*
* ← ACPI → ← PCI Bus → ← PCI Func → ← NVMe NS →
* ← GPT Partition 1 → ← ESP 내 파일 경로 →
*/
/* efibootmgr -v 출력의 Device Path 해석 예시:
*
* Boot0001* Fedora
* HD(1,GPT,abcd1234-...,0x800,0x100000)/File(\EFI\fedora\shimx64.efi)
* ^^ ^^^^^^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
* 파티션1 MBR/GPT 파티션GUID 시작LBA,크기 파일경로
*/
GOP (Graphics Output Protocol)
GOP는 UEFI에서 VGA BIOS(INT 10h)를 대체하는 그래픽 출력 인터페이스입니다. 커널은 ExitBootServices() 전에 GOP에서 프레임버퍼 정보를 획득하고, 이후 efifb/simplefb 드라이버가 이를 사용합니다.
/* GOP 프로토콜 구조 */
typedef struct _EFI_GRAPHICS_OUTPUT_PROTOCOL {
EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt; /* Block Transfer */
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode; /* 현재 모드 정보 */
} EFI_GRAPHICS_OUTPUT_PROTOCOL;
typedef struct {
UINT32 MaxMode;
UINT32 Mode; /* 현재 모드 번호 */
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
UINTN SizeOfInfo;
EFI_PHYSICAL_ADDRESS FrameBufferBase; /* 프레임버퍼 물리 주소 */
UINTN FrameBufferSize;
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
typedef struct {
UINT32 Version;
UINT32 HorizontalResolution; /* 예: 1920 */
UINT32 VerticalResolution; /* 예: 1080 */
EFI_GRAPHICS_PIXEL_FORMAT PixelFormat;
EFI_PIXEL_BITMASK PixelInformation; /* 커스텀 포맷 시 */
UINT32 PixelsPerScanLine; /* stride (패딩 포함) */
} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;
/* PixelFormat 종류 */
PixelRedGreenBlueReserved8BitPerColor /* RGBx (32bpp) */
PixelBlueGreenRedReserved8BitPerColor /* BGRx (32bpp) — 가장 일반적 */
PixelBitMask /* PixelInformation의 비트마스크 사용 */
PixelBltOnly /* 직접 프레임버퍼 접근 불가, Blt()만 사용 */
/* drivers/firmware/efi/libstub/gop.c — EFI Stub에서 GOP 정보 수집 */
static efi_status_t setup_gop(struct screen_info *si,
efi_guid_t *proto, unsigned long size,
void **handles)
{
/* 1. 모든 GOP 핸들 순회하여 프레임버퍼가 있는 것 선택 */
for (i = 0; i < nr_gops; i++) {
efi_gop_t *gop;
status = efi_bs_call(HandleProtocol, handles[i], proto, (void **)&gop);
/* PixelBltOnly는 직접 프레임버퍼 접근 불가 → 건너뜀 */
if (info->pixel_format == PIXEL_BLT_ONLY)
continue;
/* 가장 높은 해상도의 GOP 선택 */
if (pixels > best_pixels) {
best = gop;
best_pixels = pixels;
}
}
/* 2. screen_info 구조체에 프레임버퍼 정보 기록 */
si->orig_video_isVGA = VIDEO_TYPE_EFI;
si->lfb_base = fb_base; /* 프레임버퍼 물리 주소 */
si->lfb_size = fb_size;
si->lfb_width = info->horizontal_resolution;
si->lfb_height = info->vertical_resolution;
si->lfb_linelength = info->pixels_per_scan_line * 4;
si->lfb_depth = 32;
/* → boot_params.screen_info → efifb/simplefb 드라이버가 사용 */
}
/* GOP → 커널 디스플레이 드라이버 전달 경로:
*
* [UEFI 펌웨어]
* GOP.FrameBufferBase/Size + 해상도/픽셀 포맷
* ↓ (EFI Stub이 screen_info에 기록)
* [boot_params.screen_info]
* ↓
* [efifb 또는 simplefb 드라이버] → /dev/fb0
* ↓ (DRM 드라이버 로드 시)
* [i915/amdgpu/nouveau 등 DRM] → 네이티브 해상도 전환
*
* efifb는 모드 변경 불가 (ExitBootServices 후 GOP 사용 불가)
* → GOP가 설정한 해상도로 고정, DRM 드라이버가 로드될 때까지 유지 */
EFI 프레임버퍼 해상도 변경: 커널 파라미터 video=efifb:mode=1로 GOP 모드를 지정할 수 있으나, 이는 EFI Stub이 ExitBootServices() 호출 전에 GOP.SetMode()를 실행할 때만 유효합니다. GRUB에서는 gfxpayload=1920x1080으로 설정합니다.
SMM (System Management Mode)
SMM은 x86의 가장 높은 권한 수준(-2 ring)에서 실행되는 특수 CPU 모드입니다. OS와 하이퍼바이저(Hypervisor)도 SMM 내부를 볼 수 없으며, 펌웨어 전용 기능(전원 관리(Power Management), 하드웨어 에러 처리 등)에 사용됩니다.
/* SMM 아키텍처 개요
*
* 권한 레벨 (Ring):
* Ring 3 — 유저스페이스 프로세스
* Ring 0 — OS 커널
* Ring -1 — 하이퍼바이저 (VMX root)
* Ring -2 — SMM (System Management Mode) ← 가장 높은 권한
* Ring -3 — Intel ME / AMD PSP (별도 프로세서)
*
* SMM 진입:
* 1. SMI(System Management Interrupt) 발생
* - 하드웨어 SMI: 전원 버튼, 온도 알림, 하드웨어 에러
* - 소프트웨어 SMI: I/O 포트 0xB2 쓰기로 트리거
* 2. CPU가 현재 상태를 SMRAM에 저장 (State Save Area)
* 3. SMBASE(기본 0x30000)의 SMI 핸들러로 점프
* 4. 16-bit 리얼 모드와 유사한 환경에서 시작
* → 즉시 보호 모드/롱 모드로 전환 (현대 구현)
* 5. RSM(Resume) 명령으로 원래 상태 복귀
*/
/* SMRAM (System Management RAM)
*
* SMM 코드/데이터가 저장되는 보호 메모리 영역
* 일반적으로 TOLUD(Top of Low Usable DRAM) 근처에 위치
* 물리 주소 예: 0x7B000000 ~ 0x7BFFFFFF (16MB)
*
* OS에서 접근 불가:
* - SMRAM 영역은 메모리 컨트롤러가 하드웨어적으로 보호
* - SMRR(System Management Range Register) MSR로 캐시 속성 제어
* - D_OPEN/D_LOCK 비트가 설정되면 DMA로도 접근 불가
*
* State Save Area (RSM 복귀용):
* SMBASE + 0xFE00 ~ 0xFFFF 영역
* RAX~R15, RIP, RFLAGS, CR0~CR4, GDTR, IDTR, ... 모두 저장
*/
/* SMM에서 처리하는 주요 작업 */
1. ACPI SCI 에뮬레이션 /* 레거시 하드웨어 이벤트 → ACPI SCI 변환 */
2. 전원 버튼 처리 /* 전원 버튼 → S3/S4/S5 상태 전환 */
3. 온도 관리 (TCO) /* 과열 감지 → 스로틀링/셧다운 */
4. 에러 로깅 /* ECC 메모리 에러 → 로그 기록 */
5. UEFI 변수 쓰기 /* SPI Flash 접근 (일부 펌웨어) */
6. USB 레거시 에뮬레이션 /* USB 키보드 → PS/2 에뮬레이션 (부팅 시) */
/* 커널과 SMM의 상호작용 */
/* 1. SMI 트리거 (소프트웨어) — 포트 0xB2 쓰기 */
outb(smi_cmd, APM_CNT); /* APM_CNT = 0xB2, smi_cmd = ACPI FADT에서 획득 */
/* 커널의 acpi_enable()에서 ACPI 모드 전환 시 사용 */
/* 2. SMI 지연 시간 문제 — 커널 관점
*
* SMI가 발생하면 CPU가 "도둑맞음" — OS가 인지 못하는 시간 공백
* 실시간(RT) 워크로드에서 치명적:
* - SMI 처리 시간: 수십 us ~ 수 ms (펌웨어에 따라 다름)
* - 이 시간 동안 인터럽트 처리, 스케줄링 모두 정지
* - hwlatdetect/cyclictest로 탐지 가능
*
* 커널 대응:
* - CONFIG_X86_SME_PROTECTION (탐지)
* - tsc=reliable → SMI에 의한 TSC 왜곡 무시
* - msr 0x1AD: MSR_SMI_COUNT (SMI 발생 횟수)
*/
# SMI 카운트 확인 (Intel)
$ rdmsr 0x34 # MSR_SMI_COUNT — 총 SMI 발생 횟수
$ rdmsr 0x34 # 일정 시간 후 다시 읽어 증가분 확인
# SMI 지연 탐지
$ hwlatdetect --duration=60 --threshold=10
# 10us 이상 하드웨어 지연 탐지 (SMI가 주 원인)
# BIOS 설정에서 불필요한 SMI 소스 비활성화:
# - USB Legacy Support: Disabled
# - Serial Port SMI: Disabled
# - Periodic SMI: Disabled (가능한 경우)
SMM 보안 위협: SMM은 OS보다 높은 권한에서 실행되므로, SMRAM에 악성 코드가 주입되면 OS 수준에서 탐지/방어가 불가능합니다. 이를 SMM 루트킷이라 하며, Intel은 이를 방지하기 위해 STM(SMI Transfer Monitor), SMRR, SMM_BWP(BIOS Write Protection) 등의 하드웨어 보호 메커니즘을 제공합니다.
플랫폼 보안 기술
현대 시스템은 UEFI Secure Boot 외에도 하드웨어 수준의 부팅 무결성 보장 기술을 갖추고 있습니다. 이들은 커널보다 먼저 실행되어 펌웨어 자체의 무결성을 검증합니다.
Intel Boot Guard
/* Intel Boot Guard — CPU 마이크로코드 수준의 펌웨어 검증
*
* OEM이 공장에서 CPU FPF(Field Programmable Fuse)에 키 해시를 퓨징
* → 이후 해당 키로 서명되지 않은 펌웨어는 실행 불가 (영구적, 변경 불가)
*
* 두 가지 프로파일:
*
* 1. Verified Boot (VB):
* - IBB(Initial Boot Block) 무결성 검증
* - 검증 실패 시 → 부팅 정지 (shutdown)
* - ACM(Authenticated Code Module)이 검증 수행
*
* 2. Measured Boot (MB):
* - IBB 해시를 TPM PCR에 측정 (기록)
* - 검증 실패해도 부팅은 계속 (원격 증명으로 사후 판단)
* - PCR 0에 IBB 측정값 확장
*
* 실행 흐름:
* CPU 리셋 → 마이크로코드 → ACM(ROM에서 로드)
* → ACM이 FPF의 키 해시로 IBB 서명 검증
* → 성공 시 SEC → PEI → DXE → ... 정상 부팅
* → 실패 시 셧다운 또는 복구 모드
*
* 커널에서 확인:
* /sys/kernel/security/tpm0/binary_bios_measurements
* → PCR[0] = CRTM(Core Root of Trust for Measurement) 값
* Boot Guard가 기록한 IBB 측정값 포함
*/
AMD PSP (Platform Security Processor)
/* AMD PSP (Platform Security Processor) / AMD Secure Technology
*
* ARM Cortex-A5 기반 독립 보안 프로세서
* x86 CPU보다 먼저 실행되어 펌웨어 무결성 검증
*
* 부팅 순서:
* 1. PSP 부트 ROM 실행 (on-chip ROM)
* 2. PSP OS(Trustlet 환경) 로드
* 3. x86 코어 리셋 해제 전 DRAM 초기화 시작
* 4. AGESA(AMD Generic Encapsulated Software Architecture) 검증
* 5. BIOS 이미지 서명 검증
* 6. x86 코어 리셋 해제 → 일반 부팅 시작
*
* PSP 기능:
* - fTPM (firmware TPM 2.0): 별도 TPM 칩 없이 TPM 기능 제공
* - SEV (Secure Encrypted Virtualization): VM 메모리 암호화
* - SEV-SNP: VM 무결성까지 보장 (페이지 수준)
* - 메모리 암호화 키 관리 (SME/TSME)
*
* 커널에서의 상호작용:
* drivers/crypto/ccp/ — PSP/CCP(Cryptographic Coprocessor) 드라이버
* CONFIG_CRYPTO_DEV_CCP=y — PSP 디바이스 드라이버
* CONFIG_CRYPTO_DEV_SP_PSP=y — PSP 보안 프로세서 지원
* CONFIG_AMD_MEM_ENCRYPT=y — SME/SEV 메모리 암호화
*/
# PSP/fTPM 상태 확인
$ dmesg | grep -i psp
ccp 0000:04:00.2: psp enabled
ccp 0000:04:00.2: psp: tee enabled
$ dmesg | grep -i sev
SEV: Using SNP CPUID table, 31 entries present
SEV-SNP: Reserving start/end of RMP table
TPM과 Measured Boot
/* TPM (Trusted Platform Module) — 하드웨어 보안 칩
*
* 펌웨어 → 부트로더 → 커널 → 유저스페이스 측정 체인
* 각 단계에서 다음 단계를 해싱하여 TPM PCR에 확장(extend)
*
* PCR (Platform Configuration Register) 할당:
*/
PCR 0 /* CRTM, BIOS, Host Platform Extensions (Boot Guard 측정값) */
PCR 1 /* Host Platform Configuration (BIOS 설정) */
PCR 2 /* UEFI 드라이버, 옵션 ROM 코드 */
PCR 3 /* UEFI 드라이버, 옵션 ROM 설정 데이터 */
PCR 4 /* IPL (부트로더 코드) — shimx64.efi, grubx64.efi */
PCR 5 /* IPL 설정/데이터 — GPT 테이블, 부팅 시도 */
PCR 6 /* Host Platform Manufacturer 전용 */
PCR 7 /* Secure Boot Policy — PK, KEK, db, dbx, 검증 결과 */
PCR 8 /* 커널 커맨드 라인 (GRUB 측정) */
PCR 9 /* 커널 이미지, initrd (GRUB/EFI Stub 측정) */
PCR 10 /* Reserved (IMA 측정에 사용 가능) */
PCR 11 /* IMA (Integrity Measurement Architecture) 측정 값 */
PCR 14 /* shim MOK 측정 */
/* PCR 확장(extend) 연산:
* PCR_new = Hash(PCR_old || measurement)
*
* → PCR 값은 리셋 없이 초기화 불가 (단방향)
* → 모든 측정의 순서가 PCR에 누적되어 반영
* → 하나라도 다르면 최종 PCR 값이 달라짐 (변조 탐지)
*/
/* 커널에서 TPM 접근 */
$ cat /sys/class/tpm/tpm0/pcr-sha256/0 # PCR 0 값
$ cat /sys/class/tpm/tpm0/pcr-sha256/7 # Secure Boot 정책
# tpm2-tools로 PCR 읽기
$ tpm2_pcrread sha256:0,1,2,3,4,5,7
# 이벤트 로그 (각 측정의 상세 기록)
$ tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements
# 어떤 UEFI 이미지가 어떤 PCR에 측정되었는지 확인 가능
/* CONFIG_TCG_TPM=y — TPM 드라이버
* CONFIG_TCG_TIS=y — TIS(TPM Interface Specification)
* CONFIG_TCG_CRB=y — CRB(Command Response Buffer) — TPM 2.0 표준
* CONFIG_TCG_FTPM_TEE=y — fTPM (TEE 기반, AMD PSP 등)
* CONFIG_IMA=y — Integrity Measurement Architecture
* CONFIG_IMA_MEASURE_PCR_IDX=11 — IMA가 사용할 PCR 번호 */
Remote Attestation: TPM PCR 값을 원격 검증 서버에 전송하여 시스템의 부팅 무결성을 증명할 수 있습니다. tpm2_quote 명령으로 PCR에 대한 서명된 증거(quote)를 생성하고, 검증자가 이를 기대값과 비교합니다. 클라우드 환경에서 VM의 펌웨어/커널 무결성을 보장하는 데 활용됩니다(Azure Attestation, AWS Nitro Attestation 등).
Intel FSP (Firmware Support Package)
Intel FSP는 Intel이 바이너리 형태로 제공하는 실리콘 초기화 코드입니다. coreboot, EDK II 등의 오픈소스 펌웨어가 DRAM 초기화와 실리콘 설정을 위해 FSP를 호출합니다.
/* Intel FSP 아키텍처 (FSP 2.0+)
*
* FSP = 3개의 바이너리 컴포넌트:
*
* FSP-T (TempRamInit):
* - CAR(Cache-As-RAM) 설정
* - 호출 시점: 리셋 직후, DRAM 없음
* - 반환 후: 임시 스택/데이터용 메모리 사용 가능
*
* FSP-M (MemoryInit):
* - DRAM 컨트롤러 초기화 (가장 복잡)
* - SPD 읽기, 메모리 트레이닝, 타이밍 최적화
* - 호출 시점: CAR 사용 가능
* - 반환 후: 실제 DRAM 사용 가능
* - HOB(Hand-Off Block)로 메모리 맵 정보 반환
*
* FSP-S (SiliconInit):
* - CPU, PCH, PCI 등 실리콘 초기화
* - 인터럽트 라우팅, GPIO, USB, SATA 설정
* - 호출 시점: DRAM 사용 가능
* - 반환 후: 모든 실리콘 초기화 완료
*
* 호출자(coreboot/EDK II)가 UPD(Updatable Product Data)를 통해
* 플랫폼별 설정(메모리 채널, GPIO 핀맵 등)을 FSP에 전달
*/
/* FSP 호출 흐름 (coreboot 예시)
*
* bootblock:
* ├── CPU 리셋 벡터 → 최소 초기화
* └── FSP-T 호출 (TempRamInit) → CAR 설정
*
* romstage:
* ├── FSP-M 호출 (MemoryInit) → DRAM 초기화
* ├── HOB 파싱 → 메모리 맵 획득
* └── cbmem 초기화 (coreboot 메모리 테이블)
*
* ramstage:
* ├── FSP-S 호출 (SiliconInit) → 실리콘 초기화
* ├── PCI 열거, ACPI 테이블 생성
* └── payload 로드 (SeaBIOS/GRUB/LinuxBoot)
*
* FSP는 바이너리 블롭이므로 디버깅이 어려움
* → FSP 디버그 빌드 요청 또는 시리얼 로그로 추적 */
/* UPD (Updatable Product Data) 설정 예시 */
typedef struct {
UINT8 PcdFspDebugPrintErrorLevel; /* 디버그 레벨 */
UINT8 PcdSpdAddressTable[4]; /* DIMM SPD I2C 주소 */
UINT32 PcdTsegSize; /* SMRAM 크기 */
UINT8 PcdEnableHyperThreading; /* HT 활성화 */
UINT8 PcdEnableTurboMode; /* 터보 부스트 */
UINT32 PcdSerialIoUartDebugEnable; /* 시리얼 디버그 */
/* ... 수백 개의 설정 파라미터 ... */
} FSP_M_CONFIG;
ACPI AML과 DSDT/SSDT
ACPI 테이블 중 DSDT(Differentiated System Description Table)와 SSDT(Secondary System Description Table)는 AML(ACPI Machine Language) 바이트코드를 포함합니다. 커널의 ACPI 인터프리터(ACPICA)가 이를 실행하여 하드웨어 정보를 동적으로 조회합니다.
ACPI 테이블 계층
ASL과 AML — ACPI 프로그래밍
/* ASL (ACPI Source Language) → iasl 컴파일러 → AML (바이트코드)
*
* ASL은 ACPI 하드웨어 기술 언어:
* - 디바이스 트리 (Device/Scope/Name)
* - 메서드 (Method — 실행 가능한 AML 함수)
* - 연산 영역 (OperationRegion — MMIO/PIO/EC 접근)
* - 패키지, 버퍼, 정수 등 데이터 타입
*/
/* ASL 예시: PCI 호스트 브리지 + 디바이스 정의 */
DefinitionBlock ("dsdt.aml", "DSDT", 2, "VENDOR", "BOARD", 0x00000001)
{
Scope (\_SB) /* System Bus */
{
Device (PCI0) /* PCI 호스트 브리지 */
{
Name (_HID, EisaId("PNP0A08")) /* PCI Express Root */
Name (_CID, EisaId("PNP0A03")) /* PCI 호환 */
Name (_ADR, Zero)
/* _CRS: 현재 리소스 설정 반환 */
Method (_CRS, 0, Serialized)
{
Name (RBUF, ResourceTemplate()
{
WordBusNumber (,,, 0x00, 0xFF,,,) /* 버스 0~255 */
DWordMemory (,,, 0xF0000000, 0xFEFFFFFF,,,) /* MMIO */
WordIO (,,, 0x0000, 0xFFFF,,,) /* I/O 포트 */
})
Return (RBUF)
}
/* 내장 GPU */
Device (GFX0)
{
Name (_ADR, 0x00020000) /* Bus 0, Dev 2, Func 0 */
/* _DSM: Device-Specific Method (벤더별 기능) */
Method (_DSM, 4, Serialized)
{
If (Arg0 == ToUUID("3e5b41c6-...")) {
/* 백라이트 제어, 모드 전환 등 */
}
}
}
}
/* 전원 버튼 */
Device (PWRB) {
Name (_HID, EisaId("PNP0C0C"))
}
/* 노트북 덮개 */
Device (LID0) {
Name (_HID, EisaId("PNP0C0D"))
Method (_LID, 0) { /* 덮개 열림/닫힘 상태 */
Return (\_SB.PCI0.LPCB.EC0.LIDS) /* EC 레지스터 읽기 */
}
}
/* 배터리 */
Device (BAT0) {
Name (_HID, EisaId("PNP0C0A"))
Method (_BIF, 0) { /* 배터리 정보 (설계 용량, 전압 등) */ }
Method (_BST, 0) { /* 배터리 상태 (충전율, 전류 등) */ }
}
/* 열 관리 */
ThermalZone (TZ00) {
Method (_TMP, 0) { /* 현재 온도 (10분의 1 켈빈) */ }
Method (_PSV, 0) { Return (0x0E94) } /* Passive 임계값 (80°C) */
Method (_CRT, 0) { Return (0x0FA2) } /* Critical 임계값 (100°C) */
}
}
}
커널의 ACPI 처리
/* 커널의 ACPI 스택 구조
*
* drivers/acpi/
* ├── acpica/ — ACPICA (ACPI Component Architecture)
* │ ├── nsxfeval.c — AML 네임스페이스 평가
* │ ├── exoparg*.c — AML 오피코드 실행기
* │ ├── psparse.c — AML 파서
* │ └── uteval.c — 유틸리티 평가
* ├── scan.c — ACPI 디바이스 트리 스캔 → Linux 디바이스 등록
* ├── bus.c — ACPI 버스 드라이버
* ├── battery.c — 배터리 드라이버
* ├── thermal.c — 열 관리 드라이버
* ├── processor_*.c — CPU P-state/C-state 드라이버
* ├── ec.c — Embedded Controller 드라이버
* └── tables.c — ACPI 테이블 초기화/매핑
*
* ACPICA는 Intel이 제공하는 참조 구현으로,
* Linux/Windows/FreeBSD 등 여러 OS에서 공유하는 크로스플랫폼 코드
*/
/* 커널에서 AML 메서드 호출 예시 */
acpi_status status;
struct acpi_object_list arg_list;
union acpi_object args[1];
struct acpi_buffer return_buf = { ACPI_ALLOCATE_BUFFER, NULL };
args[0].type = ACPI_TYPE_INTEGER;
args[0].integer.value = brightness_level;
arg_list.count = 1;
arg_list.pointer = args;
/* _BCM(Set Brightness Level) 메서드 호출 */
status = acpi_evaluate_object(handle, "_BCM", &arg_list, NULL);
/* _BIF(Battery Information) 메서드 호출 */
status = acpi_evaluate_object(battery->device->handle,
"_BIF", NULL, &return_buf);
# ACPI 테이블 추출/디컴파일/분석
# 1. 시스템의 ACPI 테이블 추출
acpidump > acpi.dat # 모든 테이블 덤프
acpixtract -a acpi.dat # 개별 테이블 파일로 분리
# 2. DSDT 디컴파일 (AML → ASL)
iasl -d dsdt.dat # dsdt.dsl 생성 (읽을 수 있는 ASL 소스)
# 3. 커스텀 DSDT 오버라이드 (디버깅/버그 우회)
iasl -tc dsdt.dsl # 수정된 ASL → AML + C 헤더 컴파일
# initramfs에 포함: /kernel/firmware/acpi/dsdt.aml
# 커널 파라미터: acpi_table_override=force
# 4. SSDT 오버라이드 (런타임, 커널 4.6+ CONFIG_ACPI_TABLE_UPGRADE=y)
mkdir -p /lib/firmware/acpi
cp custom-ssdt.aml /lib/firmware/acpi/
# initramfs에 포함 → 부팅 시 자동 로드
# 5. ACPI 네임스페이스 탐색
ls /sys/firmware/acpi/tables/ # 원시 테이블 바이너리
ls /sys/bus/acpi/devices/ # ACPI 디바이스 목록
cat /sys/bus/acpi/devices/PNP0C0A:00/path # 배터리 디바이스 ACPI 경로
# 6. AML 런타임 디버깅
# CONFIG_ACPI_DEBUGGER=y 필요
echo "method \_SB.PCI0.GFX0._DSM" > /sys/module/acpi/parameters/debug_level
dmesg | grep ACPI # AML 실행 추적
ACPI 펌웨어 버그: DSDT/SSDT의 AML 코드 버그는 리눅스 커널에서 다양한 문제를 유발합니다 (배터리 상태 오류, 절전 복귀 실패, 팬 제어 오작동 등). 커널은 drivers/acpi/blacklist.c와 acpi_osi= 파라미터로 문제 있는 펌웨어를 우회합니다. acpi_osi="Windows 2022"로 Windows로 위장하면 일부 벤더 DSDT의 Linux 차별 분기를 우회할 수 있습니다.
UEFI 네트워크 부팅
UEFI 스펙은 펌웨어 수준의 네트워크 프로토콜 집합(IPv4/IPv6, UDP/TCP, TFTP, HTTP, TLS 관련 서비스)을 정의하여 PXE 및 HTTP Boot을 지원합니다. 다만 실제 펌웨어가 어디까지 구현하는지는 장비와 벤더별로 차이가 있습니다.
UEFI PXE Boot
/* UEFI PXE Boot 흐름
*
* 1. UEFI 네트워크 스택 초기화:
* SNP(Simple Network Protocol) → MNP → ARP → DHCP → PXE
*
* 2. DHCP 서버에서 TFTP 부팅 정보 획득:
* DHCPv4: next-server/siaddr + file 필드 또는 Option 66/67
* DHCPv6: Option 59 (OPT_BOOTFILE_URL) 기반 netboot6 경로 가능
*
* 3. TFTP로 부팅 파일 다운로드
* 4. EFI 바이너리 실행 (shimx64.efi → grubx64.efi → vmlinuz)
*
* Legacy PXE vs UEFI PXE:
* Legacy: INT 18h/19h → PXE ROM → TFTP → NBP(Network Bootstrap Program)
* UEFI PXE: SNP Protocol → DHCP → TFTP → EFI Application
* UEFI HTTP Boot: DHCP → HTTP/HTTPS URI → EFI Application
*
* UEFI PXE는 Secure Boot와 통합 가능:
* 서명된 shimx64.efi를 네트워크에서 로드 → 검증 → 실행
*/
/* 프로토콜 스택 계층:
*
* Application (GRUB, shim, OS loader)
* │
* EFI_PXE_BASE_CODE_PROTOCOL — PXE 클라이언트
* │
* EFI_MTFTP4_PROTOCOL — TFTP 파일 전송
* │
* EFI_UDP4/6_PROTOCOL — UDP 트랜스포트
* │
* EFI_IP4/6_PROTOCOL — IP 네트워크
* │
* EFI_ARP_PROTOCOL — 주소 해석
* │
* EFI_MANAGED_NETWORK_PROTOCOL — 프레임 수신/필터링
* │
* EFI_SIMPLE_NETWORK_PROTOCOL — NIC 드라이버
*/
HTTP Boot (UEFI 2.5+)
/* UEFI HTTP Boot — TFTP 대체, 더 빠르고 유연한 네트워크 부팅
*
* 장점:
* - HTTP/HTTPS 사용 → 기존 웹 인프라 활용 가능
* - TLS 지원 → 전송 중 암호화 (TFTP는 암호화 없음)
* - TCP 기반 → 대용량 이미지 안정적 전송
* - URL 기반 → 유연한 경로 지정
*
* 부팅 흐름:
* 1. DHCP에서 HTTP Boot URI 획득
* DHCPv4: vendor-class "HTTPClient" + BOOTP file 필드(또는 Option 67)에 URI
* DHCPv6: Option 59 (OPT_BOOTFILE_URL)
*
* 2. DNS로 서버 주소 해석
* 3. HTTP GET으로 EFI 바이너리 다운로드
* 4. (HTTPS 시) TLS 인증서 검증
* 5. EFI 바이너리 실행
*
* 예시 DHCP 설정 (ISC dhcpd):
*/
class "httpboot" {
match if substring (option vendor-class-identifier, 0, 10) = "HTTPClient";
filename "http://boot.example.com/efi/shimx64.efi";
}
/* 커널에서 HTTP Boot initrd 로드 (커널 5.7+):
*
* LINUX_EFI_INITRD_MEDIA_GUID를 통해 EFI Stub이 initrd를 로드
* systemd-boot이나 GRUB가 EFI_LOAD_FILE2_PROTOCOL로 initrd 제공
* → 부트로더가 HTTP로 initrd를 가져와 프로토콜을 통해 커널에 전달
*/
# HTTP Boot 서버 설정 (간단한 구성)
# 1. DHCP 서버 (dnsmasq 예시)
dnsmasq \
--dhcp-range=192.168.1.100,192.168.1.200 \
--dhcp-match=set:HTTPClient,option:vendor-class,HTTPClient \
--dhcp-boot=tag:HTTPClient,"http://192.168.1.1:8080/efi/shimx64.efi"
# 2. HTTP 서버 (nginx 예시)
server {
listen 8080;
root /srv/netboot;
# /srv/netboot/efi/shimx64.efi
# /srv/netboot/efi/grubx64.efi
# /srv/netboot/vmlinuz
# /srv/netboot/initramfs.img
}
# UEFI 펌웨어 설정에서:
# - Network Boot: Enabled
# - IPv4/IPv6 PXE Support: Enabled
# - HTTP Boot: Enabled (별도 옵션인 경우)
HTTPClient Vendor Class와 Boot URI를 사용합니다. 실무 상세는 PXE 문서의 HTTP Boot DHCP 규칙을 함께 보는 편이 정확합니다.
https://.../shimx64.efi를 사용한다고 해서 Secure Boot가 대체되는 것은 아니고, 반대로 Secure Boot가 켜져 있어도 펌웨어의 HTTPS/TLS 구현 품질은 별도 문제입니다.
| UEFI 네트워크 설치 대상 | 첫 EFI 이미지 | 그 다음 단계 | 대표 커널 인자 |
|---|---|---|---|
| Ubuntu 24.04 LTS | shimx64.efi 또는 grubx64.efi | GRUB가 live installer 커널과 initrd 로드 | autoinstall ds=nocloud-net;s=... |
| RHEL 10 계열 | shimx64.efi 또는 grubx64.efi | Anaconda installer 커널로 진입 | inst.repo=... + inst.ks=... |
| Debian 13 stable | grubx64.efi | Debian Installer 커널과 initrd 로드 | auto=true priority=critical url=... |
shimx64.efi나 grubx64.efi 같은 "첫 번째 EFI 애플리케이션"까지 전달하는 역할을 담당합니다. 실제 자동설치 성공 여부는 그 다음 단계에서 어떤 부트로더가 어떤 커널 인자를 넘기는지에 달려 있으므로, PXE/HTTP Boot 설정 문서와 배포판 설치 문서를 함께 관리하는 편이 좋습니다. 실무 예시는 PXE 문서의 Kickstart, Autoinstall, Preseed 예시를 참고하십시오.
펌웨어 업데이트 메커니즘
SPI Flash — 펌웨어 저장소
/* UEFI/BIOS 펌웨어는 SPI NOR Flash에 저장
*
* 물리적 구성:
* SPI Flash (보통 8~32MB)
* ├── Descriptor Region — Flash 레이아웃 정보 (4KB)
* ├── Intel ME/AMD PSP Region — 관리 엔진 펌웨어
* ├── GbE Region — 이더넷 NVM (MAC 주소 등)
* └── BIOS Region — UEFI 펌웨어 이미지
* ├── FV_RECOVERY — SEC + PEI (복구 부팅에 필수)
* ├── FV_MAIN — DXE 드라이버
* ├── FV_NVRAM — UEFI 변수 저장소
* └── Microcode — CPU 마이크로코드 패치
*
* SPI Flash 접근:
* - 부팅 시: CPU가 직접 접근 (메모리 매핑, 0xFF000000 근처)
* - OS 런타임: SPI 컨트롤러 레지스터 (BIOS_CNTL, PR 레지스터)
* - 보호: BIOS Write Enable(BWE) 비트, SPI Protected Ranges
*
* 커널 SPI Flash 접근:
* drivers/mtd/spi-nor/ — SPI NOR Flash 드라이버
* drivers/spi/spi-intel.c — Intel SPI 컨트롤러
* /dev/mtd0 — SPI Flash raw 접근 (주의: 위험!)
*/
# SPI Flash 정보 확인
$ dmesg | grep -i spi
intel-spi 0000:00:1f.5: BIOS locked, using software sequencing
intel-spi 0000:00:1f.5: Found chip with 16384 KiB total flash
# flashrom — SPI Flash 읽기/쓰기 도구 (오픈소스)
$ flashrom -p internal -r backup.rom # 현재 펌웨어 백업
$ flashrom -p internal --ifd -i bios -r bios.bin # BIOS 영역만
# 주의: 잘못된 쓰기는 시스템을 벽돌로 만들 수 있음
fwupd와 LVFS 생태계
/* fwupd — Linux Vendor Firmware Service 클라이언트
*
* 아키텍처:
* LVFS (Linux Vendor Firmware Service) ← 벤더가 펌웨어 업로드
* ↓ (cabinet 파일 = .cab, 내부에 capsule + metainfo.xml)
* fwupd 데몬 (D-Bus 서비스)
* ↓
* 다양한 업데이트 플러그인:
* ├── uefi-capsule — UEFI UpdateCapsule() RT Service
* ├── flashrom — SPI Flash 직접 쓰기
* ├── redfish — BMC/IPMI 기반 원격 업데이트
* ├── nvme — NVMe 펌웨어 업데이트
* ├── thunderbolt — Thunderbolt 컨트롤러
* ├── logitech-hidpp — Logitech 무선 장치
* └── ...
*
* UEFI Capsule 업데이트 상세 흐름:
* 1. fwupd가 capsule 파일을 ESP에 복사
* 2. EFI 변수 CapsuleUpdateData에 capsule 경로 설정
* 3. EFI 변수 OsIndications에 CAPSULE_UPDATE 비트 설정
* 4. 재부팅 → 펌웨어가 capsule 감지
* 5. 펌웨어 이미지 검증 (서명 확인)
* 6. SPI Flash에 새 이미지 쓰기
* 7. 결과를 CapsuleResultData 변수에 기록
* 8. 정상 부팅 → fwupd가 결과 확인
*/
# fwupd 업데이트 명령어
$ fwupdmgr get-devices # 업데이트 가능 장치 목록
$ fwupdmgr refresh # LVFS에서 메타데이터 갱신
$ fwupdmgr get-updates # 사용 가능한 업데이트 확인
$ fwupdmgr update # 모든 업데이트 적용
$ fwupdmgr get-history # 업데이트 이력
# ESRT (EFI System Resource Table) — 업데이트 가능 펌웨어 목록
$ ls /sys/firmware/efi/esrt/entries/
entry0/ entry1/ entry2/
$ cat /sys/firmware/efi/esrt/entries/entry0/fw_type # 0=Unknown, 1=System, 2=Device, 3=Driver
$ cat /sys/firmware/efi/esrt/entries/entry0/fw_version # 현재 버전
$ cat /sys/firmware/efi/esrt/entries/entry0/capsule_flags
$ cat /sys/firmware/efi/esrt/entries/entry0/last_attempt_status # 마지막 업데이트 결과
펌웨어 관련 커널 소스 트리
| 경로 | 설명 |
|---|---|
drivers/firmware/efi/ | UEFI 코어 — Runtime Services, efivars, 시스템 테이블, 캡슐 |
drivers/firmware/efi/libstub/ | EFI Stub 부트 코드 (아키텍처 독립 + 아키텍처별) |
drivers/firmware/dmi_scan.c | SMBIOS/DMI 테이블 파싱 |
drivers/firmware/memmap.c | 펌웨어 메모리 맵 sysfs 인터페이스 |
drivers/acpi/ | ACPI 서브시스템 전체 (ACPICA + 드라이버) |
drivers/acpi/acpica/ | ACPICA AML 인터프리터 (Intel 참조 구현) |
drivers/acpi/tables.c | ACPI 테이블 초기화, 오버라이드 |
arch/x86/platform/efi/ | x86 UEFI 플랫폼 초기화, 메모리 맵 |
arch/x86/kernel/acpi/ | x86 ACPI 통합 (SMP 부팅, 슬립(Sleep) 등) |
fs/efivarfs/ | efivarfs 파일시스템 구현 |
security/integrity/platform_certs/ | Secure Boot 인증서 → 커널 키링(Keyring) 로드 |
security/lockdown/ | Lockdown LSM (Secure Boot 연동) |
drivers/crypto/ccp/ | AMD PSP/CCP 드라이버 |
drivers/char/tpm/ | TPM 드라이버 (TIS, CRB, fTPM) |
drivers/mtd/spi-nor/ | SPI NOR Flash 드라이버 |
drivers/spi/spi-intel.c | Intel SPI 컨트롤러 (Flash 접근) |
UEFI 운영 점검 플레이북
UEFI 환경에서의 장애는 NVRAM 변수, 부트 엔트리, ESP 파일 상태가 얽혀서 발생하는 경우가 많습니다. 아래 점검 순서를 따르면 복구 시간을 크게 줄일 수 있습니다.
| 점검 항목 | 확인 명령 | 정상 기준 |
|---|---|---|
| UEFI 부팅 모드 | test -d /sys/firmware/efi |
디렉토리 존재 |
| 부트 엔트리 | efibootmgr -v |
BootOrder와 실제 EFI 경로 일치 |
| ESP 마운트(Mount) | findmnt /boot/efi |
FAT32 ESP 정상 마운트 |
| efivarfs 상태 | mount | grep efivarfs |
읽기/쓰기 가능 상태 |
# UEFI 변수/부트엔트리 점검
sudo efibootmgr -v
sudo ls /sys/firmware/efi/efivars | head
# ESP 파일 무결성 점검
sudo fsck.vfat -n /dev/nvme0n1p1
sudo ls /boot/efi/EFI
# 부트 엔트리 재생성 예시
sudo efibootmgr -c -d /dev/nvme0n1 -p 1 \
-L "Linux" -l "\\EFI\\linux\\grubx64.efi"
PI 아키텍처 7단계
UEFI Platform Initialization(PI) 스펙은 펌웨어 실행을 7단계로 정의합니다. 각 단계는 명확한 책임 범위와 인터페이스(PPI, HOB, Protocol)로 분리되어 있어, 단계별 모듈을 독립적으로 교체·검증할 수 있습니다.
HOB (Hand-Off Block) 상세 구조
PEI→DXE 핸드오프의 핵심은 HOB 리스트입니다. PEI 단계에서 생성된 모든 플랫폼 정보가 HOB에 담겨 DXE Core로 전달됩니다.
/* EDK II PEI Module (PEIM) 엔트리 포인트 예시 */
EFI_STATUS
EFIAPI
PlatformPeiEntryPoint(
IN EFI_PEI_FILE_HANDLE FileHandle,
IN CONST EFI_PEI_SERVICES **PeiServices
)
{
EFI_STATUS Status;
/* PPI(PEI-to-PEI Interface) 설치 — 다른 PEIM이 사용 */
Status = (**PeiServices).InstallPpi(
PeiServices, &mPlatformPpiList
);
/* HOB 생성 — DXE에 플랫폼 정보 전달 */
BuildGuidDataHob(
&gPlatformInfoHobGuid,
&PlatformInfo,
sizeof(PlatformInfo)
);
return Status;
}
/* EDK II DXE Driver Binding Protocol 예시 */
EFI_DRIVER_BINDING_PROTOCOL gMyDriverBinding = {
.Supported = MyDriverSupported,
.Start = MyDriverStart,
.Stop = MyDriverStop,
.Version = 0x10,
.ImageHandle = NULL,
.DriverBindingHandle = NULL
};
/* Supported — 이 드라이버가 해당 핸들을 지원하는지 검사 */
EFI_STATUS EFIAPI MyDriverSupported(
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
EFI_STATUS Status;
Status = gBS->OpenProtocol(
ControllerHandle,
&gEfiPciIoProtocolGuid,
NULL, This->DriverBindingHandle,
ControllerHandle, EFI_OPEN_PROTOCOL_TEST_PROTOCOL
);
return Status;
}
/* HOB List 순회 — DXE Core 내부 */
VOID ProcessHobList(EFI_HOB_HANDOFF_INFO_TABLE *HobStart)
{
EFI_PEI_HOB_POINTERS Hob;
Hob.Raw = (UINT8 *)HobStart;
while (!END_OF_HOB_LIST(Hob)) {
switch (GET_HOB_TYPE(Hob)) {
case EFI_HOB_TYPE_MEMORY_ALLOCATION:
/* 메모리 할당 HOB — 사용 가능/예약 영역 등록 */
ProcessMemoryAllocation(Hob.MemoryAllocation);
break;
case EFI_HOB_TYPE_RESOURCE_DESCRIPTOR:
/* 리소스 HOB — MMIO, I/O, 시스템 메모리 영역 */
ProcessResourceDescriptor(Hob.ResourceDescriptor);
break;
case EFI_HOB_TYPE_FV:
/* 펌웨어 볼륨 HOB — 추가 FV 등록 */
ProcessFirmwareVolume(Hob.FirmwareVolume);
break;
}
Hob.Raw = GET_NEXT_HOB(Hob);
}
}
| HOB 타입 | 용도 | DXE에서의 소비 |
|---|---|---|
| PHIT | 부팅 모드, 메모리 범위 | DXE Core 초기 설정 |
| Memory Allocation | 예약 메모리, DXE Core 위치 | 메모리 맵 구축 |
| Resource Descriptor | 시스템 메모리, MMIO, I/O | GCD(Global Coherency Domain) 등록 |
| Firmware Volume | 추가 FV 위치 | DXE 드라이버 디스패치 대상 |
| CPU | 주소 비트 폭, I/O 비트 폭 | CPU Arch Protocol |
| GUID Extension | 플랫폼 고유 데이터 | GUID별 소비자 드라이버 |
EFI Stub 소스 분석
Linux EFI Stub은 drivers/firmware/efi/libstub/에 위치하며, UEFI 펌웨어에서 직접 커널 이미지를 부팅할 수 있게 합니다. 부트로더 없이 펌웨어가 커널 PE/COFF 이미지를 로드하면, EFI Stub이 최소한의 초기화를 수행하고 커널 본체로 점프합니다.
/* drivers/firmware/efi/libstub/efi-stub-entry.c
* x86 EFI Stub 진입점 — UEFI 펌웨어가 PE 엔트리로 호출 */
efi_status_t __efiapi
efi_pe_entry(efi_handle_t handle,
efi_system_table_t *sys_table_arg)
{
struct boot_params *boot_params;
struct setup_header *hdr;
efi_status_t status;
unsigned long alloc_size;
/* 시스템 테이블 저장 — 이후 모든 EFI 호출에 사용 */
sys_table = sys_table_arg;
/* boot_params 메모리 할당 (Boot Services 사용) */
alloc_size = 0x4000; /* 16 KiB */
status = efi_allocate_pages(alloc_size,
(unsigned long *)&boot_params,
ULONG_MAX);
/* setup_header 복사 및 커널 cmdline 처리 */
memcpy(&boot_params->hdr, hdr, sizeof(*hdr));
status = efi_parse_options(cmdline_ptr);
/* efi_main으로 이동 — 공통 EFI Stub 로직 */
return efi_main(handle, boot_params);
}
/* drivers/firmware/efi/libstub/efi-stub.c
* efi_main — 아키텍처 공통 EFI Stub 핵심 로직 */
efi_status_t
efi_main(efi_handle_t handle,
struct boot_params *boot_params)
{
efi_status_t status;
unsigned long map_size, desc_size;
struct efi_boot_memmap map;
/* 1. 커널 이미지를 적절한 주소로 재배치 */
status = handle_kernel_image(&image_addr,
&image_size,
&reserve_addr,
&reserve_size);
/* 2. initrd 로드 (LINUX_EFI_INITRD_MEDIA 프로토콜 또는 cmdline) */
status = efi_load_initrd(boot_params, ULONG_MAX,
efi_get_max_initrd_addr(image_addr));
/* 3. 그래픽 설정 (GOP 정보 수집) */
setup_graphics(boot_params);
/* 4. EFI Memory Map 획득 & ExitBootServices() */
status = exit_boot_services(handle, &map);
/* 5. 커널 본체로 점프 (돌아오지 않음) */
goto_kernel(image_addr, boot_params);
}
/* drivers/firmware/efi/libstub/x86-stub.c
* handle_kernel_image — 커널 이미지 메모리 배치 */
static efi_status_t
handle_kernel_image(unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
unsigned long *reserve_size)
{
/* KASLR: 커널 로드 주소 랜덤화 */
status = efi_random_alloc(alloc_size, CONFIG_PHYSICAL_ALIGN,
&addr, seed, EFI_LOADER_CODE,
EFI_ALLOC_LIMIT);
if (status != EFI_SUCCESS)
/* fallback: 기본 주소 사용 */
addr = LOAD_PHYSICAL_ADDR;
*image_addr = addr;
return EFI_SUCCESS;
}
/* drivers/firmware/efi/libstub/gop.c
* setup_graphics — GOP에서 프레임버퍼 정보 수집 */
static void
setup_graphics(struct boot_params *boot_params)
{
efi_graphics_output_protocol_t *gop;
efi_status_t status;
/* GOP 프로토콜 핸들 검색 */
status = efi_bs_call(LocateProtocol,
&efi_graphics_output_protocol_guid,
NULL, (void **)&gop);
if (status != EFI_SUCCESS)
return;
/* boot_params->screen_info에 GOP 정보 기록 */
struct screen_info *si = &boot_params->screen_info;
si->lfb_base = gop->Mode->FrameBufferBase;
si->lfb_size = gop->Mode->FrameBufferSize;
si->lfb_width = gop->Mode->Info->HorizontalResolution;
si->lfb_height = gop->Mode->Info->VerticalResolution;
si->lfb_depth = 32;
si->orig_video_isVGA = VIDEO_TYPE_EFI;
}
| EFI Stub 소스 파일 | 역할 |
|---|---|
efi-stub-entry.c | PE 엔트리 포인트, boot_params 설정 |
efi-stub.c | efi_main 공통 로직, ExitBootServices 호출 |
x86-stub.c | x86 커널 이미지 배치, KASLR |
arm64-stub.c | ARM64 커널 이미지 배치 |
gop.c | GOP 프레임버퍼 정보 수집 |
file.c | ESP 파일 시스템 접근 |
mem.c | EFI 메모리 할당/해제 래퍼 |
random.c | EFI_RNG_PROTOCOL, KASLR 시드 |
relocate.c | 커널 이미지 재배치(Relocation) 처리 |
Memory Map 처리
UEFI 펌웨어가 전달하는 EFI Memory Map은 커널이 물리 메모리를 관리하기 위한 기초 정보입니다. x86에서는 이 맵을 e820 테이블로 변환하여 커널의 기존 메모리 관리 인프라와 통합합니다.
/* EFI Stub에서 GetMemoryMap 호출 — ExitBootServices 직전 */
efi_status_t
efi_get_memory_map(struct efi_boot_memmap *map)
{
efi_status_t status;
/* 첫 호출: 필요한 버퍼 크기 확인 */
map->map_size = 0;
status = efi_bs_call(GetMemoryMap,
&map->map_size,
NULL,
&map->map_key,
&map->desc_size,
&map->desc_ver);
/* EFI_BUFFER_TOO_SMALL 반환 — map_size에 필요 크기 기록 */
/* 여유분 추가 (할당 중 맵이 변할 수 있으므로) */
map->map_size += 2 * map->desc_size;
/* 버퍼 할당 후 재호출 */
status = efi_bs_call(AllocatePool, EfiLoaderData,
map->map_size, (void **)&map->map);
status = efi_bs_call(GetMemoryMap,
&map->map_size,
map->map,
&map->map_key,
&map->desc_size,
&map->desc_ver);
return status;
}
/* arch/x86/platform/efi/efi.c — EFI 메모리 맵 초기 처리 */
void __init efi_memmap_init_early(struct efi_memory_map_data *data)
{
struct efi_memory_map map = {
.phys_map = data->phys_map,
.nr_map = data->size / data->desc_size,
.desc_size = data->desc_size,
.desc_version = data->desc_version,
};
/* early_memremap으로 EFI 맵을 커널 가상 주소에 매핑 */
map.map = early_memremap(data->phys_map, data->size);
/* 전역 efi.memmap에 설정 */
efi.memmap = map;
}
/* EFI_MEMORY_DESCRIPTOR 파싱 — 커널 e820 변환 */
static void __init
do_add_efi_memmap(void)
{
efi_memory_desc_t *md;
for_each_efi_memory_desc(md) {
unsigned long long start = md->phys_addr;
unsigned long long size = md->num_pages << EFI_PAGE_SHIFT;
int e820_type;
switch (md->type) {
case EFI_LOADER_CODE:
case EFI_LOADER_DATA:
case EFI_BOOT_SERVICES_CODE:
case EFI_BOOT_SERVICES_DATA:
case EFI_CONVENTIONAL_MEMORY:
e820_type = E820_TYPE_RAM;
break;
case EFI_ACPI_RECLAIM_MEMORY:
e820_type = E820_TYPE_ACPI;
break;
case EFI_ACPI_MEMORY_NVS:
e820_type = E820_TYPE_NVS;
break;
case EFI_PERSISTENT_MEMORY:
e820_type = E820_TYPE_PMEM;
break;
default:
e820_type = E820_TYPE_RESERVED;
break;
}
e820__range_add(start, size, e820_type);
}
e820__update_table(e820_table);
}
efi=debug를 추가하면 EFI Memory Map 전체를 커널 로그로 출력합니다. dmesg | grep "efi: mem"으로 각 영역의 타입, 주소, 크기, 속성을 확인할 수 있습니다.
Runtime Services 래퍼 구현
ExitBootServices() 이후에도 커널은 UEFI Runtime Services(GetTime, SetVariable, ResetSystem 등)를 호출합니다. 이를 위해 커널은 런타임 영역을 가상 주소(Virtual Address)로 재매핑하고, 전용 래퍼 함수를 통해 안전하게 호출합니다.
/* arch/x86/platform/efi/efi_64.c
* Runtime Services 호출 래퍼 — 페이지 테이블 전환 */
efi_status_t efi_thunk(efi_runtime_services_t *runtime,
void *func, ...)
{
unsigned long flags;
efi_status_t status;
/* 1. 인터럽트 비활성화 + 스핀락 획득
* Runtime Services는 재진입 불가 */
spin_lock_irqsave(&efi_runtime_lock, flags);
/* 2. EFI 전용 페이지 테이블로 CR3 전환
* Runtime 영역이 매핑된 efi_pgd 사용 */
efi_switch_mm(&efi_mm);
/* 3. 실제 Runtime Service 함수 호출 */
status = efi_call(func, ...);
/* 4. 커널 페이지 테이블로 복원 */
efi_switch_mm(current->active_mm);
/* 5. 스핀락 해제 + 인터럽트 복원 */
spin_unlock_irqrestore(&efi_runtime_lock, flags);
return status;
}
/* drivers/firmware/efi/runtime-wrappers.c
* SetVirtualAddressMap — 부팅 초기 1회 호출 */
static void __init
efi_set_virtual_address_map(void)
{
efi_memory_desc_t *md;
int count = 0;
/* Runtime 영역에 가상 주소 할당 */
for_each_efi_memory_desc(md) {
if (!(md->attribute & EFI_MEMORY_RUNTIME))
continue;
/* 물리 → 가상 주소 매핑 계산 */
md->virt_addr = (u64)(unsigned long)
efi_map_region(md);
count++;
}
/* 펌웨어에 가상 주소 맵 전달
* 이후 펌웨어도 가상 주소를 사용 */
efi.runtime->SetVirtualAddressMap(
count * efi.memmap.desc_size,
efi.memmap.desc_size,
efi.memmap.desc_version,
virtual_map
);
}
/* drivers/firmware/efi/runtime-wrappers.c
* Runtime fault 복구 — 펌웨어 버그 대응 */
void efi_recover_from_page_fault(unsigned long phys_addr)
{
/* Runtime Service 호출 중 page fault 발생 */
pr_err("Page fault in EFI runtime service at 0x%lx\n",
phys_addr);
/* 해당 Runtime Service를 영구적으로 비활성화 */
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
/* 호출자에게 에러 반환
* 커널 패닉 대신 graceful degradation */
schedule_work(&efi_rts_work->work);
}
| Runtime Service | 커널 래퍼 | 주요 사용처 |
|---|---|---|
| GetVariable | efi.get_variable() | efivarfs, Secure Boot 키 읽기 |
| SetVariable | efi.set_variable() | 부트 엔트리 갱신, pstore |
| GetNextVariableName | efi.get_next_variable() | 변수 열거 |
| GetTime/SetTime | efi.get_time() | RTC 대체 (ACPI 없을 때) |
| ResetSystem | efi.reset_system() | reboot, shutdown |
| UpdateCapsule | efi.update_capsule() | 펌웨어 업데이트 |
| QueryCapsuleCapabilities | efi.query_capsule_caps() | 캡슐 지원 확인 |
efivars sysfs 상세
Linux 커널은 UEFI 변수를 두 가지 인터페이스로 노출합니다: 레거시 /sys/firmware/efi/vars/(sysfs)와 현대적 /sys/firmware/efi/efivars/(efivarfs). 현재 efivarfs가 표준이며, 변수의 CRUD 연산을 파일 I/O로 수행합니다.
# /sys/firmware/efi/efivars/ 구조
# 각 변수는 "{이름}-{벤더GUID}" 형식의 파일
$ ls /sys/firmware/efi/efivars/
Boot0001-8be4df61-93ca-11d2-aa0d-00e098032b8c
BootCurrent-8be4df61-93ca-11d2-aa0d-00e098032b8c
BootOrder-8be4df61-93ca-11d2-aa0d-00e098032b8c
SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c
PK-8be4df61-93ca-11d2-aa0d-00e098032b8c
KEK-8be4df61-93ca-11d2-aa0d-00e098032b8c
db-d719b2cb-3d3a-4596-a3bc-dad00e67656f
# 변수 읽기 — 첫 4바이트는 속성(attributes), 나머지가 데이터
$ hexdump -C /sys/firmware/efi/efivars/BootCurrent-8be4df61-*
00000000 07 00 00 00 01 00 # attrs=0x07, data=0x0001 (Boot0001)
# 변수 속성 비트마스크
# 0x01 = NON_VOLATILE
# 0x02 = BOOTSERVICE_ACCESS
# 0x04 = RUNTIME_ACCESS
# 0x20 = AUTHENTICATED_WRITE_ACCESS
# 0x40 = TIME_BASED_AUTHENTICATED_WRITE
/* fs/efivarfs/super.c — efivarfs 마운트 시 변수 열거 */
static int efivarfs_fill_super(struct super_block *sb,
struct fs_context *fc)
{
/* 모든 EFI 변수를 순회하며 inode 생성 */
while ((efivar_entry_iter_begin()) == 0) {
/* GetNextVariableName으로 변수 열거 */
status = efi.get_next_variable(&name_size, name, &vendor);
if (status == EFI_NOT_FOUND)
break;
/* "{이름}-{GUID}" 형식으로 dentry 생성 */
efivarfs_create_dentry(sb, name, &vendor);
}
return 0;
}
/* drivers/firmware/efi/efi-pstore.c — pstore EFI 백엔드
* 커널 패닉 시 콘솔 로그를 UEFI 변수에 저장 */
static struct pstore_info efi_pstore_info = {
.owner = THIS_MODULE,
.name = "efi",
.open = efi_pstore_open,
.close = efi_pstore_close,
.read = efi_pstore_read,
.write = efi_pstore_write, /* panic 시 호출 */
.erase = efi_pstore_erase,
};
/* pstore write — UEFI 변수에 크래시 로그 기록 */
static int efi_pstore_write(struct pstore_record *record)
{
char name[DUMP_NAME_LEN];
/* 변수 이름: "dump-type0-N-ID-crc32" */
sprintf(name, "dump-type%u-%u-%llu-%lu",
record->type, record->part,
record->id, crc32(0, record->buf, record->size));
/* EFI SetVariable로 NVRAM에 기록 */
return efivar_entry_set_safe(
name, vendor,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
record->size, record->buf);
}
pstore.update_ms와 최대 로그 수를 적절히 설정해야 합니다.
TPM Event Log 처리
UEFI 펌웨어는 부팅 과정에서 측정한 컴포넌트(부트로더, 드라이버, 설정 등)의 해시를 TPM PCR에 확장(extend)하고, 각 측정 이벤트를 Event Log에 기록합니다. 커널은 이 Event Log를 파싱하여 부팅 무결성을 검증할 수 있습니다.
/* drivers/char/tpm/eventlog/efi.c
* UEFI TCG2 Event Log 파싱 — EFI Configuration Table에서 획득 */
int __init tpm_read_log_efi(struct tpm_chip *chip)
{
struct efi_tcg2_final_events_table *final_tbl;
struct linux_efi_tpm_eventlog *log_tbl;
int ret;
/* EFI Configuration Table에서 Event Log 위치 획득 */
log_tbl = memremap(efi.tpm_log, sizeof(*log_tbl),
MEMREMAP_WB);
if (!log_tbl)
return -ENOMEM;
/* TCG 2.0 Event Log 형식 확인 */
if (log_tbl->version != EFI_TCG2_EVENT_LOG_FORMAT_TCG_2) {
pr_warn("Unsupported event log format: %d\n",
log_tbl->version);
return -EINVAL;
}
/* Event Log 데이터를 tpm_chip 구조체에 복사 */
chip->log.bios_event_log = kmemdup(
log_tbl->log, log_tbl->log_size, GFP_KERNEL);
chip->log.bios_event_log_end =
chip->log.bios_event_log + log_tbl->log_size;
/* Final Events Table 처리 (ExitBootServices 이후 이벤트) */
if (efi.tpm_final_log) {
final_tbl = memremap(efi.tpm_final_log,
sizeof(*final_tbl), MEMREMAP_WB);
/* ... final events 추가 ... */
}
return 0;
}
# PCR bank 열거 및 읽기
$ tpm2_pcrread sha256
sha256:
0 : 0x3D4E88B44ECAE38C... # 펌웨어 코드
1 : 0x1A2B3C4D5E6F7890... # 펌웨어 설정
4 : 0xABCD1234EFGH5678... # 부트 로더
7 : 0x9F8E7D6C5B4A3210... # Secure Boot 정책
# Event Log 바이너리 읽기
$ cat /sys/kernel/security/tpm0/binary_bios_measurements | \
tpm2_eventlog
# Event 예시 출력:
# PCRIndex: 0
# EventType: EV_S_CRTM_VERSION
# DigestCount: 1
# AlgorithmId: sha256
# Digest: "3d4e88b44ecae38c..."
# EventData: "UEFI FW v2.80"
# PCR 재현 검증 스크립트
$ tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements \
| python3 -c "
import sys, hashlib
# Event Log의 각 digest를 순서대로 extend하여
# 최종 PCR 값과 비교 → 무결성 검증
"
| PCR 인덱스 | 측정 대상 | 수정 시점 |
|---|---|---|
| PCR[0] | CRTM, BIOS, Host Platform Extensions | 펌웨어 업데이트 |
| PCR[1] | Host Platform Configuration | BIOS 설정 변경 |
| PCR[2] | UEFI Option ROM Code | 어댑터 추가/제거 |
| PCR[3] | UEFI Option ROM Data | Option ROM 설정 변경 |
| PCR[4] | IPL (Initial Program Loader) | 부트로더 변경 |
| PCR[5] | IPL Code Configuration | 부트로더 설정 변경 |
| PCR[7] | Secure Boot Policy | PK/KEK/db/dbx 변경 |
| PCR[11] | systemd-stub: 커널, initrd, cmdline | UKI 갱신 |
| PCR[14] | shim/MOK 관련 | MOK 키 변경 |
Capsule Update 커널 구현
커널의 캡슐 업데이트 메커니즘은 /dev/efi_capsule_loader 디바이스 또는 fwupd/LVFS를 통해 펌웨어 이미지를 UEFI 런타임에 전달합니다. 재부팅 시 펌웨어가 캡슐을 감지하고 플래싱을 수행합니다.
/* drivers/firmware/efi/capsule-loader.c
* 캡슐 로더 드라이버 — /dev/efi_capsule_loader */
static ssize_t
efi_capsule_write(struct file *file,
const char __user *buff,
size_t count, loff_t *offp)
{
struct capsule_info *cap_info = file->private_data;
efi_capsule_header_t *header;
int ret;
/* 사용자 공간에서 캡슐 데이터 수신 */
ret = copy_from_user(cap_info->pages[pg], buff, count);
/* 첫 번째 페이지에서 캡슐 헤더 검증 */
if (*offp == 0) {
header = cap_info->pages[0];
/* GUID, 헤더 크기, 플래그 검증 */
if (header->HeaderSize < sizeof(*header))
return -EINVAL;
cap_info->total_size = header->CapsuleImageSize;
}
/* 전체 수신 완료 시 커널에 등록 */
if (cap_info->count == cap_info->total_size) {
/* EFI 변수에 캡슐 위치 기록
* 재부팅 시 펌웨어가 이 위치에서 캡슐을 읽음 */
ret = efi_capsule_setup_info(cap_info);
}
return count;
}
# fwupd/LVFS를 통한 캡슐 업데이트 실전 흐름
# 1. LVFS에서 펌웨어 메타데이터 다운로드
$ sudo fwupdmgr refresh
# 2. 업데이트 가능 장치 확인
$ fwupdmgr get-devices
System Firmware
DeviceId: xxxxxxxx
Vendor: Dell Inc.
Version: 1.12.0
UpdateState: success
# 3. 사용 가능한 업데이트 확인
$ fwupdmgr get-updates
System Firmware has firmware update 1.14.0
Description: Security fixes for...
# 4. 업데이트 적용 (캡슐을 ESP에 복사 + EFI 변수 설정)
$ sudo fwupdmgr update
# → /boot/efi/EFI/UpdateCapsule/ 에 .cap 파일 복사
# → EFI 변수 OsIndicationsSupported & OsIndications에
# EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED 설정
# 5. 재부팅 → 펌웨어가 캡슐 감지 → 플래싱
$ sudo reboot
# 6. 재부팅 후 결과 확인
$ fwupdmgr get-results
$ cat /sys/firmware/efi/esrt/entries/entry0/last_attempt_status
# 0 = SUCCESS
$ cat /sys/firmware/efi/esrt/entries/entry0/fw_version
# 새 버전 확인
/sys/firmware/efi/esrt/에서 확인할 수 있습니다.
GOP→efifb 전환
UEFI 펌웨어의 Graphics Output Protocol(GOP)은 ExitBootServices() 이전까지만 사용 가능합니다. 커널은 EFI Stub에서 GOP 프레임버퍼 정보를 수집하여 efifb 또는 simpledrm 드라이버로 전달하고, 최종적으로 네이티브 DRM 드라이버로 핸드오프합니다.
/* drivers/video/fbdev/efifb.c — efifb 프로브 */
static int efifb_probe(struct platform_device *dev)
{
struct fb_info *info;
struct screen_info *si = &screen_info;
/* screen_info에서 GOP 프레임버퍼 정보 획득 */
if (si->orig_video_isVGA != VIDEO_TYPE_EFI)
return -ENODEV;
info = framebuffer_alloc(0, &dev->dev);
info->fix.smem_start = si->lfb_base;
info->fix.smem_len = si->lfb_size;
info->var.xres = si->lfb_width;
info->var.yres = si->lfb_height;
/* 프레임버퍼 메모리를 ioremap (WC 매핑) */
info->screen_base = ioremap_wc(
info->fix.smem_start, info->fix.smem_len);
/* fbdev 등록 — /dev/fb0 생성 */
register_framebuffer(info);
return 0;
}
/* drivers/gpu/drm/tiny/simpledrm.c — simpledrm DRM 핸드오프
* 네이티브 GPU 드라이버가 로드되면 simpledrm이 양보 */
static int simpledrm_probe(struct platform_device *pdev)
{
struct simpledrm_device *sdev;
struct drm_device *dev;
/* screen_info 기반 simple framebuffer */
sdev = simpledrm_device_create(&pdev->dev, &screen_info);
/* DRM 디바이스 등록 */
dev = &sdev->dev;
drm_dev_register(dev, 0);
drm_fbdev_generic_setup(dev, 0);
return 0;
}
/* 네이티브 DRM 드라이버(i915 등)가 프로브 시:
* drm_aperture_remove_framebuffers("simpledrm")
* → simpledrm 플랫폼 디바이스 제거
* → 네이티브 DRM이 GPU 하드웨어 직접 제어 */
ARM64 UEFI 부팅 특수성
ARM64(AArch64) 플랫폼에서의 UEFI 부팅은 x86과 몇 가지 중요한 차이가 있습니다: Device Tree와 ACPI의 공존, PE/COFF Image 헤더의 특수 구조, UEFI Memory Attribute Protocol을 통한 보안 강화 등입니다.
/* drivers/firmware/efi/libstub/arm64-stub.c
* ARM64 EFI Stub 진입 — 커널 이미지 메모리 배치 */
efi_status_t handle_kernel_image(
unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
unsigned long *reserve_size,
efi_loaded_image_t *image,
efi_handle_t image_handle)
{
efi_status_t status;
unsigned long kernel_size, kernel_codesize;
/* ARM64 커널은 2MB 정렬 필요 */
unsigned long min_kimg_align = SZ_2M;
/* KASLR: 랜덤 오프셋으로 커널 배치 */
status = efi_random_alloc(
*image_size, min_kimg_align,
image_addr,
efi_get_kimg_min_align(),
EFI_LOADER_CODE, EFI_ALLOC_LIMIT);
/* 이미지를 새 주소로 복사 */
memcpy((void *)*image_addr,
image->ImageBase, kernel_size);
/* Memory Attribute Protocol로 W^X 적용 */
efi_adjust_memory_range_protection(
*image_addr, kernel_codesize,
EFI_MEMORY_RO | EFI_MEMORY_XP);
return EFI_SUCCESS;
}
/* drivers/firmware/efi/libstub/mem.c
* UEFI Memory Attribute Protocol — W^X 적용 */
efi_status_t
efi_adjust_memory_range_protection(
unsigned long addr, unsigned long size,
unsigned long attributes)
{
efi_memory_attribute_protocol_t *memattr;
efi_status_t status;
/* Memory Attribute Protocol 검색 */
status = efi_bs_call(LocateProtocol,
&efi_memory_attribute_protocol_guid,
NULL, (void **)&memattr);
if (status != EFI_SUCCESS)
return status;
/* 코드 영역: RO (쓰기 불가, 실행 가능)
* 데이터 영역: XP (실행 불가, 쓰기 가능)
* → W^X: 동시에 쓰기+실행 불가 */
status = memattr->SetMemoryAttributes(
memattr, addr, size, attributes);
return status;
}
| 항목 | x86_64 | ARM64 |
|---|---|---|
| 부팅 체인 | SEC→PEI→DXE→BDS | TF-A BL1→BL2→BL31→BL33(UEFI) |
| 하드웨어 기술 | ACPI 전용 | ACPI 또는 Device Tree |
| SMP 부팅 | APIC/INIT-SIPI | PSCI (SMC 호출) |
| 이미지 정렬 | CONFIG_PHYSICAL_ALIGN | 2MB 필수 |
| PE/COFF | bzImage PE 스텁 | Image에 PE 헤더 내장 |
| W^X | 페이지 테이블(Page Table)로 적용 | Memory Attribute Protocol |
| Secure Boot | PK/KEK/db/dbx | 동일 + TF-A Trusted Boot |
| 런타임 격리(Isolation) | efi_pgd CR3 전환 | 별도 TTBR0 전환 |
UEFI 디버깅 코드 실전
UEFI 관련 문제를 진단할 때 활용할 수 있는 커널 파라미터, 디버그 로그, OVMF 가상 환경 설정, 실전 스크립트를 정리합니다.
# efi=debug 커널 파라미터 — EFI 관련 상세 로그 활성화
# GRUB에서 설정
# /etc/default/grub:
GRUB_CMDLINE_LINUX="efi=debug"
# sudo update-grub && reboot
# 부팅 후 EFI 디버그 로그 확인
$ dmesg | grep -i efi
[ 0.000000] efi: EFI v2.80 by EDK II
[ 0.000000] efi: ACPI 2.0=0x7fbfe000 ACPI=0x7fbfe014 SMBIOS=0x7f9e1000
[ 0.000000] efi: ESRT=0x7f623018 MOKvar=0x7f608000
[ 0.000000] efi: mem00: [Conventional Memory| | | | | | |WB|WT|WC|UC] range=[0x0000000000000000-0x000000000009ffff] (640KiB)
[ 0.000000] efi: mem01: [Reserved | | | | | | |WB|WT|WC|UC] range=[0x00000000000a0000-0x00000000000fffff] (384KiB)
[ 0.000000] efi: mem02: [Loader Data | | | | | | |WB|WT|WC|UC] range=[0x0000000000100000-0x0000000007ffffff] (127MiB)
# 기타 유용한 efi= 옵션
# efi=noruntime — Runtime Services 비활성화 (펌웨어 버그 우회)
# efi=nosoftreserve — Soft Reserved 메모리 무시
# efi=old_map — 레거시 가상 주소 매핑 사용 (x86)
# efi=nochunk — SetVirtualAddressMap 청크 분할 비활성화
# EFI 변수 덤프/분석 스크립트
#!/bin/bash
# uefi-dump.sh — UEFI 시스템 정보 종합 수집
echo "=== UEFI 시스템 정보 ==="
echo "EFI 지원: $([ -d /sys/firmware/efi ] && echo 'Yes' || echo 'No')"
echo "Secure Boot: $(od -An -tx1 /sys/firmware/efi/efivars/SecureBoot-* 2>/dev/null | tail -c3)"
echo
echo "=== 부트 엔트리 ==="
efibootmgr -v 2>/dev/null || echo "efibootmgr not available"
echo
echo "=== EFI 변수 통계 ==="
echo "총 변수 수: $(ls /sys/firmware/efi/efivars/ 2>/dev/null | wc -l)"
echo "총 크기: $(du -sh /sys/firmware/efi/efivars/ 2>/dev/null | cut -f1)"
echo
echo "=== ESRT (System Resource Table) ==="
if [ -d /sys/firmware/efi/esrt ]; then
for entry in /sys/firmware/efi/esrt/entries/entry*; do
echo " $(basename $entry):"
echo " Type: $(cat $entry/fw_type)"
echo " Version: $(cat $entry/fw_version)"
echo " Last Status: $(cat $entry/last_attempt_status)"
done
fi
echo
echo "=== EFI Memory Map (from dmesg) ==="
dmesg | grep "efi: mem" | head -20
echo
echo "=== Runtime Services 상태 ==="
dmesg | grep -i "efi.*runtime"
# OVMF (Open Virtual Machine Firmware) 디버그 환경 구축
# 1. OVMF 설치
sudo apt install ovmf # Debian/Ubuntu
sudo dnf install edk2-ovmf # Fedora/RHEL
# 2. QEMU + OVMF로 UEFI 가상머신 실행
qemu-system-x86_64 \
-bios /usr/share/ovmf/OVMF.fd \
-drive file=disk.qcow2,format=qcow2 \
-m 2G -smp 2 \
-serial stdio \
-debugcon file:debug.log \
-global isa-debugcon.iobase=0x402
# 3. OVMF 디버그 빌드 (상세 로그)
# DEBUG 레벨 OVMF 사용 시 Port 0x402로 디버그 출력
qemu-system-x86_64 \
-bios /usr/share/ovmf/OVMF_CODE.debug.fd \
-drive if=pflash,format=raw,file=OVMF_VARS.fd \
-chardev file,id=debuglog,path=ovmf-debug.log \
-device isa-debugcon,iobase=0x402,chardev=debuglog \
-serial mon:stdio \
-nographic
# 4. 디버그 로그 분석
# ovmf-debug.log 에서 단계별 로그 확인
grep "DXE" ovmf-debug.log # DXE 단계 이벤트
grep "BDS" ovmf-debug.log # BDS 부팅 선택
grep "SecureBoot" ovmf-debug.log # Secure Boot 검증
grep "ERROR\|ASSERT" ovmf-debug.log # 에러/assert
# efibootmgr 실전 예제 — 부트 엔트리 관리
# 현재 부트 엔트리 상세 표시
$ sudo efibootmgr -v
BootCurrent: 0001
BootOrder: 0001,0003,0000
Boot0000* EFI Network PciRoot(0x0)/Pci(0x1C,0x0)/...
Boot0001* ubuntu HD(1,GPT,...)/File(\EFI\ubuntu\shimx64.efi)
Boot0003* UEFI Shell FvVol(...)
# 새 부트 엔트리 생성 (직접 EFI Stub 커널 부팅)
$ sudo efibootmgr -c \
-d /dev/nvme0n1 -p 1 \
-L "Linux Direct" \
-l "\EFI\Linux\vmlinuz-6.8.0.efi" \
-u "root=UUID=xxx ro quiet"
# 부트 순서 변경
$ sudo efibootmgr -o 0004,0001,0000
# 엔트리 삭제
$ sudo efibootmgr -B -b 0003
# 다음 1회만 특정 엔트리로 부팅
$ sudo efibootmgr -n 0000
# 타임아웃 설정 (초)
$ sudo efibootmgr -t 5
# Secure Boot 상태 확인
$ mokutil --sb-state
SecureBoot enabled
# Secure Boot 키 목록
$ mokutil --list-enrolled
[key 1]
SHA1 Fingerprint: xx:xx:xx:...
Issuer: C=US, ST=..., O=Canonical Ltd.
memmap), 디바이스 경로(devtree), 프로토콜(dh -p), 변수(dmpstore)를 직접 검사할 수 있습니다. bcfg boot dump으로 부트 엔트리도 확인 가능합니다.
| 디버깅 도구 | 용도 | 커널/유저 공간 |
|---|---|---|
efi=debug | EFI 메모리 맵, 런타임 서비스 상세 로그 | 커널 파라미터 |
efibootmgr | UEFI 부트 엔트리 관리 | 유저 공간 |
mokutil | MOK/Secure Boot 키 관리 | 유저 공간 |
efivar | 개별 EFI 변수 읽기/쓰기 | 유저 공간 |
fwupdmgr | 펌웨어 업데이트 (LVFS) | 유저 공간 |
OVMF + QEMU | UEFI 가상 환경 디버깅 | 호스트 |
UEFI Shell | Boot Services 단계 직접 검사 | 펌웨어 |
/sys/firmware/efi/ | EFI 변수, ESRT, 런타임 상태 | sysfs |
참고 자료
UEFI 펌웨어와 Linux 커널 EFI 서브시스템을 깊이 이해하려면 아래 공식 사양, 커널 문서, 소스 코드를 함께 참조하는 것이 좋습니다.
- UEFI Specification 2.11 (UEFI Forum) — UEFI 공식 사양 전문. Boot Services, Runtime Services, 프로토콜 모델 정의
- PI Specification 1.8.1 (UEFI Forum) — 플랫폼 초기화(SEC→PEI→DXE→BDS) 내부 구조 정의
- ACPI Specification 6.5 — UEFI와 함께 사용되는 ACPI 테이블 및 전원 관리 사양
- Linux 커널 문서 — EFI Stub — EFI Stub을 통한 UEFI 직접 부팅 설정 가이드
- Linux 커널 문서 — efivarfs — EFI 변수 파일시스템 마운트 및 사용법
- Linux 커널 문서 — EFI Runtime Services — 커널에서 UEFI Runtime Services 접근 방식
- Linux 커널 문서 — Firmware Interfaces — 커널의 펌웨어 인터페이스 개요
- 커널 소스 — drivers/firmware/efi/ — EFI 서브시스템 코어 구현 (efi.c, efi-init.c, vars.c 등)
- 커널 소스 — drivers/firmware/efi/libstub/ — EFI Stub 부트로더 구현 (x86, ARM64, RISC-V)
- 커널 소스 — arch/x86/platform/efi/ — x86 아키텍처의 EFI 플랫폼 지원 코드
- 커널 소스 — include/linux/efi.h — EFI 관련 자료 구조 및 인터페이스 헤더
- LWN — The EFI system table (2015) — 커널이 UEFI 시스템 테이블을 파싱하는 과정 분석
- LWN — Booting Linux on EFI (2009) — EFI Stub 도입 배경과 초기 구현 설명
- LWN — EFI and the memory map (2022) — UEFI 메모리 맵과 커널 메모리 초기화 연동
- GitHub — TianoCore EDK II — UEFI PI 사양의 오픈소스 참조 구현 (OVMF 포함)
- GitHub — OvmfPkg — QEMU/KVM용 UEFI 펌웨어(OVMF) 소스 코드
- EDK II 나이틀리 빌드 — OVMF 빌드 바이너리 다운로드 (개발/테스트용)
관련 문서
UEFI와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.