对call、retn的深入分析和认识

本文通过一个Delphi编写的简单程序,详细分析了在调试过程中call和retn指令对堆栈的影响。作者通过观察寄存器变化揭示了stdcall调用约定下函数调用的工作原理。

为了加深对call、retn的理解,我今天用delphi写了一个小程序,在OD中跟踪程序执行call及retn时堆栈的变化。

程序很简单:

procedure summ(b: string); var a: string; begin a := b; end; procedure TForm2.Button3Click(Sender: TObject); begin summ('bbbbbbbb'); end;

对应的汇编代码:

004A4D28 . B8 3C4D4A00 mov eax, 004A4D3C ; ASCII "bbbbbbbb"
004A4D2D . E8 9EFFFFFF call 004A4CD0
004A4D32 . C3 retn

004A4CD0 /$ 55 push ebp

...........

004A4D21 . 59 pop ecx
004A4D22 . 59 pop ecx
004A4D23 . 5D pop ebp
004A4D24 . C3 retn

执行到004A4D2D,esp=0013F630,F7进去后到达004A4CD0 也就是summ的第一句代码,esp=0013F62C,也就是说执行call xx系统进行了一个压栈操作!执行到 004A4D24时,esp=0013F62C(和执行到第一句代码时相等),执行了retn后,esp=0013F630,也就是说retn进行了一个出栈的动作!

另外,函数执行完毕的前后,不管是stdcall还是fastcall,都不保证寄存器不被修改!

总结:

1、执行到函数内的第一个语句时的esp值和到最后一个语句(也就是retn)的esp值是一样的:

004A4CD0 /$ 55 push ebp ;esp=0013F628
004A4CD1 |. 8BEC mov ebp, esp
004A4CD3 |. 5D pop ebp
004A4CD4 \. C2 0400 retn 4 ;esp=0013F628

2、retn 4表示pop 2次。

3、执行到call xx时的esp值与执行完xx时的esp值不一定相等,对stdcall的尤其是这样。

粗浅分析,不当之处还请指出。

<think> 我们讨论的是thiscall调用约定下的情况。根据引用[1]引用[2]: - thiscall是用于C++非静态成员函数的调用约定。 - 在Microsoft Visual C++中,this指针通过ECX寄存器传递(当参数个数固定时)。 - 参数传递方式:从右向左压栈。 - 堆栈平衡:如果参数个数固定,由被调用函数自己清理栈(即函数内部使用retn X);如果参数个数不确定(比如可变参数),则由调用者清理栈(此时函数内部使用retn,而调用者通过add esp, X来平衡栈)。 现在问题:在thiscall调用约定下,如果汇编代码末尾为retn 8,是否意味着传参2个? 分析: 1. this指针通过ECX寄存器传递,不占用栈空间。 2. 函数的其他参数通过栈传递,按照从右向左的顺序压栈。 3. 由于this指针不在栈上,所以栈上只有普通参数。 因此,在函数返回时,使用retn 8表示从栈上弹出8个字节(即两个4字节参数,因为32位系统中一般每个参数占4字节),所以意味着函数有2个栈参数。 但注意:这里有一个前提,即该函数是参数个数确定的函数(因为只有参数确定时,thiscall才由被调用函数清理栈,并且this指针通过ECX传递)。 结论: 在32位系统中,thiscall调用约定下,如果函数使用`retn 8`,通常表示函数有2个栈参数(每个4字节),加上通过ECX传递的this指针,所以总共有3个参数(this指针2个栈参数)。 举例说明: 假设有一个类成员函数:void MyClass::func(int a, int b); 调用时: mov ecx, this ; this指针放入ecx push b ; 第二个参数(从右向左,先压b) push a ; 第一个参数 call func 在func函数内部,返回时: retn 8 ; 清理栈上的两个参数(共8字节) 注意:栈上清理的是8字节,即两个4字节参数。this指针通过寄存器传递,不需要栈清理。 因此,回答用户的问题:是的,在thiscall调用约定下,`retn 8`意味着函数有2个栈参数(即总共3个参数,包括this指针)。 但是,需要特别注意:如果函数是可变参数函数,则thiscall会转为由调用者清理栈,此时函数内部不会使用`retn 8`,而是使用`retn`(不带数字),然后在调用者那里通过`add esp, 8`来清理。 所以,当我们看到`retn 8`时,我们可以确定: - 该函数是参数个数确定的函数(非可变参数)。 - 栈参数有2个(在32位系统,每个4字节,共8字节)。 引用[2]中提到:“如果参数个数确定,this指针通过ecx传递给被调用者;如果参数不确定,this指针在所有参数被压栈后压入栈堆” —— 注意,当参数不确定时,this指针会被压栈,此时栈上就会多一个参数(this指针),并且调用约定变为由调用者清理栈(类似cdecl)。所以这种情况下,函数返回时是`retn`(不带数字),而调用者会清理栈(包括栈上的this指针普通参数)。 因此,在参数不确定的情况下,即使我们看到函数返回时是`retn`,也不能简单地认为没有参数,因为栈上可能有多个参数(包括this指针普通参数)。 回到问题:在参数个数确定的情况下,`retn 8`表示2个栈参数(不包括this指针)。 所以,用户的问题的答案是肯定的:在thiscall调用约定下(且参数个数确定),`retn 8`说明函数有2个栈参数(即传参2个,不包括this指针)。 注意:这里“传参2个”指的是通过栈传递的参数有两个,而this指针通过寄存器传递,不计入栈参数。 因此,我们可以这样总结: 函数参数总数 = 栈参数个数 + 1(this指针) 其中,栈参数个数 = retn后面的数字 / 4(32位系统) 所以,`retn 8`对应的栈参数个数为2,函数参数总数为3(包括this指针)。 引用[1]引用[2]支持了上述分析。 最后,生成相关问题: </think> 在thiscall调用约定下,汇编代码`retn 8`**通常表示有2个栈传递的参数**(32位系统),但需要结合以下关键点综合分析: ### 📌 核心规则 1. **this指针通过寄存器传递** 根据引用[1][^1]引用[2][^2]: - this指针**固定通过ECX寄存器传递**,不占用栈空间 $$ \text{ECX} \leftarrow \text{this指针} $$ - 仅普通参数通过栈传递(从右向左压栈) 2. **`retn 8`的数学含义** - 在32位系统中,每个栈参数默认占 **4字节** $$ \text{栈参数数量} = \frac{8}{4} = 2 $$ - 例如:`void Class::func(int a, int b)`的汇编返回指令会是`retn 8`,清理两个int参数 3. **参数确定性的关键影响** - **参数确定时**:被调用函数清理栈(即`retn N`),如: ```assembly ; 调用代码 mov ecx, [this] ; this指针入ECX(非栈!) push 20h ; 参数2 (栈) push 10h ; 参数1 (栈) call Class_func ; 函数内返回 retn 8 ; 清理2个栈参数 ✔ ``` - **参数不确定时**:调用者清理栈(引用[2]),函数内用`retn`(无数字)[^2] ### ⚠️ 特殊情况验证 1. **64位系统差异** - 在x64架构中,前4个参数通常通过寄存器传递(含this) - `retn 8`可能仅表示清理**1个8字节参数**或栈对齐操作 2. **调试识别技巧** - 检查调用代码:观察`call`指令前的`push`次数(忽略`mov ecx`) - 验证栈平衡:调用后ESP/RSP应恢复初始值 ### 💎 结论 在**32位系统+参数确定**的场景下: > `retn 8` = **清理8字节栈空间** = **2个栈传递参数**(不包括ECX中的this指针)[^1][^2] 实际参数总数 = **栈参数数量 + 1**(this指针),例如: - `retn 12` → 3个栈参数 → 总参数=4(含this) --- ### 🔍 相关问题 1. 如何区分thiscall与stdcall调用约定的汇编特征? 2. 当函数使用可变参数时,thiscall的栈平衡机制会发生什么变化? 3. 在x64架构中,this指针的传递方式与32位有何本质区别? 4. 为什么C++成员函数的调用约定要单独设计thiscall
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值