【问题标题】:Linux syscalls and errnoLinux 系统调用和 errno
【发布时间】:2016-09-07 02:53:47
【问题描述】:

上下文:我正在尝试使用内联 asm 编写一个小型 C 程序,该程序应在 x86_64 系统上的 Linux 下运行并使用 gcc 编译,以便更好地了解系统调用在 Linux 下的工作方式。

我的问题是:在这种环境中,系统调用(例如写入)如何返回错误号?我知道当我使用诸如 glibc 之类的库时,它会负责将生成的错误代码保存在全局 errno 变量中。但是当我通过内联汇编器直接调用系统调用时,错误号存储在哪里?它会被存储在一个单独的寄存器中,还是被编码为%rax

以linux上的write系统调用为例:

当调用write 然后在系统调用返回后我发现它在%rax 中存储了0xfffffffffffffff2,我需要 以某种方式从中提取错误代码?

如果我有错误代码编号,我应该在哪里查找实际发生的错误?假设我得到了返回的数字 5,我需要查阅哪个头文件才能找到相应的符号错误名称。

我这样调用 write 系统调用:

asm ("mov $1,%%rax;"
     "mov $1,%%rdi;"
     "mov %1,%%rsi;"
     "mov %2,%%rdx;"
     "syscall;"
     "mov %%rax,%0;"
     : "=r" (result)
     : "r" (msg), "r" (len)
     : "%rdx", "%rsi", "%rax", "%rdi" /* EDIT: this is needed or else the registers will be overwritten */
    );

resultmsglen 定义如下:

long result = 0;
char* msg = "Hello World\n";
long len = 12;

【问题讨论】:

  • asm调用syscall后,查看errno的内容,应该还在里面。
  • @PierreEmmanuelLallemant 否 errno 由 glibc 或正在使用的任何 C 标准库定义。如果我不使用这样的库,那么我需要手动从寄存器中获取错误号。尝试使用 errno 时出现以下编译错误:error: ‘errno’ undeclared (first use in this function)
  • 我对此有很早(20 年)的了解,所以这可能不再正确,但 Linux 过去(有时仍然如此)将任何负返回值视为错误,并且那是错误号。
  • 发布的错误是 EFAULT
  • 您说内核中使用的“errno”可能与C wrap函数包装的errno不同,但是(经过简单的转换)我能想到的唯一情况是close(),即在 Linux 中返回 EINTR 的失败案例,现在 POSIX 说应该是 EINPROGRESS: ewontfix.com/4

标签: c linux system-calls


【解决方案1】:

在 Linux 上,使用 syscall 汇编指令失败的系统调用将在 rax 寄存器中返回值 -errno。所以在你的情况下 0-0xfffffffffffffff2 == 0xE 是 14。所以你的 errno 是 14。

您如何找到 errno 14 的含义?你应该谷歌搜索“Linux错误代码表”或查看errno.h,你会找到答案。

看这里: http://www.virtsync.com/c-error-codes-include-errno

根据该表,14 是 EFAULT,意思是“错误地址”。

【讨论】:

  • 虽然链接最终会过时,但可以通过社区编辑来修复,非常适合备份您的答案。
【解决方案2】:

在 x86-64 ABI 中的 IIRC,从设置了进位位的系统调用传输错误。然后eax 包含errno 代码。

我建议研究一些libc库的源代码的底层,比如musl-libc

【讨论】:

    【解决方案3】:

    架构调用约定

    正如您已经猜到的,您不能使用errno,因为它是GLibC specific。如果是x86_64,您想要的信息将在rax 中。手册页man 2 syscall 有以下解释:

    Architecture calling conventions
    
           Every architecture has its own way of invoking and passing arguments
           to the kernel.  The details for various architectures are listed in
           the two tables below.
    
           The first table lists the instruction used to transition to kernel
           mode (which might not be the fastest or best way to transition to the
           kernel, so you might have to refer to vdso(7)), the register used to
           indicate the system call number, the register used to return the
           system call result, and the register used to signal an error.
    
           arch/ABI    instruction           syscall #  retval  error    Notes
           ────────────────────────────────────────────────────────────────────
           alpha       callsys               v0         a0      a3       [1]
           arc         trap0                 r8         r0      -
           arm/OABI    swi NR                -          a1      -        [2]
           arm/EABI    swi 0x0               r7         r0      -
           arm64       svc #0                x8         x0      -
           blackfin    excpt 0x0             P0         R0      -
           i386        int $0x80             eax        eax     -
           ia64        break 0x100000        r15        r8      r10      [1]
           m68k        trap #0               d0         d0      -
           microblaze  brki r14,8            r12        r3      -
           mips        syscall               v0         v0      a3       [1]
           nios2       trap                  r2         r2      r7
           parisc      ble 0x100(%sr2, %r0)  r20        r28     -
           powerpc     sc                    r0         r3      r0       [1]
           s390        svc 0                 r1         r2      -        [3]
           s390x       svc 0                 r1         r2      -        [3]
           superh      trap #0x17            r3         r0      -        [4]
           sparc/32    t 0x10                g1         o0      psr/csr  [1]
           sparc/64    t 0x6d                g1         o0      psr/csr  [1]
           tile        swint1                R10        R00     R01      [1]
           x86_64      syscall               rax        rax     -        [5]
           x32         syscall               rax        rax     -        [5]
           xtensa      syscall               a2         a2      -
    

    还有注号[5]

    [5] The x32 ABI uses the same instruction as the x86_64 ABI and
                   is used on the same processors.  To differentiate between
                   them, the bit mask __X32_SYSCALL_BIT is bitwise-ORed into the
                   system call number for system calls under the x32 ABI.  Both
                   system call tables are available though, so setting the bit
                   is not a hard requirement.
    

    (在那个手册页中,一个表格显示了如何将参数传递给系统调用。读起来很有趣。)


    在这种环境中,系统调用(例如写入)如何返回错误号?

    您必须检查您的rax 寄存器的返回值。

    【讨论】:

    • 谢谢,我也看到了这张表,但恕我直言,缺失的线索是错误号作为实际错误号的负数返回。
    【解决方案4】:

    linux 系统调用的约定是它们在返回值中编码可能的错误代码和成功调用的返回值。只是 glibc 或其他 C 库的包装器会将 errno 设置为下划线系统调用返回的错误代码,并且包装器将返回 -1。以write为例,内核做了类似这样的错误处理:

    ssize_t write(int fd, ...) {
        if (fd is not valid)
             return -EBADF;
        return do_write(...);
    }
    

    如您所见,错误代码只是在返回值中,并且根据语义,总有一种方法可以通过将系统调用与成功操作不可能的值进行比较来检查系统调用是否成功。对于大多数系统调用,例如 write 之一,这意味着检查它是否为负。

    【讨论】:

    猜你喜欢
    • 2011-08-06
    • 2012-10-07
    • 2012-06-30
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 2016-06-23
    • 2016-05-12
    • 2016-03-02
    相关资源
    最近更新 更多