From 4bd169875969a796270a900c73d4b4ec4c9b3661 Mon Sep 17 00:00:00 2001 From: Zach Ghera Date: Mon, 19 Jul 2021 21:33:01 +0000 Subject: OfflineUnwindUtils: Support consecutive unwinds. This CL refactors OfflineUnwindUtils to allow clients to perform multiple/consecutive unwinds. In the previous version of OfflineUnwindUtils, a client would need to create multiple OfflineUnwindUtils instances in order to test or benchmark offline unwinds. This would require modifying the existing test/benchmark fixtures and/or each of the individual test/benchmarks to support this change. This change simply adds a new Init overload for consecutive offline unwinds and leaves the remainder of the public API the same. Consecutive unwinds are necessary for profiler-like offline unwind benchmarks. See b/192012600 for more for more information regarding these benchmarks. Bug: 192012600 Test: Refactoring CL. Benchmarks still run and unit tests still pass. Change-Id: I5ebb8606eaf4bf144690de41283619218108e3b8 --- .../benchmarks/OfflineUnwindBenchmarks.cpp | 46 ++- libunwindstack/tests/UnwindOfflineTest.cpp | 98 +++--- libunwindstack/utils/OfflineUnwindUtils.cpp | 335 +++++++++++++++------ libunwindstack/utils/OfflineUnwindUtils.h | 103 +++++-- 4 files changed, 390 insertions(+), 192 deletions(-) (limited to 'libunwindstack') diff --git a/libunwindstack/benchmarks/OfflineUnwindBenchmarks.cpp b/libunwindstack/benchmarks/OfflineUnwindBenchmarks.cpp index b06c5ee..52525d8 100644 --- a/libunwindstack/benchmarks/OfflineUnwindBenchmarks.cpp +++ b/libunwindstack/benchmarks/OfflineUnwindBenchmarks.cpp @@ -42,47 +42,45 @@ class OfflineUnwindBenchmark : public benchmark::Fixture { } void RunBenchmark(benchmark::State& state, const std::string& offline_files_dir, - size_t expected_num_frames, ArchEnum arch, bool cache_maps, bool is_jit_debug) { + size_t expected_num_frames, ArchEnum arch, ProcessMemoryFlag memory_flag, + bool cache_maps = false) { std::string error_msg; - if (!offline_utils_.Init(offline_files_dir, arch, error_msg, /*add_stack=*/true, cache_maps)) { - state.SkipWithError(error_msg.c_str()); - return; - } - if (is_jit_debug && !offline_utils_.SetJitProcessMemory(error_msg)) { + if (!offline_utils_.Init(offline_files_dir, arch, &error_msg, memory_flag, cache_maps)) { state.SkipWithError(error_msg.c_str()); return; } - std::unique_ptr jit_debug; - std::stringstream err_stream; - Unwinder unwinder(0, nullptr, nullptr); for (auto _ : state) { state.PauseTiming(); // Need to init unwinder with new copy of regs each iteration because unwinding changes // the attributes of the regs object. std::unique_ptr regs_copy(offline_utils_.GetRegs()->Clone()); - // If we don't want to use cached maps, make sure to reset them. - if (!cache_maps && !offline_utils_.ResetMaps(error_msg)) { - state.SkipWithError(error_msg.c_str()); - return; + // The Maps object will still hold the parsed maps from the previous unwinds. So reset them + // unless we want to assume all Maps are cached. + if (!cache_maps) { + if (!offline_utils_.CreateMaps(&error_msg)) { + state.SkipWithError(error_msg.c_str()); + return; + } } mem_tracker_.StartTrackingAllocations(); state.ResumeTiming(); - std::shared_ptr process_memory = offline_utils_.GetProcessMemory(); - unwinder = Unwinder(128, offline_utils_.GetMaps(), regs_copy.get(), process_memory); - if (is_jit_debug) { - jit_debug = CreateJitDebug(regs_copy->Arch(), process_memory); - unwinder.SetJitDebug(jit_debug.get()); + Unwinder unwinder = Unwinder(128, offline_utils_.GetMaps(), regs_copy.get(), + offline_utils_.GetProcessMemory()); + if (memory_flag == ProcessMemoryFlag::kIncludeJitMemory) { + unwinder.SetJitDebug(offline_utils_.GetJitDebug()); } unwinder.Unwind(); state.PauseTiming(); mem_tracker_.StopTrackingAllocations(); if (unwinder.NumFrames() != expected_num_frames) { + std::stringstream err_stream; err_stream << "Failed to unwind properly.Expected " << expected_num_frames << " frames, but unwinder contained " << unwinder.NumFrames() << " frames.\n"; - break; + state.SkipWithError(err_stream.str().c_str()); + return; } state.ResumeTiming(); } @@ -95,18 +93,18 @@ class OfflineUnwindBenchmark : public benchmark::Fixture { BENCHMARK_F(OfflineUnwindBenchmark, BM_offline_straddle_arm64)(benchmark::State& state) { RunBenchmark(state, "straddle_arm64/", /*expected_num_frames=*/6, ARCH_ARM64, - /*cached_maps=*/false, /*is_jit_debug=*/false); + ProcessMemoryFlag::kNone); } BENCHMARK_F(OfflineUnwindBenchmark, BM_offline_straddle_arm64_cached_maps) (benchmark::State& state) { RunBenchmark(state, "straddle_arm64/", /*expected_num_frames=*/6, ARCH_ARM64, - /*cached_maps=*/true, /*is_jit_debug=*/false); + ProcessMemoryFlag::kNone, /*cached_maps=*/true); } -BENCHMARK_F(OfflineUnwindBenchmark, BM_offline_jit_debug_x86)(benchmark::State& state) { - RunBenchmark(state, "jit_debug_x86/", /*expected_num_frames=*/69, ARCH_X86, - /*cached_maps=*/false, /*is_jit_debug=*/true); +BENCHMARK_F(OfflineUnwindBenchmark, BM_offline_jit_debug_arm)(benchmark::State& state) { + RunBenchmark(state, "jit_debug_arm/", /*expected_num_frames=*/76, ARCH_ARM, + ProcessMemoryFlag::kIncludeJitMemory); } } // namespace diff --git a/libunwindstack/tests/UnwindOfflineTest.cpp b/libunwindstack/tests/UnwindOfflineTest.cpp index 2261449..9d09aa3 100644 --- a/libunwindstack/tests/UnwindOfflineTest.cpp +++ b/libunwindstack/tests/UnwindOfflineTest.cpp @@ -44,7 +44,7 @@ class UnwindOfflineTest : public ::testing::Test { TEST_F(UnwindOfflineTest, pc_straddle_arm) { std::string error_msg; - if (!offline_utils_.Init("straddle_arm/", ARCH_ARM, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("straddle_arm/", ARCH_ARM, &error_msg)) FAIL() << error_msg; Regs* regs = offline_utils_.GetRegs(); std::unique_ptr regs_copy(regs->Clone()); @@ -87,7 +87,7 @@ TEST_F(UnwindOfflineTest, pc_straddle_arm) { TEST_F(UnwindOfflineTest, pc_in_gnu_debugdata_arm) { std::string error_msg; - if (!offline_utils_.Init("gnu_debugdata_arm/", ARCH_ARM, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("gnu_debugdata_arm/", ARCH_ARM, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -110,7 +110,7 @@ TEST_F(UnwindOfflineTest, pc_in_gnu_debugdata_arm) { TEST_F(UnwindOfflineTest, pc_straddle_arm64) { std::string error_msg; - if (!offline_utils_.Init("straddle_arm64/", ARCH_ARM64, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("straddle_arm64/", ARCH_ARM64, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -144,15 +144,13 @@ TEST_F(UnwindOfflineTest, pc_straddle_arm64) { TEST_F(UnwindOfflineTest, jit_debug_x86) { std::string error_msg; - if (!offline_utils_.Init("jit_debug_x86/", ARCH_X86, error_msg)) FAIL() << error_msg; - - if (!offline_utils_.SetJitProcessMemory(error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("jit_debug_x86/", ARCH_X86, &error_msg, + ProcessMemoryFlag::kIncludeJitMemory)) + FAIL() << error_msg; - Regs* regs = offline_utils_.GetRegs(); - std::shared_ptr process_memory = offline_utils_.GetProcessMemory(); - std::unique_ptr jit_debug = CreateJitDebug(regs->Arch(), process_memory); - Unwinder unwinder(128, offline_utils_.GetMaps(), regs, process_memory); - unwinder.SetJitDebug(jit_debug.get()); + Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), + offline_utils_.GetProcessMemory()); + unwinder.SetJitDebug(offline_utils_.GetJitDebug()); unwinder.Unwind(); std::string frame_info(DumpFrames(unwinder)); @@ -441,15 +439,13 @@ TEST_F(UnwindOfflineTest, jit_debug_x86) { TEST_F(UnwindOfflineTest, jit_debug_arm) { std::string error_msg; - if (!offline_utils_.Init("jit_debug_arm/", ARCH_ARM, error_msg)) FAIL() << error_msg; - - if (!offline_utils_.SetJitProcessMemory(error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("jit_debug_arm/", ARCH_ARM, &error_msg, + ProcessMemoryFlag::kIncludeJitMemory)) + FAIL() << error_msg; - Regs* regs = offline_utils_.GetRegs(); - std::shared_ptr process_memory = offline_utils_.GetProcessMemory(); - std::unique_ptr jit_debug = CreateJitDebug(regs->Arch(), process_memory); - Unwinder unwinder(128, offline_utils_.GetMaps(), regs, process_memory); - unwinder.SetJitDebug(jit_debug.get()); + Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), + offline_utils_.GetProcessMemory()); + unwinder.SetJitDebug(offline_utils_.GetJitDebug()); unwinder.Unwind(); std::string frame_info(DumpFrames(unwinder)); @@ -780,9 +776,9 @@ static void OfflineUnwind(void* data) { TEST_F(UnwindOfflineTest, unwind_offline_check_for_leaks) { std::string error_msg; - if (!offline_utils_.Init("jit_debug_arm/", ARCH_ARM, error_msg)) FAIL() << error_msg; - - if (!offline_utils_.SetJitProcessMemory(error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("jit_debug_arm/", ARCH_ARM, &error_msg, + ProcessMemoryFlag::kIncludeJitMemory)) + FAIL() << error_msg; std::shared_ptr process_memory = offline_utils_.GetProcessMemory(); LeakType data(offline_utils_.GetMaps(), offline_utils_.GetRegs(), process_memory); @@ -794,7 +790,7 @@ TEST_F(UnwindOfflineTest, unwind_offline_check_for_leaks) { // No .gnu_debugdata section in the elf file, so no symbols. TEST_F(UnwindOfflineTest, bad_eh_frame_hdr_arm64) { std::string error_msg; - if (!offline_utils_.Init("bad_eh_frame_hdr_arm64/", ARCH_ARM64, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("bad_eh_frame_hdr_arm64/", ARCH_ARM64, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -825,7 +821,7 @@ TEST_F(UnwindOfflineTest, bad_eh_frame_hdr_arm64) { // is used first, the unwind will not match the expected output. TEST_F(UnwindOfflineTest, debug_frame_first_x86) { std::string error_msg; - if (!offline_utils_.Init("debug_frame_first_x86/", ARCH_X86, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("debug_frame_first_x86/", ARCH_X86, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -855,7 +851,7 @@ TEST_F(UnwindOfflineTest, debug_frame_first_x86) { // Make sure that a pc that is at the beginning of an fde unwinds correctly. TEST_F(UnwindOfflineTest, eh_frame_hdr_begin_x86_64) { std::string error_msg; - if (!offline_utils_.Init("eh_frame_hdr_begin_x86_64/", ARCH_X86_64, error_msg)) + if (!offline_utils_.Init("eh_frame_hdr_begin_x86_64/", ARCH_X86_64, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), @@ -885,15 +881,13 @@ TEST_F(UnwindOfflineTest, eh_frame_hdr_begin_x86_64) { TEST_F(UnwindOfflineTest, art_quick_osr_stub_arm) { std::string error_msg; - if (!offline_utils_.Init("art_quick_osr_stub_arm/", ARCH_ARM, error_msg)) FAIL() << error_msg; - - if (!offline_utils_.SetJitProcessMemory(error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("art_quick_osr_stub_arm/", ARCH_ARM, &error_msg, + ProcessMemoryFlag::kIncludeJitMemory)) + FAIL() << error_msg; - Regs* regs = offline_utils_.GetRegs(); - std::shared_ptr process_memory = offline_utils_.GetProcessMemory(); - std::unique_ptr jit_debug = CreateJitDebug(regs->Arch(), process_memory); - Unwinder unwinder(128, offline_utils_.GetMaps(), regs, process_memory); - unwinder.SetJitDebug(jit_debug.get()); + Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), + offline_utils_.GetProcessMemory()); + unwinder.SetJitDebug(offline_utils_.GetJitDebug()); unwinder.Unwind(); std::string frame_info(DumpFrames(unwinder)); @@ -999,7 +993,7 @@ TEST_F(UnwindOfflineTest, art_quick_osr_stub_arm) { TEST_F(UnwindOfflineTest, jit_map_arm) { std::string error_msg; - if (!offline_utils_.Init("jit_map_arm/", ARCH_ARM, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("jit_map_arm/", ARCH_ARM, &error_msg)) FAIL() << error_msg; Maps* maps = offline_utils_.GetMaps(); maps->Add(0xd025c788, 0xd025c9f0, 0, PROT_READ | PROT_EXEC | MAPS_FLAGS_JIT_SYMFILE_MAP, @@ -1041,7 +1035,7 @@ TEST_F(UnwindOfflineTest, jit_map_arm) { TEST_F(UnwindOfflineTest, offset_arm) { std::string error_msg; - if (!offline_utils_.Init("offset_arm/", ARCH_ARM, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("offset_arm/", ARCH_ARM, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -1119,7 +1113,7 @@ TEST_F(UnwindOfflineTest, offset_arm) { // encoded as 0xb, which is not set as pc relative. TEST_F(UnwindOfflineTest, debug_frame_load_bias_arm) { std::string error_msg; - if (!offline_utils_.Init("debug_frame_load_bias_arm/", ARCH_ARM, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("debug_frame_load_bias_arm/", ARCH_ARM, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -1158,7 +1152,7 @@ TEST_F(UnwindOfflineTest, debug_frame_load_bias_arm) { TEST_F(UnwindOfflineTest, shared_lib_in_apk_arm64) { std::string error_msg; - if (!offline_utils_.Init("shared_lib_in_apk_arm64/", ARCH_ARM64, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("shared_lib_in_apk_arm64/", ARCH_ARM64, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -1197,14 +1191,16 @@ TEST_F(UnwindOfflineTest, shared_lib_in_apk_arm64) { TEST_F(UnwindOfflineTest, shared_lib_in_apk_memory_only_arm64) { std::string error_msg; - if (!offline_utils_.Init("shared_lib_in_apk_memory_only_arm64/", ARCH_ARM64, error_msg)) + if (!offline_utils_.Init("shared_lib_in_apk_memory_only_arm64/", ARCH_ARM64, &error_msg)) FAIL() << error_msg; // Add the memory that represents the shared library. std::shared_ptr process_memory = offline_utils_.GetProcessMemory(); MemoryOfflineParts* memory = reinterpret_cast(process_memory.get()); - if (!AddMemory(offline_utils_.GetOfflineDirectory() + "lib_mem.data", memory, error_msg)) - FAIL() << error_msg; + const std::string* offline_files_path = offline_utils_.GetOfflineFilesPath(); + if (offline_files_path == nullptr) FAIL() << "GetOfflineFilesPath() failed."; + + if (!AddMemory(*offline_files_path + "lib_mem.data", memory, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), process_memory); unwinder.Unwind(); @@ -1241,7 +1237,7 @@ TEST_F(UnwindOfflineTest, shared_lib_in_apk_memory_only_arm64) { TEST_F(UnwindOfflineTest, shared_lib_in_apk_single_map_arm64) { std::string error_msg; - if (!offline_utils_.Init("shared_lib_in_apk_single_map_arm64/", ARCH_ARM64, error_msg)) + if (!offline_utils_.Init("shared_lib_in_apk_single_map_arm64/", ARCH_ARM64, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), @@ -1296,8 +1292,8 @@ TEST_F(UnwindOfflineTest, shared_lib_in_apk_single_map_arm64) { TEST_F(UnwindOfflineTest, invalid_elf_offset_arm) { std::string error_msg; - if (!offline_utils_.Init("invalid_elf_offset_arm/", ARCH_ARM, error_msg, - /*add_stack=*/false)) + if (!offline_utils_.Init("invalid_elf_offset_arm/", ARCH_ARM, &error_msg, + ProcessMemoryFlag::kNoMemory)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), @@ -1313,7 +1309,7 @@ TEST_F(UnwindOfflineTest, invalid_elf_offset_arm) { TEST_F(UnwindOfflineTest, load_bias_ro_rx_x86_64) { std::string error_msg; - if (!offline_utils_.Init("load_bias_ro_rx_x86_64/", ARCH_X86_64, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("load_bias_ro_rx_x86_64/", ARCH_X86_64, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -1392,7 +1388,7 @@ TEST_F(UnwindOfflineTest, load_bias_ro_rx_x86_64) { TEST_F(UnwindOfflineTest, load_bias_different_section_bias_arm64) { std::string error_msg; - if (!offline_utils_.Init("load_bias_different_section_bias_arm64/", ARCH_ARM64, error_msg)) + if (!offline_utils_.Init("load_bias_different_section_bias_arm64/", ARCH_ARM64, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), @@ -1444,7 +1440,7 @@ TEST_F(UnwindOfflineTest, load_bias_different_section_bias_arm64) { TEST_F(UnwindOfflineTest, eh_frame_bias_x86) { std::string error_msg; - if (!offline_utils_.Init("eh_frame_bias_x86/", ARCH_X86, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("eh_frame_bias_x86/", ARCH_X86, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -1492,7 +1488,7 @@ TEST_F(UnwindOfflineTest, eh_frame_bias_x86) { TEST_F(UnwindOfflineTest, signal_load_bias_arm) { std::string error_msg; - if (!offline_utils_.Init("signal_load_bias_arm/", ARCH_ARM, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("signal_load_bias_arm/", ARCH_ARM, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -1562,7 +1558,7 @@ TEST_F(UnwindOfflineTest, signal_load_bias_arm) { TEST_F(UnwindOfflineTest, empty_arm64) { std::string error_msg; - if (!offline_utils_.Init("empty_arm64/", ARCH_ARM64, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("empty_arm64/", ARCH_ARM64, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -1603,7 +1599,7 @@ TEST_F(UnwindOfflineTest, empty_arm64) { // fde to do the unwind. TEST_F(UnwindOfflineTest, signal_fde_x86) { std::string error_msg; - if (!offline_utils_.Init("signal_fde_x86/", ARCH_X86, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("signal_fde_x86/", ARCH_X86, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -1685,7 +1681,7 @@ TEST_F(UnwindOfflineTest, signal_fde_x86) { // fde to do the unwind. TEST_F(UnwindOfflineTest, signal_fde_x86_64) { std::string error_msg; - if (!offline_utils_.Init("signal_fde_x86_64/", ARCH_X86_64, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("signal_fde_x86_64/", ARCH_X86_64, &error_msg)) FAIL() << error_msg; Unwinder unwinder(128, offline_utils_.GetMaps(), offline_utils_.GetRegs(), offline_utils_.GetProcessMemory()); @@ -1758,7 +1754,7 @@ TEST_F(UnwindOfflineTest, signal_fde_x86_64) { TEST_F(UnwindOfflineTest, pauth_pc_arm64) { std::string error_msg; - if (!offline_utils_.Init("pauth_pc_arm64/", ARCH_ARM64, error_msg)) FAIL() << error_msg; + if (!offline_utils_.Init("pauth_pc_arm64/", ARCH_ARM64, &error_msg)) FAIL() << error_msg; static_cast(offline_utils_.GetRegs())->SetPACMask(0x007fff8000000000ULL); diff --git a/libunwindstack/utils/OfflineUnwindUtils.cpp b/libunwindstack/utils/OfflineUnwindUtils.cpp index 8bc4baf..d9d147e 100644 --- a/libunwindstack/utils/OfflineUnwindUtils.cpp +++ b/libunwindstack/utils/OfflineUnwindUtils.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -27,16 +28,20 @@ #include #include #include +#include +#include #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -89,12 +94,12 @@ std::string DumpFrames(const Unwinder& unwinder) { return str; } -bool AddMemory(std::string file_name, MemoryOfflineParts* parts, std::string& error_msg) { +bool AddMemory(std::string file_name, MemoryOfflineParts* parts, std::string* error_msg) { MemoryOffline* memory = new MemoryOffline; if (!memory->Init(file_name.c_str(), 0)) { std::stringstream err_stream; err_stream << "Failed to add stack '" << file_name << "' to stack memory."; - error_msg = err_stream.str(); + *error_msg = err_stream.str(); return false; } parts->Add(memory); @@ -102,132 +107,203 @@ bool AddMemory(std::string file_name, MemoryOfflineParts* parts, std::string& er return true; } -bool OfflineUnwindUtils::Init(const std::string& offline_files_dir, ArchEnum arch, - std::string& error_msg, bool add_stack, bool set_maps) { - // Change to offline files directory so we can read the ELF files - cwd_ = std::filesystem::current_path(); - offline_dir_ = GetOfflineFilesDirectory() + offline_files_dir; - std::filesystem::current_path(std::filesystem::path(offline_dir_)); - - if (!android::base::ReadFileToString((offline_dir_ + "maps.txt"), &map_buffer_)) { - std::stringstream err_stream; - err_stream << "Failed to read from '" << offline_dir_ << "maps.txt' into memory."; - error_msg = err_stream.str(); - return false; +Regs* OfflineUnwindUtils::GetRegs(const std::string& initial_sample_name) const { + const std::string& sample_name = GetAdjustedSampleName(initial_sample_name); + std::string error_msg; + if (!IsValidUnwindSample(sample_name, &error_msg)) { + std::cerr << error_msg; + return nullptr; } - if (set_maps) { - if (!ResetMaps(error_msg)) return false; + return samples_.at(sample_name).regs.get(); +} + +Maps* OfflineUnwindUtils::GetMaps(const std::string& initial_sample_name) const { + const std::string& sample_name = GetAdjustedSampleName(initial_sample_name); + std::string error_msg; + if (!IsValidUnwindSample(sample_name, &error_msg)) { + std::cerr << error_msg; + return nullptr; } + return samples_.at(sample_name).maps.get(); +} - if (!SetRegs(arch, error_msg)) return false; +std::shared_ptr OfflineUnwindUtils::GetProcessMemory( + const std::string& initial_sample_name) const { + const std::string& sample_name = GetAdjustedSampleName(initial_sample_name); + std::string error_msg; + if (!IsValidUnwindSample(sample_name, &error_msg)) { + std::cerr << error_msg; + return nullptr; + } + return samples_.at(sample_name).process_memory; +} - if (add_stack) { - if (!SetProcessMemory(error_msg)) return false; +JitDebug* OfflineUnwindUtils::GetJitDebug(const std::string& initial_sample_name) const { + const std::string& sample_name = GetAdjustedSampleName(initial_sample_name); + std::string error_msg; + if (!IsValidUnwindSample(sample_name, &error_msg)) { + std::cerr << error_msg; + return nullptr; } - if (process_memory_ == nullptr) { - process_memory_.reset(new MemoryFake); + return samples_.at(sample_name).jit_debug.get(); +} + +const std::string* OfflineUnwindUtils::GetOfflineFilesPath( + const std::string& initial_sample_name) const { + const std::string& sample_name = GetAdjustedSampleName(initial_sample_name); + std::string error_msg; + if (!IsValidUnwindSample(sample_name, &error_msg)) { + std::cerr << error_msg; + return nullptr; } - return true; + return &samples_.at(sample_name).offline_files_path; } -bool OfflineUnwindUtils::ResetMaps(std::string& error_msg) { - maps_.reset(new BufferMaps(map_buffer_.c_str())); - if (!maps_->Parse()) { - error_msg = "Failed to parse offline maps."; +bool OfflineUnwindUtils::Init(std::vector offline_files_dirs, + const std::vector& archs, std::string* error_msg, + const std::vector& memory_flags, + const std::vector& set_maps) { + if (!(offline_files_dirs.size() == archs.size() && archs.size() == memory_flags.size() && + memory_flags.size() == set_maps.size())) { + *error_msg = "Sizes of vector inputs are not the same."; return false; } - return true; -} -bool OfflineUnwindUtils::SetProcessMemory(std::string& error_msg) { - std::string stack_name(offline_dir_ + "stack.data"); - struct stat st; - if (stat(stack_name.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { - auto stack_memory = std::make_unique(); - if (!stack_memory->Init((offline_dir_ + "stack.data").c_str(), 0)) { - std::stringstream err_stream; - err_stream << "Failed to initialize stack memory from " << offline_dir_ << "stack.data."; - error_msg = err_stream.str(); + // Save the current path so the caller can switch back to it later. + cwd_ = std::filesystem::current_path(); + + // Fill in the unwind samples. + std::stringstream err_stream; + for (size_t i = 0; i < offline_files_dirs.size(); ++i) { + std::string offline_files_full_path = GetOfflineFilesDirectory() + offline_files_dirs[i]; + if (!std::filesystem::exists(offline_files_full_path)) { + err_stream << "Offline files directory '" << offline_files_full_path << "' does not exist."; + *error_msg = err_stream.str(); return false; } - process_memory_.reset(stack_memory.release()); - } else { - std::unique_ptr stack_memory(new MemoryOfflineParts); - for (size_t i = 0;; i++) { - stack_name = offline_dir_ + "stack" + std::to_string(i) + ".data"; - if (stat(stack_name.c_str(), &st) == -1 || !S_ISREG(st.st_mode)) { - if (i == 0) { - error_msg = "No stack data files found."; - return false; - } + std::string map_buffer; + if (!android::base::ReadFileToString((offline_files_full_path + "maps.txt"), &map_buffer)) { + err_stream << "Failed to read from '" << offline_files_full_path << "maps.txt' into memory."; + *error_msg = err_stream.str(); + return false; + } + + // CreateMaps, CreatRegs, and Create*Memory may need to be called later by the client. So we + // need to create the sample now in case the flags are set to call these methods in Init. + const std::string& sample_name = offline_files_dirs[i]; + samples_.emplace(sample_name, (UnwindSample){ + std::move(offline_files_full_path), std::move(map_buffer), + nullptr, // regs + nullptr, // maps + std::make_shared(), // process_memory + nullptr, // jit_debug + }); + UnwindSample& sample = samples_.at(sample_name); + + if (set_maps[i]) { + if (!CreateMaps(error_msg, sample_name)) return false; + } + if (!CreateRegs(archs[i], error_msg, sample_name)) return false; + + switch (memory_flags[i]) { + case ProcessMemoryFlag::kNone: { + if (!CreateProcessMemory(error_msg, sample_name)) return false; break; } - if (!AddMemory(stack_name, stack_memory.get(), error_msg)) return false; + case ProcessMemoryFlag::kIncludeJitMemory: { + if (!CreateProcessMemory(error_msg, sample_name)) return false; + sample.jit_debug = CreateJitDebug(sample.regs->Arch(), sample.process_memory); + break; + } + case ProcessMemoryFlag::kNoMemory: { + break; + } + default: { + std::stringstream err_stream; + err_stream << "Unknown memory type for sample '" << sample_name << "'."; + *error_msg = err_stream.str(); + return false; + } } - process_memory_.reset(stack_memory.release()); } + initted_ = true; return true; } -bool OfflineUnwindUtils::SetJitProcessMemory(std::string& error_msg) { - MemoryOfflineParts* memory = new MemoryOfflineParts; +bool OfflineUnwindUtils::Init(std::string offline_files_dir, ArchEnum arch, std::string* error_msg, + ProcessMemoryFlag memory_flag, bool set_maps) { + if (Init(std::vector{std::move(offline_files_dir)}, std::vector{arch}, + error_msg, std::vector{memory_flag}, std::vector{set_maps})) { + if (!ChangeToSampleDirectory(error_msg)) return false; + return true; + } + return false; +} - // Construct process memory from all descriptor, stack, entry, and jit files - for (const auto& file : std::filesystem::directory_iterator(offline_dir_)) { - std::string filename = file.path().string(); - if (std::regex_match(filename, - std::regex("^(.+)\\/(descriptor|stack|entry|jit)(\\d*)\\.data$"))) { - if (!AddMemory(filename, memory, error_msg)) return false; - } +bool OfflineUnwindUtils::ChangeToSampleDirectory(std::string* error_msg, + const std::string& initial_sample_name) const { + if (!initted_) { + *error_msg = + "Cannot change to sample directory because OfflineUnwindUtils::Init has not been called."; + return false; } + const std::string& sample_name = GetAdjustedSampleName(initial_sample_name); + if (!IsValidUnwindSample(sample_name, error_msg)) return false; - process_memory_.reset(memory); + std::filesystem::current_path(std::filesystem::path(samples_.at(sample_name).offline_files_path)); return true; } -bool OfflineUnwindUtils::SetRegs(ArchEnum arch, std::string& error_msg) { - switch (arch) { - case ARCH_ARM: { - RegsArm* regs = new RegsArm; - regs_.reset(regs); - if (!ReadRegs(regs, arm_regs_, error_msg)) return false; - break; - } - case ARCH_ARM64: { - RegsArm64* regs = new RegsArm64; - regs_.reset(regs); - if (!ReadRegs(regs, arm64_regs_, error_msg)) return false; - break; - } - case ARCH_X86: { - RegsX86* regs = new RegsX86; - regs_.reset(regs); - if (!ReadRegs(regs, x86_regs_, error_msg)) return false; - break; - } - case ARCH_X86_64: { - RegsX86_64* regs = new RegsX86_64; - regs_.reset(regs); - if (!ReadRegs(regs, x86_64_regs_, error_msg)) return false; - break; +bool OfflineUnwindUtils::CreateMaps(std::string* error_msg, + const std::string& initial_sample_name) { + const std::string& sample_name = GetAdjustedSampleName(initial_sample_name); + if (!IsValidUnwindSample(sample_name, error_msg)) return false; + UnwindSample& sample = samples_.at(sample_name); + + sample.maps.reset(new BufferMaps(sample.map_buffer.c_str())); + if (!sample.maps->Parse()) { + *error_msg = "Failed to parse offline maps."; + return false; + } + return true; +} + +bool OfflineUnwindUtils::CreateProcessMemory(std::string* error_msg, + const std::string& initial_sample_name) { + const std::string& sample_name = GetAdjustedSampleName(initial_sample_name); + if (!IsValidUnwindSample(sample_name, error_msg)) return false; + UnwindSample& sample = samples_.at(sample_name); + + // Construct process memory from all descriptor, stack, entry, and jit files + auto memory = std::make_unique(); + bool data_files_found = false; + for (const auto& file : std::filesystem::directory_iterator(sample.offline_files_path)) { + std::string filename = file.path().string(); + if (std::regex_match(filename, + std::regex("^(.+)\\/(descriptor|stack|entry|jit)(\\d*)\\.data$"))) { + data_files_found = true; + if (!AddMemory(filename, memory.get(), error_msg)) return false; } - default: - error_msg = "Unknown architechture " + std::to_string(arch); - return false; + } + if (!data_files_found) { + *error_msg = "No memory (stack, JIT, etc.) data files found."; + return false; } + sample.process_memory.reset(memory.release()); return true; } +namespace { template -bool OfflineUnwindUtils::ReadRegs(RegsImpl* regs, - const std::unordered_map& name_to_reg, - std::string& error_msg) { +bool ReadRegs(RegsImpl* regs, + const std::unordered_map& name_to_reg, std::string* error_msg, + const std::string& offline_files_path) { std::stringstream err_stream; - FILE* fp = fopen((offline_dir_ + "regs.txt").c_str(), "r"); + FILE* fp = fopen((offline_files_path + "regs.txt").c_str(), "r"); if (fp == nullptr) { - err_stream << "Error opening file '" << offline_dir_ << "regs.txt': " << strerror(errno); - error_msg = err_stream.str(); + err_stream << "Error opening file '" << offline_files_path << "regs.txt': " << strerror(errno); + *error_msg = err_stream.str(); return false; } @@ -235,8 +311,9 @@ bool OfflineUnwindUtils::ReadRegs(RegsImpl* regs, uint64_t value; char reg_name[100]; if (fscanf(fp, "%s %" SCNx64 "\n", reg_name, &value) != 2) { - err_stream << "Failed to read in register name/values from '" << offline_dir_ << "regs.txt'."; - error_msg = err_stream.str(); + err_stream << "Failed to read in register name/values from '" << offline_files_path + << "regs.txt'."; + *error_msg = err_stream.str(); return false; } std::string name(reg_name); @@ -247,7 +324,7 @@ bool OfflineUnwindUtils::ReadRegs(RegsImpl* regs, auto entry = name_to_reg.find(name); if (entry == name_to_reg.end()) { err_stream << "Unknown register named " << reg_name; - error_msg = err_stream.str(); + *error_msg = err_stream.str(); return false; } (*regs)[entry->second] = value; @@ -255,6 +332,72 @@ bool OfflineUnwindUtils::ReadRegs(RegsImpl* regs, fclose(fp); return true; } +} // namespace + +bool OfflineUnwindUtils::CreateRegs(ArchEnum arch, std::string* error_msg, + const std::string& initial_sample_name) { + const std::string& sample_name = GetAdjustedSampleName(initial_sample_name); + if (!IsValidUnwindSample(sample_name, error_msg)) return false; + auto& regs = samples_.at(sample_name).regs; + const auto& offline_files_path = samples_.at(sample_name).offline_files_path; + + switch (arch) { + case ARCH_ARM: { + RegsArm* regs_impl = new RegsArm; + regs.reset(regs_impl); + if (!ReadRegs(regs_impl, arm_regs_, error_msg, offline_files_path)) return false; + break; + } + case ARCH_ARM64: { + RegsArm64* regs_impl = new RegsArm64; + regs.reset(regs_impl); + if (!ReadRegs(regs_impl, arm64_regs_, error_msg, offline_files_path)) return false; + break; + } + case ARCH_X86: { + RegsX86* regs_impl = new RegsX86; + regs.reset(regs_impl); + if (!ReadRegs(regs_impl, x86_regs_, error_msg, offline_files_path)) return false; + break; + } + case ARCH_X86_64: { + RegsX86_64* regs_impl = new RegsX86_64; + regs.reset(regs_impl); + if (!ReadRegs(regs_impl, x86_64_regs_, error_msg, offline_files_path)) return false; + break; + } + default: + *error_msg = "Unknown architechture " + std::to_string(arch); + return false; + } + + return true; +} + +const std::string& OfflineUnwindUtils::GetAdjustedSampleName( + const std::string& initial_sample_name) const { + // Only return the first entry in the sample map if this is the single unwind use case. + // Otherwise return the inputted sample name so we can check if that is a valid sample name. + if (initial_sample_name == kSingleSample && samples_.size() == 1) { + return samples_.begin()->first; + } + return initial_sample_name; +} + +bool OfflineUnwindUtils::IsValidUnwindSample(const std::string& sample_name, + std::string* error_msg) const { + if (samples_.find(sample_name) == samples_.end()) { + std::stringstream err_stream; + err_stream << "Invalid sample name (offline file directory) '" << sample_name << "'."; + if (sample_name == kSingleSample) { + err_stream << " An explicit sample name must be provided for the multiple unwind use case " + "of OfflineUnwindUtils (i.e. should not use the default sample name)."; + } + *error_msg = err_stream.str(); + return false; + } + return true; +} std::unordered_map OfflineUnwindUtils::arm_regs_ = { {"r0", ARM_REG_R0}, {"r1", ARM_REG_R1}, {"r2", ARM_REG_R2}, {"r3", ARM_REG_R3}, diff --git a/libunwindstack/utils/OfflineUnwindUtils.h b/libunwindstack/utils/OfflineUnwindUtils.h index 928ac5c..75bfd38 100644 --- a/libunwindstack/utils/OfflineUnwindUtils.h +++ b/libunwindstack/utils/OfflineUnwindUtils.h @@ -17,10 +17,17 @@ #ifndef _LIBUNWINDSTACK_UTILS_OFFLINE_UNWIND_UTILS_H #define _LIBUNWINDSTACK_UTILS_OFFLINE_UNWIND_UTILS_H +#include #include #include #include +#include +#include +#include +#include +#include +#include #include #include "MemoryOffline.h" @@ -64,40 +71,97 @@ // // See b/192012600 for additional information regarding Offline Unwind Benchmarks. namespace unwindstack { + std::string GetOfflineFilesDirectory(); std::string DumpFrames(const Unwinder& unwinder); -bool AddMemory(std::string file_name, MemoryOfflineParts* parts, std::string& error_msg); +bool AddMemory(std::string file_name, MemoryOfflineParts* parts, std::string* error_msg); + +// An `UnwindSample` encapsulates the information necessary to perform an offline unwind for a +// single offline sample/snapshot. +struct UnwindSample { + std::string offline_files_path; + std::string map_buffer; + std::unique_ptr regs; + std::unique_ptr maps; + std::shared_ptr process_memory; + std::unique_ptr jit_debug; +}; +// Enum that indicates how `UnwindSample::process_memory` of `OfflineUnwindUtils::samples_` +// should be initialized. +enum class ProcessMemoryFlag { + kNone = 0, + kIncludeJitMemory, + kNoMemory, +}; + +// The `OfflineUnwindUtils` class helps perform offline unwinds by handling the creation of the +// `Regs`, `Maps`, and `Memory` objects needed for unwinding. +// +// `OfflineUnwindUtils` assists in two unwind use cases: +// 1. Single unwinds: unwind from a single sample/snapshot (one set of offline unwind files). +// 2. Consecutive/Multiple unwinds: unwind from a multiple samples/snapshots. +// +// `Init` contains two overloads for these two unwind cases. Other than `Init` and +// `ReturnToCurrentWorkingDirectory`, the remainder of the public API includes a `sample_name` +// parameter to indicate which sample/snapshot we are referencing. Specifying this value is +// REQUIRED for the multiple unwind use case. However, in the single use case, the caller has +// the choice of either providing the sample name or using the default value. class OfflineUnwindUtils { public: - Regs* GetRegs() { return regs_.get(); } + // If the sample name passed to Get* is an invalid sample, nullptr is returned. + Regs* GetRegs(const std::string& sample_name = kSingleSample) const; + + Maps* GetMaps(const std::string& sample_name = kSingleSample) const; + + std::shared_ptr GetProcessMemory(const std::string& sample_name = kSingleSample) const; - Maps* GetMaps() { return maps_.get(); } + JitDebug* GetJitDebug(const std::string& sample_name = kSingleSample) const; - std::shared_ptr GetProcessMemory() { return process_memory_; } + const std::string* GetOfflineFilesPath(const std::string& sample_name = kSingleSample) const; - std::string GetOfflineDirectory() { return offline_dir_; } + // Notes: + // * If the caller sets elements of `set_maps` to false or `memory_types` to + // kNoMemory, they are responsible for calling `CreateMaps` or `CreateProcessMemory` before + // expecting `GetMaps` or `GetProcessMemory` to return anything but nullptr. + // * Pass offline_files_dirs by value because we move each string to create the samples_ elements. + bool Init(std::vector offline_files_dirs, const std::vector& archs, + std::string* error_msg, const std::vector& memory_flags, + const std::vector& set_maps); - bool Init(const std::string& offline_files_dir, ArchEnum arch, std::string& error_msg, - bool add_stack = true, bool set_maps = true); + bool Init(std::string offline_files_dir, ArchEnum arch, std::string* error_msg, + ProcessMemoryFlag memory_flag = ProcessMemoryFlag::kNone, bool set_maps = true); - bool ResetMaps(std::string& error_msg); + // This must be called explicitly for the multiple unwind use case sometime before + // Unwinder::Unwind is called. This is required because the Unwinder must init each + // ELF object with a MemoryFileAtOffset memory object. Because the maps.txt provides a relative + // path to the ELF files, we must be in the directory of the maps.txt when unwinding. + // + // Note: Init performs the check that this sample directory exists. If Init fails, + // `initted_` is not set to true and this function will return false. + bool ChangeToSampleDirectory(std::string* error_msg, + const std::string& initial_sample_name = kSingleSample) const; - bool SetProcessMemory(std::string& error_msg); + void ReturnToCurrentWorkingDirectory() { + if (!cwd_.empty()) std::filesystem::current_path(cwd_); + } - bool SetJitProcessMemory(std::string& error_msg); + bool CreateMaps(std::string* error_msg, const std::string& sample_name = kSingleSample); - void ReturnToCurrentWorkingDirectory() { std::filesystem::current_path(cwd_); } + bool CreateProcessMemory(std::string* error_msg, const std::string& sample_name = kSingleSample); private: - bool SetRegs(ArchEnum arch, std::string& error_msg); + static constexpr char kSingleSample[] = ""; + + bool CreateRegs(ArchEnum arch, std::string* error_msg, + const std::string& sample_name = kSingleSample); + + // Needed to support using the default value `kSingleSample` for the single unwind use case. + const std::string& GetAdjustedSampleName(const std::string& sample_name) const; - template - bool ReadRegs(RegsImpl* regs, - const std::unordered_map& name_to_reg, - std::string& error_msg); + bool IsValidUnwindSample(const std::string& sample_name, std::string* error_msg) const; static std::unordered_map arm_regs_; static std::unordered_map arm64_regs_; @@ -105,11 +169,8 @@ class OfflineUnwindUtils { static std::unordered_map x86_64_regs_; std::string cwd_; - std::string offline_dir_; - std::string map_buffer_; - std::unique_ptr regs_; - std::unique_ptr maps_; - std::shared_ptr process_memory_; + std::unordered_map samples_; + bool initted_ = false; }; } // namespace unwindstack -- cgit v1.2.3