Linux syscalls и errno

контекст: я пытаюсь написать небольшую программу C со встроенным asm, которая должна работать под Linux в системе x86_64 и компилироваться с gcc, чтобы лучше понять, как syscalls работают под Linux.

мой вопрос: как номера ошибок возвращаются из syscall (например, write) в этой среде? Я понимаю, что когда я использую библиотеку, такую как glibc, она заботится о сохранении результирующего кода ошибки в global errno переменной. Но где номер ошибки, сохраненный при вызове syscall непосредственно через встроенный ассемблер? Будет ли он храниться внутри отдельного регистра или будет закодирован в %rax?

давайте возьмем запись syscall на linux в качестве примера:

при вызове write затем после возвращения syscall я нахожу, что он хранит 0xfffffffffffffff2 внутри %rax, мне нужно как-то извлечь код ошибки из этого?

если у меня есть номер кода ошибки, где я должен искать, чтобы определить фактический ошибки, что происходило? Допустим, я получаю возвращенное число 5, с каким заголовочным файлом мне нужно проконсультироваться, чтобы найти соответствующее символическое имя ошибки.

Я вызываю запись syscall следующим образом:

asm ("mov ,%%rax;"
     "mov ,%%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 */
    );

С result, msg и len определен следующим образом:

long result = 0;
char* msg = "Hello Worldn";
long len = 12;

4 ответов


соглашение linux syscall заключается в том, что они кодируют как возможный код ошибки, так и возвращаемое значение для успешного вызова в возвращаемом значении. Это просто обертки glibc или других библиотек C, которые они установят errno к коду ошибки, возвращаемому подчеркиванием syscall, и оболочка вернет -1. Взять write в качестве примера ядро выполняет обработку ошибок, аналогичную этой:

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

так как вы можете видеть, код ошибки находится только в возвращаемом значении, и в зависимости от семантики всегда есть способ проверить, удалось ли syscall или нет, сравнивая его со значением, невозможным для успешной работы. Для большинства syscalls, как write один, это означает, что проверьте, если он отрицательный.


Соглашения О Вызове Архитектуры

как вы уже догадались, вы не можете использовать errno потому что это в glibc специфичных. Информация, которую вы хотите будет в rax если это x86_64. Мужская страница 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 x80             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] 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.

(на этой справочной странице приведена таблица, показывающая, как передавать аргументы системным вызовам следует. Интересное чтение.)


как номера ошибок возвращаются из syscall (например, write) в этой среде?:

вы должны проверить ваш rax Регистрация возвращаемого значения.


в Linux неудачный системный вызов с использованием инструкции сборки syscall вернет значение-errno в регистре rax. Поэтому в вашем случае 0-0xfffffffffffffff2 == 0xE, который равен 14. Итак, ваш errno-14.

Как вы находите, что означает errno 14? Вы должны искать google "таблица кода ошибки Linux" или искать в errno.h и вы найдете ответ.

посмотреть здесь: http://www.virtsync.com/c-error-codes-include-errno

согласно этому таблица 14-EFAULT, что означает "плохой адрес".


IIRC в x86-64 ABI передается ошибка из syscall с набором битов переноса. Тогда eax содержит errno код.

Я бы предложил изучить нижние слои исходного кода некоторой библиотеки libc, например musl-библиотеки libc