On 25/11/2023 06:13, wolfgang kern wrote:
> On 24/11/2023 18:59, James Harris wrote:
>
>> efi_main is:
>
>> ;Called with x64 ms abi
>> ; RCX = image
>> ; RDX = systab
>
> where to ??? address of this call ms abi is
> [rcx + efi_stop.OutputString] ???
>
No, take a look at the spec's section 2.3.4.1. Handoff State. It shows
that when UEFI calls an application it passes in the following.
Rcx - EFI_HANDLE
Rdx - EFI_SYSTEM_TABLE*
RSP - <return address>
IOW it passes us two parameters: an efi handle and something it calls
EFI_SYSTEM_TABLE*. The asterisk is C speak for 'pointer' so that second
parameter in RDX is a pointer to the efi system table.
The final part of the handoff state appears to mean only that RSP will
point to the return address.
>
>> global efi_main
>> efi_main:
>>
>> push rbp
>> mov rbp, rsp
>> sub rsp, 32
>> mov r10, rdi ;image
>> mov r11, rsi ;systab
>>
>> lea rdx, [rel msg_hello]
>> mov rcx, [r11 + efi_systab.conout]
>> mov rax, [rcx + efi_stop.OutputString]
>
>> call rax
>>
>> add rsp, 32
>> pop rbp
>> ret
>>
>> section .data
>>
>> msg_hello: dw __?utf16?__("Hello 64 UEFI..."), 13, 10, 0
> ...
>> So pushing RBP will realign the stack. But that needs to be revisited.
>
> yes, I'd try stack aligning from very start w/o using RBP at all,
>
> may the call return any useful parameter on the stack or in REGs ?
> or can we just ignore all of it.
Yes, have a read through section 2.3.4.2. Detailed Calling Conventions.
It includes "Return values that fix [which I guess is supposed to be
"fit"] into 64-bits are returned in the Rax register" etc.
>
> copied from below:
> > struc efi_stop ;UEFI simple text output protocol
> > .Reset: resq 1
> > .OutputString: resq 1
> > .TestString: resq 1
> > .QueryMode: resq 1
> > .SetMode: resq 1
> > .SetAttribute: resq 1
> > .ClearScreen: resq 1
> > .SetCursorPosition: resq 1
> > .EnableCursor: resq 1
> > .Mode: resq 1
> > endstruc
>
> how would this struct look in my notes: ?
> I assume resq 1 mean 64 bit pointers
Not quite. All that that code is doing is reserving space, not saying
what should be in that space (pointer, integer, float etc). Nasm strucs
are documented in
https://www.nasm.us/xdoc/2.16.01/html/nasmdoc5.html#section-5.9.1
but perhaps more useful would be the sizes:
resb - reserve 1 byte
resw - reserve 1 word (2 bytes as far as Nasm is concerned)
resd - reserve 1 doubleword (4 bytes in Nasm)
resq - reserve 1 quadword (8 bytes in Nasm)
>
> 000 qq reset
> 008 qq OutputString
> 010 qq ...
> ...
> 048 qq mode
>
> and how can we know the value for the call ?
> is there a thunk table somewhere or windoze styled call by "name".
I don't know what Windows does but since posting yesterday I realised
that I should have changed my code a bit (added better comments and used
some different registers) and I had missed off code which you would need
which I didn't write but which is brought in by the link step. I'll show
the final executable's full disassembly below.
The new 'application' code is
efi_main:
push r13
push r12
push rbp
mov rbp, rsp
sub rsp, 32
;Save the supplied parameters in caller-save registers
mov r12, rdi ;image
mov r13, rsi ;systab
;Set up parms for text output
lea rdx, [rel msg_hello]
mov rcx, [r13 + efi_systab.conout]
;Make the call
mov rax, [rcx + efi_sto.OutputString]
call rax
add rsp, 32
pop rbp
pop r12
pop r13
ret
and the full disassembly which includes the above code and also the key
_start entry point is
$ objdump bin/x64cpu/bootx64b.efi -d -Mintel
bin/x64cpu/bootx64b.efi: file format pei-x86-64
Disassembly of section .text:
0000000000002000 <_text>:
2000: 41 55 push r13
2002: 41 54 push r12
2004: 55 push rbp
2005: 48 89 e5 mov rbp,rsp
2008: 48 83 ec 20 sub rsp,0x20
200c: 49 89 fc mov r12,rdi
200f: 49 89 f5 mov r13,rsi
2012: 48 8d 15 e7 1f 00 00 lea rdx,[rip+0x1fe7] # 4000
<_data>
2019: 49 8b 4d 40 mov rcx,QWORD PTR [r13+0x40]
201d: 48 8b 41 08 mov rax,QWORD PTR [rcx+0x8]
2021: ff d0 call rax
2023: 48 83 c4 20 add rsp,0x20
2027: 5d pop rbp
2028: 41 5c pop r12
202a: 41 5d pop r13
202c: c3 ret
202d: 0f 1f 00 nop DWORD PTR [rax]
0000000000002030 <_start>:
2030: 48 83 ec 08 sub rsp,0x8
2034: 51 push rcx
2035: 52 push rdx
2036: 48 8d 3d c3 df ff ff lea rdi,[rip+0xffffffffffffdfc3]
# 0 <_text-0x2000>
203d: 48 8d 35 bc 2f 00 00 lea rsi,[rip+0x2fbc] # 5000
<_DYNAMIC>
2044: 59 pop rcx
2045: 5a pop rdx
2046: 51 push rcx
2047: 52 push rdx
2048: e8 13 00 00 00 call 2060 <_relocate>
204d: 5f pop rdi
204e: 5e pop rsi
204f: e8 ac ff ff ff call 2000 <_text>
2054: 48 83 c4 08 add rsp,0x8
0000000000002058 <.exit>:
2058: c3 ret
2059: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0]
0000000000002060 <_relocate>:
2060: f3 0f 1e fa endbr64
2064: 48 8b 06 mov rax,QWORD PTR [rsi]
2067: 48 85 c0 test rax,rax
206a: 74 7e je 20ea <_relocate+0x8a>
206c: 48 83 c6 08 add rsi,0x8
2070: 31 d2 xor edx,edx
2072: 45 31 c0 xor r8d,r8d
2075: 31 c9 xor ecx,ecx
2077: eb 1a jmp 2093 <_relocate+0x33>
2079: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0]
2080: 48 83 f8 07 cmp rax,0x7
2084: 74 72 je 20f8 <_relocate+0x98>
2086: 48 8b 46 08 mov rax,QWORD PTR [rsi+0x8]
208a: 48 83 c6 10 add rsi,0x10
208e: 48 85 c0 test rax,rax
2091: 74 1c je 20af <_relocate+0x4f>
2093: 48 83 f8 08 cmp rax,0x8
2097: 74 57 je 20f0 <_relocate+0x90>
2099: 48 83 f8 09 cmp rax,0x9
209d: 75 e1 jne 2080 <_relocate+0x20>
209f: 4c 8b 06 mov r8,QWORD PTR [rsi]
20a2: 48 8b 46 08 mov rax,QWORD PTR [rsi+0x8]
20a6: 48 83 c6 10 add rsi,0x10
20aa: 48 85 c0 test rax,rax
20ad: 75 e4 jne 2093 <_relocate+0x33>
20af: 48 89 d0 mov rax,rdx
20b2: 4c 09 c0 or rax,r8
20b5: 74 33 je 20ea <_relocate+0x8a>
20b7: 48 85 d2 test rdx,rdx
20ba: 74 44 je 2100 <_relocate+0xa0>
20bc: 4d 85 c0 test r8,r8
20bf: 74 3f je 2100 <_relocate+0xa0>
20c1: 48 85 c9 test rcx,rcx
20c4: 7e 24 jle 20ea <_relocate+0x8a>
20c6: 66 2e 0f 1f 84 00 00 cs nop WORD PTR [rax+rax*1+0x0]
20cd: 00 00 00
20d0: 83 7a 08 08 cmp DWORD PTR [rdx+0x8],0x8
20d4: 75 09 jne 20df <_relocate+0x7f>
20d6: 48 8b 02 mov rax,QWORD PTR [rdx]
20d9: 48 01 f8 add rax,rdi
20dc: 48 01 38 add QWORD PTR [rax],rdi
20df: 4c 29 c1 sub rcx,r8
20e2: 4c 01 c2 add rdx,r8
20e5: 48 85 c9 test rcx,rcx
20e8: 7f e6 jg 20d0 <_relocate+0x70>
20ea: 31 c0 xor eax,eax
20ec: c3 ret
20ed: 0f 1f 00 nop DWORD PTR [rax]
20f0: 48 8b 0e mov rcx,QWORD PTR [rsi]
20f3: eb 91 jmp 2086 <_relocate+0x26>
20f5: 0f 1f 00 nop DWORD PTR [rax]
20f8: 48 8b 16 mov rdx,QWORD PTR [rsi]
20fb: 48 01 fa add rdx,rdi
20fe: eb 86 jmp 2086 <_relocate+0x26>
2100: 48 b8 01 00 00 00 00 movabs rax,0x8000000000000001
2107: 00 00 80
210a: c3 ret
210b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]
Note that the code at the _start entry point for some reason calls
efi_main with the arguments in different registers.
Can we (or do we want to) remove the other code (which comes from
libraries at link time)? Maybe but I am aware of some caveats. We can
discuss them in subsequent posts, as needed. There's also more that you
might need to know in order to generate the PE file. But, again, that
needs another post.
>
> > In that, the OutputString field holds the address of the UEFI function
>
> THAT'S the question: how to find these function addresses ?
>
> > which can be called to write a string of 16-bit chars to the screen.
> > It is passed
> >
> > ECX = the STO table for the console (i.e. conout)
> > EDX = the string to print
>
>> One thing UEFI gives the booted code is a pointer to the System Table.
>> The table is documented in section 4. It starts with the following
>> fields. (I included only as far as I needed.)
>
> how. where and when will UEFI give me that?
>
Does the disassembly answer your remaining questions?
>
>> struc efi_systab ;UEFI system table
>> .hdr: resb efi_hdr_size
>> .fvend: resq 1 ;FirmwareVendor
>> .frev: resd 1 ;FirmwareRevision
>> resd 1 ;padding
>> .coninh: resq 1 ;ConsoleInHandle
>> .conin: resq 1 ;ConIn
>> .conouth: resq 1 ;ConsoleOutHandle
>> .conout: resq 1 ;ConOut
>> endstruc
>
>
>> The header is
>
>> struc efi_hdr ;Generic header
>> .Signature: resq 1
>> .Revision: resd 1
>> .HeaderSize: resd 1
>> .CRC32: resd 1
>> resd 1 ;reserved
>> endstruc
>
> ;can you please show me the above again in my notation
> don't mind I already got your type cast style :)
See above. Hopefully I've answered that question now but please say if not.
>
>> A note for anyone not familiar with C: where the spec in section 4.3.1
>> says
>>
>> CHAR16 *FirmwareVendor;
>> UINT32 FirmwareRevision;
>>
>> whereas FirmwareRevision in the second line is uint32, as you might
>> expect, the asterisk on the first line indicates that FirmwareVendor
>> is a /pointer/ to the stated type, char16, and so in this case the
>> field is 64-bit.
>
> now I'm totally confused yet, CHAR16 means 64 bit ?
> it seems it's just another 64 bit pointer.
No, in C terms FirmwareVendor is a pointer - i.e. an address - and what
it points to is of type char16.
Those are not my terms but Nasm's. I didn't know you weren't familiar
with them but hopefully it's all now clear.
--
James Harris