什么是PC相关寻址以及如何在MASM中使用它?

时间:2013-08-26 15:26:25

标签: assembly x86 masm 68000 addressing

我正在关注Jack Crenshaw的编译器教程(如果你看一下我的个人资料,那就是我的所有问题都是关于大声笑的)并且它刚刚到了引入变量的地步。他评论说68k要求一切都是与位置无关的"这意味着它与PC相关"。我认为PC是程序计数器,而在x86上它是EIP。但他使用类似MOVE X(PC),D0的语法,其中X是变量名。我已经读过一点,之后没有说明在.data中声明一个变量。这是如何运作的?为了在x86中实现这一功能,我将在MOV EAX, X(PC)

中替换X(PC)

说实话,我甚至不确定这是否应该输出正常工作的代码,但到目前为止它已经并且我已经为我的编译器添加了代码,添加了适当的标题等等和批处理用于汇编,链接和运行结果的文件。

2 个答案:

答案 0 :(得分:5)

这里简要概述一个静态分配的全局变量(这个问题是关于什么)真正以及如何处理它们。

无论如何

是什么变量

对机器来说,没有变量这样的东西。它永远不会听到它们,它从不关心它们,它只是没有它们的概念。它们只是为RAM中的特定位置指定一致含义的约定(对于虚拟内存,地址空间中的位置)。

你实际上放置一个变量,取决于你 - 但在合理范围内。如果您要写入它(并且您可能是),则最好是在可写位置,这意味着:该变量的地址应该位于已分配和可写的内存区域内。 .data部分只是其他约定。你不必称之为,你甚至不需要一个单独的部分(你可以使.text部分可写并在那里分配你的全局,如果你真的想要),你可以< / em>甚至使用像VirtualAllocEx(或等价物)这样的OS函数来在固定位置分配内存并使用它(但不要这样做)。它取决于你。但.data部分是一个方便的地方。

&#34;分配&#34;变量只是选择一个地址,使变量不与任何其他变量重叠。这并不难,只需按顺序排列:在你要放置它们的任何地方开始指针var_ptr(所以.data部分的VA,或者如果你& #39;重新使用链接器),然后为每个变量v

  • l的{​​{1}}位置为v
  • align(var_ptr, round_up_to_power_of_2(sizeof(v)))设为var_ptr

作为一个小变化,你可以跳过对齐(大多数编译器教科书都这样做,但在现实生活中你应该对齐)。 x86通常可以让你逃脱。

作为一个更大的变化,你可以尝试填补空白&#34;由路线留下。填充至少大多数孔的最简单方法是将变量排序为最大 - 如果所有大小都是2的幂,则填充所有空洞。虽然这可能会节省一些空间(虽然不一定是任何空间,因为部分是自己对齐的),但它永远不会节省太多。在通常的对齐规则下,&#34;只是按顺序排列&#34; - 算法最坏的情况是浪费几乎它在空洞上使用的空间的一半。导致这种模式的模式是最小类型和最大类型的交替序列。说实话,这不会真的发生 - 即使它确实发生了,但并非所有 都不好。

然后,你必须确保.data段足够大以容纳所有变量,并且初始内容与变量初始化的内容相匹配。

但你甚至不必做任何这些。您可以在汇编代码中使用变量声明(您知道如何执行此操作),然后汇编器/链接器(它们通常都在此处起作用)将为您完成所有这些(当然,它也将用变量地址替换变量名。)

如何使用变量

这取决于。如果您正在使用汇编程序/链接器,只需参考您为变量提供的标签即可。当然,标签不必与源代码中的名称匹配,它可以是任何合法的唯一名称(例如,您可以使用声明的AST节点ID,前面带有下划线)。

因此加载变量可能如下所示:

l + sizeof(v)

或者,在x64上,也许这个

mov eax, dword ptr [variablelabel]

哪个会发出一个rip相对地址。如果你这样做,你不必关心RIP的当前值或分配变量的位置,汇编器/链接器将负责它。在x64上,使用类似RIP的相对地址很常见,原因如下:

  • 它允许.data段位于不是第一个4GB(或2GB)地址空间的地方,只要它接近.text段
  • 它比具有绝对64位地址的指令短
  • 只有两条指令甚至采用绝对的64位地址,即mov eax, dword ptr [rel variablelabel] mov rax,[imm64]
  • 您可以免费获得重新安置

如果您没有使用汇编程序和/或链接程序,那么(至少在某种程度上)您可以通过为其分配的任何地址替换变量名称(如果您正在使用)一个链接器,但没有汇编程序,你可以制作重定位数据,但你不会自己决定变量的绝对地址)。

当您使用绝对地址时,您可以将它们放入&#34;与发出指令并行(假设您已经分配了变量)。当您使用RIP相对地址时,只有在确定代码的位置时才能将它们放入(因此您可以发出偏移为0的代码,进行一些记录,确定代码的位置是的,然后你回去用真正的补偿来代替0,这本身就是一个非常重要的问题,除非你使用天真的方式并且不关心分支大小优化(在在这种情况下,您知道发出指令时的地址,因此变量相对于RIP的偏移量是多少。 RIP相对偏移很容易计算,只需从变量的VA(虚拟地址)当前指令后立即减去位置的RIP。

但那不是全部

你可能想让一些变量不可写,以至于任何尝试用#34编写它们的有趣方式都无法写入?#34;将失败。这可以通过将它们放在只读部分(通常称为.rdata)来实现(但名称实际上是无关紧要的,重要的是该部分的#34;可写&#34;标志是否在PE头中设置) 。虽然它有时用于字符串或数组常量(不是正确的变量),但这并不常用。

定期完成的是将零初始化变量放在他们自己的部分中,这个部分在可执行文件中不占用空间,而是简单地将其清零。放置零初始化变量可以节省可执行文件中的一些空间。此部分通常称为.bss(不是 bullsh * t部分的缩写),但与往常一样,名称无关紧要。

更多

大多数编译器教科书都以不同的数量处理这个主题,但通常不是很详细,因为当你接受它时:静态变量并不难。当然没有比较汇编的大多数其他方面。此外,某些方面非常特定于平台,例如各部分的详细信息以及事物实际上如何以可执行文件结束。

一些来源/有用的东西(我在编译器上工作时发现所有这些都很有用):

答案 1 :(得分:3)

许多处理器支持PC相对或绝对寻址。

但是在X86机器上有以下限制:

  • 跳转和呼叫始终是PC相关的(除非基于注册)
  • 其他地址总是绝对的(除非基于注册)

可以进行PC相对寻址的C编译器将通过以下方式实现:

  CALL x
x:
  ; Now address "x" is on the stack
  POP EDI
  ; Now EDI contains address of "x"
  ; Now we can do (pseudo-)PC-Relative addressing:
  MOV EAX,[EDI+1234]

如果在编译/链接时间内未知存储器中代码的地址(例如Linux下的动态库(DLL)),则使用此变量,因此变量的地址(此处位于地址“x + 1234”) )还不知道。