mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-04-03 12:05:13 +02:00
[ Upstream commit0161bd38c2] On powerpc64 as shown below by readelf, vDSO functions symbols have type NOTYPE. $ powerpc64-linux-gnu-readelf -a arch/powerpc/kernel/vdso/vdso64.so.dbg ELF Header: Magic: 7f 45 4c 46 02 02 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, big endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: PowerPC64 Version: 0x1 ... Symbol table '.dynsym' contains 12 entries: Num: Value Size Type Bind Vis Ndx Name ... 1: 0000000000000524 84 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 ... 4: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS LINUX_2.6.15 5: 00000000000006c0 48 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 Symbol table '.symtab' contains 56 entries: Num: Value Size Type Bind Vis Ndx Name ... 45: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS LINUX_2.6.15 46: 00000000000006c0 48 NOTYPE GLOBAL DEFAULT 8 __kernel_getcpu 47: 0000000000000524 84 NOTYPE GLOBAL DEFAULT 8 __kernel_clock_getres To overcome that, commitba83b3239e("selftests: vDSO: fix vDSO symbols lookup for powerpc64") was applied to have selftests also look for NOTYPE symbols, but the correct fix should be to flag VDSO entry points as functions. The original commit that brought VDSO support into powerpc/64 has the following explanation: Note that the symbols exposed by the vDSO aren't "normal" function symbols, apps can't be expected to link against them directly, the vDSO's are both seen as if they were linked at 0 and the symbols just contain offsets to the various functions. This is done on purpose to avoid a relocation step (ppc64 functions normally have descriptors with abs addresses in them). When glibc uses those functions, it's expected to use it's own trampolines that know how to reach them. The descriptors it's talking about are the OPD function descriptors used on ABI v1 (big endian). But it would be more correct for a text symbol to have type function, even if there's no function descriptor for it. glibc has a special case already for handling the VDSO symbols which creates a fake opd pointing at the kernel symbol. So changing the VDSO symbol type to function shouldn't affect that. For ABI v2, there is no function descriptors and VDSO functions can safely have function type. So lets flag VDSO entry points as functions and revert the selftest change. Link:5f2dd691b6Fixes:ba83b3239e("selftests: vDSO: fix vDSO symbols lookup for powerpc64") Signed-off-by: Christophe Leroy <christophe.leroy@csgroup.eu> Reviewed-By: Segher Boessenkool <segher@kernel.crashing.org> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Link: https://patch.msgid.link/b6ad2f1ee9887af3ca5ecade2a56f4acda517a85.1728512263.git.christophe.leroy@csgroup.eu Signed-off-by: Sasha Levin <sashal@kernel.org>
260 lines
6.3 KiB
C
260 lines
6.3 KiB
C
/*
|
|
* parse_vdso.c: Linux reference vDSO parser
|
|
* 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
|
|
* is licensed under the Creative Commons Zero License, version 1.0,
|
|
* available at http://creativecommons.org/publicdomain/zero/1.0/legalcode
|
|
*
|
|
* The vDSO is a regular ELF DSO that the kernel maps into user space when
|
|
* it starts a program. It works equally well in statically and dynamically
|
|
* linked binaries.
|
|
*
|
|
* 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>
|
|
|
|
#include "parse_vdso.h"
|
|
|
|
/* And here's the code. */
|
|
#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)
|
|
|
|
#ifdef __s390x__
|
|
#define ELF_HASH_ENTRY ELF(Xword)
|
|
#else
|
|
#define ELF_HASH_ENTRY ELF(Word)
|
|
#endif
|
|
|
|
static struct vdso_info
|
|
{
|
|
bool valid;
|
|
|
|
/* Load information */
|
|
uintptr_t load_addr;
|
|
uintptr_t load_offset; /* load_addr - recorded vaddr */
|
|
|
|
/* Symbol table */
|
|
ELF(Sym) *symtab;
|
|
const char *symstrings;
|
|
ELF_HASH_ENTRY *bucket, *chain;
|
|
ELF_HASH_ENTRY nbucket, nchain;
|
|
|
|
/* Version table */
|
|
ELF(Versym) *versym;
|
|
ELF(Verdef) *verdef;
|
|
} vdso_info;
|
|
|
|
/*
|
|
* Straight from the ELF specification...and then tweaked slightly, in order to
|
|
* avoid a few clang warnings.
|
|
*/
|
|
static unsigned long elf_hash(const char *name)
|
|
{
|
|
unsigned long h = 0, g;
|
|
const unsigned char *uch_name = (const unsigned char *)name;
|
|
|
|
while (*uch_name)
|
|
{
|
|
h = (h << 4) + *uch_name++;
|
|
g = h & 0xf0000000;
|
|
if (g)
|
|
h ^= g >> 24;
|
|
h &= ~g;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
void vdso_init_from_sysinfo_ehdr(uintptr_t base)
|
|
{
|
|
size_t i;
|
|
bool found_vaddr = false;
|
|
|
|
vdso_info.valid = false;
|
|
|
|
vdso_info.load_addr = base;
|
|
|
|
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
|
|
* and the dynamic table.
|
|
*/
|
|
for (i = 0; i < hdr->e_phnum; i++)
|
|
{
|
|
if (pt[i].p_type == PT_LOAD && !found_vaddr) {
|
|
found_vaddr = true;
|
|
vdso_info.load_offset = base
|
|
+ (uintptr_t)pt[i].p_offset
|
|
- (uintptr_t)pt[i].p_vaddr;
|
|
} else if (pt[i].p_type == PT_DYNAMIC) {
|
|
dyn = (ELF(Dyn)*)(base + pt[i].p_offset);
|
|
}
|
|
}
|
|
|
|
if (!found_vaddr || !dyn)
|
|
return; /* Failed */
|
|
|
|
/*
|
|
* Fish out the useful bits of the dynamic table.
|
|
*/
|
|
ELF_HASH_ENTRY *hash = 0;
|
|
vdso_info.symstrings = 0;
|
|
vdso_info.symtab = 0;
|
|
vdso_info.versym = 0;
|
|
vdso_info.verdef = 0;
|
|
for (i = 0; dyn[i].d_tag != DT_NULL; i++) {
|
|
switch (dyn[i].d_tag) {
|
|
case DT_STRTAB:
|
|
vdso_info.symstrings = (const char *)
|
|
((uintptr_t)dyn[i].d_un.d_ptr
|
|
+ vdso_info.load_offset);
|
|
break;
|
|
case DT_SYMTAB:
|
|
vdso_info.symtab = (ELF(Sym) *)
|
|
((uintptr_t)dyn[i].d_un.d_ptr
|
|
+ vdso_info.load_offset);
|
|
break;
|
|
case DT_HASH:
|
|
hash = (ELF_HASH_ENTRY *)
|
|
((uintptr_t)dyn[i].d_un.d_ptr
|
|
+ vdso_info.load_offset);
|
|
break;
|
|
case DT_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 = (ELF(Verdef) *)
|
|
((uintptr_t)dyn[i].d_un.d_ptr
|
|
+ vdso_info.load_offset);
|
|
break;
|
|
}
|
|
}
|
|
if (!vdso_info.symstrings || !vdso_info.symtab || !hash)
|
|
return; /* Failed */
|
|
|
|
if (!vdso_info.verdef)
|
|
vdso_info.versym = 0;
|
|
|
|
/* Parse the hash table header. */
|
|
vdso_info.nbucket = hash[0];
|
|
vdso_info.nchain = hash[1];
|
|
vdso_info.bucket = &hash[2];
|
|
vdso_info.chain = &hash[vdso_info.nbucket + 2];
|
|
|
|
/* That's all we need. */
|
|
vdso_info.valid = true;
|
|
}
|
|
|
|
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
|
|
* ver matches name (which hashes to hash).
|
|
*
|
|
* The version definition table is a mess, and I don't know how
|
|
* to do this in better than linear time without allocating memory
|
|
* to build an index. I also don't know why the table has
|
|
* variable size entries in the first place.
|
|
*
|
|
* For added fun, I can't find a comprehensible specification of how
|
|
* to parse all the weird flags in the table.
|
|
*
|
|
* So I just parse the whole table every time.
|
|
*/
|
|
|
|
/* First step: find the version definition */
|
|
ver &= 0x7fff; /* Apparently bit 15 means "hidden" */
|
|
ELF(Verdef) *def = vdso_info.verdef;
|
|
while(true) {
|
|
if ((def->vd_flags & VER_FLG_BASE) == 0
|
|
&& (def->vd_ndx & 0x7fff) == ver)
|
|
break;
|
|
|
|
if (def->vd_next == 0)
|
|
return false; /* No definition. */
|
|
|
|
def = (ELF(Verdef) *)((char *)def + def->vd_next);
|
|
}
|
|
|
|
/* Now figure out whether it matches. */
|
|
ELF(Verdaux) *aux = (ELF(Verdaux)*)((char *)def + def->vd_aux);
|
|
return def->vd_hash == hash
|
|
&& !strcmp(name, vdso_info.symstrings + aux->vda_name);
|
|
}
|
|
|
|
void *vdso_sym(const char *version, const char *name)
|
|
{
|
|
unsigned long ver_hash;
|
|
if (!vdso_info.valid)
|
|
return 0;
|
|
|
|
ver_hash = elf_hash(version);
|
|
ELF(Word) chain = vdso_info.bucket[elf_hash(name) % vdso_info.nbucket];
|
|
|
|
for (; chain != STN_UNDEF; chain = vdso_info.chain[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)
|
|
continue;
|
|
if (ELF64_ST_BIND(sym->st_info) != STB_GLOBAL &&
|
|
ELF64_ST_BIND(sym->st_info) != STB_WEAK)
|
|
continue;
|
|
if (sym->st_shndx == SHN_UNDEF)
|
|
continue;
|
|
if (strcmp(name, vdso_info.symstrings + sym->st_name))
|
|
continue;
|
|
|
|
/* Check symbol version. */
|
|
if (vdso_info.versym
|
|
&& !vdso_match_version(vdso_info.versym[chain],
|
|
version, ver_hash))
|
|
continue;
|
|
|
|
return (void *)(vdso_info.load_offset + sym->st_value);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void vdso_init_from_auxv(void *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) {
|
|
vdso_init_from_sysinfo_ehdr(elf_auxv[i].a_un.a_val);
|
|
return;
|
|
}
|
|
}
|
|
|
|
vdso_info.valid = false;
|
|
}
|