Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,72 @@
source: symbolic-debuginfo/tests/test_objects.rs
expression: "FunctionsDebug::variables(&session, &functions)"
---
> main (0x3e)
> main (0x80)
variables:
result (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x80: frame base -36

> optimized_out (0x18)
variables:
a (parameter)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x18: frame base -36
gone (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x18: frame base -20

> external_call (0x33)
variables:
a (parameter)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x33: frame base -36
kept (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x33: frame base -20
r (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x33: frame base -24

> across_call (0x32)
variables:
a (parameter)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x32: frame base -36
doubled (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x32: frame base -20
other (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x32: frame base -24

> registers (0x2d)
variables:
a (parameter)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x2d: frame base -36
b (parameter)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x2d: frame base -40
sum (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x2d: frame base -20
product (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x2d: frame base -24

> inlining (0x21)
variables:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
source: symbolic-debuginfo/tests/test_objects.rs
expression: "FunctionsDebug::variables(&session, &functions)"
---
> main (0x32)
variables:
result (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x28..0x31: register 3

> optimized_out (0x4)
variables:
a (parameter)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x4: register 5

> external_call (0x20)
variables:
a (parameter)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x14: register 5
0x14..0x1f: register 6
kept (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x10..0x1c: register 3
r (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x15..0x1b: register 0

> across_call (0xd)
variables:
a (parameter)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0xd: register 5
doubled (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x3..0xd: register 1
other (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0xa..0xc: register 0

> registers (0x9)
variables:
a (parameter)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x9: register 5
b (parameter)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x6: register 4
sum (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x3..0x8: register 0
product (local)
type:
> Primitive: int (SignedInt, 4 bytes)
0x6..0x9: register 4

> inlining (0x1)
variables:
outer (parameter)
type:
> Primitive: int (SignedInt, 4 bytes)
0x0..0x1: register 5

> aggregates (0x1)

> pointers (0x1)
variables:
num (parameter)
type:
> Pointer (8 bytes)
> Primitive: int (SignedInt, 4 bytes)
0x0..0x1: register 5
num_ptr (parameter)
type:
> Pointer (8 bytes)
> Pointer (8 bytes)
> Primitive: int (SignedInt, 4 bytes)
0x0..0x1: register 4
str (parameter)
type:
> Pointer (8 bytes)
> Unknown
0x0..0x1: register 1
any (parameter)
type:
> Unknown
0x0..0x1: register 2
fn (parameter)
type:
> Pointer (8 bytes)
> Unknown
0x0..0x1: register 8

> primitives (0x1)
16 changes: 16 additions & 0 deletions symbolic-debuginfo/tests/test_objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,22 @@
Ok(())
}

#[test]
fn test_elf_variables_opt() -> Result<(), Error> {

Check failure on line 600 in symbolic-debuginfo/tests/test_objects.rs

View check run for this annotation

@sentry/warden / warden: wrdn-dos-review

Object::parse default options allow unbounded ELF section decompression allocation

`Object::parse` uses default options where `max_decompressed_section_size` is `None` (treated as `usize::MAX`), so a crafted ELF with a compressed section declaring a huge `ch_size` causes an unbounded `Vec::with_capacity` allocation during `debug_session`, leading to an OOM process abort.
Comment thread
JoshuaMoelans marked this conversation as resolved.
let view = ByteView::open(fixture("linux/variables_opt"))?;
let object = Object::parse(&view)?;

let session = object.debug_session()?;
let functions = session.functions().collect::<Result<Vec<_>, _>>()?;

insta::assert_debug_snapshot!(
"elf_variables_opt",
FunctionsDebug::variables(&session, &functions)
);

Ok(())
}

fn elf_debug_crc() -> Result<u32, Error> {
Ok(u32::from_str_radix(
std::fs::read_to_string(fixture("linux/elf_debuglink/gen/debug_info.txt.crc"))?.trim(),
Expand Down
Binary file modified symbolic-testutils/fixtures/linux/variables
Binary file not shown.
Binary file added symbolic-testutils/fixtures/linux/variables_opt
Binary file not shown.
46 changes: 36 additions & 10 deletions symbolic-testutils/variables/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Variables fixture

Source for the debug info fixtures used by the variable extraction tests, currently
`fixtures/linux/variables` (ELF/DWARF 5, x86-64), asserted by `test_elf_variables` in
`symbolic-debuginfo/tests/test_objects.rs`.
`variables.c` is the single source for the debug info fixtures (ELF/DWARF 5, x86-64) used by the
variable extraction tests in `symbolic-debuginfo/tests/test_objects.rs`. It is compiled twice:

`variables.c` is our fixture which is meant to grow along with symbolic's variable support.
- `fixtures/linux/variables` (`-O0`), asserted by `test_elf_variables`. Covers types and variable
kinds; at `-O0` every variable has a single whole-function stack location.
- `fixtures/linux/variables_opt` (`-O2`), asserted by `test_elf_variables_opt`. Covers locations:
registers, sub-function ranges, and multi-range location lists.

The fixture is meant to grow along with symbolic's variable support.

To run the test without rebuilding the snapshot, use:

Expand All @@ -14,8 +18,8 @@ cargo test -p symbolic-debuginfo --test test_objects test_elf_variables

## Rebuilding

If you make changes to `variables.c`, updating the snapshot takes two steps. First, rebuild the
fixture — run this from *this* directory; it writes `../fixtures/linux/variables`:
If you make changes to the C sources, updating the snapshots takes two steps. First, rebuild the
fixtures — run this from *this* directory; it writes them under `../fixtures/linux/`:

```sh
docker run --rm --platform linux/amd64 \
Expand Down Expand Up @@ -61,9 +65,31 @@ the snapshot is the record of that gap; implementing origin-following will make
Currently not covered, worth adding when the surrounding support lands:

- `PrimitiveTypeEncoding::Address` — no ordinary C type on this target maps to `DW_ATE_address`.
- `Location::Register` and multi-range location lists — these need an optimized build, since at
`-O0` GCC spills every local to the stack and every variable gets a single whole-function
`DW_OP_fbreg` location. Adding an `-O2` variant of the same source is the natural next step, but
it needs `noinline`/`volatile` scaffolding to stop the optimizer deleting the fixture outright.
- Variables optimized down to a `DW_AT_const_value` instead of a location (`gone` in
`optimized_out`) — symbolic currently drops these entirely, so they do not even appear as an
empty entry. When support lands, `gone` will show up as a snapshot diff.
- Non-DWARF formats. The same source should build to a PDB and a dSYM once those backends grow
variable support.

## The optimized fixture

The `-O2` build of the same source produces the location shapes that never occur at `-O0`:
register locations, ranges shorter than the function, and location lists with multiple entries.
Keeping a variable alive under the optimizer needs scaffolding, documented in `variables.c`
itself; the short version is `NOINLINE` on every function, opaque inputs read from a `volatile`
global, and a `USE(x)` asm marker — a plain `(void)x` cast does not survive `-O2`. All of it is
harmless at `-O0`, so both fixtures build from one source of truth.

The type-oriented functions carry no such scaffolding, so the optimizer deletes most of their
variables and their blocks render (nearly) empty in the optimized snapshot — deliberately, as a
record of what optimization does to them.

Two things to know when extending the location coverage:

- A call to a function in the same file does *not* force values out of call-clobbered registers:
GCC's interprocedural register allocation sees the callee's real clobbers. Multi-range lists
come from calls to external functions (`rand()` in `external_call`), which GCC must assume
clobber everything.
- `-O2` register allocation is only stable under the pinned compiler image. Expect any edit to an
existing function to rewrite that function's whole snapshot block — the per-function churn
containment still holds, but within a function, ranges and register numbers move freely.
1 change: 1 addition & 0 deletions symbolic-testutils/variables/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ set -eu
cd "$(dirname "$0")"

gcc -g -gdwarf-5 -O0 -o ../fixtures/linux/variables variables.c
gcc -g -gdwarf-5 -O2 -o ../fixtures/linux/variables_opt variables.c
Loading
Loading