007SecMain_ASM_PFX(_ModuleEntryPoint)
这段汇编代码展示了EDK2固件中SecMain模块的入口点_ModuleEntryPoint的实现。代码主要完成以下功能:1) 初始化临时内存区域,用固定值填充;2) 设置临时栈空间,将栈顶地址存入ESP寄存器;3) 准备参数并调用SecCoreStartupWithStack函数,传递栈底地址和Boot Firmware Volume指针。整个过程为后续固件执行建立了基本的内存环境,其中栈空间布
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 (只读字符串) |
三、段的关键特性
-
权限控制:
操作系统通过段属性限制访问(如.text
可执行但不可写,.rodata
只读),是内存安全的基础。 -
链接与加载:
汇编器合并同类段,链接器分配内存地址,加载器按地址将段加载到内存对应区域。 -
内存分离:
指令、数据、常量分离存储,避免互相干扰(如防止数据修改意外破坏指令)。
四、跨汇编器差异
- 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
指令本身并不负责传参。
它的唯一功能是:
- 将返回地址(即
call
指令下一条指令的地址)压入栈顶。 - 修改
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 参数(从右向左) |
mov 到 RDI, RSI... |
mov 到 RCX, RDX... |
call 指令作用 |
压入返回地址,并跳转 | 压入返回地址,并跳转 | 压入返回地址,并跳转 |
call 指令后 |
add esp, N 清理栈 |
(无需操作) | (无需操作) |
函数内访问参数 | 通过 [ebp+8] 等 |
直接使用寄存器 | 直接使用寄存器 |
核心结论:call
指令只是函数调用过程中的一个步骤(负责跳转和保存返回地址)。而传参工作是在 call
指令之前,由调用者按照特定调用约定(使用栈或寄存器)完成的。
参考:
更多推荐
所有评论(0)