5

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

如何计算诸如@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

4

2 回答 2

6

汇编器不知道在哪里@data以及msg最终会在内存中,因此在对象 (.OBJ) 文件中生成称为重定位(或“修复”)的元数据,允许链接器和操作系统填写正确的值。

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

.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 在程序启动时,其值由操作系统的程序加载器选择。(并且该值在组装时是未知的。)

于 2015-04-20T21:39:24.763 回答
2

如何计算诸如@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”将有);当您不知道指令有多大时,您将无法知道同一部分稍后出现的任何符号的偏移量。这就是为什么您最终希望符号都具有最低可能的偏移量和最大可能的偏移量 - 这样即使您不知道符号的实际偏移量,您仍然可以知道偏移量是否足够小或太大,并且仍然可以确定指令的大小(并获得更好地了解该部分中后面符号的值)。

进一步来说; 在组装时,您想要进行多次传递,其中每次传递尝试将每条指令的中间表示转换为更具体/完整的版本,并尝试改善符号的最低可能偏移量和最高可能偏移量值(以便您拥有更多/更好下一次可以使用的信息)。

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

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

Note that there can be cases that are too complex for an object file format to support (e.g. probably the "mov eax,bar/5+33" from earlier), where an instruction that can be assembled without any problem (if the assembler is generating a flat binary) has to be treated as an error (if the assembler is generating an object file). You will discover these cases (and generate appropriate error messages) when trying to create the object file.

Note that this all fits into a nice "3 phases" arrangement, where the "front-end" converts the "plain text" input into the intermediate representation, the "middle-end" (the multiple passes) refines the intermediate representation as much as possible, and the "back-end" generates a file. Only the back-end needs to care what the target file format is.

于 2019-10-19T04:57:45.337 回答