microdump_writer_unittest.cc
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 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 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 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 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 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 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 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 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. 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 V@104.0 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. 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 V@104.0 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. 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 V@104.0 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. 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 V@104.0 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. 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 V@104.0 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 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 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