我一直在研究 1950 年代“Pegasus”计算机的模拟,并遇到了“自我修改链接”一词。这是如何运作的?
2 回答
我们首先要注意:
- 代码块被加载到顺序寄存器中执行
- 每个寄存器包含 39 位:一个数据字或两个指令。
Pegasus 计算机通过传递关于被调用者应如何返回的两条指令(在一个寄存器中)来支持子程序/函数调用——作为数据,即作为参数。然后,被调用者将获取该参数,将其存储到自己的本地内存中,并将控制权转移到那里——从而执行调用者作为数据传递的两条指令。
调用者会这样做:
load instructions from L1: into X1 register
... pass other parameters ...
load callee's first block into U0
transfer control to U0 at start
L1: load instructions from main block back into U0
transfer control to U0 at desired caller resume point (e.g. L2)
L2: ...caller resumes...
调用者会这样做:
... compute something ...
store X1 into L4 # writes the two instruction pair in X1 into L4
transfer control to L4 # may or may not be present depending on location of L4
L4: # starts empty but contains the two instructions that came from L1
# so control is transferred back to the caller
L1 处的序列从不直接执行,而是将指令作为参数传递给被调用者的模板,被调用者将该模板存储到自己的本地内存中,然后执行该副本。
他们使用术语“提示”来表示调用(子程序调用,例如将控制权转移给被调用者),使用“链接”表示返回(将控制权从子程序转移回正确的调用者)(以及机器代码指令的术语“命令”) .
因此,当他们谈到“自修改链接”时,他们的意思是子例程返回其调用者的机制使用自修改代码,即在执行时更改自己的指令的代码,即编写的代码进入指令存储器以便立即执行。这里是将 X1(L1 的代码副本)写入 L4,在写入后立即执行。
如今,自修改代码通常不受欢迎,因为它有几个负面影响:指令缓存需要与自修改保持同步,这会干扰性能,并且指令存储器必须是可写的,这会干扰安全性.
由于我们今天可能认为指令集中缺少指令(例如 PDP-8 具有 I/O 指令但 I/O 端口被硬编码到指令中),因此自修改经常用于旧处理器中没有间接 I/O 端口访问。因此,为了让子程序访问作为参数的 I/O 端口,使用自修改代码构造到该端口的 I/O 指令。(也可以使用大的 switch 语句,但需要更多的代码。)这些较旧的处理器没有指令缓存,程序员和开发工具也没有强制将指令部分与数据部分分开(即它们没有试图保护指令)。
在 Pegasus 的情况下,没有间接分支,阻碍了返回到调用者提供的地址的能力。事实证明,调用者的代码实际上也可能被覆盖了——传递两条指令让被调用者执行的能力允许为调用者的代码恢复一个寄存器块以及在该块内跳转。
(并且将完全形成的指令作为参数传递导致返回链接的指令比其他自修改替代方案更少:在给定地址参数的情况下动态构造跳转指令——这将需要更多的指令来进行位域操作。)
https://en.wikipedia.org/wiki/Ferranti_Pegasus http://bitsavers.org/pdf/ferranti/pegasus/PegasusProgrammingMan_1962.pdf