blob: 5c61e3574fdda826f1550766b982b449c6ea37cb [file] [log] [blame]
// Copyright 2018 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.
#include <string.h>
#include <zircon/syscalls/exception.h>
#include "garnet/bin/debug_agent/arch.h"
#include "garnet/bin/debug_agent/breakpoint.h"
#include "garnet/bin/debug_agent/process_breakpoint.h"
#include "garnet/bin/debug_agent/process_memory_accessor.h"
#include "gtest/gtest.h"
namespace debug_agent {
namespace {
// Provides a fake view of memory with the given initial contents.
class FakeMemory : public ProcessMemoryAccessor {
public:
FakeMemory(intptr_t address, const char* data, size_t data_len)
: address_(address) {
data_.assign(data, data + data_len);
}
const char* data() const { return &data_[0]; }
zx_status_t ReadProcessMemory(uintptr_t address, void* buffer, size_t len,
size_t* actual) override {
*actual = 0;
if (address < address_ || address + len > address_ + data_.size())
return ZX_ERR_NO_MEMORY; // We require everything to be mapped.
memcpy(buffer, &data_[address - address_], len);
*actual = len;
return ZX_OK;
}
zx_status_t WriteProcessMemory(uintptr_t address, const void* buffer,
size_t len, size_t* actual) override {
*actual = 0;
if (address < address_ || address + len > address_ + data_.size())
return ZX_ERR_NO_MEMORY; // We require everything to be mapped.
memcpy(&data_[address - address_], buffer, len);
*actual = len;
return ZX_OK;
}
private:
uintptr_t address_;
std::vector<char> data_;
};
// Provides a buffer of known memory for tests below.
class BreakpointFakeMemory {
public:
// Make a fake memory buffer with enough room to hold a break instruction.
static constexpr uintptr_t kAddress = 0x123456780;
static constexpr size_t kDataSize = 4;
static_assert(kDataSize >= sizeof(arch::BreakInstructionType),
"Make data bigger for this platform.");
static const char kOriginalData[kDataSize];
BreakpointFakeMemory() : memory_(kAddress, kOriginalData, kDataSize) {}
~BreakpointFakeMemory() {}
FakeMemory* memory() { return &memory_; }
// Returns the memory pointer read out as the type required for the
// breakpoint instruction.
arch::BreakInstructionType AsInstructionType() const {
return *reinterpret_cast<const arch::BreakInstructionType*>(memory_.data());
}
// Returns true if the buffer starts with a breakpoint instruction for the
// current platform.
bool StartsWithBreak() const {
return AsInstructionType() == arch::kBreakInstruction;
}
// Returns true if the buffer is in its original state.
bool IsOriginal() const {
return memcmp(memory_.data(), kOriginalData, kDataSize) == 0;
}
private:
FakeMemory memory_;
};
// A no-op process delegate.
class TestProcessDelegate : public Breakpoint::ProcessDelegate {
public:
TestProcessDelegate() = default;
zx_status_t RegisterBreakpoint(Breakpoint*, zx_koid_t, uint64_t) override {
return ZX_OK;
}
void UnregisterBreakpoint(Breakpoint*, zx_koid_t, uint64_t) override {}
};
constexpr uintptr_t BreakpointFakeMemory::kAddress;
constexpr size_t BreakpointFakeMemory::kDataSize;
const char
BreakpointFakeMemory::kOriginalData[BreakpointFakeMemory::kDataSize] = {
0x01, 0x02, 0x03, 0x04};
} // namespace
TEST(ProcessBreakpoint, InstallAndFixup) {
BreakpointFakeMemory mem;
TestProcessDelegate process_delegate;
Breakpoint main_breakpoint(&process_delegate);
ProcessBreakpoint bp(&main_breakpoint, mem.memory(), 1,
BreakpointFakeMemory::kAddress);
ASSERT_EQ(ZX_OK, bp.Init());
// Should have written the breakpoint instruction to the buffer.
EXPECT_TRUE(mem.StartsWithBreak());
// Make a memory block that contains the address set as the breakpoint.
// Offset it by kBlockOffset to make sure non-aligned cases are handled.
debug_ipc::MemoryBlock block;
constexpr size_t kBlockOffset = 4;
block.address = BreakpointFakeMemory::kAddress - kBlockOffset;
block.valid = true;
block.size = 16;
block.data.resize(block.size);
// Fill with current memory contents (including breakpoint instruction).
memcpy(&block.data[kBlockOffset], mem.memory()->data(),
BreakpointFakeMemory::kDataSize);
// FixupMemoryBlock should give back the original data.
bp.FixupMemoryBlock(&block);
EXPECT_EQ(
0, memcmp(&block.data[kBlockOffset], BreakpointFakeMemory::kOriginalData,
BreakpointFakeMemory::kDataSize));
}
// Attempts to step over the breakpoint from multiple threads at the same
// time.
TEST(ProcessBreakpoint, StepMultiple) {
BreakpointFakeMemory mem;
TestProcessDelegate process_delegate;
Breakpoint main_breakpoint(&process_delegate);
ProcessBreakpoint bp(&main_breakpoint, mem.memory(), 1,
BreakpointFakeMemory::kAddress);
ASSERT_EQ(ZX_OK, bp.Init());
// The breakpoint should be installed.
EXPECT_TRUE(mem.StartsWithBreak());
// Begin stepping over the breakpoint from two threads at the same time.
// The memory should be back to original.
zx_koid_t kThread1Koid = 1;
bp.BeginStepOver(kThread1Koid);
EXPECT_TRUE(mem.IsOriginal());
zx_koid_t kThread2Koid = 2;
bp.BeginStepOver(kThread2Koid);
EXPECT_TRUE(mem.IsOriginal());
// In real life, the thread would now single-step over the breakpoint. It
// would trigger a hardware breakpoint at the next instruction.
EXPECT_TRUE(
bp.BreakpointStepHasException(kThread1Koid, ZX_EXCP_HW_BREAKPOINT));
// Since one thread is still stepping, the memory should still be original.
EXPECT_TRUE(mem.IsOriginal());
// As soon as the second breakpoint is resolved, the breakpoint instruction
// should be put back.
EXPECT_TRUE(
bp.BreakpointStepHasException(kThread2Koid, ZX_EXCP_HW_BREAKPOINT));
EXPECT_TRUE(mem.StartsWithBreak());
}
} // namespace debug_agent