正在加载……
专注、离线、切勿分心
    这种技术和高级语言编译器的工作原理密切相关。我们下面结合 C 语言的函数调用,看一下用栈传递参数的思想。
    用栈传递参数的原理十分简单,就是由调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数。我们看下面的例子。
;说明:计算( a – b ) ^ 3 ,a、b 为 word 型数据。
;参数:进入子程序时,栈顶存放IP,后面依次存放 a、b
;结果:( dx : ax ) = ( a – b ) ^ 3    ( 乘法,两个数都是8位,结果存放在AX中;如果两个数都是16位,高位存放在DX中,低位存放在AX中 )


difcube : 
        push  bp
        mov  bp , sp
        mov  ax , [ bp + 4 ]   ; 将栈中a的值送入ax 中
        sub   ax , [ bp + 6 ]   ; 减栈中b的值
        mov  bp , ax
        mul  bp
        mul  bp
        pop  bp
        ret  4   ; call 返回,并恢复sp

指令 ret  n 的含义用汇编语法描述为:
pop  ip
add  sp , n
因为用栈传递参数,所以调用者在调用程序的时候要向栈中压入参数,子程序在返回的时候可以用ret  n 指令将栈顶指针修改为调用前的值。调用上面的子程序之前,需要压入两个参数。所以用ret  4 返回。


我们看一下如何调用上面的程序,设 a = 3 、b = 1 ,下面的程序段计算:( a – b ) ^ 3:
mov  ax , 1
push  ax
mov  ax , 3
push  ax        ;注意参数压栈的顺序
call  difcube
我们看一下程序的执行过程中栈的变化:
(1)假设栈的初始情况如下:
                                                            
1000:0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00      
                                                      ↑ss:sp
(2)执行以下指令:
mov  ax , 1
push  ax
mov  ax , 3
push  ax
栈的情况变为:
                                           a     b          
1000:0 00 00 00 00 00 00 00 00 00 00 00 00 03 00 01 00      
                                           ↑ss:sp           
(3)执行指令: call  difcube
栈的情况变为:
                                     IP    a     b          
1000:0 00 00 00 00 00 00 00 00 00 00 XX XX 03 00 01 00      
                                     ↑ss:sp                 
(4)执行指令: push  bp
栈的情况变为:
                               bp    IP    a     b          
1000:0 00 00 00 00 00 00 00 00 XX XX XX XX 03 00 01 00      
                               ↑ss:sp                       
(5)执行指令: mov  bp , sp  ;ss:bp 指向 1000 : 8
                               bp    IP    a     b          
1000:0 00 00 00 00 00 00 00 00 XX XX XX XX 03 00 01 00      
                               ↑ss:sp(ss:bp)                
(6)执行以下指令:
mov  ax , [ bp + 4 ] ; 将栈中a的值送入ax 中
sub   ax , [ bp + 6 ] ; 减栈中b的值
mov  bp , ax
mul  bp
mul  bp              ; DX:AX 中现在存放的是最终结果
(7)执行指令: pop  bp
栈的情况变为:
                                     IP    a     b          
1000:0 00 00 00 00 00 00 00 00 XX XX XX XX 03 00 01 00      
                                     ↑ss:sp                 
(8)执行指令: ret  4
栈的情况变为:
                                                            
1000:0 00 00 00 00 00 00 00 00 XX XX XX XX 03 00 01 00      
                                                      ↑ss:sp


assume cs:code
code segment
start:
        mov ax , 1
        push ax
        mov ax , 3
        push ax
        call difcube
        mov ax , 4c00h
        int 21h
difcube:
        push bp
        mov bp , sp
        mov ax , [bp+4]
        sub ax , [bp+6]
        mov bp , ax
        mul bp
        mul bp
        pop bp
        ret 4
code ends
end start
 // 最开始SS:SP=0769:0
因为入栈后sp-2=FFFE
 // 这段空间才是真正的栈空间
 // 1和3入栈
 // call执行,IP,BP入栈
 // BP出栈
 
// ret返回,sp恢复原来的0,结果=8,DX:AX=0000:0008


下面我们通过一个C语言程序编译后的汇编语言程序,看一下栈在参数传递中的应用。要注意的是,在C语言中,局部变量也在栈中存储。

C程序

void  add ( int , int , int ) ;
main ( )
{
        int  a = 1 ;
        int  b = 2 ;
        int  c = 0 ;
        add ( a , b , c ) ;
        c++ ;
}

void  add ( int a , int b , int c ){
        c = a + b ;
}
编译后的汇编程序
assume cs:code
code segment
start:
        mov bp , sp
        sub sp , 6
        mov word ptr [bp-6] , 0001        ; int a
        mov word ptr [bp-4] , 0002        ; int b
        mov word ptr [bp-2] , 0000        ; int c
        push [bp-2]
        push [bp-4]
        push [bp-6]
        call addr
        add sp ,6
        inc word ptr [bp-2]
addr:       
        push bp
        mov bp , sp                ; bp 暂存刚入栈时候的sp,防止后面一些操作会改变sp,从而获取不到要得到的值;还有一种原因是先分配空间,sp就不改变了,只能通过bp来对空间再赋值
        mov ax , [bp+4]                ; a
        add ax , [bp+6]                ; b
        mov [bp+8] , ax                ; c
        mov sp , bp                ; 恢复入栈 sp
        pop bp                       
        ret                        ; 子程序返回,pop ip

code ends
end start
// 加上了宏汇编开头两句和最后两句什么的,便于编译然后debug
// 初始;SS:SP=0769:0000
                                                               
0769:FFF0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00      
                                                         ↑ss:sp
// 进入main,定义变量a、b、c,开辟变量空间
                                                               
0769:FFF0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00      
                                        ↑ss:sp           ↑ss:bp
// 开辟好空间,赋值
                                        a     b     c          
0769:FFF0 00 00 00 00 00 00 00 00 00 00 10 00 20 00 00 00      
                                        ↑ss:sp           ↑ss:bp

画的所有情况都是理想下的sp不更改的情况,但是实际上有些操作会修改sp,所以要用bp来保存初始的sp,然后通过bp加减法得到栈中想要访问的值

 // a、b、c 进栈

 // 再次push a、b、c 
 // IP进栈 IP=0020 , bp 进栈 bp=0000
 // mov [bp+8] , ax  ; c=3
 // pop bp,bp=0000
 // ret 返回,pop IP
 // c++,c=1
// 进入子函数add(a , b , c),子函数要为a、b、c临时分配空间
                      a     b     c     a     b     c          
0769:FFF0 00 00 00 00 10 00 20 00 00 00 10 00 20 00 00 00      
                      ↑ss:sp                             ↑ss:bp
// 汇编call进入子程序,IP 进栈
                IP    a     b     c     a     b     c          
0769:FFF0 00 00 20 00 10 00 20 00 00 00 10 00 20 00 00 00      
                ↑ss:sp                                   ↑ss:bp
// bp 进栈
          bp    IP    a     b     c     a     b     c          
0769:FFF0 00 00 20 00 10 00 20 00 00 00 10 00 20 00 00 00      
          ↑ss:sp                                         ↑ss:bp

          bp    IP    a     b     c     a     b     c          
0769:FFF0 00 00 20 00 10 00 20 00 00 00 10 00 20 00 00 00      
          ↑ss:sp(bp)                                           
// a+b=c,c=3
          bp    IP    a     b     c     a     b     c          
0769:FFF0 00 00 20 00 10 00 20 00 30 00 10 00 20 00 00 00      
          ↑ss:sp(bp)                                           
// pop bp,ret
                      a     b     c     a     b     c          
0769:FFF0 00 00 20 00 10 00 20 00 30 00 10 00 20 00 00 00      
                      ↑ss:sp                             (bp)=0
// sp+6,退出了子函数,释放空间
                                        a     b     c          
0769:FFF0 00 00 20 00 10 00 20 00 30 00 10 00 20 00 00 00      
                                        ↑ss:sp           (bp)=0
到这里子函数add(a , b , c)开辟的空间被释放
// inc word ptr [bp-2]
                                        a     b     c          
0769:FFF0 00 00 20 00 10 00 20 00 30 00 10 00 20 00 01 00      
                                        ↑ss:sp           (bp)=0
// main函数结束,恢复sp=bp,释放所有开辟的空间
                                                               
0769:FFF0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00      
                                                         ↑ss:sp



posted on 2017-11-30 19:57  正在加载……  阅读(1968)  评论(-1编辑  收藏  举报