Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the second `r2` return value in Go's Syscall() for?

Here is Go's undocumented Syscall function:

func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)

And here is the C definition:

long syscall(long number, ...);

Pretty different. So it's fairly obvious that trap is number, and a1, a2, and a3 allow for three arguments. I also worked out that r1 is the return value, and err is errno. But what is r2? The syscall man page doesn't mention multiple return values.

It does give the actual calling conventions (still only one retval):

       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      -

But on x86 this is the implementation

    #define INVOKE_SYSCALL  INT $0x80

    TEXT    ·Syscall(SB),NOSPLIT,$0-28
        CALL    runtime·entersyscall(SB)
        MOVL    trap+0(FP), AX  // syscall entry
        MOVL    a1+4(FP), BX
        MOVL    a2+8(FP), CX
        MOVL    a3+12(FP), DX
        MOVL    $0, SI
        MOVL    $0,  DI
        INVOKE_SYSCALL
        CMPL    AX, $0xfffff001
        JLS ok
        MOVL    $-1, r1+16(FP)
        MOVL    $0, r2+20(FP)
        NEGL    AX
        MOVL    AX, err+24(FP)
        CALL    runtime·exitsyscall(SB)
        RET
    ok:
        MOVL    AX, r1+16(FP)
        MOVL    DX, r2+20(FP)
        MOVL    $0, err+24(FP)
        CALL    runtime·exitsyscall(SB)
        RET

Now, I don't read assembly too well, but I'm pretty sure it is returning EDX in r2. Why?

like image 652
Timmmm Avatar asked Aug 03 '16 08:08

Timmmm


1 Answers

I think they have multiple return values for consistency. As you can see from that table, some architectures return multiple values and if you check a few of the other assembly files from that directory you'll see they move register values to r2.


But why DX? This part is still puzzling. Scattered across the web are docs mentioning on i386 a function is allowed to use both EAX and EDX for return values. For example System V Application Binary Interface Intel386 Architecture Processor Supplement:

%edx scratch register; also used to return the upper 32bits of some 64bit return types

Later it goes on to say:

The most significant 32 bits are returned in %edx. The least unsigned long long significant 32 bits are returned in %eax.

Let's try this:

uint64_t some_function() {
  return 18446744073709551614LLU;
}

Clang ends up producing:

pushl   %ebp
movl    %esp, %ebp
movl    $-2, %eax
movl    $-1, %edx
popl    %ebp
ret

Interestingly, asm_linux_amd64.s seems to do the same thing, giving us a pretext to look at the System V ABI for AMD64. This also doc mentions in passing, about RDX:

used to pass 3rd argument to functions; 2nd return register

But Appendix A deals with Linux Conventions specifically.

The interface between the C library and the Linux kernel is the same as for the user-level applications with the following differences:

Returning from the syscall, register %rax contains the result of the system-call. A value in the range between -4095 and -1 indicates an error, it is -errno.

No mention of RDX for the system call.


I won't put my hand in the fire for this (or in general) but I suspect taking DX is not necessary for Linux which doesn't make use of such large return values that they spill out of AX.

like image 177
cnicutar Avatar answered Nov 15 '22 08:11

cnicutar