1 // Copyright 2014 Google LLC
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 // * Neither the name of Google LLC nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 #ifdef HAVE_CONFIG_H
30 #include <config.h> // Must come first
31 #endif
32
33 #include <ctype.h>
34 #include <sys/syscall.h>
35 #include <sys/types.h>
36 #include <unistd.h>
37 #include <ucontext.h>
38
39 #include <sstream>
40 #include <string>
41
42 #include "breakpad_googletest_includes.h"
43 #include "client/linux/handler/exception_handler.h"
44 #include "client/linux/handler/microdump_extra_info.h"
45 #include "client/linux/microdump_writer/microdump_writer.h"
46 #include "common/linux/breakpad_getcontext.h"
47 #include "common/linux/eintr_wrapper.h"
48 #include "common/linux/ignore_ret.h"
49 #include "common/scoped_ptr.h"
50 #include "common/tests/auto_tempdir.h"
51 #include "common/using_std_string.h"
52
53 using namespace google_breakpad;
54
55 extern "C" {
56 extern char __executable_start;
57 extern char __etext;
58 }
59
60 namespace {
61
62 typedef testing::Test MicrodumpWriterTest;
63
MakeMicrodumpExtraInfo(const char * build_fingerprint,const char * product_info,const char * gpu_fingerprint)64 MicrodumpExtraInfo MakeMicrodumpExtraInfo(
65 const char* build_fingerprint,
66 const char* product_info,
67 const char* gpu_fingerprint) {
68 MicrodumpExtraInfo info;
69 info.build_fingerprint = build_fingerprint;
70 info.product_info = product_info;
71 info.gpu_fingerprint = gpu_fingerprint;
72 info.process_type = "Browser";
73 return info;
74 }
75
ContainsMicrodump(const std::string & buf)76 bool ContainsMicrodump(const std::string& buf) {
77 return std::string::npos != buf.find("-----BEGIN BREAKPAD MICRODUMP-----") &&
78 std::string::npos != buf.find("-----END BREAKPAD MICRODUMP-----");
79 }
80
81 const char kIdentifiableString[] = "_IDENTIFIABLE_";
82 const uintptr_t kCrashAddress = 0xdeaddeadu;
83
CrashAndGetMicrodump(const MappingList & mappings,const MicrodumpExtraInfo & microdump_extra_info,std::string * microdump,bool skip_dump_if_principal_mapping_not_referenced=false,uintptr_t address_within_principal_mapping=0,bool sanitize_stack=false)84 void CrashAndGetMicrodump(const MappingList& mappings,
85 const MicrodumpExtraInfo& microdump_extra_info,
86 std::string* microdump,
87 bool skip_dump_if_principal_mapping_not_referenced = false,
88 uintptr_t address_within_principal_mapping = 0,
89 bool sanitize_stack = false) {
90 int fds[2];
91 ASSERT_NE(-1, pipe(fds));
92
93 AutoTempDir temp_dir;
94 string stderr_file = temp_dir.path() + "/stderr.log";
95 int err_fd = open(stderr_file.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
96 ASSERT_NE(-1, err_fd);
97
98 char identifiable_string[sizeof(kIdentifiableString)];
99
100 // This string should not appear in the resulting microdump if it
101 // has been sanitized.
102 strcpy(identifiable_string, kIdentifiableString);
103 // Force the strcpy to not be optimized away.
104 IGNORE_RET(write(STDOUT_FILENO, identifiable_string, 0));
105
106 const pid_t child = fork();
107 if (child == 0) {
108 close(fds[1]);
109 char b;
110 IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
111 close(fds[0]);
112 syscall(__NR_exit);
113 }
114 close(fds[0]);
115
116 ExceptionHandler::CrashContext context;
117 memset(&context, 0, sizeof(context));
118 // Pretend the current context is the child context (which is
119 // approximately right) so that we have a valid stack pointer, and
120 // can fetch child stack data via ptrace.
121 getcontext(&context.context);
122 // Set a non-zero tid to avoid tripping asserts.
123 context.tid = child;
124 context.siginfo.si_signo = MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED;
125 context.siginfo.si_addr = reinterpret_cast<void*>(kCrashAddress);
126
127 // Redirect temporarily stderr to the stderr.log file.
128 int save_err = dup(STDERR_FILENO);
129 ASSERT_NE(-1, save_err);
130 ASSERT_NE(-1, dup2(err_fd, STDERR_FILENO));
131
132 ASSERT_TRUE(WriteMicrodump(child, &context, sizeof(context), mappings,
133 skip_dump_if_principal_mapping_not_referenced,
134 address_within_principal_mapping, sanitize_stack,
135 microdump_extra_info));
136
137 // Revert stderr back to the console.
138 dup2(save_err, STDERR_FILENO);
139 close(save_err);
140
141 // Read back the stderr file and check for the microdump marker.
142 fsync(err_fd);
143 lseek(err_fd, 0, SEEK_SET);
144
145 microdump->clear();
146 char buf[1024];
147
148 while (true) {
149 int bytes_read = IGNORE_EINTR(read(err_fd, buf, 1024));
150 if (bytes_read <= 0) break;
151 microdump->append(buf, buf + bytes_read);
152 }
153 close(err_fd);
154 close(fds[1]);
155 }
156
ExtractMicrodumpStackContents(const string & microdump_content,string * result)157 void ExtractMicrodumpStackContents(const string& microdump_content,
158 string* result) {
159 std::istringstream iss(microdump_content);
160 result->clear();
161 for (string line; std::getline(iss, line);) {
162 if (line.find("S ") == 0) {
163 std::istringstream stack_data(line);
164 std::string key;
165 std::string addr;
166 std::string data;
167 stack_data >> key >> addr >> data;
168 EXPECT_TRUE((data.size() & 1u) == 0u);
169 result->reserve(result->size() + data.size() / 2);
170 for (size_t i = 0; i < data.size(); i += 2) {
171 std::string byte = data.substr(i, 2);
172 result->push_back(static_cast<char>(strtoul(byte.c_str(), NULL, 16)));
173 }
174 }
175 }
176 }
177
CheckMicrodumpContents(const string & microdump_content,const MicrodumpExtraInfo & expected_info)178 void CheckMicrodumpContents(const string& microdump_content,
179 const MicrodumpExtraInfo& expected_info) {
180 std::istringstream iss(microdump_content);
181 bool did_find_os_info = false;
182 bool did_find_product_info = false;
183 bool did_find_process_type = false;
184 bool did_find_crash_reason = false;
185 bool did_find_gpu_info = false;
186 for (string line; std::getline(iss, line);) {
187 if (line.find("O ") == 0) {
188 std::istringstream os_info_tokens(line);
189 string token;
190 os_info_tokens.ignore(2); // Ignore the "O " preamble.
191 // Check the OS descriptor char (L=Linux, A=Android).
192 os_info_tokens >> token;
193 ASSERT_TRUE(token == "L" || token == "A");
194
195 os_info_tokens >> token; // HW architecture.
196 os_info_tokens >> token; // Number of cpus.
197 for (size_t i = 0; i < token.size(); ++i)
198 ASSERT_TRUE(isxdigit(token[i]));
199 os_info_tokens >> token; // SW architecture.
200
201 // Check that the build fingerprint is in the right place.
202 os_info_tokens >> token;
203 ASSERT_FALSE(os_info_tokens.fail());
204 if (expected_info.build_fingerprint)
205 ASSERT_EQ(expected_info.build_fingerprint, token);
206 did_find_os_info = true;
207 } else if (line.find("P ") == 0) {
208 if (expected_info.process_type)
209 ASSERT_EQ(string("P ") + expected_info.process_type, line);
210 did_find_process_type = true;
211 } else if (line.find("R ") == 0) {
212 std::istringstream crash_reason_tokens(line);
213 string token;
214 unsigned crash_reason;
215 string crash_reason_str;
216 uintptr_t crash_address;
217 crash_reason_tokens.ignore(2); // Ignore the "R " preamble.
218 crash_reason_tokens >> std::hex >> crash_reason >> crash_reason_str >>
219 crash_address;
220 ASSERT_FALSE(crash_reason_tokens.fail());
221 ASSERT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED, crash_reason);
222 ASSERT_EQ("DUMP_REQUESTED", crash_reason_str);
223 ASSERT_EQ(kCrashAddress, crash_address);
224 did_find_crash_reason = true;
225 } else if (line.find("V ") == 0) {
226 if (expected_info.product_info)
227 ASSERT_EQ(string("V ") + expected_info.product_info, line);
228 did_find_product_info = true;
229 } else if (line.find("G ") == 0) {
230 if (expected_info.gpu_fingerprint)
231 ASSERT_EQ(string("G ") + expected_info.gpu_fingerprint, line);
232 did_find_gpu_info = true;
233 }
234 }
235 ASSERT_TRUE(did_find_os_info);
236 ASSERT_TRUE(did_find_product_info);
237 ASSERT_TRUE(did_find_process_type);
238 ASSERT_TRUE(did_find_crash_reason);
239 ASSERT_TRUE(did_find_gpu_info);
240 }
241
MicrodumpStackContains(const string & microdump_content,const string & expected_content)242 bool MicrodumpStackContains(const string& microdump_content,
243 const string& expected_content) {
244 string result;
245 ExtractMicrodumpStackContents(microdump_content, &result);
246 return result.find(kIdentifiableString) != string::npos;
247 }
248
CheckMicrodumpContents(const string & microdump_content,const string & expected_fingerprint,const string & expected_product_info,const string & expected_gpu_fingerprint)249 void CheckMicrodumpContents(const string& microdump_content,
250 const string& expected_fingerprint,
251 const string& expected_product_info,
252 const string& expected_gpu_fingerprint) {
253 CheckMicrodumpContents(
254 microdump_content,
255 MakeMicrodumpExtraInfo(expected_fingerprint.c_str(),
256 expected_product_info.c_str(),
257 expected_gpu_fingerprint.c_str()));
258 }
259
TEST(MicrodumpWriterTest,BasicWithMappings)260 TEST(MicrodumpWriterTest, BasicWithMappings) {
261 // Push some extra mapping to check the MappingList logic.
262 const uint32_t memory_size = sysconf(_SC_PAGESIZE);
263 const char* kMemoryName = "libfoo.so";
264 const uint8_t kModuleGUID[sizeof(MDGUID)] = {
265 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
266 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
267 };
268
269 MappingInfo info;
270 info.start_addr = memory_size;
271 info.size = memory_size;
272 info.offset = 42;
273 strcpy(info.name, kMemoryName);
274
275 MappingList mappings;
276 MappingEntry mapping;
277 mapping.first = info;
278 memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
279 mappings.push_back(mapping);
280
281 std::string buf;
282 CrashAndGetMicrodump(mappings, MicrodumpExtraInfo(), &buf);
283 ASSERT_TRUE(ContainsMicrodump(buf));
284
285 #ifdef __LP64__
286 ASSERT_NE(std::string::npos,
287 buf.find("M 0000000000001000 000000000000002A 0000000000001000 "
288 "33221100554477668899AABBCCDDEEFF0 libfoo.so"));
289 #else
290 ASSERT_NE(std::string::npos,
291 buf.find("M 00001000 0000002A 00001000 "
292 "33221100554477668899AABBCCDDEEFF0 libfoo.so"));
293 #endif
294
295 // In absence of a product info in the minidump, the writer should just write
296 // an unknown marker.
297 ASSERT_NE(std::string::npos, buf.find("V UNKNOWN:0.0.0.0"));
298 }
299
300 // Ensure that no output occurs if the interest region is set, but
301 // doesn't overlap anything on the stack.
TEST(MicrodumpWriterTest,NoOutputIfUninteresting)302 TEST(MicrodumpWriterTest, NoOutputIfUninteresting) {
303 const char kProductInfo[] = "MockProduct:42.0.2311.99";
304 const char kBuildFingerprint[] =
305 "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
306 const char kGPUFingerprint[] =
307 "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 [email protected] AU@ (GIT@Id3510ff6dc)";
308 const MicrodumpExtraInfo kMicrodumpExtraInfo(
309 MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
310
311 std::string buf;
312 MappingList no_mappings;
313
314 CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, true, 0);
315 ASSERT_FALSE(ContainsMicrodump(buf));
316 }
317
318 // Ensure that stack content does not contain an identifiable string if the
319 // stack is sanitized.
TEST(MicrodumpWriterTest,StringRemovedBySanitization)320 TEST(MicrodumpWriterTest, StringRemovedBySanitization) {
321 const char kProductInfo[] = "MockProduct:42.0.2311.99";
322 const char kBuildFingerprint[] =
323 "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
324 const char kGPUFingerprint[] =
325 "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 [email protected] AU@ (GIT@Id3510ff6dc)";
326
327 const MicrodumpExtraInfo kMicrodumpExtraInfo(
328 MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
329
330 std::string buf;
331 MappingList no_mappings;
332
333 CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, false, 0u, true);
334 ASSERT_TRUE(ContainsMicrodump(buf));
335 ASSERT_FALSE(MicrodumpStackContains(buf, kIdentifiableString));
336 }
337
338 // Ensure that stack content does contain an identifiable string if the
339 // stack is not sanitized.
TEST(MicrodumpWriterTest,StringPresentIfNotSanitized)340 TEST(MicrodumpWriterTest, StringPresentIfNotSanitized) {
341 const char kProductInfo[] = "MockProduct:42.0.2311.99";
342 const char kBuildFingerprint[] =
343 "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
344 const char kGPUFingerprint[] =
345 "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 [email protected] AU@ (GIT@Id3510ff6dc)";
346
347 const MicrodumpExtraInfo kMicrodumpExtraInfo(
348 MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
349
350 std::string buf;
351 MappingList no_mappings;
352
353 CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, false, 0u, false);
354 ASSERT_TRUE(ContainsMicrodump(buf));
355 ASSERT_TRUE(MicrodumpStackContains(buf, kIdentifiableString));
356 }
357
358 // Ensure that output occurs if the interest region is set, and
359 // does overlap something on the stack.
TEST(MicrodumpWriterTest,OutputIfInteresting)360 TEST(MicrodumpWriterTest, OutputIfInteresting) {
361 const char kProductInfo[] = "MockProduct:42.0.2311.99";
362 const char kBuildFingerprint[] =
363 "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
364 const char kGPUFingerprint[] =
365 "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 [email protected] AU@ (GIT@Id3510ff6dc)";
366
367 const MicrodumpExtraInfo kMicrodumpExtraInfo(
368 MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
369
370 std::string buf;
371 MappingList no_mappings;
372
373 CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, true,
374 reinterpret_cast<uintptr_t>(CrashAndGetMicrodump));
375 ASSERT_TRUE(ContainsMicrodump(buf));
376 }
377
378 // Ensure that the product info and build fingerprint metadata show up in the
379 // final microdump if present.
TEST(MicrodumpWriterTest,BuildFingerprintAndProductInfo)380 TEST(MicrodumpWriterTest, BuildFingerprintAndProductInfo) {
381 const char kProductInfo[] = "MockProduct:42.0.2311.99";
382 const char kBuildFingerprint[] =
383 "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
384 const char kGPUFingerprint[] =
385 "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 [email protected] AU@ (GIT@Id3510ff6dc)";
386 const MicrodumpExtraInfo kMicrodumpExtraInfo(
387 MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
388 std::string buf;
389 MappingList no_mappings;
390
391 CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf);
392 ASSERT_TRUE(ContainsMicrodump(buf));
393 CheckMicrodumpContents(buf, kMicrodumpExtraInfo);
394 }
395
TEST(MicrodumpWriterTest,NoProductInfo)396 TEST(MicrodumpWriterTest, NoProductInfo) {
397 const char kBuildFingerprint[] = "foobar";
398 const char kGPUFingerprint[] = "bazqux";
399 std::string buf;
400 MappingList no_mappings;
401
402 const MicrodumpExtraInfo kMicrodumpExtraInfoNoProductInfo(
403 MakeMicrodumpExtraInfo(kBuildFingerprint, NULL, kGPUFingerprint));
404
405 CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoProductInfo, &buf);
406 ASSERT_TRUE(ContainsMicrodump(buf));
407 CheckMicrodumpContents(buf, kBuildFingerprint, "UNKNOWN:0.0.0.0",
408 kGPUFingerprint);
409 }
410
TEST(MicrodumpWriterTest,NoGPUInfo)411 TEST(MicrodumpWriterTest, NoGPUInfo) {
412 const char kProductInfo[] = "bazqux";
413 const char kBuildFingerprint[] = "foobar";
414 std::string buf;
415 MappingList no_mappings;
416
417 const MicrodumpExtraInfo kMicrodumpExtraInfoNoGPUInfo(
418 MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, NULL));
419
420 CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoGPUInfo, &buf);
421 ASSERT_TRUE(ContainsMicrodump(buf));
422 CheckMicrodumpContents(buf, kBuildFingerprint, kProductInfo, "UNKNOWN");
423 }
424 } // namespace
425