blob: 756aa7d0fdd64cc889a41ee396fdb2f785921419 [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/process_impl.h"
#include <gtest/gtest.h>
#include "src/developer/debug/zxdb/client/memory_dump.h"
#include "src/developer/debug/zxdb/client/mock_remote_api.h"
#include "src/developer/debug/zxdb/client/remote_api_test.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/target_impl.h"
#include "src/developer/debug/zxdb/symbols/elf_symbol.h"
#include "src/developer/debug/zxdb/symbols/mock_module_symbols.h"
namespace zxdb {
namespace {
class ProcessSink : public MockRemoteAPI {
public:
ProcessSink() = default;
~ProcessSink() = default;
const debug_ipc::ResumeRequest& resume_request() const { return resume_request_; }
int resume_count() const { return resume_count_; }
void Resume(const debug_ipc::ResumeRequest& request,
fit::callback<void(const Err&, debug_ipc::ResumeReply)> cb) override {
resume_count_++;
resume_request_ = request;
debug::MessageLoop::Current()->PostTask(
FROM_HERE, [cb = std::move(cb)]() mutable { cb(Err(), debug_ipc::ResumeReply()); });
}
// No-op.
void Threads(const debug_ipc::ThreadsRequest& request,
fit::callback<void(const Err&, debug_ipc::ThreadsReply)> cb) override {
thread_request_made_ = true;
}
bool thread_request_made() const { return thread_request_made_; }
private:
debug_ipc::ResumeRequest resume_request_;
int resume_count_ = 0;
bool thread_request_made_ = false;
};
class ProcessImplTest : public RemoteAPITest {
public:
ProcessImplTest() = default;
~ProcessImplTest() override = default;
ProcessSink* sink() { return sink_; }
private:
std::unique_ptr<RemoteAPI> GetRemoteAPIImpl() override {
auto sink = std::make_unique<ProcessSink>();
sink_ = sink.get();
return sink;
}
private:
ProcessSink* sink_; // Owned by the session.
};
} // namespace
// Tests that the correct threads are resumed after the modules are loaded.
TEST_F(ProcessImplTest, OnModules) {
constexpr uint64_t kProcessKoid = 1234;
Process* process = InjectProcess(kProcessKoid);
ASSERT_TRUE(process);
EXPECT_FALSE(sink()->thread_request_made());
debug_ipc::NotifyModules notify;
notify.process_koid = kProcessKoid;
notify.modules.resize(1);
notify.modules[0].name = "comctl32.dll";
notify.modules[0].base = 0x7685348234;
constexpr uint64_t kThread1Koid = 237645;
constexpr uint64_t kThread2Koid = 809712;
notify.stopped_threads.push_back({.process = kProcessKoid, .thread = kThread1Koid});
notify.stopped_threads.push_back({.process = kProcessKoid, .thread = kThread2Koid});
session().DispatchNotifyModules(notify);
EXPECT_TRUE(sink()->thread_request_made());
// Should have resumed both of those threads.
ASSERT_EQ(1, sink()->resume_count());
const debug_ipc::ResumeRequest& resume = sink()->resume_request();
EXPECT_EQ(debug_ipc::ResumeRequest::How::kResolveAndContinue, resume.how);
EXPECT_EQ(notify.stopped_threads, resume.ids);
}
TEST_F(ProcessImplTest, GetTLSHelpers) {
constexpr uint64_t kProcessKoid = 1234;
constexpr uint64_t kThreadKoid = 5678;
constexpr char kBuildId[] = "abcd";
constexpr uint64_t kModuleBase = 0x1000;
Process* process = InjectProcess(kProcessKoid);
ASSERT_TRUE(process);
Thread* thread = InjectThread(kProcessKoid, kThreadKoid);
ASSERT_TRUE(thread);
SymbolContext symbol_context(kModuleBase);
auto module_symbols = fxl::MakeRefCounted<MockModuleSymbols>("libc.so");
session().system().GetSymbols()->InjectModuleForTesting(kBuildId, module_symbols.get());
std::vector<debug_ipc::Module> modules;
debug_ipc::Module module;
module.name = "libc";
module.base = kModuleBase;
module.build_id = kBuildId;
modules.push_back(module);
TargetImpl* target = session().system().GetTargetImpls()[0];
target->process()->OnModules(modules, {});
constexpr uint64_t kThrdTAddr = 0x1000;
constexpr uint64_t kLinkMapTlsModIdAddr = 0x2000;
constexpr uint64_t kTlsBaseAddr = 0x3000;
constexpr uint64_t kSymSize = 4;
ElfSymbolRecord thrd_t_record(ElfSymbolType::kNormal, kThrdTAddr, kSymSize, "zxdb.thrd_t");
ElfSymbolRecord link_map_tls_modid_record(ElfSymbolType::kNormal, kLinkMapTlsModIdAddr, kSymSize,
"zxdb.link_map_tls_modid");
ElfSymbolRecord tlsbase_record(ElfSymbolType::kNormal, kTlsBaseAddr, kSymSize, "zxdb.tlsbase");
auto thrd_t_sym = fxl::MakeRefCounted<ElfSymbol>(module_symbols->GetWeakPtr(), thrd_t_record);
auto link_map_tls_modid_sym =
fxl::MakeRefCounted<ElfSymbol>(module_symbols->GetWeakPtr(), link_map_tls_modid_record);
auto tlsbase_sym = fxl::MakeRefCounted<ElfSymbol>(module_symbols->GetWeakPtr(), tlsbase_record);
module_symbols->AddSymbolLocations(
Identifier(IdentifierComponent(SpecialIdentifier::kElf, "zxdb.thrd_t")),
{Location(kThrdTAddr + kModuleBase, FileLine(), 0, symbol_context, thrd_t_sym)});
module_symbols->AddSymbolLocations(
Identifier(IdentifierComponent(SpecialIdentifier::kElf, "zxdb.link_map_tls_modid")),
{Location(kLinkMapTlsModIdAddr + kModuleBase, FileLine(), 0, symbol_context,
link_map_tls_modid_sym)});
module_symbols->AddSymbolLocations(
Identifier(IdentifierComponent(SpecialIdentifier::kElf, "zxdb.tlsbase")),
{Location(kTlsBaseAddr + kModuleBase, FileLine(), 0, symbol_context, tlsbase_sym)});
sink()->AddMemory(kThrdTAddr + kModuleBase, {0x1, 0x2, 0x3, 0x4});
sink()->AddMemory(kLinkMapTlsModIdAddr + kModuleBase, {0x5, 0x6, 0x7, 0x8});
sink()->AddMemory(kTlsBaseAddr + kModuleBase, {0x9, 0xa, 0xb, 0xc});
bool called = false;
bool called_2 = false;
Process::TLSHelpers helpers;
Process::TLSHelpers helpers_2;
Err err;
Err err_2;
process->GetTLSHelpers([&called, &helpers, &err](ErrOr<const Process::TLSHelpers*> got) {
called = true;
if (got.has_error()) {
err = got.err();
} else {
helpers = *got.value();
}
});
process->GetTLSHelpers([&called_2, &helpers_2, &err_2](ErrOr<const Process::TLSHelpers*> got) {
called_2 = true;
if (got.has_error()) {
err_2 = got.err();
} else {
helpers_2 = *got.value();
}
});
EXPECT_FALSE(called);
EXPECT_FALSE(called_2);
loop().RunUntilNoTasks();
EXPECT_TRUE(called);
EXPECT_TRUE(called_2);
ASSERT_FALSE(err.has_error()) << err.msg();
EXPECT_FALSE(err_2.has_error());
ASSERT_EQ(helpers.thrd_t.size(), kSymSize);
ASSERT_EQ(helpers.link_map_tls_modid.size(), kSymSize);
ASSERT_EQ(helpers.tlsbase.size(), kSymSize);
EXPECT_EQ(0x1, helpers.thrd_t[0]);
EXPECT_EQ(0x2, helpers.thrd_t[1]);
EXPECT_EQ(0x3, helpers.thrd_t[2]);
EXPECT_EQ(0x4, helpers.thrd_t[3]);
EXPECT_EQ(0x5, helpers.link_map_tls_modid[0]);
EXPECT_EQ(0x6, helpers.link_map_tls_modid[1]);
EXPECT_EQ(0x7, helpers.link_map_tls_modid[2]);
EXPECT_EQ(0x8, helpers.link_map_tls_modid[3]);
EXPECT_EQ(0x9, helpers.tlsbase[0]);
EXPECT_EQ(0xa, helpers.tlsbase[1]);
EXPECT_EQ(0xb, helpers.tlsbase[2]);
EXPECT_EQ(0xc, helpers.tlsbase[3]);
EXPECT_EQ(0x1, helpers_2.thrd_t[0]);
EXPECT_EQ(0x2, helpers_2.thrd_t[1]);
EXPECT_EQ(0x3, helpers_2.thrd_t[2]);
EXPECT_EQ(0x4, helpers_2.thrd_t[3]);
EXPECT_EQ(0x5, helpers_2.link_map_tls_modid[0]);
EXPECT_EQ(0x6, helpers_2.link_map_tls_modid[1]);
EXPECT_EQ(0x7, helpers_2.link_map_tls_modid[2]);
EXPECT_EQ(0x8, helpers_2.link_map_tls_modid[3]);
EXPECT_EQ(0x9, helpers_2.tlsbase[0]);
EXPECT_EQ(0xa, helpers_2.tlsbase[1]);
EXPECT_EQ(0xb, helpers_2.tlsbase[2]);
EXPECT_EQ(0xc, helpers_2.tlsbase[3]);
called = false;
process->GetTLSHelpers([&called, &helpers, &err](ErrOr<const Process::TLSHelpers*> got) {
called = true;
if (got.has_error()) {
err = got.err();
} else {
helpers = *got.value();
}
});
EXPECT_TRUE(called);
ASSERT_FALSE(err.has_error()) << err.msg();
ASSERT_EQ(helpers.thrd_t.size(), kSymSize);
ASSERT_EQ(helpers.link_map_tls_modid.size(), kSymSize);
ASSERT_EQ(helpers.tlsbase.size(), kSymSize);
EXPECT_EQ(0x1, helpers.thrd_t[0]);
EXPECT_EQ(0x2, helpers.thrd_t[1]);
EXPECT_EQ(0x3, helpers.thrd_t[2]);
EXPECT_EQ(0x4, helpers.thrd_t[3]);
EXPECT_EQ(0x5, helpers.link_map_tls_modid[0]);
EXPECT_EQ(0x6, helpers.link_map_tls_modid[1]);
EXPECT_EQ(0x7, helpers.link_map_tls_modid[2]);
EXPECT_EQ(0x8, helpers.link_map_tls_modid[3]);
EXPECT_EQ(0x9, helpers.tlsbase[0]);
EXPECT_EQ(0xa, helpers.tlsbase[1]);
EXPECT_EQ(0xb, helpers.tlsbase[2]);
EXPECT_EQ(0xc, helpers.tlsbase[3]);
}
TEST_F(ProcessImplTest, ReadMemoryAutomation) {
constexpr uint64_t kProcessKoid = 1234;
constexpr uint64_t kThreadKoid = 5678;
constexpr uint64_t kBlockAddress = 0xdeadbeef;
constexpr uint64_t kBlockSize = 8;
Process* process = InjectProcess(kProcessKoid);
ASSERT_TRUE(process);
ProcessImpl* process_impl = session().system().ProcessImplFromKoid(process->GetKoid());
ASSERT_TRUE(process_impl);
// This creates the memory block to mimic getting a response from an automated breakpoint.
debug_ipc::MemoryBlock mem_block = {.address = kBlockAddress, .valid = true, .size = kBlockSize};
mem_block.data = {0, 1, 2, 3, 4, 5, 6, 7};
debug_ipc::MemoryBlock invalid_mem_block = {
.address = kBlockAddress + 32, .valid = false, .size = kBlockSize};
invalid_mem_block.data = {10, 11, 12, 13, 14, 15, 16, 17};
std::vector<debug_ipc::MemoryBlock> mem_block_vect = {mem_block, invalid_mem_block};
process_impl->SetMemoryBlocks(kThreadKoid, mem_block_vect);
// This puts some memory on the mock remote device such that if the read misses the memory block
// it'll read from this array.
sink()->AddMemory(kBlockAddress - 4, {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115});
// Attempt to read the memory block. The read request must be for a subset of one of the blocks
// returned for it to hit the memory block, otherwise it falls through to the remote device.
process_impl->ReadMemory(
kBlockAddress, kBlockSize, [](const zxdb::Err& err, zxdb::MemoryDump dump) {
ASSERT_EQ(dump.blocks()[0].data, std::vector<uint8_t>({0, 1, 2, 3, 4, 5, 6, 7}));
ASSERT_TRUE(dump.blocks()[0].valid);
});
// Read with a lower address, which should fall through to the remote device.
process_impl->ReadMemory(kBlockAddress - 2, kBlockSize,
[](const zxdb::Err& err, zxdb::MemoryDump dump) {
ASSERT_EQ(dump.blocks()[0].data, std::vector<uint8_t>({
102,
103,
104,
105,
106,
107,
108,
109,
}));
ASSERT_TRUE(dump.blocks()[0].valid);
});
// Read with a too large size, which should also fall through to the remote device.
process_impl->ReadMemory(kBlockAddress, kBlockSize + 2,
[](const zxdb::Err& err, zxdb::MemoryDump dump) {
ASSERT_EQ(dump.blocks()[0].data, std::vector<uint8_t>({
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
}));
ASSERT_TRUE(dump.blocks()[0].valid);
});
// Read a subset of the block, which should not fall through.
process_impl->ReadMemory(kBlockAddress + 2, kBlockSize - 4,
[](const zxdb::Err& err, zxdb::MemoryDump dump) {
ASSERT_EQ(dump.blocks()[0].data, std::vector<uint8_t>({
2,
3,
4,
5,
}));
ASSERT_TRUE(dump.blocks()[0].valid);
});
// Read a subset from the invalid block, which should not fall through, and also should not return
// any data in the blocks.
process_impl->ReadMemory(kBlockAddress + 32, kBlockSize - 2,
[](const zxdb::Err& err, zxdb::MemoryDump dump) {
ASSERT_EQ(dump.blocks()[0].data.size(), 0ul);
ASSERT_FALSE(dump.blocks()[0].valid);
});
loop().RunUntilNoTasks();
}
} // namespace zxdb