ASM_PFX(_ModuleEntryPoint)

这里主要是配置临时内存

  • 将临时内存(0x810000 ~ 0x820000)全部填入0x5AA55AA5
  • 配置栈
  • 跳转到C语言函数SecCoreStartupWithStack,开始执行C函数
#include <Base.h>

    SECTION .text

extern ASM_PFX(SecCoreStartupWithStack)

;
; SecCore Entry Point
;
; Processor is in flat protected mode
;
; @param[in]  EAX   Initial value of the EAX register (BIST: Built-in Self Test)
; @param[in]  DI    'BP': boot-strap processor, or 'AP': application processor
; @param[in]  EBP   Pointer to the start of the Boot Firmware Volume
; @param[in]  DS    Selector allowing flat access to all addresses
; @param[in]  ES    Selector allowing flat access to all addresses
; @param[in]  FS    Selector allowing flat access to all addresses
; @param[in]  GS    Selector allowing flat access to all addresses
; @param[in]  SS    Selector allowing flat access to all addresses
;
; @return     None  This routine does not return
;
global ASM_PFX(_ModuleEntryPoint)
ASM_PFX(_ModuleEntryPoint):

    ;
    ; Fill the temporary RAM with the initial stack value.
    ; The loop below will seed the heap as well, but that's harmless.
    ;
    mov     eax, FixedPcdGet32 (PcdInitValueInTempStack)      ; dword to store
    mov     edi, FixedPcdGet32 (PcdOvmfSecPeiTempRamBase)     ; base address,
                                                              ;   relative to
                                                              ;   ES
    mov     ecx, FixedPcdGet32 (PcdOvmfSecPeiTempRamSize) / 4 ; dword count
    cld                                                       ; store from base
                                                              ;   up
    rep stosd

    ;
    ; Load temporary RAM stack based on PCDs
    ;
    %define SEC_TOP_OF_STACK (FixedPcdGet32 (PcdOvmfSecPeiTempRamBase) + \
                          FixedPcdGet32 (PcdOvmfSecPeiTempRamSize))
    mov     eax, SEC_TOP_OF_STACK
    mov     esp, eax
    nop

    ;
    ; Setup parameters and call SecCoreStartupWithStack
    ;   [esp]   return address for call
    ;   [esp+4] BootFirmwareVolumePtr
    ;   [esp+8] TopOfCurrentStack
    ;
    push    eax
    push    ebp
    call    ASM_PFX(SecCoreStartupWithStack)

SECTION .text

SECTION 是汇编语言中用于划分程序逻辑区域的核心伪指令(指导汇编器处理程序结构,不直接生成机器码),其核心作用是将程序按内容类型(指令、数据等)分离,便于操作系统加载、内存管理和权限控制。

一、核心作用

  • 将程序划分为不同“段”(Section),每个段专门存储特定类型的内容(如指令、变量、常量等)。
  • 告诉汇编器和操作系统:“该段内容的类型、权限(读写/执行)及处理方式”。

二、常见段类型及特性(以NASM为例)

段名称 功能 核心特性 示例代码
.text 存放可执行指令 只读+可执行(防止指令被修改,允许CPU执行);程序的“执行核心”。 SECTION .text
global _start
_start: mov eax, 1
.data 存放已初始化的数据(变量、字符串等) 可读写;占用磁盘空间(因数据已初始化)。 SECTION .data
num dd 100(4字节变量,初始值100)
.bss 存放未初始化的数据(仅预留空间) 可读写;不占用磁盘空间(加载时分配内存并清零);适合缓冲区等场景。 SECTION .bss
buffer resb 1024(预留1024字节缓冲区)
.rodata 存放只读常量(不可修改的数据) 只读(修改会触发错误);用于保护常量(如字符串常量、固定数值)。 SECTION .rodata
prompt db 'Enter:', 0(只读字符串)

三、段的关键特性

  1. 权限控制
    操作系统通过段属性限制访问(如.text可执行但不可写,.rodata只读),是内存安全的基础。

  2. 链接与加载
    汇编器合并同类段,链接器分配内存地址,加载器按地址将段加载到内存对应区域。

  3. 内存分离
    指令、数据、常量分离存储,避免互相干扰(如防止数据修改意外破坏指令)。

四、跨汇编器差异

  • NASM:使用 SECTION(如 SECTION .text)。
  • GAS(GNU汇编器):使用 .section(如 .section .text)。
  • MASM(微软汇编器):使用 SEGMENT(如 .code 对应 .text 段)。
    核心逻辑一致,仅语法细节不同。

五、总结

SECTION 是汇编程序结构化的核心,通过划分不同段实现“指令-数据-常量”的分离存储和权限管理,既保证了程序的正确执行,也提高了内存安全性和效率,是理解汇编程序运行机制的基础。

ASM_PFX(_ModuleEntryPoint)

SecMain的入口函数,EDK2程序自定义的入口函数标识_ModuleEntryPoint

mov esp, eax

初始化栈

    ;
    ; Load temporary RAM stack based on PCDs
    ;
    %define SEC_TOP_OF_STACK (FixedPcdGet32 (PcdOvmfSecPeiTempRamBase) + \
                          FixedPcdGet32 (PcdOvmfSecPeiTempRamSize))
    mov     eax, SEC_TOP_OF_STACK
    mov     esp, eax
    nop

将临时内存的终止地址,存入栈指针esp中,作为栈底。完成了栈的初始化,此后,可以使用栈了

push eax

将栈起始的位置(栈底)第一个存入栈。

push ebp

将BFV的地址,第二个存入栈

call ASM_PFX(SecCoreStartupWithStack)

调用函数SecCoreStartupWithStack,然后将当前地址存入栈中。则此时栈中存放了

地址
临时内存的起始地址(PcdOvmfSecPeiTempRamBase)
栈顶(esp) call指令需要返回的地址(当前执行程序的地址)
esp + 4 BFV的地址
临时内存的终止地址/栈底(esp + 8) 栈的起始地址(SEC_TOP_OF_STACK)

call指令如何传参

call 指令本身并不负责传参

它的唯一功能是:

  1. 返回地址(即 call 指令下一条指令的地址)压入栈顶。
  2. 修改 EIP/RIP 寄存器,跳转到目标函数的地址。

传参这个动作,是由在 call 指令之前执行的代码完成的。具体如何传参,完全取决于你所使用的调用约定 (Calling Convention)

下面是 32位 和 64位 模式下主流传参约定的详细说明。


1. 32位 (x86) 模式下的参数传递(主要使用栈)

最常见的 32位 调用约定是 cdecl

规则:

  • 参数传递:所有参数通过来传递。
  • 入栈顺序:参数从右向左依次压入栈中。
  • 栈清理:由调用者负责在函数调用结束后调整栈指针。

汇编示例:
对于函数 add(5, 10)

; 1. 调用者准备参数(在CALL指令之前)
push 10         ; 先将第二个参数 (10) 压入栈(从右向左)
push 5          ; 再将第一个参数 (5) 压入栈

; 2. 执行CALL指令
call add        ; CPU将返回地址压栈,并跳转到add函数
                ; 此时栈顶是返回地址,往下是参数5和10

; 3. 调用者清理栈(在CALL指令之后)
add esp, 8      ; 栈指针ESP上移8字节(2个参数*4字节),丢弃参数,恢复栈平衡

在被调函数 add 内部,它通过 EBP 来访问参数:

add:
    push ebp             ; 保存旧的ebp
    mov  ebp, esp        ; 设置新的栈帧基址

    mov  eax, [ebp+8]    ; 访问第一个参数 (5)
    add  eax, [ebp+12]   ; 加上第二个参数 (10),结果在EAX中

    pop  ebp             ; 恢复旧的ebp
    ret                  ; 返回到调用者(返回地址在栈顶)

2. 64位 (x86-64) 模式下的参数传递(主要使用寄存器)

64位模式效率更高,优先使用寄存器传参。Unix/Linux(包括macOS)和Windows的约定不同。

a. Unix/Linux (System V AMD64 ABI)

规则:

  • 前6个整型或指针参数按顺序放入寄存器:
    RDI, RSI, RDX, RCX, R8, R9
  • 前8个浮点参数放入寄存器:
    XMM0, XMM1, …, XMM7
  • 更多参数则通过栈传递(从右向左压栈)。

汇编示例:
对于函数 add(5, 10)

; 1. 调用者准备参数(在CALL指令之前)
mov edi, 5      ; 第一个参数放入 EDI (5)
mov esi, 10     ; 第二个参数放入 ESI (10)

; 2. 执行CALL指令
call add        ; CPU将返回地址压栈,并跳转
                ; 注意:这里没有将参数压栈!

; 调用者无需清理寄存器

在被调函数 add 内部,它直接使用寄存器:

add:
    mov  eax, edi  ; 从EDI寄存器获取第一个参数
    add  eax, esi  ; 从ESI寄存器获取第二个参数并相加
    ret            ; 返回值在EAX/RAX中
b. Windows x64 Calling Convention

规则:

  • 前4个整型或指针参数按顺序放入寄存器:
    RCX, RDX, R8, R9
  • 前4个浮点参数放入寄存器:
    XMM0, XMM1, XMM2, XMM3
  • 更多参数则通过栈传递。

汇编示例:
对于函数 add(5, 10)

; 1. 调用者准备参数
mov ecx, 5      ; 第一个参数 -> RCX
mov edx, 10     ; 第二个参数 -> RDX

; 2. 执行CALL指令
call add

; 调用者无需清理寄存器

总结对比

特性 32位 (cdecl) 64位 (System V) 64位 (Windows)
call 指令前 push 参数(从右向左) movRDI, RSI... movRCX, RDX...
call 指令作用 压入返回地址,并跳转 压入返回地址,并跳转 压入返回地址,并跳转
call 指令后 add esp, N 清理栈 (无需操作) (无需操作)
函数内访问参数 通过 [ebp+8] 直接使用寄存器 直接使用寄存器

核心结论:
call 指令只是函数调用过程中的一个步骤(负责跳转和保存返回地址)。而传参工作是在 call 指令之前,由调用者按照特定调用约定(使用栈或寄存器)完成的。

参考:

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐