我仍在尝试开发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 loop”的机器代码是
00001000000000000000000000000011
根据J型指令格式,最后26位将代表目标地址。我注意到上面写的二进制代码,这个跳转指令的目标地址(基本上是“循环”的地址)是00000000000000000000000011,即3。
我开发了我的程序版本,但它为“循环”检索的目标地址远大于此。
我想知道是否有任何方法可以初始化程序计数器为0或任何其他所以我可以得到与我给出的输出文件中的目标地址相同的目标地址。程序计数器究竟是如何工作的?它是否为每行代码增加了自己?
请指教。谢谢!
这里的任何完整答案都有很多复杂性。但是,对于MIPS组装,我们可以[看下面的评论]稍微休息一下。
我们需要考虑寻址模式以及相对寻址与绝对寻址的概念。这是因为,作为zwol mentioned in a comment,编译器和汇编器的输出通常不是现成的代码,而是目标文件,充满了由链接器和/或加载器解释的指令。
链接器是一个程序,它接收多个目标文件并将它们组合成一个更完整的程序。这可以采用另一个目标文件的形式,或者基本上是目标文件集合的库。如果库格式足够简单,可以简单地通过聚合目标文件来构建库,可以选择添加目录,但有时您希望进行一定量的预链接,将特定的目标文件连接到一起一个牢不可破的单元,用于以后链接更多的目标文件或库。链接器可能非常复杂,因为它们可能必须处理符号名称(函数和变量名称)并为调试器提供信息(符号表,内存区域描述等)。
加载器获取通常至少部分由链接器解析的目标文件,有时完全解析,并将其加载到内存中。一些加载器本身是连接器,通常称为运行时链接器或运行时加载器。这允许可执行目标文件在运行时加载其他目标文件,而不是事先预先链接所有内容。
但是,这种或那种方式通常是加载时操作,它将实际地址分配给代码和数据。目标文件可能包含说明代码可以在任何地方运行的指令,或者代码必须在某个特定(固定)地址运行的指令。相同的规则可能适用于数据。如果需要固定地址,则该地址可能不可用,因此通常需要可重定位的代码 - 可以从某种默认地址移动到另一个不同的地址。
这导致了相对寻址的概念。假设一台机器通过重复执行一些非常简单的步骤来工作:
分支指令包含一个指令,用于将IP / PC寄存器更改为某个新值,或者通过添加或减去某个值。
现在,假设可执行对象文件建议将程序加载到地址0x04000000
,例如。进一步假设第十条指令 - 它将在地址0x04000028
-是一个分支指令,并且需要进行设置,以便从0x0400000c
加载下一条指令,即第三条指令:
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
鉴于我们的模型,在执行指令#10期间,IP或PC寄存器将跳转到指令#3的j loop
,保持值0400002c
,因为我们将操作描述为“load,increment-by-4,execute ”。
如果我们需要使用绝对寻址,我们需要实际的j loop
指令将字面值0400000c
直接填充到指令指针寄存器中。但是,它可能只是加载器知道程序是否真的在04000000
上运行。如果该地址正在使用中,则加载程序可能已将程序移至08000000
,而现在推入i-p寄存器的值为0800000c
。
然而,如果我们使用相对寻址,那么j loop
指令需要汇编到机器代码,而不是“转到0400000c
”,而是“从我们现在的位置前进或后退,0400002c
,到我们想要的位置0400000c
”。这显然是一个向后的飞跃,由0400002c - 0400000c
或20(十六进制,32位十进制)字节,或八个指令的价值。
编辑:看下面的评论,下一部分是错误的 - 我依赖于其他StackOverflow答案和我引用的网页假设PC相对跳跃。我更新了这个以使用j
指令的绝对寻址。
MIPS处理器使用名为pc
的寄存器(但难以访问),并支持条件分支中的相对寻址(例如,beq
;请参阅Assembly PC Relative Addressing Mode)。因此,一些复杂性可能会消失:我们只需要指示CPU向后跳八条指令,即向PC寄存器添加负八。 CPU会自动将此值乘以4,因此它会增加负32。如果我们真的装在04000000
,pc
将是0400002c
并将它移回这很多变为0400000c
,这是我们想要的。如果我们真的被加载到08000000
,相同的相对移动将我们带到0800000c
,这就是我们想要的。
如果我们使用b
指令就是这种情况。但j
指令绝对在256 MB区域内:它们只是覆盖程序计数器的低28位。
通常,我们将有一个汇编器输出我们的绝对jump
指令,其重定位类型告诉任何运行时加载器:添加所需的任何加载时偏移量。所以我们只需要确保,当我们组装时,我们知道我们打算加载的位置 - 无论是0
,还是04000000
,或者其他什么 - 我们将为j
指令发出目标的绝对地址指令,还有一些额外的链接器/加载器指令,说明:此指令中的常量可能需要在链接或加载时调整。请注意,链接器和加载器必须足够智能才能理解寻址约束:如果代码段使用j
指令跳转,那么移动程序以使得适合在一个256 MB区域内的内容现在跨越两个这样的区域是不正常的。一个地区。
(网站https://en.wikibooks.org/wiki/MIPS_Assembly/MIPS_Details声称j
指令是相对的,但这似乎是错误的;请参阅注释。)
(注意,负数表示为2的补码。由于j
指令采用26位相对地址,它自动乘以4,它可以表示28位地址范围,从-227到227-1,或者-08000000..07fffffc
,步骤为4.)