Skip to contentSkip to conversation

vDSO reference parser updates and Go workaround

PATCH Superseded Andy Lutomirski 2 participants 7 messages Jun 12, 2014 – Jun 13, 2014

Subsystems (2)

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

cover vDSO reference parser updates and Go workaround

Patches 1 and 2 update the vDSO reference parser.

Despite the late submission, this might be 3.16 material: it's just documentation. If I'd been more on top of this, I would have done it for 3.15. It's also probably worthwhile given that there is at least one rather buggy vDSO parser in the wild.

Patch 3 makes "Hello world" as built by Go stop segfaulting. It's a performance regression for Go programs: it causes them to stop finding any symbols in the vDSO, but that's a lot better than segfaulting. It has big-endian issues, though, and it's ugly, and it should probably be disabled for x32.

Andy Lutomirski (3):
doc,vdso: Rename vdso_test.c to vdso_standalone_test_x86.c doc,vdso: Make vDSO examples more portable x86,vdso: Hack to keep 64-bit Go programs working

 Documentation/vDSO/parse_vdso.c               |  59 ++++++------
 Documentation/vDSO/vdso_standalone_test_x86.c | 128 ++++++++++++++++++++++++++
 Documentation/vDSO/vdso_test.c                | 103 +++++----------------
 arch/x86/vdso/Makefile                        |   2 +-
 arch/x86/vdso/vdso-fakesections.c             |  20 ++++
 arch/x86/vdso/vdso2c.h                        |  21 ++++-
 6 files changed, 219 insertions(+), 114 deletions(-)
 create mode 100644 Documentation/vDSO/vdso_standalone_test_x86.c
 create mode 100644 arch/x86/vdso/vdso-fakesections.c
Signature · 1 line
1.9.3
#1 lore raw

PATCH 1/3 doc,vdso: Rename vdso_test.c to vdso_standalone_test_x86.c

This thing is hopelessly x86_64-specific: it's an example of how to access the vDSO without any runtime support at all.

---
 Documentation/vDSO/vdso_standalone_test_x86.c | 111 ++++++++++++++++++++++++++
 Documentation/vDSO/vdso_test.c                | 111 --------------------------
 2 files changed, 111 insertions(+), 111 deletions(-)
 create mode 100644 Documentation/vDSO/vdso_standalone_test_x86.c
 delete mode 100644 Documentation/vDSO/vdso_test.c
Documentation/vDSO/vdso_standalone_test_x86.c new file +111 -0
new file mode 100644
index 0000000..1523f5d
@@ -0,0 +1,111 @@
+/*
+ * vdso_test.c: Sample code to test parse_vdso.c on x86_64
+ * Copyright (c) 2011 Andy Lutomirski
+ * Subject to the GNU General Public License, version 2
+ *
+ * You can amuse yourself by compiling with:
+ * gcc -std=gnu99 -nostdlib
+ *     -Os -fno-asynchronous-unwind-tables -flto
+ *      vdso_standalone_test_x86.c parse_vdso.c
+ * to generate a small binary with no dependencies at all.
+ */
+
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdint.h>
+
+extern void *vdso_sym(const char *version, const char *name);
+extern void vdso_init_from_sysinfo_ehdr(uintptr_t base);
+extern void vdso_init_from_auxv(void *auxv);
+
+/* We need a libc functions... */
+int strcmp(const char *a, const char *b)
+{
+	/* This implementation is buggy: it never returns -1. */
+	while (*a || *b) {
+		if (*a != *b)
+			return 1;
+		if (*a == 0 || *b == 0)
+			return 1;
+		a++;
+		b++;
+	}
+
+	return 0;
+}
+
+/* ...and two syscalls.  This is x86_64-specific. */
+static inline long linux_write(int fd, const void *data, size_t len)
+{
+
+	long ret;
+	asm volatile ("syscall" : "=a" (ret) : "a" (__NR_write),
+		      "D" (fd), "S" (data), "d" (len) :
+		      "cc", "memory", "rcx",
+		      "r8", "r9", "r10", "r11" );
+	return ret;
+}
+
+static inline void linux_exit(int code)
+{
+	asm volatile ("syscall" : : "a" (__NR_exit), "D" (code));
+}
+
+void to_base10(char *lastdig, uint64_t n)
+{
+	while (n) {
+		*lastdig = (n % 10) + '0';
+		n /= 10;
+		lastdig--;
+	}
+}
+
+__attribute__((externally_visible)) void c_main(void **stack)
+{
+	/* Parse the stack */
+	long argc = (long)*stack;
+	stack += argc + 2;
+
+	/* Now we're pointing at the environment.  Skip it. */
+	while(*stack)
+		stack++;
+	stack++;
+
+	/* Now we're pointing at auxv.  Initialize the vDSO parser. */
+	vdso_init_from_auxv((void *)stack);
+
+	/* Find gettimeofday. */
+	typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz);
+	gtod_t gtod = (gtod_t)vdso_sym("LINUX_2.6", "__vdso_gettimeofday");
+
+	if (!gtod)
+		linux_exit(1);
+
+	struct timeval tv;
+	long ret = gtod(&tv, 0);
+
+	if (ret == 0) {
+		char buf[] = "The time is                     .000000\n";
+		to_base10(buf + 31, tv.tv_sec);
+		to_base10(buf + 38, tv.tv_usec);
+		linux_write(1, buf, sizeof(buf) - 1);
+	} else {
+		linux_exit(ret);
+	}
+
+	linux_exit(0);
+}
+
+/*
+ * This is the real entry point.  It passes the initial stack into
+ * the C entry point.
+ */
+asm (
+	".text\n"
+	".global _start\n"
+        ".type _start,@function\n"
+        "_start:\n\t"
+        "mov %rsp,%rdi\n\t"
+        "jmp c_main"
+	);
Documentation/vDSO/vdso_test.c deleted +0 -111

111 lines not on this page · raw message

Signature · 1 line
1.9.3
#2 lore raw

PATCH 2/3 doc,vdso: Make vDSO examples more portable

In reply to Andy Lutomirski

This adds a new vdso_test.c that's written entirely in C. It also makes all of the vDSO examples work on 32-bit x86.

---
 Documentation/vDSO/parse_vdso.c               | 59 +++++++++++++++------------
 Documentation/vDSO/vdso_standalone_test_x86.c | 45 +++++++++++++-------
 Documentation/vDSO/vdso_test.c                | 50 +++++++++++++++++++++++
 3 files changed, 113 insertions(+), 41 deletions(-)
 create mode 100644 Documentation/vDSO/vdso_test.c
Documentation/vDSO/parse_vdso.c +32 -27
index 8587020..ca23f06 100644
@@ -1,6 +1,6 @@
 /*
  * parse_vdso.c: Linux reference vDSO parser
- * Written by Andrew Lutomirski, 2011.
+ * Written by Andrew Lutomirski, 2011-2014.
  *
  * This code is meant to be linked in to various programs that run on Linux.
  * As such, it is available with as few restrictions as possible.  This file
@@ -11,7 +11,7 @@
  * it starts a program.  It works equally well in statically and dynamically
  * linked binaries.
  *
- * This code is tested on x86_64.  In principle it should work on any 64-bit
+ * This code is tested on x86.  In principle it should work on any
  * architecture that has a vDSO.
  */
 
@@ -20,6 +20,8 @@
 #include <string.h>
 #include <elf.h>
 
+#include <asm/bitsperlong.h>
+
 /*
  * To use this vDSO parser, first call one of the vdso_init_* functions.
  * If you've already parsed auxv, then pass the value of AT_SYSINFO_EHDR
@@ -45,11 +47,14 @@ extern void *vdso_sym(const char *version, const char *name);
 
 
 /* And here's the code. */
-
-#ifndef __x86_64__
-# error Not yet ported to non-x86_64 architectures
+#ifndef __BITS_PER_LONG
+#error We need __BITS_PER_LONG
 #endif
 
+#define ELF_BITS2(bits, x) Elf##bits##_##x
+#define ELF_BITS(bits, x) ELF_BITS2(bits, x)
+#define ELF(x) ELF_BITS(__BITS_PER_LONG, x)
+
 static struct vdso_info
 {
 	bool valid;
@@ -59,14 +64,14 @@ static struct vdso_info
 	uintptr_t load_offset;  /* load_addr - recorded vaddr */
 
 	/* Symbol table */
-	Elf64_Sym *symtab;
+	ELF(Sym) *symtab;
 	const char *symstrings;
-	Elf64_Word *bucket, *chain;
-	Elf64_Word nbucket, nchain;
+	ELF(Word) *bucket, *chain;
+	ELF(Word) nbucket, nchain;
 
 	/* Version table */
-	Elf64_Versym *versym;
-	Elf64_Verdef *verdef;
+	ELF(Versym) *versym;
+	ELF(Verdef) *verdef;
 } vdso_info;
 
 /* Straight from the ELF specification. */
@@ -92,9 +97,9 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
 
 	vdso_info.load_addr = base;
 
-	Elf64_Ehdr *hdr = (Elf64_Ehdr*)base;
-	Elf64_Phdr *pt = (Elf64_Phdr*)(vdso_info.load_addr + hdr->e_phoff);
-	Elf64_Dyn *dyn = 0;
+	ELF(Ehdr) *hdr = (ELF(Ehdr)*)base;
+	ELF(Phdr) *pt = (ELF(Phdr)*)(vdso_info.load_addr + hdr->e_phoff);
+	ELF(Dyn) *dyn = 0;
 
 	/*
 	 * We need two things from the segment table: the load offset
@@ -108,7 +113,7 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
 				+ (uintptr_t)pt[i].p_offset
 				- (uintptr_t)pt[i].p_vaddr;
 		} else if (pt[i].p_type == PT_DYNAMIC) {
-			dyn = (Elf64_Dyn*)(base + pt[i].p_offset);
+			dyn = (ELF(Dyn)*)(base + pt[i].p_offset);
 		}
 	}
 
@@ -118,7 +123,7 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
 	/*
 	 * Fish out the useful bits of the dynamic table.
 	 */
-	Elf64_Word *hash = 0;
+	ELF(Word) *hash = 0;
 	vdso_info.symstrings = 0;
 	vdso_info.symtab = 0;
 	vdso_info.versym = 0;
@@ -131,22 +136,22 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
 				 + vdso_info.load_offset);
 			break;
 		case DT_SYMTAB:
-			vdso_info.symtab = (Elf64_Sym *)
+			vdso_info.symtab = (ELF(Sym) *)
 				((uintptr_t)dyn[i].d_un.d_ptr
 				 + vdso_info.load_offset);
 			break;
 		case DT_HASH:
-			hash = (Elf64_Word *)
+			hash = (ELF(Word) *)
 				((uintptr_t)dyn[i].d_un.d_ptr
 				 + vdso_info.load_offset);
 			break;
 		case DT_VERSYM:
-			vdso_info.versym = (Elf64_Versym *)
+			vdso_info.versym = (ELF(Versym) *)
 				((uintptr_t)dyn[i].d_un.d_ptr
 				 + vdso_info.load_offset);
 			break;
 		case DT_VERDEF:
-			vdso_info.verdef = (Elf64_Verdef *)
+			vdso_info.verdef = (ELF(Verdef) *)
 				((uintptr_t)dyn[i].d_un.d_ptr
 				 + vdso_info.load_offset);
 			break;
@@ -168,8 +173,8 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
 	vdso_info.valid = true;
 }
 
-static bool vdso_match_version(Elf64_Versym ver,
-			       const char *name, Elf64_Word hash)
+static bool vdso_match_version(ELF(Versym) ver,
+			       const char *name, ELF(Word) hash)
 {
 	/*
 	 * This is a helper function to check if the version indexed by
@@ -188,7 +193,7 @@ static bool vdso_match_version(Elf64_Versym ver,
 
 	/* First step: find the version definition */
 	ver &= 0x7fff;  /* Apparently bit 15 means "hidden" */
-	Elf64_Verdef *def = vdso_info.verdef;
+	ELF(Verdef) *def = vdso_info.verdef;
 	while(true) {
 		if ((def->vd_flags & VER_FLG_BASE) == 0
 		    && (def->vd_ndx & 0x7fff) == ver)
@@ -197,11 +202,11 @@ static bool vdso_match_version(Elf64_Versym ver,
 		if (def->vd_next == 0)
 			return false;  /* No definition. */
 
-		def = (Elf64_Verdef *)((char *)def + def->vd_next);
+		def = (ELF(Verdef) *)((char *)def + def->vd_next);
 	}
 
 	/* Now figure out whether it matches. */
-	Elf64_Verdaux *aux = (Elf64_Verdaux*)((char *)def + def->vd_aux);
+	ELF(Verdaux) *aux = (ELF(Verdaux)*)((char *)def + def->vd_aux);
 	return def->vd_hash == hash
 		&& !strcmp(name, vdso_info.symstrings + aux->vda_name);
 }
@@ -213,10 +218,10 @@ void *vdso_sym(const char *version, const char *name)
 		return 0;
 
 	ver_hash = elf_hash(version);
-	Elf64_Word chain = vdso_info.bucket[elf_hash(name) % vdso_info.nbucket];
+	ELF(Word) chain = vdso_info.bucket[elf_hash(name) % vdso_info.nbucket];
 
 	for (; chain != STN_UNDEF; chain = vdso_info.chain[chain]) {
-		Elf64_Sym *sym = &vdso_info.symtab[chain];
+		ELF(Sym) *sym = &vdso_info.symtab[chain];
 
 		/* Check for a defined global or weak function w/ right name. */
 		if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC)
@@ -243,7 +248,7 @@ void *vdso_sym(const char *version, const char *name)
 
 void vdso_init_from_auxv(void *auxv)
 {
-	Elf64_auxv_t *elf_auxv = auxv;
+	ELF(auxv_t) *elf_auxv = auxv;
 	for (int i = 0; elf_auxv[i].a_type != AT_NULL; i++)
 	{
 		if (elf_auxv[i].a_type == AT_SYSINFO_EHDR) {
Documentation/vDSO/vdso_standalone_test_x86.c +31 -14

73 lines not on this page · raw message

Documentation/vDSO/vdso_test.c new file +50 -0

50 lines not on this page · raw message

Signature · 1 line
1.9.3
#3 lore raw

Re: [PATCH 2/3] doc,vdso: Make vDSO examples more portable

Documentation/vDSO/parse_vdso.c line 24

Quoted 3 lines · Andy Lutomirski
Documentation/vDSO/parse_vdso.c +2 -0
+#include <asm/bitsperlong.h>
+

This isn't portable in any way. This is probably not the right way to do this.

The portable way to do this would be something like:

#include <limits.h>

#if ULONG_MAX > 0xffffffffUL

# define ELF_BITS 64
#else
# define ELF_BITS 32
#endif

There is also the option of looking for either __LP64__ or __ILP32__ which isn't 100% portable but works with all newer versions of gcc:

#ifndef ELF_BITS

# ifdef __LP64__
#  define ELF_BITS 64
# elif defined(__ILP32__)
#  define ELF_BITS 32
# else
#  error "Unknown size, please define ELF_BITS"
# endif
#endif

... or something like that.

-hpa

#4 lore raw

PATCH 3/3 x86,vdso: Hack to keep 64-bit Go programs working

In reply to Andy Lutomirski

The Go runtime has a buggy vDSO parser that currently segfaults. This writes an empty SHT_DYNSYM entry that causes Go's runtime to malfunction by thinking that the vDSO is empty rather than malfunctioning by running off the end and segfaulting.

This is currently broken for big-endian build hosts. The hack should also be disabled for x32, but I'm not sure what the right way to do that is.

---
 arch/x86/vdso/Makefile            |  2 +-
 arch/x86/vdso/vdso-fakesections.c | 20 ++++++++++++++++++++
 arch/x86/vdso/vdso2c.h            | 21 +++++++++++++++++----
 3 files changed, 38 insertions(+), 5 deletions(-)
 create mode 100644 arch/x86/vdso/vdso-fakesections.c
arch/x86/vdso/Makefile +1 -1
index 9769df0..ffdeb5b 100644
@@ -15,7 +15,7 @@ vdso-install-$(VDSO32-y)	+= $(vdso32-images)
 
 
 # files to link into the vdso
-vobjs-y := vdso-note.o vclock_gettime.o vgetcpu.o
+vobjs-y := vdso-note.o vclock_gettime.o vgetcpu.o vdso-fakesections.o
 
 vobjs-$(VDSOX32-y) += $(vobjx32s-compat)
 
arch/x86/vdso/vdso-fakesections.c new file +20 -0
new file mode 100644
index 0000000..ef75ece
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 Andy Lutomirski
+ * Subject to the GNU Public License, v.2
+ *
+ * Hack to keep broken Go programs working.
+ */
+
+#if __x86_64__  /* hack only needed for the 64-bit vDSO */
+
+#include <linux/elf.h>
+
+extern const __visible struct elf64_shdr vdso_fake_sections[];
+const __visible struct elf64_shdr vdso_fake_sections[] = {
+	{
+		.sh_type = SHT_DYNSYM,
+		.sh_entsize = sizeof(Elf64_Sym),
+	}
+};
+
+#endif
arch/x86/vdso/vdso2c.h +17 -4
index d9f6f61..43d9ab1 100644
@@ -18,6 +18,8 @@ static void GOFUNC(void *addr, size_t len, FILE *outfile, const char *name)
 	const char *secstrings;
 	uint64_t syms[NSYMS] = {};
 
+	uint64_t fake_sections_value = 0, fake_sections_size = 0;
+
 	Elf_Phdr *pt = (Elf_Phdr *)(addr + GET_LE(&hdr->e_phoff));
 
 	/* Walk the segment table. */
@@ -84,6 +86,7 @@ static void GOFUNC(void *addr, size_t len, FILE *outfile, const char *name)
 			GET_LE(&symtab_hdr->sh_entsize) * i;
 		const char *name = addr + GET_LE(&strtab_hdr->sh_offset) +
 			GET_LE(&sym->st_name);
+
 		for (k = 0; k < NSYMS; k++) {
 			if (!strcmp(name, required_syms[k])) {
 				if (syms[k]) {
@@ -93,6 +96,13 @@ static void GOFUNC(void *addr, size_t len, FILE *outfile, const char *name)
 				syms[k] = GET_LE(&sym->st_value);
 			}
 		}
+
+		if (!strcmp(name, "vdso_fake_sections")) {
+			if (fake_sections_value)
+				fail("duplicate vdso_fake_sections\n");
+			fake_sections_value = GET_LE(&sym->st_value);
+			fake_sections_size = GET_LE(&sym->st_size);
+		}
 	}
 
 	/* Validate mapping addresses. */
@@ -112,10 +122,13 @@ static void GOFUNC(void *addr, size_t len, FILE *outfile, const char *name)
 	if (syms[sym_end_mapping] % 4096)
 		fail("end_mapping must be a multiple of 4096\n");
 
-	/* Remove sections. */
-	hdr->e_shoff = 0;
-	hdr->e_shentsize = 0;
-	hdr->e_shnum = 0;
+	/* Remove sections or use fakes */
+	if (fake_sections_size % sizeof(Elf_Shdr))
+		fail("vdso_fake_sections size is not a multiple of %ld\n",
+		     (long)sizeof(Elf_Shdr));
+	hdr->e_shoff = fake_sections_value;
+	hdr->e_shentsize = fake_sections_value ? sizeof(Elf_Shdr) : 0;
+	hdr->e_shnum = fake_sections_size / sizeof(Elf_Shdr);
 	hdr->e_shstrndx = SHN_UNDEF; /* SHN_UNDEF == 0 */
 
 	if (!name) {
Signature · 1 line
1.9.3
#5 lore raw

Re: [PATCH 3/3] x86,vdso: Hack to keep 64-bit Go programs working

Quoted 5 lines · Andy Lutomirski

This is currently broken for big-endian build hosts. The hack should also be disabled for x32, but I'm not sure what the right way to do that is.

Well, if it isn't exported into the x32 vdso it isn't an issue, right?

-hpa

#6 lore raw

Re: [PATCH 3/3] x86,vdso: Hack to keep 64-bit Go programs working

Quoted 8 lines · H. Peter Anvin
Quoted 5 lines · Andy Lutomirski

This is currently broken for big-endian build hosts. The hack should also be disabled for x32, but I'm not sure what the right way to do that is.

Well, if it isn't exported into the x32 vdso it isn't an issue, right?

It was, and it broke the build. v3 fixes it, at the cost of yet more makefile churn.

Quoted 3 lines

-hpa

Signature · 2 lines
Andy Lutomirski
AMA Capital Management, LLC