我正在努力学习装配,我可以举几个例子,但这很神秘。
内核如何知道将ecx
寄存器中的内容作为指向用户空间内存的指针来显示在stdout
上
mov edx,9 ;message length
mov ecx, name ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
如果edx是通用数据寄存器,并且eax是通用输入输出,为什么内核调用期望ecx寄存器上的数据/输出?
参数的位置是ABI的一部分。每https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#Making_a_syscall:
通过将通用寄存器设置如下来传递参数:
Syscall # | Param 1 | Param 2 | Param 3 | Param 4 | Param 5 | Param 6 eax | ebx | ecx | edx | esi | edi | ebp Return value eax
...为什么内核调用期望ecx寄存器上的数据/输出?
中断是子例程的一种特殊形式,其工作方式类似于使用call
指令调用的子例程。
当输入中断时,首先要做的是push
堆栈上的所有寄存器。这意味着所有寄存器都将存储在RAM存储器中(因为堆栈是RAM存储器)。
在Linux中,将使用汇编代码调用用C编程语言编写的函数。
在C编程语言中,如果知道数据是如何存储的,则可以使用struct
来访问存储在RAM中的数据。因为我们知道我们在汇编代码中编写了push
指令的顺序,我们可以定义一个struct
,它可以用来访问堆栈上的数据:
struct registers {
unsigned long ebx;
unsigned long ecx;
unsigned long edx;
...
unsigned long eax;
unsigned long eip;
...
}
在内核中的C编写函数中,我们现在可以访问此结构来读出寄存器值:
void systemCall_4(struct registers * regs)
{
kernelFile * f;
int (*pWrite)(kernelFile *,const void *,int);
/* Get the file from the file handle */
f = getFileFromHandle(regs->ebx);
/* No such file */
if(f == NULL)
{
regs->eax = ERROR_INVALID_HANDLE;
}
/* Call the device driver */
else
{
pWrite = f->writeFunction;
regs->eax = pWrite(f, (const void *)(regs->ecx), regs->edx);
}
}
内核程序员决定定义ecx
指向数据,edx
指的是长度。
在MS-DOS(例如)中,它是另一种方式:ecx
是长度,edx
指向数据。所以你看到Linux开发人员也可能决定采用不同的方式。