blob: 5377aaca52b00067634731697387cfd3fb655041 [file] [log] [blame]
jimblandy83e085b2010-02-09 17:08:56 +00001// Copyright (c) 2010 Google Inc.
nealsidb0baafc2009-08-17 23:12:53 +00002// 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
30#include <unistd.h>
ted.mielczarekef7262d2010-12-13 22:10:23 +000031#include <sys/poll.h>
32#include <sys/stat.h>
nealsidb0baafc2009-08-17 23:12:53 +000033#include <sys/syscall.h>
34
ted.mielczarekef7262d2010-12-13 22:10:23 +000035#include "breakpad_googletest_includes.h"
nealsidb0baafc2009-08-17 23:12:53 +000036#include "client/linux/handler/exception_handler.h"
ted.mielczarekef7262d2010-12-13 22:10:23 +000037#include "client/linux/minidump_writer/linux_dumper.h"
nealsidb0baafc2009-08-17 23:12:53 +000038#include "client/linux/minidump_writer/minidump_writer.h"
ted.mielczarekf480ba12010-02-05 18:21:31 +000039#include "common/linux/eintr_wrapper.h"
ted.mielczarekef7262d2010-12-13 22:10:23 +000040#include "common/linux/file_id.h"
41#include "google_breakpad/processor/minidump.h"
nealsidb0baafc2009-08-17 23:12:53 +000042
43using namespace google_breakpad;
44
ted.mielczarekcfc86282010-10-20 15:51:38 +000045#if !defined(__ANDROID__)
46#define TEMPDIR "/tmp"
47#else
48#define TEMPDIR "/data/local/tmp"
49#endif
50
ted.mielczarekef7262d2010-12-13 22:10:23 +000051// Length of a formatted GUID string =
52// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
53const int kGUIDStringSize = 37;
54
nealsidb0baafc2009-08-17 23:12:53 +000055namespace {
56typedef testing::Test MinidumpWriterTest;
57}
58
59TEST(MinidumpWriterTest, Setup) {
60 int fds[2];
61 ASSERT_NE(-1, pipe(fds));
62
63 const pid_t child = fork();
64 if (child == 0) {
65 close(fds[1]);
66 char b;
67 HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
68 close(fds[0]);
69 syscall(__NR_exit);
70 }
71 close(fds[0]);
72
73 ExceptionHandler::CrashContext context;
74 memset(&context, 0, sizeof(context));
75
ted.mielczarekcfc86282010-10-20 15:51:38 +000076 char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
nealsidb0baafc2009-08-17 23:12:53 +000077 mktemp(templ);
kmixter@chromium.orgb5dfa282010-12-08 22:26:20 +000078 // Set a non-zero tid to avoid tripping asserts.
79 context.tid = 1;
nealsidb0baafc2009-08-17 23:12:53 +000080 ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
81 struct stat st;
82 ASSERT_EQ(stat(templ, &st), 0);
83 ASSERT_GT(st.st_size, 0u);
84 unlink(templ);
85
86 close(fds[1]);
87}
ted.mielczarekef7262d2010-12-13 22:10:23 +000088
89// Test that mapping info can be specified when writing a minidump,
90// and that it ends up in the module list of the minidump.
91TEST(MinidumpWriterTest, MappingInfo) {
92 int fds[2];
93 ASSERT_NE(-1, pipe(fds));
94
95 // These are defined here so the parent can use them to check the
96 // data from the minidump afterwards.
97 const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
98 const char* kMemoryName = "a fake module";
99 const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
100 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
101 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
102 };
103 char module_identifier_buffer[kGUIDStringSize];
104 FileID::ConvertIdentifierToString(kModuleGUID,
105 module_identifier_buffer,
106 sizeof(module_identifier_buffer));
107 string module_identifier(module_identifier_buffer);
108 // Strip out dashes
109 size_t pos;
110 while ((pos = module_identifier.find('-')) != string::npos) {
111 module_identifier.erase(pos, 1);
112 }
113 // And append a zero, because module IDs include an "age" field
114 // which is always zero on Linux.
115 module_identifier += "0";
116
117 // Get some memory.
118 char* memory =
119 reinterpret_cast<char*>(mmap(NULL,
120 kMemorySize,
121 PROT_READ | PROT_WRITE,
122 MAP_PRIVATE | MAP_ANON,
123 -1,
124 0));
125 const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
126 ASSERT_TRUE(memory);
127
128 const pid_t child = fork();
129 if (child == 0) {
130 close(fds[1]);
131 char b;
132 HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
133 close(fds[0]);
134 syscall(__NR_exit);
135 }
136 close(fds[0]);
137
138 ExceptionHandler::CrashContext context;
139 memset(&context, 0, sizeof(context));
140 context.tid = 1;
141
142 char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
143 mktemp(templ);
144
145 // Add information about the mapped memory.
146 MappingInfo info;
147 info.start_addr = kMemoryAddress;
148 info.size = kMemorySize;
149 info.offset = 0;
150 strcpy(info.name, kMemoryName);
151
152 MappingList mappings;
153 MappingEntry mapping;
154 mapping.first = info;
155 memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
156 mappings.push_back(mapping);
157 ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context), mappings));
158
159 // Read the minidump. Load the module list, and ensure that
160 // the mmap'ed |memory| is listed with the given module name
161 // and debug ID.
162 Minidump minidump(templ);
163 ASSERT_TRUE(minidump.Read());
164
165 MinidumpModuleList* module_list = minidump.GetModuleList();
166 ASSERT_TRUE(module_list);
167 const MinidumpModule* module =
168 module_list->GetModuleForAddress(kMemoryAddress);
169 ASSERT_TRUE(module);
170
171 EXPECT_EQ(kMemoryAddress, module->base_address());
172 EXPECT_EQ(kMemorySize, module->size());
173 EXPECT_EQ(kMemoryName, module->code_file());
174 EXPECT_EQ(module_identifier, module->debug_identifier());
175
176 unlink(templ);
177 close(fds[1]);
178}
179
180// Test that mapping info can be specified, and that it overrides
181// existing mappings that are wholly contained within the specified
182// range.
183TEST(MinidumpWriterTest, MappingInfoContained) {
184 int fds[2];
185 ASSERT_NE(-1, pipe(fds));
186
187 // These are defined here so the parent can use them to check the
188 // data from the minidump afterwards.
189 const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
190 const char* kMemoryName = "a fake module";
191 const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
192 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
193 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
194 };
195 char module_identifier_buffer[kGUIDStringSize];
196 FileID::ConvertIdentifierToString(kModuleGUID,
197 module_identifier_buffer,
198 sizeof(module_identifier_buffer));
199 string module_identifier(module_identifier_buffer);
200 // Strip out dashes
201 size_t pos;
202 while ((pos = module_identifier.find('-')) != string::npos) {
203 module_identifier.erase(pos, 1);
204 }
205 // And append a zero, because module IDs include an "age" field
206 // which is always zero on Linux.
207 module_identifier += "0";
208
209 // mmap a file
210 char tempfile[] = TEMPDIR "/minidump-writer-unittest-temp-XXXXXX";
211 mktemp(tempfile);
212 int fd = open(tempfile, O_RDWR | O_CREAT, 0);
213 ASSERT_NE(-1, fd);
214 unlink(tempfile);
215 // fill with zeros
216 char buffer[kMemorySize];
217 memset(buffer, 0, kMemorySize);
218 ASSERT_EQ(kMemorySize, write(fd, buffer, kMemorySize));
219 lseek(fd, 0, SEEK_SET);
220
221 char* memory =
222 reinterpret_cast<char*>(mmap(NULL,
223 kMemorySize,
224 PROT_READ | PROT_WRITE,
225 MAP_PRIVATE,
226 fd,
227 0));
228 const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
229 ASSERT_TRUE(memory);
230 close(fd);
231
232 const pid_t child = fork();
233 if (child == 0) {
234 close(fds[1]);
235 char b;
236 HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
237 close(fds[0]);
238 syscall(__NR_exit);
239 }
240 close(fds[0]);
241
242 ExceptionHandler::CrashContext context;
243 memset(&context, 0, sizeof(context));
244 context.tid = 1;
245
246 char dumpfile[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
247 mktemp(dumpfile);
248
249 // Add information about the mapped memory. Report it as being larger than
250 // it actually is.
251 MappingInfo info;
252 info.start_addr = kMemoryAddress - kMemorySize;
253 info.size = kMemorySize * 3;
254 info.offset = 0;
255 strcpy(info.name, kMemoryName);
256
257 MappingList mappings;
258 MappingEntry mapping;
259 mapping.first = info;
260 memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
261 mappings.push_back(mapping);
262 ASSERT_TRUE(WriteMinidump(dumpfile, child, &context, sizeof(context), mappings));
263
264 // Read the minidump. Load the module list, and ensure that
265 // the mmap'ed |memory| is listed with the given module name
266 // and debug ID.
267 Minidump minidump(dumpfile);
268 ASSERT_TRUE(minidump.Read());
269
270 MinidumpModuleList* module_list = minidump.GetModuleList();
271 ASSERT_TRUE(module_list);
272 const MinidumpModule* module =
273 module_list->GetModuleForAddress(kMemoryAddress);
274 ASSERT_TRUE(module);
275
276 EXPECT_EQ(info.start_addr, module->base_address());
277 EXPECT_EQ(info.size, module->size());
278 EXPECT_EQ(kMemoryName, module->code_file());
279 EXPECT_EQ(module_identifier, module->debug_identifier());
280
281 unlink(dumpfile);
282 close(fds[1]);
283}
284
285TEST(MinidumpWriterTest, DeletedBinary) {
286 static const int kNumberOfThreadsInHelperProgram = 1;
287 char kNumberOfThreadsArgument[2];
288 sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
289
290 // Locate helper binary next to the current binary.
291 char self_path[PATH_MAX];
292 if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
293 FAIL() << "readlink failed: " << strerror(errno);
294 exit(1);
295 }
296 string helper_path(self_path);
297 size_t pos = helper_path.rfind('/');
298 if (pos == string::npos) {
299 FAIL() << "no trailing slash in path: " << helper_path;
300 exit(1);
301 }
302 helper_path.erase(pos + 1);
303 helper_path += "linux_dumper_unittest_helper";
304
305 // Copy binary to a temp file.
306 char binpath[] = TEMPDIR "/linux-dumper-unittest-helper-XXXXXX";
307 mktemp(binpath);
308 char cmdline[2 * PATH_MAX];
309 sprintf(cmdline, "/bin/cp \"%s\" \"%s\"", helper_path.c_str(), binpath);
310 ASSERT_EQ(0, system(cmdline));
311 ASSERT_EQ(0, chmod(binpath, 0755));
312
313 int fds[2];
314 ASSERT_NE(-1, pipe(fds));
315
316 pid_t child_pid = fork();
317 if (child_pid == 0) {
318 // In child process.
319 close(fds[0]);
320
321 // Pass the pipe fd and the number of threads as arguments.
322 char pipe_fd_string[8];
323 sprintf(pipe_fd_string, "%d", fds[1]);
324 execl(binpath,
325 binpath,
326 pipe_fd_string,
327 kNumberOfThreadsArgument,
328 NULL);
329 }
330 close(fds[1]);
331 // Wait for the child process to signal that it's ready.
332 struct pollfd pfd;
333 memset(&pfd, 0, sizeof(pfd));
334 pfd.fd = fds[0];
335 pfd.events = POLLIN | POLLERR;
336
337 const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
338 ASSERT_EQ(1, r);
339 ASSERT_TRUE(pfd.revents & POLLIN);
340 uint8_t junk;
341 read(fds[0], &junk, sizeof(junk));
342 close(fds[0]);
343
344 // Child is ready now.
345 // Unlink the test binary.
346 unlink(binpath);
347
348 ExceptionHandler::CrashContext context;
349 memset(&context, 0, sizeof(context));
350
351 char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
352 mktemp(templ);
353 // Set a non-zero tid to avoid tripping asserts.
354 context.tid = 1;
355 ASSERT_TRUE(WriteMinidump(templ, child_pid, &context, sizeof(context)));
356 kill(child_pid, SIGKILL);
357
358 struct stat st;
359 ASSERT_EQ(stat(templ, &st), 0);
360 ASSERT_GT(st.st_size, 0u);
361
362
363
364 Minidump minidump(templ);
365 ASSERT_TRUE(minidump.Read());
366
367 // Check that the main module filename is correct.
368 MinidumpModuleList* module_list = minidump.GetModuleList();
369 ASSERT_TRUE(module_list);
370 const MinidumpModule* module = module_list->GetMainModule();
371 EXPECT_STREQ(binpath, module->code_file().c_str());
372 // Check that the file ID is correct.
373 FileID fileid(helper_path.c_str());
374 uint8_t identifier[sizeof(MDGUID)];
375 EXPECT_TRUE(fileid.ElfFileIdentifier(identifier));
376 char identifier_string[kGUIDStringSize];
377 FileID::ConvertIdentifierToString(identifier,
378 identifier_string,
379 kGUIDStringSize);
380 string module_identifier(identifier_string);
381 // Strip out dashes
382 while ((pos = module_identifier.find('-')) != string::npos) {
383 module_identifier.erase(pos, 1);
384 }
385 // And append a zero, because module IDs include an "age" field
386 // which is always zero on Linux.
387 module_identifier += "0";
388 EXPECT_EQ(module_identifier, module->debug_identifier());
389
390 unlink(templ);
391}