Skip to content

Commit 715d53c

Browse files
committed
runtime: fallback to TEB arbitrary pointer when TLS slots are full
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]>
1 parent 14f833f commit 715d53c

File tree

6 files changed

+116
-4
lines changed

6 files changed

+116
-4
lines changed

src/runtime/signal_windows_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,59 @@ func TestLibraryCtrlHandler(t *testing.T) {
257257
t.Fatalf("Program exited with error: %v\n%s", err, &stderr)
258258
}
259259
}
260+
261+
func TestIssue59213(t *testing.T) {
262+
if runtime.GOOS != "windows" {
263+
t.Skip("skipping windows only test")
264+
}
265+
if *flagQuick {
266+
t.Skip("-quick")
267+
}
268+
testenv.MustHaveGoBuild(t)
269+
testenv.MustHaveCGO(t)
270+
271+
goEnv := func(arg string) string {
272+
cmd := testenv.Command(t, testenv.GoToolPath(t), "env", arg)
273+
cmd.Stderr = new(bytes.Buffer)
274+
275+
line, err := cmd.Output()
276+
if err != nil {
277+
t.Fatalf("%v: %v\n%s", cmd, err, cmd.Stderr)
278+
}
279+
out := string(bytes.TrimSpace(line))
280+
t.Logf("%v: %q", cmd, out)
281+
return out
282+
}
283+
284+
cc := goEnv("CC")
285+
cgoCflags := goEnv("CGO_CFLAGS")
286+
287+
t.Parallel()
288+
289+
tmpdir := t.TempDir()
290+
dllfile := filepath.Join(tmpdir, "test.dll")
291+
exefile := filepath.Join(tmpdir, "gotest.exe")
292+
293+
// build go dll
294+
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", dllfile, "-buildmode", "c-shared", "testdata/testwintls/main.go")
295+
out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
296+
if err != nil {
297+
t.Fatalf("failed to build go library: %s\n%s", err, out)
298+
}
299+
300+
// build c program
301+
cmd = testenv.Command(t, cc, "-o", exefile, "testdata/testwintls/main.c")
302+
testenv.CleanCmdEnv(cmd)
303+
cmd.Env = append(cmd.Env, "CGO_CFLAGS="+cgoCflags)
304+
out, err = cmd.CombinedOutput()
305+
if err != nil {
306+
t.Fatalf("failed to build c exe: %s\n%s", err, out)
307+
}
308+
309+
// run test program
310+
cmd = testenv.Command(t, exefile, dllfile, "GoFunc")
311+
out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
312+
if err != nil {
313+
t.Fatalf("failed: %s\n%s", err, out)
314+
}
315+
}

src/runtime/sys_windows_386.s

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
// Offsets into Thread Environment Block (pointer in FS)
1111
#define TEB_TlsSlots 0xE10
12+
#define TEB_ArbitraryPtr 0x14
1213

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

295299
// Save offset from TLS into tls_g.
296300
ADDL $TEB_TlsSlots, CX
301+
settls:
297302
MOVL CX, runtime·tls_g(SB)
298303
RET

src/runtime/sys_windows_amd64.s

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
// Offsets into Thread Environment Block (pointer in GS)
1212
#define TEB_TlsSlots 0x1480
13+
#define TEB_ArbitraryPtr 0x28
1314

1415
// void runtime·asmstdcall(void *c);
1516
TEXT runtime·asmstdcall(SB),NOSPLIT,$16
@@ -301,13 +302,18 @@ TEXT runtime·wintls(SB),NOSPLIT,$0
301302
// Assert that slot is less than 64 so we can use _TEB->TlsSlots
302303
CMPQ CX, $64
303304
JB ok
304-
CALL runtime·abort(SB)
305+
306+
// Fallback to the TEB arbitrary pointer.
307+
// TODO: don't use the arbitrary pointer (see go.dev/issue/59824)
308+
MOVQ $TEB_ArbitraryPtr, CX
309+
JMP settls
305310
ok:
306311
// Convert the TLS index at CX into
307312
// an offset from TEB_TlsSlots.
308313
SHLQ $3, CX
309314

310315
// Save offset from TLS into tls_g.
311316
ADDQ $TEB_TlsSlots, CX
317+
settls:
312318
MOVQ CX, runtime·tls_g(SB)
313319
RET

src/runtime/sys_windows_arm64.s

+6-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// Offsets into Thread Environment Block (pointer in R18)
1313
#define TEB_error 0x68
1414
#define TEB_TlsSlots 0x1480
15+
#define TEB_ArbitraryPtr 0x28
1516

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

280283
// Save offset from R18 into tls_g.
281284
LSL $3, R0
282285
ADD $TEB_TlsSlots, R0
286+
settls:
283287
MOVD R0, runtime·tls_g(SB)
284288
RET
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
#include <windows.h>
6+
7+
int main(int argc, char **argv) {
8+
if (argc < 3) {
9+
return 1;
10+
}
11+
// Allocate more than 64 TLS indices
12+
// so the Go runtime doesn't find
13+
// enough space in the TEB TLS slots.
14+
for (int i = 0; i < 65; i++) {
15+
TlsAlloc();
16+
}
17+
HMODULE hlib = LoadLibrary(argv[1]);
18+
if (hlib == NULL) {
19+
return 2;
20+
}
21+
FARPROC proc = GetProcAddress(hlib, argv[2]);
22+
if (proc == NULL) {
23+
return 3;
24+
}
25+
if (proc() != 42) {
26+
return 4;
27+
}
28+
return 0;
29+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import "C"
8+
9+
//export GoFunc
10+
func GoFunc() int { return 42 }
11+
12+
func main() {}

0 commit comments

Comments
 (0)