Skip to content

Read relocs from ELF memory dumps opened with bin.mem ##bin - #26890

Open
phix33 wants to merge 1 commit into
radareorg:masterfrom
phix33:elf-memory-layout-dumps
Open

phix33 wants to merge 1 commit into
radareorg:masterfrom
phix33:elf-memory-layout-dumps

Conversation

@phix33

@phix33 phix33 commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator
  • Mark this if you consider it ready to merge
  • I've added tests (optional)
  • I wrote some lines in the book (optional)

Description

An ELF copied out of a running process has each PT_LOAD at p_vaddr - base, not at its file offset, and r2 has no way to read it that way, so its relocs come back empty. This is the follow-up @trufae asked for when merging #26740. The image below is x86_64-tailrela.so laid out like that, with its section table zeroed (radareorg/radare2-testbins#147):

$ r2 -nqc 'oba 0 0x10000; ir' bins/elf/x86_64-tailrela-memimg
vaddr paddr type ntype name
---------------------------

With this PR and -e bin.mem=true it lists the eight relocs of the original file.

  • bin.mem tells oba <addr> [baddr] the buffer is a memory image; it travels as RBinFileOptions.mem into RBinFile and survives r_bin_reload. Nothing infers the layout from ELF fields. Both new fields sit in existing padding, so neither struct changes size; R2_ABIVERSION is bumped to 151 for the layout change.
  • In ELF, segment_backing(), as sketched in Bound ELF reloc tables by the bytes that are loaded ##bin #26740, is the one place a vaddr becomes a buffer offset: p_vaddr - memory_base within p_memsz, clamped to the bytes present. v2p, p2v, the reloc-table bound, the segment sections and PT_INTERP all use it, so the bound and the read can no longer disagree. A PT_LOAD whose span wraps is rejected, as before, and each PT_LOAD section is sized by its own header. File layout keeps its existing paths.
  • memory_base is the vaddr of the PT_LOAD that maps the ELF header and program headers; without one the image is read as a file, with a warning. A loader never maps the section table, so the header's e_shnum is ignored in this mode.
  • Not covered: a dump whose .dynamic was rebased in place by the loader (glibc may write runtime addresses into the d_ptr tags); the debugger's process-memory fallback in r_bin_open_io is left in file layout until that is handled. The ppc64 ELFv1 stub scan still reads code at p_offset.

The new tests are in test/db/formats/elf/reloc. One applies the #26740 recipe to an existing PIE. Others cut the image in the middle of the reloc table and at its end. Two more cut it inside a segment and exactly at the start of one. One keeps a header that still names its section table and one moves the PT_DYNAMIC file offset outside the image; another gives PT_DYNAMIC a size that would wrap, which is rejected. Your two reproductions are cases too: a wrapping PT_LOAD no longer bounds the reloc table in file mode, and overlapping PT_LOADs each keep their own size. Another checks that a reload keeps the layout. Two controls keep the output of master. One opens the same image without the flag and the other opens a plain file with it. An ELF whose header is not mapped by any PT_LOAD prints the same as it does without the flag and adds a warning.

@trufae trufae left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three fixes needed before merging: the lost overflow guard makes relocation bounds disagree with reads, overlapping LOADs get the wrong sizes, and the public struct changes need an ABI bump. Details and reproductions are inline.

The two cleanup suggestions remove 22 implementation lines. Prefer bin.mem; bin.memimg is the more explicit alternative.

Built this head and ran all 11 new r2r tests successfully. The two reproduced ELF cases are not covered by those tests.

Comment thread libr/include/r_bin.h Outdated
bool nofuncstarts;
bool skip_symbols; // skip symbol loading (e.g., for companion debug files)
const char *filename;
bool memlayout;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Bump R2_ABIVERSION for this public layout change. On x86-64, RBinFileOptions grows from 56 to 64 bytes, and r_bin_file_options_init() clears sizeof (*opt). An existing plugin allocating the old struct will therefore have 8 bytes overwritten past its allocation. RBinFile also grows, but libr/include/r_lib.h still declares ABI 150, so the plugin loader cannot reject incompatible builds.

Comment thread libr/bin/format/elf/elf.c Outdated
Comment on lines +724 to +726
const ut64 delta = vaddr - p->p_vaddr;
const ut64 span = eo->memory_layout? p->p_memsz: p->p_filesz;
if (delta >= span || (eo->memory_layout && p->p_vaddr < eo->memory_base)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Restore the overflow rejection removed from loaded_bytes_at(). In file mode this helper accepts a wrapping PT_LOAD that Elf_(v2p) rejects, so the bound and the read can select different segments.

Reproduced with x86_64-tailrela.so in a malloc buffer, bin.memlayout=false:

wv8 0x154 @ 0xd0
wv8 0x10d38 @ 0x88
wv8 0xffffffffffffffff @ 0x98
oba 0 0x10000
ir

The first write alone returns one complete relocation. Adding the wrapping LOAD returns two: the second has only 4 bytes left in the LOAD actually selected by v2p, but read_reloc() reads all 24. Preserve the old p_filesz > UT64_MAX - p_vaddr rejection in the shared helper and add this regression.

Comment thread libr/bin/format/elf/elf.c Outdated
Comment on lines +4713 to +4716
if (!backing_at (eo, ptr->vaddr, &ptr->paddr, &left) && ptr->vaddr >= eo->memory_base) {
ptr->paddr = ptr->vaddr - eo->memory_base;
}
ptr->size = phdr[i].p_type == PT_LOAD? left: R_MIN (ptr->size, left);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Use the current header to bound a PT_LOAD, rather than searching for the first LOAD containing its address. With x86_64-tailrela-memimg, set wv8 0xd00 @ 0x68 before oba 0 0x10000: iSS~LOAD reports LOAD1 size 0x600 instead of 0x50, and LOAD2 size 0x100 instead of 0x238. Both sizes come from the overlapping LOAD0.

Call segment_backing() with &phdr[i] for LOAD entries; keep the containing-LOAD lookup for the other header types. This fixes the sizes and removes the repeated full header scan for each LOAD. Add the overlap case to the r2r tests.

Comment thread libr/core/cmd_open.inc.c Outdated
if (desc) {
RBinFileOptions opt;
r_bin_file_options_init (&opt, desc->fd, baddr, addr, rawstr);
opt.memlayout = r_config_get_b (core->config, "bin.memlayout");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge the two memory-loading branches here. After the existing file/base64 cases, compute addr once and select the optional base:

ut64 baddr = R_STR_ISNOTEMPTY (filename)? r_num_math (core->num, filename): addr;

Then keep one copy of the descriptor lookup, options initialization, size fallback, open and finalization. The two branches otherwise do exactly the same work. This removes 18 lines, including the duplicate config lookup, without adding a helper.

Comment thread libr/bin/format/elf/elf.c Outdated
Comment on lines +748 to +750
static ut64 loaded_bytes_at(ELFOBJ *eo, ut64 vaddr) {
ut64 off, left;
return backing_at (eo, vaddr, &off, &left)? left: 0;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now a forwarding wrapper with one caller. Remove it and replace its call in reloc_read_size() with:

ut64 off, loaded = 0;
backing_at (eo, vaddr, &off, &loaded);

The helper only writes the outputs on success, so the initialized zero preserves the failure behavior. This removes another 4 lines while keeping the bounds calculation shared.

Comment thread libr/core/cconfig.c Outdated
SETB ("bin.relocs", "true", "load relocs information at startup if available");
SETB ("bin.relocs.apply", "false", "apply reloc information");
SETB ("bin.relocs.xrefs", "true", "register xrefs from reloc information");
SETB ("bin.memlayout", "false", "oba <addr> [baddr] reads the buffer as a memory image (segments at vaddr - base)");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer bin.mem for the shorter name, or bin.memimg if you want the memory-image meaning explicit. My choice is bin.mem, with help text read oba input as a mapped memory image. Use the chosen name consistently in the config and the new options/file fields; no alias is needed for an unreleased option.

@trufae

trufae commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator

Also rebase

@phix33
phix33 force-pushed the elf-memory-layout-dumps branch from 64ec25b to 592be2e Compare October 4, 2026 08:00
@phix33 phix33 changed the title Read relocs from ELF memory dumps opened with bin.memlayout ##bin Read relocs from ELF memory dumps opened with bin.mem ##bin Oct 4, 2026
@phix33

phix33 commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, all addressed, rebased onto master:

  • ABI: bumped R2_ABIVERSION to 151. I also moved both new bools into existing padding, so RBinFileOptions (56) and RBinFile (264) keep their size and every field offset.
  • Overflow guard: segment_backing() rejects span > UT64_MAX - p_vaddr again; your wrapping PT_LOAD repro is a test.
  • Overlapping LOADs: each PT_LOAD is now sized from its own header via segment_backing (&phdr[i]); your overlap repro is a test.
  • oba: the two memory branches are merged, baddr defaults to addr.
  • Wrapper: loaded_bytes_at() removed, reloc_read_size() calls backing_at().
  • Name: bin.mem, with your help text; the fields are RBinFileOptions.mem / RBinFile.mem.
  • Also fixed: in memory mode the PT_DYNAMIC size check could wrap (loaded_offset + dyn_size); it is now non-wrapping, with a test.

Not covered here:

  • oba still ignores bin.mem; that path creates the RBinFile elsewhere.
  • bf->mem shares its name with RBinObject.mem; happy to rename the fields to memimg if you prefer?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants