| 60 | } // anonymous |
| 61 | |
| 62 | KStackTrace walk_kernel_stack(const Engine& eng, const Process& p) { |
| 63 | KStackTrace out{}; |
| 64 | const auto& isf = eng.isf(); |
| 65 | const auto& ks = eng.kallsyms(); |
| 66 | if (!ks.ok || p.task_va == 0) return out; |
| 67 | |
| 68 | u64 ts_stack_off = 0, ts_thread_off = 0, thread_sp_off = 0; |
| 69 | try { |
| 70 | ts_stack_off = isf.field_offset("task_struct", "stack"); |
| 71 | ts_thread_off = isf.field_offset("task_struct", "thread"); |
| 72 | thread_sp_off = isf.field_offset("thread_struct", "sp"); |
| 73 | } catch (const std::exception& e) { |
| 74 | log::debug("kstack: ISF lacks field — {}", e.what()); |
| 75 | return out; |
| 76 | } |
| 77 | |
| 78 | // task.stack is the BASE of the kernel stack (lowest address). The |
| 79 | // stack grows DOWN from base + THREAD_SIZE, so saved-sp >= base. |
| 80 | VAddr stack_base = 0; |
| 81 | if (!kva_read_pod(eng, p.task_va + ts_stack_off, stack_base) || |
| 82 | stack_base == 0) |
| 83 | return out; |
| 84 | VAddr thread_sp = 0; |
| 85 | kva_read_pod(eng, p.task_va + ts_thread_off + thread_sp_off, thread_sp); |
| 86 | |
| 87 | out.stack_base = stack_base; |
| 88 | out.thread_sp = thread_sp; |
| 89 | |
| 90 | // Read the whole THREAD_SIZE window. Kernel stacks are slab-allocated |
| 91 | // (direct-map memory) so kva_read serves us via subtract-direct-map. |
| 92 | std::vector<u8> stack(kThreadSize, 0); |
| 93 | if (!kva_read(eng, stack_base, stack.data(), kThreadSize)) { |
| 94 | log::debug("kstack: pid {} stack @ {:#x} unreadable", |
| 95 | p.pid, stack_base); |
| 96 | return out; |
| 97 | } |
| 98 | |
| 99 | AddrIndex ai = build_addr_index(ks); |
| 100 | TextBounds tb = resolve_text_bounds(ks); |
| 101 | |
| 102 | // Scan every 8-byte aligned position for kernel-text return-address- |
| 103 | // shaped values. Dedup by symbol name (the same function can appear |
| 104 | // many times on a stack from spurious matches in stale memory). |
| 105 | std::unordered_set<std::string> seen; |
| 106 | for (std::size_t off = 0; off + 8 <= stack.size(); off += 8) { |
| 107 | u64 v = 0; |
| 108 | std::memcpy(&v, stack.data() + off, 8); |
| 109 | if (v < tb.start || v >= tb.end) continue; |
| 110 | const KallsymsEntry* sym = find_sym_below(ks, ai, v); |
| 111 | if (!sym) continue; |
| 112 | u64 dist = v - sym->address; |
| 113 | // Reject "way past nearest symbol" as not a real return into |
| 114 | // that function (some adjacent data section). |
| 115 | if (dist > 0x10000) continue; |
| 116 | // Dedup — only keep the first sighting of each symbol on the |
| 117 | // stack (lowest offset = closest to current frame). |
| 118 | if (!seen.insert(sym->name).second) continue; |
| 119 |
no test coverage detected