Fix multiple personalities support in parser of io_submit syscall

Introduce a new helper function to fetch tracee's long integers
and use it to fix multiple personalities support in io_submit parser.

* defs.h (umove_long_or_printaddr): New prototype.
* util.c (umove_long_or_printaddr): New function.
* aio.c (sys_io_submit): Use it to fetch tracee's pointers.
* tests/aio.c: New file.
* tests/aio.test: New test.
* tests/Makefile.am (check_PROGRAMS): Add aio.
(TESTS): Add aio.test.
* tests/.gitignore: Add aio.
diff --git a/aio.c b/aio.c
index 83bb671..5822a4f 100644
--- a/aio.c
+++ b/aio.c
@@ -109,26 +109,30 @@
 	tprintf("%lu, %ld, [", tcp->u_arg[0], tcp->u_arg[1]);
 	{
 		long i;
-		struct iocb **iocbs = (void *)tcp->u_arg[2];
-//FIXME: decoding of 32-bit call by 64-bit strace
+		long iocbs = tcp->u_arg[2];
 
-		for (i = 0; i < nr; i++, iocbs++) {
+		for (i = 0; i < nr; ++i, iocbs += current_wordsize) {
 			enum iocb_sub sub;
-			struct iocb *iocbp;
+			long iocbp;
 			struct iocb iocb;
+
 			if (i)
 				tprints(", ");
 
-			if (umove_or_printaddr(tcp, (unsigned long)iocbs, &iocbp)) {
-				/* No point in trying to read iocbs+1 etc */
-				/* (nr can be ridiculously large): */
+			if (umove_long_or_printaddr(tcp, iocbs, &iocbp)) {
+				/*
+				 * No point in trying to read the whole array
+				 * because nr can be ridiculously large.
+				 */
 				break;
 			}
+
 			tprints("{");
-			if (umove_or_printaddr(tcp, (unsigned long)iocbp, &iocb)) {
+			if (umove_or_printaddr(tcp, iocbp, &iocb)) {
 				tprints("}");
 				continue;
 			}
+
 			if (iocb.data) {
 				tprints("data=");
 				printaddr((long) iocb.data);
diff --git a/defs.h b/defs.h
index 20587c7..7f9c3a3 100644
--- a/defs.h
+++ b/defs.h
@@ -476,6 +476,7 @@
 extern int umoven_or_printaddr(struct tcb *, long, unsigned int, void *);
 #define umove_or_printaddr(pid, addr, objp)	\
 	umoven_or_printaddr((pid), (addr), sizeof(*(objp)), (void *) (objp))
+extern int umove_long_or_printaddr(struct tcb *, long, long *);
 extern int umovestr(struct tcb *, long, unsigned int, char *);
 extern int upeek(int pid, long, long *);
 
diff --git a/tests/.gitignore b/tests/.gitignore
index b272ceb..6326d40 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -1,3 +1,4 @@
+aio
 bpf
 caps
 epoll_create1
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0876a46..9b1703c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -9,6 +9,7 @@
 	      -I$(top_srcdir)/$(OS)
 
 check_PROGRAMS = \
+	aio \
 	bpf \
 	caps \
 	epoll_create1 \
@@ -78,6 +79,7 @@
 	strace-f.test \
 	qual_syscall.test \
 	bexecve.test \
+	aio.test \
 	bpf.test \
 	caps.test \
 	dumpio.test \
diff --git a/tests/aio.c b/tests/aio.c
new file mode 100644
index 0000000..a59ae8e
--- /dev/null
+++ b/tests/aio.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2015 Dmitry V. Levin <ldv@altlinux.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/fcntl.h>
+#include <sys/syscall.h>
+
+#if defined __NR_io_setup \
+ && defined __NR_io_submit \
+ && defined __NR_io_getevents \
+ && defined __NR_io_destroy
+# include <linux/aio_abi.h>
+
+int
+main(void)
+{
+	static char data0[4096];
+	static char data1[8192];
+
+	struct iocb cb[2] = {
+		{
+			.aio_data = 0x11111111,
+			.aio_reqprio = 11,
+			.aio_buf = (unsigned long) data0,
+			.aio_offset = 0xdefacedfacefeed,
+			.aio_nbytes = sizeof(data0)
+		},
+		{
+			.aio_data = 0x22222222,
+			.aio_reqprio = 22,
+			.aio_buf = (unsigned long) data1,
+			.aio_offset = 0xdefacedcafef00d,
+			.aio_nbytes = sizeof(data1)
+		}
+	};
+
+	long cbs[4] = {
+		(long) &cb[0], (long) &cb[1],
+		0xdeadbeef, 0xbadc0ded
+	};
+
+	unsigned long ctx = 0;
+	const unsigned int nr = sizeof(cb) / sizeof(*cb);
+	const unsigned long lnr = (unsigned long) (0xdeadbeef00000000ULL | nr);
+
+	struct io_event ev[nr];
+	struct timespec ts = { .tv_nsec = 123456789 };
+
+	(void) close(0);
+	if (open("/dev/zero", O_RDONLY))
+		return 77;
+
+	if (syscall(__NR_io_setup, lnr, &ctx))
+		return 77;
+	printf("io_setup(%u, [%lu]) = 0\n", nr, ctx);
+
+	if (syscall(__NR_io_submit, ctx, nr, cbs) != (long) nr)
+		return 77;
+	printf("io_submit(%lu, %u, ["
+		"{data=%#llx, pread, reqprio=11, filedes=0, "
+			"buf=%p, nbytes=%u, offset=%lld}, "
+		"{data=%#llx, pread, reqprio=22, filedes=0, "
+			"buf=%p, nbytes=%u, offset=%lld}"
+		"]) = %u\n",
+	       ctx, nr,
+	       (unsigned long long) cb[0].aio_data, data0,
+	       (unsigned int) sizeof(data0), (long long) cb[0].aio_offset,
+	       (unsigned long long) cb[1].aio_data, data1,
+	       (unsigned int) sizeof(data1), (long long) cb[1].aio_offset,
+	       nr);
+
+	if (syscall(__NR_io_getevents, ctx, nr, nr, ev, &ts)  != (long) nr)
+		return 77;
+	printf("io_getevents(%lu, %u, %u, ["
+		"{data=%#llx, obj=%p, res=%u, res2=0}, "
+		"{data=%#llx, obj=%p, res=%u, res2=0}"
+		"], {0, 123456789}) = %u\n",
+	       ctx, nr, nr,
+	       (unsigned long long) cb[0].aio_data, &cb[0],
+	       (unsigned int) sizeof(data0),
+	       (unsigned long long) cb[1].aio_data, &cb[1],
+	       (unsigned int) sizeof(data1),
+	       nr);
+
+	if (syscall(__NR_io_destroy, ctx))
+		return 77;
+	printf("io_destroy(%lu) = 0\n", ctx);
+
+	puts("+++ exited with 0 +++");
+	return 0;
+}
+
+#else
+
+int
+main(void)
+{
+	return 77;
+}
+
+#endif
diff --git a/tests/aio.test b/tests/aio.test
new file mode 100755
index 0000000..f5907e4
--- /dev/null
+++ b/tests/aio.test
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# Check io_* syscalls decoding.
+
+. "${srcdir=.}/init.sh"
+
+run_prog > /dev/null
+OUT="$LOG.out"
+syscalls=io_setup,io_submit,io_getevents,io_destroy
+run_strace -a14 -e trace=$syscalls $args > "$OUT"
+match_diff "$LOG" "$OUT"
+rm -f "$OUT"
+
+exit 0
diff --git a/util.c b/util.c
index 94d9265..d40eafc 100644
--- a/util.c
+++ b/util.c
@@ -1132,6 +1132,19 @@
 	return 0;
 }
 
+int
+umove_long_or_printaddr(struct tcb *tcp, const long addr, long *ptr)
+{
+	if (current_wordsize < sizeof(*ptr)) {
+		uint32_t val32;
+		int r = umove_or_printaddr(tcp, addr, &val32);
+		if (!r)
+			*ptr = (unsigned long) val32;
+		return r;
+	}
+	return umove_or_printaddr(tcp, addr, ptr);
+}
+
 /*
  * Like `umove' but make the additional effort of looking
  * for a terminating zero byte.