文档章节

学 Win32 汇编[18]: 关于压栈(PUSH)与出栈(POP) 之二

涂孟超
 涂孟超
发布于 2014/09/26 15:35
字数 991
阅读 17
收藏 0

由于 "栈" 是由高到低使用的, 所以新压入的数据的位置更低.
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 位

 
 
 
 
 

 

 

  

本文转载自:http://www.cnblogs.com/del/archive/2010/04/11/1709788.html

共有 人打赏支持
涂孟超
粉丝 12
博文 2011
码字总数 14107
作品 0
深圳
程序员
献给汇编初学者-函数调用堆栈变化分析

标 题: 献给汇编初学者-函数调用堆栈变化分析 作 者: 堕落天才 时 间: 2007-01-19,19:20:09 链 接: http://bbs.pediy.com/showthread.php?t=38234 跟一个朋友谈堆栈的时候 就写下了这段文字...

失足处男的倒霉孩子
2014/09/04
570
2
【汇编】C++ 函数调用之——有参无返回调用(传值)

C++函数有参调用有几种传参方式: 一.传值 二.传指针(地址) 三.传引用 其中参数可被const修饰,也可以有默认值。下面分情况讨论: 为了简洁,省略main函数的汇编码而直接给出func函数的汇编...

Micooz
2013/07/26
0
0
通过内存布局完成局部变量访问解析

首先看一段代码: void a(){int arr[10];int i;for(i=0;i<10;i++){arr[i]=i; // 初始化数组arr[]}} void b(){int arr2[10];int i;for(i=0;i<10;i++){printf("%d", arr2[i]); //未初始化arr2[......

长平狐
2012/11/01
43
0
逆向分析一个完整的C++程序包含寄存器与参数传递详解

最近在分析C++ dump 文件的时候觉得有必要将一些必要的反汇编东西总结一下以备别人参考,自己有时间的时候也可以进行更多的改进。下面通过一个简单的C++代码转成汇编代码后的详细解释说明一下...

长平狐
2012/06/11
122
0
为栈实现高效的max操作

最近一个美国回来的学长给我出的一道题目,他经常拿来考核去他们公司应聘的程序员,你能想出最完美的答案么? 题目: 请设计一个栈,除了提供push(压栈),pop(出栈),peak(取栈顶元素)...

乐搏学院
2016/12/02
10
0

没有更多内容

加载失败,请刷新页面

加载更多

分布式锁的那点事

在多线程并发的情况下,要保证一个代码块在同一时间只能由一个线程访问,可以用锁来保证,比如java的synchronized语法以及ReentrantLock类等等。这样子可以保证JVM进程内的多个线程同步执行。...

无语年华
4分钟前
0
0
apahce启用http2

需要前置条件传送门 其实前置做完了,h2是很简单的事 1.apache启用http2_module 2.打开apche的配置文件,写上 Protocols h2 http/1.1 3.重启apache,打开浏览器看看吧...

gcudwork
20分钟前
0
0
redis-string

set key value 设置值 set命令有以下选项: ex senconds :为健设置秒级过期时间 px millisencondes :为健设置毫秒级过期时间 nx :健不存在时候,可以设置成功,用于添加 xx : 与nx相反,不...

拐美人
25分钟前
1
0
正弦 余弦 角度 用于画时钟

<html> <head> <title>时钟</title> </head> <style> #canvas{ background: #1977ca } </style>......

一箭落旄头
42分钟前
4
0
驰狼课堂

http://www.chilangedu.com/

求是科技
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部