vDSO reference parser updates and Go workaround
Subsystems (2)
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
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
@@ -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; + asmvolatile ("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) +{ + asmvolatile ("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
PATCH 2/3 doc,vdso: Make vDSO examples more portable
In reply to Andy LutomirskiThis 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
@@ -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_infouintptr_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
H. Peter Anvin
reviewerRe: [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
PATCH 3/3 x86,vdso: Hack to keep 64-bit Go programs working
In reply to Andy LutomirskiThe 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
@@ -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
@@ -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
@@ -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
H. Peter Anvin
reviewerRe: [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
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