【问题标题】:Is 2 pass on the source file necessary for assembler and linker?汇编器和链接器是否需要 2 传递源文件?
【发布时间】:2011-08-30 06:31:59
【问题描述】:

我多次听说汇编器和链接器需要遍历其输入文件至少2次,这真的有必要吗?为什么不能一次性完成?

【问题讨论】:

    标签: linker assembly


    【解决方案1】:

    汇编器将符号汇编语言翻译成二进制表示。

    在输入语言(汇编程序)中,标签也是符号。

    在二进制输出语言中,它们通常是以字节为单位的距离,相对于当前位置或其他一些固定点(例如向前或向后跳转这么多字节)。

    第一遍只是确定从代码开头的偏移量或所有汇编程序指令的某个其他固定点,以固定标签的位置。

    这允许从第二遍中的分支指令计算正确的跳转距离。

    一次通过汇编是可能的,但你只能跳转到你已经声明的标签(“backK”)而不是向前。

    【讨论】:

    • 我只知道运行时调试器(msdos "debug"、内核调试器等),它们通常要求您使用目标地址指定分支(它们用于计算相对位移)。但是,这些不是您所知道的汇编程序。对于作为独立程序的汇编程序,只要我记得,它们就已经通过了两次并允许宏(回到 eigthies Commodore 64 的)。创建一个通过一个是没有意义的,太有限了。
    【解决方案2】:

    一个必要的例子是两个函数相互调用。

    int sub_a(int v);
    int sub_b(int v);
    
    int sub_a(int v) {
        int u = v;
        if ( 0 < u ) {
            u = sub_b( v - 1 );
        }
        return u - 1;
    }
    
    int sub_b(int v) {
        int u = v;
        if ( 0 < u ) {
            u = sub_a( v - 1 );
        }
        return u - 1;
    }
    

    然后有必要进行两次扫描。因为函数的任何排序都将依赖于尚未扫描的函数。

    【讨论】:

      【解决方案3】:

      它甚至可能需要两个以上。

      here:
        ...
        jmp outside
        ...
        jmp there
        ...
        jmp here
        ...
      there:
      

      特别是对于具有某种形式的近跳转和某种形式的远跳转的指令集。汇编程序并不总是想在每个分支/jmp 上浪费很大的精力。以上面的代码为例,当它到达 jmp here 行时,它知道在 here 标签和 jump to here 指令之间有多少条指令。如果需要将其编码为近跳或远跳,它可以做出很好的估计。通常,远版本的跳转是一种需要更多字节来实现的情况,从而导致随后的所有指令和标签发生移位。

      当它遇到 jmp there 指令时,它不知道多远,必须来 稍后再单独通过(通过数据)。当它在那里遇到标签时,它可以返回并查看到目前为止是否有对它的引用,并修补该引用。那是另一个遍历数据,通过 2。或者你只是完整地遍历源代码,然后开始来回遍历数据更多次。

      假设外部跳转不会解析标签。现在根据指令集,汇编器必须做出响应。一些指令集,比如说msp430,其中远跳转仅仅意味着内存中的绝对地址,所有内存空间,没有段或类似的东西。好吧,您可以简单地假设一个远跳并留下地址供链接器稍后填写。一些指令集,如 ARM,你必须在附近分配一些内存 指令的范围。经常将东西隐藏在无条件分支后面(这可能是一件坏事并且失败)。基本上你需要为整个地址分配一个地方 可以引用外部项目,编码指令以从附近的内存位置加载,然后让链接器稍后填写地址。

      回到这里和那里。如果在第一遍中您假设所有未知的跳转都在附近并且在第一遍中基于此计算地址怎么办。如果在那次传递中,对于一个只有 128 个字节的指令集,jmp here 指令正好是 128 个字节。所以你假设 jmp here 也很近,如果发现跳转到那里有 127 个字节,这是你最大的近前跳转,那么这会很痛苦。但是外面没有找到!它必须很远,所以你需要燃烧更多的字节,现在这里到 jmp 这里太远了它需要更多的字节,现在那里的 jmp 太远了它需要更多的字节。弄清楚这三件事需要多少次数据传递?两个以上。一关就开始。第二遍标记外部尽可能远,假设在第二遍时 jmp 附近有 jmp,当它到达 jmp here 时,它​​发现必须是远跳转,导致那里的地址发生变化。第三遍它发现 jmp 需要很远,这会影响该指令之后的所有内容。对于这个简单的代码,一切都解决了。

      考虑冒泡排序。你不断循环遍历数据进行交换,直到你有一个标志说,我在最后一次传递中没有做任何更改,表明一切都已解决,我们完成了。您必须使用汇编程序玩相同的游戏。对于像 ARM 这样的指令集,你需要做一些事情,比如尝试找到隐藏地址和常量/立即数的地方,这些地址和常量/立即数不会编码成一条指令。那就是如果汇编程序 想为你做这项工作。您可以轻松声明错误并说出目的地 对于选择的指令来说太远了。 Arm 组装器让你可以偷懒,做如下事情:

        ldr r0,=0x1234567
        ...
        ldr r1,=lab7
        ...
      lab7:
      

      汇编器查看那个 = 并且知道它必须确定,我可以在指令中编码那个常量/立即数(为你将你的 ldr 更改为 mov)还是我需要在你的代码中找到一个位置来放置单词,然后用 a 对指令进行编码 地址偏移附近。

      即使不处理近处和远处,也可以简单地解析地址,外部,那里,上面的示例需要两次传递。第一遍读取所有内容,跳到这里恰好知道第一遍这里在哪里。但是您必须再次通过程序(不一定来自磁盘,可以将信息保存在内存中)可能会跳转到此处,该跳转位于此处:标签之前。第二遍将找到外部跳转,并且知道程序中没有外部标签,根据汇编程序的规则,在第二遍将其标记为未解析或外部。第二遍将那里的跳转解析为已知标签,第二遍不会与此处的跳转混淆,因为它解决了它 第一关。这是您的经典两道题/解决方案。

      链接器也有同样的问题,它必须通过所有的源,把每个对象想象成源代码中的一个复杂行。它找到所有标签,包括在对象中找到的标签和在对象中未解析的标签。如果它在 10 个文件中的第二个文件中发现我需要一个“外部”标签,它必须遍历所有 10 个文件,然后返回文件或内存中的数据以解析所有前向引用的标签。它不会在第一次出现 jmp outside 时知道没有外部标签,第二次通过是当它在外部找到 jmp 时,查看它保存的找到标签的列表(可以认为是第三次通过)找不到外部标签并声明错误。

      【讨论】:

      • 啊,超级简短的回答。如果系统有足够的内存来保存二次传递数据所需的信息,它不必对源文件进行多次传递。如果系统内存有限,它可能会选择仅存储一些内容,例如行号和与该行号问题相关的一些信息,然后通过实际文件进行更多传递。它可以使用第二个文件来存储该信息,并且仍然不需要触摸实际文件,但仍然需要进行两次传递。
      • 通过次数是算法扫描数据的次数,而不是从磁盘加载的次数。虽然 DWelch 是对的,但在近/远极限的大小范围内跳跃会造成问题。但在这种情况下,通常会产生一个远跳,以使汇编器-链接器分离更容易。只有嵌入式汇编程序真正尝试解决此问题,但通常它们会吃掉相对较小的代码。
      猜你喜欢
      • 1970-01-01
      • 2021-08-30
      • 1970-01-01
      • 1970-01-01
      • 2016-02-16
      • 2013-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多