blob: 37d3bb526cb6cd2f806369ef0a99d0f85ae36673 [file] [log] [blame]
// Copyright 2016 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.
// This file contains basic "crash-and-recover" test support where the inferior
// crashes and then the cause of the crash is fixed in the debugger and then
// the inferior is resumed. The pieces of the test are abstracted out info
// this file as the test is done in a couple of places.
//
// The test consists of two parts:
// 1) Debugger side:
// Send RQST_CRASH_AND_RECOVER.
// In the exception handler:
// - call TestSegvPc()
// - call TestMemoryOps()
// - call FixInferiorSegv()
// - resume the inferior
// 2) Inferior side:
// On receipt of RQST_CRASH_AND_RECOVER:
// - call TestPrepAndSegv()
// - send RESP_RECOVERED_FROM_CRASH
#include "crash-and-recover.h"
#include <assert.h>
#include <inttypes.h>
#include <lib/zx/thread.h>
#include <link.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/debug.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/object.h>
#include <zircon/syscalls/port.h>
#include <zircon/threads.h>
#include <atomic>
#include <test-utils/test-utils.h>
#include <zxtest/zxtest.h>
#include "debugger.h"
#include "inferior-control.h"
#include "inferior.h"
#include "src/lib/debug/backtrace-request.h"
#include "utils.h"
namespace {
constexpr size_t kTestMemorySize = 8;
constexpr uint8_t kTestDataAdjust = 0x10;
} // namespace
bool test_prep_and_segv() {
uint8_t test_data[kTestMemorySize];
for (unsigned i = 0; i < sizeof(test_data); ++i)
test_data[i] = static_cast<uint8_t>(i);
#ifdef __x86_64__
void* segv_pc;
// Note: Fuchsia is always PIC.
__asm__("leaq .Lsegv_here(%%rip),%0" : "=r"(segv_pc));
printf("About to segv, pc %p\n", segv_pc);
// Set r9 to point to test_data so we can easily access it
// from the parent process. Likewise set r10 to segv_pc
// so the parent process can verify it matches the fault PC.
__asm__(
"\
movq %[zero],%%r8\n\
movq %[test_data],%%r9\n\
movq %[pc],%%r10\n\
.Lsegv_here:\n\
movq (%%r8),%%rax\
"
:
: [zero] "g"(0), [test_data] "g"(test_data), [pc] "g"(segv_pc)
: "rax", "r8", "r9", "r10");
#elif defined(__aarch64__)
void* segv_pc;
// Note: Fuchsia is always PIC.
__asm__(
"adrp %0, .Lsegv_here\n"
"add %0, %0, :lo12:.Lsegv_here"
: "=r"(segv_pc));
printf("About to segv, pc %p\n", segv_pc);
// Set r9 to point to test_data so we can easily access it
// from the parent process. Likewise set r10 to segv_pc
// so the parent process can verify it matches the fault PC.
__asm__(
"\
mov x8,xzr\n\
mov x9,%[test_data]\n\
mov x10,%[pc]\n\
.Lsegv_here:\n\
ldr x0,[x8]\
"
:
: [test_data] "r"(test_data), [pc] "r"(segv_pc)
: "x0", "x8", "x9", "x10");
#elif defined(__riscv)
void* segv_pc;
__asm__("lla %0, .Lsegv_here" : "=r"(segv_pc));
printf("About to segv, pc %p\n", segv_pc);
// Set s1 to point to test_data so we can easily access it
// from the parent process. Likewise set s2 to segv_pc
// so the parent process can verify it matches the fault PC.
void* scratch;
__asm__ volatile(
R"""(
mv s1, zero
mv s2, %[test_data]
mv s3, %[pc]
.Lsegv_here:
ld %[scratch], (s1)
)"""
: [scratch] "=r"(scratch)
: [test_data] "r"(test_data), [pc] "r"(segv_pc)
: "s1", "s2", "s3");
#else
#error "what machine?"
#endif
// On resumption test_data should have had kTestDataAdjust added to each element.
// Note: This is the inferior process, it's not running under the test harness.
for (unsigned i = 0; i < sizeof(test_data); ++i) {
if (test_data[i] != i + kTestDataAdjust) {
printf("TestPrepAndSegv: bad data on resumption, test_data[%u] = 0x%x\n", i, test_data[i]);
return false;
}
}
printf("Inferior successfully resumed!\n");
return true;
}
void test_segv_pc(zx_handle_t thread) {
zx_thread_state_general_regs_t regs;
read_inferior_gregs(thread, &regs);
#if defined(__x86_64__)
ASSERT_EQ(regs.rip, regs.r10, "fault PC does not match r10");
#elif defined(__aarch64__)
ASSERT_EQ(regs.pc, regs.r[10], "fault PC does not match x10");
#elif defined(__riscv)
ASSERT_EQ(regs.pc, regs.s3, "fault PC does not match s3");
#else
#error "what machine?"
#endif
}
void test_memory_ops(zx_handle_t inferior, zx_handle_t thread) {
uint64_t test_data_addr = 0;
uint8_t test_data[kTestMemorySize];
zx_thread_state_general_regs_t regs;
read_inferior_gregs(thread, &regs);
#if defined(__x86_64__)
test_data_addr = regs.r9;
#elif defined(__aarch64__)
test_data_addr = regs.r[9];
// When HWASan is enabled, r9 will contain a tag because it points to `test_data` from
// `test_memory_ops` which is also tagged. This value is passed directly to zx_process_read_memory
// which does not allow tags and incorrectly attempt to read from the address with the tag rather
// than the untagged address.
constexpr uintptr_t kAddrMask = ~(UINT64_C(0xFF) << 56);
test_data_addr &= kAddrMask;
#elif defined(__riscv)
test_data_addr = regs.s2;
#else
#error "what machine?"
#endif
size_t size = read_inferior_memory(inferior, test_data_addr, test_data, sizeof(test_data));
EXPECT_EQ(size, sizeof(test_data), "read_inferior_memory: short read");
for (unsigned i = 0; i < sizeof(test_data); ++i) {
EXPECT_EQ(test_data[i], i, "test_memory_ops");
}
for (unsigned i = 0; i < sizeof(test_data); ++i) {
test_data[i] = static_cast<uint8_t>(test_data[i] + kTestDataAdjust);
}
size = write_inferior_memory(inferior, test_data_addr, test_data, sizeof(test_data));
EXPECT_EQ(size, sizeof(test_data), "write_inferior_memory: short write");
// Note: Verification of the write is done in the inferior.
}
void fix_inferior_segv(zx_handle_t thread, const char* what) {
printf("Fixing inferior segv for %s\n", what);
// The segv was because r8 == 0, change it to a usable value. See TestPrepAndSegv.
zx_thread_state_general_regs_t regs;
read_inferior_gregs(thread, &regs);
#if defined(__x86_64__)
regs.r8 = regs.rsp;
#elif defined(__aarch64__)
regs.r[8] = regs.sp;
#elif defined(__riscv)
regs.s1 = regs.sp;
#else
#error "what machine?"
#endif
write_inferior_gregs(thread, &regs);
}