/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Check.h" #include "MemoryOffline.h" #include "utils/MemoryFake.h" #include "OfflineUnwindUtils.h" namespace unwindstack { void DecompressFiles(const std::string& directory) { namespace fs = std::filesystem; for (const auto& file : fs::recursive_directory_iterator(directory)) { fs::path src_path = file.path(); if (src_path.extension() == ".gz") { fs::path dst_path = fs::path(src_path).replace_extension(); // Remove .gz extension. if (!fs::exists(dst_path) || fs::last_write_time(src_path) > fs::last_write_time(dst_path)) { gzFile src = gzopen(src_path.c_str(), "rb"); CHECK(src != nullptr); fs::path tmp_path = fs::path(src_path).replace_extension("." + std::to_string(getpid())); std::ofstream tmp(tmp_path); // Temporary file to avoid races between unit tests. char buffer[1024]; int size; while ((size = gzread(src, buffer, sizeof(buffer))) > 0) { tmp.write(buffer, size); } tmp.close(); gzclose(src); fs::rename(tmp_path, dst_path); } } } } void CreateLinks(const std::string& directory) { namespace fs = std::filesystem; for (const auto& file : fs::recursive_directory_iterator(directory)) { fs::path src_path = file.path(); if (fs::is_regular_file(src_path) && src_path.filename() == "links.txt") { std::string contents; if (!android::base::ReadFileToString(src_path.c_str(), &contents)) { errx(1, "Unable to read file: %s", src_path.c_str()); } fs::path parent_path = src_path.parent_path(); std::vector lines(android::base::Split(contents, "\n")); for (auto line : lines) { std::string trimmed_line(android::base::Trim(line)); if (trimmed_line.empty()) { continue; } std::vector values(android::base::Split(trimmed_line, " ")); if (values.size() != 2) { errx(1, "Invalid line in %s: line %s", src_path.c_str(), line.c_str()); } // Create the symlink if it doesn't already exist. fs::path target(parent_path); target /= fs::path(values[0]); fs::path source(parent_path); source /= fs::path(values[1]); if (!fs::exists(source)) { // Ignore any errors, if this is running at the same time // in multiple processes, then this might fail. std::error_code ec; fs::create_symlink(target, source, ec); } } } } } std::string GetOfflineFilesDirectory() { std::string path = android::base::GetExecutableDirectory() + "/offline_files/"; DecompressFiles(path); CreateLinks(path); return path; } std::string DumpFrames(const Unwinder& unwinder) { std::string str; for (size_t i = 0; i < unwinder.NumFrames(); i++) { str += unwinder.FormatFrame(i) + "\n"; } return str; } 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(); return false; } parts->Add(memory); return true; } 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; } 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(); } 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; } 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; } 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 &samples_.at(sample_name).offline_files_path; } const std::string* OfflineUnwindUtils::GetFrameInfoFilepath( 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).frame_info_filepath; } bool OfflineUnwindUtils::Init(const std::vector& sample_infos, std::string* error_msg) { // 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 (const auto& sample_info : sample_infos) { std::string offline_files_full_path = GetOfflineFilesDirectory() + sample_info.offline_files_dir; 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; } std::string frame_info_filepath = offline_files_full_path + sample_info.frame_info_filename; 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 = sample_info.offline_files_dir; samples_.emplace(sample_name, (UnwindSample){ std::move(offline_files_full_path), std::move(frame_info_filepath), std::move(map_buffer), nullptr, // regs nullptr, // maps std::make_shared(), // process_memory nullptr, // jit_debug }); UnwindSample& sample = samples_.at(sample_name); if (sample_info.create_maps) { if (!CreateMaps(error_msg, sample_name)) return false; } if (!CreateRegs(sample_info.arch, error_msg, sample_name)) return false; switch (sample_info.memory_flag) { case ProcessMemoryFlag::kNone: { if (!CreateProcessMemory(error_msg, sample_name)) return false; break; } 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; } } } initted_ = true; return true; } bool OfflineUnwindUtils::Init(const UnwindSampleInfo& sample_info, std::string* error_msg) { if (Init(std::vector{sample_info}, error_msg)) { if (!ChangeToSampleDirectory(error_msg)) return false; return true; } 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; std::filesystem::current_path(std::filesystem::path(samples_.at(sample_name).offline_files_path)); return true; } bool OfflineUnwindUtils::GetExpectedNumFrames(size_t* expected_num_frames, std::string* error_msg, const std::string& initial_sample_name) const { if (!initted_) { *error_msg = "Cannot get expected number of frames of a sample 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; const std::string& sample_frames_path = samples_.at(sample_name).frame_info_filepath; if (!std::filesystem::exists(sample_frames_path)) { std::stringstream err_stream; err_stream << "Offline files directory '" << sample_frames_path << "' does not exist."; *error_msg = err_stream.str(); return false; } std::ifstream in(sample_frames_path); in.unsetf(std::ios_base::skipws); // Ensure that we do not skip newlines. *expected_num_frames = std::count(std::istream_iterator(in), std::istream_iterator(), '\n'); return true; } 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; } } 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 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_files_path + "regs.txt").c_str(), "r"); if (fp == nullptr) { err_stream << "Error opening file '" << offline_files_path << "regs.txt': " << strerror(errno); *error_msg = err_stream.str(); return false; } while (!feof(fp)) { uint64_t value; char reg_name[100]; if (fscanf(fp, "%[^:]: %" SCNx64 "\n", reg_name, &value) != 2) { 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); auto entry = name_to_reg.find(name); if (entry == name_to_reg.end()) { err_stream << "Unknown register named " << name; *error_msg = err_stream.str(); return false; } (*regs)[entry->second] = value; } 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_RISCV64: { RegsRiscv64* regs_impl = new RegsRiscv64; regs.reset(regs_impl); if (!ReadRegs(regs_impl, riscv64_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_.contains(sample_name)) { 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}, {"r4", ARM_REG_R4}, {"r5", ARM_REG_R5}, {"r6", ARM_REG_R6}, {"r7", ARM_REG_R7}, {"r8", ARM_REG_R8}, {"r9", ARM_REG_R9}, {"r10", ARM_REG_R10}, {"r11", ARM_REG_R11}, {"ip", ARM_REG_R12}, {"sp", ARM_REG_SP}, {"lr", ARM_REG_LR}, {"pc", ARM_REG_PC}, }; std::unordered_map OfflineUnwindUtils::arm64_regs_ = { {"x0", ARM64_REG_R0}, {"x1", ARM64_REG_R1}, {"x2", ARM64_REG_R2}, {"x3", ARM64_REG_R3}, {"x4", ARM64_REG_R4}, {"x5", ARM64_REG_R5}, {"x6", ARM64_REG_R6}, {"x7", ARM64_REG_R7}, {"x8", ARM64_REG_R8}, {"x9", ARM64_REG_R9}, {"x10", ARM64_REG_R10}, {"x11", ARM64_REG_R11}, {"x12", ARM64_REG_R12}, {"x13", ARM64_REG_R13}, {"x14", ARM64_REG_R14}, {"x15", ARM64_REG_R15}, {"x16", ARM64_REG_R16}, {"x17", ARM64_REG_R17}, {"x18", ARM64_REG_R18}, {"x19", ARM64_REG_R19}, {"x20", ARM64_REG_R20}, {"x21", ARM64_REG_R21}, {"x22", ARM64_REG_R22}, {"x23", ARM64_REG_R23}, {"x24", ARM64_REG_R24}, {"x25", ARM64_REG_R25}, {"x26", ARM64_REG_R26}, {"x27", ARM64_REG_R27}, {"x28", ARM64_REG_R28}, {"x29", ARM64_REG_R29}, {"sp", ARM64_REG_SP}, {"lr", ARM64_REG_LR}, {"pc", ARM64_REG_PC}, {"pst", ARM64_REG_PSTATE}, }; std::unordered_map OfflineUnwindUtils::riscv64_regs_ = { {"pc", RISCV64_REG_PC}, {"ra", RISCV64_REG_RA}, {"sp", RISCV64_REG_SP}, {"gp", RISCV64_REG_GP}, {"tp", RISCV64_REG_TP}, {"a0", RISCV64_REG_A0}, {"a1", RISCV64_REG_A1}, {"a2", RISCV64_REG_A2}, {"a3", RISCV64_REG_A3}, {"a4", RISCV64_REG_A4}, {"a5", RISCV64_REG_A5}, {"a6", RISCV64_REG_A6}, {"a7", RISCV64_REG_A7}, {"s0", RISCV64_REG_S0}, {"s1", RISCV64_REG_S1}, {"s2", RISCV64_REG_S2}, {"s3", RISCV64_REG_S3}, {"s4", RISCV64_REG_S4}, {"s5", RISCV64_REG_S5}, {"s6", RISCV64_REG_S6}, {"s7", RISCV64_REG_S7}, {"s8", RISCV64_REG_S8}, {"s9", RISCV64_REG_S9}, {"s10", RISCV64_REG_S10}, {"s11", RISCV64_REG_S11}, {"t0", RISCV64_REG_T0}, {"t1", RISCV64_REG_T1}, {"t2", RISCV64_REG_T2}, {"t3", RISCV64_REG_T3}, {"t4", RISCV64_REG_T4}, {"t5", RISCV64_REG_T5}, {"t6", RISCV64_REG_T6}, {"vlenb", RISCV64_REG_VLENB}, }; std::unordered_map OfflineUnwindUtils::x86_regs_ = { {"eax", X86_REG_EAX}, {"ebx", X86_REG_EBX}, {"ecx", X86_REG_ECX}, {"edx", X86_REG_EDX}, {"ebp", X86_REG_EBP}, {"edi", X86_REG_EDI}, {"esi", X86_REG_ESI}, {"esp", X86_REG_ESP}, {"eip", X86_REG_EIP}, }; std::unordered_map OfflineUnwindUtils::x86_64_regs_ = { {"rax", X86_64_REG_RAX}, {"rbx", X86_64_REG_RBX}, {"rcx", X86_64_REG_RCX}, {"rdx", X86_64_REG_RDX}, {"r8", X86_64_REG_R8}, {"r9", X86_64_REG_R9}, {"r10", X86_64_REG_R10}, {"r11", X86_64_REG_R11}, {"r12", X86_64_REG_R12}, {"r13", X86_64_REG_R13}, {"r14", X86_64_REG_R14}, {"r15", X86_64_REG_R15}, {"rdi", X86_64_REG_RDI}, {"rsi", X86_64_REG_RSI}, {"rbp", X86_64_REG_RBP}, {"rsp", X86_64_REG_RSP}, {"rip", X86_64_REG_RIP}, }; } // namespace unwindstack