Skip to content

Commit 1d4854a

Browse files
committed
libdrgn: implement optimized x86-64 ELF relocations
After the libdwfl conversion, we apply ELF relocations with libdwfl instead of our homegrown implementation. However, libdwfl is much slower at it than the previous implementation. We can work around this by (again) applying ELF relocations ourselves for architectures that we care about (x86-64, to start). For other architectures, we can fall back to libdwfl. This new implementation of ELF relocation reworks the parallelization to be per-file rather than per-relocation. The latter was done originally because before commit 6f16ab0 ("libdrgn: only apply ELF relocations to relocatable files"), we applied relocations to vmlinux, which is much larger than most kernel modules. Now that we don't do that, it seems to be slightly faster to parallelize by file.
1 parent e5874ad commit 1d4854a

File tree

4 files changed

+363
-0
lines changed

4 files changed

+363
-0
lines changed

libdrgn/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ libdrgnimpl_la_SOURCES = binary_search_tree.h \
1010
dwarf_index.h \
1111
dwarf_info_cache.c \
1212
dwarf_info_cache.h \
13+
elf_relocator.c \
14+
elf_relocator.h \
1315
error.c \
1416
error.h \
1517
hash_table.c \

libdrgn/dwarf_index.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <sys/types.h>
1717

1818
#include "internal.h"
19+
#include "elf_relocator.h"
1920
#include "dwarf_index.h"
2021
#include "read.h"
2122
#include "siphash.h"
@@ -432,6 +433,34 @@ static struct drgn_error *get_debug_sections(Elf *elf, Elf_Data **sections)
432433
return NULL;
433434
}
434435

436+
static struct drgn_error *apply_relocations(Dwfl_Module **modules,
437+
size_t num_modules)
438+
{
439+
struct drgn_error *err;
440+
struct drgn_elf_relocator relocator;
441+
size_t i;
442+
443+
drgn_elf_relocator_init(&relocator);
444+
for (i = 0; i < num_modules; i++) {
445+
void **userdatap;
446+
struct drgn_dwfl_module_userdata *userdata;
447+
448+
dwfl_module_info(modules[i], &userdatap, NULL, NULL, NULL, NULL,
449+
NULL, NULL);
450+
userdata = *userdatap;
451+
if (userdata->elf) {
452+
err = drgn_elf_relocator_add_elf(&relocator,
453+
userdata->elf);
454+
if (err)
455+
goto out;
456+
}
457+
}
458+
err = drgn_elf_relocator_apply(&relocator);
459+
out:
460+
drgn_elf_relocator_deinit(&relocator);
461+
return err;
462+
}
463+
435464
static struct drgn_error *read_compilation_unit_header(const char *ptr,
436465
const char *end,
437466
struct compilation_unit *cu)
@@ -1521,6 +1550,9 @@ struct drgn_error *drgn_dwarf_index_update(struct drgn_dwarf_index *dindex,
15211550
err = &drgn_enomem;
15221551
goto out;
15231552
}
1553+
err = apply_relocations(modules.data, modules.size);
1554+
if (err)
1555+
goto out;
15241556
err = read_cus(dindex, modules.data, modules.size, &cus);
15251557
if (err)
15261558
goto out;

libdrgn/elf_relocator.c

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
// Copyright 2018-2019 - Omar Sandoval
2+
// SPDX-License-Identifier: GPL-3.0+
3+
4+
#include <inttypes.h>
5+
#include <string.h>
6+
7+
#include "internal.h"
8+
#include "elf_relocator.h"
9+
10+
DEFINE_VECTOR_FUNCTIONS(elf_vector)
11+
12+
void drgn_elf_relocator_init(struct drgn_elf_relocator *relocator)
13+
{
14+
elf_vector_init(&relocator->elfs);
15+
}
16+
17+
void drgn_elf_relocator_deinit(struct drgn_elf_relocator *relocator)
18+
{
19+
elf_vector_deinit(&relocator->elfs);
20+
}
21+
22+
struct drgn_error *
23+
drgn_elf_relocator_add_elf(struct drgn_elf_relocator *relocator, Elf *elf)
24+
{
25+
GElf_Ehdr ehdr_mem, *ehdr;
26+
27+
ehdr = gelf_getehdr(elf, &ehdr_mem);
28+
if (!ehdr)
29+
return drgn_error_libelf();
30+
31+
if (ehdr->e_type != ET_REL ||
32+
ehdr->e_machine != EM_X86_64 ||
33+
ehdr->e_ident[EI_CLASS] != ELFCLASS64 ||
34+
ehdr->e_ident[EI_DATA] !=
35+
(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ ?
36+
ELFDATA2LSB : ELFDATA2MSB))
37+
return NULL;
38+
39+
if (!elf_vector_append(&relocator->elfs, &elf))
40+
return &drgn_enomem;
41+
return NULL;
42+
}
43+
44+
static struct drgn_error *apply_relocation(Elf_Data *data, uint64_t r_offset,
45+
uint32_t r_type, int64_t r_addend,
46+
uint64_t st_value)
47+
{
48+
char *p;
49+
50+
p = (char *)data->d_buf + r_offset;
51+
switch (r_type) {
52+
case R_X86_64_NONE:
53+
break;
54+
case R_X86_64_32:
55+
if (r_offset > SIZE_MAX - sizeof(uint32_t) ||
56+
r_offset + sizeof(uint32_t) > data->d_size) {
57+
return drgn_error_create(DRGN_ERROR_ELF_FORMAT,
58+
"invalid relocation offset");
59+
}
60+
*(uint32_t *)p = st_value + r_addend;
61+
break;
62+
case R_X86_64_64:
63+
if (r_offset > SIZE_MAX - sizeof(uint64_t) ||
64+
r_offset + sizeof(uint64_t) > data->d_size) {
65+
return drgn_error_create(DRGN_ERROR_ELF_FORMAT,
66+
"invalid relocation offset");
67+
}
68+
*(uint64_t *)p = st_value + r_addend;
69+
break;
70+
default:
71+
return drgn_error_format(DRGN_ERROR_ELF_FORMAT,
72+
"unimplemented relocation type %" PRIu32,
73+
r_type);
74+
}
75+
return NULL;
76+
}
77+
78+
static struct drgn_error *relocate_section(Elf_Scn *scn, Elf_Scn *rela_scn,
79+
Elf_Scn *symtab_scn,
80+
uint64_t *sh_addrs, size_t shdrnum)
81+
{
82+
struct drgn_error *err;
83+
Elf_Data *data, *rela_data, *symtab_data;
84+
const Elf64_Rela *relocs;
85+
const Elf64_Sym *syms;
86+
size_t num_relocs, num_syms;
87+
size_t i;
88+
GElf_Shdr *shdr, shdr_mem;
89+
90+
err = read_elf_section(scn, &data);
91+
if (err)
92+
return err;
93+
err = read_elf_section(rela_scn, &rela_data);
94+
if (err)
95+
return err;
96+
err = read_elf_section(symtab_scn, &symtab_data);
97+
if (err)
98+
return err;
99+
100+
relocs = (Elf64_Rela *)rela_data->d_buf;
101+
num_relocs = rela_data->d_size / sizeof(Elf64_Rela);
102+
syms = (Elf64_Sym *)symtab_data->d_buf;
103+
num_syms = symtab_data->d_size / sizeof(Elf64_Sym);
104+
105+
for (i = 0; i < num_relocs; i++) {
106+
const Elf64_Rela *reloc = &relocs[i];
107+
uint32_t r_sym, r_type;
108+
uint16_t st_shndx;
109+
uint64_t sh_addr;
110+
111+
r_sym = ELF64_R_SYM(reloc->r_info);
112+
r_type = ELF64_R_TYPE(reloc->r_info);
113+
114+
if (r_sym >= num_syms) {
115+
return drgn_error_create(DRGN_ERROR_ELF_FORMAT,
116+
"invalid relocation symbol");
117+
}
118+
st_shndx = syms[r_sym].st_shndx;
119+
if (st_shndx == 0) {
120+
sh_addr = 0;
121+
} else if (st_shndx < shdrnum) {
122+
sh_addr = sh_addrs[st_shndx - 1];
123+
} else {
124+
return drgn_error_create(DRGN_ERROR_ELF_FORMAT,
125+
"invalid symbol section index");
126+
}
127+
err = apply_relocation(data, reloc->r_offset, r_type,
128+
reloc->r_addend,
129+
sh_addr + syms[r_sym].st_value);
130+
if (err)
131+
return err;
132+
}
133+
134+
/*
135+
* Mark the relocation section as empty so that libdwfl doesn't try to
136+
* apply it again.
137+
*/
138+
shdr = gelf_getshdr(rela_scn, &shdr_mem);
139+
if (!shdr)
140+
return drgn_error_libelf();
141+
shdr->sh_size = 0;
142+
if (!gelf_update_shdr(rela_scn, shdr))
143+
return drgn_error_libelf();
144+
rela_data->d_size = 0;
145+
return NULL;
146+
}
147+
148+
static struct drgn_error *relocate_elf(Elf *elf)
149+
{
150+
struct drgn_error *err;
151+
size_t shdrnum, shstrndx;
152+
uint64_t *sh_addrs;
153+
Elf_Scn *scn;
154+
155+
if (elf_getshdrnum(elf, &shdrnum))
156+
return drgn_error_libelf();
157+
if (shdrnum > 1) {
158+
sh_addrs = calloc(shdrnum - 1, sizeof(*sh_addrs));
159+
if (!sh_addrs)
160+
return &drgn_enomem;
161+
162+
scn = NULL;
163+
while ((scn = elf_nextscn(elf, scn))) {
164+
size_t ndx;
165+
166+
ndx = elf_ndxscn(scn);
167+
if (ndx > 0 && ndx < shdrnum) {
168+
GElf_Shdr *shdr, shdr_mem;
169+
170+
shdr = gelf_getshdr(scn, &shdr_mem);
171+
if (!shdr) {
172+
err = drgn_error_libelf();
173+
goto out;
174+
}
175+
sh_addrs[ndx - 1] = shdr->sh_addr;
176+
}
177+
}
178+
} else {
179+
sh_addrs = NULL;
180+
}
181+
182+
if (elf_getshdrstrndx(elf, &shstrndx)) {
183+
err = drgn_error_libelf();
184+
goto out;
185+
}
186+
187+
scn = NULL;
188+
while ((scn = elf_nextscn(elf, scn))) {
189+
GElf_Shdr *shdr, shdr_mem;
190+
const char *scnname;
191+
192+
shdr = gelf_getshdr(scn, &shdr_mem);
193+
if (!shdr) {
194+
err = drgn_error_libelf();
195+
goto out;
196+
}
197+
198+
if (shdr->sh_type != SHT_RELA)
199+
continue;
200+
201+
scnname = elf_strptr(elf, shstrndx, shdr->sh_name);
202+
if (!scnname)
203+
continue;
204+
205+
if (strncmp(scnname, ".rela.debug_", 12) == 0) {
206+
Elf_Scn *info_scn, *link_scn;
207+
208+
info_scn = elf_getscn(elf, shdr->sh_info);
209+
if (!info_scn) {
210+
err = drgn_error_libelf();
211+
goto out;
212+
}
213+
214+
link_scn = elf_getscn(elf, shdr->sh_link);
215+
if (!link_scn) {
216+
err = drgn_error_libelf();
217+
goto out;
218+
}
219+
220+
err = relocate_section(info_scn, scn, link_scn,
221+
sh_addrs, shdrnum);
222+
if (err)
223+
goto out;
224+
}
225+
}
226+
out:
227+
free(sh_addrs);
228+
return NULL;
229+
}
230+
231+
struct drgn_error *
232+
drgn_elf_relocator_apply(struct drgn_elf_relocator *relocator)
233+
{
234+
struct drgn_error *err = NULL;
235+
Elf **elfs = relocator->elfs.data;
236+
size_t num_elfs = relocator->elfs.size;
237+
238+
#pragma omp parallel for schedule(dynamic)
239+
for (size_t i = 0; i < num_elfs; i++) {
240+
struct drgn_error *err2;
241+
242+
if (err)
243+
continue;
244+
245+
err2 = relocate_elf(elfs[i]);
246+
if (err2) {
247+
#pragma omp critical(relocators_err)
248+
{
249+
if (err)
250+
drgn_error_destroy(err2);
251+
else
252+
err = err2;
253+
}
254+
continue;
255+
}
256+
}
257+
return err;
258+
}

libdrgn/elf_relocator.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2019 - Omar Sandoval
2+
// SPDX-License-Identifier: GPL-3.0+
3+
4+
/**
5+
* @file
6+
*
7+
* ELF relocator.
8+
*
9+
* See @ref ElfRelocator.
10+
*/
11+
12+
#ifndef DRGN_ELF_RELOCATOR_H
13+
#define DRGN_ELF_RELOCATOR_H
14+
15+
#include <libelf.h>
16+
17+
#include "vector.h"
18+
19+
/**
20+
* @ingroup Internals.
21+
*
22+
* @defgroup ElfRelocator ELF relocator
23+
*
24+
* Fast ELF relocations.
25+
*
26+
* Before the debugging information in a relocatable ELF file (e.g., Linux
27+
* kernel module) can be used, it must have ELF relocations applied. This is
28+
* usually done by libdwfl. However, libdwfl is relatively slow at it. @ref
29+
* drgn_elf_relocator is a much faster, parallelized implementation of ELF
30+
* relocation. It is only implemented for x86-64; for other architectures, we
31+
* can fall back to libdwfl.
32+
*/
33+
34+
DEFINE_VECTOR_TYPE(elf_vector, Elf *)
35+
36+
/**
37+
* ELF relocation interface.
38+
*
39+
* This interface is used to apply ELF relocations to debug sections in ELF
40+
* files.
41+
*
42+
* A relocator is initialized with @ref drgn_elf_relocator_init(). Files to be
43+
* relocated are added with @ref drgn_elf_relocator_add_elf(). Once all files
44+
* have been added, relocations are applied with @ref
45+
* drgn_elf_relocator_apply(). Finally, the relocator must be cleaned up with
46+
* @ref drgn_elf_relocator_deinit().
47+
*/
48+
struct drgn_elf_relocator {
49+
struct elf_vector elfs;
50+
};
51+
52+
/** Initialize a @ref drgn_elf_relocator. */
53+
void drgn_elf_relocator_init(struct drgn_elf_relocator *relocator);
54+
55+
/** Deinitialize a @ref drgn_elf_relocator. */
56+
void drgn_elf_relocator_deinit(struct drgn_elf_relocator *relocator);
57+
58+
/**
59+
* Add an ELF file to be relocated by a @ref drgn_elf_relocator.
60+
*
61+
* If the ELF file is not relocatable or has an unsupported architecture, this
62+
* does nothing.
63+
*/
64+
struct drgn_error *
65+
drgn_elf_relocator_add_elf(struct drgn_elf_relocator *relocator, Elf *elf);
66+
67+
/** Apply ELF relocations to all files added to a @ref drgn_elf_relocator. */
68+
struct drgn_error *
69+
drgn_elf_relocator_apply(struct drgn_elf_relocator *relocator);
70+
71+
#endif /* DRGN_ELF_RELOCATOR_H */

0 commit comments

Comments
 (0)