2026-07-03
The asker is debugging a Linux system under KVM with kgdb. A userspace process is blocked inside folio_wait_bit_common() (page cache waiting for a folio bit — usually PG_locked or PG_writeback). They can see this in /proc/$pid/stack, but they need more than a symbolic backtrace: they want to inspect an argument passed to a function further up the saved call chain. Using the lx-ps gdb script, they've recovered a valid struct task_struct * (confirmed via task->comm). Now they need to reconstruct that task's kernel stack frames and locals from a stopped-in-kgdb state.
Why this is hard: The task isn't running — it's parked on a wait queue after __schedule() stashed its register state. On x86-64 there's no ABI-mandated frame pointer chain by default (kernels typically build with -fno-omit-frame-pointer only when CONFIG_FRAME_POINTER=y; ORC unwinding is the modern default). GDB's own unwinder can't unwind a task it doesn't have registers for. And even with unwind info, function arguments may live in registers that were clobbered before the call site of interest — DWARF location lists tell you where a variable lived at each PC, but only if the kernel was built with sufficient debug info (CONFIG_DEBUG_INFO=y, ideally CONFIG_DEBUG_INFO_DWARF5=y, and not stripped).
Approach:
task->thread.sp. For a sleeping task, this points into its kernel stack at the frame saved by __switch_to_asm. On x86-64 the layout pushes callee-saved regs (rbp, rbx, r12–r15) then returns into __switch_to.set $rsp = ((struct task_struct *)$task)->thread.sp, then pop the saved rbp off that stack into $rbp, and set $rip to the return address at the top. From there, bt should walk the frames.lx-symbols plus a helper like lx-bt if your kernel version ships it, or the community gdb-linux scripts that implement task-bt. These automate the register-fixup dance.info args / info locals. If GDB reports <optimized out>, inspect the raw stack near $rsp and cross-reference the disassembly at the call site to see which register or slot held the argument at the call.Gotchas: ORC unwind tables live in the vmlinux but GDB doesn't consume them — it needs DWARF. Inlined functions (folio wait paths are heavy with inlines) collapse frames; you may need disassemble and manual decoding. If CONFIG_RANDOMIZE_BASE (KASLR) is on and you loaded symbols at the compile-time base, everything is offset — use lx-symbols after KASLR resolves, or boot with nokaslr for debug builds.
task_struct.thread.sp and hoping DWARF survived the optimizer — because ORC doesn't help GDB, and inlined wait paths eat the frames you actually want.
