Skip to content

Commit aca6442

Browse files
committed
runtime: add test for fault thread crash process
1 parent 5efbcbf commit aca6442

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

src/runtime/runtime-gdb_unix_test.go

+173
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,176 @@ func TestGdbCoreSignalBacktrace(t *testing.T) {
230230
t.Fatalf("could not find runtime symbol in backtrace after signal handler:\n%s", rest)
231231
}
232232
}
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

Comments
 (0)