[parisc-linux] INLINE_SYSCALL/gcc-bug? (Was Re: pipes)

Paul Bame bame@fc.hp.com
Wed, 07 Mar 2001 10:39:35 -0700


Help!  Probable gcc bug!

= This is looking like a glibc/kernel issue regardling the width
= of the type returned by lseek():

To reproduce the bogus return value, compile x.c:

    #include <stdio.h>
    #define __USE_FILE_OFFSET64
    #include <unistd.h>
    #include <errno.h>

    int main(int argc, char *argv[])
    {
	long lwhere = lseek(0, 0L, SEEK_CUR);

	printf("lwhere %lx errno %d (%s)\n", lwhere, errno, sys_errlist[errno]);

	return 0;
    }

then run 'ls | x' which should show 'lwhere' as either 0 or -1, but instead
it's 0xffffffec or something like that (-ESPIPE, as Richard found earlier).

After much head scratching, the bug seems to be occurring in glibc's
INLINE_SYSCALL macro (glibc/sysdeps/unix/sysv/linux/hppa/sysdep.h) as
a result of a compiler bug.  Since INLINE_SYSCALL is used to implement
quite a number of syscalls, this could be a nice thing to fix!  Here's
the "proof".  Consider the ustat() syscall [only because the generated
code is simple, not because it's known to have problems]
glibc/sysdeps/unix/sysv/linux/ustat.c:
    int
    ustat (dev_t dev, struct ustat *ubuf)
    {
      unsigned short int k_dev;

      /* We must convert the value to dev_t type used by the kernel.  */
      k_dev = ((major (dev) & 0xff) << 8) | (minor (dev) & 0xff);

      return INLINE_SYSCALL (ustat, 2, k_dev, CHECK_1 (ubuf));
    }
The way things are supposed to work, INLINE_SYSCALL() tests the value
returned from the kernel syscall, which is either in the range of
-4096..-1, in which case it's an error indicator so errno is set
and INLINE_SYSCALL returns -1 which ustat() then returns (-1 is
the standard syscall error return).  Or if outside this range,
the syscall return value is generally passed back unchanged.  This
logic is apparent from INLINE_SYSCALL:

    #define INLINE_SYSCALL(name, nr, args...)       ({              \
	    unsigned long __sys_res;                                \
	    {                                                       \
		    register unsigned long __res asm("r28");        \
		    LOAD_ARGS_##nr(args)                            \
		    asm volatile(                                   \
			    "ble  0x100(%%sr2, %%r0)\n\t"   \
			    " ldi %1, %%r20"                        \
			    : "=r" (__res)                          \
			    : "i" (SYS_ify(name)) ASM_ARGS_##nr     \
			     );                                     \
		    __sys_res = __res;                              \
	    }                                                       \
	    if (__sys_res >= (unsigned long)-4095) {                \
		    __set_errno(-__sys_res);                        \
		    __sys_res == (unsigned long)-1;                 \
	    }                                                       \
	    __sys_res;                                              \
    })

The symptom is that the return value from INLINE_SYSCALL is never -1
even when the if() condition is true.  It's not even -1 when printf-ed
right after being set to -1.  Here's the generated code for
ustat() plus some comments [xc-latest, latest glibc CVS bits]:

    00000000 <ustat>:
       0:   6b c2 3f d9     stw rp,-14(sr0,sp)
       4:   6f c3 00 80     stw,ma r3,40(sr0,sp)
       8:   08 19 02 56     copy r25,r22
       c:   08 1a 02 57     copy r26,r23
      10:   d2 f6 0a f5     shrpw r22,r23,8,r21
      14:   d6 75 09 18     depw,z r21,23,8,r19
      18:   d2 fa 1b f8     extrw,u r23,31,8,r26
      1c:   08 18 02 59     copy r24,r25
      20:   0b 53 02 5a     or r19,r26,r26
      24:   e4 00 82 00     be,l 100(sr2,r0),%sr0,%r31	# make the syscall
      28:   34 14 00 7c     ldi 3e,r20
      2c:   08 1c 02 43     copy ret0,r3		# save return value
# if (__sys_res >= (unsigned long)-4095)
      30:   34 13 20 01     ldi -1000,r19
      34:   88 73 80 28     cmpb,>>= r19,r3,50 <ustat+0x50>
# {
      38:   08 03 02 5c     copy r3,ret0
      3c:   e8 40 00 00     b,l 44 <ustat+0x44>,rp
			    3c: R_PARISC_PCREL17F   __errno_location
      40:   08 00 02 40     nop
      44:   08 60 04 13     sub r0,r3,r19	 # r19 = -__sys_res
      48:   0f 93 12 80     stw  r19,0(sr0,ret0) # __set_errno(r19)
      4c:   08 03 02 5c     copy r3,ret0	 # restore syscall ret value
# }
      50:   4b c2 3f 59     ldw -54(sr0,sp),rp
      54:   e8 40 c0 00     bv r0(rp)
      58:   4f c3 3f 81     ldw,mb -40(sr0,sp),r3

There appears to be no code generated for setting __sys_res to -1 so
I think this is probably a gcc bug.  I think I confirmed that the bug
exists with no optimization too.

Unfortunately we seem to be working around this bug in
in some of the hppa-specific syscalls, for example hppa/mmap.c
[which is using a poorer algorithm than the one in INLINE_SYSCALL()!]

INLINE_SYSCALL() also has a sign/unsigned problem as willy pointed out.

Attached is a list of glibc source files which contain INLINE_SYSCALL()
so if these syscalls seem broken to you, it could be due to this defect.

./aio_sigqueue.c
./execve.c
./ftruncate64.c
./fxstat.c
./fxstat64.c
./getcwd.c
./getdents.c
./getpriority.c
./llseek.c
./lxstat.c
./lxstat64.c
./msgctl.c
./msgget.c
./msgrcv.c
./msgsnd.c
./poll.c
./pread.c
./pread64.c
./ptrace.c
./pwrite.c
./pwrite64.c
./readv.c
./reboot.c
./semctl.c
./semget.c
./semop.c
./shmat.c
./shmctl.c
./shmdt.c
./shmget.c
./sigaction.c
./sigpending.c
./sigprocmask.c
./sigqueue.c
./sigsuspend.c
./sigtimedwait.c
./sigwaitinfo.c
./sysctl.c
./truncate64.c
./ustat.c
./writev.c
./xmknod.c
./xstat.c
./xstat64.c
./hppa/brk.c
./hppa/mmap.c
./hppa/sysdep.h