blob: 98441803b71d967c990c6e5b37cc598bda182265 [file] [log] [blame] [edit]
// 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/debugged_thread.h"
#include <memory>
#include <gtest/gtest.h>
#include "src/developer/debug/debug_agent/arch.h"
#include "src/developer/debug/debug_agent/debugged_process.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_process_handle.h"
#include "src/developer/debug/debug_agent/mock_thread.h"
#include "src/developer/debug/debug_agent/mock_thread_handle.h"
#include "src/developer/debug/debug_agent/remote_api.h"
#include "src/developer/debug/ipc/protocol.h"
#include "src/developer/debug/ipc/records.h"
namespace debug_agent {
using namespace debug_ipc;
namespace {
TEST(DebuggedThread, Resume) {
MockDebugAgentHarness harness;
constexpr zx_koid_t kProcessKoid = 0x8723456;
MockProcess process(harness.debug_agent(), kProcessKoid);
constexpr zx_koid_t kThreadKoid = 0x8723457;
MockThread* thread = process.AddThread(kThreadKoid);
EXPECT_FALSE(thread->in_exception());
ExceptionHandle::Resolution resolution = ExceptionHandle::Resolution::kTryNext;
debug_ipc::ExceptionStrategy exception_strategy = debug_ipc::ExceptionStrategy::kNone;
auto exception = std::make_unique<MockExceptionHandle>(
[&resolution](ExceptionHandle::Resolution new_res) { resolution = new_res; },
[&exception_strategy](debug_ipc::ExceptionStrategy new_strategy) {
exception_strategy = new_strategy;
});
thread->set_exception_handle(std::move(exception));
EXPECT_TRUE(thread->in_exception());
thread->ClientResume(
debug_ipc::ResumeRequest{.how = debug_ipc::ResumeRequest::How::kResolveAndContinue});
EXPECT_FALSE(thread->in_exception());
EXPECT_EQ(resolution, ExceptionHandle::Resolution::kHandled);
EXPECT_EQ(exception_strategy, debug_ipc::ExceptionStrategy::kNone);
resolution = ExceptionHandle::Resolution::kTryNext;
exception_strategy = debug_ipc::ExceptionStrategy::kNone;
exception = std::make_unique<MockExceptionHandle>(
[&resolution](ExceptionHandle::Resolution new_res) { resolution = new_res; },
[&exception_strategy](debug_ipc::ExceptionStrategy new_strategy) {
exception_strategy = new_strategy;
});
thread->set_exception_handle(std::move(exception));
EXPECT_TRUE(thread->in_exception());
thread->ClientResume(
debug_ipc::ResumeRequest{.how = debug_ipc::ResumeRequest::How::kForwardAndContinue});
EXPECT_FALSE(thread->in_exception());
EXPECT_EQ(resolution, ExceptionHandle::Resolution::kTryNext);
EXPECT_EQ(exception_strategy, debug_ipc::ExceptionStrategy::kSecondChance);
}
TEST(DebuggedThread, SingleStepForwardsException) {
MockDebugAgentHarness harness;
constexpr zx_koid_t kProcessKoid = 0x8723456;
MockProcess process(harness.debug_agent(), kProcessKoid);
constexpr zx_koid_t kThreadKoid = 0x8723457;
MockThread* thread = process.AddThread(kThreadKoid);
EXPECT_FALSE(thread->in_exception());
// Set an exception.
auto resolution = ExceptionHandle::Resolution::kTryNext;
auto exception_strategy = debug_ipc::ExceptionStrategy::kNone;
auto exception = std::make_unique<MockExceptionHandle>(
[&resolution](ExceptionHandle::Resolution new_res) { resolution = new_res; },
[&exception_strategy](debug_ipc::ExceptionStrategy new_strategy) {
exception_strategy = new_strategy;
});
exception->set_type(debug_ipc::ExceptionType::kUndefinedInstruction);
exception->SetStrategy(debug_ipc::ExceptionStrategy::kFirstChance);
thread->OnException(std::move(exception));
// We shouldn't release the exception.
EXPECT_TRUE(thread->in_exception());
EXPECT_EQ(thread->exception_handle()->GetType(thread->thread_handle()),
debug_ipc::ExceptionType::kUndefinedInstruction);
EXPECT_EQ(thread->exception_handle()->GetStrategy(), debug_ipc::ExceptionStrategy::kFirstChance);
EXPECT_TRUE(thread->exception_handle()->GetResolution().is_ok());
EXPECT_EQ(*thread->exception_handle()->GetResolution(), ExceptionHandle::Resolution::kTryNext);
// The notification should be sent.
ASSERT_EQ(harness.stream_backend()->exceptions().size(), 1u);
EXPECT_EQ(harness.stream_backend()->exceptions()[0].type,
debug_ipc::ExceptionType::kUndefinedInstruction);
EXPECT_EQ(harness.stream_backend()->exceptions()[0].exception.strategy,
debug_ipc::ExceptionStrategy::kFirstChance);
// Now try to single step over the exception, which will forward the exception and mark it for
// second chance handling.
debug_ipc::ResumeRequest step_request;
step_request.how = debug_ipc::ResumeRequest::How::kStepInstruction;
thread->ClientResume(step_request);
// The exception object should be released now, but we should have set the strategy to second
// chance first.
EXPECT_EQ(thread->exception_handle(), nullptr);
EXPECT_EQ(debug_ipc::ExceptionStrategy::kSecondChance, exception_strategy);
EXPECT_EQ(ExceptionHandle::Resolution::kTryNext, resolution);
auto second_chance_exception = std::make_unique<MockExceptionHandle>(
[&resolution](ExceptionHandle::Resolution new_res) { resolution = new_res; },
[&exception_strategy](debug_ipc::ExceptionStrategy new_strategy) {
exception_strategy = new_strategy;
});
second_chance_exception->set_type(debug_ipc::ExceptionType::kUndefinedInstruction);
second_chance_exception->SetStrategy(exception_strategy);
// Now dispatch the second chance exception, simulating a process that doesn't handle its own
// exceptions.
thread->OnException(std::move(second_chance_exception));
// We should have an exception again, but this one should be second chance.
EXPECT_TRUE(thread->in_exception());
EXPECT_EQ(thread->exception_handle()->GetType(thread->thread_handle()),
debug_ipc::ExceptionType::kUndefinedInstruction);
EXPECT_EQ(thread->exception_handle()->GetStrategy(), debug_ipc::ExceptionStrategy::kSecondChance);
EXPECT_TRUE(thread->exception_handle()->GetResolution().is_ok());
EXPECT_EQ(*thread->exception_handle()->GetResolution(), ExceptionHandle::Resolution::kTryNext);
ASSERT_EQ(harness.stream_backend()->exceptions().size(), 2u);
EXPECT_EQ(harness.stream_backend()->exceptions()[1].type,
debug_ipc::ExceptionType::kUndefinedInstruction);
EXPECT_EQ(harness.stream_backend()->exceptions()[1].exception.strategy,
debug_ipc::ExceptionStrategy::kSecondChance);
// Step again.
thread->ClientResume(step_request);
// In a real system, this would mean the exception is forwarded up the job tree and would
// eventually get killed by the root job. For our purposes, we just need to be sure that we
// released it for the final time.
EXPECT_FALSE(thread->in_exception());
// We should not have marked the exception as handled.
EXPECT_EQ(ExceptionHandle::Resolution::kTryNext, resolution);
}
TEST(DebuggedThread, OnException) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
constexpr zx_koid_t kProcessKoid = 0x8723456;
MockProcess process(harness.debug_agent(), kProcessKoid);
constexpr zx_koid_t kThreadKoid = 0x8723457;
MockThread* thread = process.AddThread(kThreadKoid);
EXPECT_FALSE(thread->in_exception());
// Policy: general exceptions initially handled as first-chance.
// Exception: general, first-chance.
// Expected: no applied strategy.
{
debug_ipc::ExceptionStrategy applied_strategy = debug_ipc::ExceptionStrategy::kNone;
auto exception = std::make_unique<MockExceptionHandle>(
[](ExceptionHandle::Resolution) {},
[&applied_strategy](debug_ipc::ExceptionStrategy new_strategy) {
applied_strategy = new_strategy;
});
exception->set_type(debug_ipc::ExceptionType::kGeneral);
exception->SetStrategy(debug_ipc::ExceptionStrategy::kFirstChance);
applied_strategy = debug_ipc::ExceptionStrategy::kNone; // Clear previously set.
thread->OnException(std::move(exception));
EXPECT_EQ(debug_ipc::ExceptionStrategy::kNone, applied_strategy);
}
// Policy: general exceptions initially handled as first-chance.
// Exception: general, second-chance.
// Expected: no applied strategy (as this isn't our initial handling).
{
debug_ipc::ExceptionStrategy applied_strategy = debug_ipc::ExceptionStrategy::kNone;
auto exception = std::make_unique<MockExceptionHandle>(
[](ExceptionHandle::Resolution) {},
[&applied_strategy](debug_ipc::ExceptionStrategy new_strategy) {
applied_strategy = new_strategy;
});
exception->set_type(debug_ipc::ExceptionType::kGeneral);
exception->SetStrategy(debug_ipc::ExceptionStrategy::kSecondChance);
applied_strategy = debug_ipc::ExceptionStrategy::kNone; // Clear previously set.
thread->OnException(std::move(exception));
EXPECT_EQ(debug_ipc::ExceptionStrategy::kNone, applied_strategy);
}
// Update policy so that general exceptions are handled initially as
// second-chance.
const debug_ipc::UpdateGlobalSettingsRequest request = {
.exception_strategies =
{
{
.type = debug_ipc::ExceptionType::kGeneral,
.value = debug_ipc::ExceptionStrategy::kSecondChance,
},
},
};
debug_ipc::UpdateGlobalSettingsReply reply;
remote_api->OnUpdateGlobalSettings(request, &reply);
EXPECT_TRUE(reply.status.ok());
// Policy: general exceptions initially handled as second-chance.
// Exception: general, first-chance.
// Expected: applied strategy of second-chance.
{
debug_ipc::ExceptionStrategy applied_strategy = debug_ipc::ExceptionStrategy::kNone;
auto exception = std::make_unique<MockExceptionHandle>(
[](ExceptionHandle::Resolution) {},
[&applied_strategy](debug_ipc::ExceptionStrategy new_strategy) {
applied_strategy = new_strategy;
});
exception->set_type(debug_ipc::ExceptionType::kGeneral);
exception->SetStrategy(debug_ipc::ExceptionStrategy::kFirstChance);
applied_strategy = debug_ipc::ExceptionStrategy::kNone; // Clear previously set.
thread->OnException(std::move(exception));
EXPECT_EQ(debug_ipc::ExceptionStrategy::kSecondChance, applied_strategy);
// Since we didn't handle the exception, we expect it to have been closed.
EXPECT_EQ(nullptr, thread->exception_handle());
}
// Policy: general exceptions initially handled as second-chance.
// Exception: general, second-chance.
// Expected: no applied strategy.
{
debug_ipc::ExceptionStrategy applied_strategy = debug_ipc::ExceptionStrategy::kNone;
auto exception = std::make_unique<MockExceptionHandle>(
[](ExceptionHandle::Resolution) {},
[&applied_strategy](debug_ipc::ExceptionStrategy new_strategy) {
applied_strategy = new_strategy;
});
exception->set_type(debug_ipc::ExceptionType::kGeneral);
exception->SetStrategy(debug_ipc::ExceptionStrategy::kSecondChance);
applied_strategy = debug_ipc::ExceptionStrategy::kNone; // Clear previously set.
thread->OnException(std::move(exception));
EXPECT_EQ(debug_ipc::ExceptionStrategy::kNone, applied_strategy);
}
}
} // namespace
} // namespace debug_agent