从使用 nasm 制作的基本 PE+ uefi 应用程序调用 ExitBootServices 时遇到问题
Posted
技术标签:
【中文标题】从使用 nasm 制作的基本 PE+ uefi 应用程序调用 ExitBootServices 时遇到问题【英文标题】:Having trouble calling ExitBootServices from a bare-bones PE+ uefi application made with nasm 【发布时间】:2019-09-15 10:25:20 【问题描述】:我在 nasm 中创建了一个 PE+ UEFI 应用程序,并且能够使用 EFI_BOOT_SERVICES 打印文本、加载 GOP,当我调用 GetMemoryMap 时,返回值为 EFI_SUCCESS (0),我可以安全地返回 EFI shell我可以通过调用 ResetSystem 重新启动(虚拟)机器。
我无法让它成功退出启动服务。
我的目标是最终以 c 或汇编以外的语言(oberon,出于好奇)生成二进制文件,但要做到这一点,我必须能够创建一个最小可行的 uefi 二进制文件,以及此阶段的最后一步就是让 uefi 本身不碍事。
以下代码会打印“我们开始”,然后它不会打印“失败”,也不会关闭:
;default rel
org 0x8000000
section .header
DOS:
dd 0x00005a4d
times 14 dd 0
dd 0x00000080
times 16 dd 0
PECOFF:
dd `PE\0\0` ; sig
dw 0x8664 ; type
dw 3 ; sections
dd 0x5cba52f6 ; timestamp
dq 0 ; * symbol table + # symbols
dw osize ; oheader size
dw 0x202e ; characteristics
OHEADER:
dd 0x0000020b ; oheader + 0000 linker sig
dd 8192 ;codesize ; code size
dd 8192 ;datasize ; data size
dd 0 ; uninitialized data size
dd 4096 ; * entry
dd 4096 ; * code base
dq 0x8000000 ; * image base
dd 4096 ; section alignment
dd 4096 ; file alignment
dq 0 ; os maj, min, image maj, min
dq 0 ; subsys maj, min, reserved
dd 0x5000 ; image size
dd 4096 ; headers size
dd 0 ; checksum
dd 0x0040000A ; dll characteristics & subsystem
dq 0x10000 ; stack reserve size
dq 0x10000 ; stack commit size
dq 0x10000 ; heap reserve size
dq 0 ; heap reserve commit
dd 0 ; loader flags
dd 0x10 ; rva count
DIRS:
times 5 dq 0 ; unused
dd 0x8005000 ; virtual address .reloc
dd 0 ; size .reloc
times 10 dq 0 ; unused
OEND:
osize equ OEND - OHEADER
SECTS:
.1:
dq `.text` ; name
dd 8192 ;codesize ; virtual size
dd 4096 ; virtual address
dd 8192 ; raw data size
dd 4096 ; * raw data
dq 0 ; * relocations, * line numbers
dd 0 ; # relocations, # line numbers
dd 0x60000020 ; characteristics
.2:
dq `.data`
dd 8192 ;datasize
dd 12288
dd 8192
dd 12288
dq 0
dd 0
dd 0xC0000040
.3:
dq `.reloc`
dd 0
dd 20480
dd 0
dd 20480
dq 0
dd 0
dd 0x02000040
times 4096 - ($-$$) db 0 ;align the text section on a 4096 byte boundary
section .text follows=.header ; vstart=0x10001000
EFI_SUCCESS equ 0
EFI_SYSTEM_TABLE_SIGNATURE equ 0x5453595320494249
EFI_SYSTEM_TABLE_CONOUT equ 64
EFI_SYSTEM_TABLE_RUNTIMESERVICES equ 88
EFI_SYSTEM_TABLE_BOOTSERVICES equ 96
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_RESET equ 0
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OUTPUTSTRING equ 8
EFI_BOOT_SERVICES_GETMEMORYMAP equ 56
EFI_BOOT_SERVICES_LOCATEHANDLE equ 176
EFI_BOOT_SERVICES_LOADIMAGE equ 200
EFI_BOOT_SERVICES_EXIT equ 216
EFI_BOOT_SERVICES_EXITBOOTSERVICES equ 232
EFI_BOOT_SERVICES_LOCATEPROTOCOL equ 320
EFI_RUNTIME_SERVICES_RESETSYSTEM equ 104
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE equ 24
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE_FRAMEBUFFERBASE equ 32
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE_FRAMEBUFFERSIZE equ 40
sub rsp, 6*8
mov [Handle], rcx
mov [SystemTable], rdx
; find the interface to GOP
mov rax, [SystemTable]
mov rax, [rax + EFI_SYSTEM_TABLE_BOOTSERVICES]
mov rcx, EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID
mov rdx, 0
lea r8, [Interface]
call [rax + EFI_BOOT_SERVICES_LOCATEPROTOCOL]
mov rcx, [Interface]
mov rcx, [rcx + EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE]
mov rbx, [rcx + EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE_FRAMEBUFFERBASE]
mov [FB], rbx
mov rcx, [rcx + EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE_FRAMEBUFFERSIZE]
mov [FBS], rcx
cmp rax, EFI_SUCCESS
jne oops
g2:
lea rdx, [herewego]
mov rcx, [SystemTable]
mov rcx, [rcx + EFI_SYSTEM_TABLE_CONOUT]
call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OUTPUTSTRING]
; get the memory map
lea rcx, [MMSize]
lea rdx, [MMap]
lea r8, [MMKey]
lea r9, [MMDsz]
lea r10, [MMDsv]
push r10
mov rax, [SystemTable]
mov rax, [rax + EFI_SYSTEM_TABLE_BOOTSERVICES]
call [rax + EFI_BOOT_SERVICES_GETMEMORYMAP]
pop r10
cmp rax, EFI_SUCCESS
jne oops
; exit boot services
mov rcx, [Handle]
mov rdx, [MMKey]
mov rax, [SystemTable]
mov rax, [rax + EFI_SYSTEM_TABLE_BOOTSERVICES]
call [rax + EFI_BOOT_SERVICES_EXITBOOTSERVICES]
cmp rax, EFI_SUCCESS
je g5
; get the memory map
lea rcx, [MMSize]
lea rdx, [MMap]
lea r8, [MMKey]
lea r9, [MMDsz]
lea r10, [MMDsv]
push r10
mov rax, [SystemTable]
mov rax, [rax + EFI_SYSTEM_TABLE_BOOTSERVICES]
call [rax + EFI_BOOT_SERVICES_GETMEMORYMAP]
pop r10
cmp rax, EFI_SUCCESS
jne oops
; exit boot services
mov rcx, [Handle]
mov rdx, [MMKey]
mov rax, [SystemTable]
mov rax, [rax + EFI_SYSTEM_TABLE_BOOTSERVICES]
call [rax + EFI_BOOT_SERVICES_EXITBOOTSERVICES]
cmp rax, EFI_SUCCESS
jne oops
g5:
mov rcx, 2 ;EfiResetShutdown
mov rdx, EFI_SUCCESS ; return status
mov rax, [SystemTable]
mov rax, [rax + EFI_SYSTEM_TABLE_RUNTIMESERVICES]
call [rax + EFI_RUNTIME_SERVICES_RESETSYSTEM]
; add rsp, 6*8
; mov eax, EFI_SUCCESS
; retn
oops:
lea rdx, [fail]
mov rcx, [SystemTable]
mov rcx, [rcx + EFI_SYSTEM_TABLE_CONOUT]
call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OUTPUTSTRING]
jmp $-1
times 8192-($-$$) db 0
codesize equ $ - $$
section .data follows=.text ;vstart=0x10003000
Handle dq 0
SystemTable dq 0
Interface dq 0
FB dq 0
FBS dq 0
MMSize dq 4096
MMPtr dq 0x8004000
MMKey dq 0
MMDsz dq 48
MMDsv dq 0
EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID db 0xde, 0xa9, 0x42, 0x90, 0xdc, 0x23, 0x38, 0x4a
db 0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a
fail db __utf16__ `fail.\r\n\0`
nok db __utf16__ `Not OK.\r\n\0`
yok db __utf16__ `OK.\r\n\0`
herewego db __utf16__ `here we go\r\n\0`
times 4096-($-$$) db 0
MMap:
times 4096 db 0
datasize equ $ - $$
section .reloc follows=.data ;align=64
在上面的代码中,我调用了 GetMemoryMap 和 ExitBootServices 两次...我看到很多地方报告说,如果您第一次遇到错误,您必须再次调用它,因为内存映射可能会在第一次调用 ExitBootServices 时发生变化。
我已将整个内容放在https://github.com/charlesap/nasm-uefi,包括我与 QEMU 一起用于测试的 OVMF.fd 以及我正在使用的 qemu-system-x86_64 参数,如果这有助于了解我做错了什么.没什么大不了的,我正在尝试做一个非常基本的事情......
【问题讨论】:
您确定可以安全地覆盖 0x8004000 处的任何内容吗?设置 ImageBase 不能保证图像实际加载的位置。分配缓冲区比写入硬编码地址更安全。 我很确定,因为我在加载 .data 部分时将 804000 设置为 .data 部分中的空白空间。这是 MMap:如果事情按预期排列,则乘以 4096 db 0。我也试过用符号来引用它。但是感谢您检查我的一个假设......显然我认为我做对的事情实际上是错误的!我会采纳您的建议并尝试让 UEFI 分配空间。 (另外,我从对 GetMemoryMap 的调用中得到 EFI_SUCCESS 响应...) 在审查中,我明白你的观点,我不能相信图像实际上会落在 800000。我必须找到一种方法来测试它。 EFI 使用的 64 位 Microsoft 调用约定要求在调用之前在堆栈上分配 32 字节的影子空间(主空间)(然后您需要在调用后调整堆栈)调用删除那些 32 字节)。在我看来,您在大多数呼叫中都错过了这一点。您为 GETMEMORYMAP 执行此操作,但您不为退出服务或写入控制台执行此操作。另一个问题是堆栈需要在任何调用之前对齐 16 字节(这可能不会导致您立即出现问题,但 MS 64 位 ABI 需要它) 【参考方案1】:此版本不会在 exitbootservices 中挂起,并且可以写入帧缓冲区和/或通过调用 runtimeservices 来重置系统。如果 printhex 调用被删除,它会行为不端,所以很明显它是不正确的,但它确实会继续执行,这是……
bits 64
org 0x8000000
section .header
DOS:
dd 0x00005a4d
times 14 dd 0
dd 0x00000080
times 16 dd 0
PECOFF:
dd `PE\0\0` ; sig
dw 0x8664 ; type
dw 3 ; sections
dd 0x5cba52f6 ; timestamp
dq 0 ; * symbol table + # symbols
dw osize ; oheader size
dw 0x202e ; characteristics
OHEADER:
dd 0x0000020b ; oheader + 0000 linker sig
dd 8192 ;codesize ; code size
dd 8192 ;datasize ; data size
dd 0 ; uninitialized data size
dd 4096 ; * entry
dd 4096 ; * code base
dq 0x8000000 ; * image base
dd 4096 ; section alignment
dd 4096 ; file alignment
dq 0 ; os maj, min, image maj, min
dq 0 ; subsys maj, min, reserved
dd 0x5000 ; image size
dd 4096 ; headers size
dd 0 ; checksum
dd 0x0040000A ; dll characteristics & subsystem
dq 0x10000 ; stack reserve size
dq 0x10000 ; stack commit size
dq 0x10000 ; heap reserve size
dq 0 ; heap reserve commit
dd 0 ; loader flags
dd 0x10 ; rva count
DIRS:
times 5 dq 0 ; unused
dd 0x8005000 ; virtual address .reloc
dd 0 ; size .reloc
times 10 dq 0 ; unused
OEND:
osize equ OEND - OHEADER
SECTS:
.1:
dq `.text` ; name
dd 8192 ;codesize ; virtual size
dd 4096 ; virtual address
dd 8192 ; raw data size
dd 4096 ; * raw data
dq 0 ; * relocations, * line numbers
dd 0 ; # relocations, # line numbers
dd 0x60000020 ; characteristics
.2:
dq `.data`
dd 8192 ;datasize
dd 12288
dd 8192
dd 12288
dq 0
dd 0
dd 0xC0000040
.3:
dq `.reloc`
dd 0
dd 20480
dd 0
dd 20480
dq 0
dd 0
dd 0x02000040
times 4096 - ($-$$) db 0 ;align the text section on a 4096 byte boundary
section .text follows=.header
EFI_SUCCESS equ 0
EFI_SYSTEM_TABLE_SIGNATURE equ 0x5453595320494249
EFI_SYSTEM_TABLE_CONOUT equ 64
EFI_SYSTEM_TABLE_RUNTIMESERVICES equ 88
EFI_SYSTEM_TABLE_BOOTSERVICES equ 96
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_RESET equ 0
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OUTPUTSTRING equ 8
EFI_BOOT_SERVICES_GETMEMORYMAP equ 56
EFI_BOOT_SERVICES_LOCATEHANDLE equ 176
EFI_BOOT_SERVICES_LOADIMAGE equ 200
EFI_BOOT_SERVICES_EXIT equ 216
EFI_BOOT_SERVICES_EXITBOOTSERVICES equ 232
EFI_BOOT_SERVICES_LOCATEPROTOCOL equ 320
EFI_RUNTIME_SERVICES_RESETSYSTEM equ 104
sub rsp, 6*8
mov [Handle], rcx
mov [SystemTable], rdx
mov rax, [SystemTable]
mov rax, [rax + EFI_SYSTEM_TABLE_BOOTSERVICES]
mov [BS], rax
mov rax, [SystemTable]
mov rax, [rax + EFI_SYSTEM_TABLE_RUNTIMESERVICES]
mov [RTS], rax
lea rdx, [herewego]
mov rcx, [SystemTable]
mov rcx, [rcx + EFI_SYSTEM_TABLE_CONOUT]
call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OUTPUTSTRING]
; get the memory map
mov qword [memmapsize], 4096
lea rcx, [memmapsize]
lea rdx, [memmap]
lea r8, [memmapkey]
lea r9, [memmapdescsize]
lea r10, [memmapdescver]
mov [STK],rsp
push r10
sub rsp, 4*8
mov rbx, [BS]
call [rbx + EFI_BOOT_SERVICES_GETMEMORYMAP]
add rsp, 4*8
pop r10
mov rsp, [STK]
cmp rax, EFI_SUCCESS
jne oops
; find the interface to GOP
mov rbx, [SystemTable]
mov rbx, [rbx + EFI_SYSTEM_TABLE_BOOTSERVICES]
mov rcx, _EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID
mov rdx, 0
lea r8, [Interface]
call [rbx + EFI_BOOT_SERVICES_LOCATEPROTOCOL]
cmp rax, EFI_SUCCESS
jne oops
mov rcx, [Interface]
mov rcx, [rcx + 0x18 ] ;EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE
mov rbx, [rcx + 0x18 ] ;EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE_FRAMEBUFFERBASE
mov [FB], rbx
mov rcx, [rcx + 0x20 ] ;EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE_FRAMEBUFFERSIZE
mov [FBS], rcx
cmp rax, EFI_SUCCESS
jne oops
mov rbx, [FB]
push rax
push rcx
push rdx
call printhex
pop rdx
pop rcx
pop rax
mov rbx, [FBS]
push rax
push rcx
push rdx
call printhex
pop rdx
pop rcx
pop rax
; exit boot services
mov rcx, [Handle]
mov rdx, [memmapkey]
mov rbx, [SystemTable]
mov rbx, [rbx + EFI_SYSTEM_TABLE_BOOTSERVICES]
call [rbx + EFI_BOOT_SERVICES_EXITBOOTSERVICES]
cmp rax, EFI_SUCCESS
je g5
mov rbx, [memmapkey]
push rax
push rcx
push rdx
call printhex
pop rdx
pop rcx
pop rax
; repeat the call to get the memory map
mov qword [memmapsize], 4096
lea rcx, [memmapsize]
lea rdx, [memmap]
lea r8, [memmapkey]
lea r9, [memmapdescsize]
lea r10, [memmapdescver]
mov [STK],rsp
push r10
sub rsp, 4*8
mov rbx, [BS]
call [rbx + EFI_BOOT_SERVICES_GETMEMORYMAP]
add rsp, 4*8
pop r10
mov rsp, [STK]
cmp rax, EFI_SUCCESS
jne oops
mov rbx, [memmapkey]
push rax
push rcx
push rdx
call printhex
pop rdx
pop rcx
pop rax
; exit boot services again
mov rcx, [Handle]
mov rdx, [memmapkey]
xor r8, r8
mov rbx, [SystemTable]
mov rbx, [rbx + EFI_SYSTEM_TABLE_BOOTSERVICES]
call [rbx + EFI_BOOT_SERVICES_EXITBOOTSERVICES]
;cmp rax, EFI_SUCCESS
;je g5
;jmp oops
mov rcx, [FB]
mov rax, [FBS]
Q:
dec rax
mov byte[rcx+rax],255
jnz Q
W:
jmp W
g5:
mov rcx, 2 ;EfiResetShutdown
mov rdx, EFI_SUCCESS
mov rax, [SystemTable]
mov rax, [rax + EFI_SYSTEM_TABLE_RUNTIMESERVICES]
call [rax + EFI_RUNTIME_SERVICES_RESETSYSTEM]
oops:
lea rdx, [fail]
mov rcx, [SystemTable]
mov rcx, [rcx + EFI_SYSTEM_TABLE_CONOUT]
call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OUTPUTSTRING]
jmp $-1
printhex:
mov rbp, 16
.loop:
rol rbx, 4
mov rax, rbx
and rax, 0Fh
lea rcx, [_Hex]
mov rax, [rax + rcx]
mov byte [_Num], al
lea rdx, [_Num]
mov rcx, [SystemTable]
mov rcx, [rcx + EFI_SYSTEM_TABLE_CONOUT]
call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OUTPUTSTRING]
dec rbp
jnz .loop
lea rdx, [_Nl]
mov rcx, [SystemTable]
mov rcx, [rcx + EFI_SYSTEM_TABLE_CONOUT]
call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OUTPUTSTRING]
ret
times 8192-($-$$) db 0
codesize equ $ - $$
section .data follows=.text
Handle dq 0
SystemTable dq 0
Interface dq 0
BS dq 0
RTS dq 0
STK dq 0
FB dq 0
FBS dq 0
memmapsize dq 4096
memmapkey dq 0
memmapdescsize dq 48
memmapdescver dq 0
_EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID db 0xde, 0xa9, 0x42, 0x90, 0xdc, 0x23, 0x38, 0x4a
db 0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a
fail db __utf16__ `fail.\r\n\0`
nok db __utf16__ `Not OK.\r\n\0`
yok db __utf16__ `OK.\r\n\0`
herewego db __utf16__ `here we go\r\n\0`
_Hex db '0123456789ABCDEF'
_Num dw 0,0
_Nl dw 13,10,0
times 4096-($-$$) db 0
memmap:
times 4096 db 0
datasize equ $ - $$
section .reloc follows=.data
【讨论】:
好吧,当您在原始 GetMemoryMap 和 ExitBootServices 调用之间删除对printhex
的调用时,我看到的问题是,在这种情况下,ExitBootServices 可能会在第一次调用时通过 EFI_SUCCESS 成功。在这种情况下,您跳转到g5
,它会重置系统。我怀疑您的虚拟机在发生这种情况时会关闭。如果 ExitBootServices 在第一次调用时成功,您应该跳转到填充帧缓冲区的代码。只有当第一个 ExitBootServices 失败而第二个成功时,您才执行帧缓冲区填充。
附注。并不是说您的代码正在到达它,而是jmp $-1
不正确。您应该只执行jmp $
以进入无限循环。 $
将是当前指令开头的地址。从中减去一个可能会导致问题,具体取决于 JMP 之前的字节是什么。以上是关于从使用 nasm 制作的基本 PE+ uefi 应用程序调用 ExitBootServices 时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章
装机员U盘启动PE制作工具V5.0(UEFI+UD+首发自动安装MSDN版系统)