MASM汇编中伪指令ASSUME的作用
在学习16位MASM宏汇编时,开始犯的一个错误就是认为assume就是给CS、SS、DS等段寄存器进行初始化赋值的。但又需要在源码的开始阶段,通过mov ax,datasg和mov ds,ax对DS进行手动赋值,assume ds:datasg不是已经赋值过了么,倍感疑惑!
比如下面代码:
assume cs:codesg,ss:stacksg,ds:datasg1,es:datasg2 ;------------------------------------------- stacksg segment stack db 100 dup (0) stacksg ends ;------------------------------------------- datasg1 segment tnum1 db 6 datasg1 ends ;------------------------------------------- datasg2 segment tnum2 db 9 datasg2 ends ;------------------------------------------- codesg segment start: mov ax,datasg1 mov ds,ax mov ax,datasg2 mov es,ax ;------------------------------------------- mov al,tnum1 mov al,tnum2 ;------------------------------------------- mov ax,4c00h int 21h codesg ends end start
一、伪指令的概念
伪指令就是编译器(masm)将汇编代码编译成机器码时,为编译器提供信息,本身并不生成相应的机器码。
二、寻址的概念
就是cpu去哪里获取数据或指令,换句话就是说每一个含有操作数的汇编指令,这些操作数如果含有标号(示例中:start:)、变量名(示例中:tnum1)等,这些符号都代表着什么。
1、这些符号都代表的是地址,即去这些地址获取数据。示例中的mov ax,tnum1就会编译成mov ax,[0000],cpu会去偏移地址0000处获取数据。
2、16位的程序都是通过段地址(segment address):偏移地址(offset address)的方式计算地址,但许多指令中并不体现段地址,而是通过默认的方式进行指定,如jmp的默认段地址是cs;push和pop的默认段地址是ss;mov的默认段地址是ds。
三、编译器的地址解析
编译器(masm)其中一个作用就是把变量或标号解析成地址,一般都是偏移地址,如示例中的变量tnum1解析成[0000]。谈到偏移地址,对应的肯定就有段地址。这个[0000]对应的是哪个段,不同段的偏移地址[0000]处的内容并不相同,如datasg1偏移地址[0000]处的值就是6;datasg2偏移地址[0000]处的值就是9。mov al,tnum1或mov al,tnum2对应的执行码都是mov ax,[0000],只是段地址不相同,CPU是获得正确的值呢,请看示例代码编译后debug反汇编的代码:
示例中通过修改默认段寄存器进行区分(机器码26),编译器帮我们把位于datasg2段内的数据前加上了ES段前缀,CPU在执行这段代码时就会通过ES:OFFSET(tnum2)的方式寻址。这时ES的值是不是执行datasg2,编译器和CPU都不管,因此,执行这段代码(ES: mov al,[0000])前,要手动赋值(mov ax,datasg2 mov es,ax)。
当然也可以不修改默认段寄存器,通过修改DS的值,即在mov al,tnum2前添加mov ax,datasg1和mov ds,ax的方式定位tnum2。
四、ASSUME的作用
assume就是masm在编译代码时,如果遇到需要确定段寄存器的代码时,根据assume的设定(段与段寄存器的对应关系),生成该情况下对应的代码。官方文档:Subsequent instructions that assume a default register for referencing labels or variables automatically assume that if the default segment is segmentregister, then the label or variable is in the name segment or group 。
默认情况下,编译器会将各个段中的变量解析成与该段开始地址对应的偏移地址,即tnum1对应段datasg1的偏移地址是[0000];tnum2对应段datasg2的偏移地址也是[0000]。
如果没有assume,masm按默认情况处理,只生成偏移地址,本示例则生成的机器码相同。CPU在执行这2处指令时,则CPU都是去DS的偏移地址[0000]处获取数据,显然会出错。
如果添加assume伪指令,masm按assume的假设,把datasg1地址对应着ds,因tnum1位于datasg1段,该处不用调整。把datasg2对应着es,因tnum2位于datasg2段,该处需修改默认段寄存器,即添加机器码26。
CPU执行到此处,执行mov指令前,现将默认寄存器改为ES。
五、手动添加段前缀(segment-override operator)
如果不添加assume,手动添加段前缀也能达到相同效果,但每句都要添加。如示例mov al,tnum1默认的ds,如果ds的值是datasg1的地址,则正确执行。mov al,tnum2,如es的值是datasg2的地址,则改成:mov al,es:tnum2。
注意:这里也可以将mov al,tnum2改成mov al,ds:tnum2,则偏移地址是相对于datasg1的开始地址的,也没问题(当然如果这样,就没必要分段啊)。
六、初始化段寄存器
前面的操作都是以ds的值是datasg1的地址,es的值是datasg2的地址为前提的,assume和masm都是在这个的前提下进行偏移地址解析的。但在执行阶段,ds和es的值是不是对应的段地址,cpu无法确定,所以需要在代码最前面对ds和es进行手动初始化(如示例)。为什么ds的值和es的值,不