}
};
-// Match the format of JITCodeEntry in art/runtime/jit/debugger_itnerface.cc.
+// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc.
template <typename ADDRT>
struct __attribute__((packed)) PackedJITCodeEntry {
ADDRT next_addr;
#include <unwindstack/MachineArm64.h>
#include <unwindstack/MachineX86.h>
#include <unwindstack/MachineX86_64.h>
+#include <unwindstack/Maps.h>
#include <unwindstack/Regs.h>
#include <unwindstack/RegsArm.h>
#include <unwindstack/RegsArm64.h>
#include "read_apk.h"
#include "thread_tree.h"
+static_assert(simpleperf::map_flags::PROT_JIT_SYMFILE_MAP == PROT_JIT_SYMFILE_MAP, "");
+static_assert(simpleperf::map_flags::PROT_JIT_SYMFILE_MAP ==
+ unwindstack::MAPS_FLAGS_JIT_SYMFILE_MAP, "");
+
namespace simpleperf {
static unwindstack::Regs* GetBacktraceRegs(const RegSet& regs) {
}
}
}
- bt_map.flags = PROT_READ | PROT_EXEC;
+ bt_map.flags = PROT_READ | PROT_EXEC | map->flags;
}
cached_map.map.reset(BacktraceMap::CreateOffline(thread.pid, bt_maps));
if (!cached_map.map) {
trace_offcpu_(false),
exclude_kernel_callchain_(false),
allow_callchain_joiner_(true),
- callchain_joiner_min_matching_nodes_(1u) {
+ callchain_joiner_min_matching_nodes_(1u),
+ last_record_timestamp_(0u) {
// If we run `adb shell simpleperf record xxx` and stop profiling by ctrl-c, adb closes
// sockets connecting simpleperf. After that, simpleperf will receive SIGPIPE when writing
// to stdout/stderr, which is a problem when we use '--app' option. So ignore SIGPIPE to
std::unique_ptr<CallChainJoiner> callchain_joiner_;
std::unique_ptr<JITDebugReader> jit_debug_reader_;
+ uint64_t last_record_timestamp_; // used to insert Mmap2Records for JIT debug info
};
bool RecordCommand::Run(const std::vector<std::string>& args) {
}
}
// Profiling JITed/interpreted code is supported starting from Android P.
- if (app_pid != 0 && GetAndroidVersion() >= 9) {
+ const int kAndroidVersionP = 9;
+ if (app_pid != 0 && GetAndroidVersion() >= kAndroidVersionP) {
// JIT symfiles are stored in temporary files, and are deleted after recording. But if
// `-g --no-unwind` option is used, we want to keep symfiles to support unwinding in
// the debug-unwind cmd.
if (!UpdateJITDebugInfo()) {
return false;
}
- if (!loop->AddPeriodicEvent(SecondToTimeval(0.1), [&]() { return UpdateJITDebugInfo(); })) {
+ // It takes about 30us-130us on Pixel (depending on the cpu frequency) to check update when
+ // no update happens (most time spent in process_vm_preadv). We want to know the JIT debug
+ // info change as soon as possible, while not wasting too much time checking updates. So use
+ // a period of 100 ms.
+ const double kUpdateJITDebugInfoPeriodInSecond = 0.1;
+ if (!loop->AddPeriodicEvent(SecondToTimeval(kUpdateJITDebugInfoPeriodInSecond),
+ [&]() { return UpdateJITDebugInfo(); })) {
return false;
}
}
}
bool RecordCommand::ProcessRecord(Record* record) {
+ last_record_timestamp_ = record->Timestamp();
if (unwind_dwarf_callchain_) {
if (post_unwind_) {
return SaveRecordForPostUnwinding(record);
std::vector<JITSymFile> jit_symfiles;
std::vector<DexSymFile> dex_symfiles;
jit_debug_reader_->ReadUpdate(&jit_symfiles, &dex_symfiles);
- // TODO: Handle jit/dex symfiles.
+ if (jit_symfiles.empty() && dex_symfiles.empty()) {
+ return true;
+ }
+ // Process records before dumping symfiles, so new symfiles won't affect old samples.
+ if (!event_selection_set_.ReadMmapEventData()) {
+ return false;
+ }
+ EventAttrWithId attr_id = event_selection_set_.GetEventAttrWithId()[0];
+ for (auto& symfile : jit_symfiles) {
+ Mmap2Record record(*attr_id.attr, false, jit_debug_reader_->Pid(), jit_debug_reader_->Pid(),
+ symfile.addr, symfile.len, 0, map_flags::PROT_JIT_SYMFILE_MAP,
+ symfile.file_path, attr_id.ids[0], last_record_timestamp_);
+ if (!ProcessRecord(&record)) {
+ return false;
+ }
+ }
+ // TODO: Handle dex symfiles.
return true;
}
int GetAndroidVersion() {
#if defined(__ANDROID__)
std::string s = android::base::GetProperty("ro.build.version.release", "");
+ // The release string can be a list of numbers (like 8.1.0), a character (like Q)
+ // or many characters (like OMR1).
if (!s.empty()) {
+ // Each Android version has a version number: L is 5, M is 6, N is 7, O is 8, etc.
if (s[0] >= 'A' && s[0] <= 'Z') {
return s[0] - 'O' + 8;
}
bool ReadCounters(std::vector<CountersInfo>* counters);
bool MmapEventFiles(size_t min_mmap_pages, size_t max_mmap_pages);
bool PrepareToReadMmapEventData(const std::function<bool(Record*)>& callback);
+ bool ReadMmapEventData();
bool FinishReadMmapEventData();
// If monitored_cpus is empty, monitor all cpus.
std::string* failed_event_type);
bool MmapEventFiles(size_t mmap_pages, bool report_error);
- bool ReadMmapEventData();
bool DetectCpuHotplugEvents();
bool HandleCpuOnlineEvent(int cpu);
sample_id.ReadFromBinaryFormat(attr, p, end);
}
+Mmap2Record::Mmap2Record(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
+ uint64_t addr, uint64_t len, uint64_t pgoff, uint32_t prot,
+ const std::string& filename, uint64_t event_id, uint64_t time) {
+ SetTypeAndMisc(PERF_RECORD_MMAP2, in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
+ sample_id.CreateContent(attr, event_id);
+ sample_id.time_data.time = time;
+ Mmap2RecordDataType data;
+ data.pid = pid;
+ data.tid = tid;
+ data.addr = addr;
+ data.len = len;
+ data.pgoff = pgoff;
+ data.prot = prot;
+ SetDataAndFilename(data, filename);
+}
+
void Mmap2Record::SetDataAndFilename(const Mmap2RecordDataType& data,
const std::string& filename) {
SetSize(header_size() + sizeof(data) + Align(filename.size() + 1, 8) +
PrintIndented(indent,
"pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n",
data->pid, data->tid, data->addr, data->len);
- PrintIndented(indent, "pgoff 0x" PRIx64 ", maj %u, min %u, ino %" PRId64
+ PrintIndented(indent, "pgoff 0x%" PRIx64 ", maj %u, min %u, ino %" PRId64
", ino_generation %" PRIu64 "\n",
data->pgoff, data->maj, data->min, data->ino,
data->ino_generation);
const char* filename;
Mmap2Record(const perf_event_attr& attr, char* p);
+ Mmap2Record(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
+ uint64_t addr, uint64_t len, uint64_t pgoff, uint32_t prot,
+ const std::string& filename, uint64_t event_id, uint64_t time = 0);
void SetDataAndFilename(const Mmap2RecordDataType& data,
const std::string& filename);
void ThreadTree::AddThreadMap(int pid, int tid, uint64_t start_addr,
uint64_t len, uint64_t pgoff, uint64_t time,
- const std::string& filename) {
+ const std::string& filename, uint32_t flags) {
ThreadEntry* thread = FindThreadOrNew(pid, tid);
Dso* dso = FindUserDsoOrNew(filename, start_addr);
MapEntry* map =
- AllocateMap(MapEntry(start_addr, len, pgoff, time, dso, false));
+ AllocateMap(MapEntry(start_addr, len, pgoff, time, dso, false, flags));
FixOverlappedMap(thread->maps, map);
auto pair = thread->maps->maps.insert(map);
CHECK(pair.second);
Dso* dso = map->dso;
if (!map->in_kernel) {
// Find symbol in user space shared libraries.
- vaddr_in_file = ip - map->start_addr + map->dso->MinVirtualAddress();
+ if (map->flags & map_flags::PROT_JIT_SYMFILE_MAP) {
+ vaddr_in_file = ip;
+ } else {
+ vaddr_in_file = ip - map->start_addr + map->dso->MinVirtualAddress();
+ }
symbol = dso->FindSymbol(vaddr_in_file);
} else {
if (dso != kernel_dso_.get()) {
? "[unknown]"
: r.filename;
AddThreadMap(r.data->pid, r.data->tid, r.data->addr, r.data->len,
- r.data->pgoff, r.sample_id.time_data.time, filename);
+ r.data->pgoff, r.sample_id.time_data.time, filename, r.data->prot);
}
} else if (record.type() == PERF_RECORD_COMM) {
const CommRecord& r = *static_cast<const CommRecord*>(&record);
namespace simpleperf {
+namespace map_flags {
+constexpr uint32_t PROT_JIT_SYMFILE_MAP = 0x4000;
+} // namespace map_flags
+
struct MapEntry {
uint64_t start_addr;
uint64_t len;
uint64_t time; // Map creation time.
Dso* dso;
bool in_kernel;
+ uint32_t flags;
+
MapEntry(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time,
- Dso* dso, bool in_kernel)
+ Dso* dso, bool in_kernel, uint32_t flags = 0)
: start_addr(start_addr),
len(len),
pgoff(pgoff),
time(time),
dso(dso),
- in_kernel(in_kernel) {}
+ in_kernel(in_kernel),
+ flags(flags) {}
MapEntry() {}
uint64_t get_end_addr() const { return start_addr + len; }
void AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff,
uint64_t time, const std::string& filename);
void AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len,
- uint64_t pgoff, uint64_t time, const std::string& filename);
+ uint64_t pgoff, uint64_t time, const std::string& filename,
+ uint32_t flags = 0);
const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip,
bool in_kernel);
// Find map for an ip address when we don't know whether it is in kernel.