blob: 33d9da7c64fbe9c8417e5b1c502d57b46c2887af [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
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +000032#include <limits.h>
nealsidb0baafc2009-08-17 23:12:53 +000033#include <unistd.h>
nealsidde545c02010-03-02 00:39:48 +000034#include <signal.h>
ted.mielczarekef7262d2010-12-13 22:10:23 +000035#include <stdint.h>
ted.mielczarekb0201df2011-03-14 17:04:09 +000036#include <sys/mman.h>
ted.mielczarekef7262d2010-12-13 22:10:23 +000037#include <sys/poll.h>
nealsidde545c02010-03-02 00:39:48 +000038#include <sys/types.h>
nealsidb0baafc2009-08-17 23:12:53 +000039
nealsidde545c02010-03-02 00:39:48 +000040#include "breakpad_googletest_includes.h"
nealsidb0baafc2009-08-17 23:12:53 +000041#include "client/linux/minidump_writer/linux_dumper.h"
ted.mielczarekef7262d2010-12-13 22:10:23 +000042#include "common/linux/eintr_wrapper.h"
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +000043#include "common/linux/file_id.h"
ted.mielczarek4621ee02010-09-23 14:55:50 +000044#include "common/memory.h"
nealsidb0baafc2009-08-17 23:12:53 +000045
ted.mielczarekcfc86282010-10-20 15:51:38 +000046using std::string;
nealsidb0baafc2009-08-17 23:12:53 +000047using namespace google_breakpad;
48
49namespace {
50typedef testing::Test LinuxDumperTest;
ted.mielczarekb0201df2011-03-14 17:04:09 +000051
52string GetHelperBinary() {
53 // Locate helper binary next to the current binary.
54 char self_path[PATH_MAX];
55 if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
56 return "";
57 }
58 string helper_path(self_path);
59 size_t pos = helper_path.rfind('/');
60 if (pos == string::npos) {
61 return "";
62 }
63 helper_path.erase(pos + 1);
64 helper_path += "linux_dumper_unittest_helper";
65
66 return helper_path;
67}
68
nealsidb0baafc2009-08-17 23:12:53 +000069}
70
71TEST(LinuxDumperTest, Setup) {
72 LinuxDumper dumper(getpid());
73}
74
75TEST(LinuxDumperTest, FindMappings) {
76 LinuxDumper dumper(getpid());
77 ASSERT_TRUE(dumper.Init());
78
79 ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
80 ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
81 ASSERT_FALSE(dumper.FindMapping(NULL));
82}
83
84TEST(LinuxDumperTest, ThreadList) {
85 LinuxDumper dumper(getpid());
86 ASSERT_TRUE(dumper.Init());
87
nealsidde545c02010-03-02 00:39:48 +000088 ASSERT_GE(dumper.threads().size(), (size_t)1);
nealsidb0baafc2009-08-17 23:12:53 +000089 bool found = false;
90 for (size_t i = 0; i < dumper.threads().size(); ++i) {
91 if (dumper.threads()[i] == getpid()) {
92 found = true;
93 break;
94 }
95 }
96}
97
ted.mielczarekb0201df2011-03-14 17:04:09 +000098// Helper stack class to close a file descriptor and unmap
99// a mmap'ed mapping.
100class StackHelper {
101public:
102 StackHelper(int fd, char* mapping, size_t size)
103 : fd_(fd), mapping_(mapping), size_(size) {}
104 ~StackHelper() {
105 munmap(mapping_, size_);
106 close(fd_);
107 }
108
109private:
110 int fd_;
111 char* mapping_;
112 size_t size_;
113};
114
115TEST(LinuxDumperTest, MergedMappings) {
116 string helper_path(GetHelperBinary());
117 if (helper_path.empty()) {
118 FAIL() << "Couldn't find helper binary";
119 exit(1);
120 }
121
122 // mmap two segments out of the helper binary, one
123 // enclosed in the other, but with different protections.
124 const size_t kPageSize = sysconf(_SC_PAGESIZE);
125 const size_t kMappingSize = 3 * kPageSize;
126 int fd = open(helper_path.c_str(), O_RDONLY);
127 ASSERT_NE(-1, fd);
128 char* mapping =
129 reinterpret_cast<char*>(mmap(NULL,
130 kMappingSize,
131 PROT_READ,
132 MAP_SHARED,
133 fd,
134 0));
135 ASSERT_TRUE(mapping);
136
137 const u_int64_t kMappingAddress = reinterpret_cast<u_int64_t>(mapping);
138
139 // Ensure that things get cleaned up.
140 StackHelper helper(fd, mapping, kMappingSize);
141
142 // Carve a page out of the first mapping with different permissions.
143 char* inside_mapping = reinterpret_cast<char*>(mmap(mapping + 2 *kPageSize,
144 kPageSize,
145 PROT_NONE,
146 MAP_SHARED | MAP_FIXED,
147 fd,
148 // Map a different offset just to
149 // better test real-world conditions.
150 kPageSize));
151 ASSERT_TRUE(inside_mapping);
152
153 // Now check that LinuxDumper interpreted the mappings properly.
154 LinuxDumper dumper(getpid());
155 ASSERT_TRUE(dumper.Init());
156 int mapping_count = 0;
157 for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
158 const MappingInfo& mapping = *dumper.mappings()[i];
159 if (strcmp(mapping.name, helper_path.c_str()) == 0) {
160 // This mapping should encompass the entire original mapped
161 // range.
162 EXPECT_EQ(kMappingAddress, mapping.start_addr);
163 EXPECT_EQ(kMappingSize, mapping.size);
164 EXPECT_EQ(0, mapping.offset);
165 mapping_count++;
166 }
167 }
168 EXPECT_EQ(1, mapping_count);
169}
170
nealsidde545c02010-03-02 00:39:48 +0000171TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
172 static const int kNumberOfThreadsInHelperProgram = 5;
173 char kNumberOfThreadsArgument[2];
174 sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
175
ted.mielczarekef7262d2010-12-13 22:10:23 +0000176 int fds[2];
177 ASSERT_NE(-1, pipe(fds));
178
nealsidde545c02010-03-02 00:39:48 +0000179 pid_t child_pid = fork();
180 if (child_pid == 0) {
ted.mielczarekef7262d2010-12-13 22:10:23 +0000181 // In child process.
182 close(fds[0]);
183
ted.mielczarekb0201df2011-03-14 17:04:09 +0000184 string helper_path(GetHelperBinary());
185 if (helper_path.empty()) {
186 FAIL() << "Couldn't find helper binary";
ted.mielczarekcfc86282010-10-20 15:51:38 +0000187 exit(1);
188 }
ted.mielczarekcfc86282010-10-20 15:51:38 +0000189
ted.mielczarekef7262d2010-12-13 22:10:23 +0000190 // Pass the pipe fd and the number of threads as arguments.
191 char pipe_fd_string[8];
192 sprintf(pipe_fd_string, "%d", fds[1]);
ted.mielczarekcfc86282010-10-20 15:51:38 +0000193 execl(helper_path.c_str(),
nealsidde545c02010-03-02 00:39:48 +0000194 "linux_dumper_unittest_helper",
ted.mielczarekef7262d2010-12-13 22:10:23 +0000195 pipe_fd_string,
nealsidde545c02010-03-02 00:39:48 +0000196 kNumberOfThreadsArgument,
197 NULL);
198 // Kill if we get here.
199 printf("Errno from exec: %d", errno);
ted.mielczarekcfc86282010-10-20 15:51:38 +0000200 FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
nealsidde545c02010-03-02 00:39:48 +0000201 exit(0);
202 }
ted.mielczarekef7262d2010-12-13 22:10:23 +0000203 close(fds[1]);
204 // Wait for the child process to signal that it's ready.
205 struct pollfd pfd;
206 memset(&pfd, 0, sizeof(pfd));
207 pfd.fd = fds[0];
208 pfd.events = POLLIN | POLLERR;
209
210 const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
211 ASSERT_EQ(1, r);
212 ASSERT_TRUE(pfd.revents & POLLIN);
213 uint8_t junk;
214 read(fds[0], &junk, sizeof(junk));
215 close(fds[0]);
216
217 // Child is ready now.
nealsidde545c02010-03-02 00:39:48 +0000218 LinuxDumper dumper(child_pid);
ted.mielczarekcfc86282010-10-20 15:51:38 +0000219 ASSERT_TRUE(dumper.Init());
nealsidde545c02010-03-02 00:39:48 +0000220 EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
221 EXPECT_TRUE(dumper.ThreadsSuspend());
222
223 ThreadInfo one_thread;
224 for(size_t i = 0; i < dumper.threads().size(); ++i) {
225 EXPECT_TRUE(dumper.ThreadInfoGet(dumper.threads()[i], &one_thread));
jimblandyb68b8002010-03-29 18:23:42 +0000226 // In the helper program, we stored a pointer to the thread id in a
227 // specific register. Check that we can recover its value.
nealsidde545c02010-03-02 00:39:48 +0000228#if defined(__ARM_EABI__)
jimblandyb68b8002010-03-29 18:23:42 +0000229 pid_t *process_tid_location = (pid_t *)(one_thread.regs.uregs[3]);
nealsidde545c02010-03-02 00:39:48 +0000230#elif defined(__i386)
jimblandyb68b8002010-03-29 18:23:42 +0000231 pid_t *process_tid_location = (pid_t *)(one_thread.regs.ecx);
nealsidde545c02010-03-02 00:39:48 +0000232#elif defined(__x86_64)
jimblandyb68b8002010-03-29 18:23:42 +0000233 pid_t *process_tid_location = (pid_t *)(one_thread.regs.rcx);
nealsidde545c02010-03-02 00:39:48 +0000234#else
jimblandyb68b8002010-03-29 18:23:42 +0000235#error This test has not been ported to this platform.
nealsidde545c02010-03-02 00:39:48 +0000236#endif
237 pid_t one_thread_id;
238 dumper.CopyFromProcess(&one_thread_id,
239 dumper.threads()[i],
240 process_tid_location,
241 4);
242 EXPECT_EQ(dumper.threads()[i], one_thread_id);
243 }
244 kill(child_pid, SIGKILL);
245}
246
nealsidb0baafc2009-08-17 23:12:53 +0000247TEST(LinuxDumperTest, BuildProcPath) {
248 const pid_t pid = getpid();
249 LinuxDumper dumper(pid);
250
251 char maps_path[256] = "dummymappath";
252 char maps_path_expected[256];
253 snprintf(maps_path_expected, sizeof(maps_path_expected),
254 "/proc/%d/maps", pid);
255 dumper.BuildProcPath(maps_path, pid, "maps");
256 ASSERT_STREQ(maps_path, maps_path_expected);
257
258 // In release mode, we expect BuildProcPath to handle the invalid
259 // parameters correctly and fill map_path with an empty
260 // NULL-terminated string.
261#ifdef NDEBUG
262 snprintf(maps_path, sizeof(maps_path), "dummymappath");
263 dumper.BuildProcPath(maps_path, 0, "maps");
264 EXPECT_STREQ(maps_path, "");
265
266 snprintf(maps_path, sizeof(maps_path), "dummymappath");
267 dumper.BuildProcPath(maps_path, getpid(), "");
268 EXPECT_STREQ(maps_path, "");
269
270 snprintf(maps_path, sizeof(maps_path), "dummymappath");
271 dumper.BuildProcPath(maps_path, getpid(), NULL);
272 EXPECT_STREQ(maps_path, "");
273#endif
274}
275
nealsidde545c02010-03-02 00:39:48 +0000276#if !defined(__ARM_EABI__)
nealsidb0baafc2009-08-17 23:12:53 +0000277TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
278 LinuxDumper dumper(getpid());
279 ASSERT_TRUE(dumper.Init());
280
281 void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
nealsidde545c02010-03-02 00:39:48 +0000282 ASSERT_TRUE(linux_gate_loc);
283 bool found_linux_gate = false;
nealsidb0baafc2009-08-17 23:12:53 +0000284
nealsidde545c02010-03-02 00:39:48 +0000285 const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
286 const MappingInfo* mapping;
287 for (unsigned i = 0; i < mappings.size(); ++i) {
288 mapping = mappings[i];
289 if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
290 found_linux_gate = true;
291 break;
nealsidb0baafc2009-08-17 23:12:53 +0000292 }
nealsidb0baafc2009-08-17 23:12:53 +0000293 }
nealsidde545c02010-03-02 00:39:48 +0000294 EXPECT_TRUE(found_linux_gate);
295 EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
296 EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
nealsidb0baafc2009-08-17 23:12:53 +0000297}
nealsidde545c02010-03-02 00:39:48 +0000298#endif
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +0000299
300TEST(LinuxDumperTest, FileIDsMatch) {
301 // Calculate the File ID of our binary using both
302 // FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
303 // and ensure that we get the same result from both.
304 char exe_name[PATH_MAX];
305 ssize_t len = readlink("/proc/self/exe", exe_name, PATH_MAX - 1);
306 ASSERT_NE(len, -1);
307 exe_name[len] = '\0';
308
309 int fds[2];
310 ASSERT_NE(-1, pipe(fds));
311
312 // fork a child so we can ptrace it
313 const pid_t child = fork();
314 if (child == 0) {
315 close(fds[1]);
316 // now wait forever for the parent
317 char b;
318 HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
319 close(fds[0]);
320 syscall(__NR_exit);
321 }
322 close(fds[0]);
323
324 LinuxDumper dumper(child);
325 ASSERT_TRUE(dumper.Init());
326 const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
327 bool found_exe = false;
328 unsigned i;
329 for (i = 0; i < mappings.size(); ++i) {
330 const MappingInfo* mapping = mappings[i];
331 if (!strcmp(mapping->name, exe_name)) {
332 found_exe = true;
333 break;
334 }
335 }
336 ASSERT_TRUE(found_exe);
337
338 uint8_t identifier1[sizeof(MDGUID)];
339 uint8_t identifier2[sizeof(MDGUID)];
ted.mielczarekef7262d2010-12-13 22:10:23 +0000340 EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], i,
341 identifier1));
ted.mielczarek0a5fc5d2009-12-23 17:09:27 +0000342 FileID fileid(exe_name);
343 EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
344 char identifier_string1[37];
345 char identifier_string2[37];
346 FileID::ConvertIdentifierToString(identifier1, identifier_string1,
347 37);
348 FileID::ConvertIdentifierToString(identifier2, identifier_string2,
349 37);
350 EXPECT_STREQ(identifier_string1, identifier_string2);
351 close(fds[1]);
352}