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_;
}
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;
"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);
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));
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);
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.
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) {
return false;
}
}
-
+ LOG(VERBOSE) << "Record " << sample_record_count_ << " samples.";
return true;
}
}
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);
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";
}
}
// 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));
}
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() {
" -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"
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_ =
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_;
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) {
ReadSampleTreeFromRecordFile();
// 3. Show collected information.
- PrintReport();
+ if (!PrintReport()) {
+ return false;
+ }
return true;
}
} 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)) {
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() {
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() {
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());
}
}
}
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_) {
}
}
-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() = ' ';
}
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) {
}
}
-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()); });
}
#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");
<< "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);
+}
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);
#include <android-base/logging.h>
#include "environment.h"
+#include "read_apk.h"
#include "read_elf.h"
#include "utils.h"
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());
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);
}
bool LoadKernel();
bool LoadKernelModule();
bool LoadElfFile();
+ bool LoadEmbeddedElfFile();
void InsertSymbol(const Symbol& symbol);
void FixupSymbolLength();
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;
}
#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_
std::string GetTestData(const std::string& filename) {
return testdata_dir + filename;
}
+
+const std::string& GetTestDataDir() {
+ return testdata_dir;
+}
#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},
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];
#include <sys/types.h>
#include <unistd.h>
-#include <map>
-
#include <android-base/file.h>
#include <android-base/logging.h>
#include <ziparchive/zip_archive.h>
#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() {
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;
}
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;
}
// 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);
}
#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 {
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_
#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);
}
#include <stdio.h>
#include <string.h>
-#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#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'};
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) {
}
}
-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,
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;
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;
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_";
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);
#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);
}
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"));
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) {
}
}
+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));
}
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);
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);
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;
}
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;
// 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:
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;
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;
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,
* 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);
#include <dirent.h>
#include <errno.h>
+#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/stat.h>
#include <algorithm>
#include <string>
+#include <android-base/file.h>
#include <android-base/logging.h>
void OneTimeFreeAllocator::Clear() {
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);
}
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;
+}
#include <string>
#include <vector>
+#include <android-base/macros.h>
+
#define ALIGN(value, alignment) (((value) + (alignment)-1) & ~((alignment)-1))
#ifdef _WIN32
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);
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_