Hi
This is the second of 2 patchsets to improve dso__synthesize_plt_symbols().
This patchset focuses on getting rid of unknown symbols that show up in
Intel PT traces.
x86 has 2 more plt's, namely .plt.sec and .plt.got, so support is added for
synthesizing symbols for them. Special handing is needed for IFUNC symbols,
and it is also possible to have a .plt for static executables, so support is
added for that.
Adrian Hunter (9):
perf symbols: Correct plt entry sizes for x86
perf symbols: Add support for x86 .plt.sec
perf symbols: Sort plt relocations for x86
perf symbols: Record whether a symbol is an alias for an IFUNC symbol
perf symbols: Add support for IFUNC symbols for x86_64
perf symbols: Allow for .plt without header
perf symbols: Allow for static executables with .plt
perf symbols: Start adding support for .plt.got for x86
perf symbols: Get symbols for .plt.got for x86-64
tools/perf/util/symbol-elf.c | 362 ++++++++++++++++++++++++++++++++++++++++---
tools/perf/util/symbol.c | 4 +
tools/perf/util/symbol.h | 2 +
tools/perf/util/symsrc.h | 1 +
4 files changed, 347 insertions(+), 22 deletions(-)
Regards
Adrian
In 32-bit executables the .plt entry size can be set to 4 when it is really
16. In fact the only sizes used for x86 (32 or 64 bit) are 8 or 16, so
check for those and, if not, use the alignment to choose which it is.
Example on Ubuntu 22.04 gcc 11.3:
Before:
$ cat tstpltlib.c
void fn1(void) {}
void fn2(void) {}
void fn3(void) {}
void fn4(void) {}
$ cat tstplt.c
void fn1(void);
void fn2(void);
void fn3(void);
void fn4(void);
int main()
{
fn4();
fn1();
fn2();
fn3();
return 0;
}
$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc -m32 -Wall -Wextra -shared -o libtstpltlib32.so tstpltlib.c
$ gcc -m32 -Wall -Wextra -o tstplt32 tstplt.c -L . -ltstpltlib32 -Wl,-rpath=$(pwd)
$ perf record -e intel_pt//u --filter 'filter main @ ./tstplt32' ./tstplt32
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.011 MB perf.data ]
$ readelf -SW tstplt32 | grep 'plt\|Name'
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[10] .rel.plt REL 0000041c 00041c 000028 08 AI 5 22 4
[12] .plt PROGBITS 00001030 001030 000060 04 AX 0 0 16 <- ES is 0x04, should be 0x10
[13] .plt.got PROGBITS 00001090 001090 000008 08 AX 0 0 8
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
17894.383903029: tr strt 0 [unknown] => 565b81cd main+0x0
17894.383903029: tr end call 565b81d4 main+0x7 => 565b80d0 __x86.get_pc_thunk.bx+0x0
17894.383903031: tr strt 0 [unknown] => 565b81d9 main+0xc
17894.383903031: tr end call 565b81df main+0x12 => 565b8070 [unknown]
17894.383903032: tr strt 0 [unknown] => 565b81e4 main+0x17
17894.383903032: tr end call 565b81e4 main+0x17 => 565b8050 [unknown]
17894.383903033: tr strt 0 [unknown] => 565b81e9 main+0x1c
17894.383903033: tr end call 565b81e9 main+0x1c => 565b8080 [unknown]
17894.383903033: tr strt 0 [unknown] => 565b81ee main+0x21
17894.383903033: tr end call 565b81ee main+0x21 => 565b8060 [unknown]
17894.383903237: tr strt 0 [unknown] => 565b81f3 main+0x26
17894.383903237: tr end return 565b81fc main+0x2f => f7c21519 [unknown]
After:
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
17894.383903029: tr strt 0 [unknown] => 565b81cd main+0x0
17894.383903029: tr end call 565b81d4 main+0x7 => 565b80d0 __x86.get_pc_thunk.bx+0x0
17894.383903031: tr strt 0 [unknown] => 565b81d9 main+0xc
17894.383903031: tr end call 565b81df main+0x12 => 565b8070 fn4@plt+0x0
17894.383903032: tr strt 0 [unknown] => 565b81e4 main+0x17
17894.383903032: tr end call 565b81e4 main+0x17 => 565b8050 fn1@plt+0x0
17894.383903033: tr strt 0 [unknown] => 565b81e9 main+0x1c
17894.383903033: tr end call 565b81e9 main+0x1c => 565b8080 fn2@plt+0x0
17894.383903033: tr strt 0 [unknown] => 565b81ee main+0x21
17894.383903033: tr end call 565b81ee main+0x21 => 565b8060 fn3@plt+0x0
17894.383903237: tr strt 0 [unknown] => 565b81f3 main+0x26
17894.383903237: tr end return 565b81fc main+0x2f => f7c21519 [unknown]
Signed-off-by: Adrian Hunter <[email protected]>
---
tools/perf/util/symbol-elf.c | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index aa62735aea7b..9328c162d68f 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -360,14 +360,23 @@ static bool get_plt_sizes(struct dso *dso, GElf_Ehdr *ehdr, GElf_Shdr *shdr_plt,
*plt_header_size = 128;
*plt_entry_size = 32;
return true;
+ case EM_386:
+ case EM_X86_64:
+ *plt_entry_size = shdr_plt->sh_entsize;
+ /* Size is 8 or 16, if not, assume alignment indicates size */
+ if (*plt_entry_size != 8 && *plt_entry_size != 16)
+ *plt_entry_size = shdr_plt->sh_addralign == 8 ? 8 : 16;
+ *plt_header_size = *plt_entry_size;
+ break;
default: /* FIXME: s390/alpha/mips/parisc/poperpc/sh/xtensa need to be checked */
*plt_header_size = shdr_plt->sh_entsize;
*plt_entry_size = shdr_plt->sh_entsize;
- if (*plt_entry_size)
- return true;
- pr_debug("Missing PLT entry size for %s\n", dso->long_name);
- return false;
+ break;
}
+ if (*plt_entry_size)
+ return true;
+ pr_debug("Missing PLT entry size for %s\n", dso->long_name);
+ return false;
}
/*
--
2.34.1
The section .plt.sec was originally added for MPX and was first called
.plt.bnd. While MPX has been deprecated, .plt.sec is now also used for IBT.
On x86_64, IBT seems to be enabled by default, but can be switched off
using gcc option -fcf-protection=none. On 32-bit, option -z ibt will
enable IBT.
With .plt.sec, calls are made into .plt.sec instead of .plt, so it
makes more sense to put the symbols there instead of .plt. A notable
difference is that .plt.sec does not have a header entry.
For x86, when synthesizing symbols for plt, use offset and entry size of
.plt.sec instead of .plt when there is a .plt.sec section.
Example on Ubuntu 22.04 gcc 11.3:
Before:
$ cat tstpltlib.c
void fn1(void) {}
void fn2(void) {}
void fn3(void) {}
void fn4(void) {}
$ cat tstplt.c
void fn1(void);
void fn2(void);
void fn3(void);
void fn4(void);
int main()
{
fn4();
fn1();
fn2();
fn3();
return 0;
}
$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc -Wall -Wextra -shared -o libtstpltlib.so tstpltlib.c
$ gcc -Wall -Wextra -o tstplt tstplt.c -L . -ltstpltlib -Wl,-rpath=$(pwd)
$ readelf -SW tstplt | grep 'plt\|Name'
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[11] .rela.plt RELA 0000000000000698 000698 000060 18 AI 6 24 8
[13] .plt PROGBITS 0000000000001020 001020 000050 10 AX 0 0 16
[14] .plt.got PROGBITS 0000000000001070 001070 000010 10 AX 0 0 16
[15] .plt.sec PROGBITS 0000000000001080 001080 000040 10 AX 0 0 16
$ perf record -e intel_pt//u --filter 'filter main @ ./tstplt' ./tstplt
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.015 MB perf.data ]
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
38970.522546686: tr strt 0 [unknown] => 55fc222a81a9 main+0x0
38970.522546686: tr end call 55fc222a81b1 main+0x8 => 55fc222a80a0 [unknown]
38970.522546687: tr strt 0 [unknown] => 55fc222a81b6 main+0xd
38970.522546687: tr end call 55fc222a81b6 main+0xd => 55fc222a8080 [unknown]
38970.522546688: tr strt 0 [unknown] => 55fc222a81bb main+0x12
38970.522546688: tr end call 55fc222a81bb main+0x12 => 55fc222a80b0 [unknown]
38970.522546688: tr strt 0 [unknown] => 55fc222a81c0 main+0x17
38970.522546688: tr end call 55fc222a81c0 main+0x17 => 55fc222a8090 [unknown]
38970.522546689: tr strt 0 [unknown] => 55fc222a81c5 main+0x1c
38970.522546894: tr end return 55fc222a81cb main+0x22 => 7f3a4dc29d90 __libc_start_call_main+0x80
After:
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
38970.522546686: tr strt 0 [unknown] => 55fc222a81a9 main+0x0
38970.522546686: tr end call 55fc222a81b1 main+0x8 => 55fc222a80a0 fn4@plt+0x0
38970.522546687: tr strt 0 [unknown] => 55fc222a81b6 main+0xd
38970.522546687: tr end call 55fc222a81b6 main+0xd => 55fc222a8080 fn1@plt+0x0
38970.522546688: tr strt 0 [unknown] => 55fc222a81bb main+0x12
38970.522546688: tr end call 55fc222a81bb main+0x12 => 55fc222a80b0 fn2@plt+0x0
38970.522546688: tr strt 0 [unknown] => 55fc222a81c0 main+0x17
38970.522546688: tr end call 55fc222a81c0 main+0x17 => 55fc222a8090 fn3@plt+0x0
38970.522546689: tr strt 0 [unknown] => 55fc222a81c5 main+0x1c
38970.522546894: tr end return 55fc222a81cb main+0x22 => 7f3a4dc29d90 __libc_start_call_main+0x80
Signed-off-by: Adrian Hunter <[email protected]>
---
tools/perf/util/symbol-elf.c | 30 +++++++++++++++++++++++-------
1 file changed, 23 insertions(+), 7 deletions(-)
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 9328c162d68f..bb1b5cb3ff12 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -379,6 +379,11 @@ static bool get_plt_sizes(struct dso *dso, GElf_Ehdr *ehdr, GElf_Shdr *shdr_plt,
return false;
}
+static bool machine_is_x86(GElf_Half e_machine)
+{
+ return e_machine == EM_386 || e_machine == EM_X86_64;
+}
+
/*
* We need to check if we have a .dynsym, so that we can handle the
* .plt, synthesizing its symbols, that aren't on the symtabs (be it
@@ -391,8 +396,8 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
uint32_t nr_rel_entries, idx;
GElf_Sym sym;
u64 plt_offset, plt_header_size, plt_entry_size;
- GElf_Shdr shdr_plt;
- struct symbol *f;
+ GElf_Shdr shdr_plt, plt_sec_shdr;
+ struct symbol *f, *plt_sym;
GElf_Shdr shdr_rel_plt, shdr_dynsym;
Elf_Data *syms, *symstrs;
Elf_Scn *scn_plt_rel, *scn_symstrs, *scn_dynsym;
@@ -422,10 +427,23 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
return 0;
/* Add a symbol for .plt header */
- f = symbol__new(shdr_plt.sh_offset, plt_header_size, STB_GLOBAL, STT_FUNC, ".plt");
- if (!f)
+ plt_sym = symbol__new(shdr_plt.sh_offset, plt_header_size, STB_GLOBAL, STT_FUNC, ".plt");
+ if (!plt_sym)
goto out_elf_end;
- symbols__insert(&dso->symbols, f);
+ symbols__insert(&dso->symbols, plt_sym);
+
+ /* Only x86 has .plt.sec */
+ if (machine_is_x86(ehdr.e_machine) &&
+ elf_section_by_name(elf, &ehdr, &plt_sec_shdr, ".plt.sec", NULL)) {
+ if (!get_plt_sizes(dso, &ehdr, &plt_sec_shdr, &plt_header_size, &plt_entry_size))
+ return 0;
+ /* Extend .plt symbol to entire .plt */
+ plt_sym->end = plt_sym->start + shdr_plt.sh_size;
+ /* Use .plt.sec offset */
+ plt_offset = plt_sec_shdr.sh_offset;
+ } else {
+ plt_offset = shdr_plt.sh_offset + plt_header_size;
+ }
scn_dynsym = ss->dynsym;
shdr_dynsym = ss->dynshdr;
@@ -474,8 +492,6 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
goto out_elf_end;
nr_rel_entries = shdr_rel_plt.sh_size / shdr_rel_plt.sh_entsize;
- plt_offset = shdr_plt.sh_offset;
- plt_offset += plt_header_size;
ri.is_rela = shdr_rel_plt.sh_type == SHT_RELA;
--
2.34.1
For x86, with the addition of IFUNCs, relocation information becomes
disordered with respect to plt. Correct that by sorting the relocations by
offset.
Example:
Before:
$ cat tstpltlib.c
void fn1(void) {}
void fn2(void) {}
void fn3(void) {}
void fn4(void) {}
$ cat tstpltifunc.c
#include <stdio.h>
void thing1(void)
{
printf("thing1\n");
}
void thing2(void)
{
printf("thing2\n");
}
typedef void (*thing_fn_t)(void);
thing_fn_t thing_ifunc(void)
{
int x;
if (x & 1)
return thing2;
return thing1;
}
void thing(void) __attribute__ ((ifunc ("thing_ifunc")));
void fn1(void);
void fn2(void);
void fn3(void);
void fn4(void);
int main()
{
fn4();
fn1();
thing();
fn2();
fn3();
return 0;
}
$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc -Wall -Wextra -shared -o libtstpltlib.so tstpltlib.c
$ gcc -Wall -Wextra -Wno-uninitialized -o tstpltifunc tstpltifunc.c -L . -ltstpltlib -Wl,-rpath="$(pwd)"
$ readelf -rW tstpltifunc | grep -A99 plt
Relocation section '.rela.plt' at offset 0x738 contains 8 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000003f98 0000000300000007 R_X86_64_JUMP_SLOT 0000000000000000 puts@GLIBC_2.2.5 + 0
0000000000003fa8 0000000400000007 R_X86_64_JUMP_SLOT 0000000000000000 __stack_chk_fail@GLIBC_2.4 + 0
0000000000003fb0 0000000500000007 R_X86_64_JUMP_SLOT 0000000000000000 fn1 + 0
0000000000003fb8 0000000600000007 R_X86_64_JUMP_SLOT 0000000000000000 fn3 + 0
0000000000003fc0 0000000800000007 R_X86_64_JUMP_SLOT 0000000000000000 fn4 + 0
0000000000003fc8 0000000900000007 R_X86_64_JUMP_SLOT 0000000000000000 fn2 + 0
0000000000003fd0 0000000b00000007 R_X86_64_JUMP_SLOT 0000000000000000 getrandom@GLIBC_2.25 + 0
0000000000003fa0 0000000000000025 R_X86_64_IRELATIVE 125d
$ perf record -e intel_pt//u --filter 'filter main @ ./tstpltifunc' ./tstpltifunc
thing2
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.029 MB perf.data ]
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
20417.302513948: tr strt 0 [unknown] => 5629a74892be main+0x0
20417.302513948: tr end call 5629a74892c6 main+0x8 => 5629a7489110 fn2@plt+0x0
20417.302513949: tr strt 0 [unknown] => 5629a74892cb main+0xd
20417.302513949: tr end call 5629a74892cb main+0xd => 5629a74890f0 fn3@plt+0x0
20417.302513950: tr strt 0 [unknown] => 5629a74892d0 main+0x12
20417.302513950: tr end call 5629a74892d0 main+0x12 => 5629a74890d0 __stack_chk_fail@plt+0x0
20417.302528114: tr strt 0 [unknown] => 5629a74892d5 main+0x17
20417.302528114: tr end call 5629a74892d5 main+0x17 => 5629a7489120 getrandom@plt+0x0
20417.302528115: tr strt 0 [unknown] => 5629a74892da main+0x1c
20417.302528115: tr end call 5629a74892da main+0x1c => 5629a7489100 fn4@plt+0x0
20417.302528115: tr strt 0 [unknown] => 5629a74892df main+0x21
20417.302528115: tr end return 5629a74892e5 main+0x27 => 7ff14da29d90 __libc_start_call_main+0x80
After:
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
20417.302513948: tr strt 0 [unknown] => 5629a74892be main+0x0
20417.302513948: tr end call 5629a74892c6 main+0x8 => 5629a7489110 fn4@plt+0x0
20417.302513949: tr strt 0 [unknown] => 5629a74892cb main+0xd
20417.302513949: tr end call 5629a74892cb main+0xd => 5629a74890f0 fn1@plt+0x0
20417.302513950: tr strt 0 [unknown] => 5629a74892d0 main+0x12
20417.302513950: tr end call 5629a74892d0 main+0x12 => 5629a74890d0 offset_0x10d0@plt+0x0
20417.302528114: tr strt 0 [unknown] => 5629a74892d5 main+0x17
20417.302528114: tr end call 5629a74892d5 main+0x17 => 5629a7489120 fn2@plt+0x0
20417.302528115: tr strt 0 [unknown] => 5629a74892da main+0x1c
20417.302528115: tr end call 5629a74892da main+0x1c => 5629a7489100 fn3@plt+0x0
20417.302528115: tr strt 0 [unknown] => 5629a74892df main+0x21
20417.302528115: tr end return 5629a74892e5 main+0x27 => 7ff14da29d90 __libc_start_call_main+0x80
Signed-off-by: Adrian Hunter <[email protected]>
---
tools/perf/util/symbol-elf.c | 60 ++++++++++++++++++++++++++++++++++--
1 file changed, 57 insertions(+), 3 deletions(-)
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index bb1b5cb3ff12..07cfcf8f40e3 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -324,6 +324,8 @@ static char *demangle_sym(struct dso *dso, int kmodule, const char *elf_name)
}
struct rel_info {
+ u32 nr_entries;
+ u32 *sorted;
bool is_rela;
Elf_Data *reldata;
GElf_Rela rela;
@@ -332,6 +334,7 @@ struct rel_info {
static u32 get_rel_symidx(struct rel_info *ri, u32 idx)
{
+ idx = ri->sorted ? ri->sorted[idx] : idx;
if (ri->is_rela) {
gelf_getrela(ri->reldata, idx, &ri->rela);
return GELF_R_SYM(ri->rela.r_info);
@@ -340,6 +343,49 @@ static u32 get_rel_symidx(struct rel_info *ri, u32 idx)
return GELF_R_SYM(ri->rel.r_info);
}
+static u64 get_rel_offset(struct rel_info *ri, u32 x)
+{
+ if (ri->is_rela) {
+ GElf_Rela rela;
+
+ gelf_getrela(ri->reldata, x, &rela);
+ return rela.r_offset;
+ } else {
+ GElf_Rel rel;
+
+ gelf_getrel(ri->reldata, x, &rel);
+ return rel.r_offset;
+ }
+}
+
+static int rel_cmp(const void *a, const void *b, void *r)
+{
+ struct rel_info *ri = r;
+ u64 a_offset = get_rel_offset(ri, *(const u32 *)a);
+ u64 b_offset = get_rel_offset(ri, *(const u32 *)b);
+
+ return a_offset < b_offset ? -1 : (a_offset > b_offset ? 1 : 0);
+}
+
+static int sort_rel(struct rel_info *ri)
+{
+ size_t sz = sizeof(ri->sorted[0]);
+ u32 i;
+
+ ri->sorted = calloc(ri->nr_entries, sz);
+ if (!ri->sorted)
+ return -1;
+ for (i = 0; i < ri->nr_entries; i++)
+ ri->sorted[i] = i;
+ qsort_r(ri->sorted, ri->nr_entries, sz, rel_cmp, ri);
+ return 0;
+}
+
+static void exit_rel(struct rel_info *ri)
+{
+ free(ri->sorted);
+}
+
static bool get_plt_sizes(struct dso *dso, GElf_Ehdr *ehdr, GElf_Shdr *shdr_plt,
u64 *plt_header_size, u64 *plt_entry_size)
{
@@ -393,7 +439,7 @@ static bool machine_is_x86(GElf_Half e_machine)
*/
int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
{
- uint32_t nr_rel_entries, idx;
+ uint32_t idx;
GElf_Sym sym;
u64 plt_offset, plt_header_size, plt_entry_size;
GElf_Shdr shdr_plt, plt_sec_shdr;
@@ -491,11 +537,18 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
if (symstrs->d_size == 0)
goto out_elf_end;
- nr_rel_entries = shdr_rel_plt.sh_size / shdr_rel_plt.sh_entsize;
+ ri.nr_entries = shdr_rel_plt.sh_size / shdr_rel_plt.sh_entsize;
ri.is_rela = shdr_rel_plt.sh_type == SHT_RELA;
- for (idx = 0; idx < nr_rel_entries; idx++) {
+ /*
+ * x86 doesn't insert IFUNC relocations in .plt order, so sort to get
+ * back in order.
+ */
+ if (machine_is_x86(ehdr.e_machine) && sort_rel(&ri))
+ goto out_elf_end;
+
+ for (idx = 0; idx < ri.nr_entries; idx++) {
const char *elf_name = NULL;
char *demangled = NULL;
@@ -523,6 +576,7 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
err = 0;
out_elf_end:
+ exit_rel(&ri);
if (err == 0)
return nr;
pr_debug("%s: problems reading %s PLT info.\n",
--
2.34.1
To assist with synthesizing plt symbols for IFUNCs, record whether a
symbol is an alias of an IFUNC symbol.
Signed-off-by: Adrian Hunter <[email protected]>
---
tools/perf/util/symbol.c | 4 ++++
tools/perf/util/symbol.h | 2 ++
2 files changed, 6 insertions(+)
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index a024f06f75d8..d05727fcb30d 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -201,10 +201,14 @@ void symbols__fixup_duplicate(struct rb_root_cached *symbols)
continue;
if (choose_best_symbol(curr, next) == SYMBOL_A) {
+ if (next->type == STT_GNU_IFUNC)
+ curr->ifunc_alias = true;
rb_erase_cached(&next->rb_node, symbols);
symbol__delete(next);
goto again;
} else {
+ if (curr->type == STT_GNU_IFUNC)
+ next->ifunc_alias = true;
nd = rb_next(&curr->rb_node);
rb_erase_cached(&curr->rb_node, symbols);
symbol__delete(curr);
diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h
index 2fdeb22bd02f..7558735543c2 100644
--- a/tools/perf/util/symbol.h
+++ b/tools/perf/util/symbol.h
@@ -64,6 +64,8 @@ struct symbol {
u8 inlined:1;
/** Has symbol__annotate2 been performed. */
u8 annotate2:1;
+ /** Symbol is an alias of an STT_GNU_IFUNC */
+ u8 ifunc_alias:1;
/** Architecture specific. Unused except on PPC where it holds st_other. */
u8 arch_sym;
/** The name of length namelen associated with the symbol. */
--
2.34.1
For x86_64, the GNU linker is putting IFUNC information in the relocation
addend, so use it to try to find a symbol for plt entries that refer to
IFUNCs.
Example:
Before:
$ cat tstpltlib.c
void fn1(void) {}
void fn2(void) {}
void fn3(void) {}
void fn4(void) {}
$ cat tstpltifunc.c
#include <stdio.h>
void thing1(void)
{
printf("thing1\n");
}
void thing2(void)
{
printf("thing2\n");
}
typedef void (*thing_fn_t)(void);
thing_fn_t thing_ifunc(void)
{
int x;
if (x & 1)
return thing2;
return thing1;
}
void thing(void) __attribute__ ((ifunc ("thing_ifunc")));
void fn1(void);
void fn2(void);
void fn3(void);
void fn4(void);
int main()
{
fn4();
fn1();
thing();
fn2();
fn3();
return 0;
}
$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc -Wall -Wextra -shared -o libtstpltlib.so tstpltlib.c
$ gcc -Wall -Wextra -Wno-uninitialized -o tstpltifunc tstpltifunc.c -L . -ltstpltlib -Wl,-rpath="$(pwd)"
$ readelf -rW tstpltifunc | grep -A99 plt
Relocation section '.rela.plt' at offset 0x738 contains 8 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000003f98 0000000300000007 R_X86_64_JUMP_SLOT 0000000000000000 puts@GLIBC_2.2.5 + 0
0000000000003fa8 0000000400000007 R_X86_64_JUMP_SLOT 0000000000000000 __stack_chk_fail@GLIBC_2.4 + 0
0000000000003fb0 0000000500000007 R_X86_64_JUMP_SLOT 0000000000000000 fn1 + 0
0000000000003fb8 0000000600000007 R_X86_64_JUMP_SLOT 0000000000000000 fn3 + 0
0000000000003fc0 0000000800000007 R_X86_64_JUMP_SLOT 0000000000000000 fn4 + 0
0000000000003fc8 0000000900000007 R_X86_64_JUMP_SLOT 0000000000000000 fn2 + 0
0000000000003fd0 0000000b00000007 R_X86_64_JUMP_SLOT 0000000000000000 getrandom@GLIBC_2.25 + 0
0000000000003fa0 0000000000000025 R_X86_64_IRELATIVE 125d
$ perf record -e intel_pt//u --filter 'filter main @ ./tstpltifunc' ./tstpltifunc
thing2
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.016 MB perf.data ]
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
21860.073683659: tr strt 0 [unknown] => 561e212c42be main+0x0
21860.073683659: tr end call 561e212c42c6 main+0x8 => 561e212c4110 fn4@plt+0x0
21860.073683661: tr strt 0 [unknown] => 561e212c42cb main+0xd
21860.073683661: tr end call 561e212c42cb main+0xd => 561e212c40f0 fn1@plt+0x0
21860.073683661: tr strt 0 [unknown] => 561e212c42d0 main+0x12
21860.073683661: tr end call 561e212c42d0 main+0x12 => 561e212c40d0 offset_0x10d0@plt+0x0
21860.073698451: tr strt 0 [unknown] => 561e212c42d5 main+0x17
21860.073698451: tr end call 561e212c42d5 main+0x17 => 561e212c4120 fn2@plt+0x0
21860.073698451: tr strt 0 [unknown] => 561e212c42da main+0x1c
21860.073698451: tr end call 561e212c42da main+0x1c => 561e212c4100 fn3@plt+0x0
21860.073698452: tr strt 0 [unknown] => 561e212c42df main+0x21
21860.073698452: tr end return 561e212c42e5 main+0x27 => 7fb51cc29d90 __libc_start_call_main+0x80
After:
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
21860.073683659: tr strt 0 [unknown] => 561e212c42be main+0x0
21860.073683659: tr end call 561e212c42c6 main+0x8 => 561e212c4110 fn4@plt+0x0
21860.073683661: tr strt 0 [unknown] => 561e212c42cb main+0xd
21860.073683661: tr end call 561e212c42cb main+0xd => 561e212c40f0 fn1@plt+0x0
21860.073683661: tr strt 0 [unknown] => 561e212c42d0 main+0x12
21860.073683661: tr end call 561e212c42d0 main+0x12 => 561e212c40d0 thing_ifunc@plt+0x0
21860.073698451: tr strt 0 [unknown] => 561e212c42d5 main+0x17
21860.073698451: tr end call 561e212c42d5 main+0x17 => 561e212c4120 fn2@plt+0x0
21860.073698451: tr strt 0 [unknown] => 561e212c42da main+0x1c
21860.073698451: tr end call 561e212c42da main+0x1c => 561e212c4100 fn3@plt+0x0
21860.073698452: tr strt 0 [unknown] => 561e212c42df main+0x21
21860.073698452: tr end return 561e212c42e5 main+0x27 => 7fb51cc29d90 __libc_start_call_main+0x80
Signed-off-by: Adrian Hunter <[email protected]>
---
tools/perf/util/symbol-elf.c | 38 +++++++++++++++++++++++++++++++++++-
1 file changed, 37 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 07cfcf8f40e3..a002fc0bea03 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -381,6 +381,42 @@ static int sort_rel(struct rel_info *ri)
return 0;
}
+/*
+ * For x86_64, the GNU linker is putting IFUNC information in the relocation
+ * addend.
+ */
+static bool addend_may_be_ifunc(GElf_Ehdr *ehdr, struct rel_info *ri)
+{
+ return ehdr->e_machine == EM_X86_64 && ri->is_rela &&
+ GELF_R_TYPE(ri->rela.r_info) == R_X86_64_IRELATIVE;
+}
+
+static bool get_ifunc_name(Elf *elf, struct dso *dso, GElf_Ehdr *ehdr,
+ struct rel_info *ri, char *buf, size_t buf_sz)
+{
+ u64 addr = ri->rela.r_addend;
+ struct symbol *sym;
+ GElf_Phdr phdr;
+
+ if (!addend_may_be_ifunc(ehdr, ri))
+ return false;
+
+ if (elf_read_program_header(elf, addr, &phdr))
+ return false;
+
+ addr -= phdr.p_vaddr - phdr.p_offset;
+
+ sym = dso__find_symbol_nocache(dso, addr);
+
+ /* Expecting the address to be an IFUNC or IFUNC alias */
+ if (!sym || sym->start != addr || (sym->type != STT_GNU_IFUNC && !sym->ifunc_alias))
+ return false;
+
+ snprintf(buf, buf_sz, "%s@plt", sym->name);
+
+ return true;
+}
+
static void exit_rel(struct rel_info *ri)
{
free(ri->sorted);
@@ -560,7 +596,7 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
elf_name = demangled;
if (*elf_name)
snprintf(sympltname, sizeof(sympltname), "%s@plt", elf_name);
- else
+ else if (!get_ifunc_name(elf, dso, &ehdr, &ri, sympltname, sizeof(sympltname)))
snprintf(sympltname, sizeof(sympltname),
"offset_%#" PRIx64 "@plt", plt_offset);
free(demangled);
--
2.34.1
A static executable can have a .plt due to the presence of IFUNCs.
In that case the .plt does not have a header. Check for whether
there is a header by comparing the number of entries to the
number of relocation entries.
Signed-off-by: Adrian Hunter <[email protected]>
---
tools/perf/util/symbol-elf.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index a002fc0bea03..8f7802097c72 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -489,6 +489,7 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
Elf *elf;
int nr = 0, err = -1;
struct rel_info ri = { .is_rela = false };
+ bool lazy_plt;
elf = ss->elf;
ehdr = ss->ehdr;
@@ -523,8 +524,10 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
plt_sym->end = plt_sym->start + shdr_plt.sh_size;
/* Use .plt.sec offset */
plt_offset = plt_sec_shdr.sh_offset;
+ lazy_plt = false;
} else {
- plt_offset = shdr_plt.sh_offset + plt_header_size;
+ plt_offset = shdr_plt.sh_offset;
+ lazy_plt = true;
}
scn_dynsym = ss->dynsym;
@@ -577,6 +580,17 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
ri.is_rela = shdr_rel_plt.sh_type == SHT_RELA;
+ if (lazy_plt) {
+ /*
+ * Assume a .plt with the same number of entries as the number
+ * of relocation entries is not lazy and does not have a header.
+ */
+ if (ri.nr_entries * plt_entry_size == shdr_plt.sh_size)
+ dso__delete_symbol(dso, plt_sym);
+ else
+ plt_offset += plt_header_size;
+ }
+
/*
* x86 doesn't insert IFUNC relocations in .plt order, so sort to get
* back in order.
--
2.34.1
A statically linked executable can have a .plt due to IFUNCs, in which
case .symtab is used not .dynsym. Check the section header link to see
if that is the case, and then use symtab instead.
Example:
Before:
$ cat tstifunc.c
#include <stdio.h>
void thing1(void)
{
printf("thing1\n");
}
void thing2(void)
{
printf("thing2\n");
}
typedef void (*thing_fn_t)(void);
thing_fn_t thing_ifunc(void)
{
int x;
if (x & 1)
return thing2;
return thing1;
}
void thing(void) __attribute__ ((ifunc ("thing_ifunc")));
int main()
{
thing();
return 0;
}
$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc -static -Wall -Wextra -Wno-uninitialized -o tstifuncstatic tstifunc.c
$ readelf -SW tstifuncstatic | grep 'Name\|plt\|dyn'
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 4] .rela.plt RELA 00000000004002e8 0002e8 000258 18 AI 29 20 8
[ 6] .plt PROGBITS 0000000000401020 001020 000190 00 AX 0 0 16
[20] .got.plt PROGBITS 00000000004c5000 0c4000 0000e0 08 WA 0 0 8
$ perf record -e intel_pt//u --filter 'filter main @ ./tstifuncstatic' ./tstifuncstatic
thing1
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.008 MB perf.data ]
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
15786.690189535: tr strt 0 [unknown] => 4017cd main+0x0
15786.690189535: tr end call 4017d5 main+0x8 => 401170 [unknown]
15786.690197660: tr strt 0 [unknown] => 4017da main+0xd
15786.690197660: tr end return 4017e0 main+0x13 => 401c1a __libc_start_call_main+0x6a
After:
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
15786.690189535: tr strt 0 [unknown] => 4017cd main+0x0
15786.690189535: tr end call 4017d5 main+0x8 => 401170 thing_ifunc@plt+0x0
15786.690197660: tr strt 0 [unknown] => 4017da main+0xd
15786.690197660: tr end return 4017e0 main+0x13 => 401c1a __libc_start_call_main+0x6a
Signed-off-by: Adrian Hunter <[email protected]>
---
tools/perf/util/symbol-elf.c | 30 ++++++++++++++++++++----------
tools/perf/util/symsrc.h | 1 +
2 files changed, 21 insertions(+), 10 deletions(-)
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 8f7802097c72..9e265a726418 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -483,7 +483,6 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
GElf_Shdr shdr_rel_plt, shdr_dynsym;
Elf_Data *syms, *symstrs;
Elf_Scn *scn_plt_rel, *scn_symstrs, *scn_dynsym;
- size_t dynsym_idx;
GElf_Ehdr ehdr;
char sympltname[1024];
Elf *elf;
@@ -530,13 +529,6 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
lazy_plt = true;
}
- scn_dynsym = ss->dynsym;
- shdr_dynsym = ss->dynshdr;
- dynsym_idx = ss->dynsym_idx;
-
- if (scn_dynsym == NULL)
- return 0;
-
scn_plt_rel = elf_section_by_name(elf, &ehdr, &shdr_rel_plt,
".rela.plt", NULL);
if (scn_plt_rel == NULL) {
@@ -550,8 +542,25 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
shdr_rel_plt.sh_type != SHT_REL)
return 0;
- if (shdr_rel_plt.sh_link != dynsym_idx)
+ if (!shdr_rel_plt.sh_link)
+ return 0;
+
+ if (shdr_rel_plt.sh_link == ss->dynsym_idx) {
+ scn_dynsym = ss->dynsym;
+ shdr_dynsym = ss->dynshdr;
+ } else if (shdr_rel_plt.sh_link == ss->symtab_idx) {
+ /*
+ * A static executable can have a .plt due to IFUNCs, in which
+ * case .symtab is used not .dynsym.
+ */
+ scn_dynsym = ss->symtab;
+ shdr_dynsym = ss->symshdr;
+ } else {
goto out_elf_end;
+ }
+
+ if (!scn_dynsym)
+ return 0;
/*
* Fetch the relocation section to find the idxes to the GOT
@@ -1077,8 +1086,9 @@ int symsrc__init(struct symsrc *ss, struct dso *dso, const char *name,
ss->is_64_bit = (gelf_getclass(elf) == ELFCLASS64);
+ ss->symtab_idx = 0;
ss->symtab = elf_section_by_name(elf, &ehdr, &ss->symshdr, ".symtab",
- NULL);
+ &ss->symtab_idx);
if (ss->symshdr.sh_type != SHT_SYMTAB)
ss->symtab = NULL;
diff --git a/tools/perf/util/symsrc.h b/tools/perf/util/symsrc.h
index 2665b4bde751..edf82028c9e6 100644
--- a/tools/perf/util/symsrc.h
+++ b/tools/perf/util/symsrc.h
@@ -26,6 +26,7 @@ struct symsrc {
GElf_Shdr opdshdr;
Elf_Scn *symtab;
+ size_t symtab_idx;
GElf_Shdr symshdr;
Elf_Scn *dynsym;
--
2.34.1
For x86, .plt.got is used, for example, when the address is taken of a
dynamically linked function. Start adding support by synthesizing a
symbol for each entry. A subsequent patch will attempt to get a better
name for the symbol.
Example:
Before:
$ cat tstpltlib.c
void fn1(void) {}
void fn2(void) {}
void fn3(void) {}
void fn4(void) {}
$ cat tstpltgot.c
void fn1(void);
void fn2(void);
void fn3(void);
void fn4(void);
void callfn(void (*fn)(void))
{
fn();
}
int main()
{
fn4();
fn1();
callfn(fn3);
fn2();
fn3();
return 0;
}
$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc -Wall -Wextra -shared -o libtstpltlib.so tstpltlib.c
$ gcc -Wall -Wextra -o tstpltgot tstpltgot.c -L . -ltstpltlib -Wl,-rpath="$(pwd)"
$ readelf -SW tstpltgot | grep 'Name\|plt\|dyn'
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 6] .dynsym DYNSYM 00000000000003d8 0003d8 0000f0 18 A 7 1 8
[ 7] .dynstr STRTAB 00000000000004c8 0004c8 0000c6 00 A 0 0 1
[10] .rela.dyn RELA 00000000000005d8 0005d8 0000d8 18 A 6 0 8
[11] .rela.plt RELA 00000000000006b0 0006b0 000048 18 AI 6 24 8
[13] .plt PROGBITS 0000000000001020 001020 000040 10 AX 0 0 16
[14] .plt.got PROGBITS 0000000000001060 001060 000020 10 AX 0 0 16
[15] .plt.sec PROGBITS 0000000000001080 001080 000030 10 AX 0 0 16
[23] .dynamic DYNAMIC 0000000000003d90 002d90 000210 10 WA 7 0 8
$ perf record -e intel_pt//u --filter 'filter main @ ./tstpltgot , filter callfn @ ./tstpltgot' ./tstpltgot
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.011 MB perf.data ]
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
28393.810326915: tr strt 0 [unknown] => 562350baa1b2 main+0x0
28393.810326915: tr end call 562350baa1ba main+0x8 => 562350baa090 fn4@plt+0x0
28393.810326917: tr strt 0 [unknown] => 562350baa1bf main+0xd
28393.810326917: tr end call 562350baa1bf main+0xd => 562350baa080 fn1@plt+0x0
28393.810326917: tr strt 0 [unknown] => 562350baa1c4 main+0x12
28393.810326917: call 562350baa1ce main+0x1c => 562350baa199 callfn+0x0
28393.810326917: tr end call 562350baa1ad callfn+0x14 => 7f607d36110f fn3+0x0
28393.810326922: tr strt 0 [unknown] => 562350baa1af callfn+0x16
28393.810326922: return 562350baa1b1 callfn+0x18 => 562350baa1d3 main+0x21
28393.810326922: tr end call 562350baa1d3 main+0x21 => 562350baa0a0 fn2@plt+0x0
28393.810326924: tr strt 0 [unknown] => 562350baa1d8 main+0x26
28393.810326924: tr end call 562350baa1d8 main+0x26 => 562350baa060 [unknown] <- call to fn3 via .plt.got
28393.810326925: tr strt 0 [unknown] => 562350baa1dd main+0x2b
28393.810326925: tr end return 562350baa1e3 main+0x31 => 7f607d029d90 __libc_start_call_main+0x80
After:
$ perf script --itrace=be --ns -F+flags,-event,+addr,-period,-comm,-tid,-cpu,-dso
28393.810326915: tr strt 0 [unknown] => 562350baa1b2 main+0x0
28393.810326915: tr end call 562350baa1ba main+0x8 => 562350baa090 fn4@plt+0x0
28393.810326917: tr strt 0 [unknown] => 562350baa1bf main+0xd
28393.810326917: tr end call 562350baa1bf main+0xd => 562350baa080 fn1@plt+0x0
28393.810326917: tr strt 0 [unknown] => 562350baa1c4 main+0x12
28393.810326917: call 562350baa1ce main+0x1c => 562350baa199 callfn+0x0
28393.810326917: tr end call 562350baa1ad callfn+0x14 => 7f607d36110f fn3+0x0
28393.810326922: tr strt 0 [unknown] => 562350baa1af callfn+0x16
28393.810326922: return 562350baa1b1 callfn+0x18 => 562350baa1d3 main+0x21
28393.810326922: tr end call 562350baa1d3 main+0x21 => 562350baa0a0 fn2@plt+0x0
28393.810326924: tr strt 0 [unknown] => 562350baa1d8 main+0x26
28393.810326924: tr end call 562350baa1d8 main+0x26 => 562350baa060 offset_0x1060@plt+0x0
28393.810326925: tr strt 0 [unknown] => 562350baa1dd main+0x2b
28393.810326925: tr end return 562350baa1e3 main+0x31 => 7f607d029d90 __libc_start_call_main+0x80
Signed-off-by: Adrian Hunter <[email protected]>
---
tools/perf/util/symbol-elf.c | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 9e265a726418..254116d40e59 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -466,6 +466,30 @@ static bool machine_is_x86(GElf_Half e_machine)
return e_machine == EM_386 || e_machine == EM_X86_64;
}
+static int dso__synthesize_plt_got_symbols(struct dso *dso, Elf *elf,
+ GElf_Ehdr *ehdr,
+ char *buf, size_t buf_sz)
+{
+ struct symbol *sym;
+ GElf_Shdr shdr;
+ Elf_Scn *scn;
+ size_t i;
+
+ scn = elf_section_by_name(elf, ehdr, &shdr, ".plt.got", NULL);
+ if (!scn || !shdr.sh_entsize)
+ return 0;
+
+ for (i = 0; i < shdr.sh_size; i += shdr.sh_entsize) {
+ snprintf(buf, buf_sz, "offset_%#zx@plt", shdr.sh_offset + i);
+ sym = symbol__new(shdr.sh_offset + i, shdr.sh_entsize, STB_GLOBAL, STT_FUNC, buf);
+ if (!sym)
+ return -1;
+ symbols__insert(&dso->symbols, sym);
+ }
+
+ return 0;
+}
+
/*
* We need to check if we have a .dynsym, so that we can handle the
* .plt, synthesizing its symbols, that aren't on the symtabs (be it
@@ -514,6 +538,11 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss)
goto out_elf_end;
symbols__insert(&dso->symbols, plt_sym);
+ /* Only x86 has .plt.got */
+ if (machine_is_x86(ehdr.e_machine) &&
+ dso__synthesize_plt_got_symbols(dso, elf, &ehdr, sympltname, sizeof(sympltname)))
+ goto out_elf_end;
+
/* Only x86 has .plt.sec */
if (machine_is_x86(ehdr.e_machine) &&
elf_section_by_name(elf, &ehdr, &plt_sec_shdr, ".plt.sec", NULL)) {
--
2.34.1
For x86_64, determine a symbol for .plt.got entries. That requires
computing the target offset and finding that in .rela.dyn, which in
turn means .rela.dyn needs to be sorted by offset.
Example:
In this example, the GNU C Library is using .plt.got for malloc and
free.
Before:
$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ perf record -e intel_pt//u uname
Linux
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.027 MB perf.data ]
$ perf script --itrace=be --ns -F-event,+addr,-period,-comm,-tid,-cpu > /tmp/cmp1.txt
After:
$ perf script --itrace=be --ns -F-event,+addr,-period,-comm,-tid,-cpu > /tmp/cmp2.txt
$ diff /tmp/cmp1.txt /tmp/cmp2.txt | head -12
15509,15510c15509,15510
< 27046.755390907: 7f0b2943e3ab _nl_normalize_codeset+0x5b (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428380 offset_0x28380@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
< 27046.755390907: 7f0b29428384 offset_0x28380@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5120 malloc+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
---
> 27046.755390907: 7f0b2943e3ab _nl_normalize_codeset+0x5b (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428380 malloc@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
> 27046.755390907: 7f0b29428384 malloc@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5120 malloc+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
15821,15822c15821,15822
< 27046.755394865: 7f0b2943850c _nl_load_locale_from_archive+0x5bc (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428370 offset_0x28370@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
< 27046.755394865: 7f0b29428374 offset_0x28370@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5460 cfree@GLIBC_2.2.5+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
---
> 27046.755394865: 7f0b2943850c _nl_load_locale_from_archive+0x5bc (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428370 free@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
> 27046.755394865: 7f0b29428374 free@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5460 cfree@GLIBC_2.2.5+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
Signed-off-by: Adrian Hunter <[email protected]>
---
tools/perf/util/symbol-elf.c | 158 ++++++++++++++++++++++++++++++++++-
1 file changed, 154 insertions(+), 4 deletions(-)
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 254116d40e59..4fc8e7fc10f4 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -466,28 +466,178 @@ static bool machine_is_x86(GElf_Half e_machine)
return e_machine == EM_386 || e_machine == EM_X86_64;
}
+struct rela_dyn {
+ GElf_Addr offset;
+ u32 sym_idx;
+};
+
+struct rela_dyn_info {
+ struct dso *dso;
+ Elf_Data *plt_got_data;
+ u32 nr_entries;
+ struct rela_dyn *sorted;
+ Elf_Data *dynsym_data;
+ Elf_Data *dynstr_data;
+ Elf_Data *rela_dyn_data;
+};
+
+static void exit_rela_dyn(struct rela_dyn_info *di)
+{
+ free(di->sorted);
+}
+
+static int cmp_offset(const void *a, const void *b)
+{
+ const struct rela_dyn *va = a;
+ const struct rela_dyn *vb = b;
+
+ return va->offset < vb->offset ? -1 : (va->offset > vb->offset ? 1 : 0);
+}
+
+static int sort_rela_dyn(struct rela_dyn_info *di)
+{
+ u32 i, n;
+
+ di->sorted = calloc(di->nr_entries, sizeof(di->sorted[0]));
+ if (!di->sorted)
+ return -1;
+
+ /* Get data for sorting: the offset and symbol index */
+ for (i = 0, n = 0; i < di->nr_entries; i++) {
+ GElf_Rela rela;
+ u32 sym_idx;
+
+ gelf_getrela(di->rela_dyn_data, i, &rela);
+ sym_idx = GELF_R_SYM(rela.r_info);
+ if (sym_idx) {
+ di->sorted[n].sym_idx = sym_idx;
+ di->sorted[n].offset = rela.r_offset;
+ n += 1;
+ }
+ }
+
+ /* Sort by offset */
+ di->nr_entries = n;
+ qsort(di->sorted, n, sizeof(di->sorted[0]), cmp_offset);
+
+ return 0;
+}
+
+static void get_rela_dyn_info(Elf *elf, GElf_Ehdr *ehdr, struct rela_dyn_info *di, Elf_Scn *scn)
+{
+ GElf_Shdr rela_dyn_shdr;
+ GElf_Shdr shdr;
+
+ di->plt_got_data = elf_getdata(scn, NULL);
+
+ scn = elf_section_by_name(elf, ehdr, &rela_dyn_shdr, ".rela.dyn", NULL);
+ if (!scn || !rela_dyn_shdr.sh_link || !rela_dyn_shdr.sh_entsize)
+ return;
+
+ di->nr_entries = rela_dyn_shdr.sh_size / rela_dyn_shdr.sh_entsize;
+ di->rela_dyn_data = elf_getdata(scn, NULL);
+
+ scn = elf_getscn(elf, rela_dyn_shdr.sh_link);
+ if (!scn || !gelf_getshdr(scn, &shdr) || !shdr.sh_link)
+ return;
+
+ di->dynsym_data = elf_getdata(scn, NULL);
+ di->dynstr_data = elf_getdata(elf_getscn(elf, shdr.sh_link), NULL);
+
+ if (!di->plt_got_data || !di->dynstr_data || !di->dynsym_data || !di->rela_dyn_data)
+ return;
+
+ /* Sort into offset order */
+ sort_rela_dyn(di);
+}
+
+/* Get instruction displacement from a plt entry for x86_64 */
+static u32 get_x86_64_plt_disp(const u8 *p)
+{
+ u8 endbr64[] = {0xf3, 0x0f, 0x1e, 0xfa};
+ int n = 0;
+
+ /* Skip endbr64 */
+ if (!memcmp(p, endbr64, sizeof(endbr64)))
+ n += sizeof(endbr64);
+ /* Skip bnd prefix */
+ if (p[n] == 0xf2)
+ n += 1;
+ /* jmp with 4-byte displacement */
+ if (p[n] == 0xff && p[n + 1] == 0x25) {
+ n += 2;
+ /* Also add offset from start of entry to end of instruction */
+ return n + 4 + le32toh(*(const u32 *)(p + n));
+ }
+ return 0;
+}
+
+static bool get_plt_got_name(GElf_Shdr *shdr, size_t i,
+ struct rela_dyn_info *di,
+ char *buf, size_t buf_sz)
+{
+ void *p = di->plt_got_data->d_buf + i;
+ u32 disp = get_x86_64_plt_disp(p);
+ struct rela_dyn vi, *vr;
+ const char *sym_name;
+ char *demangled;
+ GElf_Sym sym;
+
+ if (!di->sorted || !disp)
+ return false;
+
+ /* Compute target offset of the .plt.got entry */
+ vi.offset = shdr->sh_offset + di->plt_got_data->d_off + i + disp;
+
+ /* Find that offset in .rela.dyn (sorted by offset) */
+ vr = bsearch(&vi, di->sorted, di->nr_entries, sizeof(di->sorted[0]), cmp_offset);
+ if (!vr)
+ return false;
+
+ /* Get the associated symbol */
+ gelf_getsym(di->dynsym_data, vr->sym_idx, &sym);
+ sym_name = elf_sym__name(&sym, di->dynstr_data);
+ demangled = demangle_sym(di->dso, 0, sym_name);
+ if (demangled != NULL)
+ sym_name = demangled;
+
+ snprintf(buf, buf_sz, "%s@plt", sym_name);
+
+ free(demangled);
+
+ return *sym_name;
+}
+
static int dso__synthesize_plt_got_symbols(struct dso *dso, Elf *elf,
GElf_Ehdr *ehdr,
char *buf, size_t buf_sz)
{
+ struct rela_dyn_info di = { .dso = dso };
struct symbol *sym;
GElf_Shdr shdr;
Elf_Scn *scn;
+ int err = -1;
size_t i;
scn = elf_section_by_name(elf, ehdr, &shdr, ".plt.got", NULL);
if (!scn || !shdr.sh_entsize)
return 0;
+ if (ehdr->e_machine == EM_X86_64)
+ get_rela_dyn_info(elf, ehdr, &di, scn);
+
for (i = 0; i < shdr.sh_size; i += shdr.sh_entsize) {
- snprintf(buf, buf_sz, "offset_%#zx@plt", shdr.sh_offset + i);
+ if (!get_plt_got_name(&shdr, i, &di, buf, buf_sz))
+ snprintf(buf, buf_sz, "offset_%#zx@plt", shdr.sh_offset + i);
sym = symbol__new(shdr.sh_offset + i, shdr.sh_entsize, STB_GLOBAL, STT_FUNC, buf);
if (!sym)
- return -1;
+ goto out;
symbols__insert(&dso->symbols, sym);
}
-
- return 0;
+ err = 0;
+out:
+ exit_rela_dyn(&di);
+ return err;
}
/*
--
2.34.1
Hi Adrian,
On Fri, Jan 27, 2023 at 9:02 AM Adrian Hunter <[email protected]> wrote:
>
> The section .plt.sec was originally added for MPX and was first called
> .plt.bnd. While MPX has been deprecated, .plt.sec is now also used for IBT.
> On x86_64, IBT seems to be enabled by default, but can be switched off
> using gcc option -fcf-protection=none. On 32-bit, option -z ibt will
> enable IBT.
>
> With .plt.sec, calls are made into .plt.sec instead of .plt, so it
> makes more sense to put the symbols there instead of .plt. A notable
> difference is that .plt.sec does not have a header entry.
>
> For x86, when synthesizing symbols for plt, use offset and entry size of
> .plt.sec instead of .plt when there is a .plt.sec section.
>
> Example on Ubuntu 22.04 gcc 11.3:
>
> Before:
>
> $ cat tstpltlib.c
> void fn1(void) {}
> void fn2(void) {}
> void fn3(void) {}
> void fn4(void) {}
> $ cat tstplt.c
> void fn1(void);
> void fn2(void);
> void fn3(void);
> void fn4(void);
>
> int main()
> {
> fn4();
> fn1();
> fn2();
> fn3();
> return 0;
> }
> $ gcc --version
> gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
> Copyright (C) 2021 Free Software Foundation, Inc.
> This is free software; see the source for copying conditions. There is NO
> warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> $ gcc -Wall -Wextra -shared -o libtstpltlib.so tstpltlib.c
> $ gcc -Wall -Wextra -o tstplt tstplt.c -L . -ltstpltlib -Wl,-rpath=$(pwd)
> $ readelf -SW tstplt | grep 'plt\|Name'
> [Nr] Name Type Address Off Size ES Flg Lk Inf Al
> [11] .rela.plt RELA 0000000000000698 000698 000060 18 AI 6 24 8
> [13] .plt PROGBITS 0000000000001020 001020 000050 10 AX 0 0 16
> [14] .plt.got PROGBITS 0000000000001070 001070 000010 10 AX 0 0 16
> [15] .plt.sec PROGBITS 0000000000001080 001080 000040 10 AX 0 0 16
On my machine, it's not enabled by default. And it doesn't create .plt.sec
even if I pass -fcf-protection=full option.
$ gcc --version
gcc (Debian 12.2.0-10) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc -Wall -Wextra -shared -fcf-protection=full -o libtstplt.so tstpltlib.c
$ gcc -Wall -Wextra -fcf-protection=full -o tstplt tstplt.c -L.
-ltstpltlib -Wl,-rpath,$(pwd)
$ readelf -SW tstplt | grep 'plt\|Name'
[Nr] Name Type Address Off Size
ES Flg Lk Inf Al
[11] .rela.plt RELA 0000000000000688 000688
000060 18 AI 6 24 8
[13] .plt PROGBITS 0000000000001020 001020
000050 10 AX 0 0 16
[14] .plt.got PROGBITS 0000000000001070 001070
000008 08 AX 0 0 8
[24] .got.plt PROGBITS 0000000000003fe8 002fe8
000038 08 WA 0 0 8
Thanks,
Namhyung
On 30/01/23 19:34, Namhyung Kim wrote:
> Hi Adrian,
>
> On Fri, Jan 27, 2023 at 9:02 AM Adrian Hunter <[email protected]> wrote:
>>
>> The section .plt.sec was originally added for MPX and was first called
>> .plt.bnd. While MPX has been deprecated, .plt.sec is now also used for IBT.
>> On x86_64, IBT seems to be enabled by default, but can be switched off
>> using gcc option -fcf-protection=none. On 32-bit, option -z ibt will
>> enable IBT.
>>
>> With .plt.sec, calls are made into .plt.sec instead of .plt, so it
>> makes more sense to put the symbols there instead of .plt. A notable
>> difference is that .plt.sec does not have a header entry.
>>
>> For x86, when synthesizing symbols for plt, use offset and entry size of
>> .plt.sec instead of .plt when there is a .plt.sec section.
>>
>> Example on Ubuntu 22.04 gcc 11.3:
>>
>> Before:
>>
>> $ cat tstpltlib.c
>> void fn1(void) {}
>> void fn2(void) {}
>> void fn3(void) {}
>> void fn4(void) {}
>> $ cat tstplt.c
>> void fn1(void);
>> void fn2(void);
>> void fn3(void);
>> void fn4(void);
>>
>> int main()
>> {
>> fn4();
>> fn1();
>> fn2();
>> fn3();
>> return 0;
>> }
>> $ gcc --version
>> gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
>> Copyright (C) 2021 Free Software Foundation, Inc.
>> This is free software; see the source for copying conditions. There is NO
>> warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
>> $ gcc -Wall -Wextra -shared -o libtstpltlib.so tstpltlib.c
>> $ gcc -Wall -Wextra -o tstplt tstplt.c -L . -ltstpltlib -Wl,-rpath=$(pwd)
>> $ readelf -SW tstplt | grep 'plt\|Name'
>> [Nr] Name Type Address Off Size ES Flg Lk Inf Al
>> [11] .rela.plt RELA 0000000000000698 000698 000060 18 AI 6 24 8
>> [13] .plt PROGBITS 0000000000001020 001020 000050 10 AX 0 0 16
>> [14] .plt.got PROGBITS 0000000000001070 001070 000010 10 AX 0 0 16
>> [15] .plt.sec PROGBITS 0000000000001080 001080 000040 10 AX 0 0 16
>
> On my machine, it's not enabled by default. And it doesn't create .plt.sec
> even if I pass -fcf-protection=full option.
>
> $ gcc --version
> gcc (Debian 12.2.0-10) 12.2.0
> Copyright (C) 2022 Free Software Foundation, Inc.
> This is free software; see the source for copying conditions. There is NO
> warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
>
> $ gcc -Wall -Wextra -shared -fcf-protection=full -o libtstplt.so tstpltlib.c
> $ gcc -Wall -Wextra -fcf-protection=full -o tstplt tstplt.c -L.
> -ltstpltlib -Wl,-rpath,$(pwd)
> $ readelf -SW tstplt | grep 'plt\|Name'
> [Nr] Name Type Address Off Size
> ES Flg Lk Inf Al
> [11] .rela.plt RELA 0000000000000688 000688
> 000060 18 AI 6 24 8
> [13] .plt PROGBITS 0000000000001020 001020
> 000050 10 AX 0 0 16
> [14] .plt.got PROGBITS 0000000000001070 001070
> 000008 08 AX 0 0 8
> [24] .got.plt PROGBITS 0000000000003fe8 002fe8
> 000038 08 WA 0 0 8
That is interesting. What does it say with -v i.e.
gcc -v -Wall -Wextra -o tstplt tstplt.c -L . -ltstpltlib -Wl,-rpath=$(pwd)
And what is the distribution?
On Mon, Jan 30, 2023 at 10:35 AM Adrian Hunter <[email protected]> wrote:
>
> On 30/01/23 19:34, Namhyung Kim wrote:
> > Hi Adrian,
> >
> > On Fri, Jan 27, 2023 at 9:02 AM Adrian Hunter <[email protected]> wrote:
> >>
> >> The section .plt.sec was originally added for MPX and was first called
> >> .plt.bnd. While MPX has been deprecated, .plt.sec is now also used for IBT.
> >> On x86_64, IBT seems to be enabled by default, but can be switched off
> >> using gcc option -fcf-protection=none. On 32-bit, option -z ibt will
> >> enable IBT.
> >>
> >> With .plt.sec, calls are made into .plt.sec instead of .plt, so it
> >> makes more sense to put the symbols there instead of .plt. A notable
> >> difference is that .plt.sec does not have a header entry.
> >>
> >> For x86, when synthesizing symbols for plt, use offset and entry size of
> >> .plt.sec instead of .plt when there is a .plt.sec section.
> >>
> >> Example on Ubuntu 22.04 gcc 11.3:
> >>
> >> Before:
> >>
> >> $ cat tstpltlib.c
> >> void fn1(void) {}
> >> void fn2(void) {}
> >> void fn3(void) {}
> >> void fn4(void) {}
> >> $ cat tstplt.c
> >> void fn1(void);
> >> void fn2(void);
> >> void fn3(void);
> >> void fn4(void);
> >>
> >> int main()
> >> {
> >> fn4();
> >> fn1();
> >> fn2();
> >> fn3();
> >> return 0;
> >> }
> >> $ gcc --version
> >> gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
> >> Copyright (C) 2021 Free Software Foundation, Inc.
> >> This is free software; see the source for copying conditions. There is NO
> >> warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> >> $ gcc -Wall -Wextra -shared -o libtstpltlib.so tstpltlib.c
> >> $ gcc -Wall -Wextra -o tstplt tstplt.c -L . -ltstpltlib -Wl,-rpath=$(pwd)
> >> $ readelf -SW tstplt | grep 'plt\|Name'
> >> [Nr] Name Type Address Off Size ES Flg Lk Inf Al
> >> [11] .rela.plt RELA 0000000000000698 000698 000060 18 AI 6 24 8
> >> [13] .plt PROGBITS 0000000000001020 001020 000050 10 AX 0 0 16
> >> [14] .plt.got PROGBITS 0000000000001070 001070 000010 10 AX 0 0 16
> >> [15] .plt.sec PROGBITS 0000000000001080 001080 000040 10 AX 0 0 16
> >
> > On my machine, it's not enabled by default. And it doesn't create .plt.sec
> > even if I pass -fcf-protection=full option.
> >
> > $ gcc --version
> > gcc (Debian 12.2.0-10) 12.2.0
> > Copyright (C) 2022 Free Software Foundation, Inc.
> > This is free software; see the source for copying conditions. There is NO
> > warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> >
> > $ gcc -Wall -Wextra -shared -fcf-protection=full -o libtstplt.so tstpltlib.c
> > $ gcc -Wall -Wextra -fcf-protection=full -o tstplt tstplt.c -L.
> > -ltstpltlib -Wl,-rpath,$(pwd)
> > $ readelf -SW tstplt | grep 'plt\|Name'
> > [Nr] Name Type Address Off Size
> > ES Flg Lk Inf Al
> > [11] .rela.plt RELA 0000000000000688 000688
> > 000060 18 AI 6 24 8
> > [13] .plt PROGBITS 0000000000001020 001020
> > 000050 10 AX 0 0 16
> > [14] .plt.got PROGBITS 0000000000001070 001070
> > 000008 08 AX 0 0 8
> > [24] .got.plt PROGBITS 0000000000003fe8 002fe8
> > 000038 08 WA 0 0 8
>
> That is interesting. What does it say with -v i.e.
>
> gcc -v -Wall -Wextra -o tstplt tstplt.c -L . -ltstpltlib -Wl,-rpath=$(pwd)
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/12/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian
12.2.0-10' --with-bugurl=file:///usr/share/doc/gcc-12/README.Bugs
--enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2
--prefix=/usr --with-gcc-major-version-only --program-suffix=-12
--program-prefix=x86_64-linux-gnu- --enable-shared
--enable-linker-build-id --libexecdir=/usr/lib
--without-included-gettext --enable-threads=posix --libdir=/usr/lib
--enable-nls --enable-clocale=gnu --enable-libstdcxx-debug
--enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new
--enable-gnu-unique-object --disable-vtable-verify --enable-plugin
--enable-default-pie --with-system-zlib
--enable-libphobos-checking=release --with-target-system-zlib=auto
--enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet
--with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32
--enable-multilib --with-tune=generic
--enable-offload-targets=nvptx-none=/build/gcc-12-hWCYKv/gcc-12-12.2.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-12-hWCYKv/gcc-12-12.2.0/debian/tmp-gcn/usr
--enable-offload-defaulted --without-cuda-driver
--enable-checking=release --build=x86_64-linux-gnu
--host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 12.2.0 (Debian 12.2.0-10)
COLLECT_GCC_OPTIONS='-Wall' '-Wextra' '-fcf-protection=full' '-v' '-o'
'tstplt' '-L.' '-mtune=generic' '-march=x86-64' '-dumpdir' 'tstplt-'
/usr/lib/gcc/x86_64-linux-gnu/12/cc1 -quiet -v -imultiarch
x86_64-linux-gnu tstplt.c -quiet -dumpdir tstplt- -dumpbase tstplt.c
-dumpbase-ext .c -mtune=generic -march=x86-64 -Wall -Wextra -version
-fcf-protection=full -fasynchronous-unwind-tables -o /tmp/ccKPWeTD.s
GNU C17 (Debian 12.2.0-10) version 12.2.0 (x86_64-linux-gnu)
compiled by GNU C version 12.2.0, GMP version 6.2.1, MPFR version
4.1.0, MPC version 1.2.1, isl version isl-0.25-GMP
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/12/include-fixed"
ignoring nonexistent directory
"/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/12/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
GNU C17 (Debian 12.2.0-10) version 12.2.0 (x86_64-linux-gnu)
compiled by GNU C version 12.2.0, GMP version 6.2.1, MPFR version
4.1.0, MPC version 1.2.1, isl version isl-0.25-GMP
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 0bf64a455b69fb48d1b44a013a099136
COLLECT_GCC_OPTIONS='-Wall' '-Wextra' '-fcf-protection=full' '-v' '-o'
'tstplt' '-L.' '-mtune=generic' '-march=x86-64' '-dumpdir' 'tstplt-'
as -v --64 -o /tmp/cc0IMyNr.o /tmp/ccKPWeTD.s
GNU assembler version 2.39.50 (x86_64-linux-gnu) using BFD version
(GNU Binutils for Debian) 2.39.50.20221208
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/12/:/usr/lib/gcc/x86_64-linux-gnu/12/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/12/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/12/:/usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/12/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/12/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-Wall' '-Wextra' '-fcf-protection=full' '-v' '-o'
'tstplt' '-L.' '-mtune=generic' '-march=x86-64' '-dumpdir' 'tstplt.'
/usr/lib/gcc/x86_64-linux-gnu/12/collect2 -plugin
/usr/lib/gcc/x86_64-linux-gnu/12/liblto_plugin.so
-plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/12/lto-wrapper
-plugin-opt=-fresolution=/tmp/ccU2c2jz.res
-plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s
-plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc
-plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m
elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker
/lib64/ld-linux-x86-64.so.2 -pie -o tstplt
/usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o -L.
-L/usr/lib/gcc/x86_64-linux-gnu/12
-L/usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu
-L/usr/lib/gcc/x86_64-linux-gnu/12/../../../../lib
-L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu
-L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/12/../../..
/tmp/cc0IMyNr.o -ltstpltlib -rpath=/home/namhyung/tmp/plt-test -lgcc
--push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state
--as-needed -lgcc_s --pop-state
/usr/lib/gcc/x86_64-linux-gnu/12/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/crtn.o
COLLECT_GCC_OPTIONS='-Wall' '-Wextra' '-fcf-protection=full' '-v' '-o'
'tstplt' '-L.' '-mtune=generic' '-march=x86-64' '-dumpdir' 'tstplt.'
>
> And what is the distribution?
It's a Debian (Testing) with some customization.
Thanks,
Namhyung
On Fri, Jan 27, 2023 at 07:02:22PM +0200, Adrian Hunter wrote:
> For x86_64, determine a symbol for .plt.got entries. That requires
> computing the target offset and finding that in .rela.dyn, which in
> turn means .rela.dyn needs to be sorted by offset.
>
> Example:
>
> In this example, the GNU C Library is using .plt.got for malloc and
> free.
>
> Before:
>
> $ gcc --version
> gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
> Copyright (C) 2021 Free Software Foundation, Inc.
> This is free software; see the source for copying conditions. There is NO
> warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> $ perf record -e intel_pt//u uname
> Linux
> [ perf record: Woken up 1 times to write data ]
> [ perf record: Captured and wrote 0.027 MB perf.data ]
> $ perf script --itrace=be --ns -F-event,+addr,-period,-comm,-tid,-cpu > /tmp/cmp1.txt
>
> After:
>
> $ perf script --itrace=be --ns -F-event,+addr,-period,-comm,-tid,-cpu > /tmp/cmp2.txt
> $ diff /tmp/cmp1.txt /tmp/cmp2.txt | head -12
> 15509,15510c15509,15510
> < 27046.755390907: 7f0b2943e3ab _nl_normalize_codeset+0x5b (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428380 offset_0x28380@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
> < 27046.755390907: 7f0b29428384 offset_0x28380@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5120 malloc+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
> ---
> > 27046.755390907: 7f0b2943e3ab _nl_normalize_codeset+0x5b (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428380 malloc@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
> > 27046.755390907: 7f0b29428384 malloc@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5120 malloc+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
> 15821,15822c15821,15822
> < 27046.755394865: 7f0b2943850c _nl_load_locale_from_archive+0x5bc (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428370 offset_0x28370@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
> < 27046.755394865: 7f0b29428374 offset_0x28370@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5460 cfree@GLIBC_2.2.5+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
> ---
> > 27046.755394865: 7f0b2943850c _nl_load_locale_from_archive+0x5bc (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428370 free@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
> > 27046.755394865: 7f0b29428374 free@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5460 cfree@GLIBC_2.2.5+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
>
> Signed-off-by: Adrian Hunter <[email protected]>
> ---
> tools/perf/util/symbol-elf.c | 158 ++++++++++++++++++++++++++++++++++-
> 1 file changed, 154 insertions(+), 4 deletions(-)
>
> diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
> index 254116d40e59..4fc8e7fc10f4 100644
> --- a/tools/perf/util/symbol-elf.c
> +++ b/tools/perf/util/symbol-elf.c
> @@ -466,28 +466,178 @@ static bool machine_is_x86(GElf_Half e_machine)
> return e_machine == EM_386 || e_machine == EM_X86_64;
> }
>
> +struct rela_dyn {
> + GElf_Addr offset;
> + u32 sym_idx;
> +};
> +
> +struct rela_dyn_info {
> + struct dso *dso;
> + Elf_Data *plt_got_data;
> + u32 nr_entries;
> + struct rela_dyn *sorted;
> + Elf_Data *dynsym_data;
> + Elf_Data *dynstr_data;
> + Elf_Data *rela_dyn_data;
> +};
> +
> +static void exit_rela_dyn(struct rela_dyn_info *di)
> +{
> + free(di->sorted);
> +}
> +
> +static int cmp_offset(const void *a, const void *b)
> +{
> + const struct rela_dyn *va = a;
> + const struct rela_dyn *vb = b;
> +
> + return va->offset < vb->offset ? -1 : (va->offset > vb->offset ? 1 : 0);
> +}
> +
> +static int sort_rela_dyn(struct rela_dyn_info *di)
> +{
> + u32 i, n;
> +
> + di->sorted = calloc(di->nr_entries, sizeof(di->sorted[0]));
> + if (!di->sorted)
> + return -1;
> +
> + /* Get data for sorting: the offset and symbol index */
> + for (i = 0, n = 0; i < di->nr_entries; i++) {
> + GElf_Rela rela;
> + u32 sym_idx;
> +
> + gelf_getrela(di->rela_dyn_data, i, &rela);
> + sym_idx = GELF_R_SYM(rela.r_info);
> + if (sym_idx) {
> + di->sorted[n].sym_idx = sym_idx;
> + di->sorted[n].offset = rela.r_offset;
> + n += 1;
> + }
> + }
> +
> + /* Sort by offset */
> + di->nr_entries = n;
> + qsort(di->sorted, n, sizeof(di->sorted[0]), cmp_offset);
> +
> + return 0;
> +}
> +
> +static void get_rela_dyn_info(Elf *elf, GElf_Ehdr *ehdr, struct rela_dyn_info *di, Elf_Scn *scn)
> +{
> + GElf_Shdr rela_dyn_shdr;
> + GElf_Shdr shdr;
> +
> + di->plt_got_data = elf_getdata(scn, NULL);
> +
> + scn = elf_section_by_name(elf, ehdr, &rela_dyn_shdr, ".rela.dyn", NULL);
> + if (!scn || !rela_dyn_shdr.sh_link || !rela_dyn_shdr.sh_entsize)
> + return;
> +
> + di->nr_entries = rela_dyn_shdr.sh_size / rela_dyn_shdr.sh_entsize;
> + di->rela_dyn_data = elf_getdata(scn, NULL);
> +
> + scn = elf_getscn(elf, rela_dyn_shdr.sh_link);
> + if (!scn || !gelf_getshdr(scn, &shdr) || !shdr.sh_link)
> + return;
> +
> + di->dynsym_data = elf_getdata(scn, NULL);
> + di->dynstr_data = elf_getdata(elf_getscn(elf, shdr.sh_link), NULL);
> +
> + if (!di->plt_got_data || !di->dynstr_data || !di->dynsym_data || !di->rela_dyn_data)
> + return;
> +
> + /* Sort into offset order */
> + sort_rela_dyn(di);
> +}
> +
> +/* Get instruction displacement from a plt entry for x86_64 */
> +static u32 get_x86_64_plt_disp(const u8 *p)
> +{
> + u8 endbr64[] = {0xf3, 0x0f, 0x1e, 0xfa};
> + int n = 0;
> +
> + /* Skip endbr64 */
> + if (!memcmp(p, endbr64, sizeof(endbr64)))
> + n += sizeof(endbr64);
> + /* Skip bnd prefix */
> + if (p[n] == 0xf2)
> + n += 1;
> + /* jmp with 4-byte displacement */
> + if (p[n] == 0xff && p[n + 1] == 0x25) {
> + n += 2;
> + /* Also add offset from start of entry to end of instruction */
> + return n + 4 + le32toh(*(const u32 *)(p + n));
> + }
> + return 0;
> +}
> +
> +static bool get_plt_got_name(GElf_Shdr *shdr, size_t i,
> + struct rela_dyn_info *di,
> + char *buf, size_t buf_sz)
> +{
> + void *p = di->plt_got_data->d_buf + i;
> + u32 disp = get_x86_64_plt_disp(p);
> + struct rela_dyn vi, *vr;
> + const char *sym_name;
> + char *demangled;
> + GElf_Sym sym;
> +
> + if (!di->sorted || !disp)
> + return false;
> +
> + /* Compute target offset of the .plt.got entry */
> + vi.offset = shdr->sh_offset + di->plt_got_data->d_off + i + disp;
> +
> + /* Find that offset in .rela.dyn (sorted by offset) */
> + vr = bsearch(&vi, di->sorted, di->nr_entries, sizeof(di->sorted[0]), cmp_offset);
> + if (!vr)
> + return false;
> +
> + /* Get the associated symbol */
> + gelf_getsym(di->dynsym_data, vr->sym_idx, &sym);
> + sym_name = elf_sym__name(&sym, di->dynstr_data);
> + demangled = demangle_sym(di->dso, 0, sym_name);
> + if (demangled != NULL)
> + sym_name = demangled;
> +
> + snprintf(buf, buf_sz, "%s@plt", sym_name);
> +
> + free(demangled);
> +
> + return *sym_name;
> +}
> +
> static int dso__synthesize_plt_got_symbols(struct dso *dso, Elf *elf,
> GElf_Ehdr *ehdr,
> char *buf, size_t buf_sz)
> {
> + struct rela_dyn_info di = { .dso = dso };
> struct symbol *sym;
> GElf_Shdr shdr;
> Elf_Scn *scn;
> + int err = -1;
> size_t i;
>
> scn = elf_section_by_name(elf, ehdr, &shdr, ".plt.got", NULL);
> if (!scn || !shdr.sh_entsize)
> return 0;
>
> + if (ehdr->e_machine == EM_X86_64)
> + get_rela_dyn_info(elf, ehdr, &di, scn);
What about EM_386? Now I'm seeing segfaults on 32 bit test programs
with .plt.got section.
Thanks,
Namhyung
> +
> for (i = 0; i < shdr.sh_size; i += shdr.sh_entsize) {
> - snprintf(buf, buf_sz, "offset_%#zx@plt", shdr.sh_offset + i);
> + if (!get_plt_got_name(&shdr, i, &di, buf, buf_sz))
> + snprintf(buf, buf_sz, "offset_%#zx@plt", shdr.sh_offset + i);
> sym = symbol__new(shdr.sh_offset + i, shdr.sh_entsize, STB_GLOBAL, STT_FUNC, buf);
> if (!sym)
> - return -1;
> + goto out;
> symbols__insert(&dso->symbols, sym);
> }
> -
> - return 0;
> + err = 0;
> +out:
> + exit_rela_dyn(&di);
> + return err;
> }
>
> /*
> --
> 2.34.1
>
>
On 31/01/23 00:22, Namhyung Kim wrote:
> On Mon, Jan 30, 2023 at 10:35 AM Adrian Hunter <[email protected]> wrote:
>>
>> On 30/01/23 19:34, Namhyung Kim wrote:
>>> Hi Adrian,
>>>
>>> On Fri, Jan 27, 2023 at 9:02 AM Adrian Hunter <[email protected]> wrote:
>>>>
>>>> The section .plt.sec was originally added for MPX and was first called
>>>> .plt.bnd. While MPX has been deprecated, .plt.sec is now also used for IBT.
>>>> On x86_64, IBT seems to be enabled by default, but can be switched off
>>>> using gcc option -fcf-protection=none. On 32-bit, option -z ibt will
>>>> enable IBT.
>>>>
>>>> With .plt.sec, calls are made into .plt.sec instead of .plt, so it
>>>> makes more sense to put the symbols there instead of .plt. A notable
>>>> difference is that .plt.sec does not have a header entry.
>>>>
>>>> For x86, when synthesizing symbols for plt, use offset and entry size of
>>>> .plt.sec instead of .plt when there is a .plt.sec section.
>>>>
>>>> Example on Ubuntu 22.04 gcc 11.3:
>>>>
>>>> Before:
>>>>
>>>> $ cat tstpltlib.c
>>>> void fn1(void) {}
>>>> void fn2(void) {}
>>>> void fn3(void) {}
>>>> void fn4(void) {}
>>>> $ cat tstplt.c
>>>> void fn1(void);
>>>> void fn2(void);
>>>> void fn3(void);
>>>> void fn4(void);
>>>>
>>>> int main()
>>>> {
>>>> fn4();
>>>> fn1();
>>>> fn2();
>>>> fn3();
>>>> return 0;
>>>> }
>>>> $ gcc --version
>>>> gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
>>>> Copyright (C) 2021 Free Software Foundation, Inc.
>>>> This is free software; see the source for copying conditions. There is NO
>>>> warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
>>>> $ gcc -Wall -Wextra -shared -o libtstpltlib.so tstpltlib.c
>>>> $ gcc -Wall -Wextra -o tstplt tstplt.c -L . -ltstpltlib -Wl,-rpath=$(pwd)
>>>> $ readelf -SW tstplt | grep 'plt\|Name'
>>>> [Nr] Name Type Address Off Size ES Flg Lk Inf Al
>>>> [11] .rela.plt RELA 0000000000000698 000698 000060 18 AI 6 24 8
>>>> [13] .plt PROGBITS 0000000000001020 001020 000050 10 AX 0 0 16
>>>> [14] .plt.got PROGBITS 0000000000001070 001070 000010 10 AX 0 0 16
>>>> [15] .plt.sec PROGBITS 0000000000001080 001080 000040 10 AX 0 0 16
>>>
>>> On my machine, it's not enabled by default. And it doesn't create .plt.sec
>>> even if I pass -fcf-protection=full option.
>>>
>>> $ gcc --version
>>> gcc (Debian 12.2.0-10) 12.2.0
>>> Copyright (C) 2022 Free Software Foundation, Inc.
>>> This is free software; see the source for copying conditions. There is NO
>>> warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
>>>
>>> $ gcc -Wall -Wextra -shared -fcf-protection=full -o libtstplt.so tstpltlib.c
>>> $ gcc -Wall -Wextra -fcf-protection=full -o tstplt tstplt.c -L.
>>> -ltstpltlib -Wl,-rpath,$(pwd)
>>> $ readelf -SW tstplt | grep 'plt\|Name'
>>> [Nr] Name Type Address Off Size
>>> ES Flg Lk Inf Al
>>> [11] .rela.plt RELA 0000000000000688 000688
>>> 000060 18 AI 6 24 8
>>> [13] .plt PROGBITS 0000000000001020 001020
>>> 000050 10 AX 0 0 16
>>> [14] .plt.got PROGBITS 0000000000001070 001070
>>> 000008 08 AX 0 0 8
>>> [24] .got.plt PROGBITS 0000000000003fe8 002fe8
>>> 000038 08 WA 0 0 8
>>
>> That is interesting. What does it say with -v i.e.
>>
>> gcc -v -Wall -Wextra -o tstplt tstplt.c -L . -ltstpltlib -Wl,-rpath=$(pwd)
>
> Using built-in specs.
> COLLECT_GCC=gcc
> COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/12/lto-wrapper
> OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
> OFFLOAD_TARGET_DEFAULT=1
> Target: x86_64-linux-gnu
> Configured with: ../src/configure -v --with-pkgversion='Debian
> 12.2.0-10' --with-bugurl=file:///usr/share/doc/gcc-12/README.Bugs
> --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2
> --prefix=/usr --with-gcc-major-version-only --program-suffix=-12
> --program-prefix=x86_64-linux-gnu- --enable-shared
> --enable-linker-build-id --libexecdir=/usr/lib
> --without-included-gettext --enable-threads=posix --libdir=/usr/lib
> --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug
> --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new
> --enable-gnu-unique-object --disable-vtable-verify --enable-plugin
> --enable-default-pie --with-system-zlib
> --enable-libphobos-checking=release --with-target-system-zlib=auto
> --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet
> --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32
> --enable-multilib --with-tune=generic
> --enable-offload-targets=nvptx-none=/build/gcc-12-hWCYKv/gcc-12-12.2.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-12-hWCYKv/gcc-12-12.2.0/debian/tmp-gcn/usr
> --enable-offload-defaulted --without-cuda-driver
> --enable-checking=release --build=x86_64-linux-gnu
> --host=x86_64-linux-gnu --target=x86_64-linux-gnu
> Thread model: posix
> Supported LTO compression algorithms: zlib zstd
> gcc version 12.2.0 (Debian 12.2.0-10)
> COLLECT_GCC_OPTIONS='-Wall' '-Wextra' '-fcf-protection=full' '-v' '-o'
> 'tstplt' '-L.' '-mtune=generic' '-march=x86-64' '-dumpdir' 'tstplt-'
> /usr/lib/gcc/x86_64-linux-gnu/12/cc1 -quiet -v -imultiarch
> x86_64-linux-gnu tstplt.c -quiet -dumpdir tstplt- -dumpbase tstplt.c
> -dumpbase-ext .c -mtune=generic -march=x86-64 -Wall -Wextra -version
> -fcf-protection=full -fasynchronous-unwind-tables -o /tmp/ccKPWeTD.s
> GNU C17 (Debian 12.2.0-10) version 12.2.0 (x86_64-linux-gnu)
> compiled by GNU C version 12.2.0, GMP version 6.2.1, MPFR version
> 4.1.0, MPC version 1.2.1, isl version isl-0.25-GMP
>
> GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
> ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
> ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/12/include-fixed"
> ignoring nonexistent directory
> "/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
> #include "..." search starts here:
> #include <...> search starts here:
> /usr/lib/gcc/x86_64-linux-gnu/12/include
> /usr/local/include
> /usr/include/x86_64-linux-gnu
> /usr/include
> End of search list.
> GNU C17 (Debian 12.2.0-10) version 12.2.0 (x86_64-linux-gnu)
> compiled by GNU C version 12.2.0, GMP version 6.2.1, MPFR version
> 4.1.0, MPC version 1.2.1, isl version isl-0.25-GMP
>
> GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
> Compiler executable checksum: 0bf64a455b69fb48d1b44a013a099136
> COLLECT_GCC_OPTIONS='-Wall' '-Wextra' '-fcf-protection=full' '-v' '-o'
> 'tstplt' '-L.' '-mtune=generic' '-march=x86-64' '-dumpdir' 'tstplt-'
> as -v --64 -o /tmp/cc0IMyNr.o /tmp/ccKPWeTD.s
> GNU assembler version 2.39.50 (x86_64-linux-gnu) using BFD version
> (GNU Binutils for Debian) 2.39.50.20221208
> COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/12/:/usr/lib/gcc/x86_64-linux-gnu/12/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/12/:/usr/lib/gcc/x86_64-linux-gnu/
> LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/12/:/usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/12/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/12/../../../:/lib/:/usr/lib/
> COLLECT_GCC_OPTIONS='-Wall' '-Wextra' '-fcf-protection=full' '-v' '-o'
> 'tstplt' '-L.' '-mtune=generic' '-march=x86-64' '-dumpdir' 'tstplt.'
> /usr/lib/gcc/x86_64-linux-gnu/12/collect2 -plugin
> /usr/lib/gcc/x86_64-linux-gnu/12/liblto_plugin.so
> -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/12/lto-wrapper
> -plugin-opt=-fresolution=/tmp/ccU2c2jz.res
> -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s
> -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc
> -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m
> elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker
> /lib64/ld-linux-x86-64.so.2 -pie -o tstplt
> /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o
> /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/crti.o
> /usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o -L.
> -L/usr/lib/gcc/x86_64-linux-gnu/12
> -L/usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu
> -L/usr/lib/gcc/x86_64-linux-gnu/12/../../../../lib
> -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu
> -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/12/../../..
> /tmp/cc0IMyNr.o -ltstpltlib -rpath=/home/namhyung/tmp/plt-test -lgcc
> --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state
> --as-needed -lgcc_s --pop-state
> /usr/lib/gcc/x86_64-linux-gnu/12/crtendS.o
> /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/crtn.o
> COLLECT_GCC_OPTIONS='-Wall' '-Wextra' '-fcf-protection=full' '-v' '-o'
> 'tstplt' '-L.' '-mtune=generic' '-march=x86-64' '-dumpdir' 'tstplt.'
>
>
>>
>> And what is the distribution?
>
> It's a Debian (Testing) with some customization.
Seems to need also options -z ibt and/or -z ibtplt
I will send a V2 with updated examples.
On 31/01/23 01:26, Namhyung Kim wrote:
> On Fri, Jan 27, 2023 at 07:02:22PM +0200, Adrian Hunter wrote:
>> For x86_64, determine a symbol for .plt.got entries. That requires
>> computing the target offset and finding that in .rela.dyn, which in
>> turn means .rela.dyn needs to be sorted by offset.
>>
>> Example:
>>
>> In this example, the GNU C Library is using .plt.got for malloc and
>> free.
>>
>> Before:
>>
>> $ gcc --version
>> gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
>> Copyright (C) 2021 Free Software Foundation, Inc.
>> This is free software; see the source for copying conditions. There is NO
>> warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
>> $ perf record -e intel_pt//u uname
>> Linux
>> [ perf record: Woken up 1 times to write data ]
>> [ perf record: Captured and wrote 0.027 MB perf.data ]
>> $ perf script --itrace=be --ns -F-event,+addr,-period,-comm,-tid,-cpu > /tmp/cmp1.txt
>>
>> After:
>>
>> $ perf script --itrace=be --ns -F-event,+addr,-period,-comm,-tid,-cpu > /tmp/cmp2.txt
>> $ diff /tmp/cmp1.txt /tmp/cmp2.txt | head -12
>> 15509,15510c15509,15510
>> < 27046.755390907: 7f0b2943e3ab _nl_normalize_codeset+0x5b (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428380 offset_0x28380@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
>> < 27046.755390907: 7f0b29428384 offset_0x28380@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5120 malloc+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
>> ---
>> > 27046.755390907: 7f0b2943e3ab _nl_normalize_codeset+0x5b (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428380 malloc@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
>> > 27046.755390907: 7f0b29428384 malloc@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5120 malloc+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
>> 15821,15822c15821,15822
>> < 27046.755394865: 7f0b2943850c _nl_load_locale_from_archive+0x5bc (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428370 offset_0x28370@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
>> < 27046.755394865: 7f0b29428374 offset_0x28370@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5460 cfree@GLIBC_2.2.5+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
>> ---
>> > 27046.755394865: 7f0b2943850c _nl_load_locale_from_archive+0x5bc (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b29428370 free@plt+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
>> > 27046.755394865: 7f0b29428374 free@plt+0x4 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 7f0b294a5460 cfree@GLIBC_2.2.5+0x0 (/usr/lib/x86_64-linux-gnu/libc.so.6)
>>
>> Signed-off-by: Adrian Hunter <[email protected]>
>> ---
>> tools/perf/util/symbol-elf.c | 158 ++++++++++++++++++++++++++++++++++-
>> 1 file changed, 154 insertions(+), 4 deletions(-)
>>
>> diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
>> index 254116d40e59..4fc8e7fc10f4 100644
>> --- a/tools/perf/util/symbol-elf.c
>> +++ b/tools/perf/util/symbol-elf.c
>> @@ -466,28 +466,178 @@ static bool machine_is_x86(GElf_Half e_machine)
>> return e_machine == EM_386 || e_machine == EM_X86_64;
>> }
>>
>> +struct rela_dyn {
>> + GElf_Addr offset;
>> + u32 sym_idx;
>> +};
>> +
>> +struct rela_dyn_info {
>> + struct dso *dso;
>> + Elf_Data *plt_got_data;
>> + u32 nr_entries;
>> + struct rela_dyn *sorted;
>> + Elf_Data *dynsym_data;
>> + Elf_Data *dynstr_data;
>> + Elf_Data *rela_dyn_data;
>> +};
>> +
>> +static void exit_rela_dyn(struct rela_dyn_info *di)
>> +{
>> + free(di->sorted);
>> +}
>> +
>> +static int cmp_offset(const void *a, const void *b)
>> +{
>> + const struct rela_dyn *va = a;
>> + const struct rela_dyn *vb = b;
>> +
>> + return va->offset < vb->offset ? -1 : (va->offset > vb->offset ? 1 : 0);
>> +}
>> +
>> +static int sort_rela_dyn(struct rela_dyn_info *di)
>> +{
>> + u32 i, n;
>> +
>> + di->sorted = calloc(di->nr_entries, sizeof(di->sorted[0]));
>> + if (!di->sorted)
>> + return -1;
>> +
>> + /* Get data for sorting: the offset and symbol index */
>> + for (i = 0, n = 0; i < di->nr_entries; i++) {
>> + GElf_Rela rela;
>> + u32 sym_idx;
>> +
>> + gelf_getrela(di->rela_dyn_data, i, &rela);
>> + sym_idx = GELF_R_SYM(rela.r_info);
>> + if (sym_idx) {
>> + di->sorted[n].sym_idx = sym_idx;
>> + di->sorted[n].offset = rela.r_offset;
>> + n += 1;
>> + }
>> + }
>> +
>> + /* Sort by offset */
>> + di->nr_entries = n;
>> + qsort(di->sorted, n, sizeof(di->sorted[0]), cmp_offset);
>> +
>> + return 0;
>> +}
>> +
>> +static void get_rela_dyn_info(Elf *elf, GElf_Ehdr *ehdr, struct rela_dyn_info *di, Elf_Scn *scn)
>> +{
>> + GElf_Shdr rela_dyn_shdr;
>> + GElf_Shdr shdr;
>> +
>> + di->plt_got_data = elf_getdata(scn, NULL);
>> +
>> + scn = elf_section_by_name(elf, ehdr, &rela_dyn_shdr, ".rela.dyn", NULL);
>> + if (!scn || !rela_dyn_shdr.sh_link || !rela_dyn_shdr.sh_entsize)
>> + return;
>> +
>> + di->nr_entries = rela_dyn_shdr.sh_size / rela_dyn_shdr.sh_entsize;
>> + di->rela_dyn_data = elf_getdata(scn, NULL);
>> +
>> + scn = elf_getscn(elf, rela_dyn_shdr.sh_link);
>> + if (!scn || !gelf_getshdr(scn, &shdr) || !shdr.sh_link)
>> + return;
>> +
>> + di->dynsym_data = elf_getdata(scn, NULL);
>> + di->dynstr_data = elf_getdata(elf_getscn(elf, shdr.sh_link), NULL);
>> +
>> + if (!di->plt_got_data || !di->dynstr_data || !di->dynsym_data || !di->rela_dyn_data)
>> + return;
>> +
>> + /* Sort into offset order */
>> + sort_rela_dyn(di);
>> +}
>> +
>> +/* Get instruction displacement from a plt entry for x86_64 */
>> +static u32 get_x86_64_plt_disp(const u8 *p)
>> +{
>> + u8 endbr64[] = {0xf3, 0x0f, 0x1e, 0xfa};
>> + int n = 0;
>> +
>> + /* Skip endbr64 */
>> + if (!memcmp(p, endbr64, sizeof(endbr64)))
>> + n += sizeof(endbr64);
>> + /* Skip bnd prefix */
>> + if (p[n] == 0xf2)
>> + n += 1;
>> + /* jmp with 4-byte displacement */
>> + if (p[n] == 0xff && p[n + 1] == 0x25) {
>> + n += 2;
>> + /* Also add offset from start of entry to end of instruction */
>> + return n + 4 + le32toh(*(const u32 *)(p + n));
>> + }
>> + return 0;
>> +}
>> +
>> +static bool get_plt_got_name(GElf_Shdr *shdr, size_t i,
>> + struct rela_dyn_info *di,
>> + char *buf, size_t buf_sz)
>> +{
>> + void *p = di->plt_got_data->d_buf + i;
>> + u32 disp = get_x86_64_plt_disp(p);
>> + struct rela_dyn vi, *vr;
>> + const char *sym_name;
>> + char *demangled;
>> + GElf_Sym sym;
>> +
>> + if (!di->sorted || !disp)
>> + return false;
>> +
>> + /* Compute target offset of the .plt.got entry */
>> + vi.offset = shdr->sh_offset + di->plt_got_data->d_off + i + disp;
>> +
>> + /* Find that offset in .rela.dyn (sorted by offset) */
>> + vr = bsearch(&vi, di->sorted, di->nr_entries, sizeof(di->sorted[0]), cmp_offset);
>> + if (!vr)
>> + return false;
>> +
>> + /* Get the associated symbol */
>> + gelf_getsym(di->dynsym_data, vr->sym_idx, &sym);
>> + sym_name = elf_sym__name(&sym, di->dynstr_data);
>> + demangled = demangle_sym(di->dso, 0, sym_name);
>> + if (demangled != NULL)
>> + sym_name = demangled;
>> +
>> + snprintf(buf, buf_sz, "%s@plt", sym_name);
>> +
>> + free(demangled);
>> +
>> + return *sym_name;
>> +}
>> +
>> static int dso__synthesize_plt_got_symbols(struct dso *dso, Elf *elf,
>> GElf_Ehdr *ehdr,
>> char *buf, size_t buf_sz)
>> {
>> + struct rela_dyn_info di = { .dso = dso };
>> struct symbol *sym;
>> GElf_Shdr shdr;
>> Elf_Scn *scn;
>> + int err = -1;
>> size_t i;
>>
>> scn = elf_section_by_name(elf, ehdr, &shdr, ".plt.got", NULL);
>> if (!scn || !shdr.sh_entsize)
>> return 0;
>>
>> + if (ehdr->e_machine == EM_X86_64)
>> + get_rela_dyn_info(elf, ehdr, &di, scn);
>
> What about EM_386? Now I'm seeing segfaults on 32 bit test programs
> with .plt.got section.
Thanks for looking at this.
Looks like a late change in get_plt_got_name() did not
get tested for 32-bit. It should be checking di->sorted
first. EM_386 is not supported by this logic.
I will send a V2
>
> Thanks,
> Namhyung
>
>
>> +
>> for (i = 0; i < shdr.sh_size; i += shdr.sh_entsize) {
>> - snprintf(buf, buf_sz, "offset_%#zx@plt", shdr.sh_offset + i);
>> + if (!get_plt_got_name(&shdr, i, &di, buf, buf_sz))
>> + snprintf(buf, buf_sz, "offset_%#zx@plt", shdr.sh_offset + i);
>> sym = symbol__new(shdr.sh_offset + i, shdr.sh_entsize, STB_GLOBAL, STT_FUNC, buf);
>> if (!sym)
>> - return -1;
>> + goto out;
>> symbols__insert(&dso->symbols, sym);
>> }
>> -
>> - return 0;
>> + err = 0;
>> +out:
>> + exit_rela_dyn(&di);
>> + return err;
>> }
>>
>> /*
>> --
>> 2.34.1
>>
>>