/* -*- 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