@@ -20,6 +20,43 @@ import (
20
20
"testing"
21
21
)
22
22
23
+ func canGenerateCore (t * testing.T ) bool {
24
+ // Ensure there is enough RLIMIT_CORE available to generate a full core.
25
+ var lim syscall.Rlimit
26
+ err := syscall .Getrlimit (syscall .RLIMIT_CORE , & lim )
27
+ if err != nil {
28
+ t .Fatalf ("error getting rlimit: %v" , err )
29
+ }
30
+ // Minimum RLIMIT_CORE max to allow. This is a conservative estimate.
31
+ // Most systems allow infinity.
32
+ const minRlimitCore = 100 << 20 // 100 MB
33
+ if lim .Max < minRlimitCore {
34
+ t .Skipf ("RLIMIT_CORE max too low: %#+v" , lim )
35
+ }
36
+
37
+ // Make sure core pattern will send core to the current directory.
38
+ b , err := os .ReadFile ("/proc/sys/kernel/core_pattern" )
39
+ if err != nil {
40
+ t .Fatalf ("error reading core_pattern: %v" , err )
41
+ }
42
+ if string (b ) != "core\n " {
43
+ t .Skipf ("Unexpected core pattern %q" , string (b ))
44
+ }
45
+
46
+ coreUsesPID := false
47
+ b , err = os .ReadFile ("/proc/sys/kernel/core_uses_pid" )
48
+ if err == nil {
49
+ switch string (bytes .TrimSpace (b )) {
50
+ case "0" :
51
+ case "1" :
52
+ coreUsesPID = true
53
+ default :
54
+ t .Skipf ("unexpected core_uses_pid value %q" , string (b ))
55
+ }
56
+ }
57
+ return coreUsesPID
58
+ }
59
+
23
60
const coreSignalSource = `
24
61
package main
25
62
@@ -81,45 +118,12 @@ func TestGdbCoreSignalBacktrace(t *testing.T) {
81
118
t .Parallel ()
82
119
checkGdbVersion (t )
83
120
84
- // Ensure there is enough RLIMIT_CORE available to generate a full core.
85
- var lim syscall.Rlimit
86
- err := syscall .Getrlimit (syscall .RLIMIT_CORE , & lim )
87
- if err != nil {
88
- t .Fatalf ("error getting rlimit: %v" , err )
89
- }
90
- // Minimum RLIMIT_CORE max to allow. This is a conservative estimate.
91
- // Most systems allow infinity.
92
- const minRlimitCore = 100 << 20 // 100 MB
93
- if lim .Max < minRlimitCore {
94
- t .Skipf ("RLIMIT_CORE max too low: %#+v" , lim )
95
- }
96
-
97
- // Make sure core pattern will send core to the current directory.
98
- b , err := os .ReadFile ("/proc/sys/kernel/core_pattern" )
99
- if err != nil {
100
- t .Fatalf ("error reading core_pattern: %v" , err )
101
- }
102
- if string (b ) != "core\n " {
103
- t .Skipf ("Unexpected core pattern %q" , string (b ))
104
- }
105
-
106
- coreUsesPID := false
107
- b , err = os .ReadFile ("/proc/sys/kernel/core_uses_pid" )
108
- if err == nil {
109
- switch string (bytes .TrimSpace (b )) {
110
- case "0" :
111
- case "1" :
112
- coreUsesPID = true
113
- default :
114
- t .Skipf ("unexpected core_uses_pid value %q" , string (b ))
115
- }
116
- }
117
-
118
- dir := t .TempDir ()
121
+ coreUsesPID := canGenerateCore (t )
119
122
120
123
// Build the source code.
124
+ dir := t .TempDir ()
121
125
src := filepath .Join (dir , "main.go" )
122
- err = os .WriteFile (src , []byte (coreSignalSource ), 0644 )
126
+ err : = os .WriteFile (src , []byte (coreSignalSource ), 0644 )
123
127
if err != nil {
124
128
t .Fatalf ("failed to create file: %v" , err )
125
129
}
@@ -230,3 +234,143 @@ func TestGdbCoreSignalBacktrace(t *testing.T) {
230
234
t .Fatalf ("could not find runtime symbol in backtrace after signal handler:\n %s" , rest )
231
235
}
232
236
}
237
+
238
+ const coreCrashThreadSource = `
239
+ package main
240
+
241
+ /*
242
+ #cgo CFLAGS: -g -O0
243
+ #include <stdio.h>
244
+ #include <stddef.h>
245
+ void trigger_crash()
246
+ {
247
+ int* ptr = NULL;
248
+ *ptr = 1024;
249
+ }
250
+ */
251
+ import "C"
252
+ import (
253
+ "flag"
254
+ "fmt"
255
+ "os"
256
+ "runtime/debug"
257
+ "syscall"
258
+ )
259
+
260
+ func enableCore() {
261
+ debug.SetTraceback("crash")
262
+
263
+ var lim syscall.Rlimit
264
+ err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim)
265
+ if err != nil {
266
+ panic(fmt.Sprintf("error getting rlimit: %v", err))
267
+ }
268
+ lim.Cur = lim.Max
269
+ fmt.Fprintf(os.Stderr, "Setting RLIMIT_CORE = %+#v\n", lim)
270
+ err = syscall.Setrlimit(syscall.RLIMIT_CORE, &lim)
271
+ if err != nil {
272
+ panic(fmt.Sprintf("error setting rlimit: %v", err))
273
+ }
274
+ }
275
+
276
+ func main() {
277
+ flag.Parse()
278
+
279
+ enableCore()
280
+
281
+ C.trigger_crash()
282
+ }
283
+ `
284
+
285
+ // TestGdbCoreCrashThreadBacktrace tests that runtime could let the fault thread to crash process
286
+ // and make fault thread as number one thread while gdb in a core file
287
+ func TestGdbCoreCrashThreadBacktrace (t * testing.T ) {
288
+ if runtime .GOOS != "linux" {
289
+ // N.B. This test isn't fundamentally Linux-only, but it needs
290
+ // to know how to enable/find core files on each OS.
291
+ t .Skip ("Test only supported on Linux" )
292
+ }
293
+ if runtime .GOARCH != "386" && runtime .GOARCH != "amd64" {
294
+ // TODO(go.dev/issue/25218): Other architectures use sigreturn
295
+ // via VDSO, which we somehow don't handle correctly.
296
+ t .Skip ("Backtrace through signal handler only works on 386 and amd64" )
297
+ }
298
+
299
+ checkGdbEnvironment (t )
300
+ t .Parallel ()
301
+ checkGdbVersion (t )
302
+
303
+ coreUsesPID := canGenerateCore (t )
304
+
305
+ // Build the source code.
306
+ dir := t .TempDir ()
307
+ src := filepath .Join (dir , "main.go" )
308
+ err := os .WriteFile (src , []byte (coreCrashThreadSource ), 0644 )
309
+ if err != nil {
310
+ t .Fatalf ("failed to create file: %v" , err )
311
+ }
312
+ cmd := exec .Command (testenv .GoToolPath (t ), "build" , "-o" , "a.exe" , "main.go" )
313
+ cmd .Dir = dir
314
+ out , err := testenv .CleanCmdEnv (cmd ).CombinedOutput ()
315
+ if err != nil {
316
+ t .Fatalf ("building source %v\n %s" , err , out )
317
+ }
318
+
319
+ // Start the test binary.
320
+ cmd = testenv .Command (t , "./a.exe" )
321
+ cmd .Dir = dir
322
+ var output bytes.Buffer
323
+ cmd .Stdout = & output // for test logging
324
+ cmd .Stderr = & output
325
+
326
+ if err := cmd .Start (); err != nil {
327
+ t .Fatalf ("error starting test binary: %v" , err )
328
+ }
329
+
330
+ pid := cmd .Process .Pid
331
+
332
+ err = cmd .Wait ()
333
+ t .Logf ("child output:\n %s" , output .String ())
334
+ if err == nil {
335
+ t .Fatalf ("Wait succeeded, want SIGABRT" )
336
+ }
337
+ ee , ok := err .(* exec.ExitError )
338
+ if ! ok {
339
+ t .Fatalf ("Wait err got %T %v, want exec.ExitError" , ee , ee )
340
+ }
341
+ ws , ok := ee .Sys ().(syscall.WaitStatus )
342
+ if ! ok {
343
+ t .Fatalf ("Sys got %T %v, want syscall.WaitStatus" , ee .Sys (), ee .Sys ())
344
+ }
345
+ if ws .Signal () != syscall .SIGABRT {
346
+ t .Fatalf ("Signal got %d want SIGABRT" , ws .Signal ())
347
+ }
348
+ if ! ws .CoreDump () {
349
+ t .Fatalf ("CoreDump got %v want true" , ws .CoreDump ())
350
+ }
351
+
352
+ coreFile := "core"
353
+ if coreUsesPID {
354
+ coreFile += fmt .Sprintf (".%d" , pid )
355
+ }
356
+
357
+ // Execute gdb commands.
358
+ args := []string {"-nx" , "-batch" ,
359
+ "-iex" , "add-auto-load-safe-path " + filepath .Join (testenv .GOROOT (t ), "src" , "runtime" ),
360
+ "-ex" , "backtrace" ,
361
+ filepath .Join (dir , "a.exe" ),
362
+ filepath .Join (dir , coreFile ),
363
+ }
364
+ cmd = testenv .Command (t , "gdb" , args ... )
365
+
366
+ got , err := cmd .CombinedOutput ()
367
+ t .Logf ("gdb output:\n %s" , got )
368
+ if err != nil {
369
+ t .Fatalf ("gdb exited with error: %v" , err )
370
+ }
371
+
372
+ re := regexp .MustCompile (`#.* trigger_crash` )
373
+ if found := re .Find (got ) != nil ; ! found {
374
+ t .Fatalf ("could not find trigger_crash in backtrace" )
375
+ }
376
+ }
0 commit comments