寻址
寻址根据操作数的不同可以分为三个大类:
1. 立即数寻址,如mov ecx, 0h; 这里的0h就是一个立即数;
2. 寄存器寻址,如mov eax, ebx; 这里的eax、ebx,包括前面的ecx都是寄存器寻址;
3. 存储器寻址,如mov eax, [src]; 这里的src(见之后的例子)是一个内存操作数,它表示的必须是一个有效的内存地址。
另外,根据寻址方式的不同,还可以分为直接寻址和间接寻址两个大类。
直接寻址
首先看一个例子:
得到的结果如下:
mov操作中的src,src+4实际上就是一个地址,通过src可以得到该地址位置的值。
需要注意几点:
1. 对于MASM来说,这里的[]可加可不加,结果都是得到地址内的值;
2. 对于MASM来说并没有做有效地址的判断,因此存在越界的可能:
上例中就踩到了dest的数据区域了。
由于src实际上就是一个地址,所以可以得到它的具体的地址值:
使用lea和OFFSET得到的结果是一样的,区别在于lea是运行时得到的,而OFFSET是编译时决定的。也就是说实际上src的值在编译时就决定了。如果同时运行两个程序,得到的地址会不会是一样的呢?
开启两个不同的程序,得到的结果是一样的:
第一个程序结果:
第二个程序结果:
为什么两个程序会用到同一个地址?
由于每次启动程序,得到的src的地址都是固定的011A5880h,因此可以确定的是一定是编译器决定了src的值(两次编译得到的值都不同)。而为什么两个不同的程序都使用了相同的地址,这个应该是因为在保护模式下,每个程序都有一个独立的4G的寻址空间。另外还需要了解的是这里的src实际上重定位后的地址,不过这个以后讲,先不关注。
回到直接寻址来,因为实际上src的地址值就是011A5880h,那是否可以直接在mov指令中使用这个地址来寻址呢?
从得到的结果可以看到,至少在MASM,这种类型的直接寻址是不能使用的。
这种mov ebx, 011A5880h; 可以说是真正的直接寻址。而mov eax, [src]; 这种有另外一个名字叫基址寻址。两种类型可以认为类似,但是在MASM里面不能使用直接地址的寻址。
间接寻址
使用寄存器作为指针并操纵寄存器的值来寻址,就是间接寻址。
首先需要了解汇编中的指针,包含其它变量地址的变量,就是指针变量,简称指针。
上例中的ptr1和ptr2就是指针。可以看到指针和变量并没有本质的区别。
如果将指针复制给寄存器,并用寄存器来访问指针所指向的变量的值,就是间接寻址了:
ptr1实际上就是src的地址,因此也可以直接使用OFFSET来实现间接寻址:
注意这里的[]就不能省略了,如果省略:
所以这里的[eax]表示eax表示的地址中的值。在MASM中,对于寄存器,[xxx]和xxx是不一样的,而对于普通的地址(比如src),效果是相同的。对于[eax],eax是一个间接操作数。
间接操作数可以作为源操作数,也可以作为目的操作数:
红框中的[edi]表示的间接寻址将dest中的FFh替换成了00h,得到的结果也符合预期:
注意红框中的DWORD PTR不可少,如果不加会报错:
因为[edi]只是表示了一个地址,通过它不能确定移动数据的大小;而00h是一个立即数,也没有一个确定的大小(00h可以表示成一个BYTE,也可以表示成一个WORD 0000h,等等),因此需要DWORD PTR确定了数据移动的大小。
另外,对指针还有一个需要补充的东西,在C语言中,对于指针p的p++操作有不同的解释,如果p是int指针,则是加4(假设int是4字节的);对于char指针,则是加1(假设char是1字节的),那么汇编中的指针是否也相同呢?
通过inc ptr1,ptr1的值只增加1,此时指向的值EBX并不是期望的01h。实际上这个例子是有问题的,汇编中的inc和C语言中的p++并不是对应的。inc就是简单的加1,而p++中的++实际上转化成汇编后会根据p指向的类型而加不同的值。
这一点可以通过反汇编来证明:
反汇编的结果中有:
如果将i的类型改成char,结果如下:
间接寻址的地址表示方式可以有多种,下面这一种和C语言中的数组很类似:
PS:文中代码使用VS2012进行编译,文中使用的部分代码,如DumpRegs来自《Intel汇编语言程序设计》,具体使用方法可参见【asm基础】vs下使用汇编。