cswork / libraryOpen practice workspace

Articles

GDB Interview Questions: Core Dumps, Stack Traces, and Multithreaded Debugging

Prepare for GDB interview questions with core-dump workflows, stack traces, optimized-out variables, thread analysis, deadlock evidence, and watchpoints.

A GDB interview exercise usually starts with incomplete evidence: a process crashed, a request hangs, or a variable is unavailable. The useful skill is deciding what the debugger can establish and choosing the next inspection that separates competing explanations.

The direct answer: load the matching executable and core, inspect the failing thread and its callers, compare all thread stacks, and treat optimized-out values as unavailable evidence. A stack trace locates the observed failure; it does not automatically identify the code that caused it.

The examples here assume a conventional native program on a supported Unix-like target and are original practice scenarios.

What should you preserve before opening the core?

Keep the core file, the exact executable from the failing deployment, matching shared libraries, and available debug information. Record the deployment version, command line, failure time, and relevant logs. GDB's file documentation explains how executable files, symbol information, and core files serve different roles.

A core is a captured process state, not a complete recording of how the process reached that state. Its contents depend on the operating system and dump configuration. Missing memory pages or unavailable libraries can limit what you can inspect.

In an interview, say why a newly rebuilt binary is risky even when its source revision looks right. Compiler options, dependency versions, and linking can change addresses and layout. Prefer deployment artifacts and matching build identifiers over the assumption that rebuilding reproduces the original binary exactly.

For the scenario below, imagine a request decoder crashed after a configuration reload. Preserve that timeline as context, but do not let it become the conclusion. A recent change is a candidate cause that still needs evidence.

How do you start a core-dump investigation?

A typical entry point is gdb /path/to/program /path/to/core. Check what executable and core GDB loaded, and read warnings about mismatches or unavailable symbols before interpreting source lines.

info files
info sharedlibrary
info threads
bt

These inspections establish the loaded files, library visibility, thread inventory, and selected thread's stack. They do not repair a mismatched environment. If important frames appear as unknown addresses, resolve missing artifacts before inventing source-level explanations.

Assume the selected thread stopped with SIGSEGV in decode(ptr=0x0), called from handle_request. A null pointer is a concrete observation. It does not establish whether validation was missing, a lifetime rule was violated, or the pointer was corrupted earlier.

Write the observed facts separately from your hypotheses. An interview answer becomes clearer when it says "the decoder received null" before "I suspect reload invalidated the request's configuration reference." The second statement requires further inspection.

What does a backtrace tell you?

GDB numbers the current frame as zero and shows callers outward from it. bt full can include local variables when that information is available. A backtrace for the selected thread is not automatically a backtrace for every thread.

Inspect the failing frame, then move to the caller that supplied its arguments. Use frame, up, or down to change context, and inspect arguments and locals in the selected frame. The same variable name in two frames can refer to different storage.

For the decoder scenario, ask whether handle_request received a valid configuration object and whether it passed the right member into decode. If the caller already had null, inspect its earlier contract rather than blaming the decoder's implementation immediately.

An implausible stack can itself be evidence: stack corruption, missing unwind information, or optimized code may affect interpretation. Do not claim an exact call history when the debugger reports unreliable frames. Explain which portion of the trace you trust and why.

How do you inspect values without overinterpreting them?

Use info args, info locals, and print for source-level inspection where symbols and locations support it. For raw memory, specify the format and amount deliberately, such as examining a small number of bytes around a known valid address.

ObservationWhat it supportsWhat it does not prove
Pointer is nullNo object is addressed by that pointerWhy it became null
Pointer is nonzeroIt contains a nonzero addressThe object is alive or correctly typed
Length exceeds capacityA stated bound is inconsistentWhich write introduced the mismatch
Variable is optimized outGDB cannot supply its value thereThe value was zero

Suppose a buffer reports length 4096 and capacity 1024. That violates the intended invariant in this fictional program. Check whether the fields belong to the same valid object and whether symbols match before deciding the length was overwritten.

Then work backward through operations that could change either field. A useful next experiment targets the first invalid transition, not merely another inspection of the final crash.

What should you do with optimized-out variables?

Optimization can remove, combine, move, or reuse computations and storage. GDB's optimized-code documentation warns that execution and source presentation may no longer correspond in the straightforward way expected from an unoptimized build.

If print count reports an unavailable value, do not substitute zero or a value remembered from a nearby line. Inspect caller arguments, registers, instructions, or surviving related state when those sources can answer the question. A source variable may have no recoverable location at the stop point.

For a reproducible failure, build a debug-friendly variant with symbols and appropriate optimization settings. Treat it as another experiment: changing optimization can alter timing and memory layout, so reproducing there strengthens some observations but does not prove the production run behaved identically.

In an interview, state the limitation directly: "I cannot recover count at this point; I can check the branch instruction and the caller's input, then capture it earlier in a reproduction." That is better than presenting a guessed value as evidence.

How can disassembly help when source lines are misleading?

Source-level stepping follows debugger line information, which can become surprising after inlining and instruction scheduling. Disassembly shows the instructions associated with the current execution point. Registers provide the machine state needed to interpret those instructions.

For the null-pointer scenario, inspect the instruction at the program counter. A load using a zero-valued base register can support the observation that the failing access used a null address, though the exact addressing calculation depends on the architecture and instruction.

Keep the reasoning bounded. Do not claim that one register always holds the first argument across architectures, calling conventions, or arbitrary points inside a function. The function may already have moved or transformed its inputs.

If the task is to find an out-of-bounds access, relate the effective address to the object bounds you can establish. "The instruction loads from this address" is a machine-level fact; "this address belongs to a live 1024-byte buffer" needs additional evidence about allocation and lifetime.

Why inspect every thread during a hang?

GDB's thread commands let you list threads, switch the selected thread, and apply a command across threads. thread apply all bt is a useful starting point when the selected thread merely waits for work while another thread holds the relevant resource.

For a request hang, sort the observed stacks into activities: accepting connections, waiting for a condition, executing a database call, holding application work, or waiting for a mutex. Several blocked workers may be normal if the service is idle.

Compare repeated observations during a controlled live investigation when appropriate. A worker that repeatedly remains in the same computation suggests something different from workers whose stacks change while a downstream request remains pending.

Avoid translating a library wait function directly into "deadlock." A wait can represent healthy synchronization, slow I/O, starvation, or a lock cycle. Your next inspection should identify the resource and the thread or external system expected to make progress.

How do you demonstrate a deadlock rather than guess one?

Use a small explicit scenario. Thread 1 holds lock A and waits for lock B. Thread 2 holds B and waits for A. The closed dependency cycle explains why neither can continue under the stated locking protocol.

ThreadHoldsWaits forRequired progress
1ABThread 2 must release B
2BAThread 1 must release A

To support this diagnosis in a real process, inspect the relevant lock state using knowledge of the platform and implementation. A thread blocked in a mutex function alone does not show ownership of a second lock. Application logging or instrumented reproduction may be necessary when the dump lacks enough information.

Then propose a fix tied to the cycle, such as a consistent lock acquisition order, and test the competing paths. A timeout may limit the symptom without repairing the underlying lock-order problem. Explain which guarantee your proposed change actually provides.

When should you use a watchpoint?

A watchpoint can stop execution when a watched expression changes. Hardware support, target capabilities, watched size, and available resources affect what GDB can provide. Watchpoints are a tool for a live run or suitable replay environment; a static core cannot execute forward to catch an earlier write.

Suppose the buffer capacity starts at 1024 but is 16 at the crash, despite an invariant that capacity never shrinks. In a reproduction, stop after initialization and watch the relevant capacity field. The useful event is the first unexpected modification, not the later access that eventually fails.

Be careful about lifetime and address reuse. Watching an address after its object is freed may show writes to a different allocation. Record the allocation identity and stop watching when the original object no longer exists, unless reuse is the behavior being investigated.

If the watchpoint cannot be implemented or changes timing too much, use another observation strategy. A failed watchpoint setup is not evidence that no write occurred.

How does debugger scheduling change the experiment?

Stopping and stepping a multithreaded process can change timing and progress. GDB distinguishes all-stop and non-stop behavior on supported targets, and thread scheduling controls influence which threads may run during debugging.

If you restrict execution to one thread while it waits for another to release a lock, the debugger can create an apparent lack of progress. Before calling that a program deadlock, inspect the scheduling mode and which threads are permitted to run.

For a race that disappears under breakpoints, reduce the intervention. Capture bounded logging, use appropriate race-detection tooling in a separate experiment, or choose a breakpoint closer to the invalid transition. Each technique observes the program differently and has different overhead.

Explain this as experimental control rather than debugger unreliability. The debugger accurately reports the state it observes, but your actions can influence which state the program reaches. Keep production evidence and reproduction evidence labeled separately.

What does a strong final diagnosis include?

Return to the reload scenario. A good report might say that the decoder received null, the caller selected a missing configuration entry after reload, and a reproduction demonstrates the missing-entry path. It should not claim use-after-free unless object-lifetime evidence supports that different diagnosis.

Include the failure condition, the violated contract, the earliest supported incorrect transition, and a regression test. If the root cause remains uncertain, list the remaining hypotheses and the observation that would distinguish them. That gives another engineer a concrete next action.

For interview practice, take ten minutes to inspect a small intentional crash and two minutes to present the result. Mention which artifacts matched, which values were unavailable, and why your fix addresses the cause rather than suppressing the signal.

The best answer can be incomplete without being vague. "The core establishes a null argument but not its origin; I need a matching caller trace during reload" is a precise limitation and a useful investigation plan.

Sources and Further Reading

Sources reviewed September 24, 2026. The current online manual can move ahead of an installed GDB; target, operating system, compiler, and build options affect available evidence.