blob: de6384be76f482c1b4cbf15766aa43a709aec204 [file] [log] [blame]
Benoit Lizee34d38a2018-06-18 09:19:021// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <sys/ptrace.h>
6#include <sys/types.h>
7#include <sys/wait.h>
8#include <unistd.h>
9
10#include <limits>
11#include <string>
12#include <vector>
13
14#include "base/debug/proc_maps_linux.h"
15#include "base/files/file.h"
16#include "base/files/file_path.h"
17#include "base/files/file_util.h"
18#include "base/format_macros.h"
19#include "base/logging.h"
20#include "base/posix/eintr_wrapper.h"
21#include "base/strings/string_number_conversions.h"
22#include "base/strings/string_util.h"
23#include "base/strings/stringprintf.h"
24
25namespace {
26
27using base::debug::MappedMemoryRegion;
28constexpr size_t kPageSize = 1 << 12;
29
30// See https://www.kernel.org/doc/Documentation/vm/pagemap.txt.
31struct PageMapEntry {
32 uint64_t pfn_or_swap : 55;
33 uint64_t soft_dirty : 1;
34 uint64_t exclusively_mapped : 1;
35 uint64_t unused : 4;
36 uint64_t file_mapped_or_shared_anon : 1;
37 uint64_t swapped : 1;
38 uint64_t present : 1;
39};
40static_assert(sizeof(PageMapEntry) == sizeof(uint64_t), "Wrong bitfield size");
41
42// Calls ptrace() on a process, and detaches in the destructor.
43class ScopedPtracer {
44 public:
45 ScopedPtracer(pid_t pid) : pid_(pid), is_attached_(false) {
46 // ptrace() delivers a SIGSTOP signal to one thread in the target process,
47 // unless it is already stopped. Since we want to stop the whole process,
48 // send a signal to every thread in the process group.
49 pid_t process_group_id = getpgid(pid);
50 if (killpg(process_group_id, SIGSTOP)) {
51 PLOG(ERROR) << "Cannot stop the process group of " << pid;
52 return;
53 }
54
55 if (ptrace(PTRACE_ATTACH, pid, nullptr, nullptr)) {
56 PLOG(ERROR) << "Unable to attach to " << pid;
57 return;
58 }
59 // ptrace(PTRACE_ATTACH) sends a SISTOP signal to the process, need to wait
60 // for it.
61 int status;
62 pid_t ret = HANDLE_EINTR(waitpid(pid, &status, 0));
63 if (ret != pid) {
64 PLOG(ERROR) << "Waiting for the process failed";
65 return;
66 }
67 if (!WIFSTOPPED(status)) {
68 LOG(ERROR) << "The process is not stopped";
69 ptrace(PTRACE_DETACH, pid, 0, 0);
70 return;
71 }
72 is_attached_ = true;
73 }
74
75 ~ScopedPtracer() {
76 if (!is_attached_)
77 return;
78 if (ptrace(PTRACE_DETACH, pid_, 0, 0)) {
79 PLOG(ERROR) << "Cannot detach from " << pid_;
80 }
81 pid_t process_group_id = getpgid(pid_);
82 if (killpg(process_group_id, SIGCONT)) {
83 PLOG(ERROR) << "Cannot resume the process " << pid_;
84 return;
85 }
86 }
87
88 bool IsAttached() const { return is_attached_; }
89
90 private:
91 pid_t pid_;
92 bool is_attached_;
93};
94
95bool ParseProcMaps(pid_t pid, std::vector<MappedMemoryRegion>* regions) {
96 std::string path = base::StringPrintf("/proc/%d/maps", pid);
97 std::string proc_maps;
98 bool ok = base::ReadFileToString(base::FilePath(path), &proc_maps);
99 if (!ok) {
100 LOG(ERROR) << "Cannot read " << path;
101 return false;
102 }
103 ok = base::debug::ParseProcMaps(proc_maps, regions);
104 if (!ok) {
105 LOG(ERROR) << "Cannot parse " << path;
106 return false;
107 }
108 return true;
109}
110
111// Keep anonynmous rw-p regions.
112bool ShouldDump(const MappedMemoryRegion& region) {
113 const auto rw_p = MappedMemoryRegion::READ | MappedMemoryRegion::WRITE |
114 MappedMemoryRegion::PRIVATE;
115 if (region.permissions != rw_p)
116 return false;
117 if (base::StartsWith(region.path, "/", base::CompareCase::SENSITIVE) ||
118 base::StartsWith(region.path, "[stack]", base::CompareCase::SENSITIVE)) {
119 return false;
120 }
121 return true;
122}
123
124base::File OpenProcPidFile(const char* filename, pid_t pid) {
125 std::string path = base::StringPrintf("/proc/%d/%s", pid, filename);
126 auto file = base::File(base::FilePath(path),
127 base::File::FLAG_OPEN | base::File::FLAG_READ);
128 if (!file.IsValid()) {
129 PLOG(ERROR) << "Cannot open " << path;
130 }
131 return file;
132}
133
134bool DumpRegion(const MappedMemoryRegion& region,
135 pid_t pid,
136 base::File* proc_mem,
137 base::File* proc_pagemap) {
138 size_t size_in_pages = (region.end - region.start) / kPageSize;
139 std::string output_path = base::StringPrintf("%d-%" PRIuS "-%" PRIuS ".dump",
140 pid, region.start, region.end);
141 base::File output_file(base::FilePath(output_path),
142 base::File::FLAG_WRITE | base::File::FLAG_CREATE);
143 if (!output_file.IsValid()) {
144 PLOG(ERROR) << "Cannot open " << output_path;
145 return false;
146 }
147 std::string metadata_path = output_path + std::string(".metadata");
148 base::File metadata_file(base::FilePath(metadata_path),
149 base::File::FLAG_WRITE | base::File::FLAG_CREATE);
150 if (!metadata_file.IsValid()) {
151 PLOG(ERROR) << "Cannot open " << metadata_path;
152 return false;
153 }
154
155 // Dump metadata.
156 // Important: Metadata must be dumped before the data, as reading from
157 // /proc/pid/mem will move the data back from swap, so dumping metadata
158 // later would not show anything in swap.
159 // This also means that dumping the same process twice will result in
160 // inaccurate metadata.
161 for (size_t i = 0; i < size_in_pages; ++i) {
162 // See https://www.kernel.org/doc/Documentation/vm/pagemap.txt
163 // 64 bits per page.
164 int64_t pagemap_offset =
165 ((region.start / kPageSize) + i) * sizeof(PageMapEntry);
166 PageMapEntry entry;
167 proc_pagemap->Seek(base::File::FROM_BEGIN, pagemap_offset);
168 int size_read = proc_pagemap->ReadAtCurrentPos(
169 reinterpret_cast<char*>(&entry), sizeof(PageMapEntry));
170 if (size_read != sizeof(PageMapEntry)) {
171 PLOG(ERROR) << "Cannot read from /proc/pid/pagemap at offset "
172 << pagemap_offset;
173 return false;
174 }
175 std::string metadata = base::StringPrintf(
176 "%c%c\n", entry.present ? '1' : '0', entry.swapped ? '1' : '0');
177 metadata_file.WriteAtCurrentPos(metadata.c_str(), metadata.size());
178 }
179
180 // Writing data page by page to avoid allocating too much memory.
181 std::vector<char> buffer(kPageSize);
182 for (size_t i = 0; i < size_in_pages; ++i) {
183 uint64_t address = region.start + i * kPageSize;
184 // Works because the upper half of the address space is reserved for the
185 // kernel on at least ARM64 and x86_64 bit architectures.
186 CHECK(address <= std::numeric_limits<int64_t>::max());
187 proc_mem->Seek(base::File::FROM_BEGIN, static_cast<int64_t>(address));
188 int size_read = proc_mem->ReadAtCurrentPos(&buffer[0], kPageSize);
189 if (size_read != kPageSize) {
190 PLOG(ERROR) << "Cannot read from /proc/pid/mem at offset " << address;
191 return false;
192 }
193
194 int64_t output_offset = i * kPageSize;
195 int size_written = output_file.Write(output_offset, &buffer[0], kPageSize);
196 if (size_written != kPageSize) {
197 PLOG(ERROR) << "Cannot write to output file";
198 return false;
199 }
200 }
201
202 return true;
203}
204
205// Dumps the content of all the anonymous rw-p mappings in a given process to
206// disk.
207bool DumpMappings(pid_t pid) {
208 LOG(INFO) << "Attaching to " << pid;
209 ScopedPtracer tracer(pid);
210 if (!tracer.IsAttached())
211 return false;
212
213 LOG(INFO) << "Reading /proc/pid/maps";
214 std::vector<base::debug::MappedMemoryRegion> regions;
215 bool ok = ParseProcMaps(pid, &regions);
216 if (!ok)
217 return false;
218
219 base::File proc_mem = OpenProcPidFile("mem", pid);
220 if (!proc_mem.IsValid())
221 return false;
222 base::File proc_pagemap = OpenProcPidFile("pagemap", pid);
223 if (!proc_pagemap.IsValid())
224 return false;
225
226 for (const auto& region : regions) {
227 if (!ShouldDump(region))
228 continue;
229 std::string message =
230 base::StringPrintf("%" PRIuS "-%" PRIuS " (size %" PRIuS ")",
231 region.start, region.end, region.end - region.start);
232 LOG(INFO) << "Dumping " << message;
233 ok = DumpRegion(region, pid, &proc_mem, &proc_pagemap);
234 if (!ok) {
235 LOG(WARNING) << "Failed to dump region";
236 }
237 }
238 return true;
239}
240
241} // namespace
242
243int main(int argc, char** argv) {
244 CHECK(sysconf(_SC_PAGESIZE) == kPageSize);
245
246 if (argc != 2) {
247 LOG(ERROR) << "Usage: " << argv[0] << " <pid>";
248 return 1;
249 }
250 pid_t pid;
251 bool ok = base::StringToInt(argv[1], &pid);
252 if (!ok) {
253 LOG(ERROR) << "Cannot parse PID";
254 return 1;
255 }
256
257 ok = DumpMappings(pid);
258 return ok ? 0 : 1;
259}