blob: b317d7aa1ebac18024906ae49bd5adba6e97d265 [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 "src/developer/debug/debug_agent/breakpoint.h"
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include "src/developer/debug/debug_agent/test_utils.h"
namespace debug_agent {
namespace {
using CallPair = std::pair<zx_koid_t, uint64_t>;
using CallVector = std::vector<CallPair>;
using WPPair = std::pair<zx_koid_t, debug::AddressRange>;
using WPVector = std::vector<WPPair>;
class TestProcessDelegate : public Breakpoint::ProcessDelegate {
public:
const CallVector& register_calls() const { return register_calls_; }
const CallVector& unregister_calls() const { return unregister_calls_; }
const WPVector& wp_register_calls() const { return wp_register_calls_; }
const WPVector& wp_unregister_calls() const { return wp_unregister_calls_; }
void Clear() {
register_calls_.clear();
unregister_calls_.clear();
}
debug::Status RegisterBreakpoint(Breakpoint*, zx_koid_t process_koid, uint64_t address) override {
register_calls_.push_back(std::make_pair(process_koid, address));
return debug::Status();
}
void UnregisterBreakpoint(Breakpoint*, zx_koid_t process_koid, uint64_t address) override {
unregister_calls_.push_back(std::make_pair(process_koid, address));
}
debug::Status RegisterWatchpoint(Breakpoint*, zx_koid_t process_koid,
const debug::AddressRange& range) override {
wp_register_calls_.push_back(std::make_pair(process_koid, range));
return debug::Status();
}
void UnregisterWatchpoint(Breakpoint*, zx_koid_t process_koid,
const debug::AddressRange& range) override {
wp_unregister_calls_.push_back(std::make_pair(process_koid, range));
}
private:
CallVector register_calls_;
CallVector unregister_calls_;
WPVector wp_register_calls_;
WPVector wp_unregister_calls_;
};
debug_ipc::ProcessBreakpointSettings CreateLocation(zx_koid_t process_koid, zx_koid_t thread_koid,
const debug::AddressRange& address_range) {
debug_ipc::ProcessBreakpointSettings settings = {};
settings.id = {.process = process_koid, .thread = thread_koid};
settings.address_range = address_range;
return settings;
}
// Tests -------------------------------------------------------------------------------------------
TEST(Breakpoint, Registration) {
TestProcessDelegate delegate;
Breakpoint bp(&delegate);
debug_ipc::BreakpointSettings settings;
settings.id = 1;
settings.type = debug_ipc::BreakpointType::kSoftware;
settings.locations.resize(1);
constexpr zx_koid_t kProcess1 = 1;
constexpr uint64_t kAddress1 = 0x1234;
debug_ipc::ProcessBreakpointSettings& pr_settings = settings.locations.back();
pr_settings.id = {.process = kProcess1, .thread = 0};
pr_settings.address = kAddress1;
// Apply the settings.
ASSERT_TRUE(bp.SetSettings(settings).ok());
EXPECT_EQ(CallVector({CallPair{kProcess1, kAddress1}}), delegate.register_calls());
EXPECT_TRUE(delegate.unregister_calls().empty());
delegate.Clear();
// Change the settings to move the breakpoint.
constexpr zx_koid_t kProcess2 = 2;
constexpr uint64_t kAddress2 = 0x5678;
pr_settings.id = {.process = kProcess2, .thread = 0};
pr_settings.address = kAddress2;
ASSERT_TRUE(bp.SetSettings(settings).ok());
EXPECT_EQ(CallVector({CallPair{kProcess2, kAddress2}}), delegate.register_calls());
EXPECT_EQ(CallVector({CallPair{kProcess1, kAddress1}}), delegate.unregister_calls());
// Add a new location
delegate.Clear();
// Add the old breakpoint and a new one
debug_ipc::ProcessBreakpointSettings old_pr_settings;
old_pr_settings.id = {.process = kProcess1, .thread = 0};
old_pr_settings.address = kAddress1;
constexpr zx_koid_t kProcess3 = 3;
constexpr uint64_t kAddress3 = 0x9ABC;
debug_ipc::ProcessBreakpointSettings new_pr_settings;
new_pr_settings.id = {.process = kProcess3, .thread = 0};
new_pr_settings.address = kAddress3;
settings.locations.clear();
settings.locations.push_back(old_pr_settings);
settings.locations.push_back(new_pr_settings);
ASSERT_TRUE(bp.SetSettings(settings).ok());
EXPECT_EQ(CallVector({{kProcess1, kAddress1}, {kProcess3, kAddress3}}),
delegate.register_calls());
EXPECT_EQ(CallVector({{kProcess2, kAddress2}}), delegate.unregister_calls());
}
// The destructor should clear breakpoint locations.
TEST(Breakpoint, Destructor) {
TestProcessDelegate delegate;
std::unique_ptr<Breakpoint> bp = std::make_unique<Breakpoint>(&delegate);
debug_ipc::BreakpointSettings settings;
settings.id = 1;
settings.type = debug_ipc::BreakpointType::kSoftware;
settings.locations.resize(1);
constexpr zx_koid_t kProcess1 = 1;
constexpr uint64_t kAddress1 = 0x1234;
debug_ipc::ProcessBreakpointSettings& pr_settings = settings.locations.back();
pr_settings.id = {.process = kProcess1, .thread = 0};
pr_settings.address = kAddress1;
// Apply the settings.
ASSERT_TRUE(bp->SetSettings(settings).ok());
EXPECT_EQ(CallVector({CallPair{kProcess1, kAddress1}}), delegate.register_calls());
EXPECT_TRUE(delegate.unregister_calls().empty());
delegate.Clear();
// Delete the breakpoint to make sure the locations get updated.
delegate.Clear();
bp.reset();
EXPECT_EQ(CallVector({CallPair{kProcess1, kAddress1}}), delegate.unregister_calls());
}
TEST(Breakpoint, HitCount) {
TestProcessDelegate delegate;
std::unique_ptr<Breakpoint> bp = std::make_unique<Breakpoint>(&delegate);
constexpr uint32_t kBreakpointId = 12;
debug_ipc::BreakpointSettings settings;
settings.id = kBreakpointId;
settings.type = debug_ipc::BreakpointType::kSoftware;
settings.locations.resize(1);
constexpr zx_koid_t kProcess1 = 1;
constexpr uint64_t kAddress1 = 0x1234;
debug_ipc::ProcessBreakpointSettings& pr_settings = settings.locations.back();
pr_settings.id = {.process = kProcess1, .thread = 0};
pr_settings.address = kAddress1;
// Apply the settings.
ASSERT_TRUE(bp->SetSettings(settings).ok());
delegate.Clear();
EXPECT_EQ(kBreakpointId, bp->stats().id);
EXPECT_EQ(0u, bp->stats().hit_count);
EXPECT_EQ(Breakpoint::HitResult::kHit, bp->OnHit());
EXPECT_EQ(kBreakpointId, bp->stats().id);
EXPECT_EQ(1u, bp->stats().hit_count);
EXPECT_FALSE(bp->stats().should_delete);
EXPECT_EQ(Breakpoint::HitResult::kHit, bp->OnHit());
EXPECT_EQ(kBreakpointId, bp->stats().id);
EXPECT_EQ(2u, bp->stats().hit_count);
EXPECT_FALSE(bp->stats().should_delete);
}
TEST(Breakpoint, OneShot) {
TestProcessDelegate delegate;
std::unique_ptr<Breakpoint> bp = std::make_unique<Breakpoint>(&delegate);
constexpr uint32_t kBreakpointId = 12;
debug_ipc::BreakpointSettings settings;
settings.id = kBreakpointId;
settings.type = debug_ipc::BreakpointType::kSoftware;
settings.one_shot = true;
settings.locations.resize(1);
constexpr zx_koid_t kProcess = 1;
constexpr uint64_t kAddress = 0x1234;
debug_ipc::ProcessBreakpointSettings& pr_settings = settings.locations.back();
pr_settings.id = {.process = kProcess, .thread = 0};
pr_settings.address = kAddress;
// Apply the settings.
ASSERT_TRUE(bp->SetSettings(settings).ok());
delegate.Clear();
EXPECT_EQ(kBreakpointId, bp->stats().id);
EXPECT_EQ(0u, bp->stats().hit_count);
EXPECT_FALSE(bp->stats().should_delete);
// The hit count and "should delete" flag should be set.
EXPECT_EQ(Breakpoint::HitResult::kOneShotHit, bp->OnHit());
EXPECT_EQ(kBreakpointId, bp->stats().id);
EXPECT_EQ(1u, bp->stats().hit_count);
EXPECT_TRUE(bp->stats().should_delete);
}
TEST(Breakpoint, WatchpointLocations) {
TestProcessDelegate process_delegate;
Breakpoint breakpoint(&process_delegate);
constexpr zx_koid_t kProcess1Koid = 0x1;
constexpr zx_koid_t kProcess2Koid = 0x2;
constexpr debug::AddressRange kProcess1Range = {0x100, 0x200};
constexpr debug::AddressRange kProcess2Range = {0x400, 0x800};
debug_ipc::BreakpointSettings settings;
settings.id = 1;
settings.locations.push_back(CreateLocation(kProcess1Koid, 0, kProcess1Range));
settings.locations.push_back(CreateLocation(kProcess2Koid, 0, kProcess2Range));
settings.type = debug_ipc::BreakpointType::kReadWrite;
ASSERT_TRUE(breakpoint.SetSettings(settings).ok());
settings.type = debug_ipc::BreakpointType::kWrite;
ASSERT_TRUE(breakpoint.SetSettings(settings).ok());
EXPECT_EQ(
process_delegate.wp_register_calls(),
WPVector({WPPair{kProcess1Koid, kProcess1Range}, WPPair{kProcess2Koid, kProcess2Range}}));
EXPECT_EQ(process_delegate.wp_unregister_calls(), WPVector{});
}
using BPType = debug_ipc::BreakpointType;
TEST(Breakpoint, DoesExceptionApply) {
EXPECT_TRUE(Breakpoint::DoesExceptionApply(BPType::kSoftware, BPType::kSoftware));
EXPECT_FALSE(Breakpoint::DoesExceptionApply(BPType::kSoftware, BPType::kHardware));
EXPECT_FALSE(Breakpoint::DoesExceptionApply(BPType::kSoftware, BPType::kReadWrite));
EXPECT_FALSE(Breakpoint::DoesExceptionApply(BPType::kSoftware, BPType::kWrite));
EXPECT_FALSE(Breakpoint::DoesExceptionApply(BPType::kHardware, BPType::kSoftware));
EXPECT_TRUE(Breakpoint::DoesExceptionApply(BPType::kHardware, BPType::kHardware));
EXPECT_FALSE(Breakpoint::DoesExceptionApply(BPType::kHardware, BPType::kReadWrite));
EXPECT_FALSE(Breakpoint::DoesExceptionApply(BPType::kHardware, BPType::kWrite));
EXPECT_FALSE(Breakpoint::DoesExceptionApply(BPType::kReadWrite, BPType::kSoftware));
EXPECT_FALSE(Breakpoint::DoesExceptionApply(BPType::kReadWrite, BPType::kHardware));
EXPECT_TRUE(Breakpoint::DoesExceptionApply(BPType::kReadWrite, BPType::kReadWrite));
EXPECT_TRUE(Breakpoint::DoesExceptionApply(BPType::kReadWrite, BPType::kWrite));
EXPECT_FALSE(Breakpoint::DoesExceptionApply(BPType::kWrite, BPType::kSoftware));
EXPECT_FALSE(Breakpoint::DoesExceptionApply(BPType::kWrite, BPType::kHardware));
EXPECT_TRUE(Breakpoint::DoesExceptionApply(BPType::kWrite, BPType::kReadWrite));
EXPECT_TRUE(Breakpoint::DoesExceptionApply(BPType::kWrite, BPType::kWrite));
}
} // namespace
} // namespace debug_agent