zoukankan      html  css  js  c++  java
  • 学 Win32 汇编[18]: 关于压栈(PUSH)与出栈(POP) 之二


    由于 "栈" 是由高到低使用的, 所以新压入的数据的位置更低.
    ESP 中的指针将一直指向这个新位置, 所以 ESP 中的地址数据是动态的.

    每次 PUSH, ESP = ESP - x; 每次 POP, ESP = ESP + x;
    其中的 x 只能是 4 或 2, 因为 Win32 的 PUSH 只可以压入 32 位(默认)或 16 位的数据.

    ESP 有个名字叫 "栈顶", 其实它指向的是栈中最低位置的数据.

    实例查看 ESP 的变化:
    ; Test18_1.asm
    .386
    .model flat, stdcall
    
    include    windows.inc
    include    kernel32.inc
    include    masm32.inc
    include    debug.inc
    includelib kernel32.lib
    includelib masm32.lib
    includelib debug.lib
    
    .data
        ddVal1 dd 1
        ddVal2 dd 2
        dwVal1 dw 3
        dwVal2 dw 4
    .code
    main proc
        PrintHex esp  ;0012FFA4
        
        push ddVal1
        PrintHex esp  ;0012FFA0
        push ddVal2
        PrintHex esp  ;0012FF9C
        push dwVal1
        PrintHex esp  ;0012FF9A
        push dwVal2
        PrintHex esp  ;0012FF98
        
        pop dwVal2
        PrintHex esp  ;0012FF9A
        pop dwVal1
        PrintHex esp  ;0012FF9C
        pop ddVal2
        PrintHex esp  ;0012FFA0
        pop ddVal1
        PrintHex esp  ;0012FFA4
        ret
    main endp
    end main
    

    使用参数压栈的方式调用函数, 同时揭示 invoke 的本质:
    ; Test18_2.asm
    .386
    .model flat, stdcall
    
    include    windows.inc
    include    kernel32.inc
    ;include    masm32.inc
    ;include    debug.inc
    includelib kernel32.lib
    ;includelib masm32.lib
    ;includelib debug.lib
    
    include    user32.inc
    includelib user32.lib
    
    .data
        szMsg     db 'Hello World!', 0
        szCaption db 'Hi', 0
    .code
    main proc
        ;invoke MessageBox, NULL, addr szMsg, addr szCaption, MB_OK
        ;用压栈的方式调用 MessageBox 函数; 本来就是如此, invoke 只是简化了这个步骤
        push MB_OK ;C 函数和系统函数读取参数的顺序是: 从右到左; 最左边的参数最后使用, 要先压入
        push offset szCaption
        push offset szMsg
        push NULL  ;一个常数会默认当作 32 位数据压入
        call MessageBox
        pop edx    ;随便出栈到一个地方, 已经没用了, 相当于进回收站
        pop edx    ;尽管没用, 不出是不行的, 因为 push 和 pop 要成对出现
        pop edx
        pop edx
        
        ;invoke ExitProcess, NULL
        ;用压栈的方式调用 ExitProcess 函数
        push NULL
        call ExitProcess
        pop edx
    main endp
    end main
    

    从上面的例子看出, 函数调用是需要先压栈(PUSH)参数的;

    PUSH 另一重要作用是保护数据, 调用函数前, 最先需要保护的就是 EIP, 这是执行完函数后的下一条指令的地址.
    call 指令会先把 EIP 传给 ESP; ret 指令最后把 ESP 恢复给 EIP. 所以, 压栈出栈保护的是 ESP.
    但因 ESP 是动态的, 所以一般先 mov ebp, esp, 然后 push ebp ... 像这样:
    
    mov ebp, esp
    push ebp
    ;...函数或子过程
    pop ebp
    mov esp, ebp
    ;leave ;可以使用 leave 指令代替上面两行, 它是对上面两行的简化
    

    从调试器中查看编译器添加的保护 ESP 的代码:
    ; Test18_3.asm; 这是用于调试的例子
    .386
    .model flat, stdcall
    
    include    windows.inc
    include    kernel32.inc
    include    masm32.inc
    include    debug.inc
    includelib kernel32.lib
    includelib masm32.lib
    includelib debug.lib
    
    .code
    ;求和函数
    sumProc proc v1:dword, v2:dword, v3:dword
        mov eax, v1
        add eax, v2
        add eax, v3
        ret
    sumProc endp
    ;
    main proc
        invoke sumProc, 11, 22, 33
        PrintDec eax ;66
        ret
    main endp
    end main
    ;--------------------------
    
    ;Ctrl + T 是设置或取消断点
    ;Ctrl + D 是调试运行
    ;从调试器中看到 sumProc 函数的代码变成了:
    
    PUSH EBP
    MOV EBP,ESP
    MOV EAX,DWORD PTR SS:[EBP+8]
    ADD EAX,DWORD PTR SS:[EBP+C]
    ADD EAX,DWORD PTR SS:[EBP+10]
    LEAVE
    
    ;看来保护 ESP 的工作是由编译器做的
    ;从这里也看出了 EBP 寄存器的主要用途就是中转 ESP 中的数据
    

    利用 ESP 的地址偏移读取栈中的数据:
    ; Test18_4.asm
    .386
    .model flat, stdcall
    
    include    windows.inc
    include    kernel32.inc
    include    masm32.inc
    include    debug.inc
    includelib kernel32.lib
    includelib masm32.lib
    includelib debug.lib
    
    .code
    main proc
        push 111
        push 222
        push 333
        push 444
        
        mov eax, [esp]
        PrintDec eax     ;444
        mov eax, [esp+4]
        PrintDec eax     ;333
        mov eax, [esp+12]
        PrintDec eax     ;111
        
        pop edx
        pop edx
        pop edx
        pop edx
        ret
    main endp
    end main
    

    总结 PUSH 和 POP 的主要用途: 1、暂存与恢复数据; 2、处理函数参数.

    压栈、出栈指令汇总:
    
    PUSH(PUSHW、PUSHD)  / POP   ;进出 16 位或 32 位操作数, 默认 32 位
    
    PUSHAD              / POPAD ;进出 EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI
    PUSHA               / POPA  ;进出  AX、 CX、 DX、 BX、 SP、 BP、 SI、 DI
    
    PUSHFD              / POPFD ;进出 EFLAGS
    PUSHF               / POPF  ;进出 EFLAGS 的低 16 位
    

  • 相关阅读:
    【转】C#控件——DataGridView单元格文本自动换行
    【转】右键的 在 vs 中打开 怎么去掉
    【转】C#使用Oracle.ManagedDataAccess.dll
    C#委托笔记
    【转】检索 COM 类工厂中 CLSID 为 {00024500-0000-0000-C000-000000000046} 的组件失败,原因是出现以下错误: 80070005 拒绝访问
    js创建ActiveXObject无效
    ASPxGridView编辑时弹出的editform值不是当前行值的原因
    ASPxGridView后台实现隐藏新增按钮
    oracle闪回存储过程
    oracle重新编译所有invalid objects
  • 原文地址:https://www.cnblogs.com/del/p/1709788.html
Copyright © 2011-2022 走看看