aboutsummaryrefslogtreecommitdiff
path: root/src/runtime
diff options
context:
space:
mode:
authorqmuntal <quimmuntal@gmail.com>2023-01-17 08:15:33 +0100
committerQuim Muntal <quimmuntal@gmail.com>2023-05-02 07:42:50 +0000
commit14cf82aa37ec33012ca48febd83fb16e1178beee (patch)
tree0d8b39e6bf1a7919029951aa229a996d990ee967 /src/runtime
parent53279a6af372e3708afe8eaf618d56ee98edf045 (diff)
downloadgo-14cf82aa37ec33012ca48febd83fb16e1178beee.tar.xz
cmd/link: generate .pdata PE section
This CL adds a .pdata section to the PE file generated by the Go linker. The .pdata section is a standard section [1] that contains an array of function table entries that are used for stack unwinding. The table entries layout is taken from [2]. This CL just generates the table entries without any unwinding information, which is enough to start doing some E2E tests between the Go linker and the Win32 APIs. The goal of the .pdata table is to allow Windows retrieve unwind information for a function at a given PC. It does so by doing a binary search on the table, looking for an entry that meets BeginAddress >= PC < EndAddress. Each table entry takes 12 bytes and only non-leaf functions with frame pointer needs an entry on the .pdata table. The result is that PE binaries will be ~0.7% bigger due to the unwind information, a reasonable amount considering the benefits in debuggability. Updates #57302 [1] https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-pdata-section [2] https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-runtime_function Change-Id: If675d10c64452946dbab76709da20569651e3e9f Reviewed-on: https://go-review.googlesource.com/c/go/+/461738 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Alex Brainman <alex.brainman@gmail.com> Reviewed-by: Than McIntosh <thanm@google.com> Run-TryBot: Quim Muntal <quimmuntal@gmail.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
Diffstat (limited to 'src/runtime')
-rw-r--r--src/runtime/defs_windows_386.go3
-rw-r--r--src/runtime/defs_windows_amd64.go1
-rw-r--r--src/runtime/defs_windows_arm.go3
-rw-r--r--src/runtime/defs_windows_arm64.go1
-rw-r--r--src/runtime/export_windows_test.go16
-rw-r--r--src/runtime/runtime-seh_windows_test.go63
6 files changed, 87 insertions, 0 deletions
diff --git a/src/runtime/defs_windows_386.go b/src/runtime/defs_windows_386.go
index 8d6c443a14..b11b15554e 100644
--- a/src/runtime/defs_windows_386.go
+++ b/src/runtime/defs_windows_386.go
@@ -56,6 +56,9 @@ func (c *context) set_lr(x uintptr) {}
func (c *context) set_ip(x uintptr) { c.eip = uint32(x) }
func (c *context) set_sp(x uintptr) { c.esp = uint32(x) }
+// 386 does not have frame pointer register.
+func (c *context) set_fp(x uintptr) {}
+
func prepareContextForSigResume(c *context) {
c.edx = c.esp
c.ecx = c.eip
diff --git a/src/runtime/defs_windows_amd64.go b/src/runtime/defs_windows_amd64.go
index afa8a657b8..20c9c4d932 100644
--- a/src/runtime/defs_windows_amd64.go
+++ b/src/runtime/defs_windows_amd64.go
@@ -69,6 +69,7 @@ func (c *context) set_lr(x uintptr) {}
func (c *context) set_ip(x uintptr) { c.rip = uint64(x) }
func (c *context) set_sp(x uintptr) { c.rsp = uint64(x) }
+func (c *context) set_fp(x uintptr) { c.rbp = uint64(x) }
func prepareContextForSigResume(c *context) {
c.r8 = c.rsp
diff --git a/src/runtime/defs_windows_arm.go b/src/runtime/defs_windows_arm.go
index 21c7991519..7a18c95cf1 100644
--- a/src/runtime/defs_windows_arm.go
+++ b/src/runtime/defs_windows_arm.go
@@ -58,6 +58,9 @@ func (c *context) set_ip(x uintptr) { c.pc = uint32(x) }
func (c *context) set_sp(x uintptr) { c.spr = uint32(x) }
func (c *context) set_lr(x uintptr) { c.lrr = uint32(x) }
+// arm does not have frame pointer register.
+func (c *context) set_fp(x uintptr) {}
+
func prepareContextForSigResume(c *context) {
c.r0 = c.spr
c.r1 = c.pc
diff --git a/src/runtime/defs_windows_arm64.go b/src/runtime/defs_windows_arm64.go
index 6c71133b43..ef2efb1bb3 100644
--- a/src/runtime/defs_windows_arm64.go
+++ b/src/runtime/defs_windows_arm64.go
@@ -40,6 +40,7 @@ func (c *context) lr() uintptr { return uintptr(c.x[30]) }
func (c *context) set_ip(x uintptr) { c.pc = uint64(x) }
func (c *context) set_sp(x uintptr) { c.xsp = uint64(x) }
func (c *context) set_lr(x uintptr) { c.x[30] = uint64(x) }
+func (c *context) set_fp(x uintptr) { c.x[29] = uint64(x) }
func prepareContextForSigResume(c *context) {
c.x[0] = c.xsp
diff --git a/src/runtime/export_windows_test.go b/src/runtime/export_windows_test.go
index 332136b586..5b9f08fb79 100644
--- a/src/runtime/export_windows_test.go
+++ b/src/runtime/export_windows_test.go
@@ -20,3 +20,19 @@ func NumberOfProcessors() int32 {
stdcall1(_GetSystemInfo, uintptr(unsafe.Pointer(&info)))
return int32(info.dwnumberofprocessors)
}
+
+type ContextStub struct {
+ context
+}
+
+func (c ContextStub) GetPC() uintptr {
+ return c.ip()
+}
+
+func NewContextStub() ContextStub {
+ var ctx context
+ ctx.set_ip(getcallerpc())
+ ctx.set_sp(getcallersp())
+ ctx.set_fp(getcallerfp())
+ return ContextStub{ctx}
+}
diff --git a/src/runtime/runtime-seh_windows_test.go b/src/runtime/runtime-seh_windows_test.go
new file mode 100644
index 0000000000..23f5b87bf5
--- /dev/null
+++ b/src/runtime/runtime-seh_windows_test.go
@@ -0,0 +1,63 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package runtime_test
+
+import (
+ "internal/abi"
+ "internal/syscall/windows"
+ "runtime"
+ "testing"
+)
+
+func sehf1() int {
+ return sehf1()
+}
+
+func sehf2() {}
+
+func TestSehLookupFunctionEntry(t *testing.T) {
+ if runtime.GOARCH != "amd64" {
+ t.Skip("skipping amd64-only test")
+ }
+ // This test checks that Win32 is able to retrieve
+ // function metadata stored in the .pdata section
+ // by the Go linker.
+ // Win32 unwinding will fail if this test fails,
+ // as RtlUnwindEx uses RtlLookupFunctionEntry internally.
+ // If that's the case, don't bother investigating further,
+ // first fix the .pdata generation.
+ sehf1pc := abi.FuncPCABIInternal(sehf1)
+ var fnwithframe func()
+ fnwithframe = func() {
+ fnwithframe()
+ }
+ fnwithoutframe := func() {}
+ tests := []struct {
+ name string
+ pc uintptr
+ hasframe bool
+ }{
+ {"no frame func", abi.FuncPCABIInternal(sehf2), false},
+ {"no func", sehf1pc - 1, false},
+ {"func at entry", sehf1pc, true},
+ {"func in prologue", sehf1pc + 1, true},
+ {"anonymous func with frame", abi.FuncPCABIInternal(fnwithframe), true},
+ {"anonymous func without frame", abi.FuncPCABIInternal(fnwithoutframe), false},
+ {"pc at func body", runtime.NewContextStub().GetPC(), true},
+ }
+ for _, tt := range tests {
+ var base uintptr
+ fn := windows.RtlLookupFunctionEntry(tt.pc, &base, nil)
+ if !tt.hasframe {
+ if fn != 0 {
+ t.Errorf("%s: unexpected frame", tt.name)
+ }
+ continue
+ }
+ if fn == 0 {
+ t.Errorf("%s: missing frame", tt.name)
+ }
+ }
+}