csystem-callsstraceptrace

How to know if a syscall returned an error?


I try to reproduce strace behaviour in C, using ptrace.

I would like to check if a syscall returned an error (and what error1), but I don't know how to do this?

Here is an example of an output of strace I try to reproduce:

execve("./exit", ["./exit"], 0x7ffff059f250 /* 62 vars */) = 0
read(42, NULL, 0)                       = -1 EBADF (Bad file descriptor)
exit(0)                                 = ?
+++ exited with 0 +++

I tried to compare the return value of the syscall2 with -1, but they aren't equal.

I see in GLIBC syscall's implementation (sysdeps/unix/sysv/linux/syscall.c) that INTERNAL_SYSCALL_ERROR_P macro is used, which seems to be a macro for internal use of GLIBC. I found two implementations of this macro:

#define INTERNAL_SYSCALL_ERROR_P(val) ((unsigned long int)(val) > -4096UL)
#define INTERNAL_SYSCALL_ERROR_P(val) ((unsigned long long int)(val) >= -4095LL)

Since the first one is in a header directly related to the system (unlike the second one), I would tend to say that this is the one used by syscall. But I don't know if they are generics and so if I can use it.

1To recover the errno code, inverting the syscall return value (-rax) seems to work.

2With ptrace(PTRACE_GETREGS, ...), we can get the child registers, and store them in a struct user_regs_struct (defined in sys/user.h). Accessing the rax member of this structure, we have the return value of the syscall.


Solution

  • I think I found the solution.

    syscall doesn't seem to set the carry flag.
    Otherwise, like we can see on this syscall manual, if rax is negative (lower than 0), it indicates an error.
    Since rax in struct user_regs_struct is unsigned, we just have to cast it in signed, and we can know if syscall returned and error, and what error (-rax to get errno code).

    int main(int argc, const char *argv[])
    {
        struct user_regs_struct regs;
    
        // strace logic ...
        if ((long long)regs.rax < 0)
            printf(" = -1 %s (%s)\n", strerrorname_np((int)-regs.rax), strerror((int)-regs.rax));
        else
            printf(" = %llu\n", regs.rax);
        // ...
    }