123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- // Copyright 2018 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include <sys/ptrace.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <unistd.h>
- #include <limits>
- #include <string>
- #include <vector>
- #include "base/debug/proc_maps_linux.h"
- #include "base/files/file.h"
- #include "base/files/file_path.h"
- #include "base/files/file_util.h"
- #include "base/format_macros.h"
- #include "base/logging.h"
- #include "base/posix/eintr_wrapper.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_util.h"
- #include "base/strings/stringprintf.h"
- namespace {
- using base::debug::MappedMemoryRegion;
- constexpr size_t kPageSize = 1 << 12;
- // See https://www.kernel.org/doc/Documentation/vm/pagemap.txt.
- struct PageMapEntry {
- uint64_t pfn_or_swap : 55;
- uint64_t soft_dirty : 1;
- uint64_t exclusively_mapped : 1;
- uint64_t unused : 4;
- uint64_t file_mapped_or_shared_anon : 1;
- uint64_t swapped : 1;
- uint64_t present : 1;
- };
- static_assert(sizeof(PageMapEntry) == sizeof(uint64_t), "Wrong bitfield size");
- // Calls ptrace() on a process, and detaches in the destructor.
- class ScopedPtracer {
- public:
- ScopedPtracer(pid_t pid) : pid_(pid), is_attached_(false) {
- // ptrace() delivers a SIGSTOP signal to one thread in the target process,
- // unless it is already stopped. Since we want to stop the whole process,
- // kill() it first.
- if (kill(pid, SIGSTOP)) {
- PLOG(ERROR) << "Cannot stop the process group of " << pid;
- return;
- }
- if (ptrace(PTRACE_ATTACH, pid, nullptr, nullptr)) {
- PLOG(ERROR) << "Unable to attach to " << pid;
- return;
- }
- // ptrace(PTRACE_ATTACH) sends a SISTOP signal to the process, need to wait
- // for it.
- int status;
- pid_t ret = HANDLE_EINTR(waitpid(pid, &status, 0));
- if (ret != pid) {
- PLOG(ERROR) << "Waiting for the process failed";
- return;
- }
- if (!WIFSTOPPED(status)) {
- LOG(ERROR) << "The process is not stopped";
- ptrace(PTRACE_DETACH, pid, 0, 0);
- return;
- }
- is_attached_ = true;
- }
- ~ScopedPtracer() {
- if (!is_attached_)
- return;
- if (ptrace(PTRACE_DETACH, pid_, 0, 0)) {
- PLOG(ERROR) << "Cannot detach from " << pid_;
- }
- pid_t process_group_id = getpgid(pid_);
- if (killpg(process_group_id, SIGCONT)) {
- PLOG(ERROR) << "Cannot resume the process " << pid_;
- return;
- }
- }
- bool IsAttached() const { return is_attached_; }
- private:
- pid_t pid_;
- bool is_attached_;
- };
- bool ParseProcMaps(pid_t pid, std::vector<MappedMemoryRegion>* regions) {
- std::string path = base::StringPrintf("/proc/%d/maps", pid);
- std::string proc_maps;
- bool ok = base::ReadFileToString(base::FilePath(path), &proc_maps);
- if (!ok) {
- LOG(ERROR) << "Cannot read " << path;
- return false;
- }
- ok = base::debug::ParseProcMaps(proc_maps, regions);
- if (!ok) {
- LOG(ERROR) << "Cannot parse " << path;
- return false;
- }
- return true;
- }
- // Keep anonynmous rw-p regions.
- bool ShouldDump(const MappedMemoryRegion& region) {
- const auto rw_p = MappedMemoryRegion::READ | MappedMemoryRegion::WRITE |
- MappedMemoryRegion::PRIVATE;
- if (region.permissions != rw_p)
- return false;
- if (base::StartsWith(region.path, "/", base::CompareCase::SENSITIVE) ||
- base::StartsWith(region.path, "[stack]", base::CompareCase::SENSITIVE)) {
- return false;
- }
- return true;
- }
- base::File OpenProcPidFile(const char* filename, pid_t pid) {
- std::string path = base::StringPrintf("/proc/%d/%s", pid, filename);
- auto file = base::File(base::FilePath(path),
- base::File::FLAG_OPEN | base::File::FLAG_READ);
- if (!file.IsValid()) {
- PLOG(ERROR) << "Cannot open " << path;
- }
- return file;
- }
- bool DumpRegion(const MappedMemoryRegion& region,
- pid_t pid,
- base::File* proc_mem,
- base::File* proc_pagemap) {
- size_t size_in_pages = (region.end - region.start) / kPageSize;
- std::string output_path = base::StringPrintf("%d-%" PRIuS "-%" PRIuS ".dump",
- pid, region.start, region.end);
- base::File output_file(base::FilePath(output_path),
- base::File::FLAG_WRITE | base::File::FLAG_CREATE);
- if (!output_file.IsValid()) {
- PLOG(ERROR) << "Cannot open " << output_path;
- return false;
- }
- std::string metadata_path = output_path + std::string(".metadata");
- base::File metadata_file(base::FilePath(metadata_path),
- base::File::FLAG_WRITE | base::File::FLAG_CREATE);
- if (!metadata_file.IsValid()) {
- PLOG(ERROR) << "Cannot open " << metadata_path;
- return false;
- }
- // Dump metadata.
- // Important: Metadata must be dumped before the data, as reading from
- // /proc/pid/mem will move the data back from swap, so dumping metadata
- // later would not show anything in swap.
- // This also means that dumping the same process twice will result in
- // inaccurate metadata.
- for (size_t i = 0; i < size_in_pages; ++i) {
- // See https://www.kernel.org/doc/Documentation/vm/pagemap.txt
- // 64 bits per page.
- int64_t pagemap_offset =
- ((region.start / kPageSize) + i) * sizeof(PageMapEntry);
- PageMapEntry entry;
- proc_pagemap->Seek(base::File::FROM_BEGIN, pagemap_offset);
- int size_read = proc_pagemap->ReadAtCurrentPos(
- reinterpret_cast<char*>(&entry), sizeof(PageMapEntry));
- if (size_read != sizeof(PageMapEntry)) {
- PLOG(ERROR) << "Cannot read from /proc/pid/pagemap at offset "
- << pagemap_offset;
- return false;
- }
- std::string metadata = base::StringPrintf(
- "%c%c\n", entry.present ? '1' : '0', entry.swapped ? '1' : '0');
- metadata_file.WriteAtCurrentPos(metadata.c_str(), metadata.size());
- }
- // Writing data page by page to avoid allocating too much memory.
- std::vector<char> buffer(kPageSize);
- for (size_t i = 0; i < size_in_pages; ++i) {
- uint64_t address = region.start + i * kPageSize;
- // Works because the upper half of the address space is reserved for the
- // kernel on at least ARM64 and x86_64 bit architectures.
- CHECK(address <= std::numeric_limits<int64_t>::max());
- proc_mem->Seek(base::File::FROM_BEGIN, static_cast<int64_t>(address));
- int size_read = proc_mem->ReadAtCurrentPos(&buffer[0], kPageSize);
- if (size_read != kPageSize) {
- PLOG(ERROR) << "Cannot read from /proc/pid/mem at offset " << address;
- return false;
- }
- int64_t output_offset = i * kPageSize;
- int size_written = output_file.Write(output_offset, &buffer[0], kPageSize);
- if (size_written != kPageSize) {
- PLOG(ERROR) << "Cannot write to output file";
- return false;
- }
- }
- return true;
- }
- // Dumps the content of all the anonymous rw-p mappings in a given process to
- // disk.
- bool DumpMappings(pid_t pid) {
- LOG(INFO) << "Attaching to " << pid;
- // ptrace() is not required to read the process's memory, but the permissions
- // to attach to the target process is.
- // Attach anyway to make it clearer when this fails.
- ScopedPtracer tracer(pid);
- if (!tracer.IsAttached())
- return false;
- LOG(INFO) << "Reading /proc/pid/maps";
- std::vector<base::debug::MappedMemoryRegion> regions;
- bool ok = ParseProcMaps(pid, ®ions);
- if (!ok)
- return false;
- base::File proc_mem = OpenProcPidFile("mem", pid);
- if (!proc_mem.IsValid())
- return false;
- base::File proc_pagemap = OpenProcPidFile("pagemap", pid);
- if (!proc_pagemap.IsValid())
- return false;
- for (const auto& region : regions) {
- if (!ShouldDump(region))
- continue;
- std::string message =
- base::StringPrintf("%" PRIuS "-%" PRIuS " (size %" PRIuS ")",
- region.start, region.end, region.end - region.start);
- LOG(INFO) << "Dumping " << message;
- ok = DumpRegion(region, pid, &proc_mem, &proc_pagemap);
- if (!ok) {
- LOG(WARNING) << "Failed to dump region";
- }
- }
- return true;
- }
- } // namespace
- int main(int argc, char** argv) {
- CHECK(sysconf(_SC_PAGESIZE) == kPageSize);
- if (argc != 2) {
- LOG(ERROR) << "Usage: " << argv[0] << " <pid>";
- return 1;
- }
- pid_t pid;
- bool ok = base::StringToInt(argv[1], &pid);
- if (!ok) {
- LOG(ERROR) << "Cannot parse PID";
- return 1;
- }
- ok = DumpMappings(pid);
- return ok ? 0 : 1;
- }
|