Blob Blame History Raw
commit 9dbe121e3132630e9094d36c2b0624404b75beea
Author: Jan Stancek <jstancek@redhat.com>
Date:   Tue Sep 1 15:49:35 2015 +0200

    tests/linkhuge_rw: function ptr may not refer to .text
    
    On some ABIs function pointer may not refer to .text section.
    For example on powerPC 64-bit ABI, function pointer may refer
    to a call stub from .opd section.
    
    This creates a problem for linkhuge_rw tests which run with
    HUGETLB_ELFMAP=R, because test is expecting that address of
    function pointer will be backed by huge pages. But because
    .opd section is from RW PT_LOAD segment, this doens't happen,
    since libhugetlbfs is instructed to map only R segments via
    HUGETLB_ELFMAP=R.
    
    This patch is replacing use of function pointer with address
    returned by gcc's __builtin_return_address(), that is called
    by the function itself. This should provide an address that
    is from an actual code, residing in .text section.
    
    Signed-off-by: Jan Stancek <jstancek@redhat.com>
    Cc: Adam Litke <agl@us.ibm.com>
    Cc: Eric B Munson <emunson@mgebm.net>
    Cc: Petr Holasek <pholasek@redhat.com>
    Signed-off-by: Eric B Munson <emunson@mgebm.net>

diff --git a/tests/linkhuge_rw.c b/tests/linkhuge_rw.c
index f58fff2..c1c2e96 100644
--- a/tests/linkhuge_rw.c
+++ b/tests/linkhuge_rw.c
@@ -31,7 +31,8 @@
 #include "hugetests.h"
 
 #define BLOCK_SIZE	16384
-#define CONST	0xdeadbeef
+#define CONST		0xdeadbeef
+#define RETURN_ADDRESS	0x0
 
 #define BIG_INIT	{ \
 	[0] = CONST, [17] = CONST, [BLOCK_SIZE-1] = CONST, \
@@ -45,13 +46,49 @@ static int big_bss[BLOCK_SIZE];
 const int small_const = CONST;
 const int big_const[BLOCK_SIZE] = BIG_INIT;
 
-static int static_func(int x)
+/*
+ * Turn function pointer into address from .text.
+ *
+ * On some ABIs function pointer may not refer to .text section. For example
+ * on powerPC 64-bit ABI, function pointer may refer to call stub from
+ * .opd section.
+ *
+ * This function expects that parameter data is a function pointer of type:
+ * long f(long), and when called with special parameter, it returns an address
+ * corresponding to actual code of the function. Current implementation relies
+ * on gcc's __builtin_return_address, see get_pc() below.
+ */
+static inline void *get_text_addr(void *data)
+{
+	long (*gettext)(long) = data;
+
+	return (void *)gettext(RETURN_ADDRESS);
+}
+
+static void __attribute__ ((noinline)) *get_pc(void)
+{
+#if defined(__s390__) && __WORDSIZE == 32
+	/* taken from sysdeps/unix/sysv/linux/s390/s390-32/profil-counter.h
+	 * 31-bit s390 pointers don't use the 32th bit, however integers do,
+	 * so wrap the value around at 31 bits */
+	return (void *)
+		((unsigned long) __builtin_return_address(0) & 0x7fffffffUL);
+#else
+	return __builtin_return_address(0);
+#endif
+}
+
+static long static_func(long x)
 {
+	if (x == RETURN_ADDRESS)
+		return (long)get_pc();
 	return x;
 }
 
-int global_func(int x)
+long global_func(long x)
 {
+	if (x == RETURN_ADDRESS)
+		return (long)get_pc();
 	return x;
 }
 
@@ -59,27 +96,28 @@ static struct test_entry {
 	const char *name;
 	void *data;
 	int size;
-	int writable, execable;
+	int writable;
+	int execable;
 	int is_huge;
 } testtab[] = {
-#define ENT(name, exec)	{ #name, (void *)&name, sizeof(name), 0, exec, }
+#define ENT(entry_name, exec) { \
+	.name = #entry_name, \
+	.data = (void *)&entry_name, \
+	.size = sizeof(entry_name), \
+	.writable = 0, \
+	.execable = exec }
+
 	ENT(small_data, 0),
 	ENT(big_data, 0),
 	ENT(small_bss, 0),
 	ENT(big_bss, 0),
 	ENT(small_const, 0),
 	ENT(big_const, 0),
-
-	/*
-	 * XXX: Due to the way functions are defined in the powerPC 64-bit ABI,
-	 * the following entries will point to a call stub in the data segment
-	 * instead of to the code as one might think.  Therefore, test coverage
-	 * is not quite as good as it could be for ppc64.
-	 */
 	ENT(static_func, 1),
 	ENT(global_func, 1),
 };
 
+
 #define NUM_TESTS	(sizeof(testtab) / sizeof(testtab[0]))
 
 static
@@ -116,12 +154,18 @@ static void check_if_writable(struct test_entry *te)
 {
 	int pid, ret, status;
 
-
 	pid = fork();
 	if (pid < 0)
 		FAIL("fork: %s", strerror(errno));
 	else if (pid == 0) {
-		(*(char *) te->data) = 0;
+		void *data;
+
+		if (te->execable)
+			data = get_text_addr(te->data);
+		else
+			data = te->data;
+
+		(*(char *)data) = 0;
 		exit (0);
 	} else {
 		ret = waitpid(pid, &status, 0);
@@ -137,11 +181,15 @@ static void check_if_writable(struct test_entry *te)
 static void do_test(struct test_entry *te)
 {
 	int i;
-	volatile int *p = te->data;
+	void *data = te->data;
 
 	check_if_writable(te);
+	verbose_printf("entry: %s, data: %p, writable: %d\n",
+		te->name, data, te->writable);
 
 	if (te->writable) {
+		volatile int *p = data;
+
 		for (i = 0; i < (te->size / sizeof(*p)); i++)
 			p[i] = CONST ^ i;
 
@@ -151,17 +199,23 @@ static void do_test(struct test_entry *te)
 			if (p[i] != (CONST ^ i))
 				FAIL("mismatch on %s", te->name);
 	} else if (te->execable) {
-		int (*pf)(int) = te->data;
+		long (*pf)(long) = data;
+
+		data = get_text_addr(data);
 
 		if ((*pf)(CONST) != CONST)
 			FAIL("%s returns incorrect results", te->name);
 	} else {
 		/* Otherwise just read touch it */
+		volatile int *p = data;
+
 		for (i = 0; i < (te->size / sizeof(*p)); i++)
 			p[i];
 	}
 
-	te->is_huge = (test_addr_huge(te->data) == 1);
+	te->is_huge = (test_addr_huge(data) == 1);
+	verbose_printf("entry: %s, data: %p, is_huge: %d\n",
+		te->name, data, te->is_huge);
 }
 
 int main(int argc, char *argv[])