User:Demindiro/Super Tiny UEFI Hello World
Jump to navigation
Jump to search
build.sh
#!/bin/sh
set -xe
as --64 -o main.o main.s
ld --oformat binary -o main.bin main.o
python3 uefi.py main.bin bootx64.efi
fallocate -l512K efi-fs.img
mformat -i efi-fs.img
mmd -i efi-fs.img ::/efi ::/efi/boot
mcopy -i efi-fs.img bootx64.efi ::/efi/boot/bootx64.efi
#mcopy -i efi-fs.img edk2/Build/EmulatorX64/DEBUG_GCC/X64/HelloWorld.efi ::/efi/boot/bootx64.efi
mdir -i efi-fs.img -/ -w -a
fallocate -l1M uefi-test.img
cat <<CMD | /sbin/fdisk uefi-test.img
g
n
t
uefi
w
CMD
/sbin/fdisk -l uefi-test.img
dd if=efi-fs.img of=uefi-test.img bs=512 oseek=34 conv=notrunc
/sbin/fdisk -l uefi-test.img
main.s
.intel_syntax noprefix
.section .text
.globl _start
.equ EFI_HANDLE.sizeof, 8 # VOID*
# 4.2.1
.equ EFI_TABLE_HEADER.sizeof, 8 + (4 * 4)
# 4.3.1
.equ EFI_SYSTEM_TABLE.Hdr , 0 # EFI_TABLE_HEADER
.equ EFI_SYSTEM_TABLE.FirmwareVendor , 24 # CHAR16
.equ EFI_SYSTEM_TABLE.FirmwareRevision, 32 # UINT32
.equ EFI_SYSTEM_TABLE.ConsoleInHandle , 40 # EFI_HANDLE
.equ EFI_SYSTEM_TABLE.ConIn , 48 # EFI_SIMPLE_TEXT_INPUT_PROTOCOL
.equ EFI_SYSTEM_TABLE.ConsoleOutHandle, 56 # EFI_HANDLE
.equ EFI_SYSTEM_TABLE.ConOut , 64 # EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
# there's more but idc
# 12.4.1
.equ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.Reset , 0 # EFI_TEXT_RESET
.equ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString, 8 # EFI_TEXT_STRING
# rcx: EFI_HANDLE (of ourselves, just ignore)
# rdx: EFI_SYSTEM_TABLE*
_start:
mov rax, [rdx + EFI_SYSTEM_TABLE.ConOut]
mov rbx, [rax + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString]
# 12.4.3
# typedef EFI_STATUS (EFIAPI *EFI_TEXT_STRING) (
# IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This
# IN CHAR16 *String
# );
# I will _never_ forgive Microsoft for being so stubborn with (not even!) UTF-16
# instead of at least trying to use the actually sane UTF-8 standard
mov rcx, rax
lea rdx, [rip + hello_uefi]
#call break
call rbx
2:
hlt
jmp 2b
break:
xor r15, r15
2:
test r15, r15
jz 2b
ret
hello_uefi:
.word 'H', 'e', 'l', 'l', 'o', ' ', 'U', 'E', 'F', 'I', '!', 0
uefi.py
#!/usr/bin/env python3
import sys, os
# note that no stub program is included!
MSDOS_MZ_HEADER = (b'MZ' + (b'\0' * (64 - 2 - 4))) + \
b'\x40\x00\x00\x00' # 0x40 = 64 bytes = start of PE header
PE_HEADER = b''.join((
b'PE\0\0', # magic
b'\x64\x86', # machine (EFI_IMAGE_MACHINE_X64)
b'\1\0', # number of sections
b'\0\0\0\0', # timedatestamp
b'\0\0\0\0', # pointer to symbol table
b'\0\0\0\0', # number of symbols
b'\x70\0', # size of optional header (160 bytes)
b'\x2e\x00', # characteristics (IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LINE_NUMS_STRIPPED, IMAGE_FILE_LOCAL_SYMS_STRIPPED, IMAGE_FILE_LARGE_ADDRESS_AWARE)
))
def pe32_optional_header(
*,
size_of_code,
size_of_initdata,
size_of_uninitdata,
entry_address,
base_of_code,
size_of_image,
size_of_headers,
):
return b''.join([
b'\x0b\x02', # magic (PE32+)
b'\0\0', # linker version
size_of_code.to_bytes(4, 'little'),
size_of_initdata.to_bytes(4, 'little'),
size_of_uninitdata.to_bytes(4, 'little'),
entry_address.to_bytes(4, 'little'),
base_of_code.to_bytes(4, 'little'),
b'\0\0\0\0\0\0\0\0', # image base
b'\x00\x10\x00\x00', # section alignment (4096)
#b'\x00\x02\x00\x00', # file alignment (512)
b'\x00\x10\x00\x00', # file alignment (4096)
b'\0\0\0\0', # OS version
b'\0\0\0\0', # image version
b'\0\0\0\0', # subsystem version
b'\0\0\0\0', # win32 version
size_of_image.to_bytes(4, 'little'),
size_of_headers.to_bytes(4, 'little'),
b'\0\0\0\0', # checksum (N/A since IMAGE_SCN_LNK_COMDAT is not set)
b'\x0a\x00', # subsystem
b'\0\0', # DLL characteristics
b'\0\0\0\0\0\0\0\0', # size of stack reserve
b'\0\0\0\0\0\0\0\0', # size of stack commit
b'\0\0\0\0\0\0\0\0', # size of heap reserve
b'\0\0\0\0\0\0\0\0', # size of heap commit
b'\0\0\0\0', # loader flags
b'\0\0\0\0', # number of RVA and sizes
])
def section_header(
*,
name,
virtual_size,
virtual_addr,
raw_data_size,
raw_data_offset,
):
name = name.encode('utf-8')
assert len(name) <= 8
return b''.join([
name.ljust(8, b'\0'),
virtual_size.to_bytes(4, 'little'),
virtual_addr.to_bytes(4, 'little'),
raw_data_size.to_bytes(4, 'little'),
raw_data_offset.to_bytes(4, 'little'),
b'\0\0\0\0', # pointer to relocations
b'\0\0\0\0', # pointer to line numbers
b'\0\0', # number of relocations
b'\0\0', # number of line numbers
b'\x20\x00\x00\x60', # characteristics (IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ)
])
_, code, out = sys.argv
st = os.stat(code)
with open(out, 'wb') as f:
f.write(MSDOS_MZ_HEADER)
f.write(PE_HEADER)
f.write(pe32_optional_header(
size_of_code = st.st_size,
size_of_initdata = 0,
size_of_uninitdata = 0,
base_of_code = 0,
size_of_image = 4096, # OVMF freaks out if you don't do this
size_of_headers = 64 + 24 + 112 + (1 * 40),
# we're sticking the code right after the headers
entry_address = 64 + 24 + 112 + (1 * 40),
))
f.write(section_header(
name = '.lollmao',
virtual_size = st.st_size,
raw_data_size = st.st_size,
virtual_addr = 64 + 24 + 112 + (1 * 40),
raw_data_offset = 64 + 24 + 112 + (1 * 40),
))
with open(code, 'rb') as g:
f.write(g.read())
# pad to page size
# OVMF yells otherwise
pos = f.tell()
f.write(b'\0' * ((4096 - pos) % 4096))
qemu.sh
#!/bin/sh
set -xe
qemu=qemu-system-x86_64
ovmf=edk2/Build/OvmfX64/DEBUG_GCC/FV/OVMF.fd
img=uefi-test.img
exec "$qemu" \
-machine q35 \
--enable-kvm \
--bios "$ovmf" \
-drive format=raw,file="$img" \
-debugcon file:debug.log -global isa-debugcon.iobase=0x402 \
-s \