User:Demindiro/Super Tiny UEFI Hello World

From OSDev Wiki
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 \