Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Assembly 在一条指令中使用与操作数相同的寄存器合法吗?_Assembly_X86 64_Instructions_Instruction Set - Fatal编程技术网

Assembly 在一条指令中使用与操作数相同的寄存器合法吗?

Assembly 在一条指令中使用与操作数相同的寄存器合法吗?,assembly,x86-64,instructions,instruction-set,Assembly,X86 64,Instructions,Instruction Set,例如,下面是一条X86-64指令: movq (%rdi),%rdi 它读取内容(由%rdi)并将此内容设置为%rdi,这在顺序逻辑中有效 或者我们必须将其分配到另一个寄存器并将其移回: movq (%rdi), %rsi movq rsi, %rdi 在同一条指令中重新调整寄存器的用途不仅合法,而且非常常见 在机器代码中,CPU寄存器速度很快,因此是首选存储,但它们的数量有限。而在高级语言中,变量不能用完(需要时声明一个新的变量)——编译器和汇编语言程序员倾向于将使用不重叠的变量映射到同一

例如,下面是一条X86-64指令:

movq (%rdi),%rdi
它读取内容(由
%rdi
)并将此内容设置为
%rdi
,这在顺序逻辑中有效

或者我们必须将其分配到另一个寄存器并将其移回:

movq (%rdi), %rsi
movq rsi, %rdi

在同一条指令中重新调整寄存器的用途不仅合法,而且非常常见

在机器代码中,CPU寄存器速度很快,因此是首选存储,但它们的数量有限。而在高级语言中,变量不能用完(需要时声明一个新的变量)——编译器和汇编语言程序员倾向于将使用不重叠的变量映射到同一存储


此外,在机器代码中,我们可以看到许多在高级语言中看不到的短期变量,这些变量称为临时变量或表达式临时变量。机器代码中像
A[i]
这样的简单表达式需要计算
A+i*4
,可能需要一个临时变量来保存
a
i
i*4
等。这些临时变量是短期变量,如果在后续表达式中不再需要,然后,保存它们的CPU寄存器可以立即重新用于其他用途。

一些ISA对某些指令有限制,例如旧的ARM对使用相同的寄存器作为
umull
的源和目标有一些限制。这些危险始终记录在说明集参考手册1中


英特尔和AMD的x86手册没有记录任何此类限制(8086/1862上的
推送sp
除外),因此您可以始终假设x86-64指令在写入任何输出操作数之前读取其所有输入操作数,这与您预期的完全一样。(无论如何,这是CPU内部管道的正常状态。)


脚注1:例如,Keil对
UMULL{s}{cond}RdLo、RdHi、Rn、Rm的ISA引用:
Rn必须与ARMv6之前的体系结构中的RdLo和RdHi不同。
(该指令执行完全乘法,如x86
mul
,但使用一对显式输出,而不是EDX:EAX,并使用两个显式源。)

奇怪的限制,如果ISA存在的话,通常包括乘法;例如,MIPS在
multu
及其HI:LO寄存器对周围也有很多奇怪之处。这说明即使发生中断,
mflo
之后的
multu
仍可能启动,从而导致中断恢复后的损坏状态。在开始另一个乘法运算之前,您需要将一个乘法运算结果读回普通regs的过程与2条指令分开。另见

这些软件“危险”与硬件的概念相同,但它们是硬件无法处理的情况,而不是无序执行需要检测的情况

x86没有任何这些软件危害;我提到其他国际审计准则只是为了比较和作为一个兴趣点。 脚注2

早期的x86 CPU是微码化的,而不是流水线的,
push
的微码实现实际上在最早的x86 CPU上暴露了一种奇怪的效果。见:

对于Intel 286 on上的IA-32处理器,PUSH ESP指令将ESP寄存器的值按指令执行前的状态推送。(对于IA-32体系结构的英特尔64体系结构、实地址和虚拟8086模式也是如此。)对于英特尔8086处理器,PUSH SP指令将推送SP寄存器的新值(即减2后的值)

但对于286及更高版本(包括所有x86-64 CPU),它的工作原理就像在写入任何操作数之前读取所有操作数一样,包括
push-rsp
push[rsp+8]

PUSH ESP指令按指令执行前的状态推送ESP寄存器的值。如果推送指令使用内存操作数,其中ESP寄存器用于计算操作数地址,则在ESP寄存器递减之前计算操作数地址

还必须记录在将加载结果写入寄存器之前,
pop rsp
首先执行堆栈指针递减。所以
pop-rsp
=
mov-rsp,[rsp]
,没有
添加rsp,8
之后。(本文对此进行了描述。英特尔的伪代码不使用临时代码,因此无法正确描述这种特殊情况。)


顺便说一句,您可以在编译器输出中找到大量类似的指令示例。e、 g.当源是具有寄存器寻址模式的内存操作数时,指针在链表或树中追逐(如果这是在循环中,否则在遍历间接寻址级别时,您只是在重用寄存器)

编制人:


第一条指令是合法的,工作正常。“在写入任何输出操作数之前读取其所有输入操作数,”我想知道
push(r/e)sp
pop(r/e)sp
如何适应这种情况。任何人都不应该使用,但它们确实存在。我在286+检测中使用了一些不同的行为,即186。@ecm:哦,好的,是的,8086/186确实有一个记录在案的特殊情况。我忘记了><这与ARM或MIPS危害有点不同,因为行为是完全记录的,而不是根据微体系结构条件不可预测的。286和更高版本的行为就像在写入任何操作数之前读取所有操作数一样。幸运的是,这个答案只有一个脚注,因为它询问的是x86-64。
int foo(int ****p) {
   int tmp = ****p;
   return tmp * 2;
}
foo(int****):
        mov     rax, qword ptr [rdi]
        mov     rax, qword ptr [rax]
        mov     rax, qword ptr [rax]         # write-only destination
        mov     eax, dword ptr [rax]
        add     eax, eax                     # EAX += EAX,  read-write destination
        ret