microscope is a Linux x86-64 native debugger written in Rust. Keep the implementation small and exact. Unsupported states and partial results must be visible; never guess when the result could look correct.
Run project commands inside the pinned Nix environment. For non-interactive
work, use just dev --command, for example:
just dev --command just test breakpointDo not run Cargo, Just, compilers, linters, or tests directly from the host.
Read the pages that match the work:
| Work | Read first |
|---|---|
| Repository setup or an unfamiliar subsystem | dev/index.md, dev/architecture.md |
| New data structures, ids, or numeric parameters | dev/style.md |
| Run control, breakpoints, signals, or lifecycle | dev/process-control.md, dev/testing.md |
| ELF, DWARF, unwinding, images, or the cache | dev/debug-information.md |
| Go goroutines, Rust coroutines, tokio tasks, or CPython | dev/runtime-models.md, dev/testing.md |
| Recording, captures, or profile analysis | dev/profiling.md, dev/performance.md |
| System call tracing, its BPF programs, or catchpoints | dev/syscalls.md |
| Expressions, inspection, views, or visualizers | dev/evaluation-and-views.md, docs/expressions.md, docs/views.md |
| Tests or native fixtures | dev/testing.md |
| Diagnosing a failure or reading recordings | dev/debugging-failures.md |
| Simulator or kernel model | dev/simulator.md |
| Timings, allocation, or benchmarks | dev/performance.md |
| Browser client or web protocol | dev/web-ui.md |
| Documentation | dev/documentation.md |
| Assertions, invariants, or what happens on a panic | dev/assertions.md |
The files in plans/ are decision logs and implementation records. They are
not current user documentation. Do not rewrite them while changing docs.
- Keep the public model and request/event protocol independent of ELF, DWARF, ptrace, and native register layouts.
ModuleImageis immutable static metadata.LoadedModuleis a runtime mapping. Never mixImageAddressandVirtualAddress.- UI clients use
DebuggerHandle, bounded request channels, events, and immutable snapshots. They never share mutable debugger state. - Only the controller OS thread that created the tracee may call ptrace. The
waiter may call
waitpidand send messages to the controller. - The public debugger is all-stop. Publish a stop only after every live thread is known to be stopped.
- A stopped-state mutation must carry the
StopIdthat the client observed. Reject stale requests. - Keep physical breakpoint installation separate from user and execution-plan ownership. Hide trap bytes from user memory reads.
- Debug-information providers normalize data at their boundary. The evaluator does not calculate target layouts or read a process except through its traits.
- Resolve source paths while loading metadata, but read source files lazily and outside the controller thread.
- Assert internal invariants that the type system cannot prove, and never
assert on input. Use
debug_assert!by default andassert!only when continuing would damage the tracee. Seedev/assertions.md. - Production code never calls
unwrap,expect,panic!, orunreachable!; Clippy denies them outside tests. Return an error instead. - Unsafe code is denied unless narrowly required. Every exception needs a safety comment and must pass the configured lints. Unsafe code must be tested under Miri.
Write a behavioral test before fixing a bug and watch it fail. Use the public
tests/support::Scenario path for debugger workflows. Tests must wait for
observable state, enforce deadlines, and leave no inferior process behind.
A protocol of threads and locks whose orderings a test can't force gets a
loom model in loom/; see dev/testing.md.
During development, run the focused tests and checks that finish in seconds. Run the broader gate once the change is ready:
just # formatting, Clippy, tests, and web checks
just all # full pre-commit gateRun just stress before merging lifecycle, run-control, attach, or concurrency
changes. Run just sim 600 for those changes and for simulator model changes.