上下文:我正在尝试编写一个内联asm的小型C程序,该程序应该在x86_64系统的Linux下运行,并使用gcc进行编译,以便更好地了解系统调用在Linux下如何工作。
我的问题是:在这个环境中,如何从系统调用(例如写入)返回错误号? 我明白,当我使用像glibc这样的库时,它会将所产生的错误代码保存在全局errno
variables中。 但是,当我直接通过内联汇编器调用系统调用时,存储的错误号码在哪里? 它会被存储在一个单独的寄存器,或将被编码在%rax
?
让我们以Linux上的写系统调用为例:
当调用write
然后系统调用返回时,我发现它存储%rax
里面的0xfffffffffffffff2
,我需要以某种方式提取错误代码?
如果我有错误代码编号,我应该在哪里查找出现的实际错误? 比方说,我得到的5号返回,哪个头文件我需要咨询find相应的符号错误名称。
我正在调用这样的写系统调用:
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 */ );
result
, msg
和len
定义如下:
long result = 0; char* msg = "Hello World\n"; long len = 12;
linux系统调用的约定是它们在返回值中编码可能的错误代码和成功调用的返回值。 这只是glibc或其他C库的包装,他们将设置errno
由下划线系统调用返回的错误代码,包装将返回-1
。 以write
为例,内核做类似这样的错误处理:
ssize_t write(int fd, ...) { if (fd is not valid) return -EBADF; return do_write(...); }
正如你所看到的,错误代码只是在返回值中,并且根据语义,总是有一种方法来检查系统调用是否成功,通过将它与不可能成功操作的值进行比较。 对于大多数系统调用,如write
,这意味着检查是否是负面的。
正如你已经猜到的那样,你不能使用errno
因为它是GLibC特有的 。 如果是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
寄存器的返回值。
在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,意思是“坏地址”。
IIRC在x86-64 ABI中,系统调用发送一个进位位置1的错误。 然后eax
包含errno
代码。
我建议研究一些libc库的源代码的底层,比如musl-libc