You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取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. 从设备路径获取同目录文件

拿到正确设备路径后,按以下步骤操作:

  1. 调用BootServices->LocateDevicePath获取对应的SIMPLE_FILE_SYSTEM_PROTOCOL实例(该接口比LocateHandleBuffer兼容性更好)。
  2. 打开文件系统根目录,解析当前应用的设备路径,定位到当前目录。
  3. 在当前目录下直接打开目标应用文件。

三、关键注意事项

  • 永远不要硬编码UEFI结构体偏移,必须用结构体定义引用成员,避免固件差异导致错误。
  • 严格遵循UEFI的System V AMD64调用约定,注意参数顺序、栈平衡和阴影空间的处理。
  • 设备路径是小端序结构,解析时必须处理字节序问题。
  • 所有UEFI服务调用后都要检查返回值,确保操作成功后再执行后续逻辑。

内容的提问来源于stack exchange,提问作者JD9999

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 06:17:02