汇编语言学习笔记(七)-寻址方式

寻址一直以来都是汇编中的关键内容,但是在平坦内存模式下,汇编寻址的难度降低很多,不过Intel CPU必须经历段式内存模式后才可到达页式存储,所以如果系统底层编程,也可能会涉及到实模式下的程序设计,需要掌握段式寻址。

对于实模式,处理器可以寻址20位的地址空间,但是寄存器只有16位,为了能达到20位的寻址,需要使用 段:偏移 这样的方式寻址,格式为 segreg:offset,计算出的直接偏移地址为 segreg * 16 + offset,相当于段基址左移4位后加上偏移,这样刚好有20位的地址,可以达到2M内存寻址。 在平坦内存模式下,寻址类似于c的指针,一个基址加上偏移。

对AT&T来说,寻址方式比较怪异,但又非常简洁,语法格式如下:

	segreg:base_address(offset_address, index, size)
	
	;例子
	movl %eax, label1(, $2, $4)
	movl %ebx, (label2, $2,)
	movl %ecx, (%esp)

其效果为 segreg:base_address + offset_address + index * size segreg为分段模式下段寄存器,base_address为基址,offset_address 为偏移,index * size决定了第几个元素,其中size为元素长度,只能为1,2,4,8等等,这些元素都是可选的,index默认为0,size默认为1。

对于MASM,表达相同含义的语法如下:

	segreg:[base_address + index * scale + offset_address]
	
	;例子
	mov label1[2 * 4], eax
	mov [label2 + 2], ebx
	mov [esp], ecx

对MASM,有些指令并不知道寻址以后是该取1字节还是多字节,也就是说需要制定寻址以后数据的长度,这个长度可由ptr操作符声明,比如:

	movzx eax, word ptr [base + 2];

这样指定在base + 2地址处读取word宽度字节。而在AT&T中,由于指令可跟w,l等后缀,无需再次指令,这里可以看到AT&T简洁之处。

另外,对AT&T,如果需要取全局符号的地址,可以使用$var(见框架),对于MASM,还需另外一个操作符:offset,相同的功能可写为offset var。当然也可使用lea指令,lea指令为取地址指令,语法如下:

	mov eax, offset var
	lea eax, [var]

这里跟offset的区别在于,offset在编译期间可以指定全局变量的位置,无需运行时计算,但是offset无法取得局部变量的 地址,因为局部变量位于堆栈,是在运行时才可得知其地址,而lea指令与之相反,是在运行时计算变量(可为全局和局部变量)的地址,二者的另一个区别在于 lea为cpu指令,AT&T同样有与之对应的指令(同其他指令一样,操作数相反,且指令可跟长度后缀),而offset仅仅为编译器指令,编译 成机器码之后只有一个地址,没有offset的影子。

下面举一个简单的例子,仍然是翻译c代码到汇编:

	void swap(int * a, int * b)
	{
		int tmp = *a;
		*a = *b;
		*b = tmp;
	}
	
	int main()
	{
		int a[10] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};
		for (int i = 0; i < 10; ++i)
			for (int j = i; j < 10; ++j)
			{
				if (a[i] > a[j])
					swap(a + i, a + j);
			}
		return 0;
	}

这里c代码是一个效率比较低的排序,我们改写成AT&T汇编 sort.s :

	.section .data
		array: .int 1, 3, 5, 7, 9, 2, 4, 6, 8, 0
	
	.section .text
	.globl _main
	_main:
		xorl %eax, %eax
	loop1:
		movl %eax, %ebx
	loop2:
		movl array(, %ebx, 4), %ecx
		cmpl %ecx, array(, %eax, 4)
		jbe label1
		xchg %ecx, array(, %eax, 4)
		movl %ecx, array(, %ebx, 4)
	label1:
		incl %ebx
		cmpl $10, %ebx
		jb loop2
	
		incl %eax
		cmpl $10, %eax
		jb loop1
	
	label2:
		pushl $0
		call _exit

这里的代码并不复杂,loop1, 和loop2为两个循环,之间的操作与c的含义相同,这里不做解释,其中的诸多细节需要细细体会,这里再给出MASM的一个版本,完全是从AT&T版本直译过来的。

	.386
	.model flat,stdcall
	option casemap:none
	
	include    windows.inc
	include    kernel32.inc
	includelib kernel32.lib
	
	.data
	array	dd 1, 3, 5, 7, 9, 2, 4, 6, 8, 0
	
	.code
	start:
	    xor eax, eax
	loop1:
		mov ebx, eax
	loop2:
		mov ecx, array[ebx * 4]
		cmp array[eax * 4], ecx
		jbe label1
		xchg ecx, array[eax * 4]
		mov array[ebx * 4], ecx
	label1:
		inc ebx
		cmp ebx, 10
		jb loop2
	
		inc eax
		cmp eax, 10
		jb loop1
	
	label2:
	
	    invoke ExitProcess, NULL
	end start

这里可以充分看出AT&T和MASM的语言的不同之处。

Built with Hugo
主题 StackJimmy 设计