Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/MemoryMapping.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/FileUtils.h"
#include <fstream>
#include <string>
#include <sstream>
namespace mozilla {
namespace {
struct VMFlagString {
const char* mName;
const char* mPrettyName;
VMFlag mFlag;
};
static const VMFlagString sVMFlagStrings[] = {
// clang-format off
{"ac", "Accountable", VMFlag::Accountable},
{"ar", "ArchSpecific", VMFlag::ArchSpecific},
{"dc", "NoFork", VMFlag::NoFork},
{"dd", "NoCore", VMFlag::NoCore},
{"de", "NoExpand", VMFlag::NoExpand},
{"dw", "DisabledWrite", VMFlag::DisabledWrite},
{"ex", "Executable", VMFlag::Executable},
{"gd", "GrowsDown", VMFlag::GrowsDown},
{"hg", "HugePage", VMFlag::HugePage},
{"ht", "HugeTLB", VMFlag::HugeTLB},
{"io", "IO", VMFlag::IO},
{"lo", "Locked", VMFlag::Locked},
{"me", "MayExecute", VMFlag::MayExecute},
{"mg", "Mergeable", VMFlag::Mergeable},
{"mm", "MixedMap", VMFlag::MixedMap},
{"mr", "MayRead", VMFlag::MayRead},
{"ms", "MayShare", VMFlag::MayShare},
{"mw", "MayWrite", VMFlag::MayWrite},
{"nh", "NoHugePage", VMFlag::NoHugePage},
{"nl", "NonLinear", VMFlag::NonLinear},
{"nr", "NotReserved", VMFlag::NotReserved},
{"pf", "PurePFN", VMFlag::PurePFN},
{"rd", "Readable", VMFlag::Readable},
{"rr", "Random", VMFlag::Random},
{"sd", "SoftDirty", VMFlag::SoftDirty},
{"sh", "Shared", VMFlag::Shared},
{"sr", "Sequential", VMFlag::Sequential},
{"wr", "Writable", VMFlag::Writable},
// clang-format on
};
} // anonymous namespace
constexpr size_t kVMFlags = size_t(-1);
// An array of known field names which may be present in an smaps file, and the
// offsets of the corresponding fields in a MemoryMapping class.
const MemoryMapping::Field MemoryMapping::sFields[] = {
// clang-format off
{"AnonHugePages", offsetof(MemoryMapping, mAnonHugePages)},
{"Anonymous", offsetof(MemoryMapping, mAnonymous)},
{"KernelPageSize", offsetof(MemoryMapping, mKernelPageSize)},
{"LazyFree", offsetof(MemoryMapping, mLazyFree)},
{"Locked", offsetof(MemoryMapping, mLocked)},
{"MMUPageSize", offsetof(MemoryMapping, mMMUPageSize)},
{"Private_Clean", offsetof(MemoryMapping, mPrivate_Clean)},
{"Private_Dirty", offsetof(MemoryMapping, mPrivate_Dirty)},
{"Private_Hugetlb", offsetof(MemoryMapping, mPrivate_Hugetlb)},
{"Pss", offsetof(MemoryMapping, mPss)},
{"Referenced", offsetof(MemoryMapping, mReferenced)},
{"Rss", offsetof(MemoryMapping, mRss)},
{"Shared_Clean", offsetof(MemoryMapping, mShared_Clean)},
{"Shared_Dirty", offsetof(MemoryMapping, mShared_Dirty)},
{"Shared_Hugetlb", offsetof(MemoryMapping, mShared_Hugetlb)},
{"ShmemPmdMapped", offsetof(MemoryMapping, mShmemPmdMapped)},
{"Size", offsetof(MemoryMapping, mSize)},
{"Swap", offsetof(MemoryMapping, mSwap)},
{"SwapPss", offsetof(MemoryMapping, mSwapPss)},
// VmFlags is a special case. It contains an array of flag strings, which
// describe attributes of the mapping, rather than a mapping size. We include
// it in this array to aid in parsing, but give it a separate sentinel value,
// and treat it specially.
{"VmFlags", kVMFlags},
// clang-format on
};
template <typename T, int n>
const T* FindEntry(const char* aName, const T (&aEntries)[n]) {
size_t index;
if (BinarySearchIf(
aEntries, 0, n,
[&](const T& aEntry) { return strcmp(aName, aEntry.mName); },
&index)) {
return &aEntries[index];
}
return nullptr;
}
using Perm = MemoryMapping::Perm;
using PermSet = MemoryMapping::PermSet;
nsresult GetMemoryMappings(nsTArray<MemoryMapping>& aMappings, pid_t aPid) {
std::ifstream stream;
if (aPid == 0) {
stream.open("/proc/self/smaps");
} else {
std::ostringstream path;
path << "/proc/" << aPid << "/smaps" << std::ends;
stream.open(path.str());
}
if (stream.fail()) {
return NS_ERROR_FAILURE;
}
MemoryMapping* current = nullptr;
std::string line;
while (std::getline(stream, line)) {
size_t start, end, offset;
char flags[4] = "---";
char name[512];
name[0] = 0;
// clang-format off
// Match the start of an entry. A typical line looks something like:
//
// 1487118a7000-148711a5a000 r-xp 00000000 103:03 54004561 /usr/lib/libc-2.27.so
// clang-format on
if (sscanf(line.c_str(), "%zx-%zx %4c %zx %*u:%*u %*u %511s\n", &start,
&end, flags, &offset, name) >= 4) {
PermSet perms;
if (flags[0] == 'r') {
perms += Perm::Read;
}
if (flags[1] == 'w') {
perms += Perm::Write;
}
if (flags[2] == 'x') {
perms += Perm::Execute;
}
if (flags[3] == 'p') {
perms += Perm::Private;
} else if (flags[3] == 's') {
perms += Perm::Shared;
}
current = aMappings.AppendElement(
MemoryMapping{start, end, perms, offset, name});
continue;
}
if (!current) {
continue;
}
char* savePtr;
char* fieldName = strtok_r(line.data(), ":", &savePtr);
if (!fieldName) {
continue;
}
auto* field = FindEntry(fieldName, MemoryMapping::sFields);
if (!field) {
continue;
}
if (field->mOffset == kVMFlags) {
while (char* flagName = strtok_r(nullptr, " \n", &savePtr)) {
if (auto* flag = FindEntry(flagName, sVMFlagStrings)) {
current->mFlags += flag->mFlag;
}
}
continue;
}
const char* rest = strtok_r(nullptr, "\n", &savePtr);
size_t value;
if (sscanf(rest, "%zd kB", &value) > 0) {
current->ValueForField(*field) = value * 1024;
}
}
return NS_OK;
}
void MemoryMapping::Dump(nsACString& aOut) const {
aOut.AppendPrintf("%zx-%zx Size: %zu Offset: %zx %s\n", mStart, mEnd,
mEnd - mStart, mOffset, mName.get());
for (auto& field : MemoryMapping::sFields) {
if (field.mOffset < sizeof(*this)) {
aOut.AppendPrintf(" %s: %zd\n", field.mName, ValueForField(field));
}
}
aOut.AppendPrintf(" Flags: %x\n", mFlags.serialize());
for (auto& flag : sVMFlagStrings) {
if (mFlags.contains(flag.mFlag)) {
aOut.AppendPrintf(" : %s %s\n", flag.mName, flag.mPrettyName);
}
}
}
} // namespace mozilla