#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#include "base/debug/proc_maps_linux.h"
#include <fcntl.h>
#include <stddef.h>
#include <unistd.h>
#include <unordered_map>
#include "base/files/scoped_file.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/memory/page_size.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_split.h"
#include "build/build_config.h"
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) || \
BUILDFLAG(IS_OHOS)
#include <inttypes.h>
#endif
namespace base::debug {
MappedMemoryRegion::MappedMemoryRegion() = default;
MappedMemoryRegion::MappedMemoryRegion(const MappedMemoryRegion&) = default;
MappedMemoryRegion::MappedMemoryRegion(MappedMemoryRegion&&) noexcept = default;
MappedMemoryRegion& MappedMemoryRegion::operator=(MappedMemoryRegion&) =
default;
MappedMemoryRegion& MappedMemoryRegion::operator=(
MappedMemoryRegion&&) noexcept = default;
static bool ContainsGateVMA(std::string* proc_maps, size_t pos) {
#if defined(ARCH_CPU_ARM_FAMILY)
return proc_maps->find(" [vectors]\n", pos) != std::string::npos;
#elif defined(ARCH_CPU_X86_64)
return proc_maps->find(" [vsyscall]\n", pos) != std::string::npos;
#else
return false;
#endif
}
bool ReadProcMaps(std::string* proc_maps) {
const size_t read_size = static_cast<size_t>(sysconf(_SC_PAGESIZE));
base::ScopedFD fd(HANDLE_EINTR(open("/proc/self/maps", O_RDONLY)));
if (!fd.is_valid()) {
DPLOG(ERROR) << "Couldn't open /proc/self/maps";
return false;
}
proc_maps->clear();
while (true) {
size_t pos = proc_maps->size();
proc_maps->resize(pos + read_size);
void* buffer = &(*proc_maps)[pos];
ssize_t bytes_read = HANDLE_EINTR(read(fd.get(), buffer, read_size));
if (bytes_read < 0) {
DPLOG(ERROR) << "Couldn't read /proc/self/maps";
proc_maps->clear();
return false;
}
proc_maps->resize(pos + static_cast<size_t>(bytes_read));
if (bytes_read == 0) {
break;
}
if (ContainsGateVMA(proc_maps, pos)) {
break;
}
}
return true;
}
bool ParseProcMaps(const std::string& input,
std::vector<MappedMemoryRegion>* regions_out) {
CHECK(regions_out);
std::vector<MappedMemoryRegion> regions;
std::vector<std::string> lines =
SplitString(input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
for (size_t i = 0; i < lines.size(); ++i) {
if (i == lines.size() - 1) {
if (!lines[i].empty()) {
DLOG(WARNING) << "Last line not empty";
return false;
}
break;
}
MappedMemoryRegion region;
const char* line = lines[i].c_str();
char permissions[5] = {};
uint8_t dev_major = 0;
uint8_t dev_minor = 0;
long inode = 0;
int path_index = 0;
if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n",
®ion.start, ®ion.end, permissions, ®ion.offset,
&dev_major, &dev_minor, &inode, &path_index) < 7) {
DPLOG(WARNING) << "sscanf failed for line: " << line;
return false;
}
region.inode = inode;
region.dev_major = dev_major;
region.dev_minor = dev_minor;
region.permissions = 0;
if (permissions[0] == 'r') {
region.permissions |= MappedMemoryRegion::READ;
} else if (permissions[0] != '-') {
return false;
}
if (permissions[1] == 'w') {
region.permissions |= MappedMemoryRegion::WRITE;
} else if (permissions[1] != '-') {
return false;
}
if (permissions[2] == 'x') {
region.permissions |= MappedMemoryRegion::EXECUTE;
} else if (permissions[2] != '-') {
return false;
}
if (permissions[3] == 'p') {
region.permissions |= MappedMemoryRegion::PRIVATE;
} else if (permissions[3] != 's' &&
permissions[3] != 'S') {
return false;
}
regions.push_back(region);
regions.back().path.assign(line + path_index);
}
regions_out->swap(regions);
return true;
}
std::optional<SmapsRollup> ParseSmapsRollup(const std::string& buffer) {
std::vector<std::string> lines =
SplitString(buffer, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
std::unordered_map<std::string, ByteCount> tmp;
for (const auto& line : lines) {
std::string key;
key.resize(100);
size_t val;
if (sscanf(line.c_str(), "%99s %" PRIuS " kB", key.data(), &val) == 2) {
key.resize(strlen(key.c_str()) - 1);
tmp[key] = KiB(val);
}
}
SmapsRollup smaps_rollup;
smaps_rollup.rss = tmp["Rss"];
smaps_rollup.pss = tmp["Pss"];
smaps_rollup.pss_anon = tmp["Pss_Anon"];
smaps_rollup.pss_file = tmp["Pss_File"];
smaps_rollup.pss_shmem = tmp["Pss_Shmem"];
smaps_rollup.private_dirty = tmp["Private_Dirty"];
smaps_rollup.swap = tmp["Swap"];
smaps_rollup.swap_pss = tmp["SwapPss"];
return smaps_rollup;
}
std::optional<SmapsRollup> ReadAndParseSmapsRollup() {
const size_t read_size = base::GetPageSize();
base::ScopedFD fd(HANDLE_EINTR(open("/proc/self/smaps_rollup", O_RDONLY)));
if (!fd.is_valid()) {
DPLOG(ERROR) << "Couldn't open /proc/self/smaps_rollup";
return std::nullopt;
}
std::string buffer;
buffer.resize(read_size);
ssize_t bytes_read = HANDLE_EINTR(
read(fd.get(), static_cast<void*>(buffer.data()), read_size));
if (bytes_read < 0) {
DPLOG(ERROR) << "Couldn't read /proc/self/smaps_rollup";
return std::nullopt;
}
DCHECK(static_cast<size_t>(bytes_read) < read_size);
buffer.resize(static_cast<size_t>(bytes_read));
return ParseSmapsRollup(buffer);
}
std::optional<SmapsRollup> ParseSmapsRollupForTesting(
const std::string& smaps_rollup) {
return ParseSmapsRollup(smaps_rollup);
}
}