blob: c1354d4df1ce1ef9ae2d703973da71dd5631bda6 [file] [log] [blame]
// Copyright 2019 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 "src/developer/debug/debug_agent/software_breakpoint.h"
#include <string.h>
#include <zircon/syscalls/exception.h>
#include <gtest/gtest.h>
#include "src/developer/debug/debug_agent/arch.h"
#include "src/developer/debug/debug_agent/breakpoint.h"
#include "src/developer/debug/debug_agent/debug_agent.h"
#include "src/developer/debug/debug_agent/debugged_thread.h"
#include "src/developer/debug/debug_agent/mock_job_handle.h"
#include "src/developer/debug/debug_agent/mock_process.h"
#include "src/developer/debug/debug_agent/mock_system_interface.h"
#include "src/developer/debug/debug_agent/mock_thread.h"
#include "src/developer/debug/shared/logging/debug.h"
namespace debug_agent {
namespace {
// A no-op process delegate.
class TestProcessDelegate : public Breakpoint::ProcessDelegate {
public:
TestProcessDelegate() = default;
std::map<uint64_t, std::unique_ptr<ProcessBreakpoint>>& bps() { return bps_; }
void InjectMockProcess(std::unique_ptr<MockProcess> proc) {
procs_[proc->koid()] = std::move(proc);
}
// This only gets called if Breakpoint.SetSettings() is called.
debug::Status RegisterBreakpoint(Breakpoint* bp, zx_koid_t koid, uint64_t address) override {
auto found = bps_.find(address);
if (found == bps_.end()) {
auto pbp = std::make_unique<SoftwareBreakpoint>(bp, procs_[koid].get(), address);
auto status = pbp->Init();
if (status.has_error()) {
fprintf(stderr, "Failure initializing %s\n", status.message().c_str());
return status;
}
bps_[address] = std::move(pbp);
} else {
found->second->RegisterBreakpoint(bp);
}
return debug::Status();
}
void UnregisterBreakpoint(Breakpoint* bp, zx_koid_t, uint64_t address) override {
auto found = bps_.find(address);
if (found == bps_.end())
GTEST_FAIL();
bool still_used = found->second->UnregisterBreakpoint(bp);
if (!still_used)
bps_.erase(found);
}
private:
std::map<uint64_t, std::unique_ptr<ProcessBreakpoint>> bps_;
std::map<zx_koid_t, std::unique_ptr<MockProcess>> procs_;
};
constexpr uintptr_t kAddress = 0x1234;
constexpr size_t kDataSize = 4u;
const uint8_t kOriginalData[kDataSize] = {0x01, 0x02, 0x03, 0x04};
std::vector<uint8_t> GetOriginalData() {
return std::vector<uint8_t>(std::begin(kOriginalData), std::end(kOriginalData));
}
// Returns the memory data buffer with the beginning overwritten by a software breakpoint.
std::vector<uint8_t> GetOriginalDataWithBreakpoint() {
auto result = GetOriginalData();
memcpy(result.data(), &arch::kBreakInstruction, arch::kBreakInstructionSize);
return result;
}
// Writes the original data or breakpoint data at |kAddress| to the mock data backing the given
// process handle.
void LoadOriginalMemory(MockProcessHandle& handle) {
handle.mock_memory().AddMemory(kAddress, GetOriginalData());
}
bool MemoryContains(MockProcessHandle& handle, uint64_t address, const void* data,
size_t data_len) {
if (data_len == 0)
return true;
std::unique_ptr<uint8_t[]> read(new uint8_t[data_len]);
size_t actual = 0;
if (handle.ReadMemory(address, read.get(), data_len, &actual).has_error() || actual != data_len)
return false;
return memcmp(data, read.get(), data_len) == 0;
}
bool MemoryContainsBreak(MockProcessHandle& handle, uint64_t address) {
return MemoryContains(handle, address, &arch::kBreakInstruction, arch::kBreakInstructionSize);
}
bool MemoryContainsOriginal(MockProcessHandle& handle, uint64_t address) {
return MemoryContains(handle, address, kOriginalData, arch::kBreakInstructionSize);
}
template <typename T>
std::string ToString(const std::vector<T>& v) {
std::stringstream ss;
for (size_t i = 0; i < v.size(); i++) {
if (i > 0)
ss << ", ";
ss << v[i];
}
return ss.str();
}
template <typename T>
void CheckVectorContainsElements(const debug::FileLineFunction& location, const std::vector<T>& got,
const std::vector<T>& expected) {
ASSERT_EQ(expected.size(), got.size())
<< location.ToString() << ": "
<< "Expected (" << ToString(expected) << "), Got (" << ToString(got) << ").";
for (size_t i = 0; i < expected.size(); i++) {
ASSERT_TRUE(expected[i] == got[i])
<< location.ToString() << ": "
<< "Expected (" << ToString(expected) << "), Got (" << ToString(got) << ").";
}
}
} // namespace
TEST(ProcessBreakpoint, InstallAndFixup) {
TestProcessDelegate process_delegate;
Breakpoint main_breakpoint(&process_delegate);
debug_ipc::BreakpointSettings settings;
settings.type = debug_ipc::BreakpointType::kSoftware;
main_breakpoint.SetSettings(settings);
zx_koid_t process_koid = 0x1234;
const std::string process_name = "process";
MockProcess process(nullptr, process_koid, process_name);
LoadOriginalMemory(process.mock_process_handle());
SoftwareBreakpoint bp(&main_breakpoint, &process, kAddress);
ASSERT_TRUE(bp.Init().ok());
// Should have written the breakpoint instruction.
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress));
// 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 = kAddress - kBlockOffset;
block.valid = true;
block.size = 16;
block.data.resize(block.size);
// Fill with memory contents reflecting the breakpoint instruction.
auto with_bp = GetOriginalDataWithBreakpoint();
memcpy(&block.data[kBlockOffset], with_bp.data(), with_bp.size());
// FixupMemoryBlock should give back the original data.
bp.FixupMemoryBlock(&block);
EXPECT_TRUE(
std::equal(&block.data[kBlockOffset], &block.data[kBlockOffset + kDataSize], kOriginalData));
}
TEST(ProcessBreakpoint, StepSingle) {
TestProcessDelegate process_delegate;
Breakpoint main_breakpoint(&process_delegate);
debug_ipc::BreakpointSettings settings;
settings.type = debug_ipc::BreakpointType::kSoftware;
main_breakpoint.SetSettings(settings);
constexpr zx_koid_t process_koid = 0x1234;
const std::string process_name = "process";
MockProcess process(nullptr, process_koid, process_name);
process.mock_process_handle().mock_memory().AddMemory(kAddress, GetOriginalData());
// The step over strategy is as follows:
// - Thread 1, 2, 3 will hit the breakpoint and attempt a step over.
// - Thread 4 will remain oblivious to the breakpoint, as will 5.
// - Thread 5 is suspended from the client, so it should not be resumed by the agent during
// step over.
constexpr zx_koid_t kThread1Koid = 1;
constexpr zx_koid_t kThread2Koid = 2;
constexpr zx_koid_t kThread3Koid = 3;
constexpr zx_koid_t kThread4Koid = 4;
constexpr zx_koid_t kThread5Koid = 5;
MockThread* mock_thread1 = process.AddThread(kThread1Koid);
MockThread* mock_thread2 = process.AddThread(kThread2Koid);
MockThread* mock_thread3 = process.AddThread(kThread3Koid);
MockThread* mock_thread4 = process.AddThread(kThread4Koid);
MockThread* mock_thread5 = process.AddThread(kThread5Koid);
mock_thread5->ClientSuspend();
SoftwareBreakpoint bp(&main_breakpoint, &process, kAddress);
ASSERT_TRUE(bp.Init().ok());
// The breakpoint should be installed.
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress));
// Thread 1 hits breakpoint ----------------------------------------------------------------------
bp.BeginStepOver(mock_thread1);
// Breakpoint should be removed.
EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress));
// There should be one enqueued step over.
ASSERT_EQ(process.step_over_queue().size(), 1u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &bp);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread1);
// Only thread 1 should be still running. The rest should be suspended.
EXPECT_TRUE(mock_thread1->running());
EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 1 should be stepping over.
EXPECT_TRUE(mock_thread1->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, bp.CurrentlySuspendedThreads(),
{kThread2Koid, kThread3Koid, kThread4Koid, kThread5Koid});
// Thread 2 hits breakpoint ----------------------------------------------------------------------
// Now the second thread gets the exception.
bp.BeginStepOver(mock_thread2);
// There should be 2 enqueued step overs.
ASSERT_EQ(process.step_over_queue().size(), 2u);
ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &bp);
ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread2);
// Only thread 1 should be running.
EXPECT_TRUE(mock_thread1->running());
EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 1 should be stepping over.
EXPECT_TRUE(mock_thread1->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, bp.CurrentlySuspendedThreads(),
{kThread2Koid, kThread3Koid, kThread4Koid, kThread5Koid});
// Thread 3 hits breakpoint ----------------------------------------------------------------------
bp.BeginStepOver(mock_thread3);
// There should be 2 enqueued step overs.
ASSERT_EQ(process.step_over_queue().size(), 3u);
ASSERT_EQ(process.step_over_queue()[2].process_breakpoint.get(), &bp);
ASSERT_EQ(process.step_over_queue()[2].thread.get(), mock_thread3);
// Only thread 1 should be running.
EXPECT_TRUE(mock_thread1->running());
EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 1 should be stepping over right now.
EXPECT_TRUE(mock_thread1->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, bp.CurrentlySuspendedThreads(),
{kThread2Koid, kThread3Koid, kThread4Koid, kThread5Koid});
// The memory should be original.
EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress));
// Thread 1 steps over ---------------------------------------------------------------------------
bp.EndStepOver(mock_thread1);
// There should be 2 enqueued step overs.
ASSERT_EQ(process.step_over_queue().size(), 2u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &bp);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread2);
ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &bp);
ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread3);
// Only thread 2 should be running.
EXPECT_TRUE(mock_thread1->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread2->running());
EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 2 should be stepping over right now.
EXPECT_FALSE(mock_thread1->stepping_over_breakpoint());
EXPECT_TRUE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, bp.CurrentlySuspendedThreads(),
{kThread1Koid, kThread3Koid, kThread4Koid, kThread5Koid});
// The memory should be original.
EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress));
// Thread 2 steps over ---------------------------------------------------------------------------
bp.EndStepOver(mock_thread2);
// There should be 1 enqueued step overs.
ASSERT_EQ(process.step_over_queue().size(), 1u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &bp);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread3);
// Only thread 3 should be running, as it's the only one stepping over.
EXPECT_TRUE(mock_thread1->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread3->running());
EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 3 should be stepping over right now.
EXPECT_FALSE(mock_thread1->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread2->stepping_over_breakpoint());
EXPECT_TRUE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, bp.CurrentlySuspendedThreads(),
{kThread1Koid, kThread2Koid, kThread4Koid, kThread5Koid});
// Breakpoint remains removed.
EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress));
// Thread 3 steps over ---------------------------------------------------------------------------
bp.EndStepOver(mock_thread3);
// No more enqueued elements.
ASSERT_EQ(process.step_over_queue().size(), 0u);
// All threads should be resumed except 5, which was paused by the client.
EXPECT_TRUE(mock_thread1->running());
EXPECT_TRUE(mock_thread2->running());
EXPECT_TRUE(mock_thread3->running());
EXPECT_TRUE(mock_thread4->running());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// No thread should be stepping over.
EXPECT_FALSE(mock_thread1->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
EXPECT_TRUE(bp.CurrentlySuspendedThreads().empty());
// Breakpoint is set again.
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress));
}
TEST(ProcessBreakpoint, MultipleBreakpoints) {
TestProcessDelegate process_delegate1;
TestProcessDelegate process_delegate2;
TestProcessDelegate process_delegate3;
Breakpoint main_breakpoint1(&process_delegate1);
Breakpoint main_breakpoint2(&process_delegate2);
Breakpoint main_breakpoint3(&process_delegate3);
debug_ipc::BreakpointSettings settings;
settings.type = debug_ipc::BreakpointType::kSoftware;
main_breakpoint1.SetSettings(settings);
main_breakpoint2.SetSettings(settings);
main_breakpoint3.SetSettings(settings);
constexpr zx_koid_t process_koid = 0x1234;
const std::string process_name = "process";
MockProcess process(nullptr, process_koid, process_name);
constexpr uint64_t kAddress1 = kAddress;
constexpr uint64_t kAddress2 = kAddress + 0x100;
constexpr uint64_t kAddress3 = kAddress + 0x200;
process.mock_process_handle().mock_memory().AddMemory(kAddress1, GetOriginalData());
process.mock_process_handle().mock_memory().AddMemory(kAddress2, GetOriginalData());
process.mock_process_handle().mock_memory().AddMemory(kAddress3, GetOriginalData());
// The step over strategy is as follows:
// 1. Thread 1 hits breakpoint 1.
// 2. Thread 2 hits breakpoint 2.
// 3. Thread 3 hits breakpoint 3.
// 4. Thread 1 finishes step over.
// 5. Thread 4 hits breakpoint 2 (somehow).
// 6. Thread 2 finishes step over.
// 7. Thread 3 finishes step over.
// 8. Thread 4 finishes step over.
constexpr zx_koid_t kThread1Koid = 1;
constexpr zx_koid_t kThread2Koid = 2;
constexpr zx_koid_t kThread3Koid = 3;
constexpr zx_koid_t kThread4Koid = 4;
constexpr zx_koid_t kThread5Koid = 5;
MockThread* mock_thread1 = process.AddThread(kThread1Koid);
MockThread* mock_thread2 = process.AddThread(kThread2Koid);
MockThread* mock_thread3 = process.AddThread(kThread3Koid);
MockThread* mock_thread4 = process.AddThread(kThread4Koid);
MockThread* mock_thread5 = process.AddThread(kThread5Koid);
SoftwareBreakpoint breakpoint1(&main_breakpoint1, &process, kAddress1);
SoftwareBreakpoint breakpoint2(&main_breakpoint2, &process, kAddress2);
SoftwareBreakpoint breakpoint3(&main_breakpoint3, &process, kAddress3);
ASSERT_TRUE(breakpoint1.Init().ok());
ASSERT_TRUE(breakpoint2.Init().ok());
ASSERT_TRUE(breakpoint3.Init().ok());
// The breakpoint should be installed at all locations.
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress1));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3));
// Thread 1 hits breakpoint 1 --------------------------------------------------------------------
breakpoint1.BeginStepOver(mock_thread1);
// There should be one enqueued step over.
ASSERT_EQ(process.step_over_queue().size(), 1u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint1);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread1);
// Only thread 1 should be still running. The rest should be suspended.
EXPECT_TRUE(mock_thread1->running());
EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 1 should be stepping over.
EXPECT_TRUE(mock_thread1->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(),
{kThread2Koid, kThread3Koid, kThread4Koid, kThread5Koid});
CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(), {});
CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {});
// Breakpoint should be removed. Others breakpoints should remain set.
EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress1));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3));
// Thread 2 hits breakpoint 2 --------------------------------------------------------------------
// Now the second thread gets the exception.
breakpoint2.BeginStepOver(mock_thread2);
// There should be 2 enqueued step overs.
ASSERT_EQ(process.step_over_queue().size(), 2u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint1);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread1);
ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &breakpoint2);
ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread2);
// Only thread 1 should be running.
EXPECT_TRUE(mock_thread1->running());
EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 1 should be stepping over.
EXPECT_TRUE(mock_thread1->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(),
{kThread2Koid, kThread3Koid, kThread4Koid, kThread5Koid});
CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(), {});
CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {});
// Breakpoint should be removed. Others breakpoints should remain set.
EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress1));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3));
// Thread 3 hits breakpoint ----------------------------------------------------------------------
breakpoint3.BeginStepOver(mock_thread3);
// There should be 2 enqueued step overs.
ASSERT_EQ(process.step_over_queue().size(), 3u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint1);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread1);
ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &breakpoint2);
ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread2);
ASSERT_EQ(process.step_over_queue()[2].process_breakpoint.get(), &breakpoint3);
ASSERT_EQ(process.step_over_queue()[2].thread.get(), mock_thread3);
// Only thread 1 should be running.
EXPECT_TRUE(mock_thread1->running());
EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 1 should be stepping over right now.
EXPECT_TRUE(mock_thread1->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(),
{kThread2Koid, kThread3Koid, kThread4Koid, kThread5Koid});
CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(), {});
CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {});
// Breakpoint should be removed. Others breakpoints should remain set.
EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress1));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3));
// Thread 1 steps over ---------------------------------------------------------------------------
breakpoint1.EndStepOver(mock_thread1);
// There should be 2 enqueued step overs.
ASSERT_EQ(process.step_over_queue().size(), 2u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint2);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread2);
ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &breakpoint3);
ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread3);
// Only thread 2 should be running.
EXPECT_TRUE(mock_thread1->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread2->running());
EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 2 should be stepping over right now.
EXPECT_FALSE(mock_thread1->stepping_over_breakpoint());
EXPECT_TRUE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(), {});
CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(),
{kThread1Koid, kThread3Koid, kThread4Koid, kThread5Koid});
CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {});
// Breakpoint 2 should be the only one removed.
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress1));
EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress2));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3));
// Thread 4 hits breakpoint 2 --------------------------------------------------------------------
breakpoint2.BeginStepOver(mock_thread4);
// There should be 3 enqueued step overs.
ASSERT_EQ(process.step_over_queue().size(), 3u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint2);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread2);
ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &breakpoint3);
ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread3);
ASSERT_EQ(process.step_over_queue()[2].process_breakpoint.get(), &breakpoint2);
ASSERT_EQ(process.step_over_queue()[2].thread.get(), mock_thread4);
// Only thread 2 should be running.
EXPECT_TRUE(mock_thread1->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread2->running());
EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 2 should be stepping over right now.
EXPECT_FALSE(mock_thread1->stepping_over_breakpoint());
EXPECT_TRUE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(), {});
CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(),
{kThread1Koid, kThread3Koid, kThread4Koid, kThread5Koid});
CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {});
// Breakpoint 2 should be the only one removed.
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress1));
EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress2));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3));
// Thread 2 steps over ---------------------------------------------------------------------------
breakpoint2.EndStepOver(mock_thread2);
// There should be 2 enqueued step overs.
ASSERT_EQ(process.step_over_queue().size(), 2u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint3);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread3);
ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &breakpoint2);
ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread4);
// Only thread 3 should be running, as it's the only one stepping over.
EXPECT_TRUE(mock_thread1->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread3->running());
EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 3 should be stepping over right now.
EXPECT_FALSE(mock_thread1->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread2->stepping_over_breakpoint());
EXPECT_TRUE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(), {});
CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(), {});
CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(),
{kThread1Koid, kThread2Koid, kThread4Koid, kThread5Koid});
// Breakpoint 3 should be the only one removed.
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress1));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2));
EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress3));
// Thread 3 steps over ---------------------------------------------------------------------------
breakpoint3.EndStepOver(mock_thread3);
// No more enqueued elements.
ASSERT_EQ(process.step_over_queue().size(), 1u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint2);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread4);
// Only thread 4 should be running, as it's the only one stepping over.
EXPECT_TRUE(mock_thread1->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended());
EXPECT_TRUE(mock_thread4->running());
EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended());
// Only thread 3 should be stepping over right now.
EXPECT_FALSE(mock_thread1->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_TRUE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(), {});
CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(),
{kThread1Koid, kThread2Koid, kThread3Koid, kThread5Koid});
CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {});
// Breakpoint 2 should be the only one removed.
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress1));
EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress2));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3));
// Thread 4 steps over ---------------------------------------------------------------------------
breakpoint2.EndStepOver(mock_thread4);
// All threads should be resumed.
EXPECT_TRUE(mock_thread1->running());
EXPECT_TRUE(mock_thread2->running());
EXPECT_TRUE(mock_thread3->running());
EXPECT_TRUE(mock_thread4->running());
EXPECT_TRUE(mock_thread5->running());
// No thread should be stepping over.
EXPECT_FALSE(mock_thread1->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread2->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread3->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread4->stepping_over_breakpoint());
EXPECT_FALSE(mock_thread5->stepping_over_breakpoint());
EXPECT_TRUE(breakpoint1.CurrentlySuspendedThreads().empty());
EXPECT_TRUE(breakpoint2.CurrentlySuspendedThreads().empty());
EXPECT_TRUE(breakpoint3.CurrentlySuspendedThreads().empty());
// All breakpoints are set again.
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress1));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2));
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3));
}
// It's possible for a thread to try to step over the same breakpoint twice. This normally indicates
// an error removing the breakpoint to step over it. We must not enqueue the same thread twice in
// this error case or it will be forever stuck waiting for itself.
TEST(ProcessBreakpoint, RecursiveStep) {
TestProcessDelegate process_delegate;
Breakpoint main_breakpoint(&process_delegate);
debug_ipc::BreakpointSettings settings;
settings.type = debug_ipc::BreakpointType::kSoftware;
main_breakpoint.SetSettings(settings);
constexpr zx_koid_t process_koid = 0x1234;
const std::string process_name = "process";
MockProcess process(nullptr, process_koid, process_name);
process.mock_process_handle().mock_memory().AddMemory(kAddress, GetOriginalData());
constexpr zx_koid_t kThreadKoid = 1;
MockThread* mock_thread = process.AddThread(kThreadKoid);
SoftwareBreakpoint bp(&main_breakpoint, &process, kAddress);
ASSERT_TRUE(bp.Init().ok());
// The breakpoint should be installed.
EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress));
// Hit the breakpoint the first time.
bp.BeginStepOver(mock_thread);
// There should be one enqueued step over.
ASSERT_EQ(process.step_over_queue().size(), 1u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &bp);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread);
// Recursively hits the same breakpoint.
bp.BeginStepOver(mock_thread);
// There should still just be one enqueued step over.
ASSERT_EQ(process.step_over_queue().size(), 1u);
ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &bp);
ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread);
}
// This also tests registration and unregistration of ProcessBreakpoints via the Breakpoint object.
TEST(ProcessBreakpoint, HitCount) {
// This test requires a debug agent because it causes a breakpoint hit which will try to suspend
// all processes attached.
DebugAgent debug_agent(std::make_unique<MockSystemInterface>(MockJobHandle(99999)));
TestProcessDelegate process_delegate;
constexpr zx_koid_t kProcess1 = 1;
auto owning_process = std::make_unique<MockProcess>(&debug_agent, kProcess1);
DebuggedProcess* process = owning_process.get();
owning_process->mock_process_handle().mock_memory().AddMemory(kAddress, GetOriginalData());
process_delegate.InjectMockProcess(std::move(owning_process));
auto owning_thread = std::make_unique<MockThread>(process, 28374);
MockThread* thread = owning_thread.get();
process->InjectThreadForTest(std::move(owning_thread));
constexpr uint32_t kBreakpointId1 = 12;
debug_ipc::BreakpointSettings settings;
settings.id = kBreakpointId1;
settings.type = debug_ipc::BreakpointType::kSoftware;
settings.locations.resize(1);
debug_ipc::ProcessBreakpointSettings& pr_settings = settings.locations.back();
pr_settings.id = {.process = kProcess1, .thread = 0};
pr_settings.address = kAddress;
// Create a ProcessBreakpoint referencing the two Breakpoint objects
// (corresponds to two logical breakpoints at the same address).
std::unique_ptr<Breakpoint> main_breakpoint1 = std::make_unique<Breakpoint>(&process_delegate);
auto status = main_breakpoint1->SetSettings(settings);
ASSERT_TRUE(status.ok());
std::unique_ptr<Breakpoint> main_breakpoint2 = std::make_unique<Breakpoint>(&process_delegate);
constexpr uint32_t kBreakpointId2 = 13;
settings.id = kBreakpointId2;
status = main_breakpoint2->SetSettings(settings);
ASSERT_TRUE(status.ok());
// There should only be one address with a breakpoint.
ASSERT_EQ(1u, process_delegate.bps().size());
EXPECT_EQ(kAddress, process_delegate.bps().begin()->first);
// Hitting the ProcessBreakpoint should update both Breakpoints.
std::vector<debug_ipc::BreakpointStats> stats;
std::vector<debug_ipc::ThreadRecord> affected_threads;
process_delegate.bps().begin()->second->OnHit(thread, debug_ipc::BreakpointType::kSoftware, stats,
affected_threads);
ASSERT_EQ(2u, stats.size());
// Order of the vector is not defined so allow either.
EXPECT_TRUE((stats[0].id == kBreakpointId1 && stats[1].id == kBreakpointId2) ||
(stats[0].id == kBreakpointId2 && stats[1].id == kBreakpointId1));
// The hit count of both should be 1 (order doesn't matter).
EXPECT_EQ(1u, stats[0].hit_count);
EXPECT_EQ(1u, stats[1].hit_count);
// Unregistering one Breakpoint should keep the ProcessBreakpoint.
main_breakpoint2.reset();
ASSERT_EQ(1u, process_delegate.bps().size());
// Unregistering the other should delete it.
main_breakpoint1.reset();
ASSERT_EQ(0u, process_delegate.bps().size());
}
} // namespace debug_agent