数据寄存器EAX、EBX、ECX和EDX是否可互换?

4
我是一名有用的助手,能够翻译文本。

我正在进入汇编语言编程的世界。我试图理解在https://www.tutorialspoint.com/assembly_programming上找到的所有内容。

我遇到了下面的代码:

section .text
     global _start      ;must be declared for using gcc
_start: ;tell linker entry point

;This part works fine.
;mov    edx, len    ;message length
;mov    ecx, msg    ;message to write

;This does not work because I interchanged edx and ecx.
mov ecx, len    ;message length
mov edx, msg    ;message to write

mov ebx, 1      ;file descriptor (stdout)
mov eax, 4      ;system call number (sys_write)
int 0x80        ;call kernel
mov eax, 1      ;system call number (sys_exit)
int 0x80        ;call kernel

section .data

msg db  'Hello, Kaunda!',0xa    ;our dear string
len equ $ - msg         ;length of our dear string

我可以选择将变量“len”或“msg”放入任何数据寄存器(EAX,EBX,ECX和EDX)中吗?

换句话说:

为什么变量len的内容被传输到EDX寄存器而不是ECX或其他任何寄存器?是否有明确的指导方针来了解哪个变量应该放入哪个寄存器?

我已经阅读了每个寄存器EAX,EBX,ECX和EDX的功能,但我仍不清楚。它们的功能对我来说看起来相似。

更新:我正在运行来自https://www.tutorialspoint.com/compile_assembly_online.php的代码。

我认为这是Linux环境。


5
这与汇编语言本身关系较小,更多地涉及系统调用ABI。内核在特定的寄存器中查找系统调用的参数,因为这是它的工作方式。显然,它必须具有固定的参数和寄存器之间的对应关系,因为没有其他方法知道哪个是哪个。因此,您必须指示您所编写代码的操作系统,以便查找它使用的特定syscall ABI。 - Ken Thomases
1
“可互换”?嗯,在单个指令级别上 - 是的。但是看一下内核调用在被调用时期望在寄存器中拥有什么。 - DisappointedByUnaccountableMod
@barny,“single instruction level”是什么意思? - Kaunda
1
@Kaunda:他的意思是像imul eax,ecximul edx,ebx这样的指令都做同样的事情(对不同的寄存器),CPU并不关心您是否在EBX或EDX中保留循环计数器。因此,在函数内部,寄存器分配大多是自由选择的。但是x86绝对有每个寄存器的特殊用途。例如,变量计数移位只能使用cl中的计数,除非您具有BMI2 shrx / shlx。无论如何,选择一个寄存器而不是另一个寄存器的主要原因是调用约定原因-调用方和被调用方之间关于哪个参数将在哪里的协议。 - Peter Cordes
将一个值移入寄存器 - 你所改变的东西。但内核调用API是固定的,如果你没有按照预期的顺序将值放入预期的寄存器或堆栈中,则它将无法工作。 - DisappointedByUnaccountableMod
1
明白了!我感谢所有的评论。 - Kaunda
1个回答

13
当你发出int 0x80时,你的程序会被中断,内核会检查寄存器的状态。从eax中获取你想要执行的系统调用号码,从其他寄存器中获取附加数据。例如,对于write系统调用,它从ebx中获取文件描述符,从ecx中获取指向要写入的缓冲区的指针,从edx中获取要写入的字节数。内核不知道你的意图是什么,它只是愚蠢地获取寄存器中的任何内容,因此使用哪些寄存器是很重要的。
然而,一般来说,使用哪些寄存器来存储哪些值并不重要。在你自己的代码中,你可以自由地使用几乎所有的寄存器(除了像esp这样的寄存器)来存储任何你想要的目的,只要你不与其他人的代码互动。
唯一需要注意使用哪些寄存器的情况是当你想要与其他人编写的代码互动时,例如调用函数或操作系统,或者编写将被他人调用的函数。在这种情况下,你必须将相关寄存器设置为预期值,或者可能保留它们的内容。
例如,当您编写一个由其他人的代码调用的函数时,期望您将函数的结果返回到eax中,并保留寄存器ebxesiediespebp的内容。如果您为自己的目的使用这些寄存器,则必须先将它们的值保存在某个地方(例如堆栈)并在返回之前将它们恢复到原始值。
还有一些指令要求其操作数位于特定的寄存器中(例如stosidiv),但对于大多数指令,您可以自由选择任何寄存器。
在涉及到重要的情况下,规定了哪些寄存器用于什么目的的规则写在一个应用程序二进制接口(ABI)文档中。该文档可以被理解为所有程序员之间关于调用函数或操作系统时期望哪些数据在哪些寄存器中的协议。在调用/被其他人的代码调用时,严格遵守ABI是使您的代码正确工作的必要条件。
在您当前编写的i386架构上,Linux使用i386 SysV ABI。通常,每个操作系统针对每个架构使用不同的ABI,因此在为新操作系统或架构编写代码之前,请务必查看相关的ABI。

有趣!非常有教育意义。您能否进一步解释为什么当我将变量len的内容传输到ecx寄存器和变量msg的内容传输到edx寄存器时,上面的代码没有产生“Hello, World”的输出?它也没有产生任何错误消息。反过来是可以的(因此,当我将len放入edx中,将msg放入ecx中时,“Hello, World”会显示)我期望它能够工作,因为我可以控制哪个寄存器接收哪些数据。 - Kaunda
1
没有错误消息的原因是代码没有检查错误并打印消息。写系统调用确实返回了一个错误(很可能是EFAULT)。在int 0x80之后,它应该检查eax < 0。(注意,它返回EFAQT是因为ecx中的值不是有效指针。) - prl
2
@Kaunda 好的,当您将长度放入 ecx 中并将消息指针放入 edx 中时,内核仍然认为 ecx 包含消息指针,而 edx 包含消息的长度。将长度解释为指针会指向无用的位置,而将指针解释为数字通常是一个非常大的数字,因此内核返回错误 EFAULT,意思是“无效地址”,就像 @prl 已经解释过的那样。您的工作是将内核的错误代码转换为错误消息。内核本身很少关心这个问题(除非在极少数情况下)。 - fuz
1
总之,你可能控制哪些内容进入哪个寄存器,但内核无法知道你的意图。它只是假定你遵循ABI中规定的约定。如果你不遵循这些约定,就会发生奇怪的事情。 - fuz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接