In the Go traceback frame `main.process(0xc000180000, 0x400, 0x400, 0x2)`, what are those hex values?
answer
- not decoded arguments
- raw words of the frame's argument area
- a slice costs three of them
- register spill slots go stale
- a hint, never proof
basics
~20 sThey are raw machine words from that frame's argument area, printed in hex rather than decoded as Go values. Multi-word types such as slices and strings expand into several words, so the count rarely matches the parameter count.
solid answer
~50 sThe runtime cannot decode typed Go values from a stopped stack, so it prints the raw words it finds in the frame's argument area, in hex. Multi-word types expand: a slice is three words (pointer, length, capacity), a string two, an interface two — so `process(payload []byte, attempt int)`, with two parameters, prints four words. Worse, Go passes arguments in registers, and what gets printed are the stack slots those registers spill into, which are only guaranteed live at certain points. So values can be stale or simply wrong. Treat them as a hint: a `0x0` in the receiver position of a faulting frame strongly suggests a nil pointer, an absurd length word suggests a bad size computation. If you need real values, log them at the call site or capture a core dump — do not build a theory on one hex word.
code
go · 6 lines// payload is a slice: pointer, length and capacity - three machine words.
// attempt is one word. A two-parameter call can therefore print four
// hex words in a traceback frame line.
func process(payload []byte, attempt int) error {
return nil
}go deeper
You will not be pressed on this, but do not read the parenthesised numbers as your arguments. Knowing they are raw memory words is enough at this stage.
Explain the word expansion — three words for a slice, two for a string or an interface — and say why the register calling convention makes the printed values approximate rather than exact.
Demonstrate the discipline: use a word as a hint that narrows a hypothesis, such as a nil in the receiver slot, and then confirm it from logs or a core dump rather than reporting it as fact.
Decide what the team relies on when a crash cannot be reproduced: whether crash artefacts are captured, whether the code logs enough context at boundaries, and how much time is worth spending decoding a dump.
## The values are memory words, not arguments A traceback frame line ends with a parenthesised list: ``` main.process(0xc000180000, 0x400, 0x400, 0x2) ``` It is tempting to read that as "process was called with four arguments". It is not what the runtime is telling you. The runtime cannot decode Go values from a stopped stack — it has no per-argument type information at hand while printing — so it prints the **raw machine words** it finds in the frame's argument area, in hex, and leaves the interpretation to you. ## Why the count does not match the parameter list Go's multi-word types occupy more than one word: | type | words | contents | |---|---|---| | `int`, `bool`, pointer, `float64` on 64-bit | 1 | the value | | `string` | 2 | data pointer, length | | slice `[]T` | 3 | data pointer, length, capacity | | interface (`error`, `any`) | 2 | type descriptor pointer, data pointer | | small struct | as many as it needs | its fields, laid out | So `func process(payload []byte, attempt int)` — two parameters — supplies four words: pointer, length, capacity, then the int. A `func(s string, err error)` supplies four as well. If a frame has a large argument area the runtime truncates the list and finishes it with `...`. The first thing to do with the words is therefore not to count them against the parameters but to lay the parameter types out and see which word is which. ## Why the values may be wrong Go passes arguments in **registers** under its current calling convention. Registers are not part of the frame, so what the runtime prints comes from the stack slots reserved for spilling those registers. Those slots are only guaranteed to hold the live value at certain points; at an arbitrary program counter they may be uninitialised, or hold a value the function has since overwritten. The consequence is blunt: **the printed words are a hint, not evidence.** A `0x0` in the pointer position of a frame that faulted is a strong hint you were passed a nil pointer. A length word of `0x0` next to a capacity of `0x400` is a strong hint about a slice. Neither is proof, and a value that looks absurd is more likely a stale slot than a corrupted program. ## The heap-address habit, and why it is weaker now Go engineers learned to read a word beginning `0xc000…` as "a pointer into the Go heap", because on 64-bit platforms the heap arena was reserved at a fixed, familiar base address. Go 1.26 randomises the heap base address on 64-bit, so that prefix is no longer a reliable tell — a heap pointer can now be almost anywhere. Read the *shape* instead: small values (`0x0`, `0x1`, `0x400`) are lengths, counts, flags and enums; large, page-aligned-looking values are pointers of some kind; `0x0` in a pointer slot is the interesting one. ## What to do with them - **Nil hunting.** For a nil-pointer fault, a `0x0` in the receiver or first-argument position of the faulting frame confirms which pointer was nil. - **Size sanity.** An enormous length or capacity word next to an out-of-memory or index panic points at a computed size rather than at the code that used it. - **Identity.** Two goroutines showing the same pointer word in the receiver position are working on the same object; different words mean different instances. This is often the only way to tell apart otherwise identical frames in a large dump. And what not to do: do not build a theory on a single word. If you need real values, the honest routes are logging the value at the call site, reproducing the failure under a race or debug build, or capturing a core dump and inspecting it with a debugger — not squinting harder at the hex. ## The interview point The question separates people who have *read* tracebacks from people who have *seen* them. The expected answer is three claims: the values are frame words rather than decoded arguments; multi-word types make the count differ from the parameter count; and the register calling convention makes the values approximate, so they inform a hypothesis instead of settling one.
- Why can a function with two parameters print four hex words?Because the runtime prints machine words, not parameters. A slice occupies three words — data pointer, length, capacity — and an `int` one, so `func(payload []byte, attempt int)` fills four. A `string` costs two words and an interface value two, so the word count is the sum of the parameter types' widths, not their number.
- A word in a traceback starts with 0xc000. Does that still mean it is a heap pointer?It used to be a good tell, because the heap arena sat at a familiar base address on 64-bit. Go 1.26 randomises the heap base address, so the prefix no longer identifies a heap pointer reliably. Read the shape instead — small values are lengths, counts and flags; a zero in a pointer slot is the interesting one.
- What would you actually do to get the real argument values?Log them at the call site and reproduce, or capture a core dump at the crash and inspect it with a debugger. Both give typed values instead of words. Staring harder at the traceback does not, because the words come from spill slots the calling convention does not guarantee to be current.
saying these in an interview costs you the question
- Reads the hex words as a faithful list of arguments
- Counts words and expects them to match the parameter count
- Trusts a printed value enough to conclude a variable's value
- Assumes every 0xc000-prefixed word is a heap pointer
- Thinks the values are wrong because the binary was optimised away