blob: 9e2aa6d9e36e2497a30eb28485c771946ceb971b [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/zxdb/client/finish_physical_frame_thread_controller.h"
#include <gtest/gtest.h>
#include "src/developer/debug/zxdb/client/inline_thread_controller_test.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/test_thread_observer.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/common/err.h"
#include "src/developer/debug/zxdb/symbols/function.h"
namespace zxdb {
namespace {
constexpr uint64_t kInitialAddress = 0x12345678;
constexpr uint64_t kInitialBase = 0x1000;
constexpr uint64_t kReturnAddress = 0x34567890;
constexpr uint64_t kReturnBase = kInitialBase + 0x10;
constexpr uint64_t kBottomBase = kReturnBase + 0x10;
class FinishPhysicalFrameThreadControllerTest : public InlineThreadControllerTest {
public:
// Creates a break notification with two stack frames using the constants above.
debug_ipc::NotifyException MakeBreakNotification() {
debug_ipc::NotifyException n;
n.type = debug_ipc::ExceptionType::kSoftwareBreakpoint;
n.thread.id.process = process()->GetKoid();
n.thread.id.thread = thread()->GetKoid();
n.thread.state = debug_ipc::ThreadRecord::State::kBlocked;
n.thread.stack_amount = debug_ipc::ThreadRecord::StackAmount::kMinimal;
n.thread.frames.emplace_back(kInitialAddress, kInitialBase, kReturnBase);
n.thread.frames.emplace_back(kReturnAddress, kReturnBase, kBottomBase);
return n;
}
};
} // namespace
TEST_F(FinishPhysicalFrameThreadControllerTest, Finish) {
// Notify of thread stop.
auto break_notification = MakeBreakNotification();
auto& break_frames = break_notification.thread.frames;
InjectException(break_notification);
debug_ipc::StackFrame bottom_frame(kReturnAddress, kBottomBase, kBottomBase + 0x10);
// Supply three frames for when the thread requests them: the top one (of the stop above), the one
// we'll return to, and the one before that (so the fingerprint of the one to return to can be
// computed). This stack value should be larger than above (stack grows downward).
debug_ipc::ThreadStatusReply expected_reply;
// Copy previous frames and add to it.
expected_reply.record = break_notification.thread;
expected_reply.record.stack_amount = debug_ipc::ThreadRecord::StackAmount::kFull;
expected_reply.record.frames.push_back(bottom_frame);
mock_remote_api()->set_thread_status_reply(expected_reply);
EXPECT_EQ(0, mock_remote_api()->breakpoint_add_count());
Err out_err;
mock_remote_api()->set_resume_quits_loop(true);
thread()->ContinueWith(
std::make_unique<FinishPhysicalFrameThreadController>(thread()->GetStack(), 0),
[&out_err](const Err& err) {
out_err = err;
debug::MessageLoop::Current()->QuitNow();
});
loop().Run();
TestThreadObserver thread_observer(thread());
// Finish should have added a temporary breakpoint at the return address. The particulars of this
// may change with the implementation, but it's worth testing to make sure the breakpoints are all
// hooked up to the stepping properly.
ASSERT_EQ(1, mock_remote_api()->breakpoint_add_count());
ASSERT_EQ(kReturnAddress, mock_remote_api()->last_breakpoint_address());
ASSERT_EQ(0, mock_remote_api()->breakpoint_remove_count());
// Simulate a hit of the breakpoint. This stack frame is a recursive call above the frame we're
// returning to so it should not trigger.
break_frames.emplace(break_frames.begin(), kReturnAddress, kInitialBase - 0x100, kInitialBase);
break_notification.hit_breakpoints.emplace_back();
break_notification.hit_breakpoints[0].id = mock_remote_api()->last_breakpoint_id();
InjectException(break_notification);
EXPECT_FALSE(thread_observer.got_stopped());
// Simulate a breakpoint hit with a lower BP (erase the two top ones = the recursive call and the
// old top one).
break_frames.erase(break_frames.begin(), break_frames.begin() + 2);
InjectException(break_notification);
EXPECT_TRUE(thread_observer.got_stopped());
EXPECT_EQ(1, mock_remote_api()->breakpoint_remove_count());
}
// Tests "finish" at the bottom stack frame. Normally there's a stack frame with an IP of 0 below
// the last "real" stack frame.
TEST_F(FinishPhysicalFrameThreadControllerTest, BottomStackFrame) {
// Notify of thread stop. Here we have the 0th frame of the current location, and a null frame.
auto break_notification = MakeBreakNotification();
break_notification.thread.frames[0].cfa = 0;
break_notification.thread.frames[1] = debug_ipc::StackFrame(0, 0, 0);
InjectException(break_notification);
// The backtrace reply gives the same two frames since that's all there is (the Thread doesn't
// know until it requests them).
debug_ipc::ThreadStatusReply expected_reply;
expected_reply.record = break_notification.thread;
expected_reply.record.stack_amount = debug_ipc::ThreadRecord::StackAmount::kFull;
mock_remote_api()->set_thread_status_reply(expected_reply);
EXPECT_EQ(0, mock_remote_api()->breakpoint_add_count());
Err out_err;
mock_remote_api()->set_resume_quits_loop(true);
thread()->ContinueWith(
std::make_unique<FinishPhysicalFrameThreadController>(thread()->GetStack(), 0),
[&out_err](const Err& err) {
out_err = err;
debug::MessageLoop::Current()->QuitNow();
});
loop().Run();
TestThreadObserver thread_observer(thread());
// Since the return address is null, we should not have attempted to create a breakpoint, and the
// thread should have been resumed.
ASSERT_EQ(0, mock_remote_api()->breakpoint_add_count());
ASSERT_EQ(1, mock_remote_api()->GetAndResetResumeCount());
}
// Finishing a physical frame should leave the stack at the calling frame. But the instruction after
// the function call being finished could be the first instruction of an inlined function (an
// ambiguous location -- see discussions in Stack class).
//
// In the case of ambiguity, the finish controller should leave the frame at the one that called the
// function being finished, not an inline frame that starts right atfer the call.
TEST_F(FinishPhysicalFrameThreadControllerTest, FinishToInline) {
auto mock_frames = GetStack();
// Save the return address from frame 1 (frame 2's IP).
const uint64_t return_address = mock_frames[2]->GetAddress();
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::ExceptionType::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)), true);
Stack& stack = thread()->GetStack();
// Finish stack frame #1 (top physical frame).
thread()->ContinueWith(std::make_unique<FinishPhysicalFrameThreadController>(stack, 1),
[](const Err& err) {});
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount()); // Continued.
// Should have added a breakpoint to catch completion of function
ASSERT_EQ(1, mock_remote_api()->breakpoint_add_count());
ASSERT_EQ(return_address, mock_remote_api()->last_breakpoint_address());
ASSERT_EQ(0, mock_remote_api()->breakpoint_remove_count());
// Make breakpoint hit notification.
std::vector<debug_ipc::BreakpointStats> hit_breakpoints;
hit_breakpoints.emplace_back();
hit_breakpoints[0].id = mock_remote_api()->last_breakpoint_id();
// Make an inline function starting at the return address of the function.
AddressRange second_inline_range(return_address, return_address + 4);
auto second_inline_func = fxl::MakeRefCounted<Function>(DwarfTag::kInlinedSubroutine);
second_inline_func->set_assigned_name("Second");
second_inline_func->set_code_ranges(AddressRanges(second_inline_range));
Location second_inline_loc(second_inline_range.begin(), FileLine("file.cc", 21), 0,
SymbolContext::ForRelativeAddresses(), second_inline_func);
// Construct the stack of the address after the call. In this case the frame being returned to
// immediately calls an inline subroutine, so execution will be in a new inline function off of
// the returned-to frame.
mock_frames = GetStack();
mock_frames.erase(mock_frames.begin(), mock_frames.begin() + 2);
mock_frames.insert(mock_frames.begin(), std::make_unique<MockFrame>(
nullptr, nullptr, second_inline_loc, kMiddleSP,
kBottomSP, std::vector<debug::RegisterValue>(),
kMiddleSP, mock_frames[0]->GetPhysicalFrame(), true));
InjectExceptionWithStack(
process()->GetKoid(), thread()->GetKoid(), debug_ipc::ExceptionType::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)), true, hit_breakpoints);
EXPECT_EQ(0, mock_remote_api()->GetAndResetResumeCount()); // Stopped.
EXPECT_EQ(1u, thread()->GetStack().hide_ambiguous_inline_frame_count());
}
} // namespace zxdb