Go version

go1.24.4

Output of go env in your module/workspace:

AR='ar'                                                                                                                                                       
CC='gcc'                                                                                                                                                      
CGO_CFLAGS='-O2 -g'                                                                                                                                           
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/andrew/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/andrew/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build1743267656=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/andrew/src/github.com/DataDog/datadog-agent/go.mod'
GOMODCACHE='/home/andrew/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/andrew/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/andrew/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.4.linux-amd64'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/andrew/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/andrew/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.4.linux-amd64/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.4'
GOWORK='/home/andrew/src/github.com/DataDog/datadog-agent/go.work'
PKG_CONFIG='pkg-config

What did you do?

I looked at dwarf and disassembled code for an arm64 binary.

What did you see happen?

I saw that the debug_line section for the program marks the prologue_end as occurring before the stack pointer and frame pointer are updated and the function frame has been set up.

What did you expect to see?

I expected to see the prologue_end marker after the stack pointer and frame pointer have been set up.

My best guess is that when the prologue_end marker was introduced (https://github.com/golang/go/commit/a88c26eb286098b4c8f322f5076e933556fce5ac), it was more or less in the right place. Since them, several changes around that code to manage the frame pointer and what not have merged (https://github.com/golang/go/commit/09e059afb1270498f416f5b5c75a6a5683b6d1da, https://github.com/golang/go/commit/c6d9b38dd82fea8775f1dff9a4a70a017463035d). It seems obvious from the message and review of the latter commit that this adjustment is part of the "prologue".

I think that the issue is that the bookkeeping of the prologueEnd code around here hasn't kept up with other code changes.

To make this explicit, in this commit we have this discussion:

For frame size <= 240B,
  prologue:
    STP     (R29, R30), -(offset+8)(RSP)
    SUB     $offset, RSP, RSP

If you look at some generated go code:

00000000000b5490 <main.stringArrayArg>:
   # <stack growth check>
   b549c: fe 0f 1b f8   str     x30, [sp, #-80]!
   b54a0: fd 83 1f f8   stur    x29, [sp, #-8]
   b54a4: fd 23 00 d1   sub     x29, sp, #8

If we then look at the debug_line table for this code:

0x00000000000b5490     66      0      2   0             0  is_stmt
0x00000000000b549c     66      0      2   0             0  is_stmt prologue_end

This says that the prologue ends at 0xb549c -- but that's where the stack adjustment part of the prologue starts!

Comment From: randall77

I agree, this mark doesn't look right. I noticed it as part of redoing the prologue/epilogue in https://go-review.googlesource.com/c/go/+/674615.

What we need is a test that fails if the mark is wrong. Without a test, even if we fix it, it will break again in the future.

Comment From: ajwerner

I am happy to help with testing. In the past when I’ve looked there’s only light testing of the dwarf in the x/debug repo around:

https://github.com/golang/debug/tree/master/dwtest

Would that be where you’d expect this sort of thing? Does your comment imply you have work in flight to address this if there were a test?

Comment From: randall77

Would that be where you’d expect this sort of thing?

A test in x/debug is great. A test in the main repo in cmd/link/internal/ld/dwarf_test.go would probably also work.

Does your comment imply you have work in flight to address this if there were a test?

Not currently, but it would probably be easy to incorporate into my CL. My CL is only arm64 though. Is this currently only broken on arm64?

Comment From: ajwerner

Is this currently only broken on arm64?

I have only looked in detail at amd64 and arm64. The location looks correct for x86.

Comment From: cherrymui

I believe the marker was added for Delve's need, to the place where Delve expects. I agree that this is not the usual sense of the end of prologue, but somehow Delve expects it to mark the end of split-stack prologue, not the frame creation. cc @aarzilli

Comment From: aarzilli

The logic is that users expect to be able to set a breakpoint on the header line of a function (i.e. the one that says func blah(...)) but we can not put it before the stack split prologue, because it would behave very strangely there. Anywhere "after" the stack-split prologue end is fine, so it would be fine if it were also after the frame setup. However it used to be the case that the instruction after the end of the frame setup would be marked as the first line of the function (i.e. the one right after the func... line), which would also be weird for users. I don't know if this is still true, I imagine that it is because the first instruction after the frame is setup logically belongs to the first line of the function. This is a special go-only problem because in other languages placing a breakpoint on the entry point of a function is completely fine.