From: Kris Van Hees <kris.van.hees@oracle.com>
To: dtrace@lists.linux.dev, dtrace-devel@oss.oracle.com
Subject: [PATCH 12/20] libdtrace: index module address ranges across the handle
Date: Fri, 30 Oct 2026 19:36:49 +0000 [thread overview]
Message-ID: <c0532405682a4907cacc1737efd195a5@oracle.com> (raw)
Build a sorted address-range index lazily and use prefix maxima to bound
searches across overlapping ranges. Retain module-list order when more
than one module covers an address. Fall back to the existing search if
index allocation fails.
Invalidate the index when symbols are added, a module is removed, or the
module list is reordered. Updates that discover nothing retain the index.
Expand discovery coverage for indexed text/data lookup, range boundaries,
gaps, overlaps, additive discovery, geometric range growth, and malformed
symbol input.
---
libdtrace/dt_impl.h | 10 +++
libdtrace/dt_module.c | 143 +++++++++++++++++++++++++++----
libdtrace/dt_open.c | 1 +
test/triggers/module-discovery.c | 78 +++++++++++++++++
4 files changed, 216 insertions(+), 16 deletions(-)
diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
index 5487bfcf..c83fedb2 100644
--- a/libdtrace/dt_impl.h
+++ b/libdtrace/dt_impl.h
@@ -188,6 +188,13 @@ typedef struct dt_module {
uint_t dm_aslen; /* number of entries in dm_asmap */
} dt_module_t;
+/* Handle-wide address index. order preserves module-list precedence. */
+typedef struct dt_module_range {
+ GElf_Addr start, last, max_last;
+ dt_module_t *module;
+ size_t order;
+} dt_module_range_t;
+
/*
* The path to an actual kernel module residing on the disk. Used only for
* initialization of the corresponding dt_module, since modprobe -l is
@@ -308,6 +315,9 @@ struct dtrace_hdl {
uint64_t dt_maxframes; /* system limit on stack traces */
dt_tstring_t *dt_tstrings; /* temporary string slots */
dt_list_t dt_modlist; /* linked list of dt_module_t's */
+ dt_module_range_t *dt_modranges; /* sorted module address ranges */
+ size_t dt_modranges_count;
+ int dt_modranges_valid;
dt_htab_t *dt_mods; /* hash table of dt_module_t's */
dt_htab_t *dt_kernsyms; /* htab of kernel symbol names */
int dt_ctfa_missing; /* cached ENOENT/ENOTDIR for the archive */
diff --git a/libdtrace/dt_module.c b/libdtrace/dt_module.c
index f381bdcd..9d93b9d8 100644
--- a/libdtrace/dt_module.c
+++ b/libdtrace/dt_module.c
@@ -239,9 +239,19 @@ dt_module_cmp(const dt_module_t *p, const dt_module_t *q)
DEFINE_HE_STD_LINK_FUNCS(dt_module, dt_module_t, dm_he)
+static void
+dt_module_ranges_invalidate(dtrace_hdl_t *dtp)
+{
+ free(dtp->dt_modranges);
+ dtp->dt_modranges = NULL;
+ dtp->dt_modranges_count = 0;
+ dtp->dt_modranges_valid = 0;
+}
+
static void *
dt_module_del_mod(dt_module_t *head, dt_module_t *dmp)
{
+ dt_module_ranges_invalidate(dmp->dm_dtp);
head = dt_module_del(head, dmp);
dt_list_delete(&dmp->dm_dtp->dt_modlist, dmp);
@@ -1724,6 +1734,9 @@ dt_modsym_addsym(dtrace_hdl_t *dtp, dt_module_t *dmp, dt_kallsym_t *sym,
dtrace_addr_range_t *range = NULL;
int skip = 0;
+ if (dtp->dt_modranges_valid)
+ dt_module_ranges_invalidate(dtp);
+
/*
* Add the symbol to the module's kernel symbol table.
*
@@ -2171,6 +2184,7 @@ dt_module_shuffle_to_start(dtrace_hdl_t *dtp, const char *name)
dt_list_delete(&dtp->dt_modlist, dmp);
dt_list_prepend(&dtp->dt_modlist, dmp);
+ dt_module_ranges_invalidate(dtp);
}
static dt_module_t *
@@ -2338,6 +2352,118 @@ dtrace_lookup_by_name(dtrace_hdl_t *dtp, const char *object, const char *name,
return dt_set_errno(dtp, EDT_NOSYM);
}
+static int
+dt_module_range_cmp(const void *a, const void *b)
+{
+ const dt_module_range_t *p = a, *q = b;
+
+ return p->start < q->start ? -1 : p->start > q->start;
+}
+
+/* Build lazily: handles that never symbolize addresses need no index. */
+static int
+dt_module_ranges_build(dtrace_hdl_t *dtp)
+{
+ dt_module_t *dmp;
+ dt_module_range_t *index;
+ size_t total = 0, n = 0, order = 0, i;
+ GElf_Addr max_last = 0;
+
+ for (dmp = dt_list_next(&dtp->dt_modlist); dmp;
+ dmp = dt_list_next(dmp)) {
+ if (dmp->dm_text_addrs_size > SIZE_MAX - total)
+ return -1;
+ total += dmp->dm_text_addrs_size;
+ if (dmp->dm_data_addrs_size > SIZE_MAX - total)
+ return -1;
+ total += dmp->dm_data_addrs_size;
+ }
+ if (total > SIZE_MAX / sizeof(*index))
+ return -1;
+ index = total ? malloc(total * sizeof(*index)) : NULL;
+ if (total && index == NULL)
+ return -1;
+
+ for (dmp = dt_list_next(&dtp->dt_modlist); dmp;
+ dmp = dt_list_next(dmp), order++) {
+ int text;
+
+ for (text = 0; text < 2; text++) {
+ dtrace_addr_range_t *ranges = text ? dmp->dm_text_addrs :
+ dmp->dm_data_addrs;
+ size_t size = text ? dmp->dm_text_addrs_size :
+ dmp->dm_data_addrs_size;
+
+ for (i = 0; i < size; i++) {
+ GElf_Addr start = ranges[i].dar_va;
+ GElf_Addr len = ranges[i].dar_size;
+
+ if (len == 0)
+ continue;
+ index[n].start = start;
+ index[n].last = len - 1 > UINT64_MAX - start ?
+ UINT64_MAX : start + len - 1;
+ index[n].module = dmp;
+ index[n++].order = order;
+ }
+ }
+ }
+ if (n)
+ qsort(index, n, sizeof(*index), dt_module_range_cmp);
+ for (i = 0; i < n; i++) {
+ if (index[i].last > max_last)
+ max_last = index[i].last;
+ index[i].max_last = max_last;
+ }
+ dtp->dt_modranges = index;
+ dtp->dt_modranges_count = n;
+ dtp->dt_modranges_valid = 1;
+ return 0;
+}
+
+static dt_module_t *
+dt_module_by_addr(dtrace_hdl_t *dtp, GElf_Addr addr)
+{
+ dt_module_range_t *index;
+ dt_module_t *dmp = NULL;
+ size_t lo = 0, hi, best = SIZE_MAX;
+
+ if (!dtp->dt_modranges_valid && dt_module_ranges_build(dtp) != 0) {
+ /* An optional index must not make a lookup fail under memory pressure. */
+ for (dmp = dt_list_next(&dtp->dt_modlist); dmp;
+ dmp = dt_list_next(dmp)) {
+ if (bsearch(&addr, dmp->dm_text_addrs, dmp->dm_text_addrs_size,
+ sizeof(dtrace_addr_range_t), dtrace_addr_range_cmp) ||
+ bsearch(&addr, dmp->dm_data_addrs, dmp->dm_data_addrs_size,
+ sizeof(dtrace_addr_range_t), dtrace_addr_range_cmp))
+ return dmp;
+ }
+ return NULL;
+ }
+ index = dtp->dt_modranges;
+ hi = dtp->dt_modranges_count;
+ while (lo < hi) {
+ size_t mid = lo + (hi - lo) / 2;
+
+ if (index[mid].start <= addr)
+ lo = mid + 1;
+ else
+ hi = mid;
+ }
+ /* Prefix maxima bound the backwards search, including overlapping ranges. */
+ while (lo && index[lo - 1].max_last >= addr) {
+ dt_module_range_t *r = &index[--lo];
+
+ if (r->last >= addr && r->order < best) {
+ dmp = r->module;
+ best = r->order;
+ if (best == 0)
+ break;
+ }
+ }
+ return dmp;
+}
+
/*
* Exported interface to look up a symbol by address. We return the (possibly
* partial) GElf_Sym and complete symbol information for the matching symbol.
@@ -2358,22 +2484,7 @@ dtrace_lookup_by_addr(dtrace_hdl_t *dtp, GElf_Addr addr,
if (v != NULL)
return v->dtv_lookup_by_addr(dtp->dt_varg, addr, symp, sip);
- for (dmp = dt_list_next(&dtp->dt_modlist); dmp != NULL;
- dmp = dt_list_next(dmp)) {
- void *i;
-
- i = bsearch(&addr, dmp->dm_text_addrs, dmp->dm_text_addrs_size,
- sizeof(struct dtrace_addr_range), dtrace_addr_range_cmp);
-
- if (i)
- break;
-
- i = bsearch(&addr, dmp->dm_data_addrs, dmp->dm_data_addrs_size,
- sizeof(struct dtrace_addr_range), dtrace_addr_range_cmp);
-
- if (i)
- break;
- }
+ dmp = dt_module_by_addr(dtp, addr);
if (dmp == NULL) {
dt_dprintf("No module corresponds to %lx\n", addr);
diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
index 8291b87d..fbc97ccc 100644
--- a/libdtrace/dt_open.c
+++ b/libdtrace/dt_open.c
@@ -1312,6 +1312,7 @@ dtrace_close(dtrace_hdl_t *dtp)
dt_htab_destroy(dtp->dt_kernsyms);
dtp->dt_kernsyms = NULL;
dt_htab_destroy(dtp->dt_mods);
+ free(dtp->dt_modranges);
dt_htab_destroy(dtp->dt_kernpaths);
dt_htab_destroy(dtp->dt_btf_ids);
diff --git a/test/triggers/module-discovery.c b/test/triggers/module-discovery.c
index d05d2bab..ee9604b7 100644
--- a/test/triggers/module-discovery.c
+++ b/test/triggers/module-discovery.c
@@ -97,6 +97,7 @@ check_discovery(int fallback)
dtrace_addr_range_t *ranges, *kernel_ranges;
ctf_file_t *types;
GElf_Sym elf;
+ dtrace_syminfo_t si;
int err, i;
CHECK((dtp = dtrace_open(DTRACE_VERSION, DTRACE_O_LP64, &err)) != NULL);
@@ -144,6 +145,12 @@ check_discovery(int fallback)
CHECK(ranges->dar_va == 0x1000);
CHECK(ranges->dar_size == (fallback ? 0x11 : 0x20));
CHECK(dt_symbol_by_addr(first_syms, 0x1000) == sym);
+ first->dm_flags |= DT_DM_LOADED;
+ CHECK(dtrace_lookup_by_addr(dtp, 0x1000, &elf, &si) == 0);
+ CHECK(strcmp(si.object, first->dm_name) == 0);
+ CHECK(dtrace_lookup_by_addr(dtp, 0x1010, &elf, &si) == 0);
+ CHECK(dtrace_lookup_by_addr(dtp, fallback ? 0x1011 : 0x1020,
+ &elf, &si) == -1);
/* Keep the old module while discovering another, including a duplicate name. */
btf_generation = 200;
@@ -152,15 +159,36 @@ check_discovery(int fallback)
"1000 T discovery_symbol [discovery_first]\n"
"1010 T discovery_tail [discovery_first]\n"
"2000 T discovery_symbol [discovery_second]\n"
+ "1008 T overlapping_symbol [discovery_overlap]\n"
"3000 T discovery_live [discovery_offline]\n" :
"100 1 T _end\n"
"1000 10 T discovery_symbol [discovery_first]\n"
"1010 10 T discovery_tail [discovery_first]\n"
"2000 10 T discovery_symbol [discovery_second]\n"
+ "1008 1 T overlapping_symbol [discovery_overlap]\n"
"3000 10 T discovery_live [discovery_offline]\n";
CHECK(dtrace_update(dtp) == 0);
CHECK((second = dt_module_lookup_by_name(dtp, "discovery_second")) != NULL);
CHECK(first->dm_btf_id == 101 && second->dm_btf_id == 202);
+ second->dm_flags |= DT_DM_LOADED;
+ CHECK(dtrace_lookup_by_addr(dtp, 0x2000, &elf, &si) == 0);
+ CHECK(strcmp(si.object, second->dm_name) == 0);
+ {
+ dt_module_t *a = dt_module_lookup_by_name(dtp, "discovery_multi_a");
+ dt_module_t *b = dt_module_lookup_by_name(dtp, "discovery_multi_b");
+
+ a->dm_flags |= DT_DM_LOADED;
+ b->dm_flags |= DT_DM_LOADED;
+ dt_module_t *overlap = dt_module_lookup_by_name(dtp, "discovery_overlap");
+
+ CHECK(overlap != NULL);
+ overlap->dm_flags |= DT_DM_LOADED;
+ CHECK(dtrace_lookup_by_addr(dtp, 0x1008, &elf, &si) == 0);
+ CHECK(strcmp(si.object, first->dm_name) == 0);
+ /* Equal ranges retain the first module's precedence. */
+ CHECK(dtrace_lookup_by_addr(dtp, 0x4000, &elf, &si) == 0);
+ CHECK(strcmp(si.object, a->dm_name) == 0);
+ }
CHECK(dt_module_symbol_by_name(dtp, second, "discovery_symbol") != NULL);
CHECK(dt_module_symbol_by_name(dtp, offline, "discovery_live") != NULL);
CHECK(!(offline->dm_flags & DT_DM_KERN_UNLOADED));
@@ -192,6 +220,8 @@ check_discovery(int fallback)
CHECK(kernel->dm_kernsyms == kernel_syms);
CHECK(kernel->dm_text_addrs == kernel_ranges);
CHECK(first->dm_kernsyms == first_syms);
+ CHECK(dtrace_lookup_by_addr(dtp, 0x1000, &elf, &si) == 0);
+ CHECK(strcmp(si.object, first->dm_name) == 0);
CHECK(first->dm_btf_id == 101 && second->dm_btf_id == 202);
CHECK(first->dm_text_addrs == ranges);
CHECK(first->dm_text_addrs_size == 1);
@@ -205,6 +235,54 @@ check_discovery(int fallback)
CHECK(offline->dm_ctfp == types);
}
+ /* Alternating built-in text/data fragments cross both initial capacities. */
+ {
+ char snapshot[2048];
+ size_t used = 0;
+ dt_module_t *fragmented;
+
+ for (i = 0; i < 12; i++)
+ used += snprintf(snapshot + used, sizeof(snapshot) - used,
+ fallback ? "%x %c fragment_%d [discovery_fragmented]\n" :
+ "%x 1 %c fragment_%d [discovery_fragmented]\n",
+ 0x8000 + i * 0x10, i % 2 ? 'D' : 'T', i);
+ symbols = snapshot;
+ CHECK(dtrace_update(dtp) == 0);
+ fragmented = dt_module_lookup_by_name(dtp, "discovery_fragmented");
+ CHECK(fragmented != NULL);
+ CHECK(fragmented->dm_text_addrs_size == 6);
+ CHECK(fragmented->dm_data_addrs_size == 6);
+ fragmented->dm_flags |= DT_DM_LOADED;
+ for (i = 0; i < 12; i++) {
+ CHECK(dtrace_lookup_by_addr(dtp, 0x8000 + i * 0x10, &elf, &si) == 0);
+ CHECK(strcmp(si.object, fragmented->dm_name) == 0);
+ CHECK(dtrace_lookup_by_addr(dtp, 0x8001 + i * 0x10, &elf, &si) == -1);
+ }
+ }
+ {
+ char malformed[8192], longname[PATH_MAX + 1];
+ const char *rows[] = {
+ "10000000000000000 T bad [discovery_rejected]\n",
+ "1000 T bad [discovery_rejected\n",
+ "1000 ",
+ "1000 T ",
+ NULL
+ };
+
+ for (i = 0; rows[i]; i++) {
+ symbols = rows[i];
+ CHECK(dtrace_update(dtp) == 0);
+ CHECK(dt_module_lookup_by_name(dtp, "discovery_rejected") == NULL);
+ }
+ memset(longname, 'x', sizeof(longname) - 1);
+ longname[sizeof(longname) - 1] = '\0';
+ snprintf(malformed, sizeof(malformed), fallback ?
+ "1000 T %s [discovery_rejected]\n" :
+ "1000 1 T %s [discovery_rejected]\n", longname);
+ symbols = malformed;
+ CHECK(dtrace_update(dtp) == 0);
+ CHECK(dt_module_lookup_by_name(dtp, "discovery_rejected") == NULL);
+ }
symbols = NULL;
dtrace_close(dtp);
return 0;
--
2.52.0
next reply other threads:[~2026-10-30 19:38 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-30 19:36 Kris Van Hees [this message]
2026-11-06 13:06 ` [PATCH 12/20] libdtrace: index module address ranges across the handle Nix
2026-11-07 16:01 ` Kris Van Hees
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=c0532405682a4907cacc1737efd195a5@oracle.com \
--to=kris.van.hees@oracle.com \
--cc=dtrace-devel@oss.oracle.com \
--cc=dtrace@lists.linux.dev \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.