libprocinfo: add functions reading process map file.
Add test and benchmark. Also switch libbacktrace, libunwindstack, libmemunreachable to use libprocinfo for map file reading. The benchmark shows using libprocinfo speeds up map file reading in libbacktrace and libunwindstack 18% - 36% on walleye. Bug: http://b/79118393 Test: run procinfo_test. Test: run libunwindstack_test. Test: run libbacktrace_test. Test: run memunreachable_test. Change-Id: Icf281c352f4103fc8d4ba6732c5c07b943330ca1
This commit is contained in:
parent
3607fe672a
commit
3841accba8
16 changed files with 2460 additions and 331 deletions
|
@ -91,7 +91,10 @@ cc_library {
|
|||
"libdexfile",
|
||||
],
|
||||
|
||||
static_libs: ["libcutils"],
|
||||
static_libs: [
|
||||
"libcutils",
|
||||
"libprocinfo",
|
||||
],
|
||||
|
||||
// libdexfile will eventually properly export headers, for now
|
||||
// include these directly.
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
#include <backtrace/Backtrace.h>
|
||||
#include <backtrace/BacktraceMap.h>
|
||||
#include <backtrace/backtrace_constants.h>
|
||||
#if defined(__linux__)
|
||||
#include <procinfo/process_map.h>
|
||||
#endif
|
||||
|
||||
#include "thread_utils.h"
|
||||
|
||||
|
@ -60,27 +63,19 @@ void BacktraceMap::FillIn(uint64_t addr, backtrace_map_t* map) {
|
|||
*map = {};
|
||||
}
|
||||
|
||||
bool BacktraceMap::ParseLine(const char* line, backtrace_map_t* map) {
|
||||
#if defined(__APPLE__)
|
||||
static bool ParseLine(const char* line, backtrace_map_t* map) {
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
char permissions[5];
|
||||
int name_pos;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// Mac OS vmmap(1) output:
|
||||
// __TEXT 0009f000-000a1000 [ 8K 8K] r-x/rwx SM=COW /Volumes/android/dalvik-dev/out/host/darwin-x86/bin/libcorkscrew_test\n
|
||||
// 012345678901234567890123456789012345678901234567890123456789
|
||||
// 0 1 2 3 4 5
|
||||
if (sscanf(line, "%*21c %" SCNx64 "-%" SCNx64 " [%*13c] %3c/%*3c SM=%*3c %n",
|
||||
&start, &end, permissions, &name_pos) != 3) {
|
||||
#else
|
||||
// Linux /proc/<pid>/maps lines:
|
||||
// 6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so\n
|
||||
// 012345678901234567890123456789012345678901234567890123456789
|
||||
// 0 1 2 3 4 5
|
||||
if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %4s %*x %*x:%*x %*d %n",
|
||||
&start, &end, permissions, &name_pos) != 3) {
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -107,24 +102,15 @@ bool BacktraceMap::ParseLine(const char* line, backtrace_map_t* map) {
|
|||
map->flags, map->name.c_str());
|
||||
return true;
|
||||
}
|
||||
#endif // defined(__APPLE__)
|
||||
|
||||
bool BacktraceMap::Build() {
|
||||
#if defined(__APPLE__)
|
||||
char cmd[sizeof(pid_t)*3 + sizeof("vmmap -w -resident -submap -allSplitLibs -interleaved ") + 1];
|
||||
#else
|
||||
char path[sizeof(pid_t)*3 + sizeof("/proc//maps") + 1];
|
||||
#endif
|
||||
char line[1024];
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// cmd is guaranteed to always be big enough to hold this string.
|
||||
snprintf(cmd, sizeof(cmd), "vmmap -w -resident -submap -allSplitLibs -interleaved %d", pid_);
|
||||
FILE* fp = popen(cmd, "r");
|
||||
#else
|
||||
// path is guaranteed to always be big enough to hold this string.
|
||||
snprintf(path, sizeof(path), "/proc/%d/maps", pid_);
|
||||
FILE* fp = fopen(path, "r");
|
||||
#endif
|
||||
if (fp == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
@ -135,13 +121,19 @@ bool BacktraceMap::Build() {
|
|||
maps_.push_back(map);
|
||||
}
|
||||
}
|
||||
#if defined(__APPLE__)
|
||||
pclose(fp);
|
||||
#else
|
||||
fclose(fp);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
#else
|
||||
return android::procinfo::ReadProcessMaps(
|
||||
pid_, [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t, const char* name) {
|
||||
maps_.resize(maps_.size() + 1);
|
||||
backtrace_map_t& map = maps_.back();
|
||||
map.start = start;
|
||||
map.end = end;
|
||||
map.flags = flags;
|
||||
map.name = name;
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
|
|
@ -169,8 +169,6 @@ public:
|
|||
|
||||
virtual uint64_t GetLoadBias(size_t /* index */) { return 0; }
|
||||
|
||||
virtual bool ParseLine(const char* line, backtrace_map_t* map);
|
||||
|
||||
pid_t pid_;
|
||||
std::deque<backtrace_map_t> maps_;
|
||||
std::vector<std::string> suffixes_to_ignore_;
|
||||
|
|
|
@ -29,7 +29,6 @@ cc_library {
|
|||
"HeapWalker.cpp",
|
||||
"LeakFolding.cpp",
|
||||
"LeakPipe.cpp",
|
||||
"LineBuffer.cpp",
|
||||
"MemUnreachable.cpp",
|
||||
"ProcessMappings.cpp",
|
||||
"PtracerThread.cpp",
|
||||
|
@ -38,6 +37,7 @@ cc_library {
|
|||
|
||||
static_libs: [
|
||||
"libc_malloc_debug_backtrace",
|
||||
"libprocinfo",
|
||||
],
|
||||
// Only need this for arm since libc++ uses its own unwind code that
|
||||
// doesn't mix with the other default unwind code.
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Copied from system/extras/memory_replay/LineBuffer.cpp
|
||||
// TODO(ccross): find a way to share between libmemunreachable and memory_replay?
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "LineBuffer.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
LineBuffer::LineBuffer(int fd, char* buffer, size_t buffer_len)
|
||||
: fd_(fd), buffer_(buffer), buffer_len_(buffer_len) {}
|
||||
|
||||
bool LineBuffer::GetLine(char** line, size_t* line_len) {
|
||||
while (true) {
|
||||
if (bytes_ > 0) {
|
||||
char* newline = reinterpret_cast<char*>(memchr(buffer_ + start_, '\n', bytes_));
|
||||
if (newline != nullptr) {
|
||||
*newline = '\0';
|
||||
*line = buffer_ + start_;
|
||||
start_ = newline - buffer_ + 1;
|
||||
bytes_ -= newline - *line + 1;
|
||||
*line_len = newline - *line;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (start_ > 0) {
|
||||
// Didn't find anything, copy the current to the front of the buffer.
|
||||
memmove(buffer_, buffer_ + start_, bytes_);
|
||||
start_ = 0;
|
||||
}
|
||||
ssize_t bytes = TEMP_FAILURE_RETRY(read(fd_, buffer_ + bytes_, buffer_len_ - bytes_ - 1));
|
||||
if (bytes <= 0) {
|
||||
if (bytes_ > 0) {
|
||||
// The read data might not contain a nul terminator, so add one.
|
||||
buffer_[bytes_] = '\0';
|
||||
*line = buffer_ + start_;
|
||||
*line_len = bytes_;
|
||||
bytes_ = 0;
|
||||
start_ = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bytes_ += bytes;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _LIBMEMUNREACHABLE_LINE_BUFFER_H
|
||||
#define _LIBMEMUNREACHABLE_LINE_BUFFER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class LineBuffer {
|
||||
public:
|
||||
LineBuffer(int fd, char* buffer, size_t buffer_len);
|
||||
|
||||
bool GetLine(char** line, size_t* line_len);
|
||||
|
||||
private:
|
||||
int fd_;
|
||||
char* buffer_ = nullptr;
|
||||
size_t buffer_len_ = 0;
|
||||
size_t start_ = 0;
|
||||
size_t bytes_ = 0;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // _LIBMEMUNREACHABLE_LINE_BUFFER_H
|
|
@ -14,21 +14,30 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <procinfo/process_map.h>
|
||||
|
||||
#include "LineBuffer.h"
|
||||
#include "ProcessMappings.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
// This function is not re-entrant since it uses a static buffer for
|
||||
// the line data.
|
||||
struct ReadMapCallback {
|
||||
ReadMapCallback(allocator::vector<Mapping>& mappings) : mappings_(mappings) {}
|
||||
|
||||
void operator()(uint64_t start, uint64_t end, uint16_t flags, uint64_t, const char* name) const {
|
||||
mappings_.emplace_back(start, end, flags & PROT_READ, flags & PROT_WRITE, flags & PROT_EXEC,
|
||||
name);
|
||||
}
|
||||
|
||||
allocator::vector<Mapping>& mappings_;
|
||||
};
|
||||
|
||||
bool ProcessMappings(pid_t pid, allocator::vector<Mapping>& mappings) {
|
||||
char map_buffer[1024];
|
||||
snprintf(map_buffer, sizeof(map_buffer), "/proc/%d/maps", pid);
|
||||
|
@ -36,35 +45,13 @@ bool ProcessMappings(pid_t pid, allocator::vector<Mapping>& mappings) {
|
|||
if (fd == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LineBuffer line_buf(fd, map_buffer, sizeof(map_buffer));
|
||||
char* line;
|
||||
size_t line_len;
|
||||
while (line_buf.GetLine(&line, &line_len)) {
|
||||
int name_pos;
|
||||
char perms[5];
|
||||
Mapping mapping{};
|
||||
if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4s %*x %*x:%*x %*d %n", &mapping.begin,
|
||||
&mapping.end, perms, &name_pos) == 3) {
|
||||
if (perms[0] == 'r') {
|
||||
mapping.read = true;
|
||||
}
|
||||
if (perms[1] == 'w') {
|
||||
mapping.write = true;
|
||||
}
|
||||
if (perms[2] == 'x') {
|
||||
mapping.execute = true;
|
||||
}
|
||||
if (perms[3] == 'p') {
|
||||
mapping.priv = true;
|
||||
}
|
||||
if ((size_t)name_pos < line_len) {
|
||||
strlcpy(mapping.name, line + name_pos, sizeof(mapping.name));
|
||||
}
|
||||
mappings.emplace_back(mapping);
|
||||
}
|
||||
allocator::string content(mappings.get_allocator());
|
||||
ssize_t n;
|
||||
while ((n = TEMP_FAILURE_RETRY(read(fd, map_buffer, sizeof(map_buffer)))) > 0) {
|
||||
content.append(map_buffer, n);
|
||||
}
|
||||
return true;
|
||||
ReadMapCallback callback(mappings);
|
||||
return android::procinfo::ReadMapFileContent(&content[0], callback);
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#ifndef LIBMEMUNREACHABLE_PROCESS_MAPPING_H_
|
||||
#define LIBMEMUNREACHABLE_PROCESS_MAPPING_H_
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "Allocator.h"
|
||||
|
||||
namespace android {
|
||||
|
@ -27,8 +29,13 @@ struct Mapping {
|
|||
bool read;
|
||||
bool write;
|
||||
bool execute;
|
||||
bool priv;
|
||||
char name[96];
|
||||
|
||||
Mapping() {}
|
||||
Mapping(uintptr_t begin, uintptr_t end, bool read, bool write, bool execute, const char* name)
|
||||
: begin(begin), end(end), read(read), write(write), execute(execute) {
|
||||
strlcpy(this->name, name, sizeof(this->name));
|
||||
}
|
||||
};
|
||||
|
||||
// This function is not re-entrant since it uses a static buffer for
|
||||
|
|
|
@ -59,6 +59,7 @@ cc_test {
|
|||
host_supported: true,
|
||||
srcs: [
|
||||
"process_test.cpp",
|
||||
"process_map_test.cpp",
|
||||
],
|
||||
target: {
|
||||
darwin: {
|
||||
|
@ -84,5 +85,45 @@ cc_test {
|
|||
},
|
||||
},
|
||||
|
||||
data: [
|
||||
"testdata/*",
|
||||
],
|
||||
|
||||
test_suites: ["device-tests"],
|
||||
}
|
||||
|
||||
cc_benchmark {
|
||||
name: "libprocinfo_benchmark",
|
||||
defaults: ["libprocinfo_defaults"],
|
||||
host_supported: true,
|
||||
srcs: [
|
||||
"process_map_benchmark.cpp",
|
||||
],
|
||||
target: {
|
||||
darwin: {
|
||||
enabled: false,
|
||||
},
|
||||
windows: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
shared_libs: [
|
||||
"libbacktrace",
|
||||
"libbase",
|
||||
"libprocinfo",
|
||||
"libunwindstack",
|
||||
],
|
||||
compile_multilib: "both",
|
||||
multilib: {
|
||||
lib32: {
|
||||
suffix: "32",
|
||||
},
|
||||
lib64: {
|
||||
suffix: "64",
|
||||
},
|
||||
},
|
||||
|
||||
data: [
|
||||
"testdata/*",
|
||||
],
|
||||
}
|
||||
|
|
151
libprocinfo/include/procinfo/process_map.h
Normal file
151
libprocinfo/include/procinfo/process_map.h
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include <android-base/file.h>
|
||||
|
||||
namespace android {
|
||||
namespace procinfo {
|
||||
|
||||
template <class CallbackType>
|
||||
bool ReadMapFileContent(char* content, const CallbackType& callback) {
|
||||
uint64_t start_addr;
|
||||
uint64_t end_addr;
|
||||
uint16_t flags;
|
||||
uint64_t pgoff;
|
||||
char* next_line = content;
|
||||
char* p;
|
||||
|
||||
auto pass_space = [&]() {
|
||||
if (*p != ' ') {
|
||||
return false;
|
||||
}
|
||||
while (*p == ' ') {
|
||||
p++;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto pass_xdigit = [&]() {
|
||||
if (!isxdigit(*p)) {
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
p++;
|
||||
} while (isxdigit(*p));
|
||||
return true;
|
||||
};
|
||||
|
||||
while (next_line != nullptr && *next_line != '\0') {
|
||||
p = next_line;
|
||||
next_line = strchr(next_line, '\n');
|
||||
if (next_line != nullptr) {
|
||||
*next_line = '\0';
|
||||
next_line++;
|
||||
}
|
||||
// Parse line like: 00400000-00409000 r-xp 00000000 fc:00 426998 /usr/lib/gvfs/gvfsd-http
|
||||
char* end;
|
||||
// start_addr
|
||||
start_addr = strtoull(p, &end, 16);
|
||||
if (end == p || *end != '-') {
|
||||
return false;
|
||||
}
|
||||
p = end + 1;
|
||||
// end_addr
|
||||
end_addr = strtoull(p, &end, 16);
|
||||
if (end == p) {
|
||||
return false;
|
||||
}
|
||||
p = end;
|
||||
if (!pass_space()) {
|
||||
return false;
|
||||
}
|
||||
// flags
|
||||
flags = 0;
|
||||
if (*p == 'r') {
|
||||
flags |= PROT_READ;
|
||||
} else if (*p != '-') {
|
||||
return false;
|
||||
}
|
||||
p++;
|
||||
if (*p == 'w') {
|
||||
flags |= PROT_WRITE;
|
||||
} else if (*p != '-') {
|
||||
return false;
|
||||
}
|
||||
p++;
|
||||
if (*p == 'x') {
|
||||
flags |= PROT_EXEC;
|
||||
} else if (*p != '-') {
|
||||
return false;
|
||||
}
|
||||
p++;
|
||||
if (*p != 'p' && *p != 's') {
|
||||
return false;
|
||||
}
|
||||
p++;
|
||||
if (!pass_space()) {
|
||||
return false;
|
||||
}
|
||||
// pgoff
|
||||
pgoff = strtoull(p, &end, 16);
|
||||
if (end == p) {
|
||||
return false;
|
||||
}
|
||||
p = end;
|
||||
if (!pass_space()) {
|
||||
return false;
|
||||
}
|
||||
// major:minor
|
||||
if (!pass_xdigit() || *p++ != ':' || !pass_xdigit() || !pass_space()) {
|
||||
return false;
|
||||
}
|
||||
// inode
|
||||
if (!pass_xdigit() || (*p != '\0' && !pass_space())) {
|
||||
return false;
|
||||
}
|
||||
// filename
|
||||
callback(start_addr, end_addr, flags, pgoff, p);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ReadMapFile(
|
||||
const std::string& map_file,
|
||||
const std::function<void(uint64_t, uint64_t, uint16_t, uint64_t, const char*)>& callback) {
|
||||
std::string content;
|
||||
if (!android::base::ReadFileToString(map_file, &content)) {
|
||||
return false;
|
||||
}
|
||||
return ReadMapFileContent(&content[0], callback);
|
||||
}
|
||||
|
||||
inline bool ReadProcessMaps(
|
||||
pid_t pid,
|
||||
const std::function<void(uint64_t, uint64_t, uint16_t, uint64_t, const char*)>& callback) {
|
||||
return ReadMapFile("/proc/" + std::to_string(pid) + "/maps", callback);
|
||||
}
|
||||
|
||||
} /* namespace procinfo */
|
||||
} /* namespace android */
|
85
libprocinfo/process_map_benchmark.cpp
Normal file
85
libprocinfo/process_map_benchmark.cpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <procinfo/process_map.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <backtrace/BacktraceMap.h>
|
||||
#include <unwindstack/Maps.h>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
struct MapInfo {
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
uint16_t flags;
|
||||
uint64_t pgoff;
|
||||
const std::string name;
|
||||
|
||||
MapInfo(uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, const char* name)
|
||||
: start(start), end(end), flags(flags), pgoff(pgoff), name(name) {}
|
||||
};
|
||||
|
||||
static void BM_ReadMapFile(benchmark::State& state) {
|
||||
std::string map_file = android::base::GetExecutableDirectory() + "/testdata/maps";
|
||||
for (auto _ : state) {
|
||||
std::vector<MapInfo> maps;
|
||||
android::procinfo::ReadMapFile(
|
||||
map_file, [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff,
|
||||
const char* name) { maps.emplace_back(start, end, flags, pgoff, name); });
|
||||
CHECK_EQ(maps.size(), 2043u);
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_ReadMapFile);
|
||||
|
||||
static void BM_unwindstack_FileMaps(benchmark::State& state) {
|
||||
std::string map_file = android::base::GetExecutableDirectory() + "/testdata/maps";
|
||||
for (auto _ : state) {
|
||||
unwindstack::FileMaps maps(map_file);
|
||||
maps.Parse();
|
||||
CHECK_EQ(maps.Total(), 2043u);
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_unwindstack_FileMaps);
|
||||
|
||||
static void BM_unwindstack_BufferMaps(benchmark::State& state) {
|
||||
std::string map_file = android::base::GetExecutableDirectory() + "/testdata/maps";
|
||||
std::string content;
|
||||
CHECK(android::base::ReadFileToString(map_file, &content));
|
||||
for (auto _ : state) {
|
||||
unwindstack::BufferMaps maps(content.c_str());
|
||||
maps.Parse();
|
||||
CHECK_EQ(maps.Total(), 2043u);
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_unwindstack_BufferMaps);
|
||||
|
||||
static void BM_backtrace_BacktraceMap(benchmark::State& state) {
|
||||
pid_t pid = getpid();
|
||||
for (auto _ : state) {
|
||||
BacktraceMap* map = BacktraceMap::Create(pid, true);
|
||||
CHECK(map != nullptr);
|
||||
delete map;
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_backtrace_BacktraceMap);
|
||||
|
||||
BENCHMARK_MAIN();
|
60
libprocinfo/process_map_test.cpp
Normal file
60
libprocinfo/process_map_test.cpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <procinfo/process_map.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <android-base/file.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
struct MapInfo {
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
uint16_t flags;
|
||||
uint64_t pgoff;
|
||||
const std::string name;
|
||||
|
||||
MapInfo(uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, const char* name)
|
||||
: start(start), end(end), flags(flags), pgoff(pgoff), name(name) {}
|
||||
};
|
||||
|
||||
TEST(process_map, smoke) {
|
||||
std::string map_file = android::base::GetExecutableDirectory() + "/testdata/maps";
|
||||
std::vector<MapInfo> maps;
|
||||
ASSERT_TRUE(android::procinfo::ReadMapFile(
|
||||
map_file, [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff,
|
||||
const char* name) { maps.emplace_back(start, end, flags, pgoff, name); }));
|
||||
ASSERT_EQ(2043u, maps.size());
|
||||
ASSERT_EQ(maps[0].start, 0x12c00000ULL);
|
||||
ASSERT_EQ(maps[0].end, 0x2ac00000ULL);
|
||||
ASSERT_EQ(maps[0].flags, PROT_READ | PROT_WRITE);
|
||||
ASSERT_EQ(maps[0].pgoff, 0ULL);
|
||||
ASSERT_EQ(maps[0].name, "/dev/ashmem/dalvik-main space (region space) (deleted)");
|
||||
ASSERT_EQ(maps[876].start, 0x70e6c4f000ULL);
|
||||
ASSERT_EQ(maps[876].end, 0x70e6c6b000ULL);
|
||||
ASSERT_EQ(maps[876].flags, PROT_READ | PROT_EXEC);
|
||||
ASSERT_EQ(maps[876].pgoff, 0ULL);
|
||||
ASSERT_EQ(maps[876].name, "/system/lib64/libutils.so");
|
||||
ASSERT_EQ(maps[1260].start, 0x70e96fa000ULL);
|
||||
ASSERT_EQ(maps[1260].end, 0x70e96fb000ULL);
|
||||
ASSERT_EQ(maps[1260].flags, PROT_READ);
|
||||
ASSERT_EQ(maps[1260].pgoff, 0ULL);
|
||||
ASSERT_EQ(maps[1260].name,
|
||||
"/dev/ashmem/dalvik-classes.dex extracted in memory from "
|
||||
"/data/app/com.google.sample.tunnel-HGGRU03Gu1Mwkf_-RnFmvw==/base.apk (deleted)");
|
||||
}
|
2043
libprocinfo/testdata/maps
vendored
Normal file
2043
libprocinfo/testdata/maps
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
@ -110,6 +110,10 @@ cc_library {
|
|||
},
|
||||
},
|
||||
|
||||
static_libs: [
|
||||
"libprocinfo",
|
||||
],
|
||||
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libdexfile",
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <unistd.h>
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <procinfo/process_map.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
@ -57,150 +58,16 @@ MapInfo* Maps::Find(uint64_t pc) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// Assumes that line does not end in '\n'.
|
||||
static MapInfo* InternalParseLine(const char* line) {
|
||||
// Do not use a sscanf implementation since it is not performant.
|
||||
|
||||
// Example linux /proc/<pid>/maps lines:
|
||||
// 6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so
|
||||
char* str;
|
||||
const char* old_str = line;
|
||||
uint64_t start = strtoull(old_str, &str, 16);
|
||||
if (old_str == str || *str++ != '-') {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
old_str = str;
|
||||
uint64_t end = strtoull(old_str, &str, 16);
|
||||
if (old_str == str || !std::isspace(*str++)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
while (std::isspace(*str)) {
|
||||
str++;
|
||||
}
|
||||
|
||||
// Parse permissions data.
|
||||
if (*str == '\0') {
|
||||
return nullptr;
|
||||
}
|
||||
uint16_t flags = 0;
|
||||
if (*str == 'r') {
|
||||
flags |= PROT_READ;
|
||||
} else if (*str != '-') {
|
||||
return nullptr;
|
||||
}
|
||||
str++;
|
||||
if (*str == 'w') {
|
||||
flags |= PROT_WRITE;
|
||||
} else if (*str != '-') {
|
||||
return nullptr;
|
||||
}
|
||||
str++;
|
||||
if (*str == 'x') {
|
||||
flags |= PROT_EXEC;
|
||||
} else if (*str != '-') {
|
||||
return nullptr;
|
||||
}
|
||||
str++;
|
||||
if (*str != 'p' && *str != 's') {
|
||||
return nullptr;
|
||||
}
|
||||
str++;
|
||||
|
||||
if (!std::isspace(*str++)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
old_str = str;
|
||||
uint64_t offset = strtoull(old_str, &str, 16);
|
||||
if (old_str == str || !std::isspace(*str)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Ignore the 00:00 values.
|
||||
old_str = str;
|
||||
(void)strtoull(old_str, &str, 16);
|
||||
if (old_str == str || *str++ != ':') {
|
||||
return nullptr;
|
||||
}
|
||||
if (std::isspace(*str)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Skip the inode.
|
||||
old_str = str;
|
||||
(void)strtoull(str, &str, 16);
|
||||
if (old_str == str || !std::isspace(*str++)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Skip decimal digit.
|
||||
old_str = str;
|
||||
(void)strtoull(old_str, &str, 10);
|
||||
if (old_str == str || (!std::isspace(*str) && *str != '\0')) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
while (std::isspace(*str)) {
|
||||
str++;
|
||||
}
|
||||
if (*str == '\0') {
|
||||
return new MapInfo(start, end, offset, flags, "");
|
||||
}
|
||||
|
||||
// Save the name data.
|
||||
std::string name(str);
|
||||
|
||||
// Mark a device map in /dev/ and not in /dev/ashmem/ specially.
|
||||
if (name.substr(0, 5) == "/dev/" && name.substr(5, 7) != "ashmem/") {
|
||||
flags |= MAPS_FLAGS_DEVICE_MAP;
|
||||
}
|
||||
return new MapInfo(start, end, offset, flags, name);
|
||||
}
|
||||
|
||||
bool Maps::Parse() {
|
||||
int fd = open(GetMapsFile().c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool return_value = true;
|
||||
char buffer[2048];
|
||||
size_t leftover = 0;
|
||||
while (true) {
|
||||
ssize_t bytes = read(fd, &buffer[leftover], 2048 - leftover);
|
||||
if (bytes == -1) {
|
||||
return_value = false;
|
||||
break;
|
||||
}
|
||||
if (bytes == 0) {
|
||||
break;
|
||||
}
|
||||
bytes += leftover;
|
||||
char* line = buffer;
|
||||
while (bytes > 0) {
|
||||
char* newline = static_cast<char*>(memchr(line, '\n', bytes));
|
||||
if (newline == nullptr) {
|
||||
memmove(buffer, line, bytes);
|
||||
break;
|
||||
}
|
||||
*newline = '\0';
|
||||
|
||||
MapInfo* map_info = InternalParseLine(line);
|
||||
if (map_info == nullptr) {
|
||||
return_value = false;
|
||||
break;
|
||||
}
|
||||
maps_.push_back(map_info);
|
||||
|
||||
bytes -= newline - line + 1;
|
||||
line = newline + 1;
|
||||
}
|
||||
leftover = bytes;
|
||||
}
|
||||
close(fd);
|
||||
return return_value;
|
||||
return android::procinfo::ReadMapFile(
|
||||
GetMapsFile(),
|
||||
[&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, const char* name) {
|
||||
// Mark a device map in /dev/ and not in /dev/ashmem/ specially.
|
||||
if (strncmp(name, "/dev/", 5) == 0 && strncmp(name + 5, "ashmem/", 7) != 0) {
|
||||
flags |= unwindstack::MAPS_FLAGS_DEVICE_MAP;
|
||||
}
|
||||
maps_.push_back(new MapInfo(start, end, pgoff, flags, name));
|
||||
});
|
||||
}
|
||||
|
||||
void Maps::Add(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags,
|
||||
|
@ -222,26 +89,16 @@ Maps::~Maps() {
|
|||
}
|
||||
|
||||
bool BufferMaps::Parse() {
|
||||
const char* start_of_line = buffer_;
|
||||
do {
|
||||
std::string line;
|
||||
const char* end_of_line = strchr(start_of_line, '\n');
|
||||
if (end_of_line == nullptr) {
|
||||
line = start_of_line;
|
||||
} else {
|
||||
line = std::string(start_of_line, end_of_line - start_of_line);
|
||||
end_of_line++;
|
||||
}
|
||||
|
||||
MapInfo* map_info = InternalParseLine(line.c_str());
|
||||
if (map_info == nullptr) {
|
||||
return false;
|
||||
}
|
||||
maps_.push_back(map_info);
|
||||
|
||||
start_of_line = end_of_line;
|
||||
} while (start_of_line != nullptr && *start_of_line != '\0');
|
||||
return true;
|
||||
std::string content(buffer_);
|
||||
return android::procinfo::ReadMapFileContent(
|
||||
&content[0],
|
||||
[&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, const char* name) {
|
||||
// Mark a device map in /dev/ and not in /dev/ashmem/ specially.
|
||||
if (strncmp(name, "/dev/", 5) == 0 && strncmp(name + 5, "ashmem/", 7) != 0) {
|
||||
flags |= unwindstack::MAPS_FLAGS_DEVICE_MAP;
|
||||
}
|
||||
maps_.push_back(new MapInfo(start, end, pgoff, flags, name));
|
||||
});
|
||||
}
|
||||
|
||||
const std::string RemoteMaps::GetMapsFile() const {
|
||||
|
|
|
@ -34,6 +34,13 @@ class Memory;
|
|||
struct MapInfo {
|
||||
MapInfo() = default;
|
||||
MapInfo(uint64_t start, uint64_t end) : start(start), end(end) {}
|
||||
MapInfo(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, const char* name)
|
||||
: start(start),
|
||||
end(end),
|
||||
offset(offset),
|
||||
flags(flags),
|
||||
name(name),
|
||||
load_bias(static_cast<uint64_t>(-1)) {}
|
||||
MapInfo(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, const std::string& name)
|
||||
: start(start),
|
||||
end(end),
|
||||
|
|
Loading…
Reference in a new issue