Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  x86_unittest.rs   Sprache: unbekannt

 
Untersuchungsergebnis.rs Download desUnknown {[0] [0] [0]}zum Wurzelverzeichnis wechseln

// Copyright 2015 Ted Mielczarek. See the COPYRIGHT
// file at the top-level directory of this distribution.

use crate::*;
use minidump::format::CONTEXT_X86;
use minidump::system_info::{Cpu, Os};
use std::collections::HashMap;
use test_assembler::*;

struct TestFixture {
    pub raw: CONTEXT_X86,
    pub modules: MinidumpModuleList,
    pub symbols: HashMap<String, String>,
}

impl TestFixture {
    pub fn new() -> TestFixture {
        TestFixture {
            raw: CONTEXT_X86::default(),
            // Give the two modules reasonable standard locations and names
            // for tests to play with.
            modules: MinidumpModuleList::from_modules(vec![
                MinidumpModule::new(0x40000000, 0x10000, "module1"),
                MinidumpModule::new(0x50000000, 0x10000, "module2"),
            ]),
            symbols: HashMap::new(),
        }
    }

    pub async fn walk_stack(&self, stack: Section) -> CallStack {
        let context = MinidumpContext {
            raw: MinidumpRawContext::X86(self.raw.clone()),
            valid: MinidumpContextValidity::All,
        };
        let base = stack.start().value().unwrap();
        let size = stack.size();
        let stack = stack.get_contents().unwrap();
        let stack_memory = MinidumpMemory {
            desc: Default::default(),
            base_address: base,
            size,
            bytes: &stack,
            endian: scroll::LE,
        };
        let system_info = SystemInfo {
            os: Os::Windows,
            os_version: None,
            os_build: None,
            cpu: Cpu::X86,
            cpu_info: None,
            cpu_microcode_version: None,
            cpu_count: 1,
        };
        let symbolizer = Symbolizer::new(string_symbol_supplier(self.symbols.clone()));
        let mut stack = CallStack::with_context(context);

        walk_stack(
            0,
            (),
            &mut stack,
            Some(UnifiedMemory::Memory(&stack_memory)),
            &self.modules,
            &system_info,
            &symbolizer,
        )
        .await;

        stack
    }

    pub fn add_symbols(&mut self, name: String, symbols: String) {
        self.symbols.insert(name, symbols);
    }
}

#[tokio::test]
async fn test_simple() {
    let mut f = TestFixture::new();
    let mut 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]
async fn test_traditional() {
    let mut f = TestFixture::new();
    let frame0_ebp = Label::new();
    let frame1_ebp = Label::new();
    let mut 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]
async fn test_traditional_scan() {
    let mut f = TestFixture::new();
    let frame1_esp = Label::new();
    let frame1_ebp = Label::new();
    let mut 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);

        if let MinidumpRawContext::X86(ctx) = &f0.context.raw {
            assert_eq!(ctx.eip, 0x4000f49d);
            assert_eq!(ctx.esp, stack_start as u32);
            assert_eq!(ctx.ebp, 0xd43eed6e);
        } else {
            unreachable!();
        }
    }

    {
        // To avoid reusing locals by mistake
        let f1 = &s.frames[1];
        assert_eq!(f1.trust, FrameTrust::Scan);
        if let MinidumpContextValidity::Some(ref which) = f1.context.valid {
            assert!(which.contains("eip"));
            assert!(which.contains("esp"));
            assert!(which.contains("ebp"));
        } else {
            unreachable!();
        }
        assert_eq!(f1.instruction + 1, 0x4000129d);
        assert_eq!(f1.resume_address, 0x4000129d);

        if let MinidumpRawContext::X86(ctx) = &f1.context.raw {
            assert_eq!(ctx.eip, 0x4000129d);
            assert_eq!(ctx.esp, frame1_esp.value().unwrap() as u32);
            assert_eq!(ctx.ebp, frame1_ebp.value().unwrap() as u32);
        } else {
            unreachable!();
        }
    }
}

// Force scanning for a return address a long way down the stack
#[tokio::test]
async fn test_traditional_scan_long_way() {
    let mut f = TestFixture::new();
    let frame1_esp = Label::new();
    let frame1_ebp = Label::new();
    let mut 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);

        if let MinidumpRawContext::X86(ctx) = &f0.context.raw {
            assert_eq!(ctx.eip, 0x4000f49d);
            assert_eq!(ctx.esp, stack_start as u32);
            assert_eq!(ctx.ebp, 0xd43eed6e);
        } else {
            unreachable!();
        }
    }

    {
        // To avoid reusing locals by mistake
        let f1 = &s.frames[1];
        assert_eq!(f1.trust, FrameTrust::Scan);
        if let MinidumpContextValidity::Some(ref which) = f1.context.valid {
            assert!(which.contains("eip"));
            assert!(which.contains("esp"));
            assert!(which.contains("ebp"));
        } else {
            unreachable!();
        }
        assert_eq!(f1.instruction + 1, 0x4000129d);

        if let MinidumpRawContext::X86(ctx) = &f1.context.raw {
            assert_eq!(ctx.eip, 0x4000129d);
            assert_eq!(ctx.esp, frame1_esp.value().unwrap() as u32);
            assert_eq!(ctx.ebp, frame1_ebp.value().unwrap() as u32);
        } else {
            unreachable!();
        }
    }
}

const CALLEE_SAVE_REGS: &[&str] = &["eip", "esp", "ebp", "ebx", "edi", "esi"];

fn init_cfi_state() -> (TestFixture, Section, CONTEXT_X86, MinidumpContextValidity) {
    let mut f = TestFixture::new();
    let symbols = [
        // The youngest frame's function.
        "FUNC 4000 1000 10 enchiridion\n",
        // Initially, just a return address.
        "STACK CFI INIT 4000 100 .cfa: $esp 4 + .ra: .cfa 4 - ^\n",
        // Push %ebx.
        "STACK CFI 4001 .cfa: $esp 8 + $ebx: .cfa 8 - ^\n",
        // Move %esi into %ebx.  Weird, but permitted.
        "STACK CFI 4002 $esi: $ebx\n",
        // Allocate frame space, and save %edi.
        "STACK CFI 4003 .cfa: $esp 20 + $edi: .cfa 16 - ^\n",
        // Put the return address in %edi.
        "STACK CFI 4005 .ra: $edi\n",
        // Save %ebp, and use it as a frame pointer.
        "STACK CFI 4006 .cfa: $ebp 8 + $ebp: .cfa 12 - ^\n",
        // The calling function.
        "FUNC 5000 1000 10 epictetus\n",
        // Mark it as end of stack.
        "STACK CFI INIT 5000 1000 .cfa: $esp .ra 0\n",
    ];
    f.add_symbols(String::from("module1"), symbols.concat());

    f.raw.set_register("esp", 0x80000000);
    f.raw.set_register("eip", 0x40005510);
    f.raw.set_register("ebp", 0xc0d4aab9);
    f.raw.set_register("ebx", 0x60f20ce6);
    f.raw.set_register("esi", 0x53d1379d);
    f.raw.set_register("edi", 0xafbae234);

    let raw_valid = MinidumpContextValidity::All;

    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)
}

async fn 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);

    {
        // Frame 0
        let frame = &s.frames[0];
        assert_eq!(frame.trust, FrameTrust::Context);
        assert_eq!(frame.context.valid, MinidumpContextValidity::All);
    }

    {
        // Frame 1
        if let MinidumpContextValidity::Some(ref expected_regs) = expected_valid {
            let frame = &s.frames[1];
            let valid = &frame.context.valid;
            assert_eq!(frame.trust, FrameTrust::CallFrameInfo);
            if let MinidumpContextValidity::Some(ref which) = valid {
                assert_eq!(which.len(), expected_regs.len());
            } else {
                unreachable!();
            }

            if let MinidumpRawContext::X86(ctx) = &frame.context.raw {
                for reg in expected_regs {
                    assert_eq!(
                        ctx.get_register(reg, valid),
                        expected.get_register(reg, &expected_valid),
                        "{reg} registers didn't match!"
                    );
                }
                return;
            }
        }
    }
    unreachable!();
}

#[tokio::test]
async fn test_cfi_at_4000() {
    let (mut f, mut stack, mut expected, expected_valid) = init_cfi_state();

    let frame1_rsp = Label::new();
    stack = stack
        .D32(0x40005510) // return address
        .mark(&frame1_rsp)
        .append_repeated(0, 1000);

    expected.set_register("esp", frame1_rsp.value().unwrap() as u32);
    f.raw.set_register("eip", 0x40004000);

    check_cfi(f, stack, expected, expected_valid).await;
}

#[tokio::test]
async fn test_cfi_at_4001() {
    let (mut f, mut stack, mut expected, expected_valid) = init_cfi_state();

    let frame1_rsp = Label::new();
    stack = stack
        .D32(0x60f20ce6) // saved %ebx
        .D32(0x40005510) // return address
        .mark(&frame1_rsp)
        .append_repeated(0, 1000);

    expected.set_register("esp", frame1_rsp.value().unwrap() as u32);
    f.raw.set_register("eip", 0x40004001);
    f.raw.set_register("ebx", 0x91aa9a8b);

    check_cfi(f, stack, expected, expected_valid).await;
}

#[tokio::test]
async fn test_cfi_at_4002() {
    let (mut f, mut stack, mut expected, expected_valid) = init_cfi_state();

    let frame1_rsp = Label::new();
    stack = stack
        .D32(0x60f20ce6) // saved %ebx
        .D32(0x40005510) // return address
        .mark(&frame1_rsp)
        .append_repeated(0, 1000);

    expected.set_register("esp", frame1_rsp.value().unwrap() as u32);
    f.raw.set_register("eip", 0x40004002);
    f.raw.set_register("ebx", 0x53d1379d);
    f.raw.set_register("esi", 0xa5c790ed);

    check_cfi(f, stack, expected, expected_valid).await;
}

#[tokio::test]
async fn test_cfi_at_4003() {
    let (mut f, mut stack, mut expected, expected_valid) = init_cfi_state();

    let frame1_rsp = Label::new();
    stack = stack
        .D32(0x56ec3db7) // garbage
        .D32(0xafbae234) // saved %edi
        .D32(0x53d67131) // garbage
        .D32(0x60f20ce6) // saved %ebx
        .D32(0x40005510) // return address
        .mark(&frame1_rsp)
        .append_repeated(0, 1000);

    expected.set_register("esp", frame1_rsp.value().unwrap() as u32);
    f.raw.set_register("eip", 0x40004003);
    f.raw.set_register("ebx", 0x53d1379d);
    f.raw.set_register("esi", 0xa97f229d);
    f.raw.set_register("edi", 0xb05cc997);

    check_cfi(f, stack, expected, expected_valid).await;
}

#[tokio::test]
async fn test_cfi_at_4004() {
    // Should be the same as 4003
    let (mut f, mut stack, mut expected, expected_valid) = init_cfi_state();

    let frame1_rsp = Label::new();
    stack = stack
        .D32(0x56ec3db7) // garbage
        .D32(0xafbae234) // saved %edi
        .D32(0x53d67131) // garbage
        .D32(0x60f20ce6) // saved %ebx
        .D32(0x40005510) // return address
        .mark(&frame1_rsp)
        .append_repeated(0, 1000);

    expected.set_register("esp", frame1_rsp.value().unwrap() as u32);
    f.raw.set_register("eip", 0x40004004);
    f.raw.set_register("ebx", 0x53d1379d);
    f.raw.set_register("esi", 0xa97f229d);
    f.raw.set_register("edi", 0xb05cc997);

    check_cfi(f, stack, expected, expected_valid).await;
}

#[tokio::test]
async fn test_cfi_at_4005() {
    let (mut f, mut stack, mut expected, expected_valid) = init_cfi_state();

    let frame1_rsp = Label::new();
    stack = stack
        .D32(0xe29782c2) // garbage
        .D32(0xafbae234) // saved %edi
        .D32(0x5ba29ce9) // garbage
        .D32(0x60f20ce6) // saved %ebx
        .D32(0x8036cc02) // garbage
        .mark(&frame1_rsp)
        .append_repeated(0, 1000);

    expected.set_register("esp", frame1_rsp.value().unwrap() as u32);
    f.raw.set_register("eip", 0x40004005);
    f.raw.set_register("ebx", 0x53d1379d);
    f.raw.set_register("esi", 0x0fb7dc4e);
    f.raw.set_register("edi", 0x40005510);

    check_cfi(f, stack, expected, expected_valid).await;
}

#[tokio::test]
async fn test_cfi_at_4006() {
    let (mut f, mut stack, mut expected, expected_valid) = init_cfi_state();

    let frame0_ebp = Label::new();
    let frame1_rsp = Label::new();
    stack = stack
        .D32(0xdcdd25cd) // garbage
        .D32(0xafbae234) // saved %edi
        .D32(0xc0d4aab9) // saved %ebp
        .mark(&frame0_ebp) // frame pointer points here
        .D32(0x60f20ce6) // saved %ebx
        .D32(0x8036cc02) // garbage
        .mark(&frame1_rsp)
        .append_repeated(0, 1000);

    expected.set_register("esp", frame1_rsp.value().unwrap() as u32);
    f.raw
        .set_register("ebp", frame0_ebp.value().unwrap() as u32);
    f.raw.set_register("eip", 0x40004006);
    f.raw.set_register("ebx", 0x53d1379d);
    f.raw.set_register("esi", 0x743833c9);
    f.raw.set_register("edi", 0x40005510);

    check_cfi(f, stack, expected, expected_valid).await;
}

// Totally basic STACK WIN frame data, no weird stuff.
#[tokio::test]
async fn test_stack_win_frame_data_basic() {
    let mut f = TestFixture::new();
    let symbols = [
        "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",
    ];
    f.add_symbols(String::from("module1"), symbols.concat());

    let frame1_esp = Label::new();
    let frame1_ebp = Label::new();

    let mut stack = Section::new();
    let stack_start = 0x80000000;
    stack.start().set_const(stack_start);

    stack = stack
        // frame 0
        .D32(&frame1_ebp) // saved regs: %ebp
        .D32(0xa7120d1a) //             %esi
        .D32(0x630891be) //             %edi
        .D32(0x9068a878) //             %ebx
        .D32(0xa08ea45f) // locals: unused
        .D32(0x40001350) // return address
        // frame 1
        .mark(&frame1_esp)
        .append_repeated(0, 12) // empty space
        .mark(&frame1_ebp)
        .D32(0) // saved %ebp (stack end)
        .D32(0); // saved %eip (stack end)

    f.raw.set_register("eip", 0x4000aa85);
    f.raw
        .set_register("esp", stack.start().value().unwrap() as u32);
    f.raw.set_register("ebp", 0xf052c1de);

    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, 0x4000aa85);

        if let MinidumpRawContext::X86(ctx) = &f0.context.raw {
            assert_eq!(ctx.eip, 0x4000aa85);
            assert_eq!(ctx.esp, stack_start as u32);
            assert_eq!(ctx.ebp, 0xf052c1de);
        } else {
            unreachable!();
        }
    }

    {
        let f1 = &s.frames[1];
        assert_eq!(f1.trust, FrameTrust::CallFrameInfo);
        if let MinidumpContextValidity::Some(ref which) = f1.context.valid {
            assert!(which.contains("eip"));
            assert!(which.contains("esp"));
            assert!(which.contains("ebp"));
            assert!(which.contains("ebx"));
            assert!(which.contains("esi"));
            assert!(which.contains("edi"));
        } else {
            unreachable!();
        }
        assert_eq!(f1.instruction + 1, 0x40001350);

        if let MinidumpRawContext::X86(ctx) = &f1.context.raw {
            assert_eq!(ctx.eip, 0x40001350);
            assert_eq!(ctx.esp, frame1_esp.value().unwrap() as u32);
            assert_eq!(ctx.ebp, frame1_ebp.value().unwrap() as u32);
            assert_eq!(ctx.ebx, 0x9068a878);
            assert_eq!(ctx.esi, 0xa7120d1a);
            assert_eq!(ctx.edi, 0x630891be);
        } else {
            unreachable!();
        }
    }
}

// Totally basic STACK WIN frame data, no weird stuff.
#[tokio::test]
async fn 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.
    let mut 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();

    let mut stack = Section::new();
    let stack_start = 0x80000000;
    stack.start().set_const(stack_start);

    stack = stack
        // frame 0
        .D32(&frame1_ebp) // saved regs: %ebp
        .D32(0xa7120d1a) //             %esi
        .D32(0x630891be) //             %edi
        .D32(0x9068a878) //             %ebx
        .D32(0xa08ea45f) // locals: unused
        .D32(0x40001350) // return address
        // frame 1
        .mark(&frame1_esp)
        .append_repeated(0, 12) // empty space
        .mark(&frame1_ebp)
        .D32(0) // saved %ebp (stack end)
        .D32(0); // saved %eip (stack end)

    f.raw.set_register("eip", 0x4000aa85);
    f.raw
        .set_register("esp", stack.start().value().unwrap() as u32);
    f.raw.set_register("ebp", 0xf052c1de);

    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, 0x4000aa85);

        if let MinidumpRawContext::X86(ctx) = &f0.context.raw {
            assert_eq!(ctx.eip, 0x4000aa85);
            assert_eq!(ctx.esp, stack_start as u32);
            assert_eq!(ctx.ebp, 0xf052c1de);
        } else {
            unreachable!();
        }
    }

    {
        let f1 = &s.frames[1];
        assert_eq!(f1.trust, FrameTrust::CallFrameInfo);
        if let MinidumpContextValidity::Some(ref which) = f1.context.valid {
            assert!(which.contains("eip"));
            assert!(which.contains("esp"));
            assert!(which.contains("ebp"));
            assert!(which.contains("ebx"));
            assert!(which.contains("esi"));
            assert!(which.contains("edi"));
        } else {
            unreachable!();
        }
        assert_eq!(f1.instruction + 1, 0x40001350);

        if let MinidumpRawContext::X86(ctx) = &f1.context.raw {
            assert_eq!(ctx.eip, 0x40001350);
            assert_eq!(ctx.esp, frame1_esp.value().unwrap() as u32);
            assert_eq!(ctx.ebp, frame1_ebp.value().unwrap() as u32);
            assert_eq!(ctx.ebx, 0x9068a878);
            assert_eq!(ctx.esi, 0xa7120d1a);
            assert_eq!(ctx.edi, 0x630891be);
        } else {
            unreachable!();
        }
    }
}

// Testing that grand_callee_parameter_size is properly computed.
#[tokio::test]
async fn test_stack_win_frame_data_parameter_size() {
    let mut f = TestFixture::new();

    let module1_symbols = ["FUNC 1000 100 c module1::wheedle\n"];

    let module2_symbols = [
        // Note bogus parameter size in FUNC record; the stack walker
        // should prefer the STACK WIN record, and see '4' below.
        "FUNC aa85 176 beef module2::whine\n",
        "STACK WIN 4 aa85 176 0 0 4 10 4 0 1",
        " $T2 $esp .cbLocals + .cbSavedRegs + =",
        " $T0 .raSearchStart =",
        " $eip $T0 ^ =",
        " $esp $T0 4 + =",
        " $ebp $T0 20 - ^ =",
        " $ebx $T0 8 - ^ =\n",
    ];
    f.add_symbols(String::from("module1"), module1_symbols.concat());
    f.add_symbols(String::from("module2"), module2_symbols.concat());

    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();

    let mut 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);

    {
        let f0 = &s.frames[0];
        assert_eq!(f0.trust, FrameTrust::Context);
        assert_eq!(f0.context.valid, MinidumpContextValidity::All);
        assert_eq!(f0.instruction, 0x40001004);

        if let MinidumpRawContext::X86(ctx) = &f0.context.raw {
            assert_eq!(ctx.eip, 0x40001004);
            assert_eq!(ctx.esp, frame0_esp.value().unwrap() as u32);
            assert_eq!(ctx.ebp, frame0_ebp.value().unwrap() as u32);
        } else {
            unreachable!();
        }
    }

    {
        let f1 = &s.frames[1];
        assert_eq!(f1.trust, FrameTrust::FramePointer);
        if let MinidumpContextValidity::Some(ref which) = f1.context.valid {
            assert!(which.contains("eip"));
            assert!(which.contains("esp"));
            assert!(which.contains("ebp"));
        } else {
            unreachable!();
        }
        assert_eq!(f1.instruction + 1, 0x5000aa95);

        if let MinidumpRawContext::X86(ctx) = &f1.context.raw {
            assert_eq!(ctx.eip, 0x5000aa95);
            assert_eq!(ctx.esp, frame1_esp.value().unwrap() as u32);
            assert_eq!(ctx.ebp, 0x6fa902e0);
        } else {
            unreachable!();
        }
    }

    {
        let f2 = &s.frames[2];
        assert_eq!(f2.trust, FrameTrust::CallFrameInfo);
        if let MinidumpContextValidity::Some(ref which) = f2.context.valid {
            assert!(which.contains("eip"));
            assert!(which.contains("esp"));
            assert!(which.contains("ebp"));
            assert!(which.contains("ebx"));
        } else {
            unreachable!();
        }
        assert_eq!(f2.instruction + 1, 0x2a179e38);

        if let MinidumpRawContext::X86(ctx) = &f2.context.raw {
            assert_eq!(ctx.eip, 0x2a179e38);
            assert_eq!(ctx.esp, frame2_esp.value().unwrap() as u32);
            assert_eq!(ctx.ebp, frame2_ebp.value().unwrap() as u32);
            assert_eq!(ctx.ebx, 0x2558c7f3);
        } else {
            unreachable!();
        }
    }
}

#[tokio::test]
async fn 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;

    let mut f = TestFixture::new();
    let mut 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

    f.raw.eip = 0x7a100000;
    f.raw.ebp = bad_frame_ptr;
    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]
async fn 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;

    let mut f = TestFixture::new();
    let mut 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]
async fn 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.

    let mut f = TestFixture::new();
    let mut 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);
        if let 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);
        if let MinidumpContextValidity::Some(ref which) = f1.context.valid {
            assert!(which.contains("eip"));
            assert!(which.contains("esp"));
            assert!(which.contains("ebp"));
        } else {
            unreachable!();
        }
        if let 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!();
        }
    }
}

[ zur Elbe Produktseite wechseln0.72Quellennavigators  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge