diff --git a/hellowrld.asm b/hellowrld.asm new file mode 100644 index 0000000..aeb05b6 --- /dev/null +++ b/hellowrld.asm @@ -0,0 +1,152 @@ +format pe64 dll efi +entry main + +; The UEFI calling convention is, as told in the documentation, the same +; as the standard cdecl but for convenience (because it looks like cdecl +; isn't that much standardized) here it is (in C-like notation): +; +; rax func(rcx, rdx, r8, r9, rsp+32, rsp+64, ...) +; +; basically the rest of arguments is passed through the stack with 32bit +; offset as it was designed for variadic functions + +; Also important fact about uefi is that uefi apps are called images. +; for further reading I think I recommend the official uefi documentation +; I'm not sure because maybe there might be better source + +; macro that saves the most important registers and calls the function +macro call_safe func +{ + push rcx + push rbx + call func + pop rbx + pop rcx +} + + +section '.text' code executable readable + +include 'uefi.inc' + +main: +; storing the args that the main function is called with +; main(*ImageHandle, *SystemTable) + mov [ImgHdl], rcx + mov [SysTbl], rdx + + sub rsp, 6*8+8 + +; storing the most used structures for faster use + mov rax, [rdx + EFI_SYSTEM_TABLE.BootServices] + mov [BootSrvc], rax + mov rax, [rdx + EFI_SYSTEM_TABLE.ConOut] + mov [Output], rax + +; preparing args and calling +; ConOut.SetAttribute(Output, BG_CLR | FG_CLR) + mov rax, EFI_BACKGROUND_BLACK or EFI_GREEN + + mov rcx, [Output] + call_safe [rcx + SIMPLE_TEXT_OUTPUT_INTERFACE.SetAttribute] + +; clearing the screen +; ConOut.ClearScreen(ConOut) + mov rcx, [Output] + call_safe [rcx + SIMPLE_TEXT_OUTPUT_INTERFACE.ClearScreen] + +; printing the initial message to indicate +; that the bootloader started successfully + mov rdx, BootMsg + call print + + +; TODO: Get and store framebuffer + +; Getting the memory map is kind of harder (not in this case), normally +; you would need to call the function two times to get the buffer size +; then allocate it (+ some padding because it will expand because of the +; allocation) and then call it again, but as I'm storing it somewhere +; in memory, I'm not using for now, I can just call it two times without +; the allocation. +; BootServices.GetMemoryMap( +; *MemMapSize, +; *MemMapBuffer, +; *MemMapKey, +; *MemMapDescSize, +; *MemMapDescVer) +.GetMemMap: + lea rcx, [MemMapSize] + mov rdx, [MemMapBuff] + lea r8, [MemMapKey] + lea r9, [MemMapDescSize] +; notice the last argument is moved to r10 and then "pushed" to the stack +; with 32bit offset + lea r10, [MemMapDescVer] + mov [rsp+32], r10 + + call [BootSrvc + EFI_BOOT_SERVICES_TABLE.GetMemoryMap] + +; if it returns buffer too small run the function again as it filled out +; the needed buffer length + cmp al, EFI_BUFFER_TOO_SMALL + je .GetMemMap + +; if it returned something else it maybe a problem possible TODO: proper +; error handling for now it just puts out error and exits with non-zero code + cmp rax, EFI_SUCCESS + jne .error + +; print a message saying that we didn't f**k up yet... + mov rdx, OKMsg + call print + +; preparing args and calling +; BootServices.ExitBootServices(*ImegeHandle, *MemMapKey) + mov rcx, [ImgHdl] + mov rdx, [MemMapKey] + + call_safe [BootSrvc + EFI_BOOT_SERVICES_TABLE.ExitBootServices] + + cmp rax, EFI_SUCCESS + jne .error + + mov eax, EFI_SUCCESS + jmp .exit + +.error: + mov rdx, ErrMsg + call print + mov eax, 1 + +.exit: + ret + +; print the string in rdx +print: + mov rcx, [Output] + call_safe [rcx + SIMPLE_TEXT_OUTPUT_INTERFACE.OutputString] + ret + +; Should not be reachable + hlt + jmp $ + +section '.data' data readable writeable + +ImgHdl dq ? +SysTbl dq ? +BootSrvc dq ? +Output dq ? + +MemMapSize dq 32768 +MemMapKey dq ? +MemMapDescSize dq ? +MemMapDescVer dq ? +MemMapBuff dq 0x220000 + +BootMsg du '[BOOT]: Booting the system...',13,10,0 +ErrMsg du '[BOOT]: There was an error :(',13,10,0 +OKMsg du '[BOOT]: Everything is OK :), for now...',13,10,0 + +section '.reloc' fixups data discardable