| // 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) |
| } |