I can make a simple pe+ UEFI application with fasm or nasm, I can output text using SIMPLE_TEXT_OUTPUT_INTERFACE, I can get the memory map using EFI_BOOT_SERVICES, I can reset the system using EFI_SYSTEM_TABLE.RuntimeServices but I cannot get EFI_BOOT_SERVICES.ExitBootServices to work.

It's a miracle if your code does anything at all, the UEFI ABI is not like that. You can only pass 4 arguments in registers, the others must be in stack, and you must preserve space for those 4 arguments. Also you must pass the number of arguments as a first argument on every function call. Therefore the address of the memory key (required by ExitBootServices) is NOT passed in r8.

Check out the wiki Uefi.inc. There the uefi_call_wrapper macro counts the arguments and calls uefifunc with SysV-like ABI (passing argument count in AL, and the uefi func in RBX). It worth mentioning that it also calculates function addresses dynamically. Then uefifunc arranges the stack and registers properly and calls the real function using the fastcall-like UEFI ABI.

You can only pass 4 arguments in registers, the others must be in stack

he does this.

Quote:

Also you must pass the number of arguments as a first argument on every function call.

WHAT? it's BS. there is no such a thing in the UEFI spec.

As of the OP problem, you don't need to call both GetMemoryMap() and ExitBootServices() twice, it's wrong. I mean - testing only for zero (EFI_SUCCESS) after the first call to GetMemoryMap() is not enough. And also wrong to just duplicate code after the fail without any actions. The algorithm for dealing with this pair is like this:

The weird thing is that if I comment out either block where I am using printhex to output the memmapkey, the code hangs inside ExitBootServices. If I don't comment out the printhex calls I can go on with the uefi disabled and I can reboot the system, I can write to the framebuffer, etc. as one would expect.

You can only pass 4 arguments in registers, the others must be in stack

he does this.

Quote:

Also you must pass the number of arguments as a first argument on every function call.

WHAT? it's BS. there is no such a thing in the UEFI spec.

It kind of is in the UEFI spec. You need a 16 byte aligned stack, using the "C calling convention"

UEFI Spec wrote:

The caller passes the first four integer arguments in registers. The integer values are passed from left to right in Rcx, Rdx, R8, and R9 registers. The caller passes arguments five and above onto the stack. All arguments must be right-justified in the register in which they are passed. This ensures the callee can process only the bits in the register that are required.The caller passes arrays and strings via a pointer to memory allocated by the caller. The caller passes structures and unions of size 8, 16, 32, or 64 bits as if they were integers of the same size. The caller is not allowed to pass structures and unions of other than these sizes and must pass these unions and structures via a pointer.The callee must dump the register parameters into their shadow space if required. The most common requirement is to take the address of an argument.

The weird thing is that if I comment out either block where I am using printhex to output the memmapkey, the code hangs inside ExitBootServices. If I don't comment out the printhex calls I can go on with the uefi disabled and I can reboot the system, I can write to the framebuffer, etc. as one would expect.

Keep in mind that print (I mean Simple Text Output protocol) may or may not allocate temporary variables depending on it's arguments. Your memmapkey is only valid if the memory map does not change. Therefore you must avoid calling anything that may allocate or free memory between the GetMemMap and ExitBootServices, otherwise ExitBootServices will always return with an "Invalid key" error.

What you wrote seems to be quite the opposite, which is strange. Without digging deep into your code it looks like you pass the wrong argument somehow, and printhex somehow sets it in the correct register too. I would recommend to use a debugger and check the input arguments for ExitBootServices with and without printhex, inputs should be the same.

Why is it with some EFI calls you allocate the required space (32 bytes) before making a call and other places you don't. You need the shadow space allocated on the stack before each EFI call. Maybe I'm missing something, but most of your calls seem to have this missing. Maybe I have misread your code and you are doing it.

While I do make some room on the stack at the very beginning of my routine (that's the sub rsp, 6*8) I think maybe the garbage in the shadow space may be what is tripping up ExitBootServices. Perhaps. Gonna have to test that. That's the only way I figure the pushes and pops of printhex would make a difference.

Why is it with some EFI calls you allocate the required space (32 bytes) before making a call and other places you don't. You need the shadow space allocated on the stack before each EFI call. Maybe I'm missing something, but most of your calls seem to have this missing. Maybe I have misread your code and you are doing it.

No. Every function needs to do this only once (at its beginning). It allocates at the end of its stack (topmost) the number of slots for parameters. The number of slots is the number of parameters of a function from the set of functions, that this function calls, that has the biggest number of parameters. Say, here, the main function calls several functions, from which GetMemoryMap() has the largest count of parameters - 5. So, the main function allocates 5 slots in its stack frame for the parameters.

I looked deeper at the code. It shows not understanding the calling convention. If I remembered x86 asm, I'd write it, but. For example, you don't need all those manipulations with rsp before a function call.A stack frame of a function must be like this:[saved nonvolatile registers] --- highest address[your stack variables][argN] --- N the biggest number of a parameter from the set of functions this function will call[arg(N-1)]...[arg5][r9] --- slot for r9, just have it, not initialize[r8][rdx][rcx] --- the lowest address, rsp points here before any function call

sub rsp, 6*8+8 ; Stack is misaligned by 8 when control is transferred to ; the EFI entry point. In addition to the shadow space ; (32 bytes) and space for stack based paramaters to be ; saved - we also have to allocate an additional ; 8 bytes to ensure stack alignment on a 16-byte boundary ; 8+(6*8+8)=64, 64 is evenly divisible by 16 at this point

This code was also fixed so that when the first call to ExitBootServices succeeds it also fills the frame buffer (rather than shutting down). The main entry point should save and restore the volatile registers (and restore them when finished) hoeever given that your code has no path to get to the final `ret` it can be overlooked in this case.

Who is online

Users browsing this forum: No registered users and 16 guests

You cannot post new topics in this forumYou cannot reply to topics in this forumYou cannot edit your posts in this forumYou cannot delete your posts in this forumYou cannot post attachments in this forum