#[tokio::test] asyncfn test_simple() { letmut f = TestFixture::new(); letmut stack = Section::new();
stack.start().set_const(0x80000000);
stack = stack.D32(0).D32(0); // end-of-stack marker
f.raw.eip = 0x40000200;
f.raw.ebp = 0x80000000; let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 1); let f = &s.frames[0]; let m = f.module.as_ref().unwrap();
assert_eq!(m.code_file(), "module1");
}
// Walk a traditional frame. A traditional frame saves the caller's // %ebp just below the return address, and has its own %ebp pointing // at the saved %ebp. #[tokio::test] asyncfn test_traditional() { letmut f = TestFixture::new(); let frame0_ebp = Label::new(); let frame1_ebp = Label::new(); letmut stack = Section::new();
stack.start().set_const(0x80000000);
stack = stack
.append_repeated(12, 0) // frame 0: space
.mark(&frame0_ebp) // frame 0 %ebp points here
.D32(&frame1_ebp) // frame 0: saved %ebp
.D32(0x40008679) // frame 0: resume address
.append_repeated(8, 0) // frame 1: space
.mark(&frame1_ebp) // frame 1 %ebp points here
.D32(0) // frame 1: saved %ebp (stack end)
.D32(0); // frame 1: return address (stack end)
f.raw.eip = 0x4000c7a5;
f.raw.esp = stack.start().value().unwrap() as u32;
f.raw.ebp = frame0_ebp.value().unwrap() as u32; let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
{ let f0 = &s.frames[0];
assert_eq!(f0.trust, FrameTrust::Context);
assert_eq!(f0.context.valid, MinidumpContextValidity::All);
assert_eq!(f0.instruction, 0x4000c7a5);
assert_eq!(f0.resume_address, 0x4000c7a5); // eip // ebp
}
{ let f1 = &s.frames[1];
assert_eq!(f1.trust, FrameTrust::FramePointer); // ContextValidity
assert_eq!(f1.instruction, 0x40008678);
assert_eq!(f1.resume_address, 0x40008679); // eip // ebp
}
}
// Walk a traditional frame, but use a bogus %ebp value, forcing a scan // of the stack for something that looks like a return address. #[tokio::test] asyncfn test_traditional_scan() { letmut f = TestFixture::new(); let frame1_esp = Label::new(); let frame1_ebp = Label::new(); letmut stack = Section::new(); let stack_start = 0x80000000;
stack.start().set_const(stack_start);
stack = stack // frame 0
.D32(0xf065dc76) // locals area:
.D32(0x46ee2167) // garbage that doesn't look like
.D32(0xbab023ec) // a return address
.D32(&frame1_ebp) // saved %ebp (%ebp fails to point here, forcing scan)
.D32(0x4000129d) // return address // frame 1
.mark(&frame1_esp)
.append_repeated(8, 0) // space
.mark(&frame1_ebp) // %ebp points here
.D32(0) // saved %ebp (stack end)
.D32(0); // return address (stack end)
f.raw.eip = 0x4000f49d;
f.raw.esp = stack.start().value().unwrap() as u32; // Make the frame pointer bogus, to make the stackwalker scan the stack // for something that looks like a return address.
f.raw.ebp = 0xd43eed6e;
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
{ // To avoid reusing locals by mistake let f0 = &s.frames[0];
assert_eq!(f0.trust, FrameTrust::Context);
assert_eq!(f0.context.valid, MinidumpContextValidity::All);
assert_eq!(f0.instruction, 0x4000f49d);
assert_eq!(f0.resume_address, 0x4000f49d);
// Force scanning for a return address a long way down the stack #[tokio::test] asyncfn test_traditional_scan_long_way() { letmut f = TestFixture::new(); let frame1_esp = Label::new(); let frame1_ebp = Label::new(); letmut stack = Section::new(); let stack_start = 0x80000000;
stack.start().set_const(stack_start);
stack = stack // frame 0
.D32(0xf065dc76) // locals area:
.D32(0x46ee2167) // garbage that doesn't look like
.D32(0xbab023ec) // a return address
.append_repeated(20 * 4, 0) // a bunch of space
.D32(&frame1_ebp) // saved %ebp (%ebp fails to point here, forcing scan)
.D32(0x4000129d) // return address // frame 1
.mark(&frame1_esp)
.append_repeated(8, 0) // space
.mark(&frame1_ebp) // %ebp points here
.D32(0) // saved %ebp (stack end)
.D32(0); // return address (stack end)
f.raw.eip = 0x4000f49d;
f.raw.esp = stack.start().value().unwrap() as u32; // Make the frame pointer bogus, to make the stackwalker scan the stack // for something that looks like a return address.
f.raw.ebp = 0xd43eed6e;
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
{ // To avoid reusing locals by mistake let f0 = &s.frames[0];
assert_eq!(f0.trust, FrameTrust::Context);
assert_eq!(f0.context.valid, MinidumpContextValidity::All);
assert_eq!(f0.instruction, 0x4000f49d);
let expected = f.raw.clone(); let expected_regs = CALLEE_SAVE_REGS; let expected_valid = MinidumpContextValidity::Some(expected_regs.iter().copied().collect());
let stack = Section::new();
stack
.start()
.set_const(f.raw.get_register("esp", &raw_valid).unwrap() as u64);
(f, stack, expected, expected_valid)
}
asyncfn check_cfi(
f: TestFixture,
stack: Section,
expected: CONTEXT_X86,
expected_valid: MinidumpContextValidity,
) { let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
// Totally basic STACK WIN frame data, no weird stuff. #[tokio::test] asyncfn test_stack_win_frame_data_overlapping() { // Same as frame_data_basic but there are extra entries which technically overlap // with this one, but in a way that is easily disambiguated by preferring the // one with the higher base address. This happens frequently in real symbol files. letmut f = TestFixture::new(); let symbols = [ // Entry that covers the "whole" function (junk!) "STACK WIN 4 aa80 181 0 0 4 10 4 0 1", " $eip .raSearchStart =\n", // More precise (still junk!) "STACK WIN 4 aa84 177 0 0 4 10 4 0 1", " $eip .raSearchStart =\n", // This is the one we want!!! "STACK WIN 4 aa85 176 0 0 4 10 4 0 1", " $T2 $esp .cbSavedRegs + =", " $T0 .raSearchStart =", " $eip $T0 ^ =", " $esp $T0 4 + =", " $ebx $T2 4 - ^ =", " $edi $T2 8 - ^ =", " $esi $T2 12 - ^ =", " $ebp $T2 16 - ^ =\n", // An even more precise one but past the address we care about (junk!) "STACK WIN 4 aa86 175 0 0 4 10 4 0 1", " $eip .raSearchStart =\n",
];
f.add_symbols(String::from("module1"), symbols.concat());
let frame1_esp = Label::new(); let frame1_ebp = Label::new();
letmut stack = Section::new(); let stack_start = 0x80000000;
stack.start().set_const(stack_start);
// Testing that grand_callee_parameter_size is properly computed. #[tokio::test] asyncfn test_stack_win_frame_data_parameter_size() { letmut f = TestFixture::new();
let module1_symbols = ["FUNC 1000 100 c module1::wheedle\n"];
let frame0_esp = Label::new(); let frame0_ebp = Label::new(); let frame1_esp = Label::new(); let frame2_esp = Label::new(); let frame2_ebp = Label::new();
letmut stack = Section::new(); let stack_start = 0x80000000;
stack.start().set_const(stack_start);
stack = stack // frame 0, in module1::wheedle. Traditional frame.
.mark(&frame0_esp)
.append_repeated(0, 16) // frame space
.mark(&frame0_ebp)
.D32(0x6fa902e0) // saved %ebp. Not a frame pointer.
.D32(0x5000aa95) // return address, in module2::whine // frame 1, in module2::whine. FrameData frame.
.mark(&frame1_esp)
.D32(0xbaa0cb7a) // argument 3 passed to module1::wheedle
.D32(0xbdc92f9f) // argument 2
.D32(0x0b1d8442) // argument 1
.D32(&frame2_ebp) // saved %ebp
.D32(0xb1b90a15) // unused
.D32(0xf18e072d) // unused
.D32(0x2558c7f3) // saved %ebx
.D32(0x0365e25e) // unused
.D32(0x2a179e38) // return address; $T0 points here // frame 2, in no module
.mark(&frame2_esp)
.append_repeated(0, 12) // empty space
.mark(&frame2_ebp)
.D32(0) // saved %ebp (stack end)
.D32(0); // saved %eip (stack end)
f.raw.set_register("eip", 0x40001004);
f.raw
.set_register("esp", stack.start().value().unwrap() as u32);
f.raw
.set_register("ebp", frame0_ebp.value().unwrap() as u32);
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 3);
#[tokio::test] asyncfn test_frame_pointer_overflow() { // Make sure we don't explode when trying frame pointer analysis on a value // that will overflow.
type Pointer = u32; let stack_max: Pointer = Pointer::MAX; let stack_size: Pointer = 1000; let bad_frame_ptr: Pointer = stack_max;
letmut f = TestFixture::new(); letmut stack = Section::new(); let stack_start: Pointer = stack_max - stack_size;
stack.start().set_const(stack_start as u64);
stack = stack // frame 0
.append_repeated(0, stack_size as usize); // junk, not important to the test
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 1);
// As long as we don't panic, we're good!
}
#[tokio::test] asyncfn test_frame_pointer_overflow_nonsense_32bit_stack() { // same as test_frame_pointer_overflow, but we're going to abuse the fact // that rust-minidump prefers representing things in 64-bit to create // impossible stack addresses that overflow 32-bit integers but appear // valid in 64-bit. By doing this memory reads will "succeed" but // pointer math done in the native pointer width will overflow and // everything will be sad.
type Pointer = u32; let pointer_size: u64 = std::mem::size_of::<Pointer>() as u64; let stack_max: u64 = Pointer::MAX as u64 + pointer_size * 2; let stack_size: u64 = 1000; let bad_frame_ptr: u64 = Pointer::MAX as u64 - pointer_size;
letmut f = TestFixture::new(); letmut stack = Section::new(); let stack_start: u64 = stack_max - stack_size;
stack.start().set_const(stack_start);
stack = stack // frame 0
.append_repeated(0, 1000); // junk, not important to the test
f.raw.eip = 0x7a100000;
f.raw.ebp = bad_frame_ptr as u32;
f.raw.esp = stack.start().value().unwrap() as Pointer;
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 1);
// As long as we don't panic, we're good!
}
#[tokio::test] asyncfn test_frame_pointer_barely_no_overflow() { // This is test_tradition but with the all the values pushed // as close to the upper memory boundary as possible, to confirm that // our code doesn't randomly overflow *AND* isn't overzealous in // its overflow guards.
letmut f = TestFixture::new(); letmut stack = Section::new();
type Pointer = u32; let pointer_size: Pointer = std::mem::size_of::<Pointer>() as Pointer; let stack_max: Pointer = Pointer::MAX; let stack_size: Pointer = pointer_size * 3;
let stack_start: Pointer = stack_max - stack_size; let return_address: Pointer = 0x7b302000;
stack.start().set_const(stack_start as u64);
let frame0_fp = Label::new(); let frame1_sp = Label::new(); let frame1_fp = Label::new();
stack = stack // frame 0
.mark(&frame0_fp)
.D32(&frame1_fp) // caller-pushed %rbp
.D32(return_address) // actual return address // frame 1
.mark(&frame1_sp)
.mark(&frame1_fp) // end of stack
.D32(0);
f.raw.eip = 0x7a100000;
f.raw.ebp = frame0_fp.value().unwrap() as Pointer;
f.raw.esp = stack.start().value().unwrap() as Pointer;
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
{ // To avoid reusing locals by mistake let f0 = &s.frames[0];
assert_eq!(f0.trust, FrameTrust::Context);
assert_eq!(f0.context.valid, MinidumpContextValidity::All); iflet MinidumpRawContext::X86(ctx) = &f0.context.raw {
assert_eq!(ctx.ebp, frame0_fp.value().unwrap() as Pointer);
} else {
unreachable!();
}
}
{ // To avoid reusing locals by mistake let f1 = &s.frames[1];
assert_eq!(f1.trust, FrameTrust::FramePointer); iflet MinidumpContextValidity::Some(ref which) = f1.context.valid {
assert!(which.contains("eip"));
assert!(which.contains("esp"));
assert!(which.contains("ebp"));
} else {
unreachable!();
} iflet MinidumpRawContext::X86(ctx) = &f1.context.raw {
assert_eq!(ctx.eip, return_address);
assert_eq!(ctx.esp, frame1_sp.value().unwrap() as Pointer);
assert_eq!(ctx.ebp, frame1_fp.value().unwrap() as Pointer);
} else {
unreachable!();
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.27 Sekunden
(vorverarbeitet am 2026-06-17)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.