如何获取UEFI中当前运行镜像的设备路径?代码问题排查
问题:UEFI应用中获取同目录文件的设备路径失败
我正在编写一款UEFI应用,想要加载与当前应用同目录下的第二个应用。实现这个功能需要获取当前运行文件的完整路径,但我能访问的固件都没通过locateHandleBuffer提供SIMPLE_FILE_SYSTEM_PROTOCOL实例。
我的解决方案是获取当前应用的LOADED_IMAGE_PROTOCOL,从中提取设备路径(期望是文件路径媒体设备路径),但每次运行得到的设备路径都不一样:有的固件只是路径长度变化,有的则是设备路径类型变化,而且所有测试固件的设备类型都无效。另外应用还会崩溃,推测是地址获取错误,但找不到问题所在。
想请教:如何正确获取待加载文件的设备路径?是代码有问题,还是思路错了,或者两者都有?
相关代码片段
主逻辑代码
;Get the loaded image protocol for this handle mov rcx, [EFI_HANDLE] mov rdx, GUID_EFI_LOADED_IMAGE_PROTOCOL call getProtocolFromHandle ;If getting the protocol failed, exit cmp rax, [RETURN_SUCCESS] je print cmp rax, [RETURN_UNSUPPORTED] jne error mov rcx, [CONERR] mov rdx, protocolNotFound call printString jmp printFinalString print: ;If we have a loaded image protocol, then print information about the device path add rcx, [OFFSET_LOADED_IMAGE_DEVICE_PATH_PROTOCOL] mov r8, [rcx] mov r9, [rcx] mov r10, [rcx] and r8, [MASK_FIRST_BYTE] shr r8, 56 and r9, [MASK_SECOND_BYTE] shr r9, 48 and r10, [MASK_THIRD_FOURTH_BYTES] shr r10, 32 mov rcx, [CONOUT] mov rdx, devicePathOne call printString mov rdx, r8 call printNumber mov rdx, devicePathTwo call printString mov rdx, r9 call printNumber mov rdx, devicePathThree call printString mov rdx, r10 call printNumber mov rdx, newLine call printString
协议获取函数
;**************************************************** ;*** getProtocolFromHandle [BOOT FUNCTION ONLY] *** ;*** Definition: Gets the address of the protocol *** ;*** of the specified GUID from the image handle *** ;*** Input: rcx is the image handle *** ;*** rdx is the protocol's GUID *** ;*** Output: rcx is a pointer to the protocol *** ;*** interface installed on that handle *** ;***************************************************** getProtocolFromHandle: ;Save registers push rdx push r8 push r9 push r10 push r11 ;Call the function mov r8, ADDRESS_OPEN_PROTOCOL mov r9, [EFI_HANDLE] mov r10, 0 mov r11, [CONSTANT_OPEN_PROTOCOL_GET_PROTOCOL] push r11 ;Arguments to the stack must be pushed in reverse order push r10 sub rsp, 0x20 call [BOOT_SERVICES_OPEN_PROTOCOL] ;Restore registers add rsp, 0x20 pop r10 pop r11 pop r11 pop r10 pop r9 pop r8 pop rdx ;Prepare return values and return mov rcx, [ADDRESS_OPEN_PROTOCOL] ret
常量定义
OFFSET_LOADED_IMAGE_DEVICE_PATH_PROTOCOL dq 28 ADDRESS_OPEN_PROTOCOL dq 0 MASK_FIRST_BYTE dq 0xff00000000000000 MASK_SECOND_BYTE dq 0x00ff000000000000 MASK_THIRD_FOURTH_BYTES dq 0x0000ffff00000000
运行异常截图说明
- 截图1:设备路径类型为6,子类型为222/223,信息长度为随机数
- 截图2:设备路径类型、子类型、信息长度均为随机数
- 截图3:设备路径类型为11,无后续子类型信息,应用崩溃
问题分析与解决方案
一、思路没问题,但代码存在多处致命错误
你的核心思路(通过LOADED_IMAGE_PROTOCOL获取自身设备路径)是UEFI规范中推荐的正确方式,问题出在汇编代码的实现细节上:
1. getProtocolFromHandle函数的严重错误
- 栈平衡与寄存器恢复逻辑混乱:恢复阶段重复执行
pop r11和pop r10,直接破坏栈结构,导致后续所有寄存器值完全错误,这是崩溃和随机值的核心原因。 OpenProtocol参数传递完全错位:UEFI的OpenProtocol遵循System V AMD64调用约定,参数依次在rcx, rdx, r8, r9,剩余参数压栈。你既没有保留传入的ImageHandle和GUID,也没有按正确顺序传递参数,导致调用完全无效。- 返回值处理错误:
OpenProtocol的执行结果存在rax中,但你没有保存该值就直接覆盖,主逻辑中对rax的判断完全没有意义,根本无法确认协议是否获取成功。
2. 主逻辑中的设备路径解析错误
- 硬编码偏移量不可靠:UEFI结构体在不同固件中可能存在内存填充差异,硬编码
28作为DevicePath的偏移,大概率会访问到错误的内存地址,直接导致读取随机值。 - 设备路径解析逻辑错误:UEFI设备路径节点是小端序结构,前两个字节是
Type和SubType,接下来两个字节是Length。你用64位掩码读取这些8/16位字段,完全忽略字节序规则,读取结果必然错误。
二、修复后的实现步骤
1. 修正getProtocolFromHandle函数
严格遵循调用约定,修复栈平衡与参数传递:
getProtocolFromHandle: ; 保存非易失性寄存器 push rbx push rbp push r12 push r13 push r14 push r15 ; 设置OpenProtocol参数 ; rcx = ImageHandle (传入的rcx) ; rdx = ProtocolGuid (传入的rdx) mov r8, ADDRESS_OPEN_PROTOCOL ; 输出指针:*Protocol mov r9, 0 ; AgentHandle = NULL mov r10, 0 ; ControllerHandle = NULL mov r11, CONSTANT_OPEN_PROTOCOL_GET_PROTOCOL ; Attributes ; 压栈剩余参数(逆序) push r11 push r10 sub rsp, 0x20 ; 预留阴影空间 call [BOOT_SERVICES_OPEN_PROTOCOL] ; 恢复栈和寄存器 add rsp, 0x20 pop r10 pop r11 pop r15 pop r14 pop r13 pop r12 pop rbp pop rbx ; 返回值在rax,输出协议指针在[ADDRESS_OPEN_PROTOCOL] ret
2. 正确解析LOADED_IMAGE_PROTOCOL和设备路径
先定义结构体避免硬编码偏移,再按小端序解析设备路径:
; LOADED_IMAGE_PROTOCOL 结构体定义(遵循UEFI规范) LOADED_IMAGE_PROTOCOL struct Revision dq ? ParentHandle dq ? SystemTable dq ? DeviceHandle dq ? FilePath dq ? ; 目标设备路径指针 Reserved dq ? LoadOptionsSize dd ? LoadOptions dq ? ImageBase dq ? ImageSize dq ? ImageCodeType dd ? ImageDataType dd ? Unload dq ? LOADED_IMAGE_PROTOCOL ends ; 主逻辑中获取并解析设备路径 mov rcx, [EFI_HANDLE] mov rdx, GUID_EFI_LOADED_IMAGE_PROTOCOL call getProtocolFromHandle ; 检查调用结果 cmp rax, EFI_SUCCESS jne error_handle_loaded_image_failure ; 取出LoadedImage结构体指针,再获取FilePath mov rcx, [ADDRESS_OPEN_PROTOCOL] mov rcx, [rcx + LOADED_IMAGE_PROTOCOL.FilePath] ; 解析设备路径节点 parse_device_path: ; 读取Type(第一个字节) mov al, byte [rcx] ; 读取SubType(第二个字节) mov ah, byte [rcx + 1] ; 读取Length(小端序,双字节) mov cx, word [rcx + 2] ; 打印Type、SubType、Length... ; 移动到下一个节点 add rcx, rcx ; 检查是否为结束节点(Type=0xFF, SubType=0xFF) cmp al, 0xFF je parse_done jmp parse_device_path parse_done:
3. 从设备路径获取同目录文件
拿到正确设备路径后,按以下步骤操作:
- 调用
BootServices->LocateDevicePath获取对应的SIMPLE_FILE_SYSTEM_PROTOCOL实例(该接口比LocateHandleBuffer兼容性更好)。 - 打开文件系统根目录,解析当前应用的设备路径,定位到当前目录。
- 在当前目录下直接打开目标应用文件。
三、关键注意事项
- 永远不要硬编码UEFI结构体偏移,必须用结构体定义引用成员,避免固件差异导致错误。
- 严格遵循UEFI的System V AMD64调用约定,注意参数顺序、栈平衡和阴影空间的处理。
- 设备路径是小端序结构,解析时必须处理字节序问题。
- 所有UEFI服务调用后都要检查返回值,确保操作成功后再执行后续逻辑。
内容的提问来源于stack exchange,提问作者JD9999
相关产品推荐
相关产品推荐

