Skip to content

Commit

Permalink
signal: Backtrace on SIGUSR1
Browse files Browse the repository at this point in the history
Rework the signal handling code so that if debug is enabled and a
`SIGUSR1` signal is received, backtrace to the system log but continue
to run.

Added some basic tests for the signal handling code.

Fixes kata-containers#76.

Signed-off-by: James O. D. Hunt <[email protected]>
  • Loading branch information
jodh-intel committed May 11, 2018
1 parent ef3858e commit d6b92a5
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 4 deletions.
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const (
// version is the shim version. This variable is populated at build time.
var version = "unknown"

var debug bool

// if true, coredump when an internal error occurs or a fatal signal is received
var crashOnError = false

Expand Down Expand Up @@ -76,7 +78,6 @@ func realMain() {
terminal bool
proxyExitCode bool
showVersion bool
debug bool
)

flag.BoolVar(&debug, "debug", false, "enable debug mode")
Expand Down
6 changes: 6 additions & 0 deletions shim.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ func (s *shim) forwardAllSignals() chan os.Signal {
//ignore these
continue
}

if debug && nonFatalSignal(sysSig) {
logger().WithField("signal", sig).Debug("handling signal")
backtrace()
}

// forward this signal to container
_, err := s.agent.SignalProcess(s.ctx, &pb.SignalProcessRequest{
ContainerId: s.containerID,
Expand Down
33 changes: 30 additions & 3 deletions signals.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import (
"syscall"
)

// List of fatal signals
var sigFatal = map[syscall.Signal]bool{
// List of handled signals.
//
// The value is true if receiving the signal should be fatal.
var handledSignalsMap = map[syscall.Signal]bool{
syscall.SIGABRT: true,
syscall.SIGBUS: true,
syscall.SIGILL: true,
Expand All @@ -25,6 +27,7 @@ var sigFatal = map[syscall.Signal]bool{
syscall.SIGSTKFLT: true,
syscall.SIGSYS: true,
syscall.SIGTRAP: true,
syscall.SIGUSR1: false,
}

func handlePanic() {
Expand Down Expand Up @@ -55,7 +58,31 @@ func backtrace() {
}

func fatalSignal(sig syscall.Signal) bool {
return sigFatal[sig]
s, exists := handledSignalsMap[sig]
if !exists {
return false
}

return s
}

func nonFatalSignal(sig syscall.Signal) bool {
s, exists := handledSignalsMap[sig]
if !exists {
return false
}

return !s
}

func handledSignals() []syscall.Signal {
var signals []syscall.Signal

for sig := range handledSignalsMap {
signals = append(signals, sig)
}

return signals
}

func die() {
Expand Down
135 changes: 135 additions & 0 deletions signals_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//

package main

import (
"bytes"
"reflect"
goruntime "runtime"
"sort"
"strings"
"syscall"
"testing"

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestSignalFatalSignal(t *testing.T) {
assert := assert.New(t)

for sig, fatal := range handledSignalsMap {
result := nonFatalSignal(sig)
if fatal {
assert.False(result)
} else {
assert.True(result)
}
}
}

func TestSignalHandledSignalsMap(t *testing.T) {
assert := assert.New(t)

for sig, fatal := range handledSignalsMap {
result := fatalSignal(sig)
if fatal {
assert.True(result)
} else {
assert.False(result)
}
}
}

func TestSignalHandledSignals(t *testing.T) {
assert := assert.New(t)

var expected []syscall.Signal

for sig := range handledSignalsMap {
expected = append(expected, sig)
}

got := handledSignals()

sort.Slice(expected, func(i, j int) bool {
return int(expected[i]) < int(expected[j])
})

sort.Slice(got, func(i, j int) bool {
return int(got[i]) < int(got[j])
})

assert.True(reflect.DeepEqual(expected, got))
}

func TestSignalNonFatalSignal(t *testing.T) {
assert := assert.New(t)

for sig, fatal := range handledSignalsMap {
result := nonFatalSignal(sig)
if fatal {
assert.False(result)
} else {
assert.True(result)
}
}
}

func TestSignalFatalSignalInvalidSignal(t *testing.T) {
assert := assert.New(t)

sig := syscall.SIGXCPU

result := fatalSignal(sig)
assert.False(result)
}

func TestSignalNonFatalSignalInvalidSignal(t *testing.T) {
assert := assert.New(t)

sig := syscall.SIGXCPU

result := nonFatalSignal(sig)
assert.False(result)
}

func TestSignalBacktrace(t *testing.T) {
assert := assert.New(t)

savedLog := shimLog
defer func() {
shimLog = savedLog
}()

shimLog = logrus.WithField("test-logger", true)

// create buffer to save logger output
buf := &bytes.Buffer{}

savedOut := shimLog.Logger.Out
defer func() {
shimLog.Logger.Out = savedOut
}()

// capture output to buffer
shimLog.Logger.Out = buf

// determine name of *this* function
pc := make([]uintptr, 1)
goruntime.Callers(1, pc)
fn := goruntime.FuncForPC(pc[0])
name := fn.Name()

backtrace()

b := buf.String()

// very basic tests to check if a backtrace was produced
assert.True(strings.Contains(b, "contention:"))
assert.True(strings.Contains(b, `level=error`))
assert.True(strings.Contains(b, name))
}

0 comments on commit d6b92a5

Please sign in to comment.