摘要:在 x86 汇编语言中,"RET" 和 "RETF" 是用于 从子程序(函数/过程)返回 的指令,但两者的返回方式不同,主要区别在于 是否涉及段寄存器(CS)的修改。以下是详细解析:
ret 与 RETF 指令详解
在 x86 汇编语言中,"RET" 和 "RETF" 是用于 从子程序(函数/过程)返回 的指令,但两者的返回方式不同,主要区别在于 是否涉及段寄存器(CS)的修改。以下是详细解析:
***
1. RET(Return)
(1) 功能
- 从近调用(Near Call)返回,即 段内返回。
- 仅修改 "EIP/RIP"(指令指针),不修改 "CS"(代码段寄存器)。
- 适用于 同一代码段内的函数调用。
(2) 语法
RET ; 无参数版本(从栈弹出返回地址)
RET imm16 ; 有参数版本(弹出返回地址后,额外调整栈指针)
- "imm16" 是一个 16 位立即数,表示 返回后栈指针(ESP/SP)的调整量(通常用于清理调用者压栈的参数)。
(3) 执行过程
1. 从栈顶弹出返回地址(32/64位模式下为 "EIP/RIP",16位模式下为 "IP")。
2. 如果指定了 "imm16",则 "ESP/SP += imm16"(清理调用者的参数)。
3. 继续执行 返回地址处的代码。
(4) 示例
无参数版本
; 调用者
call my_function ; 近调用(EIP压栈)
...
; 被调用者
my_function:
mov eax, 42
ret ; 弹出EIP,返回到call的下一条指令
有参数版本(清理栈参数)
; 调用者
push 10 ; 参数1
push 20 ; 参数2
call my_function
add esp, 8 ; 调用者清理栈(等价于RET 8在被调用者中)
; 被调用者
my_function:
mov eax, [esp+4] ; 读取参数1
mov ebx, [esp+8] ; 读取参数2
ret 8 ; 弹出EIP后,ESP += 8(清理2个4字节参数)
***
2. RETF(Far Return)
(1) 功能
- 从远调用(Far Call)返回,即 跨段返回。
- 同时修改 "CS"(代码段寄存器)和 "EIP/RIP"。
- 适用于 不同代码段之间的函数调用(如实模式下的跨段调用或保护模式下的特权级切换)。
(2) 语法
RETF ; 无参数版本(从栈弹出CS和EIP)
RETF imm16 ; 有参数版本(弹出CS和EIP后,额外调整栈指针)
- "imm16" 的作用与 "RET" 相同,用于调整栈指针。
(3) 执行过程
1. 从栈顶弹出 "EIP/RIP"(返回地址)。
2. 从栈顶弹出 "CS"(代码段选择子)。
3. 如果指定了 "imm16",则 "ESP/SP += imm16"。
4. 继续执行 "CS:EIP" 指向的代码。
(4) 示例
实模式下的跨段调用
; 调用者(远调用)
push cs ; 压入调用者的CS
push next_inst ; 压入返回地址(next_inst的偏移)
jmp far 0x1000:0 ; 远跳转到目标段(模拟远调用)
next_inst:
...
; 被调用者(在另一个段)
far_function:
mov ax, 1234
retf ; 弹出IP和CS,返回到next_inst
保护模式下的特权级切换
在操作系统内核中,"RETF" 可用于从 内核态(高特权级)返回到用户态(低特权级):
; 内核代码(特权级0)
mov ax, 0x23 ; 用户态数据段选择子(低特权级)
push ax ; 压入用户态SS
push esp ; 压入用户态ESP
pushf ; 压入EFLAGS
mov ax, 0x1B ; 用户态代码段选择子(低特权级)
push ax ; 压入用户态CS
push user_entry ; 压入用户态EIP
iret ; 中断返回(等效于RETF + 特权级切换)
***
3. RET 与 RETF 的关键区别
| 特性 | RET | RETF |
||||
| 返回类型 | 近返回(段内) | 远返回(跨段) |
| 修改的寄存器| "EIP/RIP" | "CS" + "EIP/RIP" |
| 栈操作 | 弹出 "EIP" | 弹出 "EIP" + "CS" |
| 典型用途 | 同一代码段的函数返回 | 跨段调用、特权级切换、实模式 |
| 参数清理 | 支持 "RET imm16" | 支持 "RETF imm16" |
***
4. 常见问题
Q1:何时使用 RETF?
- 实模式:当调用者和被调用者位于 不同的代码段 时(如 "CALL FAR")。
- 保护模式:在 特权级切换(如从内核返回到用户态)或 任务切换 时。
Q2:RET 和 RETF 如何影响栈?
- RET:仅弹出 "EIP"(32位下 4 字节,64位下 8 字节)。
- RETF:弹出 "EIP" + "CS"(32位下 8 字节,64位下 16 字节)。
Q3:如何手动模拟 RETF?
在不支持 "RETF" 的架构(如某些嵌入式系统)中,可以手动弹出 "CS" 和 "EIP":
pop eip ; 实际无此指令,需用间接跳转
pop cs ; 实际无此指令,需用远跳转
***
5. 总结
- "RET":用于 同一代码段内的函数返回,仅修改 "EIP/RIP"。
- "RETF":用于 跨段或特权级切换的返回,修改 "CS" 和 "EIP/RIP"。
- 参数清理:两者均支持 "RET imm16" 或 "RETF imm16" 形式,用于调整栈指针。
掌握 "RET" 和 "RETF" 的区别是理解 x86 调用约定和系统级编程 的关键!
来源:行走江湖的橙子