尽管高级编程语言层出不穷,但汇编语言仍然是现代设备的基础,无论是智能手机、平板电脑、桌面电脑还是服务器。如果理解了汇编语言,其他任何语言对来说都只是一套新的语法元素和一些有趣的新概念。虽然在汇编语言中进行大型项目可能没有实际的用处,但每个程序员至少应该对其底层原理有所了解。
大多数常见的编程范式并不是语言的属性,而是一种编写源代码的方式。语言可以提供支持以避免错误并简化编写过程,但仍然可以自己实现这些范式。本项目展示了完全面向对象的风格、多线程工作以及所有Windows x64程序的特性。
Visual Studio 2013包含了MASM(Microsoft Macro Assembler),它可以直接汇编源文件。如果在项目中遇到任何问题,请不要犹豫,立即联系。为了更好地布局ASM源代码,使用了Visual Studio的插件来格式化源代码。这将成为不久将来的子项目(希望如此)。目前,该插件还不值得公开发布。
以下是一些有趣的代码片段:
; x64 过程堆栈帧
actSetProgress PROC FRAME
LOCAL hwndList:QWORD
; 进度列表
LOCAL xItem:LVITEM
; 新列表项
LOCAL txBuffer [128]:WORD
; 命令名称
LOCAL xRange:PBRANGE
; 进度条范围
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字节上对齐。
; 基于寄存器的调用,参数超过4个
; 前四个参数在寄存器中,其他在堆栈上
; 但仍然为前四个参数保留空间
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),这将是块中的第一个成员。如果在对象之间共享这个表,它就代表了类和对象之间的区别。如果在列表中更改方法,它就是某种形式的重载或继承。
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 ?
; 初始化按钮
pfnLoadConfig dq ?
; 加载配置
pfnSaveConfig dq ?
; 保存配置
pfnGetRect dq ?
; 获取按钮矩形
pfnRender dq ?
; 渲染单个按钮矩形
CLASS_BUTTON_IFACE ends
CLASS_BUTTON struc
vtableThis dq ?
; 对象的方法
pxApp dq ?
; 父对象
unCmd dq ?
; 要执行的命令
txText dw 32 dup(?)
; 按钮上的文本
txParam dw DEF_PATH_LENGTH dup(?)
; 命令参数
xParams VIEW_PARAM
; 视图参数
unShortcut dq ?
; 键盘快捷键
unCol dq ?
; 水平位置
unRow dq ?
; 垂直位置
txSection dw 64 dup(?)
; 按钮配置部分
xInterface CLASS_BUTTON_IFACE
; 按钮接口
CLASS_BUTTON ends
如果类是这样定义的,并且正确地对齐到8字节,它将完全兼容Visual Studio C++类,所以甚至可以与C++对象交互或从C++调用汇编方法。
; 调用对象上的方法
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]