汇编语言Irvine64链接库
本教程提供了一个能支持 64 位编程的最小链接库,其中包含了如下过程:
尽管这个库比 32 位链接库小很多,它还是包含了许多重要工具能使得程序更具互动性。随着学习的深入,大家可以用自己的代码来扩展这个链接库。Irvine64 链接库会保留 RBX、RBP、RDI、RSI、R12、R13、R14 和 R15 寄存器的值,反之,RAX、RCX、RDX、R8、R9、R10 和 R11 寄存器的值则不会保留。
程序员只有在调用 Windows API 的函数或用 C/C++ 编写的函数时,才会使用这个调用规范。该调用规范的一些基本特性如下所示:
1) CALL 指令将 RSP(堆栈指针)寄存器减 8,因为地址是 64 位的。
2) 前四个参数依序存入 RCX、RDX、R8 和 R9 寄存器,并传递给过程。如果只有一个参数,则将其放入 RCX。如果还有第二个参数,则将其放入 RDX,以此类推。其他参数,按照从左到右的顺序压入堆栈。
3) 调用者的责任还包括在运行时堆栈分配至少 32 字节的影子空间(shadow space),这样,被调用的过程就可以选择将寄存器参数保存在这个区域中。
4) 在调用子程序时,堆栈指针(RSP)必须进行 16 字节边界对齐(16 的倍数)。CALL 指令把 8 字节的返回值压入堆栈,因此,除了已经减去的影子空间的 32 之外,调用程序还必须从堆栈指针中减去 8。后面的示例将显示如何实现这些操作。
由于过程通常使用 RAX 返回结果,因此,当从子程序返回时,调用程序也期望返回值在这个寄存器中。这样就可以说这个子程序是一个函数,因为,它接收了四个输入并(确切地说)产生了一个输出。
现在来看看本例中的其他细节:第 10 行将堆栈指针对齐到 16 字节的偶数边界。为什么要这样做?在 OS 调用主程序之前,假设堆栈指针是对齐 16 字节边界的。然后,当 OS 调用主程序时,CALL 指令将 8 字节的返回地址压入堆栈。将堆栈指针再减去 8,使其减少成一个 16 的倍数。
可以在 Visual Studio 调试器中运行该程序,并查看 RSP 寄存器(堆栈指针)改变数值。通过这个方法,能够看到用图形方式在下图中展示的十六进制数值。
上图只展示了每个地址的低 32 位,因为高 32 位为全零:
1) 执行第 10 行前,RSP=01AFE48。这表示在 OS 调用本程序之前,RSP 等于 01AFE50。( CALL 指令使得堆栈指针减 8。)
2) 执行第 10 行后,RSP=01AFE40,表示堆栈正好对齐到 16 字节边界。
3) 执行第 11 行后,RSP=01AFE20,表示 32 个字节的影子空间位置从 01AFE20 到 01AFE3F。
4) 在 AddFour 过程中,RSP=01AFE18,表示调用者的返回地址已经压入堆栈。
5) 从 AddFour 返回后,RSP 再一次等于 01AFE20,与调用 AddFour 之前的值相同。
与调用 ExitProcess 来结束程序相比,本程序选择的是执行 RET 指令,这将返回到启动本程序的过程。但是,这也就要求能将堆栈指针恢复到其在 main 程开始执行时的位置。下面的代码行能替代 CallProc_64 程序的第 20 和 21 行:
- Crlf:向控制台写一个行结束的序列。
- Random64:在0〜2⁶⁴-1 内,生成一个 64 位的伪随机整数。随机数值用 RAX 寄存器返回。
- Randomize:用一个值作为随机数生成器的种子。
- Readlnt64:从键盘读取一个 64 位有符号整数,用回车符结束。数值用 RAX 寄存器返回。
- ReadString:从键盘读取一个字符串,用回车符结束。过程用 RDX 传递输入缓冲器偏移量;用 RCX 传递用户可输入的最大字符数加 1(用于 unll 结束符字节)。返回值(用 RAX)为用户实际输入的字符数。
- Str_compare:比较两个字符串。过程将源串指针传递给 RSI,将目的串指针传递给 RDIO 用与 CMP(比较)指令一样的方式设置零标志位和进位标志位。
- Str_copy:将一个源串复制到目标指针指定的位置。源串偏移量传递给 RSI,目标偏移量传递给 RDI。
- Strjength:用 RAX 寄存器返回一个空字节结束的字符串的长度。过程用 RCX 传递字符串的偏移量。
- Writelnt64:将 RAX 寄存器中的内容显示为 64 位有符号十进制数,并加上前置加号或减号。过程没有返回值。
- WriteHex64:将 RAX 寄存器中的内容显示为 64 位十六进制数。过程没有返回值。
- WriteHexB:将 RAX 寄存器中的内容显示为 1 字节、2 字节、4 字节或 8 字节的十六进制数。将显示的大小(1、2、4 或 8) 传递给 RBX 寄存器。过程没有返回值。
- WriteString:显示一个空字节结束的 ASCII 字符串。将字符串的 64 位偏移量传递给 RDX。过程没有返回值。
尽管这个库比 32 位链接库小很多,它还是包含了许多重要工具能使得程序更具互动性。随着学习的深入,大家可以用自己的代码来扩展这个链接库。Irvine64 链接库会保留 RBX、RBP、RDI、RSI、R12、R13、R14 和 R15 寄存器的值,反之,RAX、RCX、RDX、R8、R9、R10 和 R11 寄存器的值则不会保留。
调用 64 位子程序
如果想要调用自己编写的子程序,或是 Irvine64 链接库中的子程序,则程序员需要做的就是将输入参数送入寄存器,并执行 CALL 指令。比如:mov rax,12345678h call WriteHex64还有一件小事也需要完成,即程序员要在自己程序的顶部用 PROTO 伪指令指定所有在本程序之外同时又将会被调用的过程:
ExitProcess PROTO ;位于 Windows API WriteHex64 PROTO ;位于 Irvine64 链接库
x64 调用规范
Microsoft 在 64 位程序中使用统一模式来传递参数并调用过程,称为 Microsoft x64 调用规范。该规范由 C/C++ 编译器和 Windows 应用编程接口(API)使用。程序员只有在调用 Windows API 的函数或用 C/C++ 编写的函数时,才会使用这个调用规范。该调用规范的一些基本特性如下所示:
1) CALL 指令将 RSP(堆栈指针)寄存器减 8,因为地址是 64 位的。
2) 前四个参数依序存入 RCX、RDX、R8 和 R9 寄存器,并传递给过程。如果只有一个参数,则将其放入 RCX。如果还有第二个参数,则将其放入 RDX,以此类推。其他参数,按照从左到右的顺序压入堆栈。
3) 调用者的责任还包括在运行时堆栈分配至少 32 字节的影子空间(shadow space),这样,被调用的过程就可以选择将寄存器参数保存在这个区域中。
4) 在调用子程序时,堆栈指针(RSP)必须进行 16 字节边界对齐(16 的倍数)。CALL 指令把 8 字节的返回值压入堆栈,因此,除了已经减去的影子空间的 32 之外,调用程序还必须从堆栈指针中减去 8。后面的示例将显示如何实现这些操作。
提示:调用 Irvine64 链接库中的子程序时,不需使用 Microsoft x64 调用规范;只在调用 Windows API 函数时使用它。
调用过程示例
现在编写一段小程序,使用 Microsoft x64 调用规范来调用子程序 AddFour。这个子程序将四个参数寄存器(RCX、RDX、R8 和 R9)的内容相加,并将和数保存到 RAX。由于过程通常使用 RAX 返回结果,因此,当从子程序返回时,调用程序也期望返回值在这个寄存器中。这样就可以说这个子程序是一个函数,因为,它接收了四个输入并(确切地说)产生了一个输出。
;在64模式下调用子程序 ExitProcess PROTO WriteInt64 PROTO ;Irvine64链接库 Crlf PROTO ;Irvine64链接库 .code main PROC sub rsp,8 ;对准堆栈指针 sub rsp,20h ;为影子参数保留32个字节 mov rcx,1 ;依序传递参数 mov rdx,2 mov r8,3 mov r9,4 call AddFour ;在RAX中查找返回值 call WriteInt64 ;显示数字 call Crlf ;输出回车换行符 mov ecx,0 call ExitProcess main ENDP AddFour PROC mov rax,rcx add rax,rdx add rax,r8 add rax,r9 ;和数保存在RAX中 ret AddFour ENDP END
现在来看看本例中的其他细节:第 10 行将堆栈指针对齐到 16 字节的偶数边界。为什么要这样做?在 OS 调用主程序之前,假设堆栈指针是对齐 16 字节边界的。然后,当 OS 调用主程序时,CALL 指令将 8 字节的返回地址压入堆栈。将堆栈指针再减去 8,使其减少成一个 16 的倍数。
可以在 Visual Studio 调试器中运行该程序,并查看 RSP 寄存器(堆栈指针)改变数值。通过这个方法,能够看到用图形方式在下图中展示的十六进制数值。
上图只展示了每个地址的低 32 位,因为高 32 位为全零:
1) 执行第 10 行前,RSP=01AFE48。这表示在 OS 调用本程序之前,RSP 等于 01AFE50。( CALL 指令使得堆栈指针减 8。)
2) 执行第 10 行后,RSP=01AFE40,表示堆栈正好对齐到 16 字节边界。
3) 执行第 11 行后,RSP=01AFE20,表示 32 个字节的影子空间位置从 01AFE20 到 01AFE3F。
4) 在 AddFour 过程中,RSP=01AFE18,表示调用者的返回地址已经压入堆栈。
5) 从 AddFour 返回后,RSP 再一次等于 01AFE20,与调用 AddFour 之前的值相同。
与调用 ExitProcess 来结束程序相比,本程序选择的是执行 RET 指令,这将返回到启动本程序的过程。但是,这也就要求能将堆栈指针恢复到其在 main 程开始执行时的位置。下面的代码行能替代 CallProc_64 程序的第 20 和 21 行:
add rsp,28 ;恢复堆栈指针 mov ecx,0 ;过程返回码 ret ;返回 OS
提示:要使用 Irvine64 链接库,将 Irvine64.obj 文件添加到用户的 Visual Studio 项目中。Visual Studio 中的操作步骤如下:在 Solution Explorer 窗口中右键点击项目名称,选择 Add,选择 Existing Item,再选择 Irvine64.obj 文件名。
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算