汇编程序如何计算符号地址的段和偏移量



我已经学习了编译器和汇编语言,所以我想编写自己的汇编程序作为练习。但我有一些问题;

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

以一个简单的组装程序为例:

.model small
.stack 1024
.data
msg db 128 dup('A')
.code
start:
mov ax,@data
mov ax,ds
mov dx, offset msg
; DS:DX points at msg
mov ah,4ch
int 21h            ; exit program without using msg
end

那么汇编程序如何计算@data段的段地址呢?

它如何知道mov dx, offset msg的立即数中应该放入什么?

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

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

.model small
.stack 1024
.data
msg db 'Hello, World!,'$'
.code
start:
mov ax,SEG msg
mov ds,ax
mov dx,OFFSET msg
mov ah,09h
int 21h              ; write string in DS:DX to stdout
mov ah,4ch
int 21h              ; exit(AL)
end start

当汇编这个文件时,汇编程序无法知道链接器将把这个示例程序定义的任何东西放在哪里。这对你来说可能很明显,但汇编程序不能认为它看到了一个完整的程序。汇编程序不知道你是否会将它与其他对象文件或库链接,这可能会导致链接器将msg放在数据段开头以外的其他地方。

因此,当这个示例程序被汇编成一个对象文件时,汇编程序会生成两个重新定位记录。如果你使用MASM来组装文件,你可以在使用/Fl开关生成的列表文件中看到这一点:

; listing of the .obj assembler output, before linking
0000               start:
0000  B8 ---- R            mov ax,SEG msg
0003  8E D8                mov ds,ax
0005  BA 0000 R            mov dx,OFFSET msg
0008  B4 09                mov ah,09h

列表的机器代码列中操作数旁边的R表示它们具有引用它们的重定位。当链接器从对象文件创建MS-DOS格式的可执行文件时,它将能够为msg提供从数据段开始的正确偏移量。该值是一个链路时间常数,因此只有.obj而不是.exe需要对其进行重新定位

但是,链接器将无法提供msg段(数据段)的位置,因为链接器不知道MS-DOS将在哪里将可执行文件加载到内存中。(与现代主流操作系统中每个进程都有自己的虚拟地址空间不同,真实模式只有一个地址空间,程序必须与设备驱动程序、TSR以及操作系统本身共享。)

因此,链接器将在生成的可执行文件中重新定位,告诉MS-DOS根据加载位置调整立即操作数。


请注意,您可能希望通过编写一个只适用于完整程序并只生成.COM可执行文件的汇编程序来简化汇编程序的编写练习。这样你就不用担心搬迁了。汇编程序将决定所有内容在.COM格式允许的单个段中的位置。请注意,由于.COM文件不支持段重定位,因此不能使用mov ax,@datamov ax,SEG msg之类的指令。相反,在程序启动时,CS=DS=ES=SS,其值由操作系统的程序加载程序选择。(组装时还不知道这个值。)

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

有两种情况:

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

b) 汇编程序正在生成一个目标文件,稍后发送到链接器

注意,你可以有一个混合物。例如,在一些汇编程序(如NASM)中,有关键字来创建临时部分(如absolute),并且通过内部使用临时部分来支持结构(结构中的字段是从地址0开始的临时部分的偏移量)。

对于这两种情况;汇编程序将源代码转换为某种内部表示(例如,可能是"指令数据、操作数1数据、操作数2数据…"之类的东西),其中"jmp foo"one_answers"mov eax,bar/5+33"等指令的内部表示可能过于简化,需要在符号表中包含对符号的一些引用。

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

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

更具体地说;在组装时,您需要执行多个过程,其中每个过程都试图将每条指令的中间表示转换为更具体/完整的版本,并尝试改进符号的尽可能低的偏移量和尽可能高的偏移量值(这样您就可以获得下一个过程可以使用的更多/更好的信息)。

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

当您完成了"多次传递",并且汇编程序正在生成一个对象文件时;有些事情是未知的(节的地址),有些事情是已知的(节内所有符号的偏移量,所有指令的大小);对象文件格式将为您提供一种方式来提供您不知道/不知道的事情的详细信息(例如,需要修复的事情的列表,以及链接器可以用来修复它们的信息),您可以从指令的中间表示和符号表的剩余部分提供这些信息。

请注意,对于对象文件格式来说,可能存在过于复杂而无法支持的情况(例如,可能是早期的"mov eax,bar/5+33"),其中可以毫无问题地汇编的指令(如果汇编器正在生成平面二进制)必须被视为错误(如果汇编器正在生成对象文件)。在尝试创建对象文件时,您将发现这些情况(并生成相应的错误消息)。

请注意,这一切都符合一个很好的"3阶段"安排,其中"前端"将"纯文本"输入转换为中间表示,"中间端"(多次传递)尽可能细化中间表示,而"后端"生成一个文件。只有后端才需要关心目标文件的格式

最新更新