/ src / client / linux / microdump_writer / microdump_writer_unittest.cc
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