Skip to content

Commit

Permalink
runtime: fallback to TEB arbitrary pointer when TLS slots are full
Browse files Browse the repository at this point in the history
The Go runtime allocates the TLS slot in the TEB TLS slots instead of
using the TEB arbitrary pointer. See CL 431775 for more context.

The problem is that the TEB TLS slots array only has capacity for 64
indices, allocating more requires some complex logic that we don't
support yet.

Although the Go runtime only allocates one index, a Go DLL can be
loaded in a process with more than 64 TLS slots allocated,
in which case it abort.

This CL avoids aborting by falling back to the older behavior, that
is to use the TEB arbitrary pointer.

Fixes #59213

Change-Id: I39c73286fe2da95aa9c5ec5657ee0979ecbec533
Reviewed-on: https://go-review.googlesource.com/c/go/+/486816
Reviewed-by: Dmitri Shuralyov <[email protected]>
Reviewed-by: Cherry Mui <[email protected]>
Run-TryBot: Quim Muntal <[email protected]>
Reviewed-by: Bryan Mills <[email protected]>
Reviewed-by: Alex Brainman <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
  • Loading branch information
qmuntal committed Apr 25, 2023
1 parent 14f833f commit 715d53c
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 4 deletions.
56 changes: 56 additions & 0 deletions src/runtime/signal_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,59 @@ func TestLibraryCtrlHandler(t *testing.T) {
t.Fatalf("Program exited with error: %v\n%s", err, &stderr)
}
}

func TestIssue59213(t *testing.T) {
if runtime.GOOS != "windows" {
t.Skip("skipping windows only test")
}
if *flagQuick {
t.Skip("-quick")
}
testenv.MustHaveGoBuild(t)
testenv.MustHaveCGO(t)

goEnv := func(arg string) string {
cmd := testenv.Command(t, testenv.GoToolPath(t), "env", arg)
cmd.Stderr = new(bytes.Buffer)

line, err := cmd.Output()
if err != nil {
t.Fatalf("%v: %v\n%s", cmd, err, cmd.Stderr)
}
out := string(bytes.TrimSpace(line))
t.Logf("%v: %q", cmd, out)
return out
}

cc := goEnv("CC")
cgoCflags := goEnv("CGO_CFLAGS")

t.Parallel()

tmpdir := t.TempDir()
dllfile := filepath.Join(tmpdir, "test.dll")
exefile := filepath.Join(tmpdir, "gotest.exe")

// build go dll
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", dllfile, "-buildmode", "c-shared", "testdata/testwintls/main.go")
out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
if err != nil {
t.Fatalf("failed to build go library: %s\n%s", err, out)
}

// build c program
cmd = testenv.Command(t, cc, "-o", exefile, "testdata/testwintls/main.c")
testenv.CleanCmdEnv(cmd)
cmd.Env = append(cmd.Env, "CGO_CFLAGS="+cgoCflags)
out, err = cmd.CombinedOutput()
if err != nil {
t.Fatalf("failed to build c exe: %s\n%s", err, out)
}

// run test program
cmd = testenv.Command(t, exefile, dllfile, "GoFunc")
out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
if err != nil {
t.Fatalf("failed: %s\n%s", err, out)
}
}
7 changes: 6 additions & 1 deletion src/runtime/sys_windows_386.s
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

// Offsets into Thread Environment Block (pointer in FS)
#define TEB_TlsSlots 0xE10
#define TEB_ArbitraryPtr 0x14

// void runtime·asmstdcall(void *c);
TEXT runtime·asmstdcall(SB),NOSPLIT,$0
Expand Down Expand Up @@ -286,13 +287,17 @@ TEXT runtime·wintls(SB),NOSPLIT,$0
// Assert that slot is less than 64 so we can use _TEB->TlsSlots
CMPL CX, $64
JB ok
CALL runtime·abort(SB)
// Fallback to the TEB arbitrary pointer.
// TODO: don't use the arbitrary pointer (see go.dev/issue/59824)
MOVL $TEB_ArbitraryPtr, CX
JMP settls
ok:
// Convert the TLS index at CX into
// an offset from TEB_TlsSlots.
SHLL $2, CX

// Save offset from TLS into tls_g.
ADDL $TEB_TlsSlots, CX
settls:
MOVL CX, runtime·tls_g(SB)
RET
8 changes: 7 additions & 1 deletion src/runtime/sys_windows_amd64.s
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

// Offsets into Thread Environment Block (pointer in GS)
#define TEB_TlsSlots 0x1480
#define TEB_ArbitraryPtr 0x28

// void runtime·asmstdcall(void *c);
TEXT runtime·asmstdcall(SB),NOSPLIT,$16
Expand Down Expand Up @@ -301,13 +302,18 @@ TEXT runtime·wintls(SB),NOSPLIT,$0
// Assert that slot is less than 64 so we can use _TEB->TlsSlots
CMPQ CX, $64
JB ok
CALL runtime·abort(SB)

// Fallback to the TEB arbitrary pointer.
// TODO: don't use the arbitrary pointer (see go.dev/issue/59824)
MOVQ $TEB_ArbitraryPtr, CX
JMP settls
ok:
// Convert the TLS index at CX into
// an offset from TEB_TlsSlots.
SHLQ $3, CX

// Save offset from TLS into tls_g.
ADDQ $TEB_TlsSlots, CX
settls:
MOVQ CX, runtime·tls_g(SB)
RET
8 changes: 6 additions & 2 deletions src/runtime/sys_windows_arm64.s
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// Offsets into Thread Environment Block (pointer in R18)
#define TEB_error 0x68
#define TEB_TlsSlots 0x1480
#define TEB_ArbitraryPtr 0x28

// Note: R0-R7 are args, R8 is indirect return value address,
// R9-R15 are caller-save, R19-R29 are callee-save.
Expand Down Expand Up @@ -273,12 +274,15 @@ TEXT runtime·wintls(SB),NOSPLIT,$0
// Assert that slot is less than 64 so we can use _TEB->TlsSlots
CMP $64, R0
BLT ok
MOVD $runtime·abort(SB), R1
BL (R1)
// Fallback to the TEB arbitrary pointer.
// TODO: don't use the arbitrary pointer (see go.dev/issue/59824)
MOVD $TEB_ArbitraryPtr, R0
B settls
ok:

// Save offset from R18 into tls_g.
LSL $3, R0
ADD $TEB_TlsSlots, R0
settls:
MOVD R0, runtime·tls_g(SB)
RET
29 changes: 29 additions & 0 deletions src/runtime/testdata/testwintls/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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.

#include <windows.h>

int main(int argc, char **argv) {
if (argc < 3) {
return 1;
}
// Allocate more than 64 TLS indices
// so the Go runtime doesn't find
// enough space in the TEB TLS slots.
for (int i = 0; i < 65; i++) {
TlsAlloc();
}
HMODULE hlib = LoadLibrary(argv[1]);
if (hlib == NULL) {
return 2;
}
FARPROC proc = GetProcAddress(hlib, argv[2]);
if (proc == NULL) {
return 3;
}
if (proc() != 42) {
return 4;
}
return 0;
}
12 changes: 12 additions & 0 deletions src/runtime/testdata/testwintls/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 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 main

import "C"

//export GoFunc
func GoFunc() int { return 42 }

func main() {}

0 comments on commit 715d53c

Please sign in to comment.