Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/compile: invalid DWARF location list for local var - erroneously extends to function epilogue #60493

Open
andreimatei opened this issue May 29, 2023 · 5 comments
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. help wanted NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@andreimatei
Copy link
Contributor

What version of Go are you using (go version)?

1.20.3

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/andrei/.cache/go-build"
GOENV="/home/andrei/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/andrei/work/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/andrei/work"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/andrei/sdk/go1.19.9"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/andrei/sdk/go1.19.9/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.19.9"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3969983697=/tmp/go-build -gno-record-gcc-switches"

What did you do?

The DWARF location list for some local variables (vars declared on the first line within a function?) appears to be incorrect: it extends to include code placed in the object file at the end of the function that has to do with growing the stack (i.e. calling runtime.morestack_noctxt). Even though it's placed at the end of the function, this code executes when the function start if it executes at all (i.e. when the stack actually has to grow). By including the program counters corresponding to this code in the variable's location list, the compiler is declaring that the variable is live at those locations. In fact, the variable is not live; when the respective code executes, the variable has yet to be initialized. The respective location list instructions are thus invalid - where they do be followed to read the variable, we'd read random heap memory.

Let's study an example.
Consider the variable localVar in following program:
https://go.dev/play/p/P4_7_JfCbzo

package main

var global int

//go:noinline
func foo() {
	// localVar's loclist erroneously extends to the end of foo's code -
	// including the epilogue.
	var localVar int
	localVar = global
	localVar++

	// Force the stack to grow when foo() starts executing. This isn't necessary
	// for reproducing the issue, but it is useful if trying to actually
	// demonstrate that the bug is "exploitable" by trapping on the stack
	// expansion and attempting to read the variable.
	var xx [][5000]byte
	var x1 [5000]byte
	xx = append(xx, x1)

	global = localVar
}

func main() {
	global = 42
	foo()
}

This is the debug information produced for localVar, with my arrow markings:

$ llvm-dwarfdump-16 --debug-info --color  --name=main.foo --show-children main                          

0x000640ab: DW_TAG_subprogram
              DW_AT_name        ("main.foo")
              DW_AT_low_pc      (0x0000000000457240)
---->         DW_AT_high_pc     (0x00000000004572d1)
              DW_AT_frame_base  (DW_OP_call_frame_cfa)
              DW_AT_decl_file   ("/home/andrei/work/src/github.com/andreimatei/play/go-debuginfo-examples/loclist-covering-epilogue/main.go")
              DW_AT_decl_line   (6)
              DW_AT_external    (0x01)

0x000640cd:   DW_TAG_variable
                DW_AT_name      ("localVar")
                DW_AT_decl_line (9)
                DW_AT_type      (0x000000000004cf9c "int")
                DW_AT_location  (0x0007356d: 
                   [0x0000000000457264, 0x0000000000457299): DW_OP_reg1 RDX
---->              [0x0000000000457299, 0x00000000004572d1): DW_OP_fbreg -5024)

Notice that the variable has a loclist extending to the end of the function's code - 0x00000000004572d1. The info says that the variable can be read from the stack.

By disassembling, we can see that this is incorrect. The last instructions before 0x4572d1 are about the stack growth. When that code executes, the variable is not alive, and attempting to read it according to the generated loclist would result in reading essentially random heap memory (outside of the current function's stack frame).

Disassembly
$  llvm-objdump-16 --disassemble --line-numbers --source --disassemble-symbols=main.foo  main

0000000000457240 <main.foo>:
; main.foo():
; /home/andrei/work/src/github.com/andreimatei/play/go-debuginfo-examples/loclist-covering-epilogue/main.go:6
; func foo() {
  457240: 49 89 e4                     	movq	%rsp, %r12
  457243: 49 81 ec 58 13 00 00         	subq	$0x1358, %r12           # imm = 0x1358
  45724a: 72 7b                        	jb	0x4572c7 <main.foo+0x87>
  45724c: 4d 3b 66 10                  	cmpq	0x10(%r14), %r12
  457250: 76 75                        	jbe	0x4572c7 <main.foo+0x87>
  457252: 55                           	pushq	%rbp
  457253: 48 89 e5                     	movq	%rsp, %rbp
  457256: 48 81 ec d0 13 00 00         	subq	$0x13d0, %rsp           # imm = 0x13D0
; /home/andrei/work/src/github.com/andreimatei/play/go-debuginfo-examples/loclist-covering-epilogue/main.go:10
; 	localVar = global
  45725d: 48 8b 15 fc 03 0a 00         	movq	0xa03fc(%rip), %rdx     # 0x4f7660 <main.global>
; /home/andrei/work/src/github.com/andreimatei/play/go-debuginfo-examples/loclist-covering-epilogue/main.go:18
; 	var x1 [5000]byte
  457264: 44 0f 11 7c 24 48            	movups	%xmm15, 0x48(%rsp)
; /home/andrei/work/src/github.com/andreimatei/play/go-debuginfo-examples/loclist-covering-epilogue/main.go:11
; 	localVar++
  45726a: 48 ff c2                     	incq	%rdx
  45726d: 48 89 54 24 40               	movq	%rdx, 0x40(%rsp)
; /home/andrei/work/src/github.com/andreimatei/play/go-debuginfo-examples/loclist-covering-epilogue/main.go:18
; 	var x1 [5000]byte
  457272: 48 8d 7c 24 50               	leaq	0x50(%rsp), %rdi
  457277: b9 70 02 00 00               	movl	$0x270, %ecx            # imm = 0x270
  45727c: 31 c0                        	xorl	%eax, %eax
  45727e: f3 48 ab                     	rep		stosq	%rax, %es:(%rdi)
; /home/andrei/work/src/github.com/andreimatei/play/go-debuginfo-examples/loclist-covering-epilogue/main.go:19
; 	xx = append(xx, x1)
  457281: 31 c0                        	xorl	%eax, %eax
  457283: bb 01 00 00 00               	movl	$0x1, %ebx
  457288: 31 c9                        	xorl	%ecx, %ecx
  45728a: 48 89 df                     	movq	%rbx, %rdi
  45728d: 48 8d 35 2c 5c 00 00         	leaq	0x5c2c(%rip), %rsi      # 0x45cec0 <type:*+0x4ec0>
  457294: e8 07 a5 fe ff               	callq	0x4417a0 <runtime.growslice>
  457299: 48 8b 54 24 48               	movq	0x48(%rsp), %rdx
  45729e: 48 89 10                     	movq	%rdx, (%rax)
  4572a1: 48 8d 78 08                  	leaq	0x8(%rax), %rdi
  4572a5: 48 8d 74 24 50               	leaq	0x50(%rsp), %rsi
  4572aa: b9 70 02 00 00               	movl	$0x270, %ecx            # imm = 0x270
  4572af: f3 48 a5                     	rep		movsq	(%rsi), %es:(%rdi)
; /home/andrei/work/src/github.com/andreimatei/play/go-debuginfo-examples/loclist-covering-epilogue/main.go:21
; 	global = localVar
  4572b2: 48 8b 54 24 40               	movq	0x40(%rsp), %rdx
  4572b7: 48 89 15 a2 03 0a 00         	movq	%rdx, 0xa03a2(%rip)     # 0x4f7660 <main.global>
; /home/andrei/work/src/github.com/andreimatei/play/go-debuginfo-examples/loclist-covering-epilogue/main.go:22
; }
  4572be: 48 81 c4 d0 13 00 00         	addq	$0x13d0, %rsp           # imm = 0x13D0
  4572c5: 5d                           	popq	%rbp
  4572c6: c3                           	retq
; /home/andrei/work/src/github.com/andreimatei/play/go-debuginfo-examples/loclist-covering-epilogue/main.go:6
; func foo() {
  4572c7: e8 d4 ce ff ff               	callq	0x4541a0 <runtime.morestack_noctxt.abi0>
  4572cc: e9 6f ff ff ff               	jmp	0x457240 <main.foo>
  4572d1: cc                           	int3

Notice the retq at pc 4572c6. I believe the loclist for localVar should end around there.
Also notice how the function prelude jumps to 0x4572c7 is the stack needs growth.

I have noticed (but I'm not sure) that the loclist extends to the end of the epilogue when the variable is declared at the very beginning of the function and lives until the end of the function. I think function arguments might be susceptible to the same problem.

As a random tidbit of trivia, Delve takes a variable's DW_AT_decl_line into consideration, in addition to its location list, when deciding whether a particular variable can be read at a given pc, for better or worse. This way, Delve avoids using the erroneous instructions in the pc range where they are invalid (because the respective epilogue code is marked as corresponding to the line on which the function is declared, which is below the variable's decl_line). Function arguments are treated differently by Delve; I think those might be susceptible to invalid reads (but I'm not sure).

What did you expect to see?

I expected the location list for localVar to not extend past the instruction returning from the function.

What did you see instead?

The location list for localVar extends too much.


If my involvement would be useful, I'd be happy to work on addressing this bug myself - if someone knows the solution and is willing to guide me.

cc @dr2chase @thanm @heschi as people active on go's debug symbol generation
cc @aarzilli for the Delve connection

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label May 29, 2023
andreimatei added a commit to andreimatei/play that referenced this issue May 29, 2023
andreimatei added a commit to andreimatei/delve that referenced this issue May 30, 2023
Before this patch, the visibility of function arguments for purposes
like the `locals` command was considered to start on the line on which
they are declared. I believe this was incorrect; instead their
visibility should start at the beginning of the function. This makes a
difference for function declarations like:

1: func foo(
2:   a int,
3:   b int,
4: )

Here, foo() starts on line 1 (e.g. a breakpoint on "foo" will break on
:1), but "a" has a decl_line of :2 and "b" has :3. This was causing a
and b to not be readable for a few instructions at the beginning of the
function, although they should have been.

This patch fixes it by ignoring decl_line for function arguments,
essentially making them visible throughout the function.

Note that function arguments are not subject to the bug in location
lists described in golang/go#60493. That
issues describes how the loclists for some variables erroneously extends
to the end of the function's code, covering epilogue code related to
stack growth that should not be covered because the respective variables
have yet to be initialized when that code runs. The decl_line check
helps Delve avoid making the variables visible during that epilogue, and
thus prevents reading garbage. However, function arguments are
initialized when the stack growth runs, and they can be read just fine
during the epilogue (so their loclist is fine) - so this patch does not
introduce a regression (additionally, function args declared on the same
line as the function were already unprotected by the decl_line check).
andreimatei added a commit to andreimatei/delve that referenced this issue May 30, 2023
Before this patch, the visibility of function arguments for purposes
like the `locals` command was considered to start on the line on which
they are declared. I believe this was incorrect; instead their
visibility should start at the beginning of the function. This makes a
difference for function declarations like:

1: func foo(
2:   a int,
3:   b int,
4: )

Here, foo() starts on line 1 (e.g. a breakpoint on "foo" will break on
:1), but "a" has a decl_line of :2 and "b" has :3. This was causing a
and b to not be readable for a few instructions at the beginning of the
function, although they should have been.

This patch fixes it by ignoring decl_line for function arguments,
essentially making them visible throughout the function.

Note that function arguments are not subject to the bug in location
lists described in golang/go#60493. That
issues describes how the loclists for some variables erroneously extends
to the end of the function's code, covering trailing code related to
stack growth that should not be covered because the respective variables
have yet to be initialized when that code runs. The decl_line check
helps Delve avoid making the variables visible during that trailing
code, and thus prevents reading garbage. However, function arguments are
already initialized when the stack growth runs, and they can be read
just fine from the respective PCs (so their loclist is fine). So, this
patch does not introduce a regression (additionally, function args
declared on the same line as the function were already unprotected by
the decl_line check).
@mknyszek mknyszek added this to the Backlog milestone May 30, 2023
@mknyszek mknyszek added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label May 30, 2023
andreimatei added a commit to andreimatei/delve that referenced this issue May 30, 2023
Before this patch, the visibility of function arguments for purposes
like the `locals` command was considered to start on the line on which
they are declared. I believe this was incorrect; instead their
visibility should start at the beginning of the function. This makes a
difference for function declarations like:

1: func foo(
2:   a int,
3:   b int,
4: )

Here, foo() starts on line 1 (e.g. a breakpoint on "foo" will break on
:1), but "a" has a decl_line of :2 and "b" has :3. This was causing a
and b to not be readable for a few instructions at the beginning of the
function, although they should have been.

This patch fixes it by ignoring decl_line for function arguments,
essentially making them visible throughout the function.

Note that function arguments are not subject to the bug in location
lists described in golang/go#60493. That
issues describes how the loclists for some variables erroneously extends
to the end of the function's code, covering trailing code related to
stack growth that should not be covered because the respective variables
have yet to be initialized when that code runs. The decl_line check
helps Delve avoid making the variables visible during that trailing
code, and thus prevents reading garbage. However, function arguments are
already initialized when the stack growth runs, and they can be read
just fine from the respective PCs (so their loclist is fine). So, this
patch does not introduce a regression (additionally, function args
declared on the same line as the function were already unprotected by
the decl_line check).
@randall77
Copy link
Contributor

Kind of a novice in this area, but the compiler makes the location lists, and the wrapper for stack overflow is not added until the compiler (or at least the SSA part, which builds the location lists) is done. Maybe the compiler is using some sort of "all-the-way-to-the-end" marker?
The wrapping is done by cmd/internal/obj/x86/obj6.go:stacksplit, which is called in cmd/compile/internal/pgen.go:Compile. The Flush call calls stacksplit, which is after genssa is done.
That's about the limit of my understanding in this area.

@andreimatei
Copy link
Contributor Author

Thank you for the hints; they helped.

Maybe the compiler is using some sort of "all-the-way-to-the-end" marker?

Yeah, something like this was going on. I believe I have a patch that makes the "the end" marker be interpreted correctly. I will work on sending it over.

andreimatei added a commit to andreimatei/go that referenced this issue Jun 9, 2023
This patch fixes some incorrect DWARF location lists. Before this patch,
a location list for a variable that was available until the "end of its
function" erroneously extended over trailing code that dealt with the
stack growth code. The code in question is placed at the end of the
function, but it is morally part of the function's prologue (the
prologue jumps to it if stack growth is needed, and this trailer then
jumps back to the prologue). Thus, for the purposes of the location
lists, it should be treated more like the beginning of the function than
the end of the function; instructions for reading variables at the end
of the function do not apply in this code region.

The patch makes the loclists for regular variable not expand over this
trailing code, as they do not yet exist when this code runs. Function
arguments are special though - they do exist when the stack growth code
runs, and generally can be read. For function arguments, this patch
"fixes up" the location lists generated by the compiler after the linker
has inserted the stack growth code, as described below:

The layout of a function's code is as follows:

<function prologue jumps to the stack growth trailer>
<regular function code>
...
ret
movq    %rax, 0x38(%rsp)                  <- FuncLogicalEnd
movq    %rbx, 0x40(%rsp)
...
callq   <runtime.morestack_noctxt.abi0>   <- StackGrowthCall
movq    0x38(%rsp), %rax
movq    0x40(%rsp), %rbx
...
jmp     <beginning of function>
int3                                      <- FuncPhysicalEndA

The stack growth trailer consists of register spilling (starting at
FuncLogicalEnd), a call to the runtime stack growth function (marked by
StackGrowthCall), and then register unspilling (up until
FuncPhysicalEnd).
A variable that's available at the beginning of the function (i.e. a function
argument) is available in between [FuncLogicalEnd,StackGrowthCall) using the
same DWARF instructions as the ones used at the beginning of the function.
The variable is then available in between [StackGrowthCall,FuncPhysicalEnd)
with new instructions referencing the stack location where the variable was
spilled to.

The patch only performs this fixup for function arguments on amd64; the
tracking that linker needs to do for identifying FuncLoficalEnd and
StackGrowthCall is architecture-specific and I didn't go through the
trouble of doing it outside amd64.

Fixes golang#60493
andreimatei added a commit to andreimatei/go that referenced this issue Jun 9, 2023
This patch fixes some incorrect DWARF location lists. Before this patch,
a location list for a variable that was available until the "end of its
function" erroneously extended over trailing code that dealt with the
stack growth code. The code in question is placed at the end of the
function, but it is morally part of the function's prologue (the
prologue jumps to it if stack growth is needed, and this trailer then
jumps back to the prologue). Thus, for the purposes of the location
lists, it should be treated more like the beginning of the function than
the end of the function; instructions for reading variables at the end
of the function do not apply in this code region.

The patch makes the loclists for regular variable not expand over this
trailing code, as they do not yet exist when this code runs. Function
arguments are special though - they do exist when the stack growth code
runs, and generally can be read. For function arguments, this patch
"fixes up" the location lists generated by the compiler after the linker
has inserted the stack growth code, as described below:

The layout of a function's code is as follows:

<function prologue jumps to the stack growth trailer>
<regular function code>
...
ret
movq    %rax, 0x38(%rsp)                  <- FuncLogicalEnd
movq    %rbx, 0x40(%rsp)
...
callq   <runtime.morestack_noctxt.abi0>   <- StackGrowthCall
movq    0x38(%rsp), %rax
movq    0x40(%rsp), %rbx
...
jmp     <beginning of function>
int3                                      <- FuncPhysicalEndA

The stack growth trailer consists of register spilling (starting at
FuncLogicalEnd), a call to the runtime stack growth function (marked by
StackGrowthCall), and then register unspilling (up until
FuncPhysicalEnd).
A variable that's available at the beginning of the function (i.e. a function
argument) is available in between [FuncLogicalEnd,StackGrowthCall) using the
same DWARF instructions as the ones used at the beginning of the function.
The variable is then available in between [StackGrowthCall,FuncPhysicalEnd)
with new instructions referencing the stack location where the variable was
spilled to.

The patch only performs this fixup for function arguments on amd64; the
tracking that linker needs to do for identifying FuncLoficalEnd and
StackGrowthCall is architecture-specific and I didn't go through the
trouble of doing it outside amd64.

Fixes golang#60493

Change-Id: I8ccc9ae802bddc9427ecc4ffc5a26f172a8d6b83
@gopherbot
Copy link

Change https://go.dev/cl/502117 mentions this issue: cmd/compile: fix location lists that extend over stack growth code

andreimatei added a commit to andreimatei/golang-debug that referenced this issue Jun 15, 2023
Add a test with loose assertions about location lists, checking that
60493 was fixed. The bug is fixed by https://go.dev/cl/502117 on arm64
and amd64, the same architectures as the ones where these tests
run.

For golang/go#60493

Change-Id: I55a356cbe41004a956b5dfb863ed1a403551c1cb
@gopherbot
Copy link

Change https://go.dev/cl/503878 mentions this issue: dwtest: add a test for issue 60493

andreimatei added a commit to andreimatei/go that referenced this issue Jun 15, 2023
This patch fixes some incorrect DWARF location lists. Before this patch,
a location list for a variable that was available until the "end of its
function" erroneously extended over trailing code that dealt with the
stack growth code. The code in question is placed at the end of the
function, but it is morally part of the function's prologue (the
prologue jumps to it if stack growth is needed, and this trailer then
jumps back to the prologue). Thus, for the purposes of the location
lists, it should be treated more like the beginning of the function than
the end of the function; instructions for reading variables at the end
of the function do not apply in this code region.

The patch makes the loclists for regular variable not expand over this
trailing code, as they do not yet exist when this code runs. Function
arguments are special though - they do exist when the stack growth code
runs, and generally can be read. For function arguments, this patch
"fixes up" the location lists generated by the compiler after the linker
has inserted the stack growth code, as described below:

The layout of a function's code is as follows:

<function prologue jumps to the stack growth trailer>
<regular function code>
...
ret
movq    %rax, 0x38(%rsp)                  <- FuncLogicalEnd
movq    %rbx, 0x40(%rsp)
...
callq   <runtime.morestack_noctxt.abi0>   <- StackGrowthCall
movq    0x38(%rsp), %rax
movq    0x40(%rsp), %rbx
...
jmp     <beginning of function>
int3                                      <- FuncPhysicalEndA

The stack growth trailer consists of register spilling (starting at
FuncLogicalEnd), a call to the runtime stack growth function (marked by
StackGrowthCall), and then register unspilling (up until
FuncPhysicalEnd).
A variable that's available at the beginning of the function (i.e. a function
argument) is available in between [FuncLogicalEnd,StackGrowthCall) using the
same DWARF instructions as the ones used at the beginning of the function.
The variable is then available in between [StackGrowthCall,FuncPhysicalEnd)
with new instructions referencing the stack location where the variable was
spilled to.

The patch only performs this fixup for function arguments on amd64 and
arm64; the tracking that linker needs to do for identifying
FuncLoficalEnd and StackGrowthCall is architecture-specific and I didn't
go through the trouble of doing it outside these two archs.

Fixes golang#60493

Change-Id: I8ccc9ae802bddc9427ecc4ffc5a26f172a8d6b83
@gopherbot
Copy link

Change https://go.dev/cl/504836 mentions this issue: dwtest: update go.mod for delve-based test harness

gopherbot pushed a commit to golang/debug that referenced this issue Jun 21, 2023
The test harness for the variable location tests in "dwtest"
was using an out-of-date version of Delve; update to something
more recent.

Updates golang/go#60493.

Change-Id: I4e72fd12fa1c6e07094067402b2c8693e37eea39
Reviewed-on: https://go-review.googlesource.com/c/debug/+/504836
Reviewed-by: David Chase <drchase@google.com>
Run-TryBot: Than McIntosh <thanm@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
andreimatei added a commit to andreimatei/golang-debug that referenced this issue Jun 21, 2023
Add a test with loose assertions about location lists, checking that
60493 was fixed. The bug is fixed by https://go.dev/cl/502117 on arm64
and amd64, the same architectures as the ones where these tests
run.

For golang/go#60493

Change-Id: I55a356cbe41004a956b5dfb863ed1a403551c1cb
andreimatei added a commit to andreimatei/golang-debug that referenced this issue Jun 21, 2023
Add a test with loose assertions about location lists, checking that
60493 was fixed. The bug is fixed by https://go.dev/cl/502117 on arm64
and amd64, the same architectures as the ones where these tests
run.

For golang/go#60493

Change-Id: I55a356cbe41004a956b5dfb863ed1a403551c1cb
andreimatei added a commit to andreimatei/golang-debug that referenced this issue Jun 23, 2023
Add a test with loose assertions about location lists, checking that
60493 was fixed. The bug is fixed by https://go.dev/cl/502117 on arm64
and amd64, the same architectures as the ones where these tests
run.

For golang/go#60493

Change-Id: I55a356cbe41004a956b5dfb863ed1a403551c1cb
andreimatei added a commit to andreimatei/golang-debug that referenced this issue Aug 23, 2023
Add a test with loose assertions about location lists, checking that
60493 was fixed. The bug is fixed by https://go.dev/cl/502117 on arm64
and amd64, the same architectures as the ones where these tests
run.

For golang/go#60493

Change-Id: I55a356cbe41004a956b5dfb863ed1a403551c1cb
andreimatei added a commit to andreimatei/go that referenced this issue Aug 23, 2023
This patch fixes some incorrect DWARF location lists. Before this patch,
a location list for a variable that was available until the "end of its
function" erroneously extended over trailing code that dealt with the
stack growth code. The code in question is placed at the end of the
function, but it is morally part of the function's prologue (the
prologue jumps to it if stack growth is needed, and this trailer then
jumps back to the prologue). Thus, for the purposes of the location
lists, it should be treated more like the beginning of the function than
the end of the function; instructions for reading variables at the end
of the function do not apply in this code region.

The patch makes the loclists for regular variable not expand over this
trailing code, as they do not yet exist when this code runs. Function
arguments are special though - they do exist when the stack growth code
runs, and generally can be read. For function arguments, this patch
"fixes up" the location lists generated by the compiler after the linker
has inserted the stack growth code, as described below:

The layout of a function's code is as follows:

<function prologue jumps to the stack growth trailer>
<regular function code>
...
ret
movq    %rax, 0x38(%rsp)                  <- FuncLogicalEnd
movq    %rbx, 0x40(%rsp)
...
callq   <runtime.morestack_noctxt.abi0>   <- StackGrowthCall
movq    0x38(%rsp), %rax
movq    0x40(%rsp), %rbx
...
jmp     <beginning of function>
int3                                      <- FuncPhysicalEndA

The stack growth trailer consists of register spilling (starting at
FuncLogicalEnd), a call to the runtime stack growth function (marked by
StackGrowthCall), and then register unspilling (up until
FuncPhysicalEnd).
A variable that's available at the beginning of the function (i.e. a function
argument) is available in between [FuncLogicalEnd,StackGrowthCall) using the
same DWARF instructions as the ones used at the beginning of the function.
The variable is then available in between [StackGrowthCall,FuncPhysicalEnd)
with new instructions referencing the stack location where the variable was
spilled to.

The patch only performs this fixup for function arguments on amd64 and
arm64; the tracking that linker needs to do for identifying
FuncLoficalEnd and StackGrowthCall is architecture-specific and I didn't
go through the trouble of doing it outside these two archs.

Fixes golang#60493

Change-Id: I8ccc9ae802bddc9427ecc4ffc5a26f172a8d6b83
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. help wanted NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
Development

Successfully merging a pull request may close this issue.

4 participants