Как загрузить ядро тиа ELF через службы UEFI (gnu-efi)?

Я пытаюсь загрузить 64-битное ядро по адресу 0x1000, используя следующий код:
#include <elf.h>
EFI_FILE* LoadFile(EFI_FILE* Directory, CHAR16* Path, EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable){
  EFI_FILE* LoadedFile;

  EFI_LOADED_IMAGE_PROTOCOL* LoadedImage;
  SystemTable->BootServices->HandleProtocol(ImageHandle, &gEfiLoadedImageProtocolGuid, (void**)&LoadedImage);

  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* FileSystem;
  SystemTable->BootServices->HandleProtocol(LoadedImage->DeviceHandle, &gEfiSimpleFileSystemProtocolGuid, (void**)&FileSystem);

  if (Directory == NULL){
    FileSystem->OpenVolume(FileSystem, &Directory);
  }

  EFI_STATUS s = Directory->Open(Directory, &LoadedFile, Path, EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY);
  if (s != EFI_SUCCESS){
    return NULL;
  }
  return LoadedFile;
}

int LoadKernel() {
  EFI_FILE* Kernel = LoadFile(NULL, L"kernel.elf", ImageHandle, SystemTable);
  if (Kernel == NULL){
    Print(L"Could not load kernel \n");
    return 0;
  }
  else{
    Elf64_Ehdr header;
    Elf64_Phdr* phdrs;
    {
      Kernel->SetPosition(Kernel, header.e_phoff);
      UINTN size = header.e_phnum * header.e_phentsize;
      SystemTable->BootServices->AllocatePool(EfiLoaderData, size, (void**)&phdrs);
      Kernel->Read(Kernel, &size, phdrs);
    }
    for (
      Elf64_Phdr* phdr = phdrs;
      (char*)phdr < (char*)phdrs + header.e_phnum * header.e_phentsize;
      phdr = (Elf64_Phdr*)((char*)phdr + header.e_phentsize)
    )
    {
      switch (phdr->p_type){
        case PT_LOAD:
        {
          int pages = (phdr->p_memsz + 0x1000 - 1) / 0x1000;
          Elf64_Addr segment = phdr->p_paddr;
          SystemTable->BootServices->AllocatePages(AllocateAddress, EfiLoaderData, pages, &segment);

          Kernel->SetPosition(Kernel, phdr->p_offset);
          UINTN size = phdr->p_filesz;
          Kernel->Read(Kernel, &size, (void*)segment);
          break;
        }
      }
    }

    int (*KernelStart)() = ((__attribute__((sysv_abi)) int (*)() ) header.e_entry);
    Print(L"%d\r\n", KernelStart());
    Print(L"Kernel loaded at 0x1000\n");
    return 1;
    
  }
}


Ядро является int-функцией, возвращающей число, с этим проблем нет. Компиляция идет следующим образом:

Компиляция ядра
gcc -ffreestanding -fshort-wchar -c src/kernel.c -o tmp/kernel.o
ld -T kernel.ld -shared -Bsymbolic -nostdlib tmp/kernel.o -o bin/kernel.elf


kernel.ld
OUTPUT_FORMAT(elf64-x86-64)
ENTRY(kmain)

SECTIONS
{
	.text : ALIGN(0x1000)
	{
		*(.text)
	}
	.data : ALIGN(0x1000)
	{
		*(.data)
	}
	.rodata : ALIGN(0x1000)
	{
		*(.rodata)
	}
	.bss : ALIGN(0x1000)
	{
		*(COMMON)
		*(.bss)
	}
}


Компиляция загрузчика
gcc -I/usr/include/efi -I/usr/include/efi/x86_64 -I/usr/include/efi/protocol -fno-stack-protector -m64 -fPIE -fshort-wchar -mno-red-zone -DHAVE_USE_MS_ABI -c -o tmp/main.o src/main.c
ld -nostdlib -znocombreloc -T /usr/lib/elf_x86_64_efi.lds -shared -Bsymbolic -L /usr/lib /usr/lib/crt0-efi-x86_64.o tmp/main.o  -o tmp/bootx64.so -lefi -lgnuefi
objcopy -j .text -j .sdata -j .data -j .dynamic -j .dynsym  -j .rel -j .rela -j .reloc --target=efi-app-x86_64 tmp/bootx64.so bin/bootx64.efi

При компиляции никаких проблем и ошибок не наблюдается, но когда я пытаюсь выполнить эту строку
Print(L"%d\r\n", KernelStart());
или просто KernelStart(); - ничего не происходит, загрузчик зависает на месте. При этом, все предыдущие части отрабатывают корректно, и без этого вызова все завершается нормально
  • Вопрос задан
  • 165 просмотров
Решения вопроса 1
jcmvbkbc
@jcmvbkbc
"I'm here to consult you" © Dogbert
Я пытаюсь загрузить 64-битное ядро по адресу 0x1000

Я вижу в этом коде три проблемы:
1. AllocatePages только возвращает адрес выделенной памяти по адресу в последнем параметре. Т.е. куда будут загружены сегменты ELF сказать нельзя и, соответственно, без динамической релокации этот ELF скорее всего работать не будет.
2. Нет гарантии, что между блоками памяти выделенными через AllocatePages будет то же расстояние, что и между сегментами ELF, в то время как кодогенерация с флагом -fPIE вполне может считать это расснояние неизменным. Такой ELF нужно загружать в один большой блок памяти, сохраняя расстояние между сегментами.
3. В следствие пункта 1 запуск ядра по адресу header.e_entry -- это прыжок в неизвестность.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы