blob: ae505e79d138ea7dbddaaf18c10a1968ae29d9b7 [file] [log] [blame]
// Copyright 2021 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/debug_adapter/handlers/request_stacktrace.h"
#include <gtest/gtest.h>
#include "src/developer/debug/ipc/records.h"
#include "src/developer/debug/zxdb/client/mock_frame.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/common/scoped_temp_file.h"
#include "src/developer/debug/zxdb/debug_adapter/context_test.h"
#include "src/developer/debug/zxdb/symbols/function.h"
#include "src/developer/debug/zxdb/symbols/loaded_module_symbols.h"
#include "src/developer/debug/zxdb/symbols/mock_module_symbols.h"
#include "src/developer/debug/zxdb/symbols/process_symbols.h"
namespace zxdb {
namespace {
using RequestStackTraceTest = DebugAdapterContextTest;
} // namespace
TEST_F(RequestStackTraceTest, FullFrameAvailable) {
InitializeDebugging();
InjectProcessWithModule(kProcessKoid, 0x1000);
// Run client to receive process started event.
RunClient();
auto thread = InjectThread(kProcessKoid, kThreadKoid);
// Run client to receive threads started event.
RunClient();
// Insert mock frames
// Top frame has a valid source location
ScopedTempFile temp_file;
fxl::RefPtr<Function> function1(fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram));
function1->set_assigned_name("test_func1");
function1->set_code_ranges(AddressRanges(AddressRange(0x10000, 0x10020)));
auto location1 = Location(0x10010, FileLine(temp_file.name(), 23), 10,
SymbolContext::ForRelativeAddresses(), function1);
// The source of this frame cannot be found and will not be reported in response.
fxl::RefPtr<Function> function2(fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram));
function2->set_assigned_name("test_func2");
function2->set_code_ranges(AddressRanges(AddressRange(0x10024, 0x10060)));
auto location2 =
Location(0x10040, FileLine("", 0), 0, SymbolContext::ForRelativeAddresses(), function2);
std::vector<std::unique_ptr<Frame>> frames;
frames.push_back(std::make_unique<MockFrame>(&session(), thread, location1, 0x2000));
frames.push_back(std::make_unique<MockFrame>(&session(), thread, location2, 0x2020));
InjectExceptionWithStack(kProcessKoid, kThreadKoid, debug_ipc::ExceptionType::kSingleStep,
std::move(frames), true);
// Receive thread stopped event in client.
RunClient();
// Send request from the client.
dap::StackTraceRequest request = {};
request.threadId = kThreadKoid;
auto response = client().send(request);
// Read request and process it in server.
context().OnStreamReadable();
loop().RunUntilNoTasks();
// Run client to receive response.
RunClient();
auto got = response.get();
EXPECT_FALSE(got.error);
EXPECT_EQ(got.response.totalFrames.value(), 2);
EXPECT_EQ(got.response.stackFrames[0].column, location1.column());
EXPECT_EQ(got.response.stackFrames[0].line, location1.file_line().line());
EXPECT_EQ(got.response.stackFrames[0].name, function1->GetAssignedName());
EXPECT_EQ(got.response.stackFrames[0].source.value().path.value(), temp_file.name());
EXPECT_EQ(got.response.stackFrames[1].column, location2.column());
EXPECT_EQ(got.response.stackFrames[1].line, location2.file_line().line());
EXPECT_EQ(got.response.stackFrames[1].name, function2->GetAssignedName());
EXPECT_FALSE(got.response.stackFrames[1].source.value().path.has_value());
}
TEST_F(RequestStackTraceTest, SyncFramesRequired) {
InitializeDebugging();
auto process = InjectProcess(kProcessKoid);
// Run client to receive process started event.
RunClient();
InjectThread(kProcessKoid, kThreadKoid);
// Run client to receive threads started event.
RunClient();
constexpr uint64_t kAddress[] = {0x10010, 0x10040, 0x9000};
constexpr uint64_t kStack[] = {0x3000, 0x3020, 0x3050};
constexpr size_t kStackSize = 3;
// Set up symbol resolution for stack frames.
ScopedTempFile temp_file;
auto mock_module = InjectMockModule(process, 0x10000);
auto mock_module2 = InjectMockModule(process, 0x8000);
std::vector<fxl::RefPtr<Function>> functions;
std::vector<Location> locations;
auto loaded_module1 = process->GetSymbols()->GetLoadedForModuleSymbols(mock_module.get());
ASSERT_NE(loaded_module1, nullptr);
for (size_t i = 0; i < kStackSize; i++) {
// For stack frames that did not recover an exact PC value, the address lookup will actually be
// the immediately preceding address to get the instruction from the caller rather than the
// callee. For this test, we simulate the first frame being recovered from "context" which
// always has an exact PC value, and then the following frames recovered via the return address
// register.
uint64_t lookup_address = kAddress[i];
if (i > 0)
lookup_address--;
functions.push_back(fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram));
functions[i]->set_assigned_name(std::string("test_func_") + std::to_string(i));
functions[i]->set_code_ranges(
AddressRanges(AddressRange(kAddress[i] - 0x10, kAddress[i] + 0x10)));
locations.push_back(Location(lookup_address, FileLine(temp_file.name(), 23 + i), 10 + i,
SymbolContext::ForRelativeAddresses(), functions[i]));
if (kAddress[i] >= loaded_module1->load_address()) {
mock_module->AddSymbolLocations(lookup_address, {locations.back()});
} else {
mock_module2->AddSymbolLocations(lookup_address, {locations.back()});
}
}
// Notify of thread stop and push expected stack frames.
debug_ipc::NotifyException break_notification;
break_notification.type = debug_ipc::ExceptionType::kSoftwareBreakpoint;
break_notification.thread.id = {.process = kProcessKoid, .thread = kThreadKoid};
break_notification.thread.state = debug_ipc::ThreadRecord::State::kBlocked;
break_notification.thread.stack_amount = debug_ipc::ThreadRecord::StackAmount::kFull;
for (size_t i = 0; i < kStackSize; i++) {
debug_ipc::StackFrame frame(kAddress[i], kStack[i]);
if (i > 0) {
frame.pc_is_return_address = debug_ipc::StackFrame::AddressType::kReturn;
}
break_notification.thread.frames.push_back(frame);
}
InjectException(break_notification);
// Receive exception event in client.
RunClient();
// Send request from the client.
dap::StackTraceRequest request = {};
request.threadId = kThreadKoid;
auto response = client().send(request);
// Read request and process it in server.
context().OnStreamReadable();
loop().RunUntilNoTasks();
// Run client to receive response.
RunClient();
auto got = response.get();
EXPECT_FALSE(got.error);
EXPECT_EQ(static_cast<size_t>(got.response.totalFrames.value()), kStackSize);
for (size_t i = 0; i < kStackSize; i++) {
EXPECT_EQ(got.response.stackFrames[i].source.value().path.value(), temp_file.name());
EXPECT_EQ(got.response.stackFrames[i].column, locations[i].column());
EXPECT_EQ(got.response.stackFrames[i].line, locations[i].file_line().line());
EXPECT_EQ(got.response.stackFrames[i].name, functions[i]->GetAssignedName());
}
}
TEST_F(RequestStackTraceTest, Pagination) {
InitializeDebugging();
InjectProcessWithModule(kProcessKoid, 0x1000);
// Run client to receive process started event.
RunClient();
auto thread = InjectThread(kProcessKoid, kThreadKoid);
// Run client to receive threads started event.
RunClient();
// Insert mock frames
const int kTotalFrames = 10;
std::vector<std::unique_ptr<Frame>> frames;
std::vector<Location> locations;
std::vector<fxl::RefPtr<Function>> functions;
std::vector<std::unique_ptr<ScopedTempFile>> temp_files;
for (int i = 0; i < kTotalFrames; i++) {
temp_files.push_back(std::make_unique<ScopedTempFile>());
functions.push_back(fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram));
functions[i]->set_assigned_name("test_func" + std::to_string(i));
functions[i]->set_code_ranges(
AddressRanges(AddressRange(0x10000 + i * 0x20, 0x10020 + i * 0x20)));
locations.emplace_back(0x10010 + i * 0x20, FileLine(temp_files[i]->name(), 23 + i), 10 + i,
SymbolContext::ForRelativeAddresses(), functions[i]);
frames.push_back(
std::make_unique<MockFrame>(&session(), thread, locations[i], 0x2000 + i * 0x20));
}
InjectExceptionWithStack(kProcessKoid, kThreadKoid, debug_ipc::ExceptionType::kSingleStep,
std::move(frames), true);
// Receive thread stopped event in client.
RunClient();
// Send request from the client for a subset of frames.
dap::StackTraceRequest request = {};
request.threadId = kThreadKoid;
constexpr int kStartFrame = 2;
constexpr int kLevels = 3;
request.startFrame = kStartFrame;
request.levels = kLevels;
auto response = client().send(request);
// Read request and process it in server.
context().OnStreamReadable();
loop().RunUntilNoTasks();
// Run client to receive response.
RunClient();
auto got = response.get();
EXPECT_FALSE(got.error);
EXPECT_EQ(got.response.totalFrames.value(), kTotalFrames);
ASSERT_EQ(got.response.stackFrames.size(), (size_t)kLevels);
// Check the returned frames.
for (int i = 0; i < kLevels; i++) {
int frame_index = i + kStartFrame;
EXPECT_EQ(got.response.stackFrames[i].column, locations[frame_index].column());
EXPECT_EQ(got.response.stackFrames[i].line, locations[frame_index].file_line().line());
EXPECT_EQ(got.response.stackFrames[i].name, functions[frame_index]->GetAssignedName());
EXPECT_EQ(got.response.stackFrames[i].source.value().path.value(),
temp_files[frame_index]->name());
}
}
TEST_F(RequestStackTraceTest, PaginationAllFrames) {
InitializeDebugging();
InjectProcessWithModule(kProcessKoid, 0x1000);
// Run client to receive process started event.
RunClient();
auto thread = InjectThread(kProcessKoid, kThreadKoid);
// Run client to receive threads started event.
RunClient();
// Insert mock frames
const int kTotalFrames = 5;
std::vector<std::unique_ptr<Frame>> frames;
std::vector<Location> locations;
std::vector<fxl::RefPtr<Function>> functions;
std::vector<std::unique_ptr<ScopedTempFile>> temp_files;
for (int i = 0; i < kTotalFrames; i++) {
temp_files.push_back(std::make_unique<ScopedTempFile>());
functions.push_back(fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram));
functions[i]->set_assigned_name("test_func" + std::to_string(i));
functions[i]->set_code_ranges(
AddressRanges(AddressRange(0x10000 + i * 0x20, 0x10020 + i * 0x20)));
locations.emplace_back(0x10010 + i * 0x20, FileLine(temp_files[i]->name(), 23 + i), 10 + i,
SymbolContext::ForRelativeAddresses(), functions[i]);
frames.push_back(
std::make_unique<MockFrame>(&session(), thread, locations[i], 0x2000 + i * 0x20));
}
InjectExceptionWithStack(kProcessKoid, kThreadKoid, debug_ipc::ExceptionType::kSingleStep,
std::move(frames), true);
// Receive thread stopped event in client.
RunClient();
// Send request from the client for all frames.
dap::StackTraceRequest request = {};
request.threadId = kThreadKoid;
request.startFrame = 0;
request.levels = 0; // 0 means all frames
auto response = client().send(request);
// Read request and process it in server.
context().OnStreamReadable();
loop().RunUntilNoTasks();
// Run client to receive response.
RunClient();
auto got = response.get();
EXPECT_FALSE(got.error);
EXPECT_EQ(got.response.totalFrames.value(), kTotalFrames);
ASSERT_EQ(got.response.stackFrames.size(), (size_t)kTotalFrames);
// Check the returned frames.
for (int i = 0; i < kTotalFrames; i++) {
EXPECT_EQ(got.response.stackFrames[i].column, locations[i].column());
EXPECT_EQ(got.response.stackFrames[i].line, locations[i].file_line().line());
EXPECT_EQ(got.response.stackFrames[i].name, functions[i]->GetAssignedName());
EXPECT_EQ(got.response.stackFrames[i].source.value().path.value(), temp_files[i]->name());
}
}
TEST_F(RequestStackTraceTest, PaginationTooManyFramesRequested) {
InitializeDebugging();
InjectProcessWithModule(kProcessKoid, 0x1000);
// Run client to receive process started event.
RunClient();
auto thread = InjectThread(kProcessKoid, kThreadKoid);
// Run client to receive threads started event.
RunClient();
// Insert mock frames
const int kTotalFrames = 5;
std::vector<std::unique_ptr<Frame>> frames;
std::vector<Location> locations;
std::vector<fxl::RefPtr<Function>> functions;
std::vector<std::unique_ptr<ScopedTempFile>> temp_files;
for (int i = 0; i < kTotalFrames; i++) {
temp_files.push_back(std::make_unique<ScopedTempFile>());
functions.push_back(fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram));
functions[i]->set_assigned_name("test_func" + std::to_string(i));
functions[i]->set_code_ranges(
AddressRanges(AddressRange(0x10000 + i * 0x20, 0x10020 + i * 0x20)));
locations.emplace_back(0x10010 + i * 0x20, FileLine(temp_files[i]->name(), 23 + i), 10 + i,
SymbolContext::ForRelativeAddresses(), functions[i]);
frames.push_back(
std::make_unique<MockFrame>(&session(), thread, locations[i], 0x2000 + i * 0x20));
}
InjectExceptionWithStack(kProcessKoid, kThreadKoid, debug_ipc::ExceptionType::kSingleStep,
std::move(frames), true);
// Receive thread stopped event in client.
RunClient();
// Send request from the client for a subset of frames.
constexpr int kStartFrame = 3;
constexpr int kRequestedLevels = 5; // Request more than the amount of frames after `startFrame`.
dap::StackTraceRequest request = {};
request.threadId = kThreadKoid;
request.startFrame = kStartFrame;
request.levels = kRequestedLevels;
auto response = client().send(request);
// Read request and process it in server.
context().OnStreamReadable();
loop().RunUntilNoTasks();
// Run client to receive response.
RunClient();
auto got = response.get();
EXPECT_FALSE(got.error);
EXPECT_EQ(got.response.totalFrames.value(), kTotalFrames);
size_t expected_frames = kTotalFrames - kStartFrame;
ASSERT_EQ(got.response.stackFrames.size(), expected_frames);
// Check the returned frames.
for (size_t i = 0; i < expected_frames; i++) {
int frame_index = i + kStartFrame;
EXPECT_EQ(got.response.stackFrames[i].column, locations[frame_index].column());
EXPECT_EQ(got.response.stackFrames[i].line, locations[frame_index].file_line().line());
EXPECT_EQ(got.response.stackFrames[i].name, functions[frame_index]->GetAssignedName());
EXPECT_EQ(got.response.stackFrames[i].source.value().path.value(),
temp_files[frame_index]->name());
}
}
TEST_F(RequestStackTraceTest, PaginationOutOfBoundsStartIndex) {
InitializeDebugging();
InjectProcessWithModule(kProcessKoid, 0x1000);
// Run client to receive process started event.
RunClient();
auto thread = InjectThread(kProcessKoid, kThreadKoid);
// Run client to receive threads started event.
RunClient();
// Insert mock frames
const int kTotalFrames = 5;
std::vector<std::unique_ptr<Frame>> frames;
std::vector<Location> locations;
std::vector<fxl::RefPtr<Function>> functions;
std::vector<std::unique_ptr<ScopedTempFile>> temp_files;
for (int i = 0; i < kTotalFrames; i++) {
temp_files.push_back(std::make_unique<ScopedTempFile>());
functions.push_back(fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram));
functions[i]->set_assigned_name("test_func" + std::to_string(i));
functions[i]->set_code_ranges(
AddressRanges(AddressRange(0x10000 + i * 0x20, 0x10020 + i * 0x20)));
locations.emplace_back(0x10010 + i * 0x20, FileLine(temp_files[i]->name(), 23 + i), 10 + i,
SymbolContext::ForRelativeAddresses(), functions[i]);
frames.push_back(
std::make_unique<MockFrame>(&session(), thread, locations[i], 0x2000 + i * 0x20));
}
InjectExceptionWithStack(kProcessKoid, kThreadKoid, debug_ipc::ExceptionType::kSingleStep,
std::move(frames), true);
// Receive thread stopped event in client.
RunClient();
// Send request from the client for a subset of frames.
dap::StackTraceRequest request = {};
request.threadId = kThreadKoid;
request.startFrame = 8;
request.levels = 0; // 0 means all frames
auto response = client().send(request);
// Read request and process it in server.
context().OnStreamReadable();
loop().RunUntilNoTasks();
// Run client to receive response.
RunClient();
auto got = response.get();
EXPECT_FALSE(got.error);
EXPECT_EQ(got.response.totalFrames.value(), kTotalFrames);
ASSERT_EQ(got.response.stackFrames.size(), 0u);
}
TEST_F(RequestStackTraceTest, ElideFrames) {
InitializeDebugging();
InjectProcessWithModule(kProcessKoid, 0x1000);
// Run client to receive process started event.
RunClient();
auto thread = InjectThread(kProcessKoid, kThreadKoid);
// Run client to receive threads started event.
RunClient();
std::vector<std::unique_ptr<Frame>> frames;
// Insert mock frames that will be grouped by the TestFailureStackMatcher.
frames.push_back(std::make_unique<MockFrame>(&session(), thread, 0x10160, 0x2100,
"fpromise::future_impl::operator()",
FileLine("fit/promise.h", 1174)));
frames.push_back(std::make_unique<MockFrame>(&session(), thread, 0x10140, 0x20E0,
"core::panicking::assert_failed<f64, f64>",
FileLine("panicking.rs", 394)));
// This frame should not be elided.
frames.push_back(std::make_unique<MockFrame>(&session(), thread, 0x100D0, 0x2080,
"foo::tests::foo_test", FileLine("foo.rs", 10)));
// Insert mock frames that will be grouped by different matchers.
frames.push_back(std::make_unique<MockFrame>(&session(), thread, 0x10120, 0x20C0,
"fpromise::future_impl::operator()",
FileLine("fit/promise.h", 1174)));
frames.push_back(std::make_unique<MockFrame>(&session(), thread, 0x10100, 0x20A0,
"fit::callback_impl::operator()",
FileLine("fit/function.h", 469)));
// This frame should not be elided.
frames.push_back(std::make_unique<MockFrame>(&session(), thread, 0x100D0, 0x2080,
"bar::tests::bar_test", FileLine("bar.rs", 10)));
// Insert mock frames that will be grouped by the "Rust test startup" matcher.
frames.push_back(std::make_unique<MockFrame>(&session(), thread, 0x100A0, 0x2060,
"test::__rust_begin_short_backtrace<FOO_BAR_BAZ>",
FileLine("unknown.rs", 648)));
frames.push_back(std::make_unique<MockFrame>(&session(), thread, 0x10070, 0x2040,
"arbitrary::glob_elided::function",
FileLine("anything.rs", 1)));
frames.push_back(std::make_unique<MockFrame>(&session(), thread, 0x10040, 0x2020,
"__libc_start_main",
FileLine("__libc_start_main.c", 23)));
frames.push_back(std::make_unique<MockFrame>(&session(), thread, 0x10010, 0x2000, "_start",
FileLine("start.S", 55)));
InjectExceptionWithStack(kProcessKoid, kThreadKoid, debug_ipc::ExceptionType::kSingleStep,
std::move(frames), true);
// Receive thread stopped event in client.
RunClient();
// Send request from the client.
dap::StackTraceRequest request = {};
request.threadId = kThreadKoid;
auto response = client().send(request);
// Read request and process it in server.
context().OnStreamReadable();
loop().RunUntilNoTasks();
// Run client to receive response.
RunClient();
auto got = response.get();
ASSERT_FALSE(got.error);
ASSERT_EQ(got.response.totalFrames.value(), 10);
// Frame 0: Test assertion impl (elided)
EXPECT_EQ(got.response.stackFrames[0].name, "fpromise::future_impl::operator()");
EXPECT_EQ(got.response.stackFrames[0].presentationHint.value(), "subtle");
EXPECT_EQ(got.response.stackFrames[0].source.value().origin.value(),
"Test assertion implementation");
// Frame 1: Test assertion impl (elided)
EXPECT_EQ(got.response.stackFrames[1].name, "core::panicking::assert_failed<f64, f64>");
EXPECT_EQ(got.response.stackFrames[1].presentationHint.value(), "subtle");
EXPECT_EQ(got.response.stackFrames[1].source.value().origin.value(),
"Test assertion implementation");
// Frame 2: foo_test
EXPECT_EQ(got.response.stackFrames[2].name, "foo::tests::foo_test");
EXPECT_FALSE(got.response.stackFrames[2].presentationHint.has_value());
EXPECT_FALSE(got.response.stackFrames[2].source.value().origin.has_value());
// Frame 3: fpromise::promise code (elided)
EXPECT_EQ(got.response.stackFrames[3].name, "fpromise::future_impl::operator()");
EXPECT_EQ(got.response.stackFrames[3].presentationHint.value(), "subtle");
EXPECT_EQ(got.response.stackFrames[3].source.value().origin.value(), "fpromise::promise code");
// Frame 4: fit::function code (elided)
EXPECT_EQ(got.response.stackFrames[4].name, "fit::callback_impl::operator()");
EXPECT_EQ(got.response.stackFrames[4].presentationHint.value(), "subtle");
EXPECT_EQ(got.response.stackFrames[4].source.value().origin.value(), "fit::function code");
// Frame 5: bar_test
EXPECT_EQ(got.response.stackFrames[5].name, "bar::tests::bar_test");
EXPECT_FALSE(got.response.stackFrames[5].presentationHint.has_value());
EXPECT_FALSE(got.response.stackFrames[5].source.value().origin.has_value());
// Frame 6: Rust test startup (elided)
EXPECT_EQ(got.response.stackFrames[6].name, "test::__rust_begin_short_backtrace<FOO_BAR_BAZ>");
EXPECT_EQ(got.response.stackFrames[6].presentationHint.value(), "subtle");
EXPECT_EQ(got.response.stackFrames[6].source.value().origin.value(), "Rust test startup");
// Frame 7: Rust test startup (elided)
EXPECT_EQ(got.response.stackFrames[7].name, "arbitrary::glob_elided::function");
EXPECT_EQ(got.response.stackFrames[7].presentationHint.value(), "subtle");
EXPECT_EQ(got.response.stackFrames[7].source.value().origin.value(), "Rust test startup");
// Frame 8: Rust test startup (elided)
EXPECT_EQ(got.response.stackFrames[8].name, "__libc_start_main");
EXPECT_EQ(got.response.stackFrames[8].presentationHint.value(), "subtle");
EXPECT_EQ(got.response.stackFrames[8].source.value().origin.value(), "Rust test startup");
// Frame 9: Rust test startup (elided)
EXPECT_EQ(got.response.stackFrames[9].name, "_start");
EXPECT_EQ(got.response.stackFrames[9].presentationHint.value(), "subtle");
EXPECT_EQ(got.response.stackFrames[9].source.value().origin.value(), "Rust test startup");
}
} // namespace zxdb