(MIPS汇编程序)我们可以自行初始化程序计数器吗?

时间:2019-02-22 15:20:41

标签: assembly compiler-construction mips machine-code

作为任务的一部分,我仍在尝试开发MIPS汇编器。我得到了这些输入和输出文件。

main:   lw $a0, 0($t0)          
begin:  addi $t0, $zero, 0      # beginning
    addi $t1, $zero, 1
loop:   slt $t2, $a0, $t1       # top of loop
    bne $t2, $zero, finish
    add $t0, $t0, $t1
    addi $t1, $t1, 2
    j loop              # bottom of loop
finish: add $v0, $t0, $zero

,输出应为以下机器代码:

10001101000001000000000000000000
00100000000010000000000000000000
00100000000010010000000000000001
00000000100010010101000000101010
00010101010000000000000000001000
00000001000010010100000000100000
00100001001010010000000000000010
00001000000000000000000000000011
00000001000000000001000000100000

我注意到代表指令“ j循环”的机器代码是

00001000000000000000000000000011

根据J型指令格式,最后26位将代表目标地址。我从上面编写的二进制代码中注意到,此跳转指令的目标地址(基本上是“循环”的地址)是00000000000000000000000011,即3。

我开发了该程序的版本,但是它为“循环”而检索到的目标地址却要大得多。

我想知道是否可以通过任何方式将程序计数器初始化为0或其他任何方式,以便获得与给定输出文件中的目标地址相同的目标地址。程序计数器到底如何工作?它会为每一行代码递增吗?

请告知。谢谢!

1 个答案:

答案 0 :(得分:1)

这里有很多的复杂性,可以用于任何形式的完整答案。不过,对于MIPS组装,我们[请参见下面的评论]可能会有所休息。

我们需要考虑寻址模式相对寻址绝对寻址的概念。这是因为,像zwol mentioned in a comment一样,编译器和汇编器的输出通常实际上不是准备运行的 code ,而是真正的目标文件,由 linker 和/或 loader 解释的指令。

链接器是一个程序,它接收多个目标文件并将它们组合成一个更完整的程序。这可以采取其他目标文件的形式,也可以采取本质上是目标文件集合的库的形式。如果库格式足够简单,则可以通过聚合目标文件来构建库,并可以选择添加目录,但是有时您需要进行一定数量的预链接,以将特定的目标文件连接到一起牢不可破的单元,以后可以链接到更多目标文件或库。链接器可能非常复杂,因为它们可能必须处理符号名(函数名和变量名)并为调试器提供信息(符号表,内存区域描述等)。

加载器提取经常被链接器至少部分解析,有时甚至完全解析的目标文件,并将其加载到内存中。一些加载程序本身就是链接程序,通常称为 runtime 链接程序或 runtime 加载程序。这样,可执行的目标文件就可以在运行时加载其他目标文件,而不是预先预先链接所有内容。

不过,通常是由加载时操作将实际地址分配给代码和数据。目标文件可能包含说明,说明代码可以在任何地方运行,或者代码必须在某个特定(固定)地址上运行。相同的规则可能适用于数据。如果需要固定地址,则该地址可能不可用,因此通常需要可重新定位的代码-可以从某种默认地址移动到另一个不同地址的代码。

这导致了相对寻址的概念。假设机器通过重复执行一些非常简单的步骤来工作:

  1. 从IP(指令指针)或PC(程序计数器)寄存器给定的地址加载指令。
  2. 将该寄存器增加一些常数,例如4。
  3. 执行刚刚加载的指令。

分支 指令由一个指令组成,该指令用于更改 IP / PC寄存器,或者更改为一些新值,或者加或减一些值。

现在,假设可执行对象文件建议,例如,将程序加载到地址0x04000000。进一步假设第十条指令(将位于地址0x04000028)是一个分支指令,并且需要进行设置,以便从{{1 }},即 third 指令:

0x0400000c

鉴于上面的模型,IP或PC寄存器将在执行指令#10的过程中跳转到指令#3的04000000 instruction#0 04000004 instruction#1 04000008 instruction#2 0400000c loop: instruction#3 04000010 #4 04000014 #5 04000018 #6 0400001c #7 04000020 #8 04000024 #9 04000028 j loop 0400002c 保持值j loop,因为我们已描述了该操作作为“加载,以4为增量,执行”。

如果我们需要使用绝对寻址,我们需要实际的0400002c指令将字面值j loop 直接填充到指令中-指针寄存器。但是,可能只有 loader 知道程序是否真的在0400000c上运行。如果使用了该地址,则加载程序可能已将程序移至04000000,现在要推入i-p寄存器的值为08000000

但是,如果我们使用的是 relative 寻址,则0800000c指令需要汇编为机器代码,而不是“转到j loop”,而是“转到从我们现在的位置0400000c向前或向后退到我们要位于0400002c的位置”。显然,这是一个向后的跳跃,增加了0400000c或20(十六进制,32十进制)字节,或八条指令的价值。

编辑:请参阅下面的评论,下一部分是错误的-我依靠其他StackOverflow答案和我引用的网页来假设PC相对跳转。我已经对其进行了更新,以对0400002c - 0400000c指令使用绝对寻址。

MIPS处理器使用称为j的寄存器(但难以访问),并支持条件分支中的相对寻址(例如,pc;请参见Assembly PC Relative Addressing Mode )。因此,某些复杂性可能会消失:我们只需要指示CPU向后跳八条指令,即向PC寄存器添加负八位即可。 CPU自动将此值乘以4,以便将负值加32。如果我们确实加载到beq,则04000000将是pc,并将其移回原位将其更改为0400002c,这正是我们想要的。如果实际上是将我们加载到0400000c,则相同的 relative 移动将我们降落到08000000,这正是我们想要的。

如果我们使用0800000c指令,情况就是这样。但是b指令在256 MB的区域内是绝对的:它们只是覆盖程序计数器的低28位。

通常,我们将有一个汇编器输出带有重定位类型的绝对j指令,该指令将告诉任何运行时加载器:添加所需的任何加载时偏移。因此,我们只需要确保在组装时知道打算的加载位置(无论是jump还是0等等),并且我们将为04000000指令,目标指令的绝对地址,还有一些其他链接器/加载器指令,它们表示:该指令中的常量可能需要在链接或加载时进行调整。链接器和加载器必须足够聪明,才能理解寻址限制:移动程序以使 not 正常,以便使原来适合一个256 MB区域的程序现在跨两个此类区域(如果代码段使用) j说明在一个区域内跳转。

(网站https://en.wikibooks.org/wiki/MIPS_Assembly/MIPS_Details声称j是相对的,但这似乎是错误的;请参见注释。)

(请注意,负数表示为二进制补码。由于j指令采用26位相对地址,它会自动为您乘以4,因此它可以表示28位地址范围,从- 2 27 到2 27 -1或j,以4为步长。)