{"record":{"id":"1843a5f4abdcf45e","repo":"firecracker-microvm/firecracker","slug":"error-converting-cpu-id-to-tid","errorCode":null,"errorMessage":"Error converting cpu id to Tid","messagePattern":"Error converting cpu id to Tid","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/vmm/src/gdb/event_loop.rs","lineNumber":63,"sourceCode":"\n    type StopReason = MultiThreadStopReason<u64>;\n\n    /// Poll for events from either Vcpu's or packets from the GDB connection\n    fn wait_for_stop_reason(\n        target: &mut FirecrackerTarget,\n        conn: &mut Self::Connection,\n    ) -> Result<\n        run_blocking::Event<MultiThreadStopReason<u64>>,\n        run_blocking::WaitForStopReasonError<\n            <Self::Target as Target>::Error,\n            <Self::Connection as Connection>::Error,\n        >,\n    > {\n        loop {\n            match target.gdb_event.try_recv() {\n                Ok(cpu_id) => {\n                    // The Vcpu reports it's id from raw_id so we straight convert here\n                    let tid = Tid::new(cpu_id).expect(\"Error converting cpu id to Tid\");\n                    // If notify paused returns false this means we were already debugging a single\n                    // core, the target will track this for us to pick up later\n                    target.set_paused_vcpu(tid);\n                    trace!(\"Vcpu: {tid:?} paused from debug exit\");\n\n                    let stop_reason = target\n                        .get_stop_reason(tid)\n                        .map_err(WaitForStopReasonError::Target)?;\n\n                    let Some(stop_response) = stop_reason else {\n                        // If we returned None this is a break which should be handled by\n                        // the guest kernel (e.g. kernel int3 self testing) so we won't notify\n                        // GDB and instead inject this back into the guest\n                        target\n                            .inject_bp_to_guest(tid)\n                            .map_err(WaitForStopReasonError::Target)?;\n                        target\n                            .resume_vcpu(tid)","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/firecracker-microvm/firecracker/blob/0a745def42ddf4cc2a744d79a08a27ff50b5d27a/src/vmm/src/gdb/event_loop.rs#L45-L81","documentation":"Panic in the GDB blocking event loop (event_loop.rs:63): `Tid::new(cpu_id).expect(\"Error converting cpu id to Tid\")`. In gdbstub, `Tid::new` returns None for an id of 0 because the GDB remote protocol treats thread ids as nonzero. The code converts the vcpu's raw id directly — vcpu ids are 0-indexed, so a debug exit reported by vcpu 0 produces tid 0 and panics. Note `get_raw_tid` in target.rs correctly does `cpu_id + 1`; this call site does not.","triggerScenarios":"Any KVM debug-exit event delivered by vcpu 0 (the typical case — the entry breakpoint is set on vcpu 0) reaches `Tid::new(0)`, which returns None and trips the expect.","commonSituations":"Connecting GDB and hitting the initial entry breakpoint on the first core; single-stepping or breaking on CPU 0 of a multi-vcpu guest. Anyone using the gdb feature on a single-vcpu microvm hits it deterministically.","solutions":["Apply the 1-indexing used elsewhere in the file: `Tid::new(get_raw_tid(cpu_id))` (i.e. cpu_id + 1) before constructing the Tid","Until fixed, avoid the affected path (initial breakpoint on vcpu 0) — e.g. don't enable the gdb feature on versions with this bug","Add a regression test that feeds cpu_id 0 through the event loop"],"exampleFix":"// before\nlet tid = Tid::new(cpu_id).expect(\"Error converting cpu id to Tid\");\n\n// after (vcpu ids are 0-indexed; GDB tids are 1-indexed — see get_raw_tid)\nlet tid = Tid::new(get_raw_tid(cpu_id)).expect(\"Error converting cpu id to Tid\");","handlingStrategy":"type-guard","validationCode":"// Map 0-indexed vcpu ids to 1-indexed GDB tids BEFORE constructing Tid\nlet tid_value = cpu_id + 1; // get_raw_tid semantics\nif tid_value == 0 { /* unreachable after +1, kept as guard */ return; }","typeGuard":"fn to_valid_tid(cpu_id: usize) -> Option<Tid> {\n    // GDB remote protocol thread ids are nonzero; vcpu ids are 0-indexed\n    Tid::new(cpu_id.checked_add(1)?)\n}","tryCatchPattern":null,"preventionTips":["Always use get_raw_tid(cpu_id) when crossing the vcpu-id <-> GDB-tid boundary","Fuzz the event loop with cpu_id 0 in unit tests","Document the 1-indexing invariant next to every Tid::new call site"],"tags":["rust","panic","gdb","gdbstub","off-by-one","thread-id","firecracker"],"backgroundTag":"gdb-invalid-thread-id","analyzedSha":"0a745def42ddf4cc2a744d79a08a27ff50b5d27a","analyzedAt":"2026-08-19T05:27:02.517Z","contentChangedAt":"2026-08-19T05:27:02.517Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}