blob: 7bb9b56b7bd89d95b77592847f5f5f461337ebec [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 <gtest/gtest.h>
#include "src/developer/debug/debug_agent/arch.h"
#include "src/developer/debug/debug_agent/mock_debug_agent_harness.h"
#include "src/developer/debug/debug_agent/mock_exception_handle.h"
#include "src/developer/debug/debug_agent/mock_process.h"
#include "src/developer/debug/debug_agent/mock_thread.h"
#include "src/developer/debug/debug_agent/mock_thread_handle.h"
namespace debug_agent {
TEST(DebuggedThreadBreakpoint, NormalException) {
MockDebugAgentHarness harness;
constexpr zx_koid_t kProcKoid = 12;
MockProcess* process = harness.AddProcess(kProcKoid);
constexpr zx_koid_t kThreadKoid = 23;
MockThread* thread = process->AddThread(kThreadKoid);
// Trigger the exception
constexpr uint64_t kAddress = 0xdeadbeef;
thread->SendException(kAddress, debug_ipc::ExceptionType::kPageFault);
// We should've received an exception notification.
ASSERT_EQ(harness.stream_backend()->exceptions().size(), 1u);
EXPECT_EQ(harness.stream_backend()->exceptions()[0].type, debug_ipc::ExceptionType::kPageFault);
EXPECT_EQ(harness.stream_backend()->exceptions()[0].hit_breakpoints.size(), 0u);
auto& thread_record = harness.stream_backend()->exceptions()[0].thread;
EXPECT_EQ(thread_record.process_koid, kProcKoid);
EXPECT_EQ(thread_record.thread_koid, kThreadKoid);
EXPECT_EQ(thread_record.state, debug_ipc::ThreadRecord::State::kBlocked);
EXPECT_EQ(thread_record.blocked_reason, debug_ipc::ThreadRecord::BlockedReason::kException);
EXPECT_EQ(thread_record.stack_amount, debug_ipc::ThreadRecord::StackAmount::kMinimal);
}
TEST(DebuggedThreadBreakpoint, SoftwareBreakpoint) {
MockDebugAgentHarness harness;
constexpr zx_koid_t kProcKoid = 12;
MockProcess* process = harness.AddProcess(kProcKoid);
constexpr zx_koid_t kThread1Koid = 23;
constexpr zx_koid_t kThread2Koid = 24;
MockThread* thread1 = process->AddThread(kThread1Koid);
MockThread* thread2 = process->AddThread(kThread2Koid);
// Set an exception for a software breakpoint instruction. Since no breakpoint has been installed,
// this will look like a hardcoded breakpoint instruction.
constexpr uint64_t kBreakpointAddress = 0xdeadbeef;
const uint64_t kExceptionAddress =
kBreakpointAddress + arch::kExceptionOffsetForSoftwareBreakpoint;
thread1->SendException(kExceptionAddress, debug_ipc::ExceptionType::kSoftwareBreakpoint);
// Validate the exception notification.
ASSERT_EQ(harness.stream_backend()->exceptions().size(), 1u);
auto exception = harness.stream_backend()->exceptions()[0];
EXPECT_EQ(exception.type, debug_ipc::ExceptionType::kSoftwareBreakpoint);
EXPECT_EQ(exception.hit_breakpoints.size(), 0u);
EXPECT_EQ(exception.other_affected_threads.size(), 0u); // No other threads should be stopped.
// Resume the thread to clear the exception.
harness.Resume();
// Provide backing memory for the breakpoint. This is needed for the software breakpoint to be
// installed. It doesn't matter what the contents is, only that a read will succeed.
process->mock_process_handle().mock_memory().AddMemory(kBreakpointAddress, {0, 0, 0, 0});
// Add a breakpoint on that address and throw the same exception as above.
constexpr uint32_t kBreakpointId = 1;
harness.AddOrChangeBreakpoint(kBreakpointId, kProcKoid, kBreakpointAddress);
thread1->SendException(kExceptionAddress, debug_ipc::ExceptionType::kSoftwareBreakpoint);
// Now the exception notification should reference the hit breakpoint.
ASSERT_EQ(harness.stream_backend()->exceptions().size(), 2u);
exception = harness.stream_backend()->exceptions()[1];
EXPECT_EQ(exception.type, debug_ipc::ExceptionType::kSoftwareBreakpoint);
ASSERT_EQ(exception.hit_breakpoints.size(), 1u);
EXPECT_EQ(exception.hit_breakpoints[0].id, kBreakpointId);
// The other thread should be stopped because the default breakpoint stop mode is "all".
// Note that the test doesn't update the ThreadRecord so the
// other_affected_threads[0].state won't be correct. But we do check whether the thread thinks
// it has been client suspended which is a more detailed check.
EXPECT_TRUE(thread2->mock_thread_handle().is_suspended());
ASSERT_EQ(exception.other_affected_threads.size(), 1u);
EXPECT_EQ(exception.other_affected_threads[0].process_koid, kProcKoid);
EXPECT_EQ(exception.other_affected_threads[0].thread_koid, kThread2Koid);
// The breakpoint stats should be up-to-date.
Breakpoint* breakpoint = harness.debug_agent()->GetBreakpoint(kBreakpointId);
ASSERT_TRUE(breakpoint);
EXPECT_EQ(1u, breakpoint->stats().hit_count);
}
TEST(DebuggedThreadBreakpoint, HardwareBreakpoint) {
MockDebugAgentHarness harness;
constexpr zx_koid_t kProcKoid = 12;
MockProcess* process = harness.AddProcess(kProcKoid);
constexpr zx_koid_t kThreadKoid = 23;
MockThread* thread = process->AddThread(kThreadKoid);
// Set the exception information the arch provider is going to return.
constexpr uint64_t kAddress = 0xdeadbeef;
// Add a breakpoint on that address.
constexpr uint32_t kBreakpointId = 1;
harness.AddOrChangeBreakpoint(kBreakpointId, kProcKoid, kAddress,
debug_ipc::BreakpointType::kHardware);
// Trigger an exception.
thread->SendException(kAddress, debug_ipc::ExceptionType::kHardwareBreakpoint);
// Validate the exception notification.
ASSERT_EQ(harness.stream_backend()->exceptions().size(), 1u);
auto exception = harness.stream_backend()->exceptions()[0];
EXPECT_EQ(exception.type, debug_ipc::ExceptionType::kHardwareBreakpoint);
EXPECT_EQ(exception.hit_breakpoints.size(), 1u);
EXPECT_EQ(exception.hit_breakpoints[0].id, kBreakpointId);
// The breakpoint stats should be up-to-date.
Breakpoint* breakpoint = harness.debug_agent()->GetBreakpoint(kBreakpointId);
ASSERT_TRUE(breakpoint);
EXPECT_EQ(1u, breakpoint->stats().hit_count);
}
TEST(DebuggedThreadBreakpoint, Watchpoint) {
MockDebugAgentHarness harness;
constexpr zx_koid_t kProcKoid = 12;
MockProcess* process = harness.AddProcess(kProcKoid);
constexpr zx_koid_t kThreadKoid = 23;
MockThread* thread = process->AddThread(kThreadKoid);
// Add a watchpoint.
const debug_ipc::AddressRange kRange = {0x1000, 0x1008};
constexpr uint32_t kBreakpointId = 99;
ASSERT_EQ(ZX_OK, harness.AddOrChangeBreakpoint(kBreakpointId, kProcKoid, kThreadKoid, kRange,
debug_ipc::BreakpointType::kWrite));
// Set the exception information in the debug registers to return. This should indicate the
// watchpoint that was set up and triggered.
const uint64_t kAddress = kRange.begin();
DebugRegisters debug_regs;
auto set_result = debug_regs.SetWatchpoint(debug_ipc::BreakpointType::kWrite, kRange, 4);
ASSERT_TRUE(set_result);
debug_regs.SetForHitWatchpoint(set_result->slot);
thread->mock_thread_handle().SetDebugRegisters(debug_regs);
// Trigger an exception.
thread->SendException(kAddress, debug_ipc::ExceptionType::kWatchpoint);
// Validate the expection information.
auto exception = harness.stream_backend()->exceptions()[0];
EXPECT_EQ(exception.type, debug_ipc::ExceptionType::kWatchpoint);
ASSERT_EQ(exception.hit_breakpoints.size(), 1u);
EXPECT_EQ(exception.hit_breakpoints[0].id, kBreakpointId);
// The breakpoint stats should be up-to-date.
Breakpoint* breakpoint = harness.debug_agent()->GetBreakpoint(kBreakpointId);
ASSERT_TRUE(breakpoint);
EXPECT_EQ(1u, breakpoint->stats().hit_count);
}
TEST(DebuggedThreadBreakpoint, BreakpointStepSuspendResume) {
MockDebugAgentHarness harness;
constexpr zx_koid_t kProcKoid = 1234;
MockProcess* process = harness.AddProcess(kProcKoid);
constexpr zx_koid_t kThreadKoid = 1235;
MockThread* thread = process->AddThread(kThreadKoid);
// Provide backing memory for the breakpoint. This is needed for the software breakpoint to be
// installed. It doesn't matter what the contents is, only that a read will succeed.
uint64_t kBreakpointAddress = 0x5000;
process->mock_process_handle().mock_memory().AddMemory(kBreakpointAddress, {0, 0, 0, 0});
// Create the breakpoint we'll hit.
EXPECT_EQ(ZX_OK, harness.AddOrChangeBreakpoint(1, kProcKoid, kBreakpointAddress));
// Set up a hit of the breakpoint.
const uint64_t kBreakpointExceptionAddr =
kBreakpointAddress + arch::kExceptionOffsetForSoftwareBreakpoint;
thread->SendException(kBreakpointExceptionAddr, debug_ipc::ExceptionType::kSoftwareBreakpoint);
// Resume from the breakpoint which should clear the exception and try to single-step. But before
// that does anything, pause the thread.
harness.Resume();
EXPECT_TRUE(thread->mock_thread_handle().single_step());
EXPECT_FALSE(thread->in_exception());
harness.Pause();
EXPECT_EQ(1, thread->mock_thread_handle().suspend_count());
// Now resume from the pause. This should resume from the exception and leave the thread in
// single-step mode. This is tricky because the resume should not have cleared the single-step
// flag even though the resume requested "continue".
harness.Resume();
EXPECT_TRUE(thread->mock_thread_handle().single_step());
}
} // namespace debug_agent