是否有64位UEFI ELF引导加载程序



我有ELF内核。所以我需要一个引导程序来加载我的64位ELF文件。我不需要过时的旧版BIOS引导程序,我需要带/不带GUI的UEFI引导程序。

我有ELF内核。所以我需要一个引导程序来加载我的64位ELF文件。

您有一个ELF内核;所以你可能需要一个引导加载程序:

  • 加载内核的ELF文件

  • 告诉内核关于内存映射

  • 告诉内核关于硬件的各种事情("扁平设备树"或ACPI表或…),可能包括帧缓冲区的细节。

  • 还加载其他文件(例如,初始RAM磁盘);因为(即使对于"模块化单片"内核)当内核还没有从磁盘加载磁盘驱动程序时,它也不能从磁盘加载驱动器。

  • 告诉内核某种内核配置(可以是另一个文件,也可以是"内核命令行参数")。

  • 谁知道还有什么(例如,我希望我的引导加载程序在其最终虚拟地址设置分页和映射内核;当磁盘IO缓慢时,解压缩值得考虑以改善引导时间;在信任内核文件之前进行健全性检查以查看内核文件是否已被篡改,…)。

换句话说;你需要一个关于任何/所有这些事情是如何发生的完整详细的规范(例如,内核如何从引导加载程序中检索内存映射,它采用的格式,如果有"内存映射中的2个或多个条目将不会描述内存的同一/重叠区域"之类的保证,等等);其中引导加载程序(或所有引导加载程序)和内核(或所有内核)都符合详细规范。

"内核是ELF";只是详细规范中的一小部分。

这给你留下了两个选择:

  • 找到一个由其他人设计的详细规范;内核是ELF";(或者至少"引导加载程序必须支持ELF"),并采用它们的规范,然后接受它们的所有设计决策,无论它们是否对您的操作系统有意义。这里(据我所知)唯一的选择是多引导规范。

  • 为您的操作系统创建自己的详细规范;然后编写自己的引导加载程序,或者让其他人根据您的规范编写它们。这是几乎每一个众所周知的内核(Windows、Linux、FreeBSD…)所做的。

注意1:通常情况下;一个引导加载程序";,但更像是一组(一个用于从"GPT分区硬盘"引导,一个用于从不网络引导,另一个用于自可移动媒体引导,…);引导加载程序";分成两半(处理差异的许多不同的"第一阶段",加上处理相似性的共同"第二阶段")。

注2:对于UEFI,您可以将UEFI用作";其他人设计的详细规范";并且不用麻烦拥有引导加载程序。在这种情况下,您必须将ELF转换为PE格式;或者插入你的ELF文件";"照原样";在PE文件中作为数据(其中PE文件有一些代码来解压缩其中包含的ELF)。

注3:理论上,它实际上是关于环境的;其中";引导加载程序";从一个环境(例如从UEFI)到另一个环境的改变(例如到"内核期望的环境");并且这些";改变环境的代码片段";可以是分层的(例如,可能是"BIOS->多引导->UEFI模拟器->其他东西->您的内核所期望的")。

注4:对于最后的";内核期望的环境";;可能值得考虑诸如";kexec()"其中使用内核的上一个实例来启动内核的下一个实例。这本身就有实际的好处(例如更快的内核更新),但考虑它的主要原因是改进"内核"的设计;内核期望的环境";(以帮助确定内核实际想要什么,并避免引入来自其他环境的包袱,而这些环境并不是您的内核真正想要的)。

TLDR:您最终可能会使用Multiboot2(和GRUB)。您可以在此处找到Multiboot2的规范:https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html

不久前,我在StackOverflow上问了一个问题,在那里我给出了x64 UEFI引导加载程序代码。这是供参考的代码:

#include <Uefi.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/GraphicsOutput.h>
#include <Library/BaseLib.h>
#define EFI_ACPI_TABLE_GUID { 0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d }}
#define EFI_ACPI_20_TABLE_GUID { 0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x0, 0x80, 0xc7, 0x3c, 0x88, 0x81 }} 
typedef struct {
CHAR8   Signature[8];
UINT8   Checksum;
UINT8   OemId[6];
UINT8   Revision;
UINT32  RsdtAddress;
UINT32  Length;
UINT64  XsdtAddress;
UINT8   ExtendedChecksum;
UINT8   Reserved[3];
} RSDP;
typedef struct {
UINT64 Address;
UINT64 Size;
UINT64 HorizontalResolution;
UINT64 VerticalResolution;
UINT64 PixelsPerScanLine;   
} FrameBuffer;
BOOLEAN CompareGUID(EFI_GUID rguid1, EFI_GUID rguid2){
return 
rguid1.Data1 == rguid2.Data1 &&
rguid1.Data2 == rguid2.Data2 &&
rguid1.Data3 == rguid2.Data3 &&
rguid1.Data4[0] == rguid2.Data4[0] &&
rguid1.Data4[1] == rguid2.Data4[1] &&
rguid1.Data4[2] == rguid2.Data4[2] &&
rguid1.Data4[3] == rguid2.Data4[3] &&
rguid1.Data4[4] == rguid2.Data4[4] &&
rguid1.Data4[5] == rguid2.Data4[5] &&
rguid1.Data4[6] == rguid2.Data4[6] &&
rguid1.Data4[7] == rguid2.Data4[7];
}
void MemCpy(void *dest, void *src, UINTN n){  
CHAR8* cdest = (CHAR8*) dest;
CHAR8* csrc = (CHAR8*) src;  
for (UINTN i = 0; i < n; i++) 
cdest[i] = csrc[i]; 
} 
EFI_STATUS EFIAPI UefiMain (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE  *SystemTable){
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Hello World!n");

/*Locate RSDP table*/
RSDP* rsdpPointer = NULL;
RSDP rsdp;
EFI_GUID AcpiGuid = EFI_ACPI_20_TABLE_GUID;
for (UINTN i = 0; i < SystemTable->NumberOfTableEntries; i++){
if (CompareGUID(SystemTable->ConfigurationTable[i].VendorGuid, AcpiGuid)){
CHAR8* TablePointer = (CHAR8*) SystemTable->ConfigurationTable[i].VendorTable;
if (TablePointer[0] == 'R' && TablePointer[1] == 'S' && TablePointer[2] == 'D' && TablePointer[3] == ' ' && 
TablePointer[4] == 'P' && TablePointer[5] == 'T' && TablePointer[6] == 'R' && TablePointer[7] == ' '){
rsdpPointer = (RSDP*)SystemTable->ConfigurationTable[i].VendorTable;
rsdp = *rsdpPointer;
}   
}
}
if (rsdpPointer == NULL){
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Could not locate the RSDP.n");
goto DONE;
}else{
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Found the RSDP.n");
}
RSDP* RSDPTable = (RSDP*)0x350000;
*RSDPTable = rsdp;


/*Get the kernel's file*/
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Booting the kernel.n");

EFI_STATUS Status;
EFI_BOOT_SERVICES* BS = SystemTable->BootServices;
EFI_GUID FSPGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
EFI_HANDLE* Handles = NULL;   
UINTN HandleCount = 0;
Status = BS->LocateHandleBuffer(ByProtocol, &FSPGuid, NULL, &HandleCount, &Handles);
if (EFI_ERROR(Status)){
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Could not locate the handle buffer.n");
goto DONE;
}else{
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Located the handle buffer for file system protocol.n");
}
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* FS = NULL; 
EFI_FILE_PROTOCOL* Root = NULL;
EFI_FILE_PROTOCOL* Token = NULL;
for (UINTN index = 0; index < (UINTN)HandleCount; index++)
{
Status = BS->HandleProtocol(Handles[index], &FSPGuid, (void**)&FS);
Status = FS->OpenVolume(FS, &Root);
Status = Root->Open(Root, &Token, L"kernel.elf", EFI_FILE_MODE_READ, EFI_FILE_MODE_WRITE);
if(!EFI_ERROR(Status))
break;
}
UINTN BufferSize = 100000;
CHAR8 KernelBuffer[100000];
Status = Token->Read(Token, &BufferSize, KernelBuffer);
if(EFI_ERROR(Status)){
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Located the kernel, but could not read from it.n");
goto DONE;
}else
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Could read the Kernel properly now jumping to it's entry point.n");

/*Get the frame buffer info*/
EFI_GUID GOPGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
EFI_GRAPHICS_OUTPUT_PROTOCOL* GOP;
Status = BS->LocateProtocol(&GOPGuid, NULL, (void**)&GOP);
//GOP->SetMode(GOP, GOP->Mode->MaxMode - 1);
FrameBuffer Video = {GOP->Mode->FrameBufferBase, GOP->Mode->FrameBufferSize, GOP->Mode->Info->HorizontalResolution,
GOP->Mode->Info->VerticalResolution, GOP->Mode->Info->PixelsPerScanLine};
FrameBuffer* VideoPointer = (FrameBuffer*)0x351000;
*VideoPointer = Video;

/*
Got the kernel's file in a buffer, now map it
in memory and jump to its entry point
*/
UINT64 EntryPoint;
MemCpy(&EntryPoint, KernelBuffer + 24, 8);
UINT64 ProgramHeaderPosition;
MemCpy(&ProgramHeaderPosition, KernelBuffer + 32, 8);
UINT16 NumberOfEntriesInProgramHeader;
MemCpy(&NumberOfEntriesInProgramHeader, KernelBuffer + 56, 2);
for (UINTN i = 0; i < NumberOfEntriesInProgramHeader; i++){
UINT32 SegmentType;
UINT64 SegmentDataPosition;
UINT64 SegmentVirtualAddress;
UINT64 SegmentSizeInFile;
UINT64 SegmentSizeInMemory;
MemCpy(&SegmentType, KernelBuffer + ProgramHeaderPosition, 4);
if (SegmentType == 1){
MemCpy(&SegmentDataPosition, KernelBuffer + ProgramHeaderPosition + 8, 8);
MemCpy(&SegmentVirtualAddress, KernelBuffer + ProgramHeaderPosition + 16, 8);
MemCpy(&SegmentSizeInFile, KernelBuffer + ProgramHeaderPosition + 32, 8);
MemCpy(&SegmentSizeInMemory, KernelBuffer + ProgramHeaderPosition + 40, 8);
CHAR8* VirtualAddress = (CHAR8*)SegmentVirtualAddress;
for (UINT64 i = 0; i < SegmentSizeInMemory; i++){
if (i < SegmentSizeInFile){
*(VirtualAddress + i) = *(KernelBuffer + SegmentDataPosition + i);  
}else{
*(VirtualAddress + i) = 0;
}
}
}
ProgramHeaderPosition += 56;
}

/*Final jump to the entry point*/
SystemTable->BootServices->ExitBootServices(ImageHandle, 0);
BASE_LIBRARY_JUMP_BUFFER JumpBuffer;
SetJump(&JumpBuffer);
JumpBuffer.Rip = EntryPoint;
LongJump(&JumpBuffer, 1);

DONE:
return EFI_SUCCESS;
}

该代码将RSDP放置在0x350000处,帧缓冲区上的信息放置在0x351000处。这只是我为简化将数据传递到RAM中静态位置的内核而做出的一个设计选择。

该代码为内核分配了10万字节的静态数据,这对您的内核来说可能不够。它将在硬盘ESP分区的根目录下启动任何静态的、独立的、名为kernel.ELF的ELF文件。

您可能需要使用AllocatePages来分配内核的缓冲区,而不是使用UEFI分配器。我发现,在内核达到一定大小后,静态分配会使UEFI应用程序崩溃。

如果你想了解如何编译代码的信息,请看我的答案:在linux 中构建edk2

此外,如果您决定编译引导程序并遇到任何问题,请直接询问我。我可以帮助解决任何问题。

相关内容

  • 没有找到相关文章

最新更新