Windows下x64反汇编参数传递约定,一句话,调用顺序为从左到右, Function( rcx, rdx, r8,r9, [rsp+0x20], [rsp+0x28], [rsp+0x30]..
Windows下x64反汇编参数传递约定
一句话,调用顺序为从左到右, Function( rcx, rdx, r8,r9, [rsp+0x20], [rsp+0x28], [rsp+0x30], [rsp+0x38], [rsp+0x40], [rsp+0x48], [rsp+0x50], [rsp+0x58], [rsp+0x60] ...)
注:本文资料收集于互联网。
x64 体系结构
x64 体系结构是 x86 的向后兼容扩展。 它提供与 x86 相同的旧 32 位模式,以及新的 64 位模式。
术语"x64"包括 AMD 64 和 Intel64。 指令集接近相同。
寄存 器
x64 将 x86 的 8 个常规用途寄存器扩展为 64 位,并添加了 8 个新的 64 位寄存器。 64 位寄存器的名称以"r"开头,因此例如, eax 的 64 位扩展名为 rax。 新寄存器的名称为 r8 到 r15。
每个寄存器的低 32 位、16 位和 8 位可直接在操作数中处理。 这包括寄存器,如 esi,其低 8 位以前不可处理。 下表为 64 位寄存器的下半部分指定汇编语言名称。
64 位寄存器 | 低 32 位 | 低 16 位 | 低 8 位 |
---|---|---|---|
rax |
eax |
ax |
铝 |
rbx |
ebx |
bx |
bl |
rcx |
ecx |
残雪 |
Cl |
rdx |
edx |
Dx |
Dl |
rsi |
Esi |
四 |
Sil |
rdi |
Edi |
di |
dil |
rbp |
Ebp |
Bp |
bpl |
粒子 |
Esp |
sp |
Spl |
r8 |
r8d |
r8w |
r8b |
r9 |
r9d |
r9w |
r9b |
r10 |
r10d |
r10w |
r10b |
r11 |
r11d |
r11w |
r11b |
r12 |
r12d |
r12w |
r12b |
r13 |
r13d |
r13w |
r13b |
r14 |
r14d |
r14w |
r14b |
r15 |
r15d |
r15w |
r15b |
输出到 32 位子注册的操作会自动零扩展为整个 64 位寄存器。 输出到 8 位或 16 位子注册的操作不是零扩展 (这是兼容的 x86 行为) 。
ax、bx、cx 和 dx 的高 8 位仍可作为 ah、bh、ch、dh 进行地址处理,但不能用于所有类型的操作数。
指令指针、eip 和标志寄存器已分别扩展到 (和 rflags) 64 位。
x64 处理器还提供多组浮点寄存器:
八个 80 位 x87 寄存器。
八个 64 位 MMX 寄存器。 (与 x87 registers.)
8 个 128 位 SSE 寄存器的原始集增加到 16 个。
调用约定
与 x86 不同,C/C++ 编译器仅支持 x64 上的一个调用约定。 此调用约定利用 x64 上可用的寄存器数增加:
前四个整数或指针参数在 rcx、 rdx、 r8 和 r9 寄存器中传递。
前四个浮点参数在前四个 SSE 寄存器 xmm0xmm3-中传递。
调用方在堆栈上为寄存器中传递的参数保留空间。 被调用的函数可以使用此空间将寄存器的内容溢出到堆栈。
任何其他参数在堆栈上传递。
在 rax 寄存器中返回整数或指针返回值,而浮点返回值在 xmm0 中返回。
rax、rcx、rdx、r8r11- 是可变的。
rbx、rbp、rdi、rsi、r12r15- 是非易失性。
C++ 的调用约定非常相似: 此 指针作为隐式第一个参数传递。 接下来的三个参数在剩余的寄存器中传递,其余参数在堆栈上传递。
寻址模式
64 位模式下的寻址模式类似于 x86,但不完全相同。
引用 64 位寄存器的说明以 64 位精度自动执行。 (例如 mov rax,[rbx] 从 rbx 开始将 8 个字节移动到 rax.)
为 64 位即时常量或常量地址添加了一种特殊形式的 mov 指令。 对于所有其他指令,即时常量或常量地址仍为 32 位。
x64 提供新的 与元寻址相关的寻址模式。 引用单个常量地址的指令编码为从进行翻录的 偏移量。 例如,mov rax,[addr] 指令从 addrrip + 开始将 8 个字节移动到 rax。
隐式引用指令指针的指令(如 jmp、 调用、 推送和 pop)和堆栈指针将它们视为 x64 上的 64 位寄存器。
在 Win64 下的 registers 用途
Register |
Status |
Use |
RAX | Volatile | Return value register |
RCX | Volatile | First integer argument |
RDX | Volatile | Second integer argument |
R8 | Volatile | Third integer argument |
R9 | Volatile | Fourth integer argument |
R10:R11 | Volatile | Must be preserved as needed by caller; used in syscall/sysret instructions |
R12:R15 | Nonvolatile | Must be preserved by callee |
RDI | Nonvolatile | Must be preserved by callee |
RSI | Nonvolatile | Must be preserved by callee |
RBX | Nonvolatile | Must be preserved by callee |
RBP | Nonvolatile | May be used as a frame pointer; must be preserved by callee |
RSP | Nonvolatile | Stack pointer |
XMM0 | Volatile | First FP argument |
XMM1 | Volatile | Second FP argument |
XMM2 | Volatile | Third FP argument |
XMM3 | Volatile | Fourth FP argument |
XMM4:XMM5 | Volatile | Must be preserved as needed by caller |
XMM6:XMM15 | Nonvolatile | Must be preserved as needed by callee. |
1. 传递参数
在 Win64 里使用下面寄存器来传递参数:
- rcx - 第 1 个参数
- rdx - 第 2 个参数
- r8 - 第 3 个参数
- r9 - 第 4 个参数
其它多出来的参数通过 stack 传递。
使用下面寄存器来传递浮数数:
- xmm0 - 第 1 个参数
- xmm1 - 第 2 个参数
- xmm2 - 第 3 个参数
- xmm3 - 第 4 个参数
下面的代码:
void EditTextFile(HWND hEdit, LPCTSTR szFileName) hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); ... ... } |
CreateFile() 的参数有 7 个,那么看看 VC 是怎样安排参数传递:
void EditTextFile(HWND hEdit, LPCTSTR szFileName) hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); ... ... |
上面已经对 7 个参数的传递进行了标注,前 4 个参数通过 rcx,rdx,r8 以及 r9 寄存器传递,后 3 个参数确实通过 stack 传递。
可是,事情并没有这么简单:
在 Win64 下,会为每个参数保留一份用来传递的 stack 空间,以便回写 caller 的 stack |
在上面的例子中:
- [rsp+20h] - 第 5 个参数
- [rsp+28h] - 第 6 个参数
- [rsp+30h] - 第 7 个参数
实际上已经为前面 4 个参数保留了 stack 空间,分别是:
- [rsp] - 第 1 个参数(使用 rcx 代替)
- [rsp+08h] - 第 2 个参数(使用 rdx 代替)
- [rsp+10h] - 第 3 个参数(使用 r8 代替)
- [rsp+18h] - 第 4 个参数(使用 r9 代替)
虽然是使用了 registers 来传递参数,然而还是保留了 stack 空间。接下着就是 [rsp+20h], [rsp+28h] 以及[rsp+30h] 对应的 4,5,6 个参数
2. 回写 caller stack
VC 使用了下面编译参数来实现回写 caller stack
/homeparams |
当使用了这个编译选项或者在 Debug 版下,它强制将 registers 里的值写回 stack 中
正如下面的代码:
CreateFileWImplementation: |
上面所显示的是 CreateFile() 在 kernel32.dll 模块里的实现代码,上面对回写机制进行了标注,回写的 stack 正好是 caller 调用时未参数所保留的 stack 空间,上面的代码并不是那么直观。
下面我演示一下使用 /homeparams 选项来编译代码。
上面的 EditTextFile() 函数结果如下:
void EditTextFile(HWND hEdit, LPCTSTR szFileName) hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
第 1 个参数回写 [rsp+8] 处,第 2 个参数回写 [rsp+10h] 处。
注意这里的 stack 就是对应 caller 调用时的 stack,经过调用后 [rsp] 是返回地址值,因此,在 callee 里设置:
- callee 写 [rsp+8] = caller 的 [rsp]
- callee 写 [rsp+10h] = caller 的 [rsp+8]
- callee 的 [rsp] = return address
上面很直观地显示了使用 /homeparams 选项时的效果,对比前一段没有使用 /homeparams 选项编译时的结果,很容易发现这个机制。
回写 caller stack 机制目的是为了 Debug 所需。
3. 由 callee 保存
在一个程序里应尽量使用 registers,在 x64 里有 16 个通用寄存器和 16 个 xmm 寄存器,可是一些 registers 在使用前必须保存原来值,以防丢失原来值。
因此,在 callee 使用它们时会将原值压入栈中保存,在 Win64 里,下面 registers 由 callee 负责保存:
- rbx, rbp, rsi, rdi
- r12 - r15
- xmm6 - xmm15
每进入一个 callee,在使用它们之前都保存起来,返回 caller 之前,恢复原来值。因此这些寄存器的值是保持恒定的。
void EditTextFile(HWND hEdit, LPCTSTR szFileName) ... ... 000000013F911708 48 83 C4 58 add rsp,58h |
4. stack frame 结构
进入每个 callee 时,都会生成属于自己的 stack frame 结构,返回时会注销自己的 stack frame
- rbp
- rsp
由这两个 registers 来构造 stack frame 结构,rbp 是 stack frame pointer,rsp 是 stack pointer
可是,在 Win64 里,似乎不使用 stack frame 结构,VC 不会为每个函数创建 stack frame 结构 |
在 Win64 里,始终在使用动态使用 rsp 来维护 stack
void EditTextFile(HWND hEdit, LPCTSTR szFileName) ... ... 000000013F911708 48 83 C4 58 add rsp,58h // 注销 callee stack 结构 |
VC 不会生成 x86 下典型的 stack frame 结构,始终由 rsp 维护 stack,/Gd 编译选项在 Win64 下会被忽略,rbp 被保留起来
在 Win64 里,rdi 寄存器的角色变得很微妙,在某些场合下它充当了一部分 stack frame pointer 的角色。
5. r11 与 rcx 以及 r10
在 64 位模式下,在 sysret 指令返回时,将从 rcx 处得到返回地址,从 r11 处得到 rflags 值,因此在进入 system services routine(系统服务例程)前,或者在系统服务例程中的第1个任务是 rcx 与 r11 寄存器,以便 sysret 返回。
在 Win64 里,r10 寄存器充当保存 rcx 值的作用,如下:
NtCallbackReturn: |
在进入 system call 之前,保存 rcx 的值。
x86:又名 x32 ,表示 Intel x86 架构,即 Intel 的32位 80386 汇编指令集。
x64:表示 AMD64 和 Intel 的 EM64T ,而不包括 IA64 。至于三者间的区别,可自行搜索。
x64 跟 x86 相比寄存器的变化,如图:
从图上可以看到,X64架构相对于X86架构的主要变化,是将原来所有的寄存器都扩大了一倍,例如EAX现在扩充成RAX,同时,又新增加了从R8~R15这8个64位的寄位器,有点RISC的味道(RISC特点就是寄存器多)。
然后还有下面的一些改变:
- x64上面默认的函数调用约定是 fast call ,也就是 ABI 是 fast call ;
- 一个函数在调用时,前四个参数是从左至右依次存放于RCX、RDX、R8、R9寄存器里面,剩下的参数从左至右顺序入栈;
- 调用者负责在栈上分配32字节的“shadow space”,用于存放那四个存放调用参数的寄存器的值(亦即前四个调用参数);
- 小于64位(bit)的参数传递时高位并不填充零(例如只传递ecx),大于64位需要按照地址传递;
- 被调用函数的返回值是整数时,则返回值会被存放于RAX;
- 被调用函数不负责清栈,调用者负责清理栈;
- RAX,RCX,RDX,R8,R9,R10,R11是“易挥发”的,不用特别保护,其余寄存器需要保护。(x86下只有eax, ecx, edx是易挥发的)
- 栈需要16字节对齐,“call”指令会入栈一个8字节的返回值(注:即函数调用前原来的RIP指令寄存器的值),这样一来,栈就对不齐了(因为RCX、RDX、R8、R9四个寄存器刚好是32个字节,是16字节对齐的,现在多出来了8个字节)。所以,所有非叶子结点调用的函数,都必须调整栈RSP的地址为16n+8,来使栈对齐。
- 对于 R8~R15 寄存器,我们可以使用 r8, r8d, r8w, r8b 分别代表 r8 寄存器的64位、低32位、低16位和低8位。
一些其他要注意的小问题:
- 另外一些小问题要注意,AMD64不支持 push 32bit 寄存器的指令,最好的方法就是 push 和 pop 都用64位寄存器,即 push rbx ,不要使用 push ebx 。
- 另外要补充的一点是,在一般情况下,X64 平台的 RBP 栈基指针被废弃掉,只作为普通寄存器来用,所有的栈操作都通过 RSP 指针来完成。
遗留问题
以上都是关于 Windows 上的调用约定,即 Visual Studio 上使用的调用约定,至于 GCC 的函数调用约定是否一致,还不清楚,有知道的请指点一下,我从 asmlib 的64位汇编看,GCC 好像第一个参数用的是 rdi ,而不是 rcx 。
示例:
; 示例代码 1.asm; 语法:GoASMDATA SECTIONtext db 'Hello x64!',caption db 'My First x64 Application',CODE SECTIONSTART:sub rsp, 28h ; 堆栈预留 shadow space (40 + 8)字节xor r9d, r9d ; r9lea r8, caption ; r8lea rdx, text ; rdxxor rcx, rcx ; rcxcall MessageBoxAadd rsp, 28h ; 调用者自己恢复堆栈ret
一般编译器实现调用调用约定无外乎以下这几种:
- CDECL:C/C++默认的调用约定,调用方平栈,不定参数的函数可以使用,参数通过堆栈传递.
- STDCALL:被调方平栈,不定参数的函数无法使用,参数默认全部通过堆栈传递.
- FASTCALL32:被调方平栈,不定参数的函数无法使用,前两个参数放入(ECX, EDX),剩下的参数压栈保存.
- FASTCALL64:被调方平栈,不定参数的函数无法使用,前四个参数放入(RCX, RDX, R8, R9),剩下的参数压栈保存.
- System V:类Linux系统默认约定,前八个参数放入(RDI,RSI, RDX, RCX, R8, R9),剩下的参数压栈保存.
当栈顶指针esp小于栈底指针ebp时,就形成了栈帧,栈帧中可以寻址的数据有局部变量,函数返回地址,函数参数等。不同的两次函数调用,所形成的栈帧也不相同,当由一个函数进入另一个函数时,就会针对调用的函数开辟出其所需的栈空间,形成此函数的独有栈帧,而当调用结束时,则清除掉它所使用的栈空间,关闭栈帧,该过程通俗的讲叫做栈平衡。而如果栈在使用结束后没有恢复或过度恢复,则会造成栈的上溢或下溢,给程序带来致命错误。
cdecl 调用者平栈: cdecl是C/C++默认调用约定,该调用方式在函数内不进行任何平衡参数操作,而是在退出函数后对esp执行加4操作,从而实现栈平衡。
该约定会采用复写传播优化,将每次参数平衡的操作进行归并,在函数结束后一次性平衡栈顶指针esp,且不定参数函数可使用此约定。
stdcall 被调用者平栈: stdcall与cdecl只在参数平衡上有所不同,其余部分都一样,但该约定不定参数函数无法使用。
cdecl调用方式的函数在同一作用域内多次被调用,会在效率上比stdcall高一些,因为它可以使用复写传播优化,而stdcall在函数内平衡栈,无法使用复写传播优化。
fastcall 被调用者平栈: fastcall效率最高,它可利用寄存器传递参数,一般前两个或前四个参数用寄存器传递,其余参数传递则转换为栈传递,此约定不定参数函数无法使用。
对于32位来说使用ecx,edx传递前两个参数,后面的用堆栈传递。
对于64位则会使用RCX,RDX,R8,R9传递前四个参数,后面的用堆栈传递。
使用esp寻址: 在O2编译器选项中,为了提高程序执行效率,只要栈顶是稳定的,就可以不再使用ebp指针,而是利用esp指针直接访问局部变量,这样可节省一个寄存器资源。
Windows下x64反汇编参数传递约定,一句话,调用顺序为从左到右, Function( rcx, rdx, r8,r9, [rsp+0x20], [rsp+0x28], [rsp+0x30]..相关推荐
- 将一个5X5的矩阵中最大的元素放在中心, 4个角分别放4个最小的元素(顺序为从左到右,从上到下,从小到大存放)其余数字从小到大
将一个5X5的矩阵中最大的元素放在中心, 4个角分别放4个最小的元素(顺序为从左到右,从上到下,从小到大存放) 其余数字从小到大 在以前的要求上更改了一下,其余数字从小到大排序 #include &l ...
- php 调用memcache,Windows下的Memcache安装(php调用)
Windows下的Memcache安装: 1. 下载memcache的windows稳定版,解压放某个盘下面,比如在c:\memcached 2. 在终端(也即cmd命令界面)下输入 'c:\memc ...
- Windows下查看dll被哪个进程调用
转载博客菜鸟leihttp://www.cnblogs.com/leipei2352/archive/2013/02/05/2892482.html 卸载程序,结果没卸载干净---程序的安装目录中还剩 ...
- windows下bat文件一直循环一句话如何解决
最近用GMT画图的,一开始没事,但是后面一直出现bug: 可以看到一直重复一句话,把gmt卸了重装也没用,后来发现了问题所在: 我有一个叫gmt.bat的文件,所以每次调用别的bat文件运行时,默认先 ...
- 解决windows下C32ASM反汇编无法打开的问题
无意间发现C32ASM的启动和Server服务有关,刚好今天我看了网上的win7系统优化的文章, 也就按照上面的方法优化系统,由于server服务和共享有关,于是把server服务也禁用了, 后来我又 ...
- WIndows下AppAche服务中调试php页面出现警告:Call to undefined function mysql_connect()
今天在windows server 2003上调试PHP源码的时候,遇到php连接mysql时的错误:Call to undefined function mysql_connect(): 现总结如下 ...
- windows下SecureCRT无法使用backspace(空格键)和上下左右键
在使用SecureCRT登陆liunx(我的为CenterOS)系统,发现删除(backspace)键.和上下左右键不起作用,郁闷了很久没有找到解决办法, 今天终于看到了一篇有用的文章,在此记录一下! ...
- x64 汇编 参数传递
参数传递在不同的系统上是不一样的 称作 calling convention 调用约定 windows rcx,rdx,r8,r9 用来存储整数或指针参数,按照从左到右的顺序 xmm0,1,2,3 用 ...
- windows下python如何安装模块或包? How to install package or module in windows OS when using PYTHON?
摘要:本文介绍了在windows下利用cmd安装第三方模块或包的方法. 更新20170531:作为小白,发现使用setup.py安装并不是万能的,找到了使用pip安装的方法,步骤为配置好环境变量后(参 ...
最新文章
- 在vue中使用vuex,修改state的值示例
- Object-C时间与字符串的转化 因多语言设置中造成返回Nil的解决方法
- mysql两台服务器怎么做数据同步_两台mysql服务器实现双机互备配置并测试数据同步...
- ORM SQLAlchemy 简介
- 使用模块化工具Rollup打包自己开发的JS库
- oracle--索引--
- 设计模式系列 - 装饰器模式
- Atitit.jsou html转换纯文本 java c# php
- 用ABAP编程破解世界上最难数独游戏
- 中国行政区划编码-省市县镇村
- visual studio code打不开
- 私人问卷收集系统-Surveyking问卷收集系统
- 数学之美 吴军 读书笔记
- 38.DevOps之基于Jenkins实现的CI与CD
- ubuntu teamviewer
- Thinkcmf QQ邮箱配置
- 由《编程之美》想到的
- Javafx+MySQL 学生成绩管理系统
- Failed to import package with error: Couldn't decompress package的解决方案
- Both setBehindContentView must be called in onCreate in addition to setContentView.
热门文章
- vc++实现内核级进程保护
- python爬虫— 利用js2xml 获取 script 数据
- Python基础入门:(一)从变量到异常处理 --阿里云天池
- PEST分析顺丰服务需求_顺丰内外部环境分析.doc
- [转载]关于sql连接语句中的Integrated Security=SSPI
- 易车的第三个十年不好走
- Win10系统下怎么开启管理员administrator权限?
- c word to html 走样,打印机打印效果走样解决办法.pptx
- Ol4网格生成以及优化
- IDEA中两中默认背景颜色的RGB