【问题标题】:(MIPS Assembler) Can we initialize the Program Counter on our own?(MIPS 汇编程序)我们可以自己初始化程序计数器吗?
【发布时间】:2019-02-22 15:20:41
【问题描述】:

我仍在尝试开发 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 或任何其他方法,以便获得与输出文件中给定的目标地址相同的目标地址。程序计数器究竟是如何工作的?它会为每一行代码自增吗?

请指教。谢谢!

【问题讨论】:

  • 是的,cpu运行代码时会自动递增。由于您正在编写汇编程序,因此您需要维护它的副本。它只是当前指令的地址。你可以初始化任何东西。不涉及魔法。正如我在您的另一个问题中已经说过的那样,地址被移动了 2,因此在您的情况下它实际上是 0x0c
  • 感谢您的回答。所以,地址总是移动了两位,对吧?但是我究竟该如何初始化它......?是否像为某个变量分配零一样简单?我认为情况并非如此......?
  • 将机器代码加载到 RAM 并初始化 PC 以指向起始地址不是汇编程序的工作。在一个完整的操作系统中,通常汇编器产生一个“目标代码”文件,在所有跳转指令上都有注释,称为“重定位记录”,它告诉下一个处理阶段,“链接”,调整地址以匹配哪里代码实际上将被放置在内存中。这不是一个很好记录的过程,我唯一的建议是一整本书,约翰·莱文的 Linkers and Loaders
  • 你的汇编器大概有一个变量来维护指令的地址。您需要它来解析标签。将该变量初始化为零,并为每条指令将其递增 4,如果您支持指令,请执行任何适当的操作。
  • @Jester 澄清一下……那个变量是我自己定义的,对吧?例如,我将其定义为start_counter,然后基本上只为读取的文件中的每条指令将其增加 4?

标签: assembly compiler-construction mips machine-code


【解决方案1】:

这里的任何完整答案都有很多的复杂性。不过,对于 MIPS 组装,我们可能 [见下面的 cmets] 会稍作休息。

我们需要考虑寻址模式相对寻址绝对寻址的概念。这是因为,正如zwol mentioned in a comment,编译器和汇编器的输出通常不是实际上可以运行的代码,而是目标文件,充满了指令被 linker 和/或 loader 解释。

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

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

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

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

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

branch 指令由一个指令组成,用于更改 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直接填充到指令指针寄存器中。但是,可能只有 loader 知道程序是否真的 运行在04000000。如果该地址正在使用中,则加载程序可能已将程序移至 08000000,而要推入 i-p 寄存器的值现在改为 0800000c

但是,如果我们使用相对寻址,j loop 指令需要汇编成机器代码,而不是“转到0400000c”,而是“向前或向后从我们现在的位置,0400002c,到我们想要的位置0400000c"。这显然是向后飞跃,相差0400002c - 0400000c 或 20(十六进制,32 十进制)字节,或 8 条指令的价值。

编辑:见下面的 cmets,下一部分是错误的——我依赖于 StackOverflow 的另一个答案和我引用的网页来假设 PC 相关的跳转。我已将其更新为对 j 指令使用绝对寻址。

MIPS 处理器使用名为pc 的寄存器(但难以访问),并支持条件分支中的相对寻址(例如beq;参见Assembly PC Relative Addressing Mode)。因此,一些复杂性可能会消失:我们只需要指示 CPU 向后跳转 8 条指令,即向 PC 寄存器添加负 8。 CPU 会自动将此值乘以 4,从而添加负 32。如果我们真的在04000000 加载,pc 将是0400002c 并将其移回这么多将其更改为0400000c,这就是我们想要的。如果我们真的在08000000 加载,相同的relative 移动将我们降落在0800000c,这就是我们想要的。

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

一般来说,我们将有一个汇编程序输出我们的绝对 jump 指令,其重定位类型告诉任何运行时加载程序:添加所需的任何加载时间偏移量。所以我们只需要确保当我们组装时,我们知道我们打算在哪里加载——无论是0,还是04000000,或者其他什么——我们将发出j指令,目标指令的绝对地址,还有一些附加的链接器/加载器指令说:此指令中的常量可能需要在链接或加载时调整。请注意,链接器和加载器必须足够智能了解寻址约束:如果代码段使用j 指令在一个地区。

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

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

【讨论】:

  • MIPS j 指令并不完全与 PC 相关。它们在一个区域内是绝对的(PC+4 的前 4 个地址位保持不变)。 How to Calculate Jump Target Address and Branch Target Address?。您的示例不起作用:如果没有运行时修复,在 0x800002c 加载的 j 0x400000c 仍会跳转到 0x400000c,因为它们都在同一个 1GB 区域内。如果您想要一个真正的相对无条件分支,您可以使用beq $zero, $zero, target,它确实 为 PC 添加了偏移量。 (别名 b = 始终分支)。
  • @PeterCordes:我会买那个(它实际上更接近我多年前的回忆),但是为什么我链接的另一个答案和网页说它们是 pc 相关的?
  • 因为它是关于分支,而不是跳跃。 (不幸的是,问题和答案都没有提到跳转不是那样的。) MIPS 有两种不同类别的控制转移指令。 (而使用 MIPS64r5 或 r6 我忘记了哪个,也没有分支延迟槽的分支和跳转。)
  • 啊哈!好的,尽管我看到了这些示例,但实际上这些示例是针对条件分支的。
  • 是的,但请记住,您可以通过测试 $zero == 本身来编码无条件的相对分支。就像我说的,b 伪指令就是这样做的。这就是您在与位置无关的代码中实现跳转的方式。无论如何,要实现一个汇编器,你必须同时支持两者。
猜你喜欢
  • 2011-10-07
  • 2014-11-19
  • 2012-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-06
相关资源
最近更新 更多