blob: d45f393bdbac2161095f7e5783ea497fb2d0757d [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 <lib/async/cpp/task.h>
#include <lib/zx/channel.h>
#include "garnet/lib/debugger_utils/threads.h"
#include "garnet/lib/inferior_control/process.h"
#include "garnet/lib/inferior_control/test_helper.h"
#include "garnet/lib/inferior_control/test_server.h"
#include "gtest/gtest.h"
namespace inferior_control {
namespace {
// Test resume from exception and try-next.
// Note: Exceptions are handled in the same thread as server.Run().
class TryNextThreadTest : public TestServer {
public:
TryNextThreadTest() = default;
bool got_sw_breakpoint() const { return got_sw_breakpoint_; }
bool got_unexpected_exception() const { return got_unexpected_exception_; }
void OnArchitecturalException(
Process* process, Thread* thread, zx_handle_t eport, zx_excp_type_t type,
const zx_exception_context_t& context) {
FXL_LOG(INFO) << "Got exception "
<< debugger_utils::ExceptionNameAsString(type);
if (type == ZX_EXCP_SW_BREAKPOINT) {
got_sw_breakpoint_ = true;
thread->TryNext(eport);
} else {
// We shouldn't get here, test has failed.
// Record the fact for the test, and terminate the inferior, we don't
// want the exception propagating to the system exception handler.
got_unexpected_exception_ = true;
process->process().kill();
}
}
private:
bool got_sw_breakpoint_ = false;
bool got_unexpected_exception_ = false;
};
// FLK-229: disabled pending fix
TEST_F(TryNextThreadTest, DISABLED_ResumeTryNextTest) {
std::vector<std::string> argv{
kTestHelperPath,
"trigger-sw-bkpt-with-handler",
};
zx::channel our_channel, their_channel;
ASSERT_EQ(zx::channel::create(0, &our_channel, &their_channel), ZX_OK);
ASSERT_TRUE(SetupInferior(argv, std::move(their_channel)));
EXPECT_TRUE(RunHelperProgram());
// The inferior is waiting for us to close our side of the channel.
our_channel.reset();
EXPECT_TRUE(Run());
EXPECT_TRUE(TestSuccessfulExit());
EXPECT_TRUE(got_sw_breakpoint());
EXPECT_FALSE(got_unexpected_exception());
}
class SuspendResumeThreadTest : public TestServer {
public:
SuspendResumeThreadTest() = default;
bool got_sw_breakpoint() const { return got_sw_breakpoint_; }
bool got_unexpected_exception() const { return got_unexpected_exception_; }
void OnThreadStarting(Process* process, Thread* thread, zx_handle_t eport,
const zx_exception_context_t& context) override {
if (main_thread_id_ == ZX_KOID_INVALID) {
// Must be the inferior's main thread.
main_thread_id_ = thread->id();
FXL_LOG(INFO) << "Main thread = " << main_thread_id_;
} else {
// Must be the exception handling thread.
FXL_CHECK(exception_handling_thread_id_ == ZX_KOID_INVALID);
exception_handling_thread_id_ = thread->id();
FXL_LOG(INFO) << "Exception handling thread = "
<< exception_handling_thread_id_;
}
TestServer::OnThreadStarting(process, thread, eport, context);
}
void OnArchitecturalException(
Process* process, Thread* thread, zx_handle_t eport, zx_excp_type_t type,
const zx_exception_context_t& context) override {
FXL_LOG(INFO) << "Got exception "
<< debugger_utils::ExceptionNameAsString(type);
if (type == ZX_EXCP_SW_BREAKPOINT) {
FXL_CHECK(thread->id() == main_thread_id_) << thread->id();
got_sw_breakpoint_ = true;
// The exception handling thread is either in |zx_port_wait()| or on
// its way there.
Thread* ethread = process->FindThreadById(exception_handling_thread_id_);
FXL_CHECK(ethread);
FXL_CHECK(ethread->RequestSuspend());
} else {
// We shouldn't get here, test has failed.
// Record the fact for the test, and terminate the inferior, we don't
// want the exception propagating to the system exception handler.
got_unexpected_exception_ = true;
process->process().kill();
}
}
void OnThreadSuspension(Thread* thread) override {
// This should be the exception-handling thread. The thread that got the
// s/w breakpoint should still be in the breakpoint.
FXL_CHECK(thread->id() == exception_handling_thread_id_) << thread->id();
FXL_CHECK(debugger_utils::GetThreadOsState(thread->handle()) ==
ZX_THREAD_STATE_SUSPENDED);
Process* process = thread->process();
Thread* mthread = process->FindThreadById(main_thread_id_);
FXL_CHECK(mthread);
FXL_CHECK(debugger_utils::GetThreadOsState(mthread->handle()) ==
ZX_THREAD_STATE_BLOCKED_EXCEPTION);
thread->ResumeFromSuspension();
}
void OnThreadResumption(Thread* thread) override {
// We also get ZX_THREAD_RUNNING signals when threads are resumed from
// exceptions. If this is the exception-handling thread then the thread
// that got the s/w breakpoint should still be in the breakpoint.
if (thread->id() == main_thread_id_) {
// Nothing to do.
} else {
// Wait until the main thread is in the s/w breakpoint.
if (got_sw_breakpoint_) {
FXL_CHECK(thread->id() == exception_handling_thread_id_);
Process* process = thread->process();
Thread* mthread = process->FindThreadById(main_thread_id_);
FXL_CHECK(mthread);
FXL_CHECK(debugger_utils::GetThreadOsState(mthread->handle()) ==
ZX_THREAD_STATE_BLOCKED_EXCEPTION)
<< debugger_utils::GetThreadOsState(mthread->handle());
mthread->TryNext(process->server()->exception_port_handle());
}
}
}
private:
zx_koid_t main_thread_id_ = ZX_KOID_INVALID;
zx_koid_t exception_handling_thread_id_ = ZX_KOID_INVALID;
bool got_sw_breakpoint_ = false;
bool got_unexpected_exception_ = false;
};
TEST_F(SuspendResumeThreadTest, SuspendResumeTest) {
std::vector<std::string> argv{
kTestHelperPath,
"trigger-sw-bkpt-with-handler",
};
zx::channel our_channel, their_channel;
ASSERT_EQ(zx::channel::create(0, &our_channel, &their_channel), ZX_OK);
ASSERT_TRUE(SetupInferior(argv, std::move(their_channel)));
EXPECT_TRUE(RunHelperProgram());
// The inferior is waiting for us to close our side of the channel.
our_channel.reset();
EXPECT_TRUE(Run());
EXPECT_TRUE(TestSuccessfulExit());
EXPECT_TRUE(got_sw_breakpoint());
EXPECT_FALSE(got_unexpected_exception());
}
class ResumeAfterSwBreakThreadTest : public TestServer {
public:
ResumeAfterSwBreakThreadTest() = default;
bool got_sw_breakpoint() const { return got_sw_breakpoint_; }
bool got_unexpected_exception() const { return got_unexpected_exception_; }
bool resume_after_break_succeeded() const {
return resume_after_break_succeeded_;
}
void OnArchitecturalException(
Process* process, Thread* thread, zx_handle_t eport, zx_excp_type_t type,
const zx_exception_context_t& context) {
FXL_LOG(INFO) << "Got exception "
<< debugger_utils::ExceptionNameAsString(type);
if (type == ZX_EXCP_SW_BREAKPOINT) {
got_sw_breakpoint_ = true;
resume_after_break_succeeded_ =
thread->ResumeAfterSoftwareBreakpointInstruction(eport);
} else {
// We shouldn't get here, test has failed.
// Record the fact for the test, and terminate the inferior, we don't
// want the exception propagating to the system exception handler.
got_unexpected_exception_ = true;
process->process().get();
}
}
private:
bool got_sw_breakpoint_ = false;
bool got_unexpected_exception_ = false;
bool resume_after_break_succeeded_ = false;
};
TEST_F(ResumeAfterSwBreakThreadTest, ResumeAfterSwBreakTest) {
std::vector<std::string> argv{
kTestHelperPath,
"trigger-sw-bkpt",
};
zx::channel our_channel, their_channel;
ASSERT_EQ(zx::channel::create(0, &our_channel, &their_channel), ZX_OK);
ASSERT_TRUE(SetupInferior(argv, std::move(their_channel)));
EXPECT_TRUE(RunHelperProgram());
// The inferior is waiting for us to close our side of the channel.
our_channel.reset();
EXPECT_TRUE(Run());
EXPECT_TRUE(TestSuccessfulExit());
EXPECT_TRUE(resume_after_break_succeeded());
EXPECT_TRUE(got_sw_breakpoint());
EXPECT_FALSE(got_unexpected_exception());
}
} // namespace
} // namespace inferior_control