blob: fb287cac35d9da79fe7e49684ed0f52d45d8549a [file] [log] [blame]
nealsidb0baafc2009-08-17 23:12:53 +00001// Copyright (c) 2009, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
ted.mielczarekcfc86282010-10-20 15:51:38 +000030#include <string>
31
jessicag.feedback@gmail.com3b3f0c32011-03-22 02:50:41 +000032#include <fcntl.h>
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +000033#include <limits.h>
nealsidb0baafc2009-08-17 23:12:53 +000034#include <unistd.h>
nealsidde545c02010-03-02 00:39:48 +000035#include <signal.h>
ted.mielczarekef7262d2010-12-13 22:10:23 +000036#include <stdint.h>
ted.mielczarekb0201df2011-03-14 17:04:09 +000037#include <sys/mman.h>
ted.mielczarekef7262d2010-12-13 22:10:23 +000038#include <sys/poll.h>
jessicag.feedback@gmail.com3b3f0c32011-03-22 02:50:41 +000039#include <sys/stat.h>
nealsidde545c02010-03-02 00:39:48 +000040#include <sys/types.h>
nealsidb0baafc2009-08-17 23:12:53 +000041
nealsidde545c02010-03-02 00:39:48 +000042#include "breakpad_googletest_includes.h"
nealsidb0baafc2009-08-17 23:12:53 +000043#include "client/linux/minidump_writer/linux_dumper.h"
ted.mielczarekef7262d2010-12-13 22:10:23 +000044#include "common/linux/eintr_wrapper.h"
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +000045#include "common/linux/file_id.h"
ted.mielczarek4621ee02010-09-23 14:55:50 +000046#include "common/memory.h"
nealsidb0baafc2009-08-17 23:12:53 +000047
ted.mielczarekcfc86282010-10-20 15:51:38 +000048using std::string;
nealsidb0baafc2009-08-17 23:12:53 +000049using namespace google_breakpad;
50
51namespace {
52typedef testing::Test LinuxDumperTest;
ted.mielczarekb0201df2011-03-14 17:04:09 +000053
54string GetHelperBinary() {
55 // Locate helper binary next to the current binary.
56 char self_path[PATH_MAX];
57 if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
58 return "";
59 }
60 string helper_path(self_path);
61 size_t pos = helper_path.rfind('/');
62 if (pos == string::npos) {
63 return "";
64 }
65 helper_path.erase(pos + 1);
66 helper_path += "linux_dumper_unittest_helper";
67
68 return helper_path;
69}
70
nealsidb0baafc2009-08-17 23:12:53 +000071}
72
73TEST(LinuxDumperTest, Setup) {
74 LinuxDumper dumper(getpid());
75}
76
77TEST(LinuxDumperTest, FindMappings) {
78 LinuxDumper dumper(getpid());
79 ASSERT_TRUE(dumper.Init());
80
81 ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
82 ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
83 ASSERT_FALSE(dumper.FindMapping(NULL));
84}
85
86TEST(LinuxDumperTest, ThreadList) {
87 LinuxDumper dumper(getpid());
88 ASSERT_TRUE(dumper.Init());
89
nealsidde545c02010-03-02 00:39:48 +000090 ASSERT_GE(dumper.threads().size(), (size_t)1);
nealsidb0baafc2009-08-17 23:12:53 +000091 bool found = false;
92 for (size_t i = 0; i < dumper.threads().size(); ++i) {
93 if (dumper.threads()[i] == getpid()) {
94 found = true;
95 break;
96 }
97 }
98}
99
ted.mielczarekb0201df2011-03-14 17:04:09 +0000100// Helper stack class to close a file descriptor and unmap
101// a mmap'ed mapping.
102class StackHelper {
103public:
104 StackHelper(int fd, char* mapping, size_t size)
105 : fd_(fd), mapping_(mapping), size_(size) {}
106 ~StackHelper() {
107 munmap(mapping_, size_);
108 close(fd_);
109 }
110
111private:
112 int fd_;
113 char* mapping_;
114 size_t size_;
115};
116
117TEST(LinuxDumperTest, MergedMappings) {
118 string helper_path(GetHelperBinary());
119 if (helper_path.empty()) {
120 FAIL() << "Couldn't find helper binary";
121 exit(1);
122 }
123
124 // mmap two segments out of the helper binary, one
125 // enclosed in the other, but with different protections.
126 const size_t kPageSize = sysconf(_SC_PAGESIZE);
127 const size_t kMappingSize = 3 * kPageSize;
128 int fd = open(helper_path.c_str(), O_RDONLY);
129 ASSERT_NE(-1, fd);
130 char* mapping =
131 reinterpret_cast<char*>(mmap(NULL,
132 kMappingSize,
133 PROT_READ,
134 MAP_SHARED,
135 fd,
136 0));
137 ASSERT_TRUE(mapping);
138
139 const u_int64_t kMappingAddress = reinterpret_cast<u_int64_t>(mapping);
140
141 // Ensure that things get cleaned up.
142 StackHelper helper(fd, mapping, kMappingSize);
143
144 // Carve a page out of the first mapping with different permissions.
145 char* inside_mapping = reinterpret_cast<char*>(mmap(mapping + 2 *kPageSize,
146 kPageSize,
147 PROT_NONE,
148 MAP_SHARED | MAP_FIXED,
149 fd,
150 // Map a different offset just to
151 // better test real-world conditions.
152 kPageSize));
153 ASSERT_TRUE(inside_mapping);
154
155 // Now check that LinuxDumper interpreted the mappings properly.
156 LinuxDumper dumper(getpid());
157 ASSERT_TRUE(dumper.Init());
158 int mapping_count = 0;
159 for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
160 const MappingInfo& mapping = *dumper.mappings()[i];
161 if (strcmp(mapping.name, helper_path.c_str()) == 0) {
162 // This mapping should encompass the entire original mapped
163 // range.
164 EXPECT_EQ(kMappingAddress, mapping.start_addr);
165 EXPECT_EQ(kMappingSize, mapping.size);
166 EXPECT_EQ(0, mapping.offset);
167 mapping_count++;
168 }
169 }
170 EXPECT_EQ(1, mapping_count);
171}
172
Ken Mixtere3bad6a2011-10-28 23:26:35 +0000173// Comment out this test due to crosbug.com/6757. Only seems to
Ken Mixter5492dd22010-11-11 15:58:22 -0800174// fail on heavily loaded buildbots and is written with timing
175// assumptions.
176#if 0
nealsidde545c02010-03-02 00:39:48 +0000177TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
178 static const int kNumberOfThreadsInHelperProgram = 5;
179 char kNumberOfThreadsArgument[2];
180 sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
181
ted.mielczarekef7262d2010-12-13 22:10:23 +0000182 int fds[2];
183 ASSERT_NE(-1, pipe(fds));
184
nealsidde545c02010-03-02 00:39:48 +0000185 pid_t child_pid = fork();
186 if (child_pid == 0) {
ted.mielczarekef7262d2010-12-13 22:10:23 +0000187 // In child process.
188 close(fds[0]);
ted.mielczarekcfc86282010-10-20 15:51:38 +0000189
ted.mielczarekb0201df2011-03-14 17:04:09 +0000190 string helper_path(GetHelperBinary());
191 if (helper_path.empty()) {
192 FAIL() << "Couldn't find helper binary";
nealsidb0baafc2009-08-17 23:12:53 +0000193 exit(1);
194 }
nealsidde545c02010-03-02 00:39:48 +0000195
ted.mielczarekef7262d2010-12-13 22:10:23 +0000196 // Pass the pipe fd and the number of threads as arguments.
197 char pipe_fd_string[8];
198 sprintf(pipe_fd_string, "%d", fds[1]);
ted.mielczarekcfc86282010-10-20 15:51:38 +0000199 execl(helper_path.c_str(),
nealsidde545c02010-03-02 00:39:48 +0000200 "linux_dumper_unittest_helper",
ted.mielczarekef7262d2010-12-13 22:10:23 +0000201 pipe_fd_string,
nealsidde545c02010-03-02 00:39:48 +0000202 kNumberOfThreadsArgument,
203 NULL);
204 // Kill if we get here.
205 printf("Errno from exec: %d", errno);
ted.mielczarekcfc86282010-10-20 15:51:38 +0000206 FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
nealsidde545c02010-03-02 00:39:48 +0000207 exit(0);
208 }
ted.mielczarekef7262d2010-12-13 22:10:23 +0000209 close(fds[1]);
210 // Wait for the child process to signal that it's ready.
211 struct pollfd pfd;
212 memset(&pfd, 0, sizeof(pfd));
213 pfd.fd = fds[0];
214 pfd.events = POLLIN | POLLERR;
215
216 const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
217 ASSERT_EQ(1, r);
218 ASSERT_TRUE(pfd.revents & POLLIN);
219 uint8_t junk;
220 read(fds[0], &junk, sizeof(junk));
221 close(fds[0]);
222
223 // Child is ready now.
nealsidde545c02010-03-02 00:39:48 +0000224 LinuxDumper dumper(child_pid);
ted.mielczarekcfc86282010-10-20 15:51:38 +0000225 ASSERT_TRUE(dumper.Init());
nealsidde545c02010-03-02 00:39:48 +0000226 EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
227 EXPECT_TRUE(dumper.ThreadsSuspend());
228
229 ThreadInfo one_thread;
230 for(size_t i = 0; i < dumper.threads().size(); ++i) {
231 EXPECT_TRUE(dumper.ThreadInfoGet(dumper.threads()[i], &one_thread));
jimblandyb68b8002010-03-29 18:23:42 +0000232 // In the helper program, we stored a pointer to the thread id in a
233 // specific register. Check that we can recover its value.
nealsidde545c02010-03-02 00:39:48 +0000234#if defined(__ARM_EABI__)
jimblandyb68b8002010-03-29 18:23:42 +0000235 pid_t *process_tid_location = (pid_t *)(one_thread.regs.uregs[3]);
nealsidde545c02010-03-02 00:39:48 +0000236#elif defined(__i386)
jimblandyb68b8002010-03-29 18:23:42 +0000237 pid_t *process_tid_location = (pid_t *)(one_thread.regs.ecx);
nealsidde545c02010-03-02 00:39:48 +0000238#elif defined(__x86_64)
jimblandyb68b8002010-03-29 18:23:42 +0000239 pid_t *process_tid_location = (pid_t *)(one_thread.regs.rcx);
nealsidde545c02010-03-02 00:39:48 +0000240#else
jimblandyb68b8002010-03-29 18:23:42 +0000241#error This test has not been ported to this platform.
nealsidde545c02010-03-02 00:39:48 +0000242#endif
243 pid_t one_thread_id;
244 dumper.CopyFromProcess(&one_thread_id,
245 dumper.threads()[i],
246 process_tid_location,
247 4);
248 EXPECT_EQ(dumper.threads()[i], one_thread_id);
249 }
250 kill(child_pid, SIGKILL);
251}
Ken Mixter5492dd22010-11-11 15:58:22 -0800252#endif
nealsidde545c02010-03-02 00:39:48 +0000253
nealsidb0baafc2009-08-17 23:12:53 +0000254TEST(LinuxDumperTest, BuildProcPath) {
255 const pid_t pid = getpid();
256 LinuxDumper dumper(pid);
257
258 char maps_path[256] = "dummymappath";
259 char maps_path_expected[256];
260 snprintf(maps_path_expected, sizeof(maps_path_expected),
261 "/proc/%d/maps", pid);
262 dumper.BuildProcPath(maps_path, pid, "maps");
263 ASSERT_STREQ(maps_path, maps_path_expected);
264
265 // In release mode, we expect BuildProcPath to handle the invalid
266 // parameters correctly and fill map_path with an empty
267 // NULL-terminated string.
268#ifdef NDEBUG
269 snprintf(maps_path, sizeof(maps_path), "dummymappath");
270 dumper.BuildProcPath(maps_path, 0, "maps");
271 EXPECT_STREQ(maps_path, "");
272
273 snprintf(maps_path, sizeof(maps_path), "dummymappath");
274 dumper.BuildProcPath(maps_path, getpid(), "");
275 EXPECT_STREQ(maps_path, "");
276
277 snprintf(maps_path, sizeof(maps_path), "dummymappath");
278 dumper.BuildProcPath(maps_path, getpid(), NULL);
279 EXPECT_STREQ(maps_path, "");
280#endif
281}
282
nealsidde545c02010-03-02 00:39:48 +0000283#if !defined(__ARM_EABI__)
ted.mielczarek1807e382011-05-06 23:23:31 +0000284// Ensure that the linux-gate VDSO is included in the mapping list.
nealsidb0baafc2009-08-17 23:12:53 +0000285TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
286 LinuxDumper dumper(getpid());
287 ASSERT_TRUE(dumper.Init());
288
289 void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
nealsidde545c02010-03-02 00:39:48 +0000290 ASSERT_TRUE(linux_gate_loc);
291 bool found_linux_gate = false;
nealsidb0baafc2009-08-17 23:12:53 +0000292
nealsidde545c02010-03-02 00:39:48 +0000293 const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
294 const MappingInfo* mapping;
295 for (unsigned i = 0; i < mappings.size(); ++i) {
296 mapping = mappings[i];
297 if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
298 found_linux_gate = true;
299 break;
nealsidb0baafc2009-08-17 23:12:53 +0000300 }
nealsidb0baafc2009-08-17 23:12:53 +0000301 }
nealsidde545c02010-03-02 00:39:48 +0000302 EXPECT_TRUE(found_linux_gate);
303 EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
304 EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
nealsidb0baafc2009-08-17 23:12:53 +0000305}
ted.mielczarek1807e382011-05-06 23:23:31 +0000306
307// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
308TEST(LinuxDumperTest, LinuxGateMappingID) {
309 LinuxDumper dumper(getpid());
310 ASSERT_TRUE(dumper.Init());
311
312 bool found_linux_gate = false;
313 const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
314 unsigned index = 0;
315 for (unsigned i = 0; i < mappings.size(); ++i) {
316 if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
317 found_linux_gate = true;
318 index = i;
319 break;
320 }
321 }
322 ASSERT_TRUE(found_linux_gate);
323
324 uint8_t identifier[sizeof(MDGUID)];
325 ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
326 true,
327 index,
328 identifier));
329 uint8_t empty_identifier[sizeof(MDGUID)];
330 memset(empty_identifier, 0, sizeof(empty_identifier));
331 EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
332}
333
334// Ensure that the linux-gate VDSO can generate a non-zeroed File ID
335// from a child process.
336TEST(LinuxDumperTest, LinuxGateMappingIDChild) {
337 int fds[2];
338 ASSERT_NE(-1, pipe(fds));
339
340 // Fork a child so ptrace works.
341 const pid_t child = fork();
342 if (child == 0) {
343 close(fds[1]);
344 // Now wait forever for the parent.
345 char b;
346 HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
347 close(fds[0]);
348 syscall(__NR_exit);
349 }
350 close(fds[0]);
351
352 LinuxDumper dumper(child);
353 ASSERT_TRUE(dumper.Init());
354
355 bool found_linux_gate = false;
356 const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
357 unsigned index = 0;
358 for (unsigned i = 0; i < mappings.size(); ++i) {
359 if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
360 found_linux_gate = true;
361 index = i;
362 break;
363 }
364 }
365 ASSERT_TRUE(found_linux_gate);
366
367 // Need to suspend the child so ptrace actually works.
368 ASSERT_TRUE(dumper.ThreadsSuspend());
369 uint8_t identifier[sizeof(MDGUID)];
370 ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
371 true,
372 index,
373 identifier));
374 uint8_t empty_identifier[sizeof(MDGUID)];
375 memset(empty_identifier, 0, sizeof(empty_identifier));
376 EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
377 EXPECT_TRUE(dumper.ThreadsResume());
378 close(fds[1]);
379}
nealsidde545c02010-03-02 00:39:48 +0000380#endif
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +0000381
382TEST(LinuxDumperTest, FileIDsMatch) {
383 // Calculate the File ID of our binary using both
384 // FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
385 // and ensure that we get the same result from both.
386 char exe_name[PATH_MAX];
387 ssize_t len = readlink("/proc/self/exe", exe_name, PATH_MAX - 1);
388 ASSERT_NE(len, -1);
389 exe_name[len] = '\0';
390
391 int fds[2];
392 ASSERT_NE(-1, pipe(fds));
393
ted.mielczarek1807e382011-05-06 23:23:31 +0000394 // Fork a child so ptrace works.
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +0000395 const pid_t child = fork();
396 if (child == 0) {
397 close(fds[1]);
ted.mielczarek1807e382011-05-06 23:23:31 +0000398 // Now wait forever for the parent.
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +0000399 char b;
400 HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
401 close(fds[0]);
402 syscall(__NR_exit);
403 }
404 close(fds[0]);
405
406 LinuxDumper dumper(child);
407 ASSERT_TRUE(dumper.Init());
408 const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
409 bool found_exe = false;
410 unsigned i;
411 for (i = 0; i < mappings.size(); ++i) {
412 const MappingInfo* mapping = mappings[i];
413 if (!strcmp(mapping->name, exe_name)) {
414 found_exe = true;
415 break;
416 }
417 }
418 ASSERT_TRUE(found_exe);
419
420 uint8_t identifier1[sizeof(MDGUID)];
421 uint8_t identifier2[sizeof(MDGUID)];
jessicag.feedback@gmail.com23c82992011-03-30 21:42:27 +0000422 EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
ted.mielczarekef7262d2010-12-13 22:10:23 +0000423 identifier1));
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +0000424 FileID fileid(exe_name);
425 EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
426 char identifier_string1[37];
427 char identifier_string2[37];
428 FileID::ConvertIdentifierToString(identifier1, identifier_string1,
429 37);
430 FileID::ConvertIdentifierToString(identifier2, identifier_string2,
431 37);
432 EXPECT_STREQ(identifier_string1, identifier_string2);
433 close(fds[1]);
434}