2 * parse_vdso.c: Linux reference vDSO parser
3 * Written by Andrew Lutomirski, 2011.
5 * This code is meant to be linked in to various programs that run on Linux.
6 * As such, it is available with as few restrictions as possible. This file
7 * is licensed under the Creative Commons Zero License, version 1.0,
8 * available at http://creativecommons.org/publicdomain/zero/1.0/legalcode
10 * The vDSO is a regular ELF DSO that the kernel maps into user space when
11 * it starts a program. It works equally well in statically and dynamically
14 * This code is tested on x86_64. In principle it should work on any 64-bit
15 * architecture that has a vDSO.
24 * To use this vDSO parser, first call one of the vdso_init_* functions.
25 * If you've already parsed auxv, then pass the value of AT_SYSINFO_EHDR
26 * to vdso_init_from_sysinfo_ehdr. Otherwise pass auxv to vdso_init_from_auxv.
27 * Then call vdso_sym for each symbol you want. For example, to look up
28 * gettimeofday on x86_64, use:
30 * <some pointer> = vdso_sym("LINUX_2.6", "gettimeofday");
32 * <some pointer> = vdso_sym("LINUX_2.6", "__vdso_gettimeofday");
34 * vdso_sym will return 0 if the symbol doesn't exist or if the init function
35 * failed or was not called. vdso_sym is a little slow, so its return value
38 * vdso_sym is threadsafe; the init functions are not.
40 * These are the prototypes:
42 extern void vdso_init_from_auxv(void *auxv
);
43 extern void vdso_init_from_sysinfo_ehdr(uintptr_t base
);
44 extern void *vdso_sym(const char *version
, const char *name
);
47 /* And here's the code. */
50 # error Not yet ported to non-x86_64 architectures
53 static struct vdso_info
57 /* Load information */
59 uintptr_t load_offset
; /* load_addr - recorded vaddr */
63 const char *symstrings
;
64 Elf64_Word
*bucket
, *chain
;
65 Elf64_Word nbucket
, nchain
;
72 /* Straight from the ELF specification. */
73 static unsigned long elf_hash(const unsigned char *name
)
75 unsigned long h
= 0, g
;
78 h
= (h
<< 4) + *name
++;
79 if (g
= h
& 0xf0000000)
86 void vdso_init_from_sysinfo_ehdr(uintptr_t base
)
89 bool found_vaddr
= false;
91 vdso_info
.valid
= false;
93 vdso_info
.load_addr
= base
;
95 Elf64_Ehdr
*hdr
= (Elf64_Ehdr
*)base
;
96 Elf64_Phdr
*pt
= (Elf64_Phdr
*)(vdso_info
.load_addr
+ hdr
->e_phoff
);
100 * We need two things from the segment table: the load offset
101 * and the dynamic table.
103 for (i
= 0; i
< hdr
->e_phnum
; i
++)
105 if (pt
[i
].p_type
== PT_LOAD
&& !found_vaddr
) {
107 vdso_info
.load_offset
= base
108 + (uintptr_t)pt
[i
].p_offset
109 - (uintptr_t)pt
[i
].p_vaddr
;
110 } else if (pt
[i
].p_type
== PT_DYNAMIC
) {
111 dyn
= (Elf64_Dyn
*)(base
+ pt
[i
].p_offset
);
115 if (!found_vaddr
|| !dyn
)
119 * Fish out the useful bits of the dynamic table.
121 Elf64_Word
*hash
= 0;
122 vdso_info
.symstrings
= 0;
123 vdso_info
.symtab
= 0;
124 vdso_info
.versym
= 0;
125 vdso_info
.verdef
= 0;
126 for (i
= 0; dyn
[i
].d_tag
!= DT_NULL
; i
++) {
127 switch (dyn
[i
].d_tag
) {
129 vdso_info
.symstrings
= (const char *)
130 ((uintptr_t)dyn
[i
].d_un
.d_ptr
131 + vdso_info
.load_offset
);
134 vdso_info
.symtab
= (Elf64_Sym
*)
135 ((uintptr_t)dyn
[i
].d_un
.d_ptr
136 + vdso_info
.load_offset
);
139 hash
= (Elf64_Word
*)
140 ((uintptr_t)dyn
[i
].d_un
.d_ptr
141 + vdso_info
.load_offset
);
144 vdso_info
.versym
= (Elf64_Versym
*)
145 ((uintptr_t)dyn
[i
].d_un
.d_ptr
146 + vdso_info
.load_offset
);
149 vdso_info
.verdef
= (Elf64_Verdef
*)
150 ((uintptr_t)dyn
[i
].d_un
.d_ptr
151 + vdso_info
.load_offset
);
155 if (!vdso_info
.symstrings
|| !vdso_info
.symtab
|| !hash
)
158 if (!vdso_info
.verdef
)
159 vdso_info
.versym
= 0;
161 /* Parse the hash table header. */
162 vdso_info
.nbucket
= hash
[0];
163 vdso_info
.nchain
= hash
[1];
164 vdso_info
.bucket
= &hash
[2];
165 vdso_info
.chain
= &hash
[vdso_info
.nbucket
+ 2];
167 /* That's all we need. */
168 vdso_info
.valid
= true;
171 static bool vdso_match_version(Elf64_Versym ver
,
172 const char *name
, Elf64_Word hash
)
175 * This is a helper function to check if the version indexed by
176 * ver matches name (which hashes to hash).
178 * The version definition table is a mess, and I don't know how
179 * to do this in better than linear time without allocating memory
180 * to build an index. I also don't know why the table has
181 * variable size entries in the first place.
183 * For added fun, I can't find a comprehensible specification of how
184 * to parse all the weird flags in the table.
186 * So I just parse the whole table every time.
189 /* First step: find the version definition */
190 ver
&= 0x7fff; /* Apparently bit 15 means "hidden" */
191 Elf64_Verdef
*def
= vdso_info
.verdef
;
193 if ((def
->vd_flags
& VER_FLG_BASE
) == 0
194 && (def
->vd_ndx
& 0x7fff) == ver
)
197 if (def
->vd_next
== 0)
198 return false; /* No definition. */
200 def
= (Elf64_Verdef
*)((char *)def
+ def
->vd_next
);
203 /* Now figure out whether it matches. */
204 Elf64_Verdaux
*aux
= (Elf64_Verdaux
*)((char *)def
+ def
->vd_aux
);
205 return def
->vd_hash
== hash
206 && !strcmp(name
, vdso_info
.symstrings
+ aux
->vda_name
);
209 void *vdso_sym(const char *version
, const char *name
)
211 unsigned long ver_hash
;
212 if (!vdso_info
.valid
)
215 ver_hash
= elf_hash(version
);
216 Elf64_Word chain
= vdso_info
.bucket
[elf_hash(name
) % vdso_info
.nbucket
];
218 for (; chain
!= STN_UNDEF
; chain
= vdso_info
.chain
[chain
]) {
219 Elf64_Sym
*sym
= &vdso_info
.symtab
[chain
];
221 /* Check for a defined global or weak function w/ right name. */
222 if (ELF64_ST_TYPE(sym
->st_info
) != STT_FUNC
)
224 if (ELF64_ST_BIND(sym
->st_info
) != STB_GLOBAL
&&
225 ELF64_ST_BIND(sym
->st_info
) != STB_WEAK
)
227 if (sym
->st_shndx
== SHN_UNDEF
)
229 if (strcmp(name
, vdso_info
.symstrings
+ sym
->st_name
))
232 /* Check symbol version. */
234 && !vdso_match_version(vdso_info
.versym
[chain
],
238 return (void *)(vdso_info
.load_offset
+ sym
->st_value
);
244 void vdso_init_from_auxv(void *auxv
)
246 Elf64_auxv_t
*elf_auxv
= auxv
;
247 for (int i
= 0; elf_auxv
[i
].a_type
!= AT_NULL
; i
++)
249 if (elf_auxv
[i
].a_type
== AT_SYSINFO_EHDR
) {
250 vdso_init_from_sysinfo_ehdr(elf_auxv
[i
].a_un
.a_val
);
255 vdso_info
.valid
= false;