rpa*_*001 3 x86 assembly 68000 masm addressing
我正在关注Jack Crenshaw的编译器教程(如果你看一下我的个人资料,这就是我所有的问题都是关于大声笑的)并且它刚刚到了引入变量的地步.他评论说68k要求所有东西都是"与位置无关"的,这意味着它是"PC相对的".我得到的PC是程序计数器,在x86上它是EIP.但他使用的语法就像MOVE X(PC),D0X是一个变量名.我已经阅读了一点,之前没有说明在.data中声明一个变量.这是如何运作的?为了在x86中工作,我将用什么替换X(PC)MOV EAX, X(PC)?
说实话,我甚至不确定这是否应该输出正常工作的代码,但到目前为止它已经和我已经添加了代码到我的编译器,添加了相应的标题等和批处理文件来汇编,链接和运行结果.
许多处理器支持 PC 相对或绝对寻址。
然而在 X86 机器上有以下限制:
可以进行 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]
Run Code Online (Sandbox Code Playgroud)
如果在编译/链接期间未知内存中代码的地址(例如,对于 Linux 下的动态库(DLL)),则使用此方法,因此变量的地址(此处位于地址“x+1234”)不知道已知,尚未。
下面简要概述一个静态分配的全局变量(这个问题是关于什么)真正是什么以及如何处理它们.
对机器来说,没有变量这样的东西.它永远不会听到它们,它从不关心它们,它只是没有它们的概念.它们只是为RAM中的特定位置分配一致含义的约定(在虚拟内存的情况下,在地址空间中的位置).
你实际上放置变量的地方,取决于你 - 但在合理范围内.如果你要写它(你可能是),最好是在一个可写的位置,这意味着:该变量的地址应该在一个可分配和可写的内存区域内..data部分只是其他约定.你不必调用它,你甚至不需要一个单独的部分(你可以使你的.text部分可写并在那里分配你的全局,如果你真的想要),你甚至可以使用OS函数VirtualAllocEx(或等效的) )在固定位置分配内存并使用它(但不要这样做).由你决定.但.data部分是一个方便的放置它们的地方.
"分配"变量只是选择一个地址,使变量不与任何其他变量重叠.这并不难,只需按顺序排列它们:var_ptr在你要放置它们的任何地方的开始处开始一个指针(所以.data部分的VA,或者如果你使用链接器则为0),然后是每一个变量v:
l的vISalign(var_ptr, round_up_to_power_of_2(sizeof(v)))var_ptr为l + sizeof(v)作为一个小变化,你可以跳过对齐(大多数编译器教科书都这样做,但在现实生活中你应该对齐).x86通常可以让你逃脱.
作为一个更大的变化,您可以尝试"填充对齐"留下的"孔".填充至少大多数孔的最简单方法是将变量排序为最大 - 如果所有大小都是2的幂,则填充所有空洞.虽然这可能会节省一些空间(虽然不一定是任何空间,因为部分是自己对齐的),但它永远不会节省太多.在通常的对齐规则下,"只是按顺序排列" - 算法在最坏的情况下会浪费几乎一半的空间.导致这种模式的模式是最小类型和最大类型的交替序列.说实话,这不会真的发生 - 即使它确实发生了,那也不是那么糟糕.
然后,您必须确保.data段足以容纳所有变量,并且初始内容与变量初始化的内容相匹配.
但你甚至不需要做任何这些.您可以在汇编代码中使用变量声明(您知道如何执行此操作),然后汇编器/链接器(它们通常都在此处起作用)将为您完成所有这些(当然,它也将用变量地址替换变量名称.
这取决于.如果您使用的是汇编程序/链接程序,请参阅您为变量指定的标签.当然,标签不必与源代码中的名称匹配,它可以是任何合法的唯一名称(例如,您可以使用声明的AST节点ID,前面有下划线).
因此加载变量可能如下所示:
mov eax, dword ptr [variablelabel]
Run Code Online (Sandbox Code Playgroud)
或者,在x64上,也许这个
mov eax, dword ptr [rel variablelabel]
Run Code Online (Sandbox Code Playgroud)
哪个会发出一个rip相对地址.如果这样做,您不必关心RIP的当前值或分配变量的位置,汇编器/链接器将负责处理它.在x64上,使用这样的RIP相对地址很常见,原因如下:
mov rax,[imm64]和mov [imm64],rax如果您没有使用汇编程序和/或链接程序,那么(至少在某种程度上)它会成为您自己的工作,用您为它们分配的任何地址替换变量名称(如果您使用的是链接器但没有汇编程序,那么制作重定位数据,但你不会自己决定变量的绝对地址.
当您使用绝对地址时,您可以将"放入"并与发出指令并行(前提是您已经分配了变量).当您使用RIP相对地址时,只能在确定代码所在的位置时将它们放入(因此您将发出偏移量为0的代码,执行一些记账,确定代码的位置,然后是返回并用实际偏移量替换0,这本身就是一个非常重要的问题,除非你使用天真的方式并且不关心分支大小优化(在这种情况下你知道指令的地址)你发射它的时间,以及变量相对于RIP的偏移量是多少.RIP相对偏移很容易计算,只需从变量的VA(虚拟地址)中的当前指令之后立即减去位置的RIP .
您可能希望使一些变量不可写,以至于任何以"编译无法检测到的有趣方式"写入它们的尝试都将失败.这可以通过将它们放在只读段中来实现,通常称为.rdata(但名称实际上是无关紧要的,重要的是该段的"可写"标志是否在PE头中设置).虽然它有时用于字符串或数组常量(不是正确的变量),但这种做法并不常见.
什么是定期进行,是把零初始化变量在自己的节,一节,需要在可执行文件中没有空间,但不是简单地归零.放置零初始化变量可以节省可执行文件中的一些空间.此部分通常称为.bss(不是 bullsh*t部分的缩写),但与往常一样,名称无关紧要.
大多数编译器教科书都会以不同的数量来处理这个主题,但通常不是很详细,因为当你接受它时:静态变量并不难.当然没有比较汇编的大多数其他方面.此外,某些方面非常特定于平台,例如各部分的详细信息以及事物实际上如何以可执行文件结束.
一些来源/有用的东西(我在编译器上工作时发现所有这些都很有用):