65.9K
CodeProject 正在变化。 阅读更多。
Home

使用纯 x64 汇编语言访问 Windows API

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (11投票s)

2016年10月7日

CPOL

3分钟阅读

viewsIcon

35709

downloadIcon

1160

本文展示了如何使用纯 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

寄存器 rcxrdxr8r9 保存前四个参数。 如果需要更多参数,则将它们放在堆栈上。 保留前 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 月:首次发布
© . All rights reserved.