该内核调用如何知道如何从计数器寄存器中获取

时间:2019-04-11 03:31:50

标签: assembly x86 system-calls api-design calling-convention

我正在尝试学习汇编,并且可以得到一些示例,但这是令人迷惑的。

内核如何知道抢占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寄存器上的数据/输出?

2 个答案:

答案 0 :(得分:2)

参数的位置是ABI的一部分。根据{{​​3}}:

  

通过如下设置通用寄存器来传递参数:

Syscall # | Param 1 | Param 2 | Param 3 | Param 4 | Param 5 | Param 6
eax       | ebx     | ecx     | edx     | esi     | edi     | ebp

Return value
eax

答案 1 :(得分:1)

  

...为什么一个内核调用会期望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开发人员也可能会决定采用其他方式。