xref: /aosp_15_r20/external/google-breakpad/src/client/linux/microdump_writer/microdump_writer_unittest.cc (revision 9712c20fc9bbfbac4935993a2ca0b3958c5adad2)
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