Incorrect preadv/pwritev argument parsing on ARM EABI

Dima Kogan lists at dima.secretsauce.net
Wed Apr 16 00:50:20 UTC 2014


Hi. I'm seeing arguments to preadv and pwritev parsed incorrectly on an
ARM EABI kernel. I have this test program (built with gcc -std=gnu99
-D_FILE_OFFSET_BITS=64)

 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <sys/uio.h>

 int main(void)
 {
     const off_t offset = 1234567890123456789LL;
     char buf[4];

     int fd_zero = open("/dev/zero", O_RDONLY);
     pread (fd_zero, buf, sizeof(buf), offset);
     preadv(fd_zero,
            &(struct iovec){ .iov_base = buf,
                    .iov_len = sizeof(buf)},
            1, offset );

     int fd_null = open("/dev/null", O_WRONLY);
     pwrite(fd_null, buf, sizeof(buf), offset);
     pwritev(fd_null,
             &(struct iovec){.iov_base = buf, .iov_len = sizeof(buf)},
             1, offset );

     return 0;
 }

The relevant parts of strace output are

 open("/dev/zero", O_RDONLY|O_LARGEFILE) = 3
 pread(3, "\0\0\0\0", 4, 1234567890123456789) = 4
 preadv(3, [{"\0\0\0\0", 4}], 1, 4582412532) = 4
 open("/dev/null", O_WRONLY|O_LARGEFILE) = 4
 pwrite(4, "\0\0\0\0", 4, 1234567890123456789) = 4
 pwritev(4, [{"\0\0\0\0", 4}], 1, 4582412532) = 4

Note that the 'offset' parameter in preadv/pwritev is reported as
4582412532. As you can see in the source, the offset is actually the
same for all the calls: 1234567890123456789.

I can fix it with the following patch (not proposing this patch for
merging; it's just to demonstrate the issue).


--- a/io.c
+++ b/io.c
@@ -231,7 +231,7 @@ sys_preadv(struct tcb *tcp)
 		}
 		tprint_iov(tcp, tcp->u_arg[2], tcp->u_arg[1], 1);
 		tprintf(", %lu, ", tcp->u_arg[2]);
-		printllval(tcp, "%llu", PREAD_OFFSET_ARG);
+		printllval(tcp, "%llu", PREAD_OFFSET_ARG + 1000);
 	}
 	return 0;
 }
@@ -244,7 +244,7 @@ sys_pwritev(struct tcb *tcp)
 		tprints(", ");
 		tprint_iov(tcp, tcp->u_arg[2], tcp->u_arg[1], 1);
 		tprintf(", %lu, ", tcp->u_arg[2]);
-		printllval(tcp, "%llu", PREAD_OFFSET_ARG);
+		printllval(tcp, "%llu", PREAD_OFFSET_ARG + 1000);
 	}
 	return 0;
 }
diff --git a/util.c b/util.c
index 85bb94c..49a6c2e 100644
--- a/util.c
+++ b/util.c
@@ -262,7 +262,7 @@ printllval(struct tcb *tcp, const char *format, int arg_no)
      defined POWERPC || \
      defined XTENSA
 	/* Align arg_no to the next even number. */
-	arg_no = (arg_no + 1) & 0xe;
+	arg_no = arg_no > 1000 ? (arg_no - 1000) : ((arg_no + 1) & 0xe);
 # endif
 	tprintf(format, LONG_LONG(tcp->u_arg[arg_no], tcp->u_arg[arg_no + 1]));
 	arg_no += 2;


This relates to how the kernel passes 64-bit arguments in 32-bit
architectures. The ARM EABI is supposed to pass these in a consecutive
pair of 32-bit registers, starting with an even one. This is what strace
does. For some reason that I'm not yet clear on, this appears to be
wrong for preadv and pwritev. The patch above short-circuits this
logic for preadv/pwritev, to take this argument in r3/r4 instead of
r4/r5.

Does anybody know what's going on? I can imagine anything from the
kernel being inconsistent with itself to glibc producing an incorrect
kernel interface.

dima




More information about the Strace-devel mailing list