@@ -230,3 +230,176 @@ func TestGdbCoreSignalBacktrace(t *testing.T) {
230
230
t .Fatalf ("could not find runtime symbol in backtrace after signal handler:\n %s" , rest )
231
231
}
232
232
}
233
+
234
+ const coreCrashThreadSource = `
235
+ package main
236
+
237
+ /*
238
+ #cgo CFLAGS: -g -O0 -Wall
239
+ #include <stdio.h>
240
+ #include <stddef.h>
241
+ void trigger_crash()
242
+ {
243
+ int* ptr = NULL;
244
+ *ptr = 1024;
245
+ }
246
+ */
247
+ import "C"
248
+ import (
249
+ "flag"
250
+ "fmt"
251
+ "os"
252
+ "runtime/debug"
253
+ "syscall"
254
+ )
255
+
256
+ func enableCore() {
257
+ debug.SetTraceback("crash")
258
+
259
+ var lim syscall.Rlimit
260
+ err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim)
261
+ if err != nil {
262
+ panic(fmt.Sprintf("error getting rlimit: %v", err))
263
+ }
264
+ lim.Cur = lim.Max
265
+ fmt.Fprintf(os.Stderr, "Setting RLIMIT_CORE = %+#v\n", lim)
266
+ err = syscall.Setrlimit(syscall.RLIMIT_CORE, &lim)
267
+ if err != nil {
268
+ panic(fmt.Sprintf("error setting rlimit: %v", err))
269
+ }
270
+ }
271
+
272
+ func main() {
273
+ flag.Parse()
274
+
275
+ enableCore()
276
+
277
+ C.trigger_crash()
278
+ }
279
+ `
280
+
281
+ // TestGdbCoreCrashThreadBacktrace tests that gdb can unwind the stack correctly
282
+ // through a signal handler in a core file
283
+ func TestGdbCoreCrashThreadBacktrace (t * testing.T ) {
284
+ if runtime .GOOS != "linux" {
285
+ // N.B. This test isn't fundamentally Linux-only, but it needs
286
+ // to know how to enable/find core files on each OS.
287
+ t .Skip ("Test only supported on Linux" )
288
+ }
289
+ if runtime .GOARCH != "386" && runtime .GOARCH != "amd64" {
290
+ // TODO(go.dev/issue/25218): Other architectures use sigreturn
291
+ // via VDSO, which we somehow don't handle correctly.
292
+ t .Skip ("Backtrace through signal handler only works on 386 and amd64" )
293
+ }
294
+
295
+ checkGdbEnvironment (t )
296
+ t .Parallel ()
297
+ checkGdbVersion (t )
298
+
299
+ // Ensure there is enough RLIMIT_CORE available to generate a full core.
300
+ var lim syscall.Rlimit
301
+ err := syscall .Getrlimit (syscall .RLIMIT_CORE , & lim )
302
+ if err != nil {
303
+ t .Fatalf ("error getting rlimit: %v" , err )
304
+ }
305
+ // Minimum RLIMIT_CORE max to allow. This is a conservative estimate.
306
+ // Most systems allow infinity.
307
+ const minRlimitCore = 100 << 20 // 100 MB
308
+ if lim .Max < minRlimitCore {
309
+ t .Skipf ("RLIMIT_CORE max too low: %#+v" , lim )
310
+ }
311
+
312
+ // Make sure core pattern will send core to the current directory.
313
+ b , err := os .ReadFile ("/proc/sys/kernel/core_pattern" )
314
+ if err != nil {
315
+ t .Fatalf ("error reading core_pattern: %v" , err )
316
+ }
317
+ if string (b ) != "core\n " {
318
+ t .Skipf ("Unexpected core pattern %q" , string (b ))
319
+ }
320
+
321
+ coreUsesPID := false
322
+ b , err = os .ReadFile ("/proc/sys/kernel/core_uses_pid" )
323
+ if err == nil {
324
+ switch string (bytes .TrimSpace (b )) {
325
+ case "0" :
326
+ case "1" :
327
+ coreUsesPID = true
328
+ default :
329
+ t .Skipf ("unexpected core_uses_pid value %q" , string (b ))
330
+ }
331
+ }
332
+
333
+ dir := t .TempDir ()
334
+
335
+ // Build the source code.
336
+ src := filepath .Join (dir , "main.go" )
337
+ err = os .WriteFile (src , []byte (coreCrashThreadSource ), 0644 )
338
+ if err != nil {
339
+ t .Fatalf ("failed to create file: %v" , err )
340
+ }
341
+ cmd := exec .Command (testenv .GoToolPath (t ), "build" , "-o" , "a.exe" , "main.go" )
342
+ cmd .Dir = dir
343
+ out , err := testenv .CleanCmdEnv (cmd ).CombinedOutput ()
344
+ if err != nil {
345
+ t .Fatalf ("building source %v\n %s" , err , out )
346
+ }
347
+
348
+ // Start the test binary.
349
+ cmd = testenv .Command (t , "./a.exe" )
350
+ cmd .Dir = dir
351
+ var output bytes.Buffer
352
+ cmd .Stdout = & output // for test logging
353
+ cmd .Stderr = & output
354
+
355
+ if err := cmd .Start (); err != nil {
356
+ t .Fatalf ("error starting test binary: %v" , err )
357
+ }
358
+
359
+ pid := cmd .Process .Pid
360
+
361
+ err = cmd .Wait ()
362
+ t .Logf ("child output:\n %s" , output .String ())
363
+ if err == nil {
364
+ t .Fatalf ("Wait succeeded, want SIGSEGV" )
365
+ }
366
+ ee , ok := err .(* exec.ExitError )
367
+ if ! ok {
368
+ t .Fatalf ("Wait err got %T %v, want exec.ExitError" , ee , ee )
369
+ }
370
+ ws , ok := ee .Sys ().(syscall.WaitStatus )
371
+ if ! ok {
372
+ t .Fatalf ("Sys got %T %v, want syscall.WaitStatus" , ee .Sys (), ee .Sys ())
373
+ }
374
+ if ws .Signal () != syscall .SIGABRT {
375
+ t .Fatalf ("Signal got %d want SIGABRT" , ws .Signal ())
376
+ }
377
+ if ! ws .CoreDump () {
378
+ t .Fatalf ("CoreDump got %v want true" , ws .CoreDump ())
379
+ }
380
+
381
+ coreFile := "core"
382
+ if coreUsesPID {
383
+ coreFile += fmt .Sprintf (".%d" , pid )
384
+ }
385
+
386
+ // Execute gdb commands.
387
+ args := []string {"-nx" , "-batch" ,
388
+ "-iex" , "add-auto-load-safe-path " + filepath .Join (testenv .GOROOT (t ), "src" , "runtime" ),
389
+ "-ex" , "backtrace" ,
390
+ filepath .Join (dir , "a.exe" ),
391
+ filepath .Join (dir , coreFile ),
392
+ }
393
+ cmd = testenv .Command (t , "gdb" , args ... )
394
+
395
+ got , err := cmd .CombinedOutput ()
396
+ t .Logf ("gdb output:\n %s" , got )
397
+ if err != nil {
398
+ t .Fatalf ("gdb exited with error: %v" , err )
399
+ }
400
+
401
+ re := regexp .MustCompile (`#.* trigger_crash` )
402
+ if found := re .Find (got ) != nil ; ! found {
403
+ t .Fatalf ("could not find trigger_crash in backtrace" )
404
+ }
405
+ }
0 commit comments