blob: 65a5e23e380bbff0545af9ec98c3af7377fb47da [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"go.fuchsia.dev/fuchsia/tools/emulator"
"go.fuchsia.dev/fuchsia/tools/emulator/emulatortest"
)
// Boots an instance, |crash_cmd|, waits for the system to reboot, prints the
// recovered crash report.
//
// |crash_cmd| : The command to execute on the kernel command
// line to trigger the crash.
// |expected_crash_indicator| : The string to wait for which indicates that the
// system in crashing in the expected way in
// response to |crash_cmd|.
// |expected_reboot_reason| : The string we expect to see in the recovered
// crashlog which reports the expected reason for
// the crash/reboot.
func testCommon(t *testing.T,
crash_cmd string,
expected_crash_indicator string,
expected_reboot_reason string) *emulatortest.Instance {
exDir := execDir(t)
distro := emulatortest.UnpackFrom(t, filepath.Join(exDir, "test_data"), emulator.DistributionParams{
Emulator: emulator.Qemu,
})
arch := distro.TargetCPU()
if arch != emulator.Arm64 {
// TODO(maniscalco): Flesh out the qemu/x64 support for stowing/retrieving a
// crashlog.
t.Skipf("Skipping test. This test only supports arm64 targets.\n")
return nil
}
device := emulator.DefaultVirtualDevice(string(arch))
device.Hw.EnableKvm = false
// Be sure to reboot instead of halt so the newly booted kernel instance can retrieve
// the previous instance's crashlog.
//
// Upon booting run "k", which will print a usage message. By waiting for the usage
// message, we can be sure the system has booted and is ready to accept "k"
// commands.
device.KernelArgs = append(device.KernelArgs,
"kernel.halt-on-panic=false",
"kernel.render-dlog-to-crashlog=true",
"zircon.autorun.boot=/boot/bin/sh+-c+k")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
i := distro.CreateContext(ctx, device)
// Boot.
i.Start()
// Wait for the system to finish booting.
i.WaitForLogMessage("usage: k <command>")
// Crash the kernel.
i.RunCommand(crash_cmd)
i.WaitForLogMessage(expected_crash_indicator)
// Now that the kernel has panicked, it should reboot. Wait for it to come back up.
i.WaitForLogMessage("welcome to Zircon")
i.WaitForLogMessage("usage: k <command>")
// Early in boot, the system should have recovered the stowed crashlog and stored it in
// last-panic.txt. We're dumping that file using dd instead of cat because dd is part of
// the system image and cat is not.
i.RunCommand("dd if=/boot/log/last-panic.txt")
// See that the crashlog looks reasonable.
i.WaitForLogMessage(fmt.Sprintf("ZIRCON REBOOT REASON (%s)", expected_reboot_reason))
return i
}
// See that the kernel stows a crashlog upon panicking.
func TestKernelCrashlog(t *testing.T) {
i := testCommon(t, "k crash", "ZIRCON KERNEL PANIC", "KERNEL PANIC")
// There should be a banner present, with a "VERSION" line.
i.WaitForLogMessage("VERSION")
// Next up should be the symbolizer context, which starts with a reset tag.
i.WaitForLogMessage("{{{reset}}}")
// Register dump should be next.
i.WaitForLogMessage("REGISTERS")
// See that the crash report contains ESR and FAR.
//
// This is a regression test for fxbug.dev/52182. 'k crash' is going to
// attempt to store a value at a bad address in order to trigger its
// exception. While we cannot always rely on the lower bits of the ESR being
// consistent, we should be able to rely on the top byte being consistent in
// this case. The breakdown of the top bits is:
//
// 1) [31:26] EC : 0b100101 => Data Abort taken without a change in Exception level.
// 2) [25] IL : 0b1 => Instruction was not a 16 bit instruction
// 3) [24] ISV : 0b0 => Should always be 0, for this EC, the ARM ARM states "ISV is 0 for
// all faults reported in ESR_EL1 or ESR_EL3."
i.WaitForLogMessage("esr: 0x96")
i.WaitForLogMessage("far: 0x1")
// Next, the backtrace.
i.WaitForLogMessage("BACKTRACE")
i.WaitForLogMessage("{{{bt:0")
// Followed by critical counters.
i.WaitForLogMessage("counters: ")
// And, finally, the debug log.
i.WaitForLogMessage("--- BEGIN DLOG DUMP ---")
i.WaitForLogMessage("stopping other cpus")
i.WaitForLogMessage("--- END DLOG DUMP ---")
}
// See that when the kernel crashes because of an assert failure the crashlog contains the assert
// message.
func TestKernelCrashlogAssert(t *testing.T) {
i := testCommon(t, "k crash_assert", "ZIRCON KERNEL PANIC", "KERNEL PANIC")
// Similar checks to a non-assert crash, except that we expect a panic buffer
// with the details of the assert now, and no registers.
i.WaitForLogMessage("VERSION")
i.WaitForLogMessage("{{{reset}}}")
i.WaitForLogMessage("BACKTRACE")
i.WaitForLogMessage("{{{bt:0")
i.WaitForLogMessage("counters: ")
i.WaitForLogMessage("panic buffer: ")
i.WaitForLogMessage("KERNEL PANIC")
i.WaitForLogMessage("ASSERT FAILED")
i.WaitForLogMessage("value 42")
i.WaitForLogMessage("--- BEGIN DLOG DUMP ---")
i.WaitForLogMessage("stopping other cpus")
i.WaitForLogMessage("--- END DLOG DUMP ---")
}
func TestKernelCrashlogOom(t *testing.T) {
i := testCommon(t, "k pmm oom", "memory-pressure: rebooting due to OOM", "OOM")
// OOMs should have smaller crashlogs. They will be missing the
// registers/backtrace and the panic buffer.
i.WaitForLogMessage("VERSION")
i.WaitForLogMessage("counters: ")
i.WaitForLogMessage("--- BEGIN DLOG DUMP ---")
i.WaitForLogMessage("memory-pressure: rebooting due to OOM")
i.WaitForLogMessage("--- END DLOG DUMP ---")
}
func TestKernelCrashlogRootJobTermination(t *testing.T) {
i := testCommon(t, "killall bootsvc", "root-job: taking reboot action", "USERSPACE ROOT JOB TERMINATION")
// Root job termination should have the same contents as an OOM,
// plus the critical process name.
i.WaitForLogMessage("VERSION")
i.WaitForLogMessage("ROOT JOB TERMINATED BY CRITICAL PROCESS DEATH: bootsvc")
i.WaitForLogMessage("counters: ")
i.WaitForLogMessage("--- BEGIN DLOG DUMP ---")
i.WaitForLogMessage("root-job: taking reboot action")
i.WaitForLogMessage("--- END DLOG DUMP ---")
}
func TestKernelCrashlogNoCrash(t *testing.T) {
testCommon(t, "dm reboot", "[shutdown-shim]: started", "NO CRASH")
// We don't (currently) expect any payload from an graceful reboot.
}
func execDir(t *testing.T) string {
ex, err := os.Executable()
if err != nil {
t.Fatal(err)
}
return filepath.Dir(ex)
}