blob: 42a2bbecbd689575a21d49b9e9dac6ded5f8154b [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/console/format_context.h"
#include "gtest/gtest.h"
#include "src/developer/debug/zxdb/client/arch_info.h"
#include "src/developer/debug/zxdb/client/memory_dump.h"
#include "src/developer/debug/zxdb/client/mock_process.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/symbols/mock_module_symbols.h"
#include "src/developer/debug/zxdb/symbols/mock_source_file_provider.h"
#include "src/developer/debug/zxdb/symbols/process_symbols_test_setup.h"
namespace zxdb {
static const char kSimpleProgram[] =
R"(#include "foo.h"
int main(int argc, char** argv) {
printf("Hello, world");
return 1;
}
)";
TEST(FormatContext, FormatSourceContext) {
FormatSourceOpts opts;
opts.first_line = 2;
opts.last_line = 6;
opts.active_line = 4;
opts.highlight_line = 4;
opts.highlight_column = 11;
OutputBuffer out;
ASSERT_FALSE(FormatSourceContext("file", kSimpleProgram, opts, &out).has_error());
EXPECT_EQ(
" 2 \n"
" 3 int main(int argc, char** argv) {\n"
" ▶ 4 printf(\"Hello, world\");\n"
" 5 return 1;\n"
" 6 }\n",
out.AsString());
}
TEST(FormatContext, FormatSourceContext_OffBeginning) {
FormatSourceOpts opts;
opts.first_line = 0;
opts.last_line = 4;
opts.active_line = 2;
opts.highlight_line = 2;
opts.highlight_column = 11;
OutputBuffer out;
// This column is off the end of line two, and the context has one less line at the beginning
// because it hit the top of the file.
ASSERT_FALSE(FormatSourceContext("file", kSimpleProgram, opts, &out).has_error());
EXPECT_EQ(
" 1 #include \"foo.h\"\n"
" ▶ 2 \n"
" 3 int main(int argc, char** argv) {\n"
" 4 printf(\"Hello, world\");\n",
out.AsString());
}
TEST(FormatContext, FormatSourceContext_OffEnd) {
FormatSourceOpts opts;
opts.first_line = 4;
opts.last_line = 8;
opts.active_line = 6;
opts.highlight_line = 6;
opts.highlight_column = 6;
OutputBuffer out;
// This column is off the end of line two, and the context has one less line at the beginning
// because it hit the top of the file.
ASSERT_FALSE(FormatSourceContext("file", kSimpleProgram, opts, &out).has_error());
EXPECT_EQ(
" 4 printf(\"Hello, world\");\n"
" 5 return 1;\n"
" ▶ 6 }\n",
out.AsString());
}
TEST(FormatContext, FormatSourceContext_LineOffEnd) {
FormatSourceOpts opts;
opts.first_line = 0;
opts.last_line = 100;
opts.active_line = 10; // This line is off the end of the input.
opts.highlight_line = 10;
opts.require_active_line = true;
OutputBuffer out;
Err err = FormatSourceContext("file.cc", kSimpleProgram, opts, &out);
ASSERT_TRUE(err.has_error());
EXPECT_EQ("There is no line 10 in the file file.cc", err.msg());
}
TEST(FormatContext, FormatAsmContext) {
ArchInfo arch;
Err err = arch.Init(debug_ipc::Arch::kX64);
ASSERT_FALSE(err.has_error());
// Make a little memory dump.
constexpr uint64_t start_address = 0x123456780;
debug_ipc::MemoryBlock block_with_data;
block_with_data.address = start_address;
block_with_data.valid = true;
block_with_data.data = std::vector<uint8_t>{
0xbf, 0xe0, 0xe5, 0x28, 0x00, // mov edi, 0x28e5e0
0x48, 0x89, 0xde, // mov rsi, rbx
0x48, 0x8d, 0x7c, 0x24, 0x0c, // lea rdi, [rsp + 0xc]
0xe8, 0xce, 0x00, 0x00, 0x00 // call +0xce (relative to next instruction).
};
block_with_data.size = static_cast<uint32_t>(block_with_data.data.size());
MemoryDump dump(std::vector<debug_ipc::MemoryBlock>({block_with_data}));
FormatAsmOpts opts;
opts.emit_addresses = true;
opts.emit_bytes = false;
opts.active_address = 0x123456785;
opts.max_instructions = 100;
opts.include_source = false;
opts.bp_addrs[start_address] = true;
OutputBuffer out;
err = FormatAsmContext(&arch, dump, opts, nullptr, SourceFileProvider(), &out);
ASSERT_FALSE(err.has_error());
EXPECT_EQ(
" ◉ 0x123456780 mov edi, 0x28e5e0 \n"
" ▶ 0x123456785 mov rsi, rbx \n"
" 0x123456788 lea rdi, [rsp + 0xc] \n"
" 0x12345678d call 0xce ➔ 0x123456860\n",
out.AsString());
// Try again with source bytes and a disabled breakpoint on the same line as
// the active address.
out = OutputBuffer();
opts.emit_bytes = true;
opts.bp_addrs.clear();
opts.bp_addrs[opts.active_address] = false;
err = FormatAsmContext(&arch, dump, opts, nullptr, SourceFileProvider(), &out);
ASSERT_FALSE(err.has_error());
EXPECT_EQ(
" 0x123456780 bf e0 e5 28 00 mov edi, 0x28e5e0 \n"
"◯▶ 0x123456785 48 89 de mov rsi, rbx \n"
" 0x123456788 48 8d 7c 24 0c lea rdi, [rsp + 0xc] \n"
" 0x12345678d e8 ce 00 00 00 call 0xce ➔ 0x123456860\n",
out.AsString());
// Combined source/assembly.
out = OutputBuffer();
opts.emit_bytes = false;
opts.include_source = true;
opts.bp_addrs.clear();
// Source code.
MockSourceFileProvider file_provider;
const char kFileName[] = "file.cc";
file_provider.SetFileData(
kFileName, SourceFileProvider::FileData("// Copyright\n" // Line 1.
"\n" // Line 2.
"int main() {\n" // Line 3.
" printf(\"Hello, world.\");\n" // Line 4.
" return 0;\n" // Line 5.
"}\n", // Line 6.
kFileName, 0));
// Process setup for mocking the symbol requests.
ProcessSymbolsTestSetup symbols;
MockModuleSymbols* module_symbols = symbols.InjectMockModule();
SymbolContext symbol_context(ProcessSymbolsTestSetup::kDefaultLoadAddress);
Session session;
MockProcess process(&session);
process.set_symbols(&symbols.process());
// Setup address-to-source mapping. These must match the addresses in the assembly. Line 4
// maps to two addresses.
module_symbols->AddSymbolLocations(
0x123456780, {Location(0x123456780, FileLine(kFileName, 4), 0, symbol_context)});
module_symbols->AddSymbolLocations(
0x123456785, {Location(0x123456785, FileLine(kFileName, 4), 0, symbol_context)});
module_symbols->AddSymbolLocations(
0x123456788, {Location(0x123456788, FileLine(kFileName, 5), 0, symbol_context)});
err = FormatAsmContext(&arch, dump, opts, &process, file_provider, &out);
ASSERT_FALSE(err.has_error());
EXPECT_EQ(
" 1 // Copyright\n"
" 2 \n"
" 3 int main() {\n"
" 4 printf(\"Hello, world.\");\n"
" 0x123456780 mov edi, 0x28e5e0 \n"
" ▶ 0x123456785 mov rsi, rbx \n"
" 5 return 0;\n"
" 0x123456788 lea rdi, [rsp + 0xc] \n"
" 0x12345678d call 0xce ➔ 0x123456860\n",
out.AsString());
}
TEST(FormatContext, FormatSourceFileContext_FileName) {
const char kFileName[] = "file.cc";
const char kFilePath[] = "/home/me/fuchsia/file.cc";
MockSourceFileProvider file_provider;
file_provider.SetFileData(kFileName, SourceFileProvider::FileData("Line 1\n"
"Line 2\n"
"Line 3\n"
"Line 4\n"
"Line 5\n",
kFilePath, 0));
FormatSourceOpts opts;
opts.show_file_name = true;
opts.first_line = 2;
opts.last_line = 4;
opts.active_line = 3;
opts.highlight_line = 3;
opts.highlight_column = 0;
OutputBuffer out;
Err err = FormatSourceFileContext(FileLine(kFileName, 3), file_provider, opts, &out);
ASSERT_TRUE(err.ok()) << err.msg();
EXPECT_EQ(
"📄 /home/me/fuchsia/file.cc\n"
" 2 Line 2\n"
" ▶ 3 Line 3\n"
" 4 Line 4\n",
out.AsString());
}
TEST(FormatContext, FormatSourceFileContext_Stale) {
constexpr std::size_t kFileTime = 10000000;
const char kFileName[] = "file.cc";
MockSourceFileProvider file_provider;
file_provider.SetFileData(kFileName,
SourceFileProvider::FileData(kSimpleProgram, kFileName, kFileTime));
auto mod_sym = fxl::MakeRefCounted<MockModuleSymbols>("file.so");
// Report build good (module is newer than source file.
mod_sym->set_modification_time(kFileTime + 10);
FormatSourceOpts opts;
opts.first_line = 2;
opts.last_line = 6;
opts.active_line = 4;
opts.highlight_line = 4;
opts.highlight_column = 11;
opts.module_for_time_warning = mod_sym->GetWeakPtr();
std::string expected_code =
" 2 \n"
" 3 int main(int argc, char** argv) {\n"
" ▶ 4 printf(\"Hello, world\");\n"
" 5 return 1;\n"
" 6 }\n";
// Should not give a warning.
OutputBuffer out;
ASSERT_FALSE(
FormatSourceFileContext(FileLine(kFileName, 4), file_provider, opts, &out).has_error());
EXPECT_EQ(expected_code, out.AsString());
// Say the module is older. This should give a warning.
mod_sym->set_modification_time(kFileTime - 10);
out = OutputBuffer();
ASSERT_FALSE(
FormatSourceFileContext(FileLine(kFileName, 4), file_provider, opts, &out).has_error());
EXPECT_EQ(
"⚠️ Warning: Source file is newer than the binary. The build may be out-of-date.\n" +
expected_code,
out.AsString());
// Doing the same file again should not give a warning. Each file should be warned about once.
out = OutputBuffer();
ASSERT_FALSE(
FormatSourceFileContext(FileLine(kFileName, 4), file_provider, opts, &out).has_error());
EXPECT_EQ(expected_code, out.AsString());
}
} // namespace zxdb