使用纯 x64 汇编语言访问 Windows API
本文展示了如何使用纯 x64 汇编编程语言(MASM 风格)访问 Windows API。 它还将概述如何在低级别上应用一些编程技术,例如 OOP 和多线程。
引言
这个工具是一个简单的 64 位 Windows 文件管理器,具有 100% 的汇编语言源代码。 该工具将作为在汇编语言环境中使用高级技术的示例,例如原生 64 位、面向对象和堆栈帧的展开能力。
等等,汇编语言? 它不是已经死了吗?
不,它没有。 虽然编程语言变得越来越高级,但汇编语言仍然是每个现代设备、智能手机、平板电脑、台式机或服务器的基础。 如果你理解汇编语言,那么每种其他语言都只是一组新的语法元素和一些有趣的新概念。
用汇编语言做一个大型项目可能没有任何实际意义,但每个程序员至少应该了解底层发生了什么。
汇编语言如何使用像 OOP 这样的现代技术?
大多数常见的编程范例不是语言的属性,而是一种如何编写源代码的方式。 语言可以为你提供支持以避免错误并使编写更容易,但是你仍然可以自己完成。
该项目具有完全面向对象的风格、多线程工作和所有用于 x64 程序的 Windows 功能。
如何使用汇编语言?
Visual Studio 2013 包含 MASM(Microsoft Macro Assembler),它可以立即汇编源文件。 如果您在使用该项目时遇到任何问题,请随时联系。
为了更好地布局 ASM 源文件,我们使用 VStudio 插件来格式化源代码。 这将在不久的将来成为一个子项目(希望如此)。 目前,该插件不值得公开发布。
Using the Code
一些有趣的代码片段
//
// x64 procedure stack frame
//
actSetProgress PROC FRAME
LOCAL hwndList:QWORD ; progress list
LOCAL xItem:LVITEM ; new list item
LOCAL txBuffer [128]:WORD ; command name
LOCAL xRange:PBRANGE ; progress bar range
push rbp
.pushreg rbp
push r12
.pushreg r12
push r15
.pushreg r15
mov rbp, rsp
.setframe rbp, 0
sub rsp, 8 + 2 * 128 + sizeof LVITEM + sizeof PBRANGE
sub rsp, 48
.allocstack 48 + 8 + 2 * 128 + sizeof LVITEM + sizeof PBRANGE
.endprolog
aspDone: mov rax, 0
add rsp, 48
add rsp, 8 + 2 * 128 + sizeof LVITEM + sizeof PBRANGE
pop r15
pop r12
pop rbp
ret 0
align 4
过程以 PROC FRAME
开头,因为 MASM 无法在 x64 模式下声明参数,所以后面没有任何内容。
之后,声明具有适当数据类型的局部变量。 应手动推送和弹出使用的寄存器,并使用 .pushreg
关键字声明为如此。 必须手动分配变量空间,以及此过程内部任何调用使用的空间(例如本例中的 sub rsp, 48
)。 堆栈使用量用 .allocstack
声明。 由于所有调用都是基于寄存器的,因此必须在返回时更正堆栈并使用 ret 0
。 最后,标头以 .endprolog
结尾。
你必须注意堆栈的对齐方式为 8 字节。 代码应与 4 对齐。
调用基于寄存器的函数
//
// register based call with more than 4 parameters
// first four paramters in registers, others on stack
// but keep space for first four parameters anyway
//
lea rax, [r15.VIEW_PARAM.txFontName]
mov [rsp + 104], rax
mov dword ptr [rsp + 96], DEFAULT_PITCH OR FF_DONTCARE
mov dword ptr [rsp + 88], DEFAULT_QUALITY
mov dword ptr [rsp + 80], CLIP_DEFAULT_PRECIS
mov dword ptr [rsp + 72], OUT_DEFAULT_PRECIS
mov dword ptr [rsp + 64], DEFAULT_CHARSET
mov dword ptr [rsp + 56], FALSE
mov dword ptr [rsp + 48], FALSE
mov eax, dword ptr [r15.VIEW_PARAM.unItalic]
mov dword ptr [rsp + 40], eax
mov eax, dword ptr [r15.VIEW_PARAM.unWeight]
mov dword ptr [rsp + 32], eax
mov r9d, 0
mov r8d, 0
mov edx, 0
mov ecx, dword ptr [r15.VIEW_PARAM.unFontSize]
call CreateFont
寄存器 rcx
、rdx
、r8
和 r9
保存前四个参数。 如果需要更多参数,则将它们放在堆栈上。 保留前 64 位参数的空间,因为该过程可以在需要时将参数写回堆栈。
进行基本的面向对象
每个对象都必须是一个小的已分配内存块。 它包含它定义的方法列表 (vtable
),它将始终是块中的第一个成员。 如果你在对象之间共享此表,则它代表类和对象之间的区别。 如果你更改列表中的方法,它将是某种重载或继承。
通过手工 new
操作生成一个新对象
buttonNew PROC FRAME
...
; -- allocate and set vtable --
call GetProcessHeap
test rax, rax
jz btnExit
mov r8, sizeof CLASS_BUTTON
mov edx, HEAP_ZERO_MEMORY
mov rcx, rax
call HeapAlloc
test rax, rax
jz btnExit
lea rcx, [rax.CLASS_BUTTON.xInterface]
mov [rax.CLASS_BUTTON.vtableThis], rcx
; -- fill in method pointers --
lea rdx, btnInit
mov [rcx.CLASS_BUTTON_IFACE.pfnInit], rdx
lea rdx, btnLoadConfig
mov [rcx.CLASS_BUTTON_IFACE.pfnLoadConfig], rdx
lea rdx, btnSaveConfig
mov [rcx.CLASS_BUTTON_IFACE.pfnSaveConfig], rdx
lea rdx, btnGetRect
mov [rcx.CLASS_BUTTON_IFACE.pfnGetRect], rdx
lea rdx, btnRender
mov [rcx.CLASS_BUTTON_IFACE.pfnRender], rdx
对象大小在进程堆中分配。 带有方法指针的表位于对象数据内部。 所有指针都写入表中,并且表指针被填充到第一个位置。
示例类定义
CLASS_BUTTON_IFACE struc
pfnInit dq ? ; init button
pfnLoadConfig dq ? ; load configuration
pfnSaveConfig dq ? ; save configuration
pfnGetRect dq ? ; get buttons rect
pfnRender dq ? ; render single button rectangle
CLASS_BUTTON_IFACE ends
CLASS_BUTTON struc
vtableThis dq ? ; objects methods
pxApp dq ? ; parent object
unCmd dq ? ; command to execute
txText dw 32 dup(?) ; text on button
txParam dw DEF_PATH_LENGTH dup(?) ; parameters for command
xParams VIEW_PARAM ; view parameters
unShortcut dq ? ; keyboard shortcut
unCol dq ? ; horizontal position
unRow dq ? ; vertical position
txSection dw 64 dup(?) ; button configuration section
xInterface CLASS_BUTTON_IFACE ; button interface
CLASS_BUTTON ends
如果以这种方式定义类并使其正确对齐到 8 字节,它将与 Visual Studio C++ 类完全兼容,因此你甚至可以与 C++ 对象交互或从 C++ 调用汇编方法。
调用对象方法(MS 风格,也称为 STDMETHODCALL
)
//
// calling a method on an object
//
lea r9, txTitle
mov r8, [r12.CLASS_APP.unLang]
mov rdx, IDS_ABOUT_TITLE
mov rcx, r12
mov rax, [r12.CLASS_APP.vtableThis]
call [rax.CLASS_APP_IFACE.pfnLoadString]
对象必须在 rcx
寄存器中,rax
保存 vtable
指针,并且调用转到 vtable
内部所需的方法。
历史
- 2016 年 10 月:首次发布