3
\$\begingroup\$

UEFI bootloader.

It reads the file kernel.elf on the disk image, reads through the program headers, copies the LOAD segments into memory, and finally calls kmain()

The kmain() is nothing but an infinite loop because I want to get feedback on the bootloader before starting to make the kernel

Parts of the Makefile are copied from someone else but I had to add commands for compiling and linking the kernel file, and also for updating the BOOTx64.EFI file on the disk image, so please give feedback on the makefile aswell

main.c

#include <efi.h>
#include <efilib.h>
#include <string.h>
#include "types.h"
#include "elf.h"
#include "mmu.h"
EFI_FILE_HANDLE getvolume(EFI_HANDLE);
EFI_FILE_HANDLE openfile(EFI_FILE_HANDLE, CHAR16*);
CHAR16 readfile(EFI_FILE_HANDLE);
EFI_MEMORY_DESCRIPTOR* getmemmap(UINTN*, UINTN*, UINTN*);
EFI_STATUS getmap(EFI_MEMORY_DESCRIPTOR**,
 UINTN *,
 UINTN *,
 UINTN *,
 UINT32 *);
 
EFI_STATUS stat;
typedef struct {
 EFI_MEMORY_DESCRIPTOR *mmap;
 UINTN mmapsize;
 UINTN descsize;
} Bootinfo;
EFI_STATUS efi_main(EFI_HANDLE IH, EFI_SYSTEM_TABLE *ST) {
 InitializeLib(IH, ST); 
 uefi_call_wrapper(ST->ConOut->Reset, 2, ST->ConOut, 1);
 
 EFI_FILE_HANDLE vol = getvolume(IH);
 EFI_FILE_HANDLE filehandle = openfile(vol, L"kernel.elf");
 CHAR16 buf16 = readfile(filehandle);
 char *buf = (char*) buf16;
 struct elfh *elf = (struct elfh*) buf;
 struct progh *prog = (struct progh*) (buf + elf->phoff);
 UINTN descsize;
 UINTN mmapsize = 0;
 UINTN mapkey = 0;
 UINT32 descversion;
 EFI_MEMORY_DESCRIPTOR *desc = NULL;
 getmap(&desc, &descsize, &mmapsize, &mapkey, &descversion);
 uint allocamount = 0;
 for (uint i = 0; i < elf->phnum; ++i, ++prog) {
 if (prog->type != PT_LOAD)
 continue;
 allocamount = prog->vaddr + prog->memsz;
 }
 uint64_t physaddr;
 stat = uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, EfiLoaderData, (allocamount / PGSIZE), &physaddr);
 buf = (char*) physaddr;
 // loop over program headers again, this time setting the memory
 prog = (struct progh*) (buf + elf->phoff);
 for (uint i = 0; i < elf->phnum; ++i, ++prog) {
 if (prog->type != PT_LOAD)
 continue;
 // copy to memory from offset
 memcpy(buf + prog->vaddr, buf + prog->offset, prog->filesz);
 // fill with zeroes if needed
 if (prog->memsz > prog->filesz)
 memset(buf + prog->vaddr + prog->filesz, 0, (prog->memsz - prog->filesz));
 }
 stat = getmap(&desc, &descsize, &mmapsize, &mapkey, &descversion);
 stat = uefi_call_wrapper(BS->ExitBootServices, 2, IH, mapkey);
 buf = (char*) buf16;
 int (*kmain)(void*) = (int(*)(void*)) (buf + elf->entry);
 Bootinfo bootinfo;
 bootinfo.mmap = desc;
 bootinfo.descsize = descsize;
 bootinfo.mmapsize = mmapsize;
 kmain(&bootinfo);
}
EFI_STATUS getmap(EFI_MEMORY_DESCRIPTOR** map,
 UINTN *descsize,
 UINTN *mapsize,
 UINTN *mapkey,
 UINT32 *dversion) {
 stat = uefi_call_wrapper(BS->GetMemoryMap, 5, mapsize, *map, mapkey, descsize, dversion);
 
 // needed because a new descriptor will be added to memory
 *mapsize += *descsize;
 stat = uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData, *mapsize, (VOID**)map);
 stat = uefi_call_wrapper(BS->GetMemoryMap, 5, mapsize, *map, mapkey, descsize, dversion);
 Print(L"%d\n", stat);
 return stat;
}
CHAR16 readfile(EFI_FILE_HANDLE filehandle) {
 UINTN bufsz = 10000;
 CHAR16 buf16[bufsz];
 uefi_call_wrapper(filehandle->Read, 3, filehandle, &bufsz, buf16);
 return buf16;
}
EFI_FILE_HANDLE openfile(EFI_FILE_HANDLE vol, CHAR16* filename) {
 EFI_FILE_HANDLE temphandle;
 EFI_STATUS status = uefi_call_wrapper(vol->Open, 5, vol, &temphandle, filename, EFI_FILE_MODE_READ, 0);
 if (status != 0)
 return 0;
 return temphandle;
}
EFI_FILE_HANDLE getvolume(EFI_HANDLE IH) {
 EFI_LOADED_IMAGE *limg = 0;
 EFI_GUID lipGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
 EFI_FILE_IO_INTERFACE *IOVolume;
 EFI_GUID fsGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
 EFI_FILE_HANDLE Volume;
 uefi_call_wrapper(BS->HandleProtocol, 3, IH, &lipGuid, (void**)&limg);
 uefi_call_wrapper(BS->HandleProtocol, 3, limg->DeviceHandle, &fsGuid, (void*)&IOVolume);
 uefi_call_wrapper(IOVolume->OpenVolume, 2, IOVolume, &Volume);
 return Volume;
}

mmu.h (this file will have more macros in the future)

#define PGSIZE 4096

elf.h

struct elfh {
 uint32_t magic;
 uint8_t elf[12];
 uint16_t type;
 uint16_t machine;
 uint32_t version;
 uint64_t entry;
 uint64_t phoff;
 uint64_t shoff;
 uint32_t flags;
 uint16_t ehsize;
 uint16_t phentsize;
 uint16_t phnum;
 uint16_t shentsize;
 uint16_t shnum;
 uint16_t shstrndx;
};
struct progh {
 uint type;
 uint32_t flags;
 uint64_t offset;
 uint64_t vaddr;
 uint64_t paddr;
 uint64_t filesz;
 uint64_t memsz;
 uint64_t align;
};
#define PT_LOAD 0x00000001

types.h

struct elfh {
 uint32_t magic;
 uint8_t elf[12];
 uint16_t type;
 uint16_t machine;
 uint32_t version;
 uint64_t entry;
 uint64_t phoff;
 uint64_t shoff;
 uint32_t flags;
 uint16_t ehsize;
 uint16_t phentsize;
 uint16_t phnum;
 uint16_t shentsize;
 uint16_t shnum;
 uint16_t shstrndx;
};
struct progh {
 uint type;
 uint32_t flags;
 uint64_t offset;
 uint64_t vaddr;
 uint64_t paddr;
 uint64_t filesz;
 uint64_t memsz;
 uint64_t align;
};
#define PT_LOAD 0x00000001

kernel/main.c

int kmain() {
 for (;;);
}

Makefile

ARCH = $(shell uname -m | sed s,i[3456789]86,ia32,)
OBJS = main.o
TARGET = boot.efi
EFIINC = /usr/include/efi
EFIINCS = -I$(EFIINC) -I$(EFIINC)/$(ARCH) -I$(EFIINC)/protocol
LIB = /usr/lib
EFILIB = /usr/lib
EFI_CRT_OBJS = $(EFILIB)/crt0-efi-$(ARCH).o
EFI_LDS = $(EFILIB)/elf_$(ARCH)_efi.lds
CFLAGS = $(EFIINCS) -fno-stack-protector -fpic \
 -fshort-wchar -mno-red-zone -Wall 
ifeq ($(ARCH),x86_64)
 CFLAGS += -DEFI_FUNCTION_WRAPPER
endif
LDFLAGS = -nostdlib -no-pie -znocombreloc -T $(EFI_LDS) -shared \
 -Bsymbolic -L $(EFILIB) -L $(LIB) $(EFI_CRT_OBJS) 
.PHONY: kernel 
# I made kernel and all
kernel:
 sudo gcc -Werror -nostdlib -o kernel.o -c kernel/main.c 
 sudo gcc -nostdlib -ffreestanding -e kmain -o kernel.elf kernel.o
all: $(TARGET) kernel
 $(eval loop=$(shell sudo losetup --partscan --find --show /os.img))
 $(shell sudo mount $(loop)p1 /mnt)
 $(shell sudo cp $(TARGET) /mnt/EFI/BOOT/BOOTx64.EFI)
 $(shell sudo cp kernel.elf /mnt)
 $(shell sudo umount /mnt)
 $(shell sudo losetup -d $(loop))
 $(shell sudo qemu-system-x86_64 -bios /usr/share/ovmf/OVMF.fd -drive file=/os.img,format=raw)
boot.so: $(OBJS)
 ld $(LDFLAGS) $(OBJS) -o $@ -lefi -lgnuefi
%.efi: %.so
 objcopy -j .text -j .sdata -j .data -j .dynamic \
 -j .dynsym -j .rel -j .rela -j .reloc \
 --target=efi-app-$(ARCH) $^ $@
```
asked Jul 28, 2022 at 20:13
\$\endgroup\$
1
  • \$\begingroup\$ Is UINTN used in "elf.h"? \$\endgroup\$ Commented Jul 30, 2022 at 15:16

1 Answer 1

1
\$\begingroup\$

Small review

Fully initialize

Consider

// OP's code. 3 members initialized.
Bootinfo bootinfo;
bootinfo.mmap = desc;
bootinfo.descsize = descsize;
bootinfo.mmapsize = mmapsize;
kmain(&bootinfo);

vs.

// All members initialized, even if more than 3.
Bootinfo bootinfo = { .mmap = desc, .descsize = descsize, .mmapsize = mmapsize};
kmain(&bootinfo);

The 2nd is easier to debug and less maintenance. Over different systems and time, the number of members may differ.

Avoid naked magic numbers

Why 2, 5, 12, 10000 in the below?

uefi_call_wrapper(BS->ExitBootServices, 2, IH, mapkey);
uefi_call_wrapper(BS->GetMemoryMap, 5, mapsize, *map, mapkey, descsize, dversion);
uint8_t elf[12];
UINTN bufsz = 10000;

Instead use a named constant.

#define ELF_N 12 // UEFI spec # 1.2.3
uint8_t elf[ELF_N];

This is especially valuable when the constant is used in multiple places.

answered Jul 30, 2022 at 15:13
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.