From: Ian Rogers <irogers@google.com>
To: Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Namhyung Kim <namhyung@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>,
Alireza Haghdoost <haghdoost@uber.com>,
linux-perf-users@vger.kernel.org, linux-kernel@vger.kernel.org,
Ian Rogers <irogers@google.com>
Subject: [PATCH 2/3] perf symbol-elf: Fix zero-size symbol end calculation across .symtab and .dynsym
Date: Fri, 30 Oct 2026 23:35:48 -0700 [thread overview]
Message-ID: <20261031063549.1281324-3-irogers@google.com> (raw)
In-Reply-To: <20261031063549.1281324-1-irogers@google.com>
When loading symbols from an ELF binary in dso__load_sym(),
dso__load_sym_internal() can be invoked multiple times: first for
.symtab (or .gnu_debugdata's .symtab) and then for .dynsym.
Previously, dso__load_sym_internal() ran symbols__fixup_end() and
symbols__fixup_duplicate() at the end of each individual symbol table
load. Consequently, a zero-size symbol in .symtab had its end address
extended to the start of the next .symtab symbol before .dynsym was
loaded. If an exported function existed only in .dynsym (for example,
when stripped from .symtab or when using .gnu_debugdata minidebuginfo)
at an address between the two .symtab symbols, the zero-size .symtab
symbol overlapped the .dynsym function and samples inside the .dynsym
function were misattributed to the preceding zero-size symbol.
Move symbols__fixup_end(), symbols__fixup_duplicate(), and
maps__fixup_end() from dso__load_sym_internal() to dso__load_sym() so
they run once after all symbol tables have been loaded into the DSO, and
add a unit test in 'perf test symbols' verifying that a zero-size
.symtab symbol ends at a following .dynsym-only function rather than
overlapping it.
Fixes: 87704345cc60 ("perf symbol-elf: Decode dynsym even if symtab exists")
Fixes: b10f74308e13 ("perf symbol: Support .gnu_debugdata for symbols")
Suggested-by: Alireza Haghdoost <haghdoost@uber.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/symbols.c | 169 +++++++++++++++++++++++++++++++++++
tools/perf/util/symbol-elf.c | 36 ++++----
2 files changed, 190 insertions(+), 15 deletions(-)
diff --git a/tools/perf/tests/symbols.c b/tools/perf/tests/symbols.c
index b644d95c6be9..d3180db45ee6 100644
--- a/tools/perf/tests/symbols.c
+++ b/tools/perf/tests/symbols.c
@@ -2,8 +2,11 @@
#include <linux/compiler.h>
#include <linux/string.h>
#include <sys/mman.h>
+#include <elf.h>
#include <inttypes.h>
#include <limits.h>
+#include <stdlib.h>
+#include <unistd.h>
#include "debug.h"
#include "dso.h"
#include "env.h"
@@ -308,9 +311,175 @@ static int test__kallsyms_fixup_end(struct test_suite *test __maybe_unused,
return ret;
}
+#ifdef HAVE_LIBELF_SUPPORT
+static int write_test_elf(int fd)
+{
+ static const char shstrtab[] = "\0.shstrtab\0.text\0.strtab\0.symtab\0.dynsym";
+ static const char strtab[] = "\0zfun\0efun\0after_fun";
+ struct {
+ Elf64_Ehdr ehdr;
+ Elf64_Shdr shdrs[6];
+ char shstr[sizeof(shstrtab)];
+ char str[sizeof(strtab)];
+ Elf64_Sym symtab[3];
+ Elf64_Sym dynsym[2];
+ u8 text[0x60];
+ } img;
+
+ memset(&img, 0, sizeof(img));
+ memcpy(img.ehdr.e_ident, ELFMAG, SELFMAG);
+ img.ehdr.e_ident[EI_CLASS] = ELFCLASS64;
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ img.ehdr.e_ident[EI_DATA] = ELFDATA2MSB;
+#else
+ img.ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
+#endif
+ img.ehdr.e_ident[EI_VERSION] = EV_CURRENT;
+ img.ehdr.e_type = ET_DYN;
+ img.ehdr.e_machine = EM_X86_64;
+ img.ehdr.e_version = EV_CURRENT;
+ img.ehdr.e_shoff = offsetof(typeof(img), shdrs);
+ img.ehdr.e_ehsize = sizeof(Elf64_Ehdr);
+ img.ehdr.e_shentsize = sizeof(Elf64_Shdr);
+ img.ehdr.e_shnum = ARRAY_SIZE(img.shdrs);
+ img.ehdr.e_shstrndx = 1;
+
+ memcpy(img.shstr, shstrtab, sizeof(shstrtab));
+ memcpy(img.str, strtab, sizeof(strtab));
+
+ /* Section 1: .shstrtab */
+ img.shdrs[1].sh_name = 1;
+ img.shdrs[1].sh_type = SHT_STRTAB;
+ img.shdrs[1].sh_offset = offsetof(typeof(img), shstr);
+ img.shdrs[1].sh_size = sizeof(shstrtab);
+
+ /* Section 2: .text */
+ img.shdrs[2].sh_name = 11;
+ img.shdrs[2].sh_type = SHT_PROGBITS;
+ img.shdrs[2].sh_flags = SHF_ALLOC | SHF_EXECINSTR;
+ img.shdrs[2].sh_addr = 0x1000;
+ img.shdrs[2].sh_offset = offsetof(typeof(img), text);
+ img.shdrs[2].sh_size = sizeof(img.text);
+
+ /* Section 3: .strtab */
+ img.shdrs[3].sh_name = 17;
+ img.shdrs[3].sh_type = SHT_STRTAB;
+ img.shdrs[3].sh_flags = SHF_ALLOC;
+ img.shdrs[3].sh_offset = offsetof(typeof(img), str);
+ img.shdrs[3].sh_size = sizeof(strtab);
+
+ /*
+ * Section 4: .symtab with zero-size zfun at 0x1010 and after_fun at
+ * 0x1040, but without efun (simulating objcopy --strip-symbol=efun or
+ * .gnu_debugdata minidebuginfo where exported symbols live only in
+ * .dynsym).
+ */
+ img.shdrs[4].sh_name = 25;
+ img.shdrs[4].sh_type = SHT_SYMTAB;
+ img.shdrs[4].sh_offset = offsetof(typeof(img), symtab);
+ img.shdrs[4].sh_size = sizeof(img.symtab);
+ img.shdrs[4].sh_link = 3;
+ img.shdrs[4].sh_info = 2;
+ img.shdrs[4].sh_entsize = sizeof(Elf64_Sym);
+
+ img.symtab[1].st_name = 1; /* zfun */
+ img.symtab[1].st_info = ELF64_ST_INFO(STB_LOCAL, STT_FUNC);
+ img.symtab[1].st_shndx = 2;
+ img.symtab[1].st_value = 0x1010;
+ img.symtab[1].st_size = 0;
+
+ img.symtab[2].st_name = 11; /* after_fun */
+ img.symtab[2].st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC);
+ img.symtab[2].st_shndx = 2;
+ img.symtab[2].st_value = 0x1040;
+ img.symtab[2].st_size = 0x10;
+
+ /* Section 5: .dynsym with efun at 0x1020..0x1030 */
+ img.shdrs[5].sh_name = 33;
+ img.shdrs[5].sh_type = SHT_DYNSYM;
+ img.shdrs[5].sh_flags = SHF_ALLOC;
+ img.shdrs[5].sh_offset = offsetof(typeof(img), dynsym);
+ img.shdrs[5].sh_size = sizeof(img.dynsym);
+ img.shdrs[5].sh_link = 3;
+ img.shdrs[5].sh_info = 1;
+ img.shdrs[5].sh_entsize = sizeof(Elf64_Sym);
+
+ img.dynsym[1].st_name = 6; /* efun */
+ img.dynsym[1].st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC);
+ img.dynsym[1].st_shndx = 2;
+ img.dynsym[1].st_value = 0x1020;
+ img.dynsym[1].st_size = 0x10;
+
+ return write(fd, &img, sizeof(img)) == (ssize_t)sizeof(img) ? 0 : -1;
+}
+
+static int test__elf_symtab_dynsym_fixup_end(struct test_suite *test __maybe_unused,
+ int subtest __maybe_unused)
+{
+ char tmpfile[] = "/tmp/perf-test-symtab-dynsym-XXXXXX";
+ struct test_info ti;
+ struct map *map = NULL;
+ struct dso *dso;
+ u64 text_offs;
+ int fd, ret;
+
+ fd = mkstemp(tmpfile);
+ if (fd < 0)
+ return TEST_FAIL;
+
+ if (write_test_elf(fd)) {
+ close(fd);
+ unlink(tmpfile);
+ return TEST_FAIL;
+ }
+ close(fd);
+
+ ret = init_test_info(&ti);
+ if (ret != TEST_OK) {
+ unlink(tmpfile);
+ return ret;
+ }
+
+ ret = create_map(&ti, tmpfile, &map);
+ if (ret != TEST_OK)
+ goto out_exit;
+
+ dso = map__dso(map);
+ if (dso__load(dso, map) <= 0) {
+ ret = TEST_FAIL;
+ goto out_put;
+ }
+
+ /* Verify no overlapping or zero-length symbols exist. */
+ ret = test_dso(dso);
+ text_offs = 0x1000 - dso__text_offset(dso);
+ if (check_symbol(dso, text_offs + 0x14, "zfun", text_offs + 0x20))
+ ret = TEST_FAIL;
+ if (check_symbol(dso, text_offs + 0x24, "efun", text_offs + 0x30))
+ ret = TEST_FAIL;
+ if (check_symbol(dso, text_offs + 0x44, "after_fun", text_offs + 0x50))
+ ret = TEST_FAIL;
+
+out_put:
+ map__put(map);
+out_exit:
+ exit_test_info(&ti);
+ unlink(tmpfile);
+ return ret;
+}
+#else
+static int test__elf_symtab_dynsym_fixup_end(struct test_suite *test __maybe_unused,
+ int subtest __maybe_unused)
+{
+ return TEST_SKIP;
+}
+#endif
+
static struct test_case tests__symbols[] = {
TEST_CASE("Symbols", symbols),
TEST_CASE("Kallsyms symbol ends", kallsyms_fixup_end),
+ TEST_CASE("ELF zero-size symbol end across .symtab and .dynsym",
+ elf_symtab_dynsym_fixup_end),
{ .name = NULL, }
};
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 1e2716c6af45..77ef5b2dbf32 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -1789,21 +1789,6 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
nr++;
}
dso__put(curr_dso);
-
- /*
- * For misannotated, zeroed, ASM function sizes.
- */
- if (nr > 0) {
- symbols__fixup_end(dso__symbols(dso), false);
- symbols__fixup_duplicate(dso__symbols(dso));
- if (kmap) {
- /*
- * We need to fixup this here too because we create new
- * maps here, for things like vsyscall sections.
- */
- maps__fixup_end(kmaps);
- }
- }
return nr;
out_elf_end:
dso__put(curr_dso);
@@ -1864,6 +1849,27 @@ int dso__load_sym(struct dso *dso, struct map *map, struct symsrc *syms_ss,
nr += err;
}
+ /*
+ * For misannotated, zeroed, ASM function sizes. Run after all symbol
+ * tables (.symtab, .dynsym, .gnu_debugdata) have been loaded so a
+ * zero-size symbol in .symtab ends at the next symbol across all
+ * tables rather than overlapping a .dynsym-only function.
+ */
+ if (nr > 0) {
+ struct kmap *kmap = dso__kernel(dso) ? map__kmap(map) : NULL;
+ struct maps *kmaps = kmap ? map__kmaps(map) : NULL;
+
+ symbols__fixup_end(dso__symbols(dso), false);
+ symbols__fixup_duplicate(dso__symbols(dso));
+ if (kmap) {
+ /*
+ * We need to fixup this here too because we create new
+ * maps here, for things like vsyscall sections.
+ */
+ maps__fixup_end(kmaps);
+ }
+ }
+
return nr;
}
--
2.56.0.317.g454f8302e1-goog
next prev parent reply other threads:[~2026-10-31 6:36 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-31 6:35 [PATCH 0/3] perf symbols/callchain/annotate: Bug fixes from symbol reference counting review Ian Rogers
2026-10-31 6:35 ` [PATCH 1/3] perf callchain: Fix loss of symbol in merge_chain_branch() Ian Rogers
2026-10-31 6:35 ` Ian Rogers [this message]
2026-10-31 6:35 ` [PATCH 3/3] perf annotate: Fix block_range symbol loss on split and leaks on exit Ian Rogers
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261031063549.1281324-3-irogers@google.com \
--to=irogers@google.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=haghdoost@uber.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®