diff options
author | Yabin Cui <yabinc@google.com> | 2016-02-14 19:18:02 -0800 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2016-02-17 22:06:52 -0800 |
commit | b1a885b014540a2f7798b5a35ea0f0ec150d93ee (patch) | |
tree | 1590510f01a2b195944cb4451e5d53472e22973f | |
parent | 1d06628497bcf090cbb749c73d89f22167fe1281 (diff) | |
download | extras-b1a885b014540a2f7798b5a35ea0f0ec150d93ee.tar.gz |
simpleperf: report symbols of native libraries in apk file.
Changes included:
1. provide interface in read_apk.h to read build id and symbols.
2. report symbols of native libraries in apk file.
3. refactor code in read_elf.cpp and read_apk.cpp.
4. add verbose log.
5. add -o report_file_name option for report command.
6. add corresponding unit tests.
Bug: 26962895
Change-Id: I0d5398996e0c29dba4a6f5226692b758ca096bbd
27 files changed, 570 insertions, 381 deletions
diff --git a/simpleperf/build_id.h b/simpleperf/build_id.h index 05c37d59..bbd13c41 100644 --- a/simpleperf/build_id.h +++ b/simpleperf/build_id.h @@ -33,10 +33,29 @@ class BuildId { memset(data_, '\0', BUILD_ID_SIZE); } - BuildId(const void* data, size_t len = BUILD_ID_SIZE) : BuildId() { + // Copy build id from a byte array, like {0x76, 0x00, 0x32,...}. + BuildId(const void* data, size_t len) : BuildId() { memcpy(data_, data, std::min(len, BUILD_ID_SIZE)); } + // Read build id from a hex string, like "7600329e31058e12b145d153ef27cd40e1a5f7b9". + explicit BuildId(const std::string& s) : BuildId() { + for (size_t i = 0; i < s.size() && i < BUILD_ID_SIZE * 2; i += 2) { + unsigned char ch = 0; + for (size_t j = i; j < i + 2; ++j) { + ch <<= 4; + if (s[j] >= '0' && s[j] <= '9') { + ch |= s[j] - '0'; + } else if (s[j] >= 'a' && s[j] <= 'f') { + ch |= s[j] - 'a' + 10; + } else if (s[j] >= 'A' && s[j] <= 'F') { + ch |= s[j] - 'A' + 10; + } + } + data_[i / 2] = ch; + } + } + const unsigned char* Data() const { return data_; } @@ -53,6 +72,10 @@ class BuildId { return memcmp(data_, build_id.data_, BUILD_ID_SIZE) == 0; } + bool operator!=(const BuildId& build_id) const { + return !(*this == build_id); + } + bool IsEmpty() const { static BuildId empty_build_id; return *this == empty_build_id; diff --git a/simpleperf/cmd_help.cpp b/simpleperf/cmd_help.cpp index a29ef727..93a90ee1 100644 --- a/simpleperf/cmd_help.cpp +++ b/simpleperf/cmd_help.cpp @@ -60,7 +60,8 @@ void HelpCommand::PrintShortHelp() { "common options:\n" " -h/--help Print this help information.\n" " --log <severity> Set the minimum severity of logging. Possible severities\n" - " include debug, warning, error, fatal. Default is warning.\n" + " include verbose, debug, warning, error, fatal. Default is\n" + " warning.\n" "subcommands:\n"); for (auto& cmd_name : GetAllCommandNames()) { std::unique_ptr<Command> cmd = CreateCommandInstance(cmd_name); diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp index edeb64cf..b913f111 100644 --- a/simpleperf/cmd_record.cpp +++ b/simpleperf/cmd_record.cpp @@ -126,7 +126,8 @@ class RecordCommand : public Command { post_unwind_(false), child_inherit_(true), perf_mmap_pages_(16), - record_filename_("perf.data") { + record_filename_("perf.data"), + sample_record_count_(0) { signaled = false; scoped_signal_handler_.reset( new ScopedSignalHandler({SIGCHLD, SIGINT, SIGTERM}, signal_handler)); @@ -146,6 +147,7 @@ class RecordCommand : public Command { bool DumpThreadCommAndMmaps(bool all_threads, const std::vector<pid_t>& selected_threads); bool CollectRecordsFromKernel(const char* data, size_t size); bool ProcessRecord(Record* record); + void UpdateRecordForEmbeddedElfPath(Record* record); void UnwindRecord(Record* record); bool PostUnwind(const std::vector<std::string>& args); bool DumpAdditionalFeatures(const std::vector<std::string>& args); @@ -153,7 +155,6 @@ class RecordCommand : public Command { void CollectHitFileInfo(Record* record); std::pair<std::string, uint64_t> TestForEmbeddedElf(Dso *dso, uint64_t pgoff); - bool use_sample_freq_; // Use sample_freq_ when true, otherwise using sample_period_. uint64_t sample_freq_; // Sample 'sample_freq_' times per second. uint64_t sample_period_; // Sample once when 'sample_period_' events occur. @@ -180,10 +181,10 @@ class RecordCommand : public Command { std::unique_ptr<RecordFileWriter> record_file_writer_; std::set<std::string> hit_kernel_modules_; - std::set<std::pair<std::string, uint64_t> > hit_user_files_; - ApkInspector apk_inspector_; + std::set<std::string> hit_user_files_; std::unique_ptr<ScopedSignalHandler> scoped_signal_handler_; + uint64_t sample_record_count_; }; bool RecordCommand::Run(const std::vector<std::string>& args) { @@ -280,7 +281,7 @@ bool RecordCommand::Run(const std::vector<std::string>& args) { return false; } } - + LOG(VERBOSE) << "Record " << sample_record_count_ << " samples."; return true; } @@ -637,15 +638,52 @@ bool RecordCommand::CollectRecordsFromKernel(const char* data, size_t size) { } bool RecordCommand::ProcessRecord(Record* record) { + UpdateRecordForEmbeddedElfPath(record); BuildThreadTree(*record, &thread_tree_); CollectHitFileInfo(record); if (unwind_dwarf_callchain_ && !post_unwind_) { UnwindRecord(record); } + if (record->type() == PERF_RECORD_SAMPLE) { + sample_record_count_++; + } bool result = record_file_writer_->WriteData(record->BinaryFormat()); return result; } +template<class RecordType> +void UpdateMmapRecordForEmbeddedElfPath(RecordType* record) { + RecordType& r = *record; + bool in_kernel = ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL); + if (!in_kernel && r.data.pgoff != 0) { + // For the case of a shared library "foobar.so" embedded + // inside an APK, we rewrite the original MMAP from + // ["path.apk" offset=X] to ["path.apk!/foobar.so" offset=W] + // so as to make the library name explicit. This update is + // done here (as part of the record operation) as opposed to + // on the host during the report, since we want to report + // the correct library name even if the the APK in question + // is not present on the host. The new offset W is + // calculated to be with respect to the start of foobar.so, + // not to the start of path.apk. + EmbeddedElf* ee = ApkInspector::FindElfInApkByOffset(r.filename, r.data.pgoff); + if (ee != nullptr) { + // Compute new offset relative to start of elf in APK. + r.data.pgoff -= ee->entry_offset(); + r.filename = GetUrlInApk(r.filename, ee->entry_name()); + r.AdjustSizeBasedOnData(); + } + } +} + +void RecordCommand::UpdateRecordForEmbeddedElfPath(Record* record) { + if (record->type() == PERF_RECORD_MMAP) { + UpdateMmapRecordForEmbeddedElfPath(static_cast<MmapRecord*>(record)); + } else if (record->type() == PERF_RECORD_MMAP2) { + UpdateMmapRecordForEmbeddedElfPath(static_cast<Mmap2Record*>(record)); + } +} + void RecordCommand::UnwindRecord(Record* record) { if (record->type() == PERF_RECORD_SAMPLE) { SampleRecord& r = *static_cast<SampleRecord*>(record); @@ -746,7 +784,7 @@ bool RecordCommand::DumpBuildIdFeature() { std::vector<BuildIdRecord> build_id_records; BuildId build_id; // Add build_ids for kernel/modules. - for (auto& filename : hit_kernel_modules_) { + for (const auto& filename : hit_kernel_modules_) { if (filename == DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID) { if (!GetKernelBuildId(&build_id)) { LOG(DEBUG) << "can't read build_id for kernel"; @@ -768,29 +806,21 @@ bool RecordCommand::DumpBuildIdFeature() { } } // Add build_ids for user elf files. - for (auto& dso_origin : hit_user_files_) { - auto& filename = dso_origin.first; - auto& offset = dso_origin.second; + for (const auto& filename : hit_user_files_) { if (filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP) { continue; } - EmbeddedElf *ee = apk_inspector_.FindElfInApkByMmapOffset(filename, offset); - if (ee) { - if (!GetBuildIdFromEmbeddedElfFile(filename, - ee->entry_offset(), - ee->entry_size(), - &build_id)) { - LOG(DEBUG) << "can't read build_id from archive file " << filename - << "entry " << ee->entry_name(); + auto tuple = SplitUrlInApk(filename); + if (std::get<0>(tuple)) { + if (!GetBuildIdFromApkFile(std::get<1>(tuple), std::get<2>(tuple), &build_id)) { + LOG(DEBUG) << "can't read build_id from file " << filename; + continue; + } + } else { + if (!GetBuildIdFromElfFile(filename, &build_id)) { + LOG(DEBUG) << "can't read build_id from file " << filename; continue; } - std::string ee_filename = filename + "!" + ee->entry_name(); - build_id_records.push_back(CreateBuildIdRecord(false, UINT_MAX, build_id, ee_filename)); - continue; - } - if (!GetBuildIdFromElfFile(filename, &build_id)) { - LOG(DEBUG) << "can't read build_id from file " << filename; - continue; } build_id_records.push_back(CreateBuildIdRecord(false, UINT_MAX, build_id, filename)); } @@ -809,51 +839,9 @@ void RecordCommand::CollectHitFileInfo(Record* record) { if (in_kernel) { hit_kernel_modules_.insert(map->dso->Path()); } else { - auto apair = std::make_pair(map->dso->Path(), map->pgoff); - hit_user_files_.insert(apair); + hit_user_files_.insert(map->dso->Path()); } } - if (record->type() == PERF_RECORD_MMAP) { - MmapRecord& r = *static_cast<MmapRecord*>(record); - bool in_kernel = ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL); - if (!in_kernel) { - const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.data.pid, r.data.tid); - const MapEntry* map = thread_tree_.FindMap(thread, r.data.addr, in_kernel); - if (map->pgoff != 0u) { - std::pair<std::string, uint64_t> ee_info = TestForEmbeddedElf(map->dso, map->pgoff); - if (!ee_info.first.empty()) { - // For the case of a shared library "foobar.so" embedded - // inside an APK, we rewrite the original MMAP from - // ["path.apk" offset=X] to ["path.apk!foobar.so" offset=W] - // so as to make the library name explicit. This update is - // done here (as part of the record operation) as opposed to - // on the host during the report, since we want to report - // the correct library name even if the the APK in question - // is not present on the host. The new offset W is - // calculated to be with respect to the start of foobar.so, - // not to the start of path.apk. - const std::string& entry_name = ee_info.first; - uint64_t new_offset = ee_info.second; - std::string new_filename = r.filename + "!" + entry_name; - UpdateMmapRecord(&r, new_filename, new_offset); - } - } - } - } -} - -std::pair<std::string, uint64_t> RecordCommand::TestForEmbeddedElf(Dso *dso, uint64_t pgoff) -{ - // Examine the DSO to determine whether it corresponds to an ELF - // file embedded in an APK. - std::string ee_name; - EmbeddedElf *ee = apk_inspector_.FindElfInApkByMmapOffset(dso->Path(), pgoff); - if (ee) { - // Compute new offset relative to start of elf in APK. - uint64_t elf_offset = pgoff - ee->entry_offset(); - return std::make_pair(ee->entry_name(), elf_offset); - } - return std::make_pair(std::string(), 0u); } __attribute__((constructor)) static void RegisterRecordCommand() { diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp index b667de69..9fbf82ef 100644 --- a/simpleperf/cmd_report.cpp +++ b/simpleperf/cmd_report.cpp @@ -256,6 +256,7 @@ class ReportCommand : public Command { " -i <file> Specify path of record file, default is perf.data.\n" " -n Print the sample count for each item.\n" " --no-demangle Don't demangle symbol names.\n" + " -o report_file_name Set report file name, default is stdout.\n" " --pid pid1,pid2,...\n" " Report only for selected pids.\n" " --sort key1,key2,...\n" @@ -272,7 +273,8 @@ class ReportCommand : public Command { use_branch_address_(false), accumulate_callchain_(false), print_callgraph_(false), - callgraph_show_callee_(true) { + callgraph_show_callee_(true), + report_fp_(nullptr) { compare_sample_func_t compare_sample_callback = std::bind( &ReportCommand::CompareSampleEntry, this, std::placeholders::_1, std::placeholders::_2); sample_tree_ = @@ -289,13 +291,15 @@ class ReportCommand : public Command { void ProcessSampleRecord(const SampleRecord& r); bool ReadFeaturesFromRecordFile(); int CompareSampleEntry(const SampleEntry& sample1, const SampleEntry& sample2); - void PrintReport(); + bool PrintReport(); void PrintReportContext(); void CollectReportWidth(); void CollectReportEntryWidth(const SampleEntry& sample); void PrintReportHeader(); void PrintReportEntry(const SampleEntry& sample); void PrintCallGraph(const SampleEntry& sample); + void PrintCallGraphEntry(size_t depth, std::string prefix, const std::unique_ptr<CallChainNode>& node, + uint64_t parent_period, bool last); std::string record_filename_; std::unique_ptr<RecordFileReader> record_file_reader_; @@ -309,6 +313,9 @@ class ReportCommand : public Command { bool accumulate_callchain_; bool print_callgraph_; bool callgraph_show_callee_; + + std::string report_filename_; + FILE* report_fp_; }; bool ReportCommand::Run(const std::vector<std::string>& args) { @@ -332,7 +339,9 @@ bool ReportCommand::Run(const std::vector<std::string>& args) { ReadSampleTreeFromRecordFile(); // 3. Show collected information. - PrintReport(); + if (!PrintReport()) { + return false; + } return true; } @@ -386,6 +395,11 @@ bool ReportCommand::ParseOptions(const std::vector<std::string>& args) { } else if (args[i] == "--no-demangle") { demangle = false; + } else if (args[i] == "-o") { + if (!NextArgumentOrError(args, &i)) { + return false; + } + report_filename_ = args[i]; } else if (args[i] == "--pids" || args[i] == "--tids") { if (!NextArgumentOrError(args, &i)) { @@ -641,13 +655,29 @@ int ReportCommand::CompareSampleEntry(const SampleEntry& sample1, const SampleEn return 0; } -void ReportCommand::PrintReport() { +bool ReportCommand::PrintReport() { + std::unique_ptr<FILE, decltype(&fclose)> file_handler(nullptr, fclose); + if (report_filename_.empty()) { + report_fp_ = stdout; + } else { + report_fp_ = fopen(report_filename_.c_str(), "w"); + if (report_fp_ == nullptr) { + PLOG(ERROR) << "failed to open file " << report_filename_; + return false; + } + file_handler.reset(report_fp_); + } PrintReportContext(); CollectReportWidth(); PrintReportHeader(); sample_tree_->VisitAllSamples( std::bind(&ReportCommand::PrintReportEntry, this, std::placeholders::_1)); - fflush(stdout); + fflush(report_fp_); + if (ferror(report_fp_) != 0) { + PLOG(ERROR) << "print report failed"; + return false; + } + return true; } void ReportCommand::PrintReportContext() { @@ -660,11 +690,11 @@ void ReportCommand::PrintReportContext() { android::base::StringPrintf("(type %u, config %llu)", event_attr_.type, event_attr_.config); } if (!record_cmdline_.empty()) { - printf("Cmdline: %s\n", record_cmdline_.c_str()); + fprintf(report_fp_, "Cmdline: %s\n", record_cmdline_.c_str()); } - printf("Samples: %" PRIu64 " of event '%s'\n", sample_tree_->TotalSamples(), - event_type_name.c_str()); - printf("Event count: %" PRIu64 "\n\n", sample_tree_->TotalPeriod()); + fprintf(report_fp_, "Samples: %" PRIu64 " of event '%s'\n", sample_tree_->TotalSamples(), + event_type_name.c_str()); + fprintf(report_fp_, "Event count: %" PRIu64 "\n\n", sample_tree_->TotalPeriod()); } void ReportCommand::CollectReportWidth() { @@ -682,9 +712,9 @@ void ReportCommand::PrintReportHeader() { for (size_t i = 0; i < displayable_items_.size(); ++i) { auto& item = displayable_items_[i]; if (i != displayable_items_.size() - 1) { - printf("%-*s ", static_cast<int>(item->Width()), item->Name().c_str()); + fprintf(report_fp_, "%-*s ", static_cast<int>(item->Width()), item->Name().c_str()); } else { - printf("%s\n", item->Name().c_str()); + fprintf(report_fp_, "%s\n", item->Name().c_str()); } } } @@ -693,9 +723,9 @@ void ReportCommand::PrintReportEntry(const SampleEntry& sample) { for (size_t i = 0; i < displayable_items_.size(); ++i) { auto& item = displayable_items_[i]; if (i != displayable_items_.size() - 1) { - printf("%-*s ", static_cast<int>(item->Width()), item->Show(sample).c_str()); + fprintf(report_fp_, "%-*s ", static_cast<int>(item->Width()), item->Show(sample).c_str()); } else { - printf("%s\n", item->Show(sample).c_str()); + fprintf(report_fp_, "%s\n", item->Show(sample).c_str()); } } if (print_callgraph_) { @@ -703,15 +733,26 @@ void ReportCommand::PrintReportEntry(const SampleEntry& sample) { } } -static void PrintCallGraphEntry(size_t depth, std::string prefix, - const std::unique_ptr<CallChainNode>& node, uint64_t parent_period, - bool last) { +void ReportCommand::PrintCallGraph(const SampleEntry& sample) { + std::string prefix = " "; + fprintf(report_fp_, "%s|\n", prefix.c_str()); + fprintf(report_fp_, "%s-- %s\n", prefix.c_str(), sample.symbol->DemangledName()); + prefix.append(3, ' '); + for (size_t i = 0; i < sample.callchain.children.size(); ++i) { + PrintCallGraphEntry(1, prefix, sample.callchain.children[i], sample.callchain.children_period, + (i + 1 == sample.callchain.children.size())); + } +} + +void ReportCommand::PrintCallGraphEntry(size_t depth, std::string prefix, + const std::unique_ptr<CallChainNode>& node, + uint64_t parent_period, bool last) { if (depth > 20) { LOG(WARNING) << "truncated callgraph at depth " << depth; return; } prefix += "|"; - printf("%s\n", prefix.c_str()); + fprintf(report_fp_, "%s\n", prefix.c_str()); if (last) { prefix.back() = ' '; } @@ -720,10 +761,10 @@ static void PrintCallGraphEntry(size_t depth, std::string prefix, double percentage = 100.0 * (node->period + node->children_period) / parent_period; percentage_s = android::base::StringPrintf("--%.2lf%%-- ", percentage); } - printf("%s%s%s\n", prefix.c_str(), percentage_s.c_str(), node->chain[0]->symbol->DemangledName()); + fprintf(report_fp_, "%s%s%s\n", prefix.c_str(), percentage_s.c_str(), node->chain[0]->symbol->DemangledName()); prefix.append(percentage_s.size(), ' '); for (size_t i = 1; i < node->chain.size(); ++i) { - printf("%s%s\n", prefix.c_str(), node->chain[i]->symbol->DemangledName()); + fprintf(report_fp_, "%s%s\n", prefix.c_str(), node->chain[i]->symbol->DemangledName()); } for (size_t i = 0; i < node->children.size(); ++i) { @@ -732,17 +773,6 @@ static void PrintCallGraphEntry(size_t depth, std::string prefix, } } -void ReportCommand::PrintCallGraph(const SampleEntry& sample) { - std::string prefix = " "; - printf("%s|\n", prefix.c_str()); - printf("%s-- %s\n", prefix.c_str(), sample.symbol->DemangledName()); - prefix.append(3, ' '); - for (size_t i = 0; i < sample.callchain.children.size(); ++i) { - PrintCallGraphEntry(1, prefix, sample.callchain.children[i], sample.callchain.children_period, - (i + 1 == sample.callchain.children.size())); - } -} - __attribute__((constructor)) static void RegisterReportCommand() { RegisterCommand("report", [] { return std::unique_ptr<Command>(new ReportCommand()); }); } diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp index 4feac19e..afdd9fdf 100644 --- a/simpleperf/cmd_report_test.cpp +++ b/simpleperf/cmd_report_test.cpp @@ -16,8 +16,13 @@ #include <gtest/gtest.h> +#include <android-base/file.h> +#include <android-base/test_utils.h> + #include "command.h" #include "event_selection_set.h" +#include "get_test_data.h" +#include "read_apk.h" static std::unique_ptr<Command> RecordCmd() { return CreateCommandInstance("record"); @@ -102,3 +107,13 @@ TEST(report_cmd, dwarf_callgraph) { << "This test does nothing as dwarf callchain sampling is not supported on this device."; } } + +TEST(report_cmd, report_symbols_of_nativelib_in_apk) { + TemporaryFile tmp_file; + ASSERT_TRUE(ReportCmd()->Run({"-i", GetTestData(NATIVELIB_IN_APK_PERF_DATA), + "--symfs", GetTestDataDir(), "-o", tmp_file.path})); + std::string content; + ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &content)); + ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)), std::string::npos); + ASSERT_NE(content.find("GlobalFunc"), std::string::npos); +} diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp index 34448066..b45e02ff 100644 --- a/simpleperf/cmd_stat_test.cpp +++ b/simpleperf/cmd_stat_test.cpp @@ -49,6 +49,16 @@ TEST(stat_cmd, event_modifier) { ASSERT_TRUE(StatCmd()->Run({"-e", "cpu-cycles:u,sched:sched_switch:k", "sleep", "1"})); } +void CreateProcesses(size_t count, std::vector<std::unique_ptr<Workload>>* workloads) { + workloads->clear(); + for (size_t i = 0; i < count; ++i) { + auto workload = Workload::CreateWorkload({"sleep", "1"}); + ASSERT_TRUE(workload != nullptr); + ASSERT_TRUE(workload->Start()); + workloads->push_back(std::move(workload)); + } +} + TEST(stat_cmd, existing_processes) { std::vector<std::unique_ptr<Workload>> workloads; CreateProcesses(2, &workloads); diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp index 6397eaa3..9c336676 100644 --- a/simpleperf/dso.cpp +++ b/simpleperf/dso.cpp @@ -26,6 +26,7 @@ #include <android-base/logging.h> #include "environment.h" +#include "read_apk.h" #include "read_elf.h" #include "utils.h" @@ -199,9 +200,14 @@ bool Dso::Load() { case DSO_KERNEL_MODULE: result = LoadKernelModule(); break; - case DSO_ELF_FILE: - result = LoadElfFile(); + case DSO_ELF_FILE: { + if (std::get<0>(SplitUrlInApk(path_))) { + result = LoadEmbeddedElfFile(); + } else { + result = LoadElfFile(); + } break; + } } if (result) { std::sort(symbols_.begin(), symbols_.end(), SymbolComparator()); @@ -305,6 +311,16 @@ bool Dso::LoadElfFile() { return loaded; } +bool Dso::LoadEmbeddedElfFile() { + std::string path = GetAccessiblePath(); + BuildId build_id = GetExpectedBuildId(path); + auto tuple = SplitUrlInApk(path); + CHECK(std::get<0>(tuple)); + return ParseSymbolsFromApkFile(std::get<1>(tuple), std::get<2>(tuple), build_id, + std::bind(ElfFileSymbolCallback, std::placeholders::_1, + this, SymbolFilterForDso)); +} + void Dso::InsertSymbol(const Symbol& symbol) { symbols_.push_back(symbol); } diff --git a/simpleperf/dso.h b/simpleperf/dso.h index a140e5e9..9697319b 100644 --- a/simpleperf/dso.h +++ b/simpleperf/dso.h @@ -93,6 +93,7 @@ struct Dso { bool LoadKernel(); bool LoadKernelModule(); bool LoadElfFile(); + bool LoadEmbeddedElfFile(); void InsertSymbol(const Symbol& symbol); void FixupSymbolLength(); diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp index 038e577e..df731f1a 100644 --- a/simpleperf/event_selection_set.cpp +++ b/simpleperf/event_selection_set.cpp @@ -211,6 +211,7 @@ bool EventSelectionSet::OpenEventFiles(const std::vector<pid_t>& threads, for (auto& cpu : cpus) { auto event_fd = EventFd::OpenEventFile(selection.event_attr, tid, cpu); if (event_fd != nullptr) { + LOG(VERBOSE) << "OpenEventFile for tid " << tid << ", cpu " << cpu; selection.event_fds.push_back(std::move(event_fd)); ++open_per_thread; } diff --git a/simpleperf/get_test_data.h b/simpleperf/get_test_data.h index 313da040..bceedc0b 100644 --- a/simpleperf/get_test_data.h +++ b/simpleperf/get_test_data.h @@ -19,6 +19,20 @@ #include <string> +#include "build_id.h" + std::string GetTestData(const std::string& filename); +const std::string& GetTestDataDir(); + +static const std::string APK_FILE = "data/app/com.example.hellojni-1/base.apk"; +static const std::string NATIVELIB_IN_APK = "lib/arm64-v8a/libhello-jni.so"; +static const std::string NATIVELIB_IN_APK_PERF_DATA = "has_embedded_native_libs_apk_perf.data"; + +constexpr size_t NATIVELIB_OFFSET_IN_APK = 0x8000; +constexpr size_t NATIVELIB_SIZE_IN_APK = 0x15d8; + +static BuildId elf_file_build_id("7600329e31058e12b145d153ef27cd40e1a5f7b9"); + +static BuildId native_lib_build_id("b46f51cb9c4b71fb08a2fdbefc2c187894f14008"); #endif // SIMPLE_PERF_GET_TEST_DATA_H_ diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp index 04412eaa..5e463ab7 100644 --- a/simpleperf/gtest_main.cpp +++ b/simpleperf/gtest_main.cpp @@ -45,3 +45,7 @@ int main(int argc, char** argv) { std::string GetTestData(const std::string& filename) { return testdata_dir + filename; } + +const std::string& GetTestDataDir() { + return testdata_dir; +} diff --git a/simpleperf/main.cpp b/simpleperf/main.cpp index 7fdae423..75313a54 100644 --- a/simpleperf/main.cpp +++ b/simpleperf/main.cpp @@ -24,6 +24,7 @@ #include "command.h" static std::map<std::string, android::base::LogSeverity> log_severity_map = { + {"verbose", android::base::VERBOSE}, {"debug", android::base::DEBUG}, {"warning", android::base::WARNING}, {"error", android::base::ERROR}, @@ -35,33 +36,32 @@ int main(int argc, char** argv) { std::vector<std::string> args; android::base::LogSeverity log_severity = android::base::WARNING; - if (argc == 1) { - args.push_back("help"); - } else { - for (int i = 1; i < argc; ++i) { - if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { - args.insert(args.begin(), "help"); - } else if (strcmp(argv[i], "--log") == 0) { - if (i + 1 < argc) { - ++i; - auto it = log_severity_map.find(argv[i]); - if (it != log_severity_map.end()) { - log_severity = it->second; - } else { - LOG(ERROR) << "Unknown log severity: " << argv[i]; - return 1; - } + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + args.insert(args.begin(), "help"); + } else if (strcmp(argv[i], "--log") == 0) { + if (i + 1 < argc) { + ++i; + auto it = log_severity_map.find(argv[i]); + if (it != log_severity_map.end()) { + log_severity = it->second; } else { - LOG(ERROR) << "Missing argument for --log option.\n"; + LOG(ERROR) << "Unknown log severity: " << argv[i]; return 1; } } else { - args.push_back(argv[i]); + LOG(ERROR) << "Missing argument for --log option.\n"; + return 1; } + } else { + args.push_back(argv[i]); } } android::base::ScopedLogSeverity severity(log_severity); + if (args.empty()) { + args.push_back("help"); + } std::unique_ptr<Command> command = CreateCommandInstance(args[0]); if (command == nullptr) { LOG(ERROR) << "malformed command line: unknown command " << args[0]; diff --git a/simpleperf/read_apk.cpp b/simpleperf/read_apk.cpp index 270b3052..55de8446 100644 --- a/simpleperf/read_apk.cpp +++ b/simpleperf/read_apk.cpp @@ -24,8 +24,6 @@ #include <sys/types.h> #include <unistd.h> -#include <map> - #include <android-base/file.h> #include <android-base/logging.h> #include <ziparchive/zip_archive.h> @@ -33,31 +31,14 @@ #include "read_elf.h" #include "utils.h" -bool IsValidJarOrApkPath(const std::string& filename) { - static const char zip_preamble[] = {0x50, 0x4b, 0x03, 0x04 }; - if (!IsRegularFile(filename)) { - return false; - } - std::string mode = std::string("rb") + CLOSE_ON_EXEC_MODE; - FILE* fp = fopen(filename.c_str(), mode.c_str()); - if (fp == nullptr) { - return false; - } - char buf[4]; - if (fread(buf, 4, 1, fp) != 1) { - fclose(fp); - return false; - } - fclose(fp); - return memcmp(buf, zip_preamble, 4) == 0; -} - class ArchiveHelper { public: - explicit ArchiveHelper(int fd) : valid_(false) { + explicit ArchiveHelper(int fd, const std::string& debug_filename) : valid_(false) { int rc = OpenArchiveFd(fd, "", &handle_, false); if (rc == 0) { valid_ = true; + } else { + LOG(ERROR) << "Failed to open archive " << debug_filename << ": " << ErrorCodeString(rc); } } ~ArchiveHelper() { @@ -73,41 +54,33 @@ class ArchiveHelper { bool valid_; }; -// First component of pair is APK file path, second is offset into APK -typedef std::pair<std::string, size_t> ApkOffset; - -class ApkInspectorImpl { - public: - EmbeddedElf *FindElfInApkByMmapOffset(const std::string& apk_path, - size_t mmap_offset); - private: - std::vector<EmbeddedElf> embedded_elf_files_; - // Value is either 0 (no elf) or 1-based slot in array above. - std::map<ApkOffset, uint32_t> cache_; -}; +std::map<ApkInspector::ApkOffset, std::unique_ptr<EmbeddedElf>> ApkInspector::embedded_elf_cache_; -EmbeddedElf *ApkInspectorImpl::FindElfInApkByMmapOffset(const std::string& apk_path, - size_t mmap_offset) -{ +EmbeddedElf* ApkInspector::FindElfInApkByOffset(const std::string& apk_path, off64_t file_offset) { // Already in cache? - ApkOffset ami(apk_path, mmap_offset); - auto it = cache_.find(ami); - if (it != cache_.end()) { - uint32_t idx = it->second; - return (idx ? &embedded_elf_files_[idx-1] : nullptr); + ApkOffset ami(apk_path, file_offset); + auto it = embedded_elf_cache_.find(ami); + if (it != embedded_elf_cache_.end()) { + return it->second.get(); } - cache_[ami] = 0u; + std::unique_ptr<EmbeddedElf> elf = FindElfInApkByOffsetWithoutCache(apk_path, file_offset); + EmbeddedElf* result = elf.get(); + embedded_elf_cache_[ami] = std::move(elf); + return result; +} +std::unique_ptr<EmbeddedElf> ApkInspector::FindElfInApkByOffsetWithoutCache(const std::string& apk_path, + off64_t file_offset) { // Crack open the apk(zip) file and take a look. - if (! IsValidJarOrApkPath(apk_path)) { + if (!IsValidApkPath(apk_path)) { return nullptr; } FileHelper fhelper(apk_path.c_str()); - if (fhelper.fd() == -1) { + if (!fhelper) { return nullptr; } - ArchiveHelper ahelper(fhelper.fd()); + ArchiveHelper ahelper(fhelper.fd(), apk_path); if (!ahelper.valid()) { return nullptr; } @@ -124,11 +97,10 @@ EmbeddedElf *ApkInspectorImpl::FindElfInApkByMmapOffset(const std::string& apk_p ZipString zname; bool found = false; int zrc; - off64_t mmap_off64 = mmap_offset; while ((zrc = Next(iteration_cookie, &zentry, &zname)) == 0) { if (zentry.method == kCompressStored && - mmap_off64 >= zentry.offset && - mmap_off64 < zentry.offset + zentry.uncompressed_length) { + file_offset >= zentry.offset && + file_offset < zentry.offset + zentry.uncompressed_length) { // Found. found = true; break; @@ -154,26 +126,87 @@ EmbeddedElf *ApkInspectorImpl::FindElfInApkByMmapOffset(const std::string& apk_p } // Elf found: add EmbeddedElf to vector, update cache. - EmbeddedElf ee(apk_path, entry_name, zentry.offset, zentry.uncompressed_length); - embedded_elf_files_.push_back(ee); - unsigned idx = embedded_elf_files_.size(); - cache_[ami] = idx; - return &embedded_elf_files_[idx-1]; + return std::unique_ptr<EmbeddedElf>(new EmbeddedElf(apk_path, entry_name, zentry.offset, + zentry.uncompressed_length)); } -// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +std::unique_ptr<EmbeddedElf> ApkInspector::FindElfInApkByName(const std::string& apk_path, + const std::string& elf_filename) { + if (!IsValidApkPath(apk_path)) { + return nullptr; + } + FileHelper fhelper(apk_path.c_str()); + if (!fhelper) { + return nullptr; + } + ArchiveHelper ahelper(fhelper.fd(), apk_path); + if (!ahelper.valid()) { + return nullptr; + } + ZipArchiveHandle& handle = ahelper.archive_handle(); + ZipEntry zentry; + int32_t rc = FindEntry(handle, ZipString(elf_filename.c_str()), &zentry); + if (rc != 0) { + LOG(ERROR) << "failed to find " << elf_filename << " in " << apk_path + << ": " << ErrorCodeString(rc); + return nullptr; + } + if (zentry.method != kCompressStored || zentry.compressed_length != zentry.uncompressed_length) { + LOG(ERROR) << "shared library " << elf_filename << " in " << apk_path << " is compressed"; + return nullptr; + } + return std::unique_ptr<EmbeddedElf>(new EmbeddedElf(apk_path, elf_filename, zentry.offset, + zentry.uncompressed_length)); +} -ApkInspector::ApkInspector() - : impl_(new ApkInspectorImpl()) -{ +bool IsValidApkPath(const std::string& apk_path) { + static const char zip_preamble[] = {0x50, 0x4b, 0x03, 0x04 }; + if (!IsRegularFile(apk_path)) { + return false; + } + std::string mode = std::string("rb") + CLOSE_ON_EXEC_MODE; + FILE* fp = fopen(apk_path.c_str(), mode.c_str()); + if (fp == nullptr) { + return false; + } + char buf[4]; + if (fread(buf, 4, 1, fp) != 1) { + fclose(fp); + return false; + } + fclose(fp); + return memcmp(buf, zip_preamble, 4) == 0; +} + +// Refer file in apk in compliance with http://developer.android.com/reference/java/net/JarURLConnection.html. +std::string GetUrlInApk(const std::string& apk_path, const std::string& elf_filename) { + return apk_path + "!/" + elf_filename; } -ApkInspector::~ApkInspector() -{ +std::tuple<bool, std::string, std::string> SplitUrlInApk(const std::string& path) { + size_t pos = path.find("!/"); + if (pos == std::string::npos) { + return std::make_tuple(false, "", ""); + } + return std::make_tuple(true, path.substr(0, pos), path.substr(pos + 2)); } -EmbeddedElf *ApkInspector::FindElfInApkByMmapOffset(const std::string& apk_path, - size_t mmap_offset) -{ - return impl_->FindElfInApkByMmapOffset(apk_path, mmap_offset); +bool GetBuildIdFromApkFile(const std::string& apk_path, const std::string& elf_filename, + BuildId* build_id) { + std::unique_ptr<EmbeddedElf> ee = ApkInspector::FindElfInApkByName(apk_path, elf_filename); + if (ee == nullptr) { + return false; + } + return GetBuildIdFromEmbeddedElfFile(apk_path, ee->entry_offset(), ee->entry_size(), build_id); +} + +bool ParseSymbolsFromApkFile(const std::string& apk_path, const std::string& elf_filename, + const BuildId& expected_build_id, + std::function<void(const ElfFileSymbol&)> callback) { + std::unique_ptr<EmbeddedElf> ee = ApkInspector::FindElfInApkByName(apk_path, elf_filename); + if (ee == nullptr) { + return false; + } + return ParseSymbolsFromEmbeddedElfFile(apk_path, ee->entry_offset(), ee->entry_size(), + expected_build_id, callback); } diff --git a/simpleperf/read_apk.h b/simpleperf/read_apk.h index c35cac73..89c051ae 100644 --- a/simpleperf/read_apk.h +++ b/simpleperf/read_apk.h @@ -17,13 +17,12 @@ #ifndef SIMPLE_PERF_READ_APK_H_ #define SIMPLE_PERF_READ_APK_H_ +#include <map> #include <memory> #include <string> +#include <tuple> -#include <android-base/file.h> - -// Exposed for unit testing -bool IsValidJarOrApkPath(const std::string& filename); +#include "read_elf.h" // Container for info an on ELF file embedded into an APK file class EmbeddedElf { @@ -64,36 +63,38 @@ class EmbeddedElf { uint32_t entry_size_; // size of ELF file in zip }; -struct EmbeddedElfComparator { - bool operator()(const EmbeddedElf& ee1, const EmbeddedElf& ee2) { - int res1 = ee1.filepath().compare(ee2.filepath()); - if (res1 != 0) { - return res1 < 0; - } - int res2 = ee1.entry_name().compare(ee2.entry_name()); - if (res2 != 0) { - return res2 < 0; - } - return ee1.entry_offset() < ee2.entry_offset(); - } -}; - -class ApkInspectorImpl; - // APK inspector helper class class ApkInspector { public: - ApkInspector(); - ~ApkInspector(); - // Given an APK/ZIP/JAR file and an offset into that file, if the // corresponding region of the APK corresponds to an uncompressed // ELF file, then return pertinent info on the ELF. - EmbeddedElf *FindElfInApkByMmapOffset(const std::string& apk_path, - size_t mmap_offset); + static EmbeddedElf* FindElfInApkByOffset(const std::string& apk_path, off64_t file_offset); + static std::unique_ptr<EmbeddedElf> FindElfInApkByName(const std::string& apk_path, + const std::string& elf_filename); private: - std::unique_ptr<ApkInspectorImpl> impl_; + static std::unique_ptr<EmbeddedElf> FindElfInApkByOffsetWithoutCache(const std::string& apk_path, + off64_t file_offset); + + // First component of pair is APK file path, second is offset into APK. + typedef std::pair<std::string, size_t> ApkOffset; + + static std::map<ApkOffset, std::unique_ptr<EmbeddedElf>> embedded_elf_cache_; }; +// Export for test only. +bool IsValidApkPath(const std::string& apk_path); + +std::string GetUrlInApk(const std::string& apk_path, const std::string& elf_filename); +std::tuple<bool, std::string, std::string> SplitUrlInApk(const std::string& path); + +bool GetBuildIdFromApkFile(const std::string& apk_path, const std::string& elf_filename, + BuildId* build_id); + +bool ParseSymbolsFromApkFile(const std::string& apk_path, const std::string& elf_filename, + const BuildId& expected_build_id, + std::function<void(const ElfFileSymbol&)> callback); + + #endif // SIMPLE_PERF_READ_APK_H_ diff --git a/simpleperf/read_apk_test.cpp b/simpleperf/read_apk_test.cpp index 5824f4b7..aeedbbe3 100644 --- a/simpleperf/read_apk_test.cpp +++ b/simpleperf/read_apk_test.cpp @@ -18,24 +18,44 @@ #include <gtest/gtest.h> #include "get_test_data.h" +#include "test_util.h" -static const std::string fibjar = "fibonacci.jar"; -static const std::string jniapk = "has_embedded_native_libs.apk"; -TEST(read_apk, IsValidJarOrApkPath) { - ASSERT_FALSE(IsValidJarOrApkPath("/dev/zero")); - ASSERT_FALSE(IsValidJarOrApkPath(GetTestData("elf_file"))); - ASSERT_TRUE(IsValidJarOrApkPath(GetTestData(fibjar))); +TEST(read_apk, IsValidApkPath) { + ASSERT_FALSE(IsValidApkPath("/dev/zero")); + ASSERT_FALSE(IsValidApkPath(GetTestData("elf_file"))); + ASSERT_TRUE(IsValidApkPath(GetTestData(APK_FILE))); } -TEST(read_apk, CollectEmbeddedElfInfoFromApk) { +TEST(read_apk, FindElfInApkByOffset) { ApkInspector inspector; - ASSERT_TRUE(inspector.FindElfInApkByMmapOffset("/dev/null", 0) == nullptr); - ASSERT_TRUE(inspector.FindElfInApkByMmapOffset(GetTestData(fibjar), 0) == nullptr); - ASSERT_TRUE(inspector.FindElfInApkByMmapOffset(GetTestData(jniapk), 0) == nullptr); - EmbeddedElf *ee1 = inspector.FindElfInApkByMmapOffset(GetTestData(jniapk), 0x91000); - ASSERT_TRUE(ee1 != nullptr); - ASSERT_EQ(ee1->entry_name(), "lib/armeabi-v7a/libframeworks_coretests_jni.so"); - ASSERT_TRUE(ee1->entry_offset() == 593920); - ASSERT_TRUE(ee1->entry_size() == 13904); + ASSERT_TRUE(inspector.FindElfInApkByOffset("/dev/null", 0) == nullptr); + ASSERT_TRUE(inspector.FindElfInApkByOffset(GetTestData(APK_FILE), 0) == nullptr); + EmbeddedElf* ee = inspector.FindElfInApkByOffset(GetTestData(APK_FILE), 0x9000); + ASSERT_TRUE(ee != nullptr); + ASSERT_EQ(ee->entry_name(), NATIVELIB_IN_APK); + ASSERT_EQ(NATIVELIB_OFFSET_IN_APK, ee->entry_offset()); + ASSERT_EQ(NATIVELIB_SIZE_IN_APK, ee->entry_size()); +} + +TEST(read_apk, FindElfInApkByName) { + ASSERT_TRUE(ApkInspector::FindElfInApkByName("/dev/null", "") == nullptr); + ASSERT_TRUE(ApkInspector::FindElfInApkByName(GetTestData(APK_FILE), "") == nullptr); + auto ee = ApkInspector::FindElfInApkByName(GetTestData(APK_FILE), NATIVELIB_IN_APK); + ASSERT_TRUE(ee != nullptr); + ASSERT_EQ(NATIVELIB_OFFSET_IN_APK, ee->entry_offset()); + ASSERT_EQ(NATIVELIB_SIZE_IN_APK, ee->entry_size()); +} + +TEST(read_apk, GetBuildIdFromApkFile) { + BuildId build_id; + ASSERT_TRUE(GetBuildIdFromApkFile(GetTestData(APK_FILE), NATIVELIB_IN_APK, &build_id)); + ASSERT_EQ(build_id, native_lib_build_id); +} + +TEST(read_apk, ParseSymbolsFromApkFile) { + std::map<std::string, ElfFileSymbol> symbols; + ASSERT_TRUE(ParseSymbolsFromApkFile(GetTestData(APK_FILE), NATIVELIB_IN_APK, native_lib_build_id, + std::bind(ParseSymbol, std::placeholders::_1, &symbols))); + CheckElfFileSymbols(symbols); } diff --git a/simpleperf/read_elf.cpp b/simpleperf/read_elf.cpp index 03bdcc56..e66e68e6 100644 --- a/simpleperf/read_elf.cpp +++ b/simpleperf/read_elf.cpp @@ -19,7 +19,6 @@ #include <stdio.h> #include <string.h> -#include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> @@ -44,15 +43,6 @@ #define ELF_NOTE_GNU "GNU" #define NT_GNU_BUILD_ID 3 -FileHelper::FileHelper(const char *filename) : fd_(-1) -{ - fd_ = TEMP_FAILURE_RETRY(open(filename, O_RDONLY | O_BINARY)); -} - -FileHelper::~FileHelper() -{ - if (fd_ != -1) { close(fd_); } -} bool IsValidElfFile(int fd) { static const char elf_magic[] = {0x7f, 'E', 'L', 'F'}; @@ -145,62 +135,65 @@ static bool GetBuildIdFromObjectFile(llvm::object::ObjectFile* obj, BuildId* bui return result; } -bool GetBuildIdFromEmbeddedElfFile(const std::string& filename, - uint64_t offsetInFile, - uint32_t sizeInFile, - BuildId* build_id) { - FileHelper opener(filename.c_str()); - if (opener.fd() == -1) { - LOG(DEBUG) << "unable to open " << filename - << "to collect embedded ELF build id"; - return false; +struct BinaryRet { + llvm::object::OwningBinary<llvm::object::Binary> binary; + llvm::object::ObjectFile* obj; + + BinaryRet() : obj(nullptr) { } - llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> bufferOrErr = - llvm::MemoryBuffer::getOpenFileSlice(opener.fd(), filename, sizeInFile, - offsetInFile); - if (std::error_code EC = bufferOrErr.getError()) { - LOG(DEBUG) << "MemoryBuffer::getOpenFileSlice failed opening " - << filename << "while collecting embedded ELF build id: " - << EC.message(); - return false; +}; + +static BinaryRet OpenObjectFile(const std::string& filename, uint64_t file_offset = 0, + uint64_t file_size = 0) { + BinaryRet ret; + FileHelper fhelper(filename); + if (!fhelper) { + PLOG(DEBUG) << "failed to open " << filename; + return ret; } - std::unique_ptr<llvm::MemoryBuffer> buffer = std::move(bufferOrErr.get()); - llvm::LLVMContext *context = nullptr; - llvm::ErrorOr<std::unique_ptr<llvm::object::Binary>> binaryOrErr = - llvm::object::createBinary(buffer->getMemBufferRef(), context); - if (std::error_code EC = binaryOrErr.getError()) { - LOG(DEBUG) << "llvm::object::createBinary failed opening " - << filename << "while collecting embedded ELF build id: " - << EC.message(); - return false; + if (file_size == 0) { + file_size = GetFileSize(filename); + if (file_size == 0) { + PLOG(ERROR) << "failed to get size of file " << filename; + return ret; + } } - std::unique_ptr<llvm::object::Binary> binary = std::move(binaryOrErr.get()); - auto obj = llvm::dyn_cast<llvm::object::ObjectFile>(binary.get()); - if (obj == nullptr) { - LOG(DEBUG) << "unable to cast to interpret contents of " << filename - << "at offset " << offsetInFile - << ": failed to cast to llvm::object::ObjectFile"; - return false; + auto buffer_or_err = llvm::MemoryBuffer::getOpenFileSlice(fhelper.fd(), filename, file_size, file_offset); + if (!buffer_or_err) { + LOG(ERROR) << "failed to read " << filename << " [" << file_offset << "-" << (file_offset + file_size) + << "]: " << buffer_or_err.getError().message(); + return ret; + } + auto binary_or_err = llvm::object::createBinary(buffer_or_err.get()->getMemBufferRef()); + if (!binary_or_err) { + LOG(ERROR) << filename << " [" << file_offset << "-" << (file_offset + file_size) + << "] is not a binary file: " << binary_or_err.getError().message(); + return ret; + } + ret.binary = llvm::object::OwningBinary<llvm::object::Binary>(std::move(binary_or_err.get()), + std::move(buffer_or_err.get())); + ret.obj = llvm::dyn_cast<llvm::object::ObjectFile>(ret.binary.getBinary()); + if (ret.obj == nullptr) { + LOG(ERROR) << filename << " [" << file_offset << "-" << (file_offset + file_size) + << "] is not an object file"; } - return GetBuildIdFromObjectFile(obj, build_id); + return ret; } bool GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id) { if (!IsValidElfPath(filename)) { return false; } - auto owning_binary = llvm::object::createBinary(llvm::StringRef(filename)); - if (owning_binary.getError()) { - PLOG(DEBUG) << "can't open file " << filename; - return false; - } - llvm::object::Binary* binary = owning_binary.get().getBinary(); - auto obj = llvm::dyn_cast<llvm::object::ObjectFile>(binary); - if (obj == nullptr) { - LOG(DEBUG) << filename << " is not an object file"; + return GetBuildIdFromEmbeddedElfFile(filename, 0, 0, build_id); +} + +bool GetBuildIdFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset, + uint32_t file_size, BuildId* build_id) { + BinaryRet ret = OpenObjectFile(filename, file_offset, file_size); + if (ret.obj == nullptr) { return false; } - return GetBuildIdFromObjectFile(obj, build_id); + return GetBuildIdFromObjectFile(ret.obj, build_id); } bool IsArmMappingSymbol(const char* name) { @@ -274,31 +267,22 @@ void ParseSymbolsFromELFFile(const llvm::object::ELFFile<ELFT>* elf, } } -static llvm::object::ObjectFile* GetObjectFile( - llvm::ErrorOr<llvm::object::OwningBinary<llvm::object::Binary>>& owning_binary, - const std::string& filename, const BuildId& expected_build_id) { - if (owning_binary.getError()) { - PLOG(DEBUG) << "can't open file '" << filename << "'"; - return nullptr; +bool MatchBuildId(llvm::object::ObjectFile* obj, const BuildId& expected_build_id, + const std::string& debug_filename) { + if (expected_build_id.IsEmpty()) { + return true; } - llvm::object::Binary* binary = owning_binary.get().getBinary(); - auto obj = llvm::dyn_cast<llvm::object::ObjectFile>(binary); - if (obj == nullptr) { - LOG(DEBUG) << filename << " is not an object file"; - return nullptr; + BuildId real_build_id; + if (!GetBuildIdFromObjectFile(obj, &real_build_id)) { + return false; } - if (!expected_build_id.IsEmpty()) { - BuildId real_build_id; - GetBuildIdFromObjectFile(obj, &real_build_id); - bool result = (expected_build_id == real_build_id); - LOG(DEBUG) << "check build id for \"" << filename << "\" (" << (result ? "match" : "mismatch") - << "): expected " << expected_build_id.ToString() << ", real " - << real_build_id.ToString(); - if (!result) { - return nullptr; - } + if (expected_build_id != real_build_id) { + LOG(DEBUG) << "build id for " << debug_filename << " mismatch: " + << "expected " << expected_build_id.ToString() + << ", real " << real_build_id.ToString(); + return false; } - return obj; + return true; } bool ParseSymbolsFromElfFile(const std::string& filename, const BuildId& expected_build_id, @@ -306,18 +290,22 @@ bool ParseSymbolsFromElfFile(const std::string& filename, const BuildId& expecte if (!IsValidElfPath(filename)) { return false; } - auto owning_binary = llvm::object::createBinary(llvm::StringRef(filename)); - llvm::object::ObjectFile* obj = GetObjectFile(owning_binary, filename, expected_build_id); - if (obj == nullptr) { + return ParseSymbolsFromEmbeddedElfFile(filename, 0, 0, expected_build_id, callback); +} + +bool ParseSymbolsFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset, + uint32_t file_size, const BuildId& expected_build_id, + std::function<void(const ElfFileSymbol&)> callback) { + BinaryRet ret = OpenObjectFile(filename, file_offset, file_size); + if (ret.obj == nullptr || !MatchBuildId(ret.obj, expected_build_id, filename)) { return false; } - - if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(obj)) { + if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(ret.obj)) { ParseSymbolsFromELFFile(elf->getELFFile(), callback); - } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(obj)) { + } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(ret.obj)) { ParseSymbolsFromELFFile(elf->getELFFile(), callback); } else { - LOG(ERROR) << "unknown elf format in file" << filename; + LOG(ERROR) << "unknown elf format in file " << filename; return false; } return true; @@ -347,16 +335,15 @@ bool ReadMinExecutableVirtualAddressFromElfFile(const std::string& filename, if (!IsValidElfPath(filename)) { return false; } - auto owning_binary = llvm::object::createBinary(llvm::StringRef(filename)); - llvm::object::ObjectFile* obj = GetObjectFile(owning_binary, filename, expected_build_id); - if (obj == nullptr) { + BinaryRet ret = OpenObjectFile(filename); + if (ret.obj == nullptr || !MatchBuildId(ret.obj, expected_build_id, filename)) { return false; } bool result = false; - if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(obj)) { + if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(ret.obj)) { result = ReadMinExecutableVirtualAddress(elf->getELFFile(), min_vaddr); - } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(obj)) { + } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(ret.obj)) { result = ReadMinExecutableVirtualAddress(elf->getELFFile(), min_vaddr); } else { LOG(ERROR) << "unknown elf format in file" << filename; diff --git a/simpleperf/read_elf.h b/simpleperf/read_elf.h index d0626b24..11dc8d84 100644 --- a/simpleperf/read_elf.h +++ b/simpleperf/read_elf.h @@ -23,10 +23,8 @@ bool GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id); bool GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id); -bool GetBuildIdFromEmbeddedElfFile(const std::string& filename, - uint64_t offsetInFile, - uint32_t sizeInFile, - BuildId* build_id); +bool GetBuildIdFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset, + uint32_t file_size, BuildId* build_id); // The symbol prefix used to indicate that the symbol belongs to android linker. static const std::string linker_prefix = "__dl_"; @@ -45,22 +43,14 @@ struct ElfFileSymbol { bool ParseSymbolsFromElfFile(const std::string& filename, const BuildId& expected_build_id, std::function<void(const ElfFileSymbol&)> callback); +bool ParseSymbolsFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset, + uint32_t file_size, const BuildId& expected_build_id, + std::function<void(const ElfFileSymbol&)> callback); bool ReadMinExecutableVirtualAddressFromElfFile(const std::string& filename, const BuildId& expected_build_id, uint64_t* min_addr); -// Opens file in constructor, then closes file when object is destroyed. -class FileHelper { - public: - explicit FileHelper(const char *filename); - ~FileHelper(); - int fd() const { return fd_; } - - private: - int fd_; -}; - // Expose the following functions for unit tests. bool IsArmMappingSymbol(const char* name); bool IsValidElfFile(int fd); diff --git a/simpleperf/read_elf_test.cpp b/simpleperf/read_elf_test.cpp index 7a5194ea..920da276 100644 --- a/simpleperf/read_elf_test.cpp +++ b/simpleperf/read_elf_test.cpp @@ -21,22 +21,24 @@ #include <map> #include "get_test_data.h" -static const unsigned char elf_file_build_id[] = { - 0x76, 0x00, 0x32, 0x9e, 0x31, 0x05, 0x8e, 0x12, 0xb1, 0x45, - 0xd1, 0x53, 0xef, 0x27, 0xcd, 0x40, 0xe1, 0xa5, 0xf7, 0xb9 -}; - TEST(read_elf, GetBuildIdFromElfFile) { BuildId build_id; ASSERT_TRUE(GetBuildIdFromElfFile(GetTestData("elf_file"), &build_id)); ASSERT_EQ(build_id, BuildId(elf_file_build_id)); } -static void ParseSymbol(const ElfFileSymbol& symbol, std::map<std::string, ElfFileSymbol>* symbols) { +TEST(read_elf, GetBuildIdFromEmbeddedElfFile) { + BuildId build_id; + ASSERT_TRUE(GetBuildIdFromEmbeddedElfFile(GetTestData(APK_FILE), NATIVELIB_OFFSET_IN_APK, + NATIVELIB_SIZE_IN_APK, &build_id)); + ASSERT_EQ(build_id, native_lib_build_id); +} + +void ParseSymbol(const ElfFileSymbol& symbol, std::map<std::string, ElfFileSymbol>* symbols) { (*symbols)[symbol.name] = symbol; } -static void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols) { +void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols) { auto pos = symbols.find("GlobalVar"); ASSERT_NE(pos, symbols.end()); ASSERT_FALSE(pos->second.is_func); @@ -47,28 +49,34 @@ static void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symb } TEST(read_elf, parse_symbols_from_elf_file_with_correct_build_id) { - BuildId build_id(elf_file_build_id); std::map<std::string, ElfFileSymbol> symbols; - ASSERT_TRUE(ParseSymbolsFromElfFile(GetTestData("elf_file"), build_id, + ASSERT_TRUE(ParseSymbolsFromElfFile(GetTestData("elf_file"), elf_file_build_id, std::bind(ParseSymbol, std::placeholders::_1, &symbols))); CheckElfFileSymbols(symbols); } TEST(read_elf, parse_symbols_from_elf_file_without_build_id) { - BuildId build_id; std::map<std::string, ElfFileSymbol> symbols; - ASSERT_TRUE(ParseSymbolsFromElfFile(GetTestData("elf_file"), build_id, + ASSERT_TRUE(ParseSymbolsFromElfFile(GetTestData("elf_file"), BuildId(), std::bind(ParseSymbol, std::placeholders::_1, &symbols))); CheckElfFileSymbols(symbols); } TEST(read_elf, parse_symbols_from_elf_file_with_wrong_build_id) { - BuildId build_id("wrong_build_id"); + BuildId build_id("01010101010101010101"); std::map<std::string, ElfFileSymbol> symbols; ASSERT_FALSE(ParseSymbolsFromElfFile(GetTestData("elf_file"), build_id, std::bind(ParseSymbol, std::placeholders::_1, &symbols))); } +TEST(read_elf, ParseSymbolsFromEmbeddedElfFile) { + std::map<std::string, ElfFileSymbol> symbols; + ASSERT_TRUE(ParseSymbolsFromEmbeddedElfFile(GetTestData(APK_FILE), NATIVELIB_OFFSET_IN_APK, + NATIVELIB_SIZE_IN_APK, native_lib_build_id, + std::bind(ParseSymbol, std::placeholders::_1, &symbols))); + CheckElfFileSymbols(symbols); +} + TEST(read_elf, arm_mapping_symbol) { ASSERT_TRUE(IsArmMappingSymbol("$a")); ASSERT_FALSE(IsArmMappingSymbol("$b")); diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp index 26bc588a..660634c7 100644 --- a/simpleperf/record.cpp +++ b/simpleperf/record.cpp @@ -73,25 +73,7 @@ size_t SampleId::CreateContent(const perf_event_attr& attr) { sample_id_all = attr.sample_id_all; sample_type = attr.sample_type; // Other data are not necessary. TODO: Set missing SampleId data. - size_t size = 0; - if (sample_id_all) { - if (sample_type & PERF_SAMPLE_TID) { - size += sizeof(PerfSampleTidType); - } - if (sample_type & PERF_SAMPLE_TIME) { - size += sizeof(PerfSampleTimeType); - } - if (sample_type & PERF_SAMPLE_ID) { - size += sizeof(PerfSampleIdType); - } - if (sample_type & PERF_SAMPLE_STREAM_ID) { - size += sizeof(PerfSampleStreamIdType); - } - if (sample_type & PERF_SAMPLE_CPU) { - size += sizeof(PerfSampleCpuType); - } - } - return size; + return Size(); } void SampleId::ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end) { @@ -161,6 +143,28 @@ void SampleId::Dump(size_t indent) const { } } +size_t SampleId::Size() const { + size_t size = 0; + if (sample_id_all) { + if (sample_type & PERF_SAMPLE_TID) { + size += sizeof(PerfSampleTidType); + } + if (sample_type & PERF_SAMPLE_TIME) { + size += sizeof(PerfSampleTimeType); + } + if (sample_type & PERF_SAMPLE_ID) { + size += sizeof(PerfSampleIdType); + } + if (sample_type & PERF_SAMPLE_STREAM_ID) { + size += sizeof(PerfSampleStreamIdType); + } + if (sample_type & PERF_SAMPLE_CPU) { + size += sizeof(PerfSampleCpuType); + } + } + return size; +} + Record::Record() { memset(&header, 0, sizeof(header)); } @@ -202,6 +206,10 @@ std::vector<char> MmapRecord::BinaryFormat() const { return buf; } +void MmapRecord::AdjustSizeBasedOnData() { + header.size = sizeof(header) + sizeof(data) + ALIGN(filename.size() + 1, 8) + sample_id.Size(); +} + void MmapRecord::DumpData(size_t indent) const { PrintIndented(indent, "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n", data.pid, data.tid, data.addr, data.len); @@ -230,6 +238,10 @@ std::vector<char> Mmap2Record::BinaryFormat() const { return buf; } +void Mmap2Record::AdjustSizeBasedOnData() { + header.size = sizeof(header) + sizeof(data) + ALIGN(filename.size() + 1, 8) + sample_id.Size(); +} + void Mmap2Record::DumpData(size_t indent) const { PrintIndented(indent, "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n", data.pid, data.tid, data.addr, data.len); @@ -437,7 +449,8 @@ std::vector<char> SampleRecord::BinaryFormat() const { void SampleRecord::AdjustSizeBasedOnData() { size_t size = BinaryFormat().size(); - LOG(DEBUG) << "SampleRecord size is changed from " << header.size << " to " << size; + LOG(DEBUG) << "Record (type " << RecordTypeToString(header.type) << ") size is changed from " + << header.size << " to " << size; header.size = size; } @@ -632,18 +645,6 @@ MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_ return record; } -void UpdateMmapRecord(MmapRecord *record, const std::string& new_filename, uint64_t new_pgoff) -{ - size_t new_filename_size = ALIGN(new_filename.size() + 1, 8); - size_t old_filename_size = ALIGN(record->filename.size() + 1, 8); - record->data.pgoff = new_pgoff; - record->filename = new_filename; - if (new_filename_size > old_filename_size) - record->header.size += (new_filename_size - old_filename_size); - else if (new_filename_size < old_filename_size) - record->header.size += (old_filename_size - new_filename_size); -} - CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, const std::string& comm) { CommRecord record; diff --git a/simpleperf/record.h b/simpleperf/record.h index 26e45990..a94a9179 100644 --- a/simpleperf/record.h +++ b/simpleperf/record.h @@ -125,6 +125,7 @@ struct SampleId { // Write the binary format of sample_id to the buffer pointed by p. void WriteToBinaryFormat(char*& p) const; void Dump(size_t indent) const; + size_t Size() const; }; // Usually one record contains the following three parts in order in binary format: @@ -173,6 +174,7 @@ struct MmapRecord : public Record { MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader); std::vector<char> BinaryFormat() const override; + void AdjustSizeBasedOnData(); protected: void DumpData(size_t indent) const override; @@ -197,6 +199,7 @@ struct Mmap2Record : public Record { Mmap2Record(const perf_event_attr& attr, const perf_event_header* pheader); std::vector<char> BinaryFormat() const override; + void AdjustSizeBasedOnData(); protected: void DumpData(size_t indent) const override; @@ -357,7 +360,6 @@ std::unique_ptr<Record> ReadRecordFromFile(const perf_event_attr& attr, FILE* fp MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid, uint64_t addr, uint64_t len, uint64_t pgoff, const std::string& filename); -void UpdateMmapRecord(MmapRecord *record, const std::string& new_filename, uint64_t new_pgoff); CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, const std::string& comm); ForkRecord CreateForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, uint32_t ppid, diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h index 34155a3a..734a48b6 100644 --- a/simpleperf/test_util.h +++ b/simpleperf/test_util.h @@ -14,14 +14,12 @@ * limitations under the License. */ +#include <map> + +#include "read_elf.h" #include "workload.h" -static void CreateProcesses(size_t count, std::vector<std::unique_ptr<Workload>>* workloads) { - workloads->clear(); - for (size_t i = 0; i < count; ++i) { - auto workload = Workload::CreateWorkload({"sleep", "1"}); - ASSERT_TRUE(workload != nullptr); - ASSERT_TRUE(workload->Start()); - workloads->push_back(std::move(workload)); - } -} +void CreateProcesses(size_t count, std::vector<std::unique_ptr<Workload>>* workloads); + +void ParseSymbol(const ElfFileSymbol& symbol, std::map<std::string, ElfFileSymbol>* symbols); +void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols); diff --git a/simpleperf/testdata/data/app/com.example.hellojni-1/base.apk b/simpleperf/testdata/data/app/com.example.hellojni-1/base.apk Binary files differnew file mode 100644 index 00000000..c757e9e8 --- /dev/null +++ b/simpleperf/testdata/data/app/com.example.hellojni-1/base.apk diff --git a/simpleperf/testdata/fibonacci.jar b/simpleperf/testdata/fibonacci.jar Binary files differdeleted file mode 100644 index df57e40f..00000000 --- a/simpleperf/testdata/fibonacci.jar +++ /dev/null diff --git a/simpleperf/testdata/has_embedded_native_libs.apk b/simpleperf/testdata/has_embedded_native_libs.apk Binary files differdeleted file mode 100644 index 2a1924cd..00000000 --- a/simpleperf/testdata/has_embedded_native_libs.apk +++ /dev/null diff --git a/simpleperf/testdata/has_embedded_native_libs_apk_perf.data b/simpleperf/testdata/has_embedded_native_libs_apk_perf.data Binary files differnew file mode 100644 index 00000000..f85c9d3f --- /dev/null +++ b/simpleperf/testdata/has_embedded_native_libs_apk_perf.data diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp index eabad29f..2e68767a 100644 --- a/simpleperf/utils.cpp +++ b/simpleperf/utils.cpp @@ -18,6 +18,7 @@ #include <dirent.h> #include <errno.h> +#include <fcntl.h> #include <stdarg.h> #include <stdio.h> #include <sys/stat.h> @@ -26,6 +27,7 @@ #include <algorithm> #include <string> +#include <android-base/file.h> #include <android-base/logging.h> void OneTimeFreeAllocator::Clear() { @@ -52,6 +54,19 @@ const char* OneTimeFreeAllocator::AllocateString(const std::string& s) { return result; } +FileHelper::FileHelper() : fd_(-1) { +} + +FileHelper::FileHelper(const std::string& filename) { + fd_ = TEMP_FAILURE_RETRY(open(filename.c_str(), O_RDONLY | O_BINARY)); +} + +FileHelper::~FileHelper() { + if (fd_ != -1) { + close(fd_); + } +} + void PrintIndented(size_t indent, const char* fmt, ...) { va_list ap; va_start(ap, fmt); @@ -114,3 +129,11 @@ bool IsRegularFile(const std::string& filename) { } return false; } + +uint64_t GetFileSize(const std::string& filename) { + struct stat st; + if (stat(filename.c_str(), &st) == 0) { + return static_cast<uint64_t>(st.st_size); + } + return 0; +} diff --git a/simpleperf/utils.h b/simpleperf/utils.h index 2ce0726d..6581a76d 100644 --- a/simpleperf/utils.h +++ b/simpleperf/utils.h @@ -22,6 +22,8 @@ #include <string> #include <vector> +#include <android-base/macros.h> + #define ALIGN(value, alignment) (((value) + (alignment)-1) & ~((alignment)-1)) #ifdef _WIN32 @@ -52,6 +54,26 @@ class OneTimeFreeAllocator { char* end_; }; +class FileHelper { + public: + FileHelper(); + explicit FileHelper(const std::string& filename); + ~FileHelper(); + + explicit operator bool() const { + return fd_ != -1; + } + + int fd() const { + return fd_; + } + + private: + int fd_; + + DISALLOW_COPY_AND_ASSIGN(FileHelper); +}; + template <class T> void MoveFromBinaryFormat(T& data, const char*& p) { data = *reinterpret_cast<const T*>(p); @@ -66,5 +88,6 @@ void GetEntriesInDir(const std::string& dirpath, std::vector<std::string>* files std::vector<std::string>* subdirs); bool IsDir(const std::string& dirpath); bool IsRegularFile(const std::string& filename); +uint64_t GetFileSize(const std::string& filename); #endif // SIMPLE_PERF_UTILS_H_ |