flat assembler
Message board for the users of flat assembler.

Index > OS Construction > Pushing relocatable addresses onto UEFI stack

Author
Thread Post new topic Reply to topic
pfranz



Joined: 13 Jan 2007
Posts: 116
Location: Italy
pfranz 17 Jan 2024, 03:34
When you call UEFI64 functions, parameters beyond 4 are pushed onto stack.
If they are relocatable addresses, pushing them with:

push qword Buffer
...
Buffer rq 1024

may not work. The push only takes 32bit value and sign-extends to 64bit onto the stack. So it works only if the address is relocated in the first 2GB of memory, because the upper 32 bits become 0.
When relocated between 2GB and 4GB, the upper 32 bits become 1 and the address is ruined.
Is there a way to ensure bootx64.efi loaded into the first 2GB? If not, to ensure it's loaded in the first 4GB?
And how can I detect that a parameter is a relocatable address in fasm?
Post 17 Jan 2024, 03:34
View user's profile Send private message Reply with quote
sinsi



Joined: 10 Aug 2007
Posts: 794
Location: Adelaide
sinsi 17 Jan 2024, 04:22
Code:
  lea rax,[Buffer]
  push rax    

Extra bonus, this is now position-independent code, since LEA should use RIP-relative addressing (as long as it is within 2GB)

Just curious, how does removing the qword qualifier work?
Post 17 Jan 2024, 04:22
View user's profile Send private message Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8359
Location: Kraków, Poland
Tomasz Grysztar 17 Jan 2024, 06:48
pfranz wrote:
And how can I detect that a parameter is a relocatable address in fasm?
Try:
Code:
if ~ Buffer relativeto 0    
Post 17 Jan 2024, 06:48
View user's profile Send private message Visit poster's website Reply with quote
pfranz



Joined: 13 Jan 2007
Posts: 116
Location: Italy
pfranz 17 Jan 2024, 06:52
Yes, using registers (either with lea or mov) works, I wanted to avoid it or at least give a warning when an address would be passed to "push".
Removing "qword" shouldn't change anything.
Post 17 Jan 2024, 06:52
View user's profile Send private message Reply with quote
sinsi



Joined: 10 Aug 2007
Posts: 794
Location: Adelaide
sinsi 17 Jan 2024, 06:58
LEA uses RIP-relative (PIC), MOV will use a relocatable address (not PIC)
Post 17 Jan 2024, 06:58
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20458
Location: In your JS exploiting you and your system
revolution 17 Jan 2024, 07:35
pfranz wrote:
YRemoving "qword" shouldn't change anything.
Using the qword override disables some of the optimisations and checks that fasm can do. In general if you can avoid using overrides it can make many things easier, and more robust.
Post 17 Jan 2024, 07:35
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8359
Location: Kraków, Poland
Tomasz Grysztar 17 Jan 2024, 07:50
revolution wrote:
pfranz wrote:
YRemoving "qword" shouldn't change anything.
Using the qword override disables some of the optimisations and checks that fasm can do. In general if you can avoid using overrides it can make many things easier, and more robust.
This applies when you use a size operator with immediate operand, it then enforces the large encoding of the immediate value (compare "or dword [eax],1" with "or [eax],dword 1", the latter produces longer opcode). Mnemonics like "pushq" were added in fasm 1 to allow ensuring the size without losing the optimization.

I consider this a legacy feature, which I dropped when implementing instruction encoders for fasm 2 - there the new instruction decorators would allow to control the encoding when really necessary (which is rare).
Post 17 Jan 2024, 07:50
View user's profile Send private message Visit poster's website Reply with quote
pfranz



Joined: 13 Jan 2007
Posts: 116
Location: Italy
pfranz 17 Jan 2024, 13:05
But in this case nothing changes, addresses need at least 32 bit and push only encodes 32 bits, can't encode 64 bits. You always get 68h + 32bit address.
PE64 stores 32bit for the address that is more than enough when assembling; but what happens if that address gets relocated beyond 4GB? Push only have space for 32 bits.
Post 17 Jan 2024, 13:05
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20458
Location: In your JS exploiting you and your system
revolution 17 Jan 2024, 13:22
This is what happens for values out of range for push.
Code:
push 1 shl 31
processed: push 1 shl 31
error: value out of range.    
Also note that you can't push relocatable addresses unless you use RIP relative code, as mentioned by sinsi above. An address that is relocatable needs all 64-bits.
Post 17 Jan 2024, 13:22
View user's profile Send private message Visit poster's website Reply with quote
pfranz



Joined: 13 Jan 2007
Posts: 116
Location: Italy
pfranz 17 Jan 2024, 14:17
revolution wrote:
This is what happens for values out of range for push.
Code:
push 1 shl 31
processed: push 1 shl 31
error: value out of range.    
Also note that you can't push relocatable addresses unless you use RIP relative code, as mentioned by sinsi above. An address that is relocatable needs all 64-bits.
I pushed relocatable addresses (see my initial post) without any error.FASM doesn't complain because at assemble time the value is small. It may get beyond 32 bits at runtime, with relocation.
Also, using RIP relative code is not the only solution. You can also use

mov r64, relocatable address
push r64

and it will work because that kind of mov encodes all 64 bits and relocation works fine.
But you still must use registers, what I wanted to avoid
Post 17 Jan 2024, 14:17
View user's profile Send private message Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8359
Location: Kraków, Poland
Tomasz Grysztar 17 Jan 2024, 14:17
revolution wrote:
Also note that you can't push relocatable addresses unless you use RIP relative code, as mentioned by sinsi above. An address that is relocatable needs all 64-bits.
While fasm used to disallow this, it was later changed on a request.
Post 17 Jan 2024, 14:17
View user's profile Send private message Visit poster's website Reply with quote
pfranz



Joined: 13 Jan 2007
Posts: 116
Location: Italy
pfranz 17 Jan 2024, 14:22
Tomasz Grysztar wrote:
revolution wrote:
Also note that you can't push relocatable addresses unless you use RIP relative code, as mentioned by sinsi above. An address that is relocatable needs all 64-bits.
While fasm used to disallow this, it was later changed on a request.
From a first quick read, I understand that you rely on the linker to warn about the problem.
There is no linker involved when I produce bootx64.efi with "format pe64 efi".
I believe a warning from fasm is needed.
Post 17 Jan 2024, 14:22
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20458
Location: In your JS exploiting you and your system
revolution 17 Jan 2024, 15:57
pfranz wrote:
I pushed relocatable addresses (see my initial post) without any error.FASM doesn't complain because at assemble time the value is small.
Then I used the wrong word. Instead of "can't" then I should have said "shouldn't". If the OS decides to relocate to 0x1_0000_0000 then your "push imm" is impossible to be correct. It makes to code very fragile.

Why is there difficulty with using registers?


Last edited by revolution on 17 Jan 2024, 16:42; edited 1 time in total
Post 17 Jan 2024, 15:57
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8359
Location: Kraków, Poland
Tomasz Grysztar 17 Jan 2024, 16:37
If you try fasm 2, you can disable the option of making 32-bit relocations by adding a "purge dword?" line after the format setting:
Code:
format PE64
purge dword?    ; disables 32-bit relocations with fasm 2, has no effect on fasm 1

data fixups
end data

        mov     rax,$   ; 64-bit relocation
        push    $       ; 32-bit relocation    
You could also easily add DISPLAY command to the macro, to make it show a warning whenever it becomes used.
Post 17 Jan 2024, 16:37
View user's profile Send private message Visit poster's website Reply with quote
pfranz



Joined: 13 Jan 2007
Posts: 116
Location: Italy
pfranz 17 Jan 2024, 17:33
revolution wrote:
Why is there difficulty with using registers?
I am writing a general purpose macro to call uefi functions, sooner or later I will forget that behind certain parameters there is a push on the stack and will pass a relocatable address directly, without getting any error.
It just happened: my loader worked fine everywhere, then I tried it on a 4GB machine and it would get stuck. It took me some time to find the problem out.
Post 17 Jan 2024, 17:33
View user's profile Send private message Reply with quote
bzt



Joined: 09 Nov 2018
Posts: 79
bzt 17 Jan 2024, 19:40
pfranz wrote:
I am writing a general purpose macro to call uefi functions
I've already wrote that macro it is not as simple as you might think. First, you need to call InitializeLib to save the pointers, then you can use the uefi_call_wrapper macro to call UEFI functions (but a macro alone not enough, this needs a helper function too so that fastcall ABI stack requirements could be fulfilled). Public Domain, use as you please.
Code:
macro InitializeLib
{
                clc
                or                      rdx, rdx
                jz                      .badout
                cmp                     dword [rdx], 20494249h
                je                      @f
.badout:          xor                   rcx, rcx
                xor                     rdx, rdx
                stc
@@:             mov                     [efi_handler], rcx              ; ImageHandle
                mov                     [efi_ptr], rdx                  ; pointer to SystemTable
}
 
;invoke an UEFI function
macro uefi_call_wrapper                 interface,function,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11
{
numarg = 0
if ~ arg11 eq
 numarg = numarg + 1
 if ~ arg11 eq rdi
                mov                     rdi, arg11
 end if
end if
if ~ arg10 eq
 numarg = numarg + 1
 if ~ arg10 eq rsi
                mov                     rsi, arg10
 end if
end if
if ~ arg9 eq
 numarg = numarg + 1
 if ~ arg9 eq r14
                mov                     r14, arg9
 end if
end if
if ~ arg8 eq
 numarg = numarg + 1
 if ~ arg8 eq r13
                mov                     r13, arg8
 end if
end if
if ~ arg7 eq
 numarg = numarg + 1
 if ~ arg7 eq r12
                mov                     r12, arg7
 end if
end if
if ~ arg6 eq
 numarg = numarg + 1
 if ~ arg6 eq r11
                mov                     r11, arg6
 end if
end if
if ~ arg5 eq
 numarg = numarg + 1
 if ~ arg5 eq r10
                mov                     r10, arg5
 end if
end if
if ~ arg4 eq
 numarg = numarg + 1
 if ~ arg4 eq r9
                mov                     r9, arg4
 end if
end if
if ~ arg3 eq
 numarg = numarg + 1
 if ~ arg3 eq r8
                mov                     r8, arg3
 end if
end if
if ~ arg2 eq
 numarg = numarg + 1
 if ~ arg2 eq rdx
                mov                     rdx, arg2
 end if
end if
if ~ arg1 eq
 numarg = numarg + 1
 if ~ arg1 eq rcx
  if ~ arg1 in <ConsoleInHandle,ConIn,ConsoleOutHandle,ConOut,StandardErrorHandle,StdErr,RuntimeServices,BootServices>
                mov                     rcx, arg1
  end if
 end if
end if
                xor                     rax, rax
                mov                     al, numarg
if interface in <ConsoleInHandle,ConIn,ConsoleOutHandle,ConOut,StandardErrorHandle,StdErr,RuntimeServices,BootServices>
                mov                     rbx, [efi_ptr]
                mov                     rbx, [rbx + EFI_SYSTEM_TABLE.#interface]
else
 if ~ interface eq rbx
                mov                     rbx, interface
 end if
end if
if arg1 in <ConsoleInHandle,ConIn,ConsoleOutHandle,ConOut,StandardErrorHandle,StdErr,RuntimeServices,BootServices>
                mov                     rcx, rbx
end if
if defined SIMPLE_INPUT_INTERFACE.#function
                mov                     rbx, [rbx + SIMPLE_INPUT_INTERFACE.#function]
else
 if defined SIMPLE_TEXT_OUTPUT_INTERFACE.#function
                mov                     rbx, [rbx + SIMPLE_TEXT_OUTPUT_INTERFACE.#function]
 else
  if defined EFI_BOOT_SERVICES_TABLE.#function
                mov                     rbx, [rbx + EFI_BOOT_SERVICES_TABLE.#function]
  else
   if defined EFI_RUNTIME_SERVICES_TABLE.#function
                mov                     rbx, [rbx + EFI_RUNTIME_SERVICES_TABLE.#function]
   else
    if defined EFI_GRAPHICS_OUTPUT_PROTOCOL.#function
                mov                     rbx, [rbx + EFI_GRAPHICS_OUTPUT_PROTOCOL.#function]
    else
     if defined EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE.#function
                mov                     rbx, [rbx + EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE.#function]
     else
                mov                     rbx, [rbx + function]
     end if
    end if
   end if
  end if
 end if
end if
                call                    uefifunc
}
 
;*********************************************************************
;*                       Library functions                           *
;*********************************************************************
 
section '.text' code executable readable
 
uefifunc:       ;save stack pointer
                mov                     qword [uefi_rsptmp], rsp
                ;set up new aligned stack
                and                     esp, 0FFFFFFF0h
                ;alignment check on arguments
                bt                      eax, 0
                jnc                     @f
                push                    rax
                ;arguments
@@:             cmp                     al, 11
                jb                      @f
                push                    rdi
@@:             cmp                     al, 10
                jb                      @f
                push                    rsi
@@:             cmp                     al, 9
                jb                      @f
                push                    r14
@@:             cmp                     al, 8
                jb                      @f
                push                    r13
@@:             cmp                     al, 7
                jb                      @f
                push                    r12
@@:             cmp                     al, 6
                jb                      @f
                push                    r11
@@:             cmp                     al, 5
                jb                      @f
                push                    r10
@@:             ;space for
                ;r9
                ;r8
                ;rdx
                ;rcx
                sub                     rsp, 4*8
                ;call function
                call                    rbx
                ;restore old stack
                mov                     rsp, qword [uefi_rsptmp]
                ret
 
section '.data' data readable writeable
efi_handler:    dq                      0
efi_ptr:        dq                      0
uefi_rsptmp:    dq                      0
    
Post 17 Jan 2024, 19:40
View user's profile Send private message Reply with quote
pfranz



Joined: 13 Jan 2007
Posts: 116
Location: Italy
pfranz 17 Jan 2024, 21:08
I had started from yours, but modified it because it was too cumbersome, in my opinion, and most of all trashes too many registers. You don't have my problem because you use a register for each parameter, even those pushed on the stack, but this creates a mess when you use a register as argument.
It may be difficult to figure out what happens in these cases:

mov r10, 57
uefi_call interface, function, r10, 1,2,3,4

With your code, you think you pass 57 as first parameter, but you are actually passing 4.
Post 17 Jan 2024, 21:08
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20458
Location: In your JS exploiting you and your system
revolution 17 Jan 2024, 21:13
There is a simple fix that doesn't trash any registers.
Code:
        push    rax
        lea     rax,[arg]
        xchg    rax,[rsp]    
Post 17 Jan 2024, 21:13
View user's profile Send private message Visit poster's website Reply with quote
pfranz



Joined: 13 Jan 2007
Posts: 116
Location: Italy
pfranz 17 Jan 2024, 23:52
revolution wrote:
There is a simple fix that doesn't trash any registers.
Code:
        push    rax
        lea     rax,[arg]
        xchg    rax,[rsp]    
This can't be used in a macro because you never know if a parameter is an address.
But you can use mov instead of lea, that works. I was trying to do something simpler, that is why I asked whether it was possible to force bootx64.efi to be loaded in the first 2GB.
In the first 4GB you may use

push arg
mov dword [rsp+4], 0

which is simpler but you have to test whether arg is an immediate negative number, a 64-bit memory reference, a register ... (in which case you skip the mov), or else make sure it is a relocatable address with relativeto 0 as Tomasz suggested. Either way, it's not so simple.
Post 17 Jan 2024, 23:52
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.