探索汇编语言在现代编程中的应用

尽管高级编程语言层出不穷,但汇编语言仍然是现代设备的基础,无论是智能手机、平板电脑、桌面电脑还是服务器。如果理解了汇编语言,其他任何语言对来说都只是一套新的语法元素和一些有趣的新概念。虽然在汇编语言中进行大型项目可能没有实际的用处,但每个程序员至少应该对其底层原理有所了解。

汇编语言如何使用现代技术,例如面向对象编程(OOP)?

大多数常见的编程范式并不是语言的属性,而是一种编写源代码的方式。语言可以提供支持以避免错误并简化编写过程,但仍然可以自己实现这些范式。本项目展示了完全面向对象的风格、多线程工作以及所有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),这将是块中的第一个成员。如果在对象之间共享这个表,它就代表了类和对象之间的区别。如果在列表中更改方法,它就是某种形式的重载或继承。

通过手工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 ? ; 初始化按钮 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++调用汇编方法。

调用对象方法(MS风格,即STDMETHODCALL):

; 调用对象上的方法 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]
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485