从汇编语言的角度看形参变量和实参变量

在编程中,如果能够在汇编级别弄懂具体每条语句的内部实现,就会避免很多错误的发生,同时也能更加清楚合理的去调用内存空间。因此我们以一段代码为例,来从汇编语言的角度浅谈该段代码的函数调用关系和其具体的形参变量和实参变量之间的对应关系。

#include <stdio.h>

void fun1(double x);
int fun2(int a, int d, int c);

void fun1(double x) {
    printf("%lf\n",x);
}

int fun2(int a, int b, int c) {
    int tmp;
    tmp = a + b + c;
    ++tmp;
    
    return tmp;
}
int main() {
    int one = 1;
    int two = 2;
    int three = 3;
    
    fun1(3.14);
    one += two;
    printf("%d\n",fun2(one, two, three));
    
    return 0;    

}

==================================================================

其在VC环境下所产生的汇编源代码如下:

    TITLE    aboutVirable.c
    .386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT    SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT    ENDS
_DATA    SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA    ENDS
CONST    SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST    ENDS
_BSS    SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS    ENDS
_TLS    SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS    ENDS
FLAT    GROUP _DATA, CONST, _BSS
    ASSUME    CS: FLAT, DS: FLAT, SS: FLAT
endif
PUBLIC    _fun2
_TEXT    SEGMENT
_a$ = 8
_b$ = 12
_c$ = 16
_tmp$ = -4
_fun2    PROC NEAR

; 6    : int fun2(int a, int b, int c) {

    push    ebp
    mov    ebp, esp
    push    ecx

; 7    :     int tmp;
; 8    :     
; 9    :     tmp = a + b + c;

    mov    eax, DWORD PTR _a$[ebp]
    add    eax, DWORD PTR _b$[ebp]
    add    eax, DWORD PTR _c$[ebp]
    mov    DWORD PTR _tmp$[ebp], eax

; 10   :     ++tmp;

    mov    ecx, DWORD PTR _tmp$[ebp]
    add    ecx, 1
    mov    DWORD PTR _tmp$[ebp], ecx

; 11   :     
; 12   :     return tmp;

    mov    eax, DWORD PTR _tmp$[ebp]

; 13   : }

    mov    esp, ebp
    pop    ebp
    ret    0
_fun2    ENDP
_TEXT    ENDS
PUBLIC    _fun1
EXTRN    _printf:NEAR
EXTRN    __fltused:NEAR
_DATA    SEGMENT        
$SG355    DB    '%lf', 00H
_DATA    ENDS
_TEXT    SEGMENT
_x$ = 8
_fun1    PROC NEAR

; 15   : void fun1(double x) {

    push    ebp            
    mov    ebp, esp    

; 16   :     printf("%lf", x);

    mov    eax, DWORD PTR _x$[ebp+4]    
    push    eax
    mov    ecx, DWORD PTR _x$[ebp]        
    push    ecx
    push    OFFSET FLAT:$SG355    
    call    _printf
    add    esp, 12                    ; 0000000cH

; 17   : }

    pop    ebp    
    ret    0
_fun1    ENDP
_TEXT    ENDS
PUBLIC    _main
_DATA    SEGMENT
$SG361    DB    '%d', 0aH, 00H
_DATA    ENDS
_TEXT    SEGMENT
_one$ = -8
_two$ = -12
_three$ = -4    
_main    PROC NEAR

; 19   : int main() {

    push    ebp
    mov    ebp, esp
    sub    esp, 12                    ; 0000000cH

; 20   :     int one = 1;

    mov    DWORD PTR _one$[ebp], 1

; 21   :     int two = 2;

    mov    DWORD PTR _two$[ebp], 2

; 22   :     int three = 3;

    mov    DWORD PTR _three$[ebp], 3
; 23   :     
; 24   :     fun1(3.14);

    push    1074339512                ; 40091eb8H
    push    1374389535                ; 51eb851fH
    call    _fun1
    add    esp, 8

; 25   :     one += two;

    mov    eax, DWORD PTR _one$[ebp]
    add    eax, DWORD PTR _two$[ebp]
    mov    DWORD PTR _one$[ebp], eax

; 26   :     printf("%d\n", fun2(one, two, three));

    mov    ecx, DWORD PTR _three$[ebp]
    push    ecx
    mov    edx, DWORD PTR _two$[ebp]
    push    edx
    mov    eax, DWORD PTR _one$[ebp]
    push    eax
    call    _fun2
    add    esp, 12                    ; 0000000cH
    push    eax
    push    OFFSET FLAT:$SG361
    call    _printf
    add    esp, 8

; 27   :     
; 28   :     return 0;

    xor    eax, eax

; 29   : }

    mov    esp, ebp
    pop    ebp
    ret    0
_main    ENDP
_TEXT    ENDS

END

==================================================================

于此我做出如下几点解释:

写在前面:

1.esp--系统堆栈栈顶指针;

2.ebp--系统堆栈当前栈底指针;

3.根据系统堆栈向低端增长,push(入栈)会--esp; pop(出栈)会++esp;

第一:; 22   :     int three = 3;

    mov    DWORD PTR _three$[ebp], 3

这句话是主函数中申请的一个局部变量,该变量的类型为int型,变量名为one; 通过"_three$ = -4"这句话可知," mov    DWORD PTR _three$[ebp], 3" 等价于 "  mov    DWORD PTR -4[ebp], 3" 。这句汇编语言的意思是,将常量3赋值给以ebp-4为首地址的int空间,DWORD PTR就是int的对应。

第二:; 19   : int main() {

    push    ebp
    mov    ebp, esp
    sub    esp, 12                    ; 0000000cH

这三句话的含义分别是:

1.将ebp的值入栈,esp会-4;2.将esp的值赋值给ebp,使得esp和ebp指向同一个空间,形成一个空栈,这个栈是被main()函数来使用的;3.将esp的值-12,也就是说,将栈的栈顶指针抬高12,用来存储三个局部变量one,two,three的值。

逐条分析下面这段代码:

; 19   : int main() {

    push    ebp
    mov    ebp, esp
    sub    esp, 12                    ; 0000000cH

; 20   :     int one = 1;

    mov    DWORD PTR _one$[ebp], 1

; 21   :     int two = 2;

    mov    DWORD PTR _two$[ebp], 2

; 22   :     int three = 3;

    mov    DWORD PTR _three$[ebp], 3
; 23   :     
; 24   :     fun1(3.14);

    push    1074339512                ; 40091eb8H
    push    1374389535                ; 51eb851fH
    call    _fun1
    add    esp, 8

第三:编译系统调用主函数开始,此时系统堆栈会分配出一段空间用来执行这段代码,首先有一个4字节空间用来存储原ebp的值,然后执行第二点中的三条语句,给主函数中所申请的三个4字节分配空间。从汇编语言可以看出,入栈的先后顺序依次为 three, two, one(-4,-8,-12)。将调用fun1()中所传入的实参变量3.14入栈,由于fun1函数的行参变量类型是double型的,故此时esp会向上飘8个字节。

第四:执行"call fun1" , call指令内部会执行push eip的操作,保护eip的值,以便返回时能继续执行主函数的下一条指令,这里就是"add esp, 8";而且接着call会执行mov eip, fun1;(保存fun1函数的eip值)这里的fun1就是子函数fun1的首地址常量。于是,CPU将取出fun1函数的第一条指令开始执行。

; 15   : void fun1(double x) {

    push    ebp            
    mov    ebp, esp    

这段代码的意思是:首先,将main()函数中的栈底指针ebp入栈,esp再向上飘4个字节,将esp的值赋值给ebp,使得esp和ebp指向同一个空间,此时这段空间也就相当于是fun1()函数自己一个栈。

_x$ = 8
; 16   :     printf("%lf", x);

    mov    eax, DWORD PTR _x$[ebp+4]    
    push    eax
    mov    ecx, DWORD PTR _x$[ebp]        
    push    ecx
    push    OFFSET FLAT:$SG355    
    call    _printf
    add    esp, 12                    ; 0000000cH

; 17   : }

接下来的这段代码的意思是,将以ebp+4+8为首地址的4字节空间的值赋值给eax寄存器,然后将eax的值入栈,再将以ebp+8为首地址的4字节空间的值赋值给ecx寄存器,然后将ecx的值入栈,这个操作的目的是将fun1()函数的实参变量3.14这个值复制一份,入栈,将fun1()函数的实参变量和行参变量对应起来,然后再次调用一个系统函数printf(),将"%lf"的首地址入栈,esp再次向上飘4个字节,调用完printf()函数之后,esp回落12个字节(一个首地址的4个字节加上一个行参变量8个字节)。

。。。未完待续。。。







  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值