blob: e0070a3b1d01d310ad51c44001c3cce8b0606cdb [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.
Michael Krebs223e62f2011-11-14 18:43:57 -080056 char self_path[PATH_MAX + 1];
57 ssize_t self_path_len = readlink("/proc/self/exe", self_path,
58 sizeof(self_path) - 1);
59 if (self_path_len < 0 || self_path_len == sizeof(self_path) - 1)
ted.mielczarekb0201df2011-03-14 17:04:09 +000060 return "";
Michael Krebs223e62f2011-11-14 18:43:57 -080061 self_path[self_path_len] = '\0'; // null-terminate path
ted.mielczarekb0201df2011-03-14 17:04:09 +000062 string helper_path(self_path);
63 size_t pos = helper_path.rfind('/');
64 if (pos == string::npos) {
65 return "";
66 }
67 helper_path.erase(pos + 1);
68 helper_path += "linux_dumper_unittest_helper";
69
70 return helper_path;
71}
72
nealsidb0baafc2009-08-17 23:12:53 +000073}
74
75TEST(LinuxDumperTest, Setup) {
76 LinuxDumper dumper(getpid());
77}
78
79TEST(LinuxDumperTest, FindMappings) {
80 LinuxDumper dumper(getpid());
81 ASSERT_TRUE(dumper.Init());
82
83 ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
84 ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
85 ASSERT_FALSE(dumper.FindMapping(NULL));
86}
87
88TEST(LinuxDumperTest, ThreadList) {
89 LinuxDumper dumper(getpid());
90 ASSERT_TRUE(dumper.Init());
91
nealsidde545c02010-03-02 00:39:48 +000092 ASSERT_GE(dumper.threads().size(), (size_t)1);
nealsidb0baafc2009-08-17 23:12:53 +000093 bool found = false;
94 for (size_t i = 0; i < dumper.threads().size(); ++i) {
95 if (dumper.threads()[i] == getpid()) {
96 found = true;
97 break;
98 }
99 }
100}
101
ted.mielczarekb0201df2011-03-14 17:04:09 +0000102// Helper stack class to close a file descriptor and unmap
103// a mmap'ed mapping.
104class StackHelper {
105public:
106 StackHelper(int fd, char* mapping, size_t size)
107 : fd_(fd), mapping_(mapping), size_(size) {}
108 ~StackHelper() {
109 munmap(mapping_, size_);
110 close(fd_);
111 }
112
113private:
114 int fd_;
115 char* mapping_;
116 size_t size_;
117};
118
119TEST(LinuxDumperTest, MergedMappings) {
120 string helper_path(GetHelperBinary());
121 if (helper_path.empty()) {
122 FAIL() << "Couldn't find helper binary";
123 exit(1);
124 }
125
126 // mmap two segments out of the helper binary, one
127 // enclosed in the other, but with different protections.
128 const size_t kPageSize = sysconf(_SC_PAGESIZE);
129 const size_t kMappingSize = 3 * kPageSize;
130 int fd = open(helper_path.c_str(), O_RDONLY);
131 ASSERT_NE(-1, fd);
132 char* mapping =
133 reinterpret_cast<char*>(mmap(NULL,
134 kMappingSize,
135 PROT_READ,
136 MAP_SHARED,
137 fd,
138 0));
139 ASSERT_TRUE(mapping);
140
141 const u_int64_t kMappingAddress = reinterpret_cast<u_int64_t>(mapping);
142
143 // Ensure that things get cleaned up.
144 StackHelper helper(fd, mapping, kMappingSize);
145
146 // Carve a page out of the first mapping with different permissions.
147 char* inside_mapping = reinterpret_cast<char*>(mmap(mapping + 2 *kPageSize,
148 kPageSize,
149 PROT_NONE,
150 MAP_SHARED | MAP_FIXED,
151 fd,
152 // Map a different offset just to
153 // better test real-world conditions.
154 kPageSize));
155 ASSERT_TRUE(inside_mapping);
156
157 // Now check that LinuxDumper interpreted the mappings properly.
158 LinuxDumper dumper(getpid());
159 ASSERT_TRUE(dumper.Init());
160 int mapping_count = 0;
161 for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
162 const MappingInfo& mapping = *dumper.mappings()[i];
163 if (strcmp(mapping.name, helper_path.c_str()) == 0) {
164 // This mapping should encompass the entire original mapped
165 // range.
166 EXPECT_EQ(kMappingAddress, mapping.start_addr);
167 EXPECT_EQ(kMappingSize, mapping.size);
168 EXPECT_EQ(0, mapping.offset);
169 mapping_count++;
170 }
171 }
172 EXPECT_EQ(1, mapping_count);
173}
174
Ken Mixtere3bad6a2011-10-28 23:26:35 +0000175// Comment out this test due to crosbug.com/6757. Only seems to
Ken Mixter5492dd22010-11-11 15:58:22 -0800176// fail on heavily loaded buildbots and is written with timing
177// assumptions.
178#if 0
nealsidde545c02010-03-02 00:39:48 +0000179TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
180 static const int kNumberOfThreadsInHelperProgram = 5;
181 char kNumberOfThreadsArgument[2];
182 sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
183
ted.mielczarekef7262d2010-12-13 22:10:23 +0000184 int fds[2];
185 ASSERT_NE(-1, pipe(fds));
186
nealsidde545c02010-03-02 00:39:48 +0000187 pid_t child_pid = fork();
188 if (child_pid == 0) {
ted.mielczarekef7262d2010-12-13 22:10:23 +0000189 // In child process.
190 close(fds[0]);
ted.mielczarekcfc86282010-10-20 15:51:38 +0000191
ted.mielczarekb0201df2011-03-14 17:04:09 +0000192 string helper_path(GetHelperBinary());
193 if (helper_path.empty()) {
194 FAIL() << "Couldn't find helper binary";
nealsidb0baafc2009-08-17 23:12:53 +0000195 exit(1);
196 }
nealsidde545c02010-03-02 00:39:48 +0000197
ted.mielczarekef7262d2010-12-13 22:10:23 +0000198 // Pass the pipe fd and the number of threads as arguments.
199 char pipe_fd_string[8];
200 sprintf(pipe_fd_string, "%d", fds[1]);
ted.mielczarekcfc86282010-10-20 15:51:38 +0000201 execl(helper_path.c_str(),
nealsidde545c02010-03-02 00:39:48 +0000202 "linux_dumper_unittest_helper",
ted.mielczarekef7262d2010-12-13 22:10:23 +0000203 pipe_fd_string,
nealsidde545c02010-03-02 00:39:48 +0000204 kNumberOfThreadsArgument,
205 NULL);
206 // Kill if we get here.
207 printf("Errno from exec: %d", errno);
ted.mielczarekcfc86282010-10-20 15:51:38 +0000208 FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
nealsidde545c02010-03-02 00:39:48 +0000209 exit(0);
210 }
ted.mielczarekef7262d2010-12-13 22:10:23 +0000211 close(fds[1]);
212 // Wait for the child process to signal that it's ready.
213 struct pollfd pfd;
214 memset(&pfd, 0, sizeof(pfd));
215 pfd.fd = fds[0];
216 pfd.events = POLLIN | POLLERR;
217
218 const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
219 ASSERT_EQ(1, r);
220 ASSERT_TRUE(pfd.revents & POLLIN);
221 uint8_t junk;
222 read(fds[0], &junk, sizeof(junk));
223 close(fds[0]);
224
225 // Child is ready now.
nealsidde545c02010-03-02 00:39:48 +0000226 LinuxDumper dumper(child_pid);
ted.mielczarekcfc86282010-10-20 15:51:38 +0000227 ASSERT_TRUE(dumper.Init());
nealsidde545c02010-03-02 00:39:48 +0000228 EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
229 EXPECT_TRUE(dumper.ThreadsSuspend());
230
231 ThreadInfo one_thread;
232 for(size_t i = 0; i < dumper.threads().size(); ++i) {
233 EXPECT_TRUE(dumper.ThreadInfoGet(dumper.threads()[i], &one_thread));
jimblandyb68b8002010-03-29 18:23:42 +0000234 // In the helper program, we stored a pointer to the thread id in a
235 // specific register. Check that we can recover its value.
nealsidde545c02010-03-02 00:39:48 +0000236#if defined(__ARM_EABI__)
jimblandyb68b8002010-03-29 18:23:42 +0000237 pid_t *process_tid_location = (pid_t *)(one_thread.regs.uregs[3]);
nealsidde545c02010-03-02 00:39:48 +0000238#elif defined(__i386)
jimblandyb68b8002010-03-29 18:23:42 +0000239 pid_t *process_tid_location = (pid_t *)(one_thread.regs.ecx);
nealsidde545c02010-03-02 00:39:48 +0000240#elif defined(__x86_64)
jimblandyb68b8002010-03-29 18:23:42 +0000241 pid_t *process_tid_location = (pid_t *)(one_thread.regs.rcx);
nealsidde545c02010-03-02 00:39:48 +0000242#else
jimblandyb68b8002010-03-29 18:23:42 +0000243#error This test has not been ported to this platform.
nealsidde545c02010-03-02 00:39:48 +0000244#endif
245 pid_t one_thread_id;
246 dumper.CopyFromProcess(&one_thread_id,
247 dumper.threads()[i],
248 process_tid_location,
249 4);
250 EXPECT_EQ(dumper.threads()[i], one_thread_id);
251 }
252 kill(child_pid, SIGKILL);
253}
Ken Mixter5492dd22010-11-11 15:58:22 -0800254#endif
nealsidde545c02010-03-02 00:39:48 +0000255
nealsidb0baafc2009-08-17 23:12:53 +0000256TEST(LinuxDumperTest, BuildProcPath) {
257 const pid_t pid = getpid();
258 LinuxDumper dumper(pid);
259
260 char maps_path[256] = "dummymappath";
261 char maps_path_expected[256];
262 snprintf(maps_path_expected, sizeof(maps_path_expected),
263 "/proc/%d/maps", pid);
264 dumper.BuildProcPath(maps_path, pid, "maps");
265 ASSERT_STREQ(maps_path, maps_path_expected);
266
267 // In release mode, we expect BuildProcPath to handle the invalid
268 // parameters correctly and fill map_path with an empty
269 // NULL-terminated string.
270#ifdef NDEBUG
271 snprintf(maps_path, sizeof(maps_path), "dummymappath");
272 dumper.BuildProcPath(maps_path, 0, "maps");
273 EXPECT_STREQ(maps_path, "");
274
275 snprintf(maps_path, sizeof(maps_path), "dummymappath");
276 dumper.BuildProcPath(maps_path, getpid(), "");
277 EXPECT_STREQ(maps_path, "");
278
279 snprintf(maps_path, sizeof(maps_path), "dummymappath");
280 dumper.BuildProcPath(maps_path, getpid(), NULL);
281 EXPECT_STREQ(maps_path, "");
282#endif
283}
284
nealsidde545c02010-03-02 00:39:48 +0000285#if !defined(__ARM_EABI__)
ted.mielczarek1807e382011-05-06 23:23:31 +0000286// Ensure that the linux-gate VDSO is included in the mapping list.
nealsidb0baafc2009-08-17 23:12:53 +0000287TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
288 LinuxDumper dumper(getpid());
289 ASSERT_TRUE(dumper.Init());
290
291 void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
nealsidde545c02010-03-02 00:39:48 +0000292 ASSERT_TRUE(linux_gate_loc);
293 bool found_linux_gate = false;
nealsidb0baafc2009-08-17 23:12:53 +0000294
nealsidde545c02010-03-02 00:39:48 +0000295 const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
296 const MappingInfo* mapping;
297 for (unsigned i = 0; i < mappings.size(); ++i) {
298 mapping = mappings[i];
299 if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
300 found_linux_gate = true;
301 break;
nealsidb0baafc2009-08-17 23:12:53 +0000302 }
nealsidb0baafc2009-08-17 23:12:53 +0000303 }
nealsidde545c02010-03-02 00:39:48 +0000304 EXPECT_TRUE(found_linux_gate);
305 EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
306 EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
nealsidb0baafc2009-08-17 23:12:53 +0000307}
ted.mielczarek1807e382011-05-06 23:23:31 +0000308
309// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
310TEST(LinuxDumperTest, LinuxGateMappingID) {
311 LinuxDumper dumper(getpid());
312 ASSERT_TRUE(dumper.Init());
313
314 bool found_linux_gate = false;
315 const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
316 unsigned index = 0;
317 for (unsigned i = 0; i < mappings.size(); ++i) {
318 if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
319 found_linux_gate = true;
320 index = i;
321 break;
322 }
323 }
324 ASSERT_TRUE(found_linux_gate);
325
326 uint8_t identifier[sizeof(MDGUID)];
327 ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
328 true,
329 index,
330 identifier));
331 uint8_t empty_identifier[sizeof(MDGUID)];
332 memset(empty_identifier, 0, sizeof(empty_identifier));
333 EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
334}
335
336// Ensure that the linux-gate VDSO can generate a non-zeroed File ID
337// from a child process.
338TEST(LinuxDumperTest, LinuxGateMappingIDChild) {
339 int fds[2];
340 ASSERT_NE(-1, pipe(fds));
341
342 // Fork a child so ptrace works.
343 const pid_t child = fork();
344 if (child == 0) {
345 close(fds[1]);
346 // Now wait forever for the parent.
347 char b;
348 HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
349 close(fds[0]);
350 syscall(__NR_exit);
351 }
352 close(fds[0]);
353
354 LinuxDumper dumper(child);
355 ASSERT_TRUE(dumper.Init());
356
357 bool found_linux_gate = false;
358 const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
359 unsigned index = 0;
360 for (unsigned i = 0; i < mappings.size(); ++i) {
361 if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
362 found_linux_gate = true;
363 index = i;
364 break;
365 }
366 }
367 ASSERT_TRUE(found_linux_gate);
368
369 // Need to suspend the child so ptrace actually works.
370 ASSERT_TRUE(dumper.ThreadsSuspend());
371 uint8_t identifier[sizeof(MDGUID)];
372 ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
373 true,
374 index,
375 identifier));
376 uint8_t empty_identifier[sizeof(MDGUID)];
377 memset(empty_identifier, 0, sizeof(empty_identifier));
378 EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
379 EXPECT_TRUE(dumper.ThreadsResume());
380 close(fds[1]);
381}
nealsidde545c02010-03-02 00:39:48 +0000382#endif
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +0000383
384TEST(LinuxDumperTest, FileIDsMatch) {
385 // Calculate the File ID of our binary using both
386 // FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
387 // and ensure that we get the same result from both.
388 char exe_name[PATH_MAX];
389 ssize_t len = readlink("/proc/self/exe", exe_name, PATH_MAX - 1);
390 ASSERT_NE(len, -1);
Michael Krebs223e62f2011-11-14 18:43:57 -0800391 ASSERT_NE(len, PATH_MAX - 1);
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +0000392 exe_name[len] = '\0';
393
394 int fds[2];
395 ASSERT_NE(-1, pipe(fds));
396
ted.mielczarek1807e382011-05-06 23:23:31 +0000397 // Fork a child so ptrace works.
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +0000398 const pid_t child = fork();
399 if (child == 0) {
400 close(fds[1]);
ted.mielczarek1807e382011-05-06 23:23:31 +0000401 // Now wait forever for the parent.
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +0000402 char b;
403 HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
404 close(fds[0]);
405 syscall(__NR_exit);
406 }
407 close(fds[0]);
408
409 LinuxDumper dumper(child);
410 ASSERT_TRUE(dumper.Init());
411 const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
412 bool found_exe = false;
413 unsigned i;
414 for (i = 0; i < mappings.size(); ++i) {
415 const MappingInfo* mapping = mappings[i];
416 if (!strcmp(mapping->name, exe_name)) {
417 found_exe = true;
418 break;
419 }
420 }
421 ASSERT_TRUE(found_exe);
422
423 uint8_t identifier1[sizeof(MDGUID)];
424 uint8_t identifier2[sizeof(MDGUID)];
jessicag.feedback@gmail.com23c82992011-03-30 21:42:27 +0000425 EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
ted.mielczarekef7262d2010-12-13 22:10:23 +0000426 identifier1));
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +0000427 FileID fileid(exe_name);
428 EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
429 char identifier_string1[37];
430 char identifier_string2[37];
431 FileID::ConvertIdentifierToString(identifier1, identifier_string1,
432 37);
433 FileID::ConvertIdentifierToString(identifier2, identifier_string2,
434 37);
435 EXPECT_STREQ(identifier_string1, identifier_string2);
436 close(fds[1]);
437}