User:Demindiro/Super Tiny UEFI Hello World

From OSDev Wiki
Jump to navigation Jump to search

GAS + Python

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))

FASM

boot.asm

use64

org 0

IMAGE_FILE_EXECUTABLE_IMAGE    = 1 shl 1
IMAGE_FILE_LARGE_ADDRESS_AWARE = 1 shl 5

IMAGE_SCN_CNT_CODE    = 0x20
IMAGE_SCN_MEM_EXECUTE = 0x20000000
IMAGE_SCN_MEM_READ    = 0x40000000

dos:
        db "MZ"
        times 58 db 0
        dd pe
pe:
        db "PE", 0, 0
        dw 0x8664 ; machine type
        dw 1      ; sections
        dd 0, 0, 0
        dw .opt.end - .opt
        dw IMAGE_FILE_EXECUTABLE_IMAGE or IMAGE_FILE_LARGE_ADDRESS_AWARE
.opt:
        dw 0x20b, 0
        dd text_end     ; size of code
        dd 0, 0         ; size of data
        dd start, start ; entry, code base
        dq 0            ; image base (irrelevant, PIE)
        dd 4096, 4096   ; alignment
        dd 0, 0, 0, 0
        dd text_end     ; image size
        dd pe.end       ; headers size
        dd 0
        dw 10           ; EFI subsystem
        dw 0
        dq 0, 0, 0, 0   ; stack/heap (irrelevant)
        dd 0, 0         ; RVA
.opt.end:
        db ".david", 0, 0
        times 2 \
        dd text_end - start, start  ; virtual/raw size/addr
        dd 0, 0, 0      ; ptr to reloc/lines, num
        dd IMAGE_SCN_CNT_CODE or IMAGE_SCN_MEM_EXECUTE or IMAGE_SCN_MEM_READ
.end:


EFI_HANDLE.sizeof = 8 ; VOID*

; 4.2.1
EFI_TABLE_HEADER.sizeof = 8 + (4 * 4)

; 4.3.1
EFI_SYSTEM_TABLE.Hdr              = 0 ; EFI_TABLE_HEADER
EFI_SYSTEM_TABLE.FirmwareVendor   = 24 ; CHAR16
EFI_SYSTEM_TABLE.FirmwareRevision = 32 ; UINT32
EFI_SYSTEM_TABLE.ConsoleInHandle  = 40 ; EFI_HANDLE
EFI_SYSTEM_TABLE.ConIn            = 48 ; EFI_SIMPLE_TEXT_INPUT_PROTOCOL
EFI_SYSTEM_TABLE.ConsoleOutHandle = 56 ; EFI_HANDLE
EFI_SYSTEM_TABLE.ConOut           = 64 ; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
; there's more but idc

; 12.4.1
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.Reset        = 0 ; EFI_TEXT_RESET
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, [hello_uefi]
        ;call _break
        call rbx

@@:     hlt
        jmp @b

_break:
        xor r15, r15
@@:     test r15, r15
        jz @b
        ret

hello_uefi: dw 'H', 'e', 'l', 'l', 'o', ' ', 'U', 'E', 'F', 'I', '!', 0


times ((-$) and 0xfff) db 0

text_end:

build.sh

#!/bin/sh
src="$(dirname "$0")"
set -xe
mkdir -p $out/
fasm $src/boot.asm $out/bootx64.efi


# https://wiki.osdev.org/User:Demindiro/Super_Tiny_UEFI_Hello_World

fat=$out/efi-fs.img
gpt=$out/root.img
fallocate -l512K $fat
mformat -i $fat
mmd     -i $fat             ::/efi ::/efi/boot
mcopy   -i $fat $out/bootx64.efi ::/efi/boot/bootx64.efi
mdir    -i $fat -/ -w -a

fallocate -l1M $gpt
cat <<CMD | fdisk $gpt
g
n



t
uefi
w
CMD
fdisk -l $gpt

dd if=$fat of=$gpt bs=512 oseek=34 conv=notrunc

fdisk -l $gpt

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 \