概述
X86和ARM函数调用中参数传递和返回值
arm属于RISC指令集,而x86则是CISC指令集的代表,编译器生成的结果比较有代表性。其中,参数传递和返回值是汇编/C混合编程比较关注的部分,尤其是在bootloader中编程中非常重要。总的来说,RISC倾向于寄存器穿参数,而x86则是通过堆栈传参数。而返回值则都通过效率最高的寄存器完成,arm中是r0,x86是eax。
以下的示例是通过写一个简单例子,并且反编译objdump来看生成的汇编来了解这些机制,呵呵,比较实验化,可是也只能这样了,编译技术也不是那么简单就能理解得了的。注意,这样,编译选项影响就可能比较大,我的实验仅局限在加/减-O选项,而其他的比较细节的就无能为力了。不过,这个方法对我理解参数和返回值这点来说,还是比较有效的。
返回值:
1) X86采用eax作为返回值。
return i; 2d: 89 c0 mov %eax,%eax
2) ARM使用r0作为返回值。
RETURN I; 4C: E1A00003 MOV R0, R3
参数传递
1)X86:主要是采用堆栈,除非指定以寄存器传递(通过"regparm (NUMBER)"注:NUMBER<=3指定)。
如果指定寄存器传递参数,则eax为第一个参数,edx为第二个参数, ecx为第三个参数。
int hello(int );
t=hello(13);
b: e8 fc ff ff ff call <hello>
2)ARM:寄存器到堆栈,首先将参数赋给r0, r1等,同时,未经优化的代码,在函数的堆栈中,也会为每个参数预留一个参数堆栈。
ARM的参数结构看起来比较奇怪,对其的解释是:出于效率考虑,如果在函数中的寄存器足够分配的话,则经过优化后,它不会进栈,而直接使用寄存器即可。这样的方式可以保证优化只局限于函数内部,实际上一般使用-O优化过的代码最终普遍在函数中不再进栈的。
int hello(int );
t=hello(13);
未优化:
14: ebfffffe bl <hello>
... ...
<hello>
......
3c: e50b0010 str r0, [fp, -#16]
4: e3a0000d mov r0, #13 ;
...
bl <hello>
...
1c: e1a0f00e mov pc, lr
汇编指令
SUB
sub ax,9 给ax减9,之后的结果赋值给ax
sub ax,bx 语意是ax = bx - ax
sub ax,[0] 将偏移地址为0的内存单元 - ax 再赋值给ax
LEA
LEA指令的功能是取偏移地址,例如LEA AX,[1000H],作用是将源操作数[1000H]的偏移地址1000H送至AX;
MOV
MOV指令的功能是传送数据,例如MOV AX,[1000H],作用是将1000H作为偏移地址,寻址找到内存单元,将该内存单元中的数据送至AX。
CMP
cmp是比较指令,cmp的功能相当于减法指令。它不保存结果,只是影响相应的标志位。其他的指令通过识别这些被影响的标志位来得知比较结果。 cmp指令格式: cmp 操作对象1, 操作对象2 计算 操作对象1 - 操作对象2 但不保存结果,只是根据结果修改相应的标志位。 举例假如此时eax = 0h 那么cmp eax, eax (eax - eax = 0) 此时我们的指令执行后, ZF标志位 = 1, PF = 0, SF =0 , CF =0 , OF = 0 。
数据常量定义
var_CC= byte ptr -0CCh var_8= dword ptr -8 argc= dword ptr 8 argv= dword ptr 0Ch envp= dword ptr 10h
函数调用
EBP是"基址指针"(BASE POINTER), 它最经常被用作高级语言函数调用的"框架指针"(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码:
push ebp ;保存当前ebp
mov ebp,esp ;EBP设为当前堆栈指针
sub esp, xxx ;预留xxx字节给函数临时变量.
...
这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的EBP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可.ESP 专门用作堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。在32位平台上,ESP每次减少4字节。
int __cdecl Sum(int a, int b) { push ebp // 保存上一层函数栈底指针 mov ebp,esp // 设置本层函数栈底指针 sub esp,0C0h // 设置本层函数栈顶指针 push ebx // 保存寄存器的值:ebx、esi、edi push esi push edi ...... } pop edi // 恢复寄存器的值:edi、esi、ebx(相反的顺序弹出) pop esi pop ebx mov esp,ebp // 恢复上层函数栈顶指针 pop ebp // 恢复上层函数栈底指针 ret // 没有栈平衡操作
所以在外面调用的地方平衡栈
call Sum (13611A9h) // 调用函数 add esp,8 // 调用方平衡堆栈(弹出参数) mov dword ptr [sum],eax // 返回值保存在eax中
如果在函数里面把栈平衡了,则调用的地方就不需要做了
int __stdcall Sum(int a, int b) { push ebp mov ebp,esp sub esp,0C0h push ebx push esi push edi ........ } pop edi pop esi pop ebx mov esp,ebp pop ebp ret 8 // 平衡栈操作,栈弹出8个字节,等价于esp += 8
在保存寄存器的后面,会有一段调试使用的指令(debug模式才有,release没有)push ebx;压入ebx push esi;压入esi push edi;压入edi
比如:lea edi, [ebp+var_CC] ;将申请的栈空间大小存入变址寄存器 EDI mov ecx, 33h ;循环次数33h,ecx = 33h(51),33h*4 = 0CCh mov eax, 0CCCCCCCCh ;重复在es:[edi]存入33h个;0CCCCCCCCh, Debug模式下把Stack上的变量初始化为0xcc,检查未初始化的问题(debug才有) rep stosd 【伪代码】 int stack_new_space[36h]; _asm { mov ecx, 36h; } for (int i = 0; i < ecx; i++) { stack_new_space[i] = 0xCCCCCCCC; }
检查堆栈平衡
在debug下, 函数调用结束,对ESP恢复后,有段编译器添加的堆栈平衡的功能: j_RTC_CheckEsp
参考:
1. 函数调用(ebp,esp,堆栈快照)
最后
以上就是谦让猎豹为你收集整理的汇编指令X86和ARM函数调用中参数传递和返回值 汇编指令数据常量定义函数调用检查堆栈平衡的全部内容,希望文章能够帮你解决汇编指令X86和ARM函数调用中参数传递和返回值 汇编指令数据常量定义函数调用检查堆栈平衡所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复