Skip to contentSkip to conversation

vDSO reference parser updates and Go workaround

PATCH Superseded Andy Lutomirski 1 participant 4 messages Jun 13, 2014

Subsystems (2)

v2 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

v2 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.

Changes from v1:
- Improve portability of the vdso parser.
- Add an explicit ELFCLASS check in the vdso parser (why not?).
- Fix x32 build.
- Add a comment describing what the hack works around, why, and where the Go bug was introduced.

Andy Lutomirski (3):
doc,vdso: Make vDSO examples more portable x86,vdso: Add PUT_LE to store little-endian values x86,vdso: Hack to keep 64-bit Go programs working

 Documentation/vDSO/parse_vdso.c               | 67 ++++++++++++++++-----------
 Documentation/vDSO/vdso_standalone_test_x86.c | 45 ++++++++++++------
 Documentation/vDSO/vdso_test.c                | 52 +++++++++++++++++++++
 arch/x86/vdso/Makefile                        | 18 +++----
 arch/x86/vdso/vdso-fakesections.c             | 32 +++++++++++++
 arch/x86/vdso/vdso2c.c                        | 19 ++++++--
 arch/x86/vdso/vdso2c.h                        | 23 +++++++--
 7 files changed, 199 insertions(+), 57 deletions(-)
 create mode 100644 Documentation/vDSO/vdso_test.c
 create mode 100644 arch/x86/vdso/vdso-fakesections.c
Signature · 1 line
1.9.3
#1 lore raw

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

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               | 67 ++++++++++++++++-----------
 Documentation/vDSO/vdso_standalone_test_x86.c | 45 ++++++++++++------
 Documentation/vDSO/vdso_test.c                | 52 +++++++++++++++++++++
 3 files changed, 123 insertions(+), 41 deletions(-)
 create mode 100644 Documentation/vDSO/vdso_test.c
Documentation/vDSO/parse_vdso.c +40 -27
index 8587020..1dbb4b8 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,13 +11,14 @@
  * 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.
  */
 
 #include <stdbool.h>
 #include <stdint.h>
 #include <string.h>
+#include <limits.h>
 #include <elf.h>
 
 /*
@@ -45,11 +46,18 @@ 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 ELF_BITS
+# if ULONG_MAX > 0xffffffffUL
+#  define ELF_BITS 64
+# else
+#  define ELF_BITS 32
+# endif
 #endif
 
+#define ELF_BITS_XFORM2(bits, x) Elf##bits##_##x
+#define ELF_BITS_XFORM(bits, x) ELF_BITS_XFORM2(bits, x)
+#define ELF(x) ELF_BITS_XFORM(ELF_BITS, x)
+
 static struct vdso_info
 {
 	bool valid;
@@ -59,14 +67,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 +100,14 @@ 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;
+	if (hdr->e_ident[EI_CLASS] !=
+	    (ELF_BITS == 32 ? ELFCLASS32 : ELFCLASS64)) {
+		return;  /* Wrong ELF class -- check ELF_BITS */
+	}
+
+	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 +121,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 +131,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 +144,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 +181,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 +201,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 +210,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 +226,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 +256,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 +52 -0

52 lines not on this page · raw message

Signature · 1 line
1.9.3
#2 lore raw

PATCH v2 2/3 x86,vdso: Add PUT_LE to store little-endian values

In reply to Andy Lutomirski
---
 arch/x86/vdso/vdso2c.c | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)
arch/x86/vdso/vdso2c.c +16 -3
index 450ac6e..7a6bf50 100644
@@ -54,7 +54,7 @@ static void fail(const char *format, ...)
 }
 
 /*
- * Evil macros to do a little-endian read.
+ * Evil macros for little-endian reads and writes
  */
 #define GLE(x, bits, ifnot)						\
 	__builtin_choose_expr(						\
@@ -62,11 +62,24 @@ static void fail(const char *format, ...)
 		(__typeof__(*(x)))get_unaligned_le##bits(x), ifnot)
 
 extern void bad_get_le(void);
-#define LAST_LE(x)							\
+#define LAST_GLE(x)							\
 	__builtin_choose_expr(sizeof(*(x)) == 1, *(x), bad_get_le())
 
 #define GET_LE(x)							\
-	GLE(x, 64, GLE(x, 32, GLE(x, 16, LAST_LE(x))))
+	GLE(x, 64, GLE(x, 32, GLE(x, 16, LAST_GLE(x))))
+
+#define PLE(x, val, bits, ifnot)					\
+	__builtin_choose_expr(						\
+		(sizeof(*(x)) == bits/8),				\
+		put_unaligned_le##bits((val), (x)), ifnot)
+
+extern void bad_put_le(void);
+#define LAST_PLE(x, val)						\
+	__builtin_choose_expr(sizeof(*(x)) == 1, *(x) = (val), bad_put_le())
+
+#define PUT_LE(x, val)					\
+	PLE(x, val, 64, PLE(x, val, 32, PLE(x, val, 16, LAST_PLE(x, val))))
+
 
 #define NSYMS (sizeof(required_syms) / sizeof(required_syms[0]))
 
Signature · 1 line
1.9.3
#3 lore raw

PATCH v2 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            | 18 ++++++++++--------
 arch/x86/vdso/vdso-fakesections.c | 32 ++++++++++++++++++++++++++++++++
 arch/x86/vdso/vdso2c.h            | 23 ++++++++++++++++++-----
 3 files changed, 60 insertions(+), 13 deletions(-)
 create mode 100644 arch/x86/vdso/vdso-fakesections.c
arch/x86/vdso/Makefile +10 -8
index 9769df0..ba6fc27 100644
@@ -15,12 +15,8 @@ vdso-install-$(VDSO32-y)	+= $(vdso32-images)
 
 
 # files to link into the vdso
-vobjs-y := vdso-note.o vclock_gettime.o vgetcpu.o
-
-vobjs-$(VDSOX32-y) += $(vobjx32s-compat)
-
-# Filter out x32 objects.
-vobj64s := $(filter-out $(vobjx32s-compat),$(vobjs-y))
+vobjs-y := vdso-note.o vclock_gettime.o vgetcpu.o vdso-fakesections.o
+vobjs-nox32 := vdso-fakesections.o
 
 # files to link into kernel
 obj-y				+= vma.o
@@ -34,7 +30,7 @@ vdso_img-$(VDSO32-y)		+= 32-sysenter
 
 obj-$(VDSO32-y)			+= vdso32-setup.o
 
-vobjs := $(foreach F,$(vobj64s),$(obj)/$F)
+vobjs := $(foreach F,$(vobjs-y),$(obj)/$F)
 
 $(obj)/vdso.o: $(obj)/vdso.so
 
@@ -104,7 +100,13 @@ VDSO_LDFLAGS_vdsox32.lds = -Wl,-m,elf32_x86_64 \
 			   -Wl,-z,max-page-size=4096 \
 			   -Wl,-z,common-page-size=4096
 
-vobjx32s-y := $(vobj64s:.o=-x32.o)
+# 64-bit objects to re-brand as x32
+vobjs64-for-x32 := $(filter-out $(vobjs-nox32),$(vobjs-y))
+
+# x32-rebranded versions
+vobjx32s-y := $(vobjs64-for-x32:.o=-x32.o)
+
+# same thing, but in the output directory
 vobjx32s := $(foreach F,$(vobjx32s-y),$(obj)/$F)
 
 # Convert 64bit object file to x32 for x32 vDSO.
arch/x86/vdso/vdso-fakesections.c new file +32 -0
new file mode 100644
index 0000000..cb8a8d7
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 Andy Lutomirski
+ * Subject to the GNU Public License, v.2
+ *
+ * Hack to keep broken Go programs working.
+ *
+ * The Go runtime had a couple of bugs: it would read the section table to try
+ * to figure out how many dynamic symbols there were (it shouldn't have looked
+ * at the section table at all) and, if there were no SHT_SYNDYM section table
+ * entry, it would use an uninitialized value for the number of symbols.  As a
+ * workaround, we supply a minimal section table.  vdso2c will adjust the
+ * in-memory image so that "vdso_fake_sections" becomes the section table.
+ *
+ * The bug was introduced by:
+ * https://code.google.com/p/go/source/detail?r=56ea40aac72b (2012-08-31)
+ * and is being addressed in the Go runtime in this issue:
+ * https://code.google.com/p/go/issues/detail?id=8197
+ */
+
+#ifndef __x86_64__
+#error This hack is specific to the 64-bit vDSO
+#endif
+
+#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),
+	}
+};
arch/x86/vdso/vdso2c.h +18 -5
index d9f6f61..c6eefaf 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,11 +122,14 @@ 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;
-	hdr->e_shstrndx = SHN_UNDEF; /* SHN_UNDEF == 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));
+	PUT_LE(&hdr->e_shoff, fake_sections_value);
+	PUT_LE(&hdr->e_shentsize, fake_sections_value ? sizeof(Elf_Shdr) : 0);
+	PUT_LE(&hdr->e_shnum, fake_sections_size / sizeof(Elf_Shdr));
+	PUT_LE(&hdr->e_shstrndx, SHN_UNDEF);
 
 	if (!name) {
 		fwrite(addr, load_size, 1, outfile);
Signature · 1 line
1.9.3