Assembly 汇编程序如何计算符号地址的段和偏移量?
我已经学习了编译器和汇编语言,所以我想编写自己的汇编程序作为练习。但我有一些问题 如何计算@DATA或类似OFFSET/ADDR VarA的段的地址 以简易装配程序为例:Assembly 汇编程序如何计算符号地址的段和偏移量?,assembly,compiler-construction,masm,x86-16,memory-segmentation,Assembly,Compiler Construction,Masm,X86 16,Memory Segmentation,我已经学习了编译器和汇编语言,所以我想编写自己的汇编程序作为练习。但我有一些问题 如何计算@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
.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
放入立即数?汇编程序不知道@data
和msg
将在内存中结束,因此在对象(.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将在何处将可执行文件加载到内存中。(与现代主流操作系统下每个进程都有自己的虚拟地址空间不同,real mode只有一个地址空间,程序必须与设备驱动程序和TSR以及操作系统本身共享。)
因此,链接器将在生成的可执行文件中进行重新定位,以告知MS-DOS根据加载的位置调整立即操作数
请注意,您可能希望编写一个仅适用于完整程序且仅生成.COM可执行文件的汇编程序,从而简化您的汇编程序编写练习。这样你就不用担心搬迁了。您的汇编器将决定在.COM格式允许的单个段中放置所有内容的位置。请注意,由于.COM文件不支持段重定位,因此无法使用诸如
mov ax、@data
或mov ax、SEG msg
之类的指令。相反,CS=DS=ES=SS在程序启动时,由操作系统的程序加载器选择一个值。(该值在组装时未知。)
如何计算@DATA或类似OFFSET/ADDR VarA的段的地址
有两种情况:
a) 汇编程序本身正在生成一个平面二进制文件或可执行文件,不涉及任何链接器
b) 汇编程序正在生成一个对象文件,稍后发送给链接器
注意,你可以有一个混合物。例如,在某些汇编器(例如NASM)中,有一些关键字用于创建临时节(例如,
absolute
),并且通过内部使用临时节(结构中的字段是从地址零开始的临时节的偏移量)来支持结构
对于这两种情况;汇编程序将源代码转换为某种内部表示形式(例如,可能是“指令数据、操作数1数据、操作数2数据等”,其中指令的内部表示形式如“jmp foo
”和“mov eax,bar/5+33
”可以简化得太多,需要在符号表中包含一些对符号的引用
对于符号表本身,每个条目都有一个符号名称(例如“foo”),它位于哪个部分,该部分内的最小可能偏移量和该部分内的最大可能偏移量。当最低可能偏移量和最高可能偏移量匹配时,并且该段具有已知地址,汇编程序可以用实际值替换内部表示中对该符号的引用
请注意,在某些情况下,您要等到以后才能知道指令的大小(例如,对于80x86;“jmp-foo
”如果目标地址接近,则可能是2字节指令,但如果目标地址不接近,则可能需要是3字节指令或5字节指令,并且在了解“foo”将具有的值之前,您无法做出决定);当你不知道一条指令有多大的时候,你就不知道同一部分后面出现的任何符号的偏移量。这就是为什么您希望符号同时具有最低可能偏移量和最高可能偏移量的原因,这样即使您不知道符号的实际偏移量,您仍然可以知道偏移量足够小或太大,并且仍然可以确定指令的大小(并更好地了解该部分后面符号的值)
更具体地说,在汇编过程中,您希望执行多个过程,其中每个过程尝试将每条指令的中间表示形式转换为更具体/完整的版本,并尝试改进符号的最低可能偏移量和最高可能偏移量值(这样您就有了更多/更好的信息,下一关可以使用)
当您完成了“多次传递”,并且汇编器正在生成一个平面二进制文件,并且不涉及任何链接器时,所有内容都将是已知的(包括sec的地址)