Assembler如何计算偏移量?

时间:2015-04-20 13:58:06

标签: assembly compiler-construction

我已经学习了“编译器”和ASM语言,所以我喜欢编写自己的asm编译器作为练习。但是我有一些问题,如何计算@DATA或类似OFFSET / ADDR VarA的偏移?

以一个简单的asm程序为例:

.model small
.stack 1024
.data
      msg db 128 dup('A')
.code
start:
    mov ax,@data
    mov ax,ds
    mov bx, offset msg
    mov ah,4ch
    int 21h
end

那么剂量汇编程序如何计算数据段的偏移量(例如@data)?以及它如何处理mov bx offset msg? THX

2 个答案:

答案 0 :(得分:2)

汇编程序不知道@datamsg将在内存中的哪个位置,因此在对象中生成称为重定位(或#34; fixups")的指令(.OBJ )文件,允许链接器和操作系统填写正确的值。

让我们看一下稍微不同的示例程序会发生什么:

.model small
.stack 1024
.data
    msg db 'Hello, World!,'$'
.code
start:
    mov ax,SEG msg
    mov ds,ax
    mov bx,OFFSET msg
    mov ah,09h
    int 21h
    mov ah,4ch
    int 21h
end start

汇编此文件时,汇编程序无法知道链接器将放置此示例程序定义的任何内容。这对您来说可能显而易见,但汇编程序无法假设它看到了完整的程序。汇编程序不知道您是否将其与其他目标文件或库链接,这可能导致链接器将msg放在除数据段开头之外的某处。

因此,当此示例程序汇编到目标文件中时,汇编程序会生成两个重定位记录。如果使用MASM汇编文件,可以在使用/ Fl开关生成的列表文件中看到:

 0000               start:
 0000  B8 ---- R            mov ax,SEG msg
 0003  8E D8                mov ds,ax
 0005  BB 0000 R            mov bx,OFFSET msg
 0008  B4 09                mov ah,09h

列表的机器代码列中操作数旁边的R表示它们具有重定位参考它们。当链接器从目标文件创建MS-DOS格式可执行文件时,它将能够从msg的数据段开头提供正确的偏移量。

然而,链接器无法提供msg(数据段)段的位置,因为链接器不知道MS-DOS将可执行文件加载到内存的位置。因此链接器将在生成的可执行文件中放置一个重定位,告诉MS-DOS根据加载的位置调整立即操作数。

请注意,您可能只想编写一个只能使用完整程序并仅生成.COM可执行文件的汇编程序。这样你就不用担心重新安置了。您的汇编程序将决定在.COM格式允许的单个段中放置所有内容的位置。请注意,由于.COM文件不支持细分重定位,因此无法使用mov ax,@datamov ax,SEG msg等指令。

答案 1 :(得分:1)

  

如何计算@DATA或OFFSET / ADDR VarA等段的地址?

有2种情况:

a)汇编程序本身会生成一个平面二进制文件或可执行文件,并且不涉及链接器

b)汇编器正在生成一个目标文件,以后再发送给链接器

请注意,您可以混合使用。例如,在某些汇编程序(例如NASM)中,存在用于创建临时节(例如absolute)的关键字,并且结构在内部使用临时节得到支持(结构中的字段是临时节的偏移量,从地址零开始)。

对于两种情况;汇编程序将源代码转换为某种内部表示形式(例如,“指令数据,操作数1数据,操作数2数据……”),其中诸如“ jmp foo”和“ mov eax,bar/5+33”可以简化得太多,需要在符号表中包含对符号的一些引用。

对于符号表本身,每个条目都有一个符号名称(例如“ foo”),它位于该节中,该节中的最小偏移量和该节中的最大偏移量。当可能的最低偏移量和最高的偏移量匹配且该段具有已知地址时,汇编程序可以将内部表示形式中对该符号的引用替换为实际值。

请注意,在某些情况下,您可能直到以后才知道一条指令的大小(例如,对于80x86;如果目标地址接近,则“ jmp foo”可能是2字节指令,但可能需要如果目标地址未关闭,则为3字节指令或5字节指令,直到您对“ foo”的值有所了解后才能决定);当您不知道一条指令的大小时,您将不知道同一节后面出现的任何符号的偏移量。这就是为什么您最终希望符号同时具有最小的偏移量和最大的偏移量的原因-因此,即使您不知道符号的实际偏移量,您仍然可以知道偏移量将足够小或太大,并且可以仍要确定一条指令的大小(并对该部分中后面的符号的值有更好的了解)。

更具体地说;在组装时,您需要进行多次遍历,每次遍历都会尝试将每条指令的中间表示形式转换为更具体/完整的版本,并尝试提高符号的最低偏移量和最高偏移值(以便您拥有更多/更好的符号)下一遍可以使用的信息。

完成“多次传递”后,汇编器将生成平面二进制文件,并且不涉及链接程序;一切都知道了(包括节的地址和节内所有符号的偏移量,并将所有指令转换为实际字节),您可以生成最终文件。

完成“多次通过”后,汇编器将生成目标文件;有些事情是未知的(各节的地址),有些事情是未知的(各节内所有符号的偏移量,所有指令的大小);并且目标文件格式将为您提供一种方式,您可以从中提供您不知道/不知道的内容的详细信息(例如,需要修复的列表以及链接程序可以用来修复它们的信息)。指令和符号表的中间表示还剩下什么?

请注意,对于某些情况,有些情况可能太复杂而无法支持目标文件格式(例如,前面提到的“ mov eax,bar/5+33”),在这种情况下,可以毫无问题地进行汇编的指令(如果汇编程序是生成平面二进制文件)必须视为错误(如果汇编器正在生成目标文件)。尝试创建目标文件时,您会发现这些情况(并生成适当的错误消息)。

请注意,所有这些都适合于一个很好的“ 3相”排列,其中“前端”将“纯文本”输入转换为中间表示,“中间”(多次通过)优化中间尽可能多地表示,“后端”生成一个文件。只有后端需要关心目标文件格式是什么。