Open aarzilli opened 7 years ago
Brief investigation:
.debug_frame is generated from FuncInfo.Pcsp
, which is populated in linkpcln
using pctospadj
, which reads Spadj
from Prog
s, which is populated by the CPU-specific assembler, mostly in preprocess
. For X86, it understands explicit push/pop instructions, but not the SUBQ ..., SP that crosscall2 does. I don't know enough assembly to fill a full list of every instruction that could be used to manipulate the stack pointer, but it seems straightforward to fix this specific case for x86 at least.
I see that crosscall2 is defined in src/runtime/cgo/asm_*.s, why do all of them do the SUB manually instead of declaring a frame size in the TEXT header? I can see why the one for amd64 would do that (because it has different frame sizes on windows and linux) but why do all the others do it too?
There is probably no special reason. It's probably worth trying setting up the frame in the TEXT
pseudo-op.
More notes from the clueless (me):
A naive attempt at this fails because the assembler doesn't like unbalanced stack operations, and there are a few functions in the runtime that aren't balanced, like runtime.rt0_go which doesn't bother cleaning up because it never returns. These are fixable, sort of, though it's sort of weird to cater to the subset of instructions I've bothered implementing. We could also just disable the error.
Either way, it points to a bigger question: how realistic is it to expect the assembler to figure this out for an arbitrary function? For example, asmcgocall is switching to the system stack before making a cgo call. I presume the assembler can't automatically generate the right information for something that weird.
It seems that GNU as has directives for emitting call frame information. Without implementing something like that, and using it everywhere, it seems unlikely we'll ever emit perfect .debug_frame information for assembly functions.
@aarzilli: Can you explain a little more why you need this? If all you're trying to do is unwind the stack, aren't the frame pointers supposed to be enough?
Frame pointers would be enough if there was no FDE for crosscall2.
Without implementing something like that, and using it everywhere, it seems unlikely we'll ever emit perfect .debug_frame information for assembly functions.
True, but crosscall2 isn't weird like asmcgocall.
True, but crosscall2 isn't weird like asmcgocall.
Sure. But it may show up in a stack trace Delve cares about, e.g. a program that goes Go -> C -> Go, which isn't unheard of. So I think it pays to worry about it.
Dropping the FDEs for assembly functions feels a lot safer and more future-proof than trying to massage debugger-relevant functions to fit into the subset that the assembler understands. Let me see how that would work.
That seems like a bit of a overreaction. Most assembly functions are called on goroutine stacks and therefore need to have correct unwind information, otherwise the garbage collector wouldn't work. Even asmcgocall and cgocallback_gofunc are correct at their safe points as long as you limit the unwind to the goroutine stack. crosscall2 is getting away with being wrong because it lives on the cgo stack only.
Change https://golang.org/cl/70937 mentions this issue: cgo: fix FDE of crosscall2 on amd64 and 386
Fixed? Or do we want to try to get the other architectures done too?
The other architectures matters too, yes.
The x86 fix does not apply to the other architectures, because the assembly in crosscall2 needs to behave like a C function, and using the standard TEXT-induced prologue makes the code behave like a Go function. Those happen to be close enough to the same on x86, but they are not on the other architectures (they differ in where the link register is saved, for example).
I am not convinced this bug is worth fixing on the other architectures. Why does crosscall2's debug info need to be correct at all?
If there is anything left for the other architectures, it will need to happen in a future cycle. (One option would be to add a NODEBUG bit that disables generation of debug info for a function.)
Why does crosscall2's debug info need to be correct at all?
I was trying to get delve to print stacktraces "correctly" when the stack contains cgo calls (here "correctly" means "matching the programmer's model").
Having a bad debug_frame entry for crosscall2 was a problem because I couldn't walk the crosscall2 frame and get to the runtime.asmcgocall frame.
I don't think other architectures are a priority since delve doesn't support them and other debuggers aren't in a position to take advantage of it anyway.
As far as I am concerned it can go under an 'unscheduled' tag instead of 1.11.
The debug_frame entry for crosscall2 is wrong in any cgo program.
This is crosscall2:
The debug_info entry:
the debug_frame CIE:
and the debug_frame entry for crosscall2:
the cfa offset is 8, it should be 0x58 + 0x8 after the first instruction.