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