Skip to content

Commit

Permalink
Add end-to-end test for libbpf#875
Browse files Browse the repository at this point in the history
Add an end-to-end test for the fix provided by pull request libbpf#875. The
test basically normalizes an address in a specially crafted binary and
symbolizes the resulting file offset. It fails without commit
1a4e107 ("Use file size in file offset -> virtual offset
translation"), because then the file offset to virtual offset
translation produces a virtual offset that can't be symbolized to the
expected _start function.

For the record, the binary looks roughly as follows:

  $ readelf --segments --wide test-block.bin
  > Program Headers:
  >   Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  >   LOAD           0x000000 0x00000000000ff000 0x00000000000ff000 0x000200 0x301000 RW  0x1000
  >   LOAD           0x000200 0x0000000000400200 0x0000000000400200 0x000030 0x000030 R   0x1000
  >   LOAD           0x001000 0x0000000000401000 0x0000000000401000 0x00005b 0x00005b R E 0x1000
  >   LOAD           0x002000 0x0000000000402000 0x0000000000402000 0x000050 0x000050 R   0x1000
  >   [...]

Signed-off-by: Daniel Müller <deso@posteo.net>
  • Loading branch information
d-e-s-o committed Oct 31, 2024
1 parent 1155849 commit ab0e157
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 0 deletions.
10 changes: 10 additions & 0 deletions data/test-block-augmented.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* Linker script meant to augment the default one and insert some
* fill bytes at a relatively low address (hopefully before any of the
* regular relevant code. */

SECTIONS {
.whatevs (0x100000): {
FILL(0xdead)
. = ABSOLUTE(. + 0x300000);
}
}
46 changes: 46 additions & 0 deletions data/test-block.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* A binary basically just blocking waiting for input and exiting. It also write
* the address of its `_start` function to stdout (unformatted; just a byte
* dump).
*
* It uses raw system calls to avoid dependency on libc, which pulls in
* start up code and various other artifacts that perturb ELF layout in
* semi-unforeseeable ways, in an attempt to provide us with maximum
* control over the final binary.
*
* Likely only works on x86_64.
*/

#include <unistd.h>
#include <sys/syscall.h>


void _start(void) {
char buf[2];
int rc;
void* addr = (void*)&_start;
/* Write the address of `_start` to stderr. We use stderr because it's
unbuffered, so we spare ourselves from the pains of writing a
newline as well... */
asm volatile (
"syscall"
: "=a"(rc)
: "a"(SYS_write), "D"(STDERR_FILENO), "S"(&addr), "d"(sizeof(addr))
: "rcx", "r11", "memory"
);
asm volatile (
"syscall"
: "=a"(rc)
: "a"(SYS_read), "D"(STDIN_FILENO), "S"(buf), "d"(sizeof(buf))
: "rcx", "r11", "memory"
);
if (rc > 0) {
/* No error, so we can exit successfully. */
rc = 0;
}
asm volatile (
"syscall"
: "=a"(rc)
: "a"(SYS_exit), "D"(rc)
: "rcx", "r11", "memory"
);
}
12 changes: 12 additions & 0 deletions dev/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,18 @@ fn prepare_test_files() {
let src = data_dir.join("test-mnt-ns.c");
cc(&src, "test-mnt-ns.bin", &[]);

let src = data_dir.join("test-block.c");
let ld_script = data_dir.join("test-block-augmented.ld");
let args = &[
"-static",
"-Wl,--build-id=none",
"-nostdlib",
// Just passing the linker script as "regular" input file causes
// it to "augment" the default linker script.
ld_script.to_str().unwrap(),
];
cc(&src, "test-block.bin", args);

cc_stable_addrs(
"test-stable-addrs.bin",
&["-gdwarf-4", "-Wl,--build-id=none", "-O0"],
Expand Down
64 changes: 64 additions & 0 deletions tests/blazesym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,70 @@ fn symbolize_process_with_custom_dispatch() {
test(process_no_dispatch);
}

/// Symbolize a normalized address from a binary with an artificially
/// inflated ELF segment.
///
/// This is a regression test for the case that a program header
/// with a memory size greater than file size is located before a
/// program header that would otherwise match the file offset. Refer
/// to commit 1a4e10740652 ("Use file size in file offset -> virtual
/// offset translation").
#[cfg(linux)]
#[test]
fn symbolize_normalized_large_memsize() {
let test_block = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-block.bin");
let mut child = Command::new(&test_block)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let pid = child.id();
defer!({
// Best effort only. The child may end up terminating gracefully
// if everything goes as planned.
// TODO: Ideally this kill would be pid FD based to eliminate
// any possibility of killing the wrong entity.
let _rc = unsafe { libc::kill(pid as _, libc::SIGKILL) };
});

let mut buf = [0u8; size_of::<Addr>()];
let count = child
.stderr
.as_mut()
.unwrap()
.read(&mut buf)
.expect("failed to read child output");
assert_eq!(count, buf.len());
let addr = Addr::from_ne_bytes(buf);
let pid = Pid::from(child.id());

let normalizer = Normalizer::new();
let normalized = normalizer
.normalize_user_addrs(pid, [addr].as_slice())
.unwrap();

assert_eq!(normalized.outputs.len(), 1);
assert_eq!(normalized.meta.len(), 1);
let file_offset = normalized.outputs[0].0;

let elf = symbolize::Elf::new(test_block);
let src = symbolize::Source::Elf(elf);
let symbolizer = Symbolizer::new();
let sym = symbolizer
.symbolize_single(&src, symbolize::Input::FileOffset(file_offset))
.unwrap()
.into_sym()
.unwrap();
assert_eq!(sym.name, "_start");

// "Signal" the child to terminate gracefully.
let () = child.stdin.as_ref().unwrap().write_all(&[0x04]).unwrap();
let _status = child.wait().unwrap();
}

/// Check that we can normalize addresses in an ELF shared object.
#[cfg(linux)]
#[test]
Expand Down

0 comments on commit ab0e157

Please sign in to comment.