The first pass of a debugging session is usually messy. The second pass should be repeatable.

Session shape

Start with the exact target build, debugger version, symbol path, and boot flags. Write down every command that changes the state of the target.

When the target exposes a suspicious parser path, I usually mark terms like DriverEntry, IOCTL, and dispatch routine inline so the important handles are easy to scan later.

target: sample-vm
symbols: srv*C:\symbols*https://msdl.microsoft.com/download/symbols
breaks: DriverEntry, dispatch routine, vulnerable path
#include <cstdint>
#include <iostream>

struct PacketHeader {
    std::uint32_t magic;
    std::uint16_t version;
    std::uint16_t length;
};

bool is_valid_header(const PacketHeader& header) {
    constexpr std::uint32_t expected_magic = 0xfeedface;

    if (header.magic != expected_magic) {
        return false;
    }

    return header.version == 1 && header.length >= sizeof(PacketHeader);
}

int main() {
    PacketHeader header {
        .magic = 0xfeedface,
        .version = 1,
        .length = 128,
    };

    std::cout << "valid=" << is_valid_header(header) << "\n";
    return 0;
}

Useful habit

Keep one file for commands and one file for observations. Commands describe how to replay the session. Observations describe what changed your mind.