From 30566abed8d95b752f31f67fde94c0ae0a1502d2 Mon Sep 17 00:00:00 2001 From: "benchan@chromium.org" Date: Thu, 19 Jan 2012 07:14:51 +0000 Subject: Implement core dump to minidump conversion. This patch is part of a bigger patch that helps merging the breakpad code with the modified version in Chromium OS. Specifically, this patch makes the following changes: 1. Turn the LinuxDumper class into a base class and move ptrace related code into a new derived class, LinuxPtraceDumper. 2. Add a LinuxCoreDumper class, which is derived from LinuxDumper, to extract information from a crashed process via a core dump file instead of ptrace. 3. Add a WriteMinidumpFromCore function to src/client/linux/minidump_writer/minidump_writer.h, which uses LinuxCoreDumper to extract information from a core dump file. 4. Add a core2md utility, which simply wraps WriteMinidumpFromCore, for converting a core dump to a minidump. BUG=455 TEST=Tested the following: 1. Build on 32-bit and 64-bit Linux with gcc 4.4.3 and gcc 4.6. 2. Build on Mac OS X 10.6.8 with gcc 4.2 and clang 3.0 (with latest gmock). 3. All unit tests pass. 4. Run Chromium OS tests to test core2md. Review URL: http://breakpad.appspot.com/343001 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@905 4c0a9323-5329-0410-9bdc-e9ce6186880e --- Makefile.am | 57 ++- Makefile.in | 224 ++++++++--- .../linux/minidump_writer/linux_core_dumper.cc | 234 +++++++++++ .../linux/minidump_writer/linux_core_dumper.h | 122 ++++++ .../minidump_writer/linux_core_dumper_unittest.cc | 117 ++++++ src/client/linux/minidump_writer/linux_dumper.cc | 275 +------------ src/client/linux/minidump_writer/linux_dumper.h | 47 ++- .../linux/minidump_writer/linux_dumper_unittest.cc | 428 -------------------- .../linux/minidump_writer/linux_ptrace_dumper.cc | 292 ++++++++++++++ .../linux/minidump_writer/linux_ptrace_dumper.h | 92 +++++ .../linux_ptrace_dumper_unittest.cc | 430 +++++++++++++++++++++ .../linux/minidump_writer/minidump_writer.cc | 60 ++- src/client/linux/minidump_writer/minidump_writer.h | 6 + src/common/linux/elf_core_dump_unittest.cc | 2 +- src/common/linux/tests/crash_generator.cc | 6 +- src/common/linux/tests/crash_generator.h | 2 +- src/tools/linux/core2md/core2md.cc | 57 +++ 17 files changed, 1649 insertions(+), 802 deletions(-) create mode 100644 src/client/linux/minidump_writer/linux_core_dumper.cc create mode 100644 src/client/linux/minidump_writer/linux_core_dumper.h create mode 100644 src/client/linux/minidump_writer/linux_core_dumper_unittest.cc delete mode 100644 src/client/linux/minidump_writer/linux_dumper_unittest.cc create mode 100644 src/client/linux/minidump_writer/linux_ptrace_dumper.cc create mode 100644 src/client/linux/minidump_writer/linux_ptrace_dumper.h create mode 100644 src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc create mode 100644 src/tools/linux/core2md/core2md.cc diff --git a/Makefile.am b/Makefile.am index d4540f15..934b72d2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -66,6 +66,8 @@ src_client_linux_libbreakpad_client_a_SOURCES = \ src/client/linux/crash_generation/crash_generation_client.cc \ src/client/linux/handler/exception_handler.cc \ src/client/linux/minidump_writer/linux_dumper.cc \ + src/client/linux/minidump_writer/linux_core_dumper.cc \ + src/client/linux/minidump_writer/linux_ptrace_dumper.cc \ src/client/linux/minidump_writer/minidump_writer.cc \ src/client/minidump_file_writer.cc \ src/common/convert_UTF.c \ @@ -214,6 +216,7 @@ bin_PROGRAMS += \ if !DISABLE_TOOLS bin_PROGRAMS += \ + src/tools/linux/core2md/core2md \ src/tools/linux/dump_syms/dump_syms \ src/tools/linux/md2core/minidump-2-core \ src/tools/linux/symupload/minidump_upload \ @@ -289,10 +292,13 @@ src_client_linux_linux_client_unittest_SOURCES = \ src/client/linux/handler/exception_handler_unittest.cc \ src/client/linux/minidump_writer/directory_reader_unittest.cc \ src/client/linux/minidump_writer/line_reader_unittest.cc \ - src/client/linux/minidump_writer/linux_dumper_unittest.cc \ + src/client/linux/minidump_writer/linux_core_dumper_unittest.cc \ + src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc \ src/client/linux/minidump_writer/minidump_writer_unittest.cc \ src/common/linux/linux_libc_support_unittest.cc \ + src/common/linux/tests/crash_generator.cc \ src/common/memory_unittest.cc \ + src/common/tests/file_utils.cc \ src/testing/gtest/src/gtest-all.cc \ src/testing/gtest/src/gtest_main.cc \ src/testing/src/gmock-all.cc \ @@ -311,6 +317,8 @@ src_client_linux_linux_client_unittest_LDADD = \ src/client/linux/handler/exception_handler.o \ src/client/linux/crash_generation/crash_generation_client.o \ src/client/linux/minidump_writer/linux_dumper.o \ + src/client/linux/minidump_writer/linux_core_dumper.o \ + src/client/linux/minidump_writer/linux_ptrace_dumper.o \ src/client/linux/minidump_writer/minidump_writer.o \ src/client/minidump_file_writer.o \ src/common/convert_UTF.o \ @@ -320,11 +328,20 @@ src_client_linux_linux_client_unittest_LDADD = \ src/common/linux/guid_creator.o \ src/common/linux/memory_mapped_file.o \ src/common/linux/safe_readlink.o \ - src/common/string_conversion.o + src/common/string_conversion.o \ + $(PTHREAD_CFLAGS) $(PTHREAD_LIBS) -src_client_linux_linux_client_unittest_DEPENDENCIES = src/client/linux/linux_dumper_unittest_helper src/client/linux/libbreakpad_client.a src/libbreakpad.a +src_client_linux_linux_client_unittest_DEPENDENCIES = \ + src/client/linux/linux_dumper_unittest_helper \ + src/client/linux/libbreakpad_client.a \ + src/libbreakpad.a if !DISABLE_TOOLS +src_tools_linux_core2md_core2md_SOURCES = \ + src/tools/linux/core2md/core2md.cc +src_tools_linux_core2md_core2md_LDADD = \ + src/client/linux/libbreakpad_client.a + src_tools_linux_dump_syms_dump_syms_SOURCES = \ src/common/dwarf_cfi_to_module.cc \ src/common/dwarf_cu_to_module.cc \ @@ -534,25 +551,25 @@ src_processor_disassembler_x86_unittest_LDADD = \ src/third_party/libdisasm/libdisasm.a src_processor_fast_source_line_resolver_unittest_SOURCES = \ - src/processor/fast_source_line_resolver_unittest.cc \ - src/testing/gtest/src/gtest-all.cc \ - src/testing/src/gmock-all.cc + src/processor/fast_source_line_resolver_unittest.cc \ + src/testing/gtest/src/gtest-all.cc \ + src/testing/src/gmock-all.cc src_processor_fast_source_line_resolver_unittest_CPPFLAGS = \ - -I$(top_srcdir)/src \ - -I$(top_srcdir)/src/testing/include \ - -I$(top_srcdir)/src/testing/gtest/include \ - -I$(top_srcdir)/src/testing/gtest \ - -I$(top_srcdir)/src/testing + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/testing/include \ + -I$(top_srcdir)/src/testing/gtest/include \ + -I$(top_srcdir)/src/testing/gtest \ + -I$(top_srcdir)/src/testing src_processor_fast_source_line_resolver_unittest_LDADD = \ - src/processor/fast_source_line_resolver.o \ - src/processor/basic_source_line_resolver.o \ - src/processor/cfi_frame_info.o \ - src/processor/module_comparer.o \ - src/processor/module_serializer.o \ - src/processor/pathname_stripper.o \ - src/processor/logging.o \ - src/processor/source_line_resolver_base.o \ - src/processor/tokenize.o + src/processor/fast_source_line_resolver.o \ + src/processor/basic_source_line_resolver.o \ + src/processor/cfi_frame_info.o \ + src/processor/module_comparer.o \ + src/processor/module_serializer.o \ + src/processor/pathname_stripper.o \ + src/processor/logging.o \ + src/processor/source_line_resolver_base.o \ + src/processor/tokenize.o src_processor_map_serializers_unittest_SOURCES = \ src/processor/map_serializers_unittest.cc \ diff --git a/Makefile.in b/Makefile.in index e42e3d1d..651ae6c8 100644 --- a/Makefile.in +++ b/Makefile.in @@ -80,6 +80,7 @@ check_PROGRAMS = $(am__EXEEXT_4) $(am__EXEEXT_5) $(am__EXEEXT_6) \ @LINUX_HOST_TRUE@ src/client/linux/linux_dumper_unittest_helper @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@am__append_6 = \ +@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/tools/linux/core2md/core2md \ @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/tools/linux/dump_syms/dump_syms \ @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/tools/linux/md2core/minidump-2-core \ @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/tools/linux/symupload/minidump_upload \ @@ -171,6 +172,8 @@ am__src_client_linux_libbreakpad_client_a_SOURCES_DIST = \ src/client/linux/crash_generation/crash_generation_client.cc \ src/client/linux/handler/exception_handler.cc \ src/client/linux/minidump_writer/linux_dumper.cc \ + src/client/linux/minidump_writer/linux_core_dumper.cc \ + src/client/linux/minidump_writer/linux_ptrace_dumper.cc \ src/client/linux/minidump_writer/minidump_writer.cc \ src/client/minidump_file_writer.cc src/common/convert_UTF.c \ src/common/md5.cc src/common/string_conversion.cc \ @@ -182,6 +185,8 @@ am__dirstamp = $(am__leading_dot)dirstamp @LINUX_HOST_TRUE@am_src_client_linux_libbreakpad_client_a_OBJECTS = src/client/linux/crash_generation/crash_generation_client.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/client/linux/handler/exception_handler.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_dumper.$(OBJEXT) \ +@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_core_dumper.$(OBJEXT) \ +@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_ptrace_dumper.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/minidump_writer.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/client/minidump_file_writer.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/common/convert_UTF.$(OBJEXT) \ @@ -352,7 +357,8 @@ src_third_party_libdisasm_libdisasm_a_OBJECTS = \ @DISABLE_PROCESSOR_FALSE@ src/processor/minidump_dump$(EXEEXT) \ @DISABLE_PROCESSOR_FALSE@ src/processor/minidump_stackwalk$(EXEEXT) @LINUX_HOST_TRUE@am__EXEEXT_2 = src/client/linux/linux_dumper_unittest_helper$(EXEEXT) -@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@am__EXEEXT_3 = src/tools/linux/dump_syms/dump_syms$(EXEEXT) \ +@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@am__EXEEXT_3 = src/tools/linux/core2md/core2md$(EXEEXT) \ +@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/tools/linux/dump_syms/dump_syms$(EXEEXT) \ @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/tools/linux/md2core/minidump-2-core$(EXEEXT) \ @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/tools/linux/symupload/minidump_upload$(EXEEXT) \ @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/tools/linux/symupload/sym_upload$(EXEEXT) @@ -388,10 +394,12 @@ am__src_client_linux_linux_client_unittest_SOURCES_DIST = \ src/client/linux/handler/exception_handler_unittest.cc \ src/client/linux/minidump_writer/directory_reader_unittest.cc \ src/client/linux/minidump_writer/line_reader_unittest.cc \ - src/client/linux/minidump_writer/linux_dumper_unittest.cc \ + src/client/linux/minidump_writer/linux_core_dumper_unittest.cc \ + src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc \ src/client/linux/minidump_writer/minidump_writer_unittest.cc \ src/common/linux/linux_libc_support_unittest.cc \ - src/common/memory_unittest.cc \ + src/common/linux/tests/crash_generator.cc \ + src/common/memory_unittest.cc src/common/tests/file_utils.cc \ src/testing/gtest/src/gtest-all.cc \ src/testing/gtest/src/gtest_main.cc \ src/testing/src/gmock-all.cc \ @@ -400,10 +408,13 @@ am__src_client_linux_linux_client_unittest_SOURCES_DIST = \ @LINUX_HOST_TRUE@am_src_client_linux_linux_client_unittest_OBJECTS = src/client/linux/handler/src_client_linux_linux_client_unittest-exception_handler_unittest.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-directory_reader_unittest.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-line_reader_unittest.$(OBJEXT) \ -@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.$(OBJEXT) \ +@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.$(OBJEXT) \ +@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-minidump_writer_unittest.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/common/linux/src_client_linux_linux_client_unittest-linux_libc_support_unittest.$(OBJEXT) \ +@LINUX_HOST_TRUE@ src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/common/src_client_linux_linux_client_unittest-memory_unittest.$(OBJEXT) \ +@LINUX_HOST_TRUE@ src/common/tests/src_client_linux_linux_client_unittest-file_utils.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/testing/gtest/src/src_client_linux_linux_client_unittest-gtest-all.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/testing/gtest/src/src_client_linux_linux_client_unittest-gtest_main.$(OBJEXT) \ @LINUX_HOST_TRUE@ src/testing/src/src_client_linux_linux_client_unittest-gmock-all.$(OBJEXT) \ @@ -413,6 +424,7 @@ am__src_client_linux_linux_client_unittest_SOURCES_DIST = \ @LINUX_HOST_TRUE@ src/processor/src_client_linux_linux_client_unittest-pathname_stripper.$(OBJEXT) src_client_linux_linux_client_unittest_OBJECTS = \ $(am_src_client_linux_linux_client_unittest_OBJECTS) +am__DEPENDENCIES_1 = am__src_client_linux_linux_dumper_unittest_helper_SOURCES_DIST = src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc @LINUX_HOST_TRUE@am_src_client_linux_linux_dumper_unittest_helper_OBJECTS = src/client/linux/minidump_writer/src_client_linux_linux_dumper_unittest_helper-linux_dumper_unittest_helper.$(OBJEXT) src_client_linux_linux_dumper_unittest_helper_OBJECTS = \ @@ -506,7 +518,6 @@ am__src_common_dumper_unittest_SOURCES_DIST = \ @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/testing/src/src_common_dumper_unittest-gmock-all.$(OBJEXT) src_common_dumper_unittest_OBJECTS = \ $(am_src_common_dumper_unittest_OBJECTS) -am__DEPENDENCIES_1 = @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@src_common_dumper_unittest_DEPENDENCIES = \ @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ $(am__DEPENDENCIES_1) \ @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ $(am__DEPENDENCIES_1) @@ -900,6 +911,12 @@ am__src_processor_synth_minidump_unittest_SOURCES_DIST = \ src_processor_synth_minidump_unittest_OBJECTS = \ $(am_src_processor_synth_minidump_unittest_OBJECTS) src_processor_synth_minidump_unittest_LDADD = $(LDADD) +am__src_tools_linux_core2md_core2md_SOURCES_DIST = \ + src/tools/linux/core2md/core2md.cc +@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@am_src_tools_linux_core2md_core2md_OBJECTS = src/tools/linux/core2md/core2md.$(OBJEXT) +src_tools_linux_core2md_core2md_OBJECTS = \ + $(am_src_tools_linux_core2md_core2md_OBJECTS) +@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@src_tools_linux_core2md_core2md_DEPENDENCIES = src/client/linux/libbreakpad_client.a am__src_tools_linux_dump_syms_dump_syms_SOURCES_DIST = \ src/common/dwarf_cfi_to_module.cc \ src/common/dwarf_cu_to_module.cc \ @@ -1021,6 +1038,7 @@ SOURCES = $(src_client_linux_libbreakpad_client_a_SOURCES) \ $(src_processor_static_map_unittest_SOURCES) \ $(src_processor_static_range_map_unittest_SOURCES) \ $(src_processor_synth_minidump_unittest_SOURCES) \ + $(src_tools_linux_core2md_core2md_SOURCES) \ $(src_tools_linux_dump_syms_dump_syms_SOURCES) \ $(src_tools_linux_md2core_minidump_2_core_SOURCES) \ $(src_tools_linux_md2core_minidump_2_core_unittest_SOURCES) \ @@ -1059,6 +1077,7 @@ DIST_SOURCES = \ $(am__src_processor_static_map_unittest_SOURCES_DIST) \ $(am__src_processor_static_range_map_unittest_SOURCES_DIST) \ $(am__src_processor_synth_minidump_unittest_SOURCES_DIST) \ + $(am__src_tools_linux_core2md_core2md_SOURCES_DIST) \ $(am__src_tools_linux_dump_syms_dump_syms_SOURCES_DIST) \ $(am__src_tools_linux_md2core_minidump_2_core_SOURCES_DIST) \ $(am__src_tools_linux_md2core_minidump_2_core_unittest_SOURCES_DIST) \ @@ -1203,6 +1222,8 @@ lib_LIBRARIES = $(am__append_1) $(am__append_3) @LINUX_HOST_TRUE@ src/client/linux/crash_generation/crash_generation_client.cc \ @LINUX_HOST_TRUE@ src/client/linux/handler/exception_handler.cc \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_dumper.cc \ +@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_core_dumper.cc \ +@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_ptrace_dumper.cc \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/minidump_writer.cc \ @LINUX_HOST_TRUE@ src/client/minidump_file_writer.cc \ @LINUX_HOST_TRUE@ src/common/convert_UTF.c \ @@ -1353,10 +1374,13 @@ TESTS_ENVIRONMENT = @LINUX_HOST_TRUE@ src/client/linux/handler/exception_handler_unittest.cc \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/directory_reader_unittest.cc \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/line_reader_unittest.cc \ -@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_dumper_unittest.cc \ +@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_core_dumper_unittest.cc \ +@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/minidump_writer_unittest.cc \ @LINUX_HOST_TRUE@ src/common/linux/linux_libc_support_unittest.cc \ +@LINUX_HOST_TRUE@ src/common/linux/tests/crash_generator.cc \ @LINUX_HOST_TRUE@ src/common/memory_unittest.cc \ +@LINUX_HOST_TRUE@ src/common/tests/file_utils.cc \ @LINUX_HOST_TRUE@ src/testing/gtest/src/gtest-all.cc \ @LINUX_HOST_TRUE@ src/testing/gtest/src/gtest_main.cc \ @LINUX_HOST_TRUE@ src/testing/src/gmock-all.cc \ @@ -1376,6 +1400,8 @@ TESTS_ENVIRONMENT = @LINUX_HOST_TRUE@ src/client/linux/handler/exception_handler.o \ @LINUX_HOST_TRUE@ src/client/linux/crash_generation/crash_generation_client.o \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_dumper.o \ +@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_core_dumper.o \ +@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_ptrace_dumper.o \ @LINUX_HOST_TRUE@ src/client/linux/minidump_writer/minidump_writer.o \ @LINUX_HOST_TRUE@ src/client/minidump_file_writer.o \ @LINUX_HOST_TRUE@ src/common/convert_UTF.o \ @@ -1385,9 +1411,20 @@ TESTS_ENVIRONMENT = @LINUX_HOST_TRUE@ src/common/linux/guid_creator.o \ @LINUX_HOST_TRUE@ src/common/linux/memory_mapped_file.o \ @LINUX_HOST_TRUE@ src/common/linux/safe_readlink.o \ -@LINUX_HOST_TRUE@ src/common/string_conversion.o +@LINUX_HOST_TRUE@ src/common/string_conversion.o \ +@LINUX_HOST_TRUE@ $(PTHREAD_CFLAGS) $(PTHREAD_LIBS) + +@LINUX_HOST_TRUE@src_client_linux_linux_client_unittest_DEPENDENCIES = \ +@LINUX_HOST_TRUE@ src/client/linux/linux_dumper_unittest_helper \ +@LINUX_HOST_TRUE@ src/client/linux/libbreakpad_client.a \ +@LINUX_HOST_TRUE@ src/libbreakpad.a + +@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@src_tools_linux_core2md_core2md_SOURCES = \ +@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/tools/linux/core2md/core2md.cc + +@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@src_tools_linux_core2md_core2md_LDADD = \ +@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/client/linux/libbreakpad_client.a -@LINUX_HOST_TRUE@src_client_linux_linux_client_unittest_DEPENDENCIES = src/client/linux/linux_dumper_unittest_helper src/client/linux/libbreakpad_client.a src/libbreakpad.a @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@src_tools_linux_dump_syms_dump_syms_SOURCES = \ @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/dwarf_cfi_to_module.cc \ @DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/dwarf_cu_to_module.cc \ @@ -1607,27 +1644,27 @@ TESTS_ENVIRONMENT = @DISABLE_PROCESSOR_FALSE@ src/third_party/libdisasm/libdisasm.a @DISABLE_PROCESSOR_FALSE@src_processor_fast_source_line_resolver_unittest_SOURCES = \ -@DISABLE_PROCESSOR_FALSE@ src/processor/fast_source_line_resolver_unittest.cc \ -@DISABLE_PROCESSOR_FALSE@ src/testing/gtest/src/gtest-all.cc \ -@DISABLE_PROCESSOR_FALSE@ src/testing/src/gmock-all.cc +@DISABLE_PROCESSOR_FALSE@ src/processor/fast_source_line_resolver_unittest.cc \ +@DISABLE_PROCESSOR_FALSE@ src/testing/gtest/src/gtest-all.cc \ +@DISABLE_PROCESSOR_FALSE@ src/testing/src/gmock-all.cc @DISABLE_PROCESSOR_FALSE@src_processor_fast_source_line_resolver_unittest_CPPFLAGS = \ -@DISABLE_PROCESSOR_FALSE@ -I$(top_srcdir)/src \ -@DISABLE_PROCESSOR_FALSE@ -I$(top_srcdir)/src/testing/include \ -@DISABLE_PROCESSOR_FALSE@ -I$(top_srcdir)/src/testing/gtest/include \ -@DISABLE_PROCESSOR_FALSE@ -I$(top_srcdir)/src/testing/gtest \ -@DISABLE_PROCESSOR_FALSE@ -I$(top_srcdir)/src/testing +@DISABLE_PROCESSOR_FALSE@ -I$(top_srcdir)/src \ +@DISABLE_PROCESSOR_FALSE@ -I$(top_srcdir)/src/testing/include \ +@DISABLE_PROCESSOR_FALSE@ -I$(top_srcdir)/src/testing/gtest/include \ +@DISABLE_PROCESSOR_FALSE@ -I$(top_srcdir)/src/testing/gtest \ +@DISABLE_PROCESSOR_FALSE@ -I$(top_srcdir)/src/testing @DISABLE_PROCESSOR_FALSE@src_processor_fast_source_line_resolver_unittest_LDADD = \ -@DISABLE_PROCESSOR_FALSE@ src/processor/fast_source_line_resolver.o \ -@DISABLE_PROCESSOR_FALSE@ src/processor/basic_source_line_resolver.o \ -@DISABLE_PROCESSOR_FALSE@ src/processor/cfi_frame_info.o \ -@DISABLE_PROCESSOR_FALSE@ src/processor/module_comparer.o \ -@DISABLE_PROCESSOR_FALSE@ src/processor/module_serializer.o \ -@DISABLE_PROCESSOR_FALSE@ src/processor/pathname_stripper.o \ -@DISABLE_PROCESSOR_FALSE@ src/processor/logging.o \ -@DISABLE_PROCESSOR_FALSE@ src/processor/source_line_resolver_base.o \ -@DISABLE_PROCESSOR_FALSE@ src/processor/tokenize.o +@DISABLE_PROCESSOR_FALSE@ src/processor/fast_source_line_resolver.o \ +@DISABLE_PROCESSOR_FALSE@ src/processor/basic_source_line_resolver.o \ +@DISABLE_PROCESSOR_FALSE@ src/processor/cfi_frame_info.o \ +@DISABLE_PROCESSOR_FALSE@ src/processor/module_comparer.o \ +@DISABLE_PROCESSOR_FALSE@ src/processor/module_serializer.o \ +@DISABLE_PROCESSOR_FALSE@ src/processor/pathname_stripper.o \ +@DISABLE_PROCESSOR_FALSE@ src/processor/logging.o \ +@DISABLE_PROCESSOR_FALSE@ src/processor/source_line_resolver_base.o \ +@DISABLE_PROCESSOR_FALSE@ src/processor/tokenize.o @DISABLE_PROCESSOR_FALSE@src_processor_map_serializers_unittest_SOURCES = \ @DISABLE_PROCESSOR_FALSE@ src/processor/map_serializers_unittest.cc \ @@ -2178,6 +2215,12 @@ src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp): src/client/linux/minidump_writer/linux_dumper.$(OBJEXT): \ src/client/linux/minidump_writer/$(am__dirstamp) \ src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp) +src/client/linux/minidump_writer/linux_core_dumper.$(OBJEXT): \ + src/client/linux/minidump_writer/$(am__dirstamp) \ + src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp) +src/client/linux/minidump_writer/linux_ptrace_dumper.$(OBJEXT): \ + src/client/linux/minidump_writer/$(am__dirstamp) \ + src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp) src/client/linux/minidump_writer/minidump_writer.$(OBJEXT): \ src/client/linux/minidump_writer/$(am__dirstamp) \ src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp) @@ -2411,7 +2454,10 @@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-director src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-line_reader_unittest.$(OBJEXT): \ src/client/linux/minidump_writer/$(am__dirstamp) \ src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp) -src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.$(OBJEXT): \ +src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.$(OBJEXT): \ + src/client/linux/minidump_writer/$(am__dirstamp) \ + src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp) +src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.$(OBJEXT): \ src/client/linux/minidump_writer/$(am__dirstamp) \ src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp) src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-minidump_writer_unittest.$(OBJEXT): \ @@ -2420,9 +2466,27 @@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-minidump src/common/linux/src_client_linux_linux_client_unittest-linux_libc_support_unittest.$(OBJEXT): \ src/common/linux/$(am__dirstamp) \ src/common/linux/$(DEPDIR)/$(am__dirstamp) +src/common/linux/tests/$(am__dirstamp): + @$(MKDIR_P) src/common/linux/tests + @: > src/common/linux/tests/$(am__dirstamp) +src/common/linux/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/common/linux/tests/$(DEPDIR) + @: > src/common/linux/tests/$(DEPDIR)/$(am__dirstamp) +src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.$(OBJEXT): \ + src/common/linux/tests/$(am__dirstamp) \ + src/common/linux/tests/$(DEPDIR)/$(am__dirstamp) src/common/src_client_linux_linux_client_unittest-memory_unittest.$(OBJEXT): \ src/common/$(am__dirstamp) \ src/common/$(DEPDIR)/$(am__dirstamp) +src/common/tests/$(am__dirstamp): + @$(MKDIR_P) src/common/tests + @: > src/common/tests/$(am__dirstamp) +src/common/tests/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/common/tests/$(DEPDIR) + @: > src/common/tests/$(DEPDIR)/$(am__dirstamp) +src/common/tests/src_client_linux_linux_client_unittest-file_utils.$(OBJEXT): \ + src/common/tests/$(am__dirstamp) \ + src/common/tests/$(DEPDIR)/$(am__dirstamp) src/testing/gtest/src/$(am__dirstamp): @$(MKDIR_P) src/testing/gtest/src @: > src/testing/gtest/src/$(am__dirstamp) @@ -2582,21 +2646,9 @@ src/common/linux/src_common_dumper_unittest-safe_readlink.$(OBJEXT): \ src/common/linux/src_common_dumper_unittest-safe_readlink_unittest.$(OBJEXT): \ src/common/linux/$(am__dirstamp) \ src/common/linux/$(DEPDIR)/$(am__dirstamp) -src/common/linux/tests/$(am__dirstamp): - @$(MKDIR_P) src/common/linux/tests - @: > src/common/linux/tests/$(am__dirstamp) -src/common/linux/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/common/linux/tests/$(DEPDIR) - @: > src/common/linux/tests/$(DEPDIR)/$(am__dirstamp) src/common/linux/tests/src_common_dumper_unittest-crash_generator.$(OBJEXT): \ src/common/linux/tests/$(am__dirstamp) \ src/common/linux/tests/$(DEPDIR)/$(am__dirstamp) -src/common/tests/$(am__dirstamp): - @$(MKDIR_P) src/common/tests - @: > src/common/tests/$(am__dirstamp) -src/common/tests/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) src/common/tests/$(DEPDIR) - @: > src/common/tests/$(DEPDIR)/$(am__dirstamp) src/common/tests/src_common_dumper_unittest-file_utils.$(OBJEXT): \ src/common/tests/$(am__dirstamp) \ src/common/tests/$(DEPDIR)/$(am__dirstamp) @@ -2926,6 +2978,18 @@ src/processor/src_processor_synth_minidump_unittest-synth_minidump.$(OBJEXT): \ src/processor/synth_minidump_unittest$(EXEEXT): $(src_processor_synth_minidump_unittest_OBJECTS) $(src_processor_synth_minidump_unittest_DEPENDENCIES) src/processor/$(am__dirstamp) @rm -f src/processor/synth_minidump_unittest$(EXEEXT) $(CXXLINK) $(src_processor_synth_minidump_unittest_OBJECTS) $(src_processor_synth_minidump_unittest_LDADD) $(LIBS) +src/tools/linux/core2md/$(am__dirstamp): + @$(MKDIR_P) src/tools/linux/core2md + @: > src/tools/linux/core2md/$(am__dirstamp) +src/tools/linux/core2md/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/tools/linux/core2md/$(DEPDIR) + @: > src/tools/linux/core2md/$(DEPDIR)/$(am__dirstamp) +src/tools/linux/core2md/core2md.$(OBJEXT): \ + src/tools/linux/core2md/$(am__dirstamp) \ + src/tools/linux/core2md/$(DEPDIR)/$(am__dirstamp) +src/tools/linux/core2md/core2md$(EXEEXT): $(src_tools_linux_core2md_core2md_OBJECTS) $(src_tools_linux_core2md_core2md_DEPENDENCIES) src/tools/linux/core2md/$(am__dirstamp) + @rm -f src/tools/linux/core2md/core2md$(EXEEXT) + $(CXXLINK) $(src_tools_linux_core2md_core2md_OBJECTS) $(src_tools_linux_core2md_core2md_LDADD) $(LIBS) src/common/dwarf_cfi_to_module.$(OBJEXT): src/common/$(am__dirstamp) \ src/common/$(DEPDIR)/$(am__dirstamp) src/common/dwarf_cu_to_module.$(OBJEXT): src/common/$(am__dirstamp) \ @@ -3021,11 +3085,14 @@ mostlyclean-compile: -rm -f src/client/linux/crash_generation/crash_generation_client.$(OBJEXT) -rm -f src/client/linux/handler/exception_handler.$(OBJEXT) -rm -f src/client/linux/handler/src_client_linux_linux_client_unittest-exception_handler_unittest.$(OBJEXT) + -rm -f src/client/linux/minidump_writer/linux_core_dumper.$(OBJEXT) -rm -f src/client/linux/minidump_writer/linux_dumper.$(OBJEXT) + -rm -f src/client/linux/minidump_writer/linux_ptrace_dumper.$(OBJEXT) -rm -f src/client/linux/minidump_writer/minidump_writer.$(OBJEXT) -rm -f src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-directory_reader_unittest.$(OBJEXT) -rm -f src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-line_reader_unittest.$(OBJEXT) - -rm -f src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.$(OBJEXT) + -rm -f src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.$(OBJEXT) + -rm -f src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.$(OBJEXT) -rm -f src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-minidump_writer_unittest.$(OBJEXT) -rm -f src/client/linux/minidump_writer/src_client_linux_linux_dumper_unittest_helper-linux_dumper_unittest_helper.$(OBJEXT) -rm -f src/client/minidump_file_writer.$(OBJEXT) @@ -3067,6 +3134,7 @@ mostlyclean-compile: -rm -f src/common/linux/src_common_dumper_unittest-safe_readlink_unittest.$(OBJEXT) -rm -f src/common/linux/src_common_dumper_unittest-synth_elf.$(OBJEXT) -rm -f src/common/linux/src_common_dumper_unittest-synth_elf_unittest.$(OBJEXT) + -rm -f src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.$(OBJEXT) -rm -f src/common/linux/tests/src_common_dumper_unittest-crash_generator.$(OBJEXT) -rm -f src/common/md5.$(OBJEXT) -rm -f src/common/module.$(OBJEXT) @@ -3097,6 +3165,7 @@ mostlyclean-compile: -rm -f src/common/stabs_reader.$(OBJEXT) -rm -f src/common/stabs_to_module.$(OBJEXT) -rm -f src/common/string_conversion.$(OBJEXT) + -rm -f src/common/tests/src_client_linux_linux_client_unittest-file_utils.$(OBJEXT) -rm -f src/common/tests/src_common_dumper_unittest-file_utils.$(OBJEXT) -rm -f src/processor/address_map_unittest.$(OBJEXT) -rm -f src/processor/basic_code_modules.$(OBJEXT) @@ -3222,6 +3291,7 @@ mostlyclean-compile: -rm -f src/third_party/libdisasm/x86_insn.$(OBJEXT) -rm -f src/third_party/libdisasm/x86_misc.$(OBJEXT) -rm -f src/third_party/libdisasm/x86_operand_list.$(OBJEXT) + -rm -f src/tools/linux/core2md/core2md.$(OBJEXT) -rm -f src/tools/linux/dump_syms/dump_syms.$(OBJEXT) -rm -f src/tools/linux/md2core/minidump-2-core.$(OBJEXT) -rm -f src/tools/linux/md2core/src_tools_linux_md2core_minidump_2_core_unittest-minidump_memory_range_unittest.$(OBJEXT) @@ -3235,11 +3305,14 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/crash_generation/$(DEPDIR)/crash_generation_client.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/handler/$(DEPDIR)/exception_handler.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/handler/$(DEPDIR)/src_client_linux_linux_client_unittest-exception_handler_unittest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_core_dumper.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_dumper.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_ptrace_dumper.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/minidump_writer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-directory_reader_unittest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-line_reader_unittest.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_dumper_unittest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-minidump_writer_unittest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_dumper_unittest_helper-linux_dumper_unittest_helper.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/common/$(DEPDIR)/convert_UTF.Po@am__quote@ @@ -3309,7 +3382,9 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/src_common_dumper_unittest-safe_readlink_unittest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf_unittest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-crash_generator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/tests/$(DEPDIR)/src_common_dumper_unittest-crash_generator.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/common/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-file_utils.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/common/tests/$(DEPDIR)/src_common_dumper_unittest-file_utils.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/address_map_unittest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/basic_code_modules.Po@am__quote@ @@ -3435,6 +3510,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@src/third_party/libdisasm/$(DEPDIR)/x86_insn.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/third_party/libdisasm/$(DEPDIR)/x86_misc.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/third_party/libdisasm/$(DEPDIR)/x86_operand_list.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/tools/linux/core2md/$(DEPDIR)/core2md.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/tools/linux/dump_syms/$(DEPDIR)/dump_syms.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/tools/linux/md2core/$(DEPDIR)/minidump-2-core.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/tools/linux/md2core/$(DEPDIR)/src_tools_linux_md2core_minidump_2_core_unittest-minidump_memory_range_unittest.Po@am__quote@ @@ -3515,19 +3591,33 @@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-line_rea @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-line_reader_unittest.obj `if test -f 'src/client/linux/minidump_writer/line_reader_unittest.cc'; then $(CYGPATH_W) 'src/client/linux/minidump_writer/line_reader_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/client/linux/minidump_writer/line_reader_unittest.cc'; fi` -src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.o: src/client/linux/minidump_writer/linux_dumper_unittest.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.o -MD -MP -MF src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_dumper_unittest.Tpo -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.o `test -f 'src/client/linux/minidump_writer/linux_dumper_unittest.cc' || echo '$(srcdir)/'`src/client/linux/minidump_writer/linux_dumper_unittest.cc -@am__fastdepCXX_TRUE@ $(am__mv) src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_dumper_unittest.Tpo src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_dumper_unittest.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/client/linux/minidump_writer/linux_dumper_unittest.cc' object='src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.o' libtool=no @AMDEPBACKSLASH@ +src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.o: src/client/linux/minidump_writer/linux_core_dumper_unittest.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.o -MD -MP -MF src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.Tpo -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.o `test -f 'src/client/linux/minidump_writer/linux_core_dumper_unittest.cc' || echo '$(srcdir)/'`src/client/linux/minidump_writer/linux_core_dumper_unittest.cc +@am__fastdepCXX_TRUE@ $(am__mv) src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.Tpo src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/client/linux/minidump_writer/linux_core_dumper_unittest.cc' object='src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.o `test -f 'src/client/linux/minidump_writer/linux_core_dumper_unittest.cc' || echo '$(srcdir)/'`src/client/linux/minidump_writer/linux_core_dumper_unittest.cc + +src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.obj: src/client/linux/minidump_writer/linux_core_dumper_unittest.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.obj -MD -MP -MF src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.Tpo -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.obj `if test -f 'src/client/linux/minidump_writer/linux_core_dumper_unittest.cc'; then $(CYGPATH_W) 'src/client/linux/minidump_writer/linux_core_dumper_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(am__mv) src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.Tpo src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/client/linux/minidump_writer/linux_core_dumper_unittest.cc' object='src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_core_dumper_unittest.obj `if test -f 'src/client/linux/minidump_writer/linux_core_dumper_unittest.cc'; then $(CYGPATH_W) 'src/client/linux/minidump_writer/linux_core_dumper_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc'; fi` + +src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.o: src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.o -MD -MP -MF src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.Tpo -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.o `test -f 'src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc' || echo '$(srcdir)/'`src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc +@am__fastdepCXX_TRUE@ $(am__mv) src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.Tpo src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc' object='src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.o `test -f 'src/client/linux/minidump_writer/linux_dumper_unittest.cc' || echo '$(srcdir)/'`src/client/linux/minidump_writer/linux_dumper_unittest.cc +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.o `test -f 'src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc' || echo '$(srcdir)/'`src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc -src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.obj: src/client/linux/minidump_writer/linux_dumper_unittest.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.obj -MD -MP -MF src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_dumper_unittest.Tpo -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.obj `if test -f 'src/client/linux/minidump_writer/linux_dumper_unittest.cc'; then $(CYGPATH_W) 'src/client/linux/minidump_writer/linux_dumper_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/client/linux/minidump_writer/linux_dumper_unittest.cc'; fi` -@am__fastdepCXX_TRUE@ $(am__mv) src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_dumper_unittest.Tpo src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_dumper_unittest.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/client/linux/minidump_writer/linux_dumper_unittest.cc' object='src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.obj' libtool=no @AMDEPBACKSLASH@ +src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.obj: src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.obj -MD -MP -MF src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.Tpo -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.obj `if test -f 'src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc'; then $(CYGPATH_W) 'src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(am__mv) src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.Tpo src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc' object='src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.obj `if test -f 'src/client/linux/minidump_writer/linux_dumper_unittest.cc'; then $(CYGPATH_W) 'src/client/linux/minidump_writer/linux_dumper_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/client/linux/minidump_writer/linux_dumper_unittest.cc'; fi` +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_ptrace_dumper_unittest.obj `if test -f 'src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc'; then $(CYGPATH_W) 'src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc'; fi` src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-minidump_writer_unittest.o: src/client/linux/minidump_writer/minidump_writer_unittest.cc @am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-minidump_writer_unittest.o -MD -MP -MF src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_client_unittest-minidump_writer_unittest.Tpo -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-minidump_writer_unittest.o `test -f 'src/client/linux/minidump_writer/minidump_writer_unittest.cc' || echo '$(srcdir)/'`src/client/linux/minidump_writer/minidump_writer_unittest.cc @@ -3557,6 +3647,20 @@ src/common/linux/src_client_linux_linux_client_unittest-linux_libc_support_unitt @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/linux/src_client_linux_linux_client_unittest-linux_libc_support_unittest.obj `if test -f 'src/common/linux/linux_libc_support_unittest.cc'; then $(CYGPATH_W) 'src/common/linux/linux_libc_support_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/linux/linux_libc_support_unittest.cc'; fi` +src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.o: src/common/linux/tests/crash_generator.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.o -MD -MP -MF src/common/linux/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-crash_generator.Tpo -c -o src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.o `test -f 'src/common/linux/tests/crash_generator.cc' || echo '$(srcdir)/'`src/common/linux/tests/crash_generator.cc +@am__fastdepCXX_TRUE@ $(am__mv) src/common/linux/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-crash_generator.Tpo src/common/linux/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-crash_generator.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/linux/tests/crash_generator.cc' object='src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.o `test -f 'src/common/linux/tests/crash_generator.cc' || echo '$(srcdir)/'`src/common/linux/tests/crash_generator.cc + +src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.obj: src/common/linux/tests/crash_generator.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.obj -MD -MP -MF src/common/linux/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-crash_generator.Tpo -c -o src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.obj `if test -f 'src/common/linux/tests/crash_generator.cc'; then $(CYGPATH_W) 'src/common/linux/tests/crash_generator.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/linux/tests/crash_generator.cc'; fi` +@am__fastdepCXX_TRUE@ $(am__mv) src/common/linux/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-crash_generator.Tpo src/common/linux/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-crash_generator.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/linux/tests/crash_generator.cc' object='src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/linux/tests/src_client_linux_linux_client_unittest-crash_generator.obj `if test -f 'src/common/linux/tests/crash_generator.cc'; then $(CYGPATH_W) 'src/common/linux/tests/crash_generator.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/linux/tests/crash_generator.cc'; fi` + src/common/src_client_linux_linux_client_unittest-memory_unittest.o: src/common/memory_unittest.cc @am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/src_client_linux_linux_client_unittest-memory_unittest.o -MD -MP -MF src/common/$(DEPDIR)/src_client_linux_linux_client_unittest-memory_unittest.Tpo -c -o src/common/src_client_linux_linux_client_unittest-memory_unittest.o `test -f 'src/common/memory_unittest.cc' || echo '$(srcdir)/'`src/common/memory_unittest.cc @am__fastdepCXX_TRUE@ $(am__mv) src/common/$(DEPDIR)/src_client_linux_linux_client_unittest-memory_unittest.Tpo src/common/$(DEPDIR)/src_client_linux_linux_client_unittest-memory_unittest.Po @@ -3571,6 +3675,20 @@ src/common/src_client_linux_linux_client_unittest-memory_unittest.obj: src/commo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/src_client_linux_linux_client_unittest-memory_unittest.obj `if test -f 'src/common/memory_unittest.cc'; then $(CYGPATH_W) 'src/common/memory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/memory_unittest.cc'; fi` +src/common/tests/src_client_linux_linux_client_unittest-file_utils.o: src/common/tests/file_utils.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/tests/src_client_linux_linux_client_unittest-file_utils.o -MD -MP -MF src/common/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-file_utils.Tpo -c -o src/common/tests/src_client_linux_linux_client_unittest-file_utils.o `test -f 'src/common/tests/file_utils.cc' || echo '$(srcdir)/'`src/common/tests/file_utils.cc +@am__fastdepCXX_TRUE@ $(am__mv) src/common/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-file_utils.Tpo src/common/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-file_utils.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/tests/file_utils.cc' object='src/common/tests/src_client_linux_linux_client_unittest-file_utils.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/tests/src_client_linux_linux_client_unittest-file_utils.o `test -f 'src/common/tests/file_utils.cc' || echo '$(srcdir)/'`src/common/tests/file_utils.cc + +src/common/tests/src_client_linux_linux_client_unittest-file_utils.obj: src/common/tests/file_utils.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/tests/src_client_linux_linux_client_unittest-file_utils.obj -MD -MP -MF src/common/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-file_utils.Tpo -c -o src/common/tests/src_client_linux_linux_client_unittest-file_utils.obj `if test -f 'src/common/tests/file_utils.cc'; then $(CYGPATH_W) 'src/common/tests/file_utils.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/tests/file_utils.cc'; fi` +@am__fastdepCXX_TRUE@ $(am__mv) src/common/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-file_utils.Tpo src/common/tests/$(DEPDIR)/src_client_linux_linux_client_unittest-file_utils.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/tests/file_utils.cc' object='src/common/tests/src_client_linux_linux_client_unittest-file_utils.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/tests/src_client_linux_linux_client_unittest-file_utils.obj `if test -f 'src/common/tests/file_utils.cc'; then $(CYGPATH_W) 'src/common/tests/file_utils.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/tests/file_utils.cc'; fi` + src/testing/gtest/src/src_client_linux_linux_client_unittest-gtest-all.o: src/testing/gtest/src/gtest-all.cc @am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/testing/gtest/src/src_client_linux_linux_client_unittest-gtest-all.o -MD -MP -MF src/testing/gtest/src/$(DEPDIR)/src_client_linux_linux_client_unittest-gtest-all.Tpo -c -o src/testing/gtest/src/src_client_linux_linux_client_unittest-gtest-all.o `test -f 'src/testing/gtest/src/gtest-all.cc' || echo '$(srcdir)/'`src/testing/gtest/src/gtest-all.cc @am__fastdepCXX_TRUE@ $(am__mv) src/testing/gtest/src/$(DEPDIR)/src_client_linux_linux_client_unittest-gtest-all.Tpo src/testing/gtest/src/$(DEPDIR)/src_client_linux_linux_client_unittest-gtest-all.Po @@ -5690,6 +5808,8 @@ distclean-generic: -rm -f src/testing/src/$(am__dirstamp) -rm -f src/third_party/libdisasm/$(DEPDIR)/$(am__dirstamp) -rm -f src/third_party/libdisasm/$(am__dirstamp) + -rm -f src/tools/linux/core2md/$(DEPDIR)/$(am__dirstamp) + -rm -f src/tools/linux/core2md/$(am__dirstamp) -rm -f src/tools/linux/dump_syms/$(DEPDIR)/$(am__dirstamp) -rm -f src/tools/linux/dump_syms/$(am__dirstamp) -rm -f src/tools/linux/md2core/$(DEPDIR)/$(am__dirstamp) @@ -5708,7 +5828,7 @@ clean-am: clean-binPROGRAMS clean-checkPROGRAMS clean-generic \ distclean: distclean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) - -rm -rf src/client/$(DEPDIR) src/client/linux/crash_generation/$(DEPDIR) src/client/linux/handler/$(DEPDIR) src/client/linux/minidump_writer/$(DEPDIR) src/common/$(DEPDIR) src/common/dwarf/$(DEPDIR) src/common/linux/$(DEPDIR) src/common/linux/tests/$(DEPDIR) src/common/tests/$(DEPDIR) src/processor/$(DEPDIR) src/testing/gtest/src/$(DEPDIR) src/testing/src/$(DEPDIR) src/third_party/libdisasm/$(DEPDIR) src/tools/linux/dump_syms/$(DEPDIR) src/tools/linux/md2core/$(DEPDIR) src/tools/linux/symupload/$(DEPDIR) + -rm -rf src/client/$(DEPDIR) src/client/linux/crash_generation/$(DEPDIR) src/client/linux/handler/$(DEPDIR) src/client/linux/minidump_writer/$(DEPDIR) src/common/$(DEPDIR) src/common/dwarf/$(DEPDIR) src/common/linux/$(DEPDIR) src/common/linux/tests/$(DEPDIR) src/common/tests/$(DEPDIR) src/processor/$(DEPDIR) src/testing/gtest/src/$(DEPDIR) src/testing/src/$(DEPDIR) src/third_party/libdisasm/$(DEPDIR) src/tools/linux/core2md/$(DEPDIR) src/tools/linux/dump_syms/$(DEPDIR) src/tools/linux/md2core/$(DEPDIR) src/tools/linux/symupload/$(DEPDIR) -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-hdr distclean-tags @@ -5756,7 +5876,7 @@ installcheck-am: maintainer-clean: maintainer-clean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -rf $(top_srcdir)/autom4te.cache - -rm -rf src/client/$(DEPDIR) src/client/linux/crash_generation/$(DEPDIR) src/client/linux/handler/$(DEPDIR) src/client/linux/minidump_writer/$(DEPDIR) src/common/$(DEPDIR) src/common/dwarf/$(DEPDIR) src/common/linux/$(DEPDIR) src/common/linux/tests/$(DEPDIR) src/common/tests/$(DEPDIR) src/processor/$(DEPDIR) src/testing/gtest/src/$(DEPDIR) src/testing/src/$(DEPDIR) src/third_party/libdisasm/$(DEPDIR) src/tools/linux/dump_syms/$(DEPDIR) src/tools/linux/md2core/$(DEPDIR) src/tools/linux/symupload/$(DEPDIR) + -rm -rf src/client/$(DEPDIR) src/client/linux/crash_generation/$(DEPDIR) src/client/linux/handler/$(DEPDIR) src/client/linux/minidump_writer/$(DEPDIR) src/common/$(DEPDIR) src/common/dwarf/$(DEPDIR) src/common/linux/$(DEPDIR) src/common/linux/tests/$(DEPDIR) src/common/tests/$(DEPDIR) src/processor/$(DEPDIR) src/testing/gtest/src/$(DEPDIR) src/testing/src/$(DEPDIR) src/third_party/libdisasm/$(DEPDIR) src/tools/linux/core2md/$(DEPDIR) src/tools/linux/dump_syms/$(DEPDIR) src/tools/linux/md2core/$(DEPDIR) src/tools/linux/symupload/$(DEPDIR) -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic diff --git a/src/client/linux/minidump_writer/linux_core_dumper.cc b/src/client/linux/minidump_writer/linux_core_dumper.cc new file mode 100644 index 00000000..3e8c92fe --- /dev/null +++ b/src/client/linux/minidump_writer/linux_core_dumper.cc @@ -0,0 +1,234 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// linux_core_dumper.cc: Implement google_breakpad::LinuxCoreDumper. +// See linux_core_dumper.h for details. + +#include "client/linux/minidump_writer/linux_core_dumper.h" + +#include +#include +#include +#include +#include +#include + +#include "common/linux/linux_libc_support.h" + +namespace google_breakpad { + +LinuxCoreDumper::LinuxCoreDumper(pid_t pid, + const char* core_path, + const char* procfs_path) + : LinuxDumper(pid), + core_path_(core_path), + procfs_path_(procfs_path), + thread_infos_(&allocator_, 8) { + assert(core_path_); +} + +bool LinuxCoreDumper::BuildProcPath(char* path, pid_t pid, + const char* node) const { + if (!path || !node) + return false; + + size_t node_len = my_strlen(node); + if (node_len == 0) + return false; + + size_t procfs_path_len = my_strlen(procfs_path_); + size_t total_length = procfs_path_len + 1 + node_len; + if (total_length >= NAME_MAX) + return false; + + memcpy(path, procfs_path_, procfs_path_len); + path[procfs_path_len] = '/'; + memcpy(path + procfs_path_len + 1, node, node_len); + path[total_length] = '\0'; + return true; +} + +void LinuxCoreDumper::CopyFromProcess(void* dest, pid_t child, + const void* src, size_t length) { + ElfCoreDump::Addr virtual_address = reinterpret_cast(src); + // TODO(benchan): Investigate whether the data to be copied could span + // across multiple segments in the core dump file. ElfCoreDump::CopyData + // and this method do not handle that case yet. + if (!core_.CopyData(dest, virtual_address, length)) { + // If the data segment is not found in the core dump, fill the result + // with marker characters. + memset(dest, 0xab, length); + } +} + +bool LinuxCoreDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) { + if (index >= thread_infos_.size()) + return false; + + *info = thread_infos_[index]; + const uint8_t* stack_pointer; +#if defined(__i386) + memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp)); +#elif defined(__x86_64) + memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp)); +#elif defined(__ARM_EABI__) + memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp)); +#else +#error "This code hasn't been ported to your platform yet." +#endif + + return GetStackInfo(&info->stack, &info->stack_len, + reinterpret_cast(stack_pointer)); +} + +bool LinuxCoreDumper::IsPostMortem() const { + return true; +} + +bool LinuxCoreDumper::ThreadsSuspend() { + return true; +} + +bool LinuxCoreDumper::ThreadsResume() { + return true; +} + +bool LinuxCoreDumper::EnumerateThreads() { + if (!mapped_core_file_.Map(core_path_)) { + fprintf(stderr, "Could not map core dump file into memory\n"); + return false; + } + + core_.SetContent(mapped_core_file_.content()); + if (!core_.IsValid()) { + fprintf(stderr, "Invalid core dump file\n"); + return false; + } + + ElfCoreDump::Note note = core_.GetFirstNote(); + if (!note.IsValid()) { + fprintf(stderr, "PT_NOTE section not found\n"); + return false; + } + + bool first_thread = true; + do { + ElfCoreDump::Word type = note.GetType(); + MemoryRange name = note.GetName(); + MemoryRange description = note.GetDescription(); + + if (type == 0 || name.IsEmpty() || description.IsEmpty()) { + fprintf(stderr, "Could not found a valid PT_NOTE.\n"); + return false; + } + + // Based on write_note_info() in linux/kernel/fs/binfmt_elf.c, notes are + // ordered as follows (NT_PRXFPREG and NT_386_TLS are i386 specific): + // Thread Name Type + // ------------------------------------------------------------------- + // 1st thread CORE NT_PRSTATUS + // process-wide CORE NT_PRPSINFO + // process-wide CORE NT_AUXV + // 1st thread CORE NT_FPREGSET + // 1st thread LINUX NT_PRXFPREG + // 1st thread LINUX NT_386_TLS + // + // 2nd thread CORE NT_PRSTATUS + // 2nd thread CORE NT_FPREGSET + // 2nd thread LINUX NT_PRXFPREG + // 2nd thread LINUX NT_386_TLS + // + // 3rd thread CORE NT_PRSTATUS + // 3rd thread CORE NT_FPREGSET + // 3rd thread LINUX NT_PRXFPREG + // 3rd thread LINUX NT_386_TLS + // + // The following code only works if notes are ordered as expected. + switch (type) { + case NT_PRSTATUS: { + if (description.length() != sizeof(elf_prstatus)) { + fprintf(stderr, "Found NT_PRSTATUS descriptor of unexpected size\n"); + return false; + } + + const elf_prstatus* status = + reinterpret_cast(description.data()); + pid_t pid = status->pr_pid; + ThreadInfo info; + memset(&info, 0, sizeof(ThreadInfo)); + info.tgid = status->pr_pgrp; + info.ppid = status->pr_ppid; + memcpy(&info.regs, status->pr_reg, sizeof(info.regs)); + if (first_thread) { + crash_thread_ = pid; + crash_signal_ = status->pr_info.si_signo; + } + first_thread = false; + threads_.push_back(pid); + thread_infos_.push_back(info); + break; + } +#if defined(__i386) || defined(__x86_64) + case NT_FPREGSET: { + if (thread_infos_.empty()) + return false; + + ThreadInfo* info = &thread_infos_.back(); + if (description.length() != sizeof(info->fpregs)) { + fprintf(stderr, "Found NT_FPREGSET descriptor of unexpected size\n"); + return false; + } + + memcpy(&info->fpregs, description.data(), sizeof(info->fpregs)); + break; + } +#endif +#if defined(__i386) + case NT_PRXFPREG: { + if (thread_infos_.empty()) + return false; + + ThreadInfo* info = &thread_infos_.back(); + if (description.length() != sizeof(info->fpxregs)) { + fprintf(stderr, "Found NT_PRXFPREG descriptor of unexpected size\n"); + return false; + } + + memcpy(&info->fpxregs, description.data(), sizeof(info->fpxregs)); + break; + } +#endif + } + note = note.GetNextNote(); + } while (note.IsValid()); + + return true; +} + +} // namespace google_breakpad diff --git a/src/client/linux/minidump_writer/linux_core_dumper.h b/src/client/linux/minidump_writer/linux_core_dumper.h new file mode 100644 index 00000000..edb9e738 --- /dev/null +++ b/src/client/linux/minidump_writer/linux_core_dumper.h @@ -0,0 +1,122 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// linux_core_dumper.h: Define the google_breakpad::LinuxCoreDumper +// class, which is derived from google_breakpad::LinuxDumper to extract +// information from a crashed process via its core dump and proc files. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_ + +#include "client/linux/minidump_writer/linux_dumper.h" +#include "common/linux/elf_core_dump.h" +#include "common/linux/memory_mapped_file.h" + +namespace google_breakpad { + +class LinuxCoreDumper : public LinuxDumper { + public: + // Constructs a dumper for extracting information of a given process + // with a process ID of |pid| via its core dump file at |core_path| and + // its proc files at |procfs_path|. If |procfs_path| is a copy of + // /proc/, it should contain the following files: + // auxv, cmdline, environ, exe, maps, status + LinuxCoreDumper(pid_t pid, const char* core_path, const char* procfs_path); + + // Implements LinuxDumper::BuildProcPath(). + // Builds a proc path for a certain pid for a node (/proc//). + // |path| is a character array of at least NAME_MAX bytes to return the + // result.|node| is the final node without any slashes. Return true on + // success. + // + // As this dumper performs a post-mortem dump and makes use of a copy + // of the proc files of the crashed process, this derived method does + // not actually make use of |pid| and always returns a subpath of + // |procfs_path_| regardless of whether |pid| corresponds to the main + // process or a thread of the process, i.e. assuming both the main process + // and its threads have the following proc files with the same content: + // auxv, cmdline, environ, exe, maps, status + virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const; + + // Implements LinuxDumper::CopyFromProcess(). + // Copies content of |length| bytes from a given process |child|, + // starting from |src|, into |dest|. This method extracts the content + // the core dump and fills |dest| with a sequence of marker bytes + // if the expected data is not found in the core dump. + virtual void CopyFromProcess(void* dest, pid_t child, const void* src, + size_t length); + + // Implements LinuxDumper::GetThreadInfoByIndex(). + // Reads information about the |index|-th thread of |threads_|. + // Returns true on success. One must have called |ThreadsSuspend| first. + virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info); + + // Implements LinuxDumper::IsPostMortem(). + // Always returns true to indicate that this dumper performs a + // post-mortem dump of a crashed process via a core dump file. + virtual bool IsPostMortem() const; + + // Implements LinuxDumper::ThreadsSuspend(). + // As the dumper performs a post-mortem dump via a core dump file, + // there is no threads to suspend. This method does nothing and + // always returns true. + virtual bool ThreadsSuspend(); + + // Implements LinuxDumper::ThreadsResume(). + // As the dumper performs a post-mortem dump via a core dump file, + // there is no threads to resume. This method does nothing and + // always returns true. + virtual bool ThreadsResume(); + + protected: + // Implements LinuxDumper::EnumerateThreads(). + // Enumerates all threads of the given process into |threads_|. + virtual bool EnumerateThreads(); + + private: + // Path of the core dump file. + const char* core_path_; + + // Path of the directory containing the proc files of the given process, + // which is usually a copy of /proc/. + const char* procfs_path_; + + // Memory-mapped core dump file at |core_path_|. + MemoryMappedFile mapped_core_file_; + + // Content of the core dump file. + ElfCoreDump core_; + + // Thread info found in the core dump file. + wasteful_vector thread_infos_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_HANDLER_LINUX_CORE_DUMPER_H_ diff --git a/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc new file mode 100644 index 00000000..e335413b --- /dev/null +++ b/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// linux_core_dumper_unittest.cc: +// Unit tests for google_breakpad::LinuxCoreDumoer. + +#include "breakpad_googletest_includes.h" +#include "client/linux/minidump_writer/linux_core_dumper.h" +#include "common/linux/tests/crash_generator.h" + +using std::string; +using namespace google_breakpad; + +TEST(LinuxCoreDumperTest, BuildProcPath) { + const pid_t pid = getpid(); + const char procfs_path[] = "/procfs_copy"; + LinuxCoreDumper dumper(getpid(), "core_file", procfs_path); + + char maps_path[NAME_MAX] = ""; + char maps_path_expected[NAME_MAX]; + snprintf(maps_path_expected, sizeof(maps_path_expected), + "%s/maps", procfs_path); + EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps")); + EXPECT_STREQ(maps_path_expected, maps_path); + + EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps")); + EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, "")); + EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL)); + + char long_node[NAME_MAX]; + size_t long_node_len = NAME_MAX - strlen(procfs_path) - 1; + memset(long_node, 'a', long_node_len); + long_node[long_node_len] = '\0'; + EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, long_node)); +} + +TEST(LinuxCoreDumperTest, VerifyDumpWithMultipleThreads) { + CrashGenerator crash_generator; + if (!crash_generator.HasDefaultCorePattern()) { + fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test" + "is skipped due to non-default core pattern\n"); + return; + } + + const unsigned kNumOfThreads = 3; + const unsigned kCrashThread = 1; + const int kCrashSignal = SIGABRT; + pid_t child_pid; + // TODO(benchan): Revert to use ASSERT_TRUE once the flakiness in + // CrashGenerator is identified and fixed. + if (!crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread, + kCrashSignal, &child_pid)) { + fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test" + "is skipped due to no core dump generated\n"); + return; + } + + pid_t pid = getpid(); + const char* core_file = crash_generator.GetCoreFilePath().c_str(); + + // Since CrashGenerator::CreateChildCrash() simply crashed a fork of + // this process, we expect that those proc files, which are used by + // LinuxCoreDumper, of crashed child process have the same content of + // this process. So we simply pass the proc files of this process to + // LinuxCoreDumper. + char procfs_path[NAME_MAX]; + snprintf(procfs_path, NAME_MAX, "/proc/%d", pid); + + LinuxCoreDumper dumper(child_pid, core_file, procfs_path); + dumper.Init(); + + EXPECT_TRUE(dumper.IsPostMortem()); + + // These are no-ops and should always return true. + EXPECT_TRUE(dumper.ThreadsSuspend()); + EXPECT_TRUE(dumper.ThreadsResume()); + + // LinuxCoreDumper cannot determine the crash address and thus it always + // sets the crash address to 0. + EXPECT_EQ(0, dumper.crash_address()); + EXPECT_EQ(kCrashSignal, dumper.crash_signal()); + EXPECT_EQ(crash_generator.GetThreadId(kCrashThread), + dumper.crash_thread()); + + EXPECT_EQ(kNumOfThreads, dumper.threads().size()); + for (unsigned i = 0; i < kNumOfThreads; ++i) { + ThreadInfo info; + EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &info)); + EXPECT_EQ(getpid(), info.ppid); + } +} diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc index e1eb9847..2cf1b40f 100644 --- a/src/client/linux/minidump_writer/linux_dumper.cc +++ b/src/client/linux/minidump_writer/linux_dumper.cc @@ -27,6 +27,9 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// linux_dumper.cc: Implement google_breakpad::LinuxDumper. +// See linux_dumper.h for details. + // This code deals with the mechanics of getting information about a crashed // process. Since this code may run in a compromised address space, the same // rules apply as detailed at the top of minidump_writer.h: no libc calls and @@ -34,25 +37,12 @@ #include "client/linux/minidump_writer/linux_dumper.h" -#include #include -#include #include #include -#if !defined(__ANDROID__) -#include -#endif #include -#include -#include #include -#include -#include -#include - -#include -#include "client/linux/minidump_writer/directory_reader.h" #include "client/linux/minidump_writer/line_reader.h" #include "common/linux/file_id.h" #include "common/linux/linux_libc_support.h" @@ -63,48 +53,6 @@ static const char kMappedFileUnsafePrefix[] = "/dev/"; static const char kDeletedSuffix[] = " (deleted)"; -// Suspend a thread by attaching to it. -static bool SuspendThread(pid_t pid) { - // This may fail if the thread has just died or debugged. - errno = 0; - if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 && - errno != 0) { - return false; - } - while (sys_waitpid(pid, NULL, __WALL) < 0) { - if (errno != EINTR) { - sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); - return false; - } - } -#if defined(__i386) || defined(__x86_64) - // On x86, the stack pointer is NULL or -1, when executing trusted code in - // the seccomp sandbox. Not only does this cause difficulties down the line - // when trying to dump the thread's stack, it also results in the minidumps - // containing information about the trusted threads. This information is - // generally completely meaningless and just pollutes the minidumps. - // We thus test the stack pointer and exclude any threads that are part of - // the seccomp sandbox's trusted code. - user_regs_struct regs; - if (sys_ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1 || -#if defined(__i386) - !regs.esp -#elif defined(__x86_64) - !regs.rsp -#endif - ) { - sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); - return false; - } -#endif - return true; -} - -// Resume a thread by detaching from it. -static bool ResumeThread(pid_t pid) { - return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0; -} - inline static bool IsMappedFileOpenUnsafe( const google_breakpad::MappingInfo& mapping) { // It is unsafe to attempt to open a mapped file that lives under /dev, @@ -123,7 +71,6 @@ LinuxDumper::LinuxDumper(pid_t pid) crash_address_(0), crash_signal_(0), crash_thread_(0), - threads_suspended_(false), threads_(&allocator_, 8), mappings_(&allocator_) { } @@ -135,80 +82,6 @@ bool LinuxDumper::Init() { return EnumerateThreads() && EnumerateMappings(); } -bool LinuxDumper::ThreadsSuspend() { - if (threads_suspended_) - return true; - for (size_t i = 0; i < threads_.size(); ++i) { - if (!SuspendThread(threads_[i])) { - // If the thread either disappeared before we could attach to it, or if - // it was part of the seccomp sandbox's trusted code, it is OK to - // silently drop it from the minidump. - memmove(&threads_[i], &threads_[i+1], - (threads_.size() - i - 1) * sizeof(threads_[i])); - threads_.resize(threads_.size() - 1); - --i; - } - } - threads_suspended_ = true; - return threads_.size() > 0; -} - -bool LinuxDumper::ThreadsResume() { - if (!threads_suspended_) - return false; - bool good = true; - for (size_t i = 0; i < threads_.size(); ++i) - good &= ResumeThread(threads_[i]); - threads_suspended_ = false; - return good; -} - -void -LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const { - assert(path); - if (!path) { - return; - } - - path[0] = '\0'; - - const unsigned pid_len = my_int_len(pid); - - assert(node); - if (!node) { - return; - } - - size_t node_len = my_strlen(node); - assert(node_len < NAME_MAX); - if (node_len >= NAME_MAX) { - return; - } - - assert(node_len > 0); - if (node_len == 0) { - return; - } - - assert(pid > 0); - if (pid <= 0) { - return; - } - - const size_t total_length = 6 + pid_len + 1 + node_len; - - assert(total_length < NAME_MAX); - if (total_length >= NAME_MAX) { - return; - } - - memcpy(path, "/proc/", 6); - my_itos(path + 6, pid, pid_len); - memcpy(path + 6 + pid_len, "/", 1); - memcpy(path + 6 + pid_len + 1, node, node_len); - path[total_length] = '\0'; -} - bool LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping, bool member, @@ -261,10 +134,8 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping, void* LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const { char auxv_path[NAME_MAX]; - BuildProcPath(auxv_path, pid, "auxv"); - - // If BuildProcPath errors out due to invalid input, we'll handle it when - // we try to sys_open the file. + if (!BuildProcPath(auxv_path, pid, "auxv")) + return NULL; // Find the AT_SYSINFO_EHDR entry for linux-gate.so // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more @@ -290,7 +161,8 @@ LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const { bool LinuxDumper::EnumerateMappings() { char maps_path[NAME_MAX]; - BuildProcPath(maps_path, pid_, "maps"); + if (!BuildProcPath(maps_path, pid_, "maps")) + return false; // linux_gate_loc is the beginning of the kernel's mapping of // linux-gate.so in the process. It doesn't actually show up in the @@ -359,118 +231,6 @@ bool LinuxDumper::EnumerateMappings() { return !mappings_.empty(); } -// Parse /proc/$pid/task to list all the threads of the process identified by -// pid. -bool LinuxDumper::EnumerateThreads() { - char task_path[NAME_MAX]; - BuildProcPath(task_path, pid_, "task"); - - const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0); - if (fd < 0) - return false; - DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd); - - // The directory may contain duplicate entries which we filter by assuming - // that they are consecutive. - int last_tid = -1; - const char* dent_name; - while (dir_reader->GetNextEntry(&dent_name)) { - if (my_strcmp(dent_name, ".") && - my_strcmp(dent_name, "..")) { - int tid = 0; - if (my_strtoui(&tid, dent_name) && - last_tid != tid) { - last_tid = tid; - threads_.push_back(tid); - } - } - dir_reader->PopEntry(); - } - - sys_close(fd); - return true; -} - -// Read thread info from /proc/$pid/status. -// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable, -// these members are set to -1. Returns true iff all three members are -// available. -bool LinuxDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) { - if (index >= threads_.size()) - return false; - - pid_t tid = threads_[index]; - - assert(info != NULL); - char status_path[NAME_MAX]; - BuildProcPath(status_path, tid, "status"); - - const int fd = sys_open(status_path, O_RDONLY, 0); - if (fd < 0) - return false; - - LineReader* const line_reader = new(allocator_) LineReader(fd); - const char* line; - unsigned line_len; - - info->ppid = info->tgid = -1; - - while (line_reader->GetNextLine(&line, &line_len)) { - if (my_strncmp("Tgid:\t", line, 6) == 0) { - my_strtoui(&info->tgid, line + 6); - } else if (my_strncmp("PPid:\t", line, 6) == 0) { - my_strtoui(&info->ppid, line + 6); - } - - line_reader->PopLine(line_len); - } - - if (info->ppid == -1 || info->tgid == -1) - return false; - - if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1) { - return false; - } - -#if !defined(__ANDROID__) - if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) { - return false; - } -#endif - -#if defined(__i386) - if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1) - return false; -#endif - -#if defined(__i386) || defined(__x86_64) - for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) { - if (sys_ptrace( - PTRACE_PEEKUSER, tid, - reinterpret_cast (offsetof(struct user, - u_debugreg[0]) + i * - sizeof(debugreg_t)), - &info->dregs[i]) == -1) { - return false; - } - } -#endif - - const uint8_t* stack_pointer; -#if defined(__i386) - memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp)); -#elif defined(__x86_64) - memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp)); -#elif defined(__ARM_EABI__) - memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp)); -#else -#error "This code hasn't been ported to your platform yet." -#endif - - return GetStackInfo(&info->stack, &info->stack_len, - (uintptr_t) stack_pointer); -} - // Get information about the stack, given the stack pointer. We don't try to // walk the stack since we might not have all the information needed to do // unwind. So we just grab, up to, 32k of stack. @@ -497,24 +257,6 @@ bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len, return true; } -void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src, - size_t length) { - unsigned long tmp = 55; - size_t done = 0; - static const size_t word_size = sizeof(tmp); - uint8_t* const local = (uint8_t*) dest; - uint8_t* const remote = (uint8_t*) src; - - while (done < length) { - const size_t l = length - done > word_size ? word_size : length - done; - if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) { - tmp = 0; - } - memcpy(local + done, &tmp, l); - done += l; - } -} - // Find the mapping which the given memory address falls in. const MappingInfo* LinuxDumper::FindMapping(const void* address) const { const uintptr_t addr = (uintptr_t) address; @@ -544,7 +286,8 @@ bool LinuxDumper::HandleDeletedFileInMapping(char* path) const { // Check |path| against the /proc/pid/exe 'symlink'. char exe_link[NAME_MAX]; char new_path[NAME_MAX]; - BuildProcPath(exe_link, pid_, "exe"); + if (!BuildProcPath(exe_link, pid_, "exe")) + return false; if (!SafeReadLink(exe_link, new_path)) return false; if (my_strcmp(path, new_path) != 0) diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h index b0e4f855..45779699 100644 --- a/src/client/linux/minidump_writer/linux_dumper.h +++ b/src/client/linux/minidump_writer/linux_dumper.h @@ -27,6 +27,14 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// linux_dumper.h: Define the google_breakpad::LinuxDumper class, which +// is a base class for extracting information of a crashed process. It +// was originally a complete implementation using the ptrace API, but +// has been refactored to allow derived implementations supporting both +// ptrace and core dump. A portion of the original implementation is now +// in google_breakpad::LinuxPtraceDumper (see linux_ptrace_dumper.h for +// details). + #ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_ #define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_ @@ -121,15 +129,18 @@ class LinuxDumper { virtual ~LinuxDumper(); // Parse the data for |threads| and |mappings|. - bool Init(); + virtual bool Init(); + + // Return true if the dumper performs a post-mortem dump. + virtual bool IsPostMortem() const = 0; // Suspend/resume all threads in the given process. - bool ThreadsSuspend(); - bool ThreadsResume(); + virtual bool ThreadsSuspend() = 0; + virtual bool ThreadsResume() = 0; // Read information about the |index|-th thread of |threads_|. // Returns true on success. One must have called |ThreadsSuspend| first. - virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info); + virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info) = 0; // These are only valid after a call to |Init|. const wasteful_vector &threads() { return threads_; } @@ -146,13 +157,14 @@ class LinuxDumper { // Copy content of |length| bytes from a given process |child|, // starting from |src|, into |dest|. - void CopyFromProcess(void* dest, pid_t child, const void* src, - size_t length); + virtual void CopyFromProcess(void* dest, pid_t child, const void* src, + size_t length) = 0; - // Builds a proc path for a certain pid for a node. path is a - // character array that is overwritten, and node is the final node - // without any slashes. - void BuildProcPath(char* path, pid_t pid, const char* node) const; + // Builds a proc path for a certain pid for a node (/proc//). + // |path| is a character array of at least NAME_MAX bytes to return the + // result.|node| is the final node without any slashes. Returns true on + // success. + virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const = 0; // Generate a File ID from the .text section of a mapped entry. // If not a member, mapping_id is ignored. @@ -179,9 +191,10 @@ class LinuxDumper { pid_t crash_thread() const { return crash_thread_; } void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; } - private: - bool EnumerateMappings(); - bool EnumerateThreads(); + protected: + virtual bool EnumerateMappings(); + + virtual bool EnumerateThreads() = 0; // For the case where a running program has been deleted, it'll show up in // /proc/pid/maps as "/path/to/program (deleted)". If this is the case, then @@ -208,9 +221,11 @@ class LinuxDumper { mutable PageAllocator allocator_; - bool threads_suspended_; - wasteful_vector threads_; // the ids of all the threads - wasteful_vector mappings_; // info from /proc//maps + // IDs of all the threads. + wasteful_vector threads_; + + // Info from /proc//maps. + wasteful_vector mappings_; }; } // namespace google_breakpad diff --git a/src/client/linux/minidump_writer/linux_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_dumper_unittest.cc deleted file mode 100644 index cf389b57..00000000 --- a/src/client/linux/minidump_writer/linux_dumper_unittest.cc +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright (c) 2009, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "breakpad_googletest_includes.h" -#include "client/linux/minidump_writer/linux_dumper.h" -#include "common/linux/eintr_wrapper.h" -#include "common/linux/file_id.h" -#include "common/linux/safe_readlink.h" -#include "common/memory.h" - -using std::string; -using namespace google_breakpad; - -namespace { -typedef testing::Test LinuxDumperTest; - -string GetHelperBinary() { - // Locate helper binary next to the current binary. - char self_path[PATH_MAX]; - if (!SafeReadLink("/proc/self/exe", self_path)) { - return ""; - } - string helper_path(self_path); - size_t pos = helper_path.rfind('/'); - if (pos == string::npos) { - return ""; - } - helper_path.erase(pos + 1); - helper_path += "linux_dumper_unittest_helper"; - - return helper_path; -} - -} - -TEST(LinuxDumperTest, Setup) { - LinuxDumper dumper(getpid()); -} - -TEST(LinuxDumperTest, FindMappings) { - LinuxDumper dumper(getpid()); - ASSERT_TRUE(dumper.Init()); - - ASSERT_TRUE(dumper.FindMapping(reinterpret_cast(getpid))); - ASSERT_TRUE(dumper.FindMapping(reinterpret_cast(printf))); - ASSERT_FALSE(dumper.FindMapping(NULL)); -} - -TEST(LinuxDumperTest, ThreadList) { - LinuxDumper dumper(getpid()); - ASSERT_TRUE(dumper.Init()); - - ASSERT_GE(dumper.threads().size(), (size_t)1); - bool found = false; - for (size_t i = 0; i < dumper.threads().size(); ++i) { - if (dumper.threads()[i] == getpid()) { - found = true; - break; - } - } -} - -// Helper stack class to close a file descriptor and unmap -// a mmap'ed mapping. -class StackHelper { -public: - StackHelper(int fd, char* mapping, size_t size) - : fd_(fd), mapping_(mapping), size_(size) {} - ~StackHelper() { - munmap(mapping_, size_); - close(fd_); - } - -private: - int fd_; - char* mapping_; - size_t size_; -}; - -TEST(LinuxDumperTest, MergedMappings) { - string helper_path(GetHelperBinary()); - if (helper_path.empty()) { - FAIL() << "Couldn't find helper binary"; - exit(1); - } - - // mmap two segments out of the helper binary, one - // enclosed in the other, but with different protections. - const size_t kPageSize = sysconf(_SC_PAGESIZE); - const size_t kMappingSize = 3 * kPageSize; - int fd = open(helper_path.c_str(), O_RDONLY); - ASSERT_NE(-1, fd); - char* mapping = - reinterpret_cast(mmap(NULL, - kMappingSize, - PROT_READ, - MAP_SHARED, - fd, - 0)); - ASSERT_TRUE(mapping); - - const u_int64_t kMappingAddress = reinterpret_cast(mapping); - - // Ensure that things get cleaned up. - StackHelper helper(fd, mapping, kMappingSize); - - // Carve a page out of the first mapping with different permissions. - char* inside_mapping = reinterpret_cast(mmap(mapping + 2 *kPageSize, - kPageSize, - PROT_NONE, - MAP_SHARED | MAP_FIXED, - fd, - // Map a different offset just to - // better test real-world conditions. - kPageSize)); - ASSERT_TRUE(inside_mapping); - - // Now check that LinuxDumper interpreted the mappings properly. - LinuxDumper dumper(getpid()); - ASSERT_TRUE(dumper.Init()); - int mapping_count = 0; - for (unsigned i = 0; i < dumper.mappings().size(); ++i) { - const MappingInfo& mapping = *dumper.mappings()[i]; - if (strcmp(mapping.name, helper_path.c_str()) == 0) { - // This mapping should encompass the entire original mapped - // range. - EXPECT_EQ(kMappingAddress, mapping.start_addr); - EXPECT_EQ(kMappingSize, mapping.size); - EXPECT_EQ(0, mapping.offset); - mapping_count++; - } - } - EXPECT_EQ(1, mapping_count); -} - -TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) { - static const int kNumberOfThreadsInHelperProgram = 5; - char kNumberOfThreadsArgument[2]; - sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram); - - int fds[2]; - ASSERT_NE(-1, pipe(fds)); - - pid_t child_pid = fork(); - if (child_pid == 0) { - // In child process. - close(fds[0]); - - string helper_path(GetHelperBinary()); - if (helper_path.empty()) { - FAIL() << "Couldn't find helper binary"; - exit(1); - } - - // Pass the pipe fd and the number of threads as arguments. - char pipe_fd_string[8]; - sprintf(pipe_fd_string, "%d", fds[1]); - execl(helper_path.c_str(), - "linux_dumper_unittest_helper", - pipe_fd_string, - kNumberOfThreadsArgument, - NULL); - // Kill if we get here. - printf("Errno from exec: %d", errno); - FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno); - exit(0); - } - close(fds[1]); - // Wait for the child process to signal that it's ready. - struct pollfd pfd; - memset(&pfd, 0, sizeof(pfd)); - pfd.fd = fds[0]; - pfd.events = POLLIN | POLLERR; - - const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); - ASSERT_EQ(1, r); - ASSERT_TRUE(pfd.revents & POLLIN); - uint8_t junk; - read(fds[0], &junk, sizeof(junk)); - close(fds[0]); - - // Child is ready now. - LinuxDumper dumper(child_pid); - ASSERT_TRUE(dumper.Init()); - EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size()); - EXPECT_TRUE(dumper.ThreadsSuspend()); - - ThreadInfo one_thread; - for(size_t i = 0; i < dumper.threads().size(); ++i) { - EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread)); - // In the helper program, we stored a pointer to the thread id in a - // specific register. Check that we can recover its value. -#if defined(__ARM_EABI__) - pid_t *process_tid_location = (pid_t *)(one_thread.regs.uregs[3]); -#elif defined(__i386) - pid_t *process_tid_location = (pid_t *)(one_thread.regs.ecx); -#elif defined(__x86_64) - pid_t *process_tid_location = (pid_t *)(one_thread.regs.rcx); -#else -#error This test has not been ported to this platform. -#endif - pid_t one_thread_id; - dumper.CopyFromProcess(&one_thread_id, - dumper.threads()[i], - process_tid_location, - 4); - EXPECT_EQ(dumper.threads()[i], one_thread_id); - } - kill(child_pid, SIGKILL); -} - -TEST(LinuxDumperTest, BuildProcPath) { - const pid_t pid = getpid(); - LinuxDumper dumper(pid); - - char maps_path[256] = "dummymappath"; - char maps_path_expected[256]; - snprintf(maps_path_expected, sizeof(maps_path_expected), - "/proc/%d/maps", pid); - dumper.BuildProcPath(maps_path, pid, "maps"); - ASSERT_STREQ(maps_path, maps_path_expected); - - // In release mode, we expect BuildProcPath to handle the invalid - // parameters correctly and fill map_path with an empty - // NULL-terminated string. -#ifdef NDEBUG - snprintf(maps_path, sizeof(maps_path), "dummymappath"); - dumper.BuildProcPath(maps_path, 0, "maps"); - EXPECT_STREQ(maps_path, ""); - - snprintf(maps_path, sizeof(maps_path), "dummymappath"); - dumper.BuildProcPath(maps_path, getpid(), ""); - EXPECT_STREQ(maps_path, ""); - - snprintf(maps_path, sizeof(maps_path), "dummymappath"); - dumper.BuildProcPath(maps_path, getpid(), NULL); - EXPECT_STREQ(maps_path, ""); -#endif -} - -#if !defined(__ARM_EABI__) -// Ensure that the linux-gate VDSO is included in the mapping list. -TEST(LinuxDumperTest, MappingsIncludeLinuxGate) { - LinuxDumper dumper(getpid()); - ASSERT_TRUE(dumper.Init()); - - void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid()); - ASSERT_TRUE(linux_gate_loc); - bool found_linux_gate = false; - - const wasteful_vector mappings = dumper.mappings(); - const MappingInfo* mapping; - for (unsigned i = 0; i < mappings.size(); ++i) { - mapping = mappings[i]; - if (!strcmp(mapping->name, kLinuxGateLibraryName)) { - found_linux_gate = true; - break; - } - } - EXPECT_TRUE(found_linux_gate); - EXPECT_EQ(linux_gate_loc, reinterpret_cast(mapping->start_addr)); - EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG)); -} - -// Ensure that the linux-gate VDSO can generate a non-zeroed File ID. -TEST(LinuxDumperTest, LinuxGateMappingID) { - LinuxDumper dumper(getpid()); - ASSERT_TRUE(dumper.Init()); - - bool found_linux_gate = false; - const wasteful_vector mappings = dumper.mappings(); - unsigned index = 0; - for (unsigned i = 0; i < mappings.size(); ++i) { - if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) { - found_linux_gate = true; - index = i; - break; - } - } - ASSERT_TRUE(found_linux_gate); - - uint8_t identifier[sizeof(MDGUID)]; - ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index], - true, - index, - identifier)); - uint8_t empty_identifier[sizeof(MDGUID)]; - memset(empty_identifier, 0, sizeof(empty_identifier)); - EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier))); -} - -// Ensure that the linux-gate VDSO can generate a non-zeroed File ID -// from a child process. -TEST(LinuxDumperTest, LinuxGateMappingIDChild) { - int fds[2]; - ASSERT_NE(-1, pipe(fds)); - - // Fork a child so ptrace works. - const pid_t child = fork(); - if (child == 0) { - close(fds[1]); - // Now wait forever for the parent. - char b; - HANDLE_EINTR(read(fds[0], &b, sizeof(b))); - close(fds[0]); - syscall(__NR_exit); - } - close(fds[0]); - - LinuxDumper dumper(child); - ASSERT_TRUE(dumper.Init()); - - bool found_linux_gate = false; - const wasteful_vector mappings = dumper.mappings(); - unsigned index = 0; - for (unsigned i = 0; i < mappings.size(); ++i) { - if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) { - found_linux_gate = true; - index = i; - break; - } - } - ASSERT_TRUE(found_linux_gate); - - // Need to suspend the child so ptrace actually works. - ASSERT_TRUE(dumper.ThreadsSuspend()); - uint8_t identifier[sizeof(MDGUID)]; - ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index], - true, - index, - identifier)); - uint8_t empty_identifier[sizeof(MDGUID)]; - memset(empty_identifier, 0, sizeof(empty_identifier)); - EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier))); - EXPECT_TRUE(dumper.ThreadsResume()); - close(fds[1]); -} -#endif - -TEST(LinuxDumperTest, FileIDsMatch) { - // Calculate the File ID of our binary using both - // FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping - // and ensure that we get the same result from both. - char exe_name[PATH_MAX]; - ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name)); - - int fds[2]; - ASSERT_NE(-1, pipe(fds)); - - // Fork a child so ptrace works. - const pid_t child = fork(); - if (child == 0) { - close(fds[1]); - // Now wait forever for the parent. - char b; - HANDLE_EINTR(read(fds[0], &b, sizeof(b))); - close(fds[0]); - syscall(__NR_exit); - } - close(fds[0]); - - LinuxDumper dumper(child); - ASSERT_TRUE(dumper.Init()); - const wasteful_vector mappings = dumper.mappings(); - bool found_exe = false; - unsigned i; - for (i = 0; i < mappings.size(); ++i) { - const MappingInfo* mapping = mappings[i]; - if (!strcmp(mapping->name, exe_name)) { - found_exe = true; - break; - } - } - ASSERT_TRUE(found_exe); - - uint8_t identifier1[sizeof(MDGUID)]; - uint8_t identifier2[sizeof(MDGUID)]; - EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i, - identifier1)); - FileID fileid(exe_name); - EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2)); - char identifier_string1[37]; - char identifier_string2[37]; - FileID::ConvertIdentifierToString(identifier1, identifier_string1, - 37); - FileID::ConvertIdentifierToString(identifier2, identifier_string2, - 37); - EXPECT_STREQ(identifier_string1, identifier_string2); - close(fds[1]); -} diff --git a/src/client/linux/minidump_writer/linux_ptrace_dumper.cc b/src/client/linux/minidump_writer/linux_ptrace_dumper.cc new file mode 100644 index 00000000..ea58dd11 --- /dev/null +++ b/src/client/linux/minidump_writer/linux_ptrace_dumper.cc @@ -0,0 +1,292 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// linux_ptrace_dumper.cc: Implement google_breakpad::LinuxPtraceDumper. +// See linux_ptrace_dumper.h for detals. +// This class was originally splitted from google_breakpad::LinuxDumper. + +// This code deals with the mechanics of getting information about a crashed +// process. Since this code may run in a compromised address space, the same +// rules apply as detailed at the top of minidump_writer.h: no libc calls and +// use the alternative allocator. + +#include "client/linux/minidump_writer/linux_ptrace_dumper.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "client/linux/minidump_writer/directory_reader.h" +#include "client/linux/minidump_writer/line_reader.h" +#include "common/linux/linux_libc_support.h" +#include "third_party/lss/linux_syscall_support.h" + +// Suspends a thread by attaching to it. +static bool SuspendThread(pid_t pid) { + // This may fail if the thread has just died or debugged. + errno = 0; + if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 && + errno != 0) { + return false; + } + while (sys_waitpid(pid, NULL, __WALL) < 0) { + if (errno != EINTR) { + sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); + return false; + } + } +#if defined(__i386) || defined(__x86_64) + // On x86, the stack pointer is NULL or -1, when executing trusted code in + // the seccomp sandbox. Not only does this cause difficulties down the line + // when trying to dump the thread's stack, it also results in the minidumps + // containing information about the trusted threads. This information is + // generally completely meaningless and just pollutes the minidumps. + // We thus test the stack pointer and exclude any threads that are part of + // the seccomp sandbox's trusted code. + user_regs_struct regs; + if (sys_ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1 || +#if defined(__i386) + !regs.esp +#elif defined(__x86_64) + !regs.rsp +#endif + ) { + sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); + return false; + } +#endif + return true; +} + +// Resumes a thread by detaching from it. +static bool ResumeThread(pid_t pid) { + return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0; +} + +namespace google_breakpad { + +LinuxPtraceDumper::LinuxPtraceDumper(pid_t pid) + : LinuxDumper(pid), + threads_suspended_(false) { +} + +bool LinuxPtraceDumper::BuildProcPath(char* path, pid_t pid, + const char* node) const { + if (!path || !node || pid <= 0) + return false; + + size_t node_len = my_strlen(node); + if (node_len == 0) + return false; + + const unsigned pid_len = my_int_len(pid); + const size_t total_length = 6 + pid_len + 1 + node_len; + if (total_length >= NAME_MAX) + return false; + + memcpy(path, "/proc/", 6); + my_itos(path + 6, pid, pid_len); + path[6 + pid_len] = '/'; + memcpy(path + 6 + pid_len + 1, node, node_len); + path[total_length] = '\0'; + return true; +} + +void LinuxPtraceDumper::CopyFromProcess(void* dest, pid_t child, + const void* src, size_t length) { + unsigned long tmp = 55; + size_t done = 0; + static const size_t word_size = sizeof(tmp); + uint8_t* const local = (uint8_t*) dest; + uint8_t* const remote = (uint8_t*) src; + + while (done < length) { + const size_t l = (length - done > word_size) ? word_size : (length - done); + if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) { + tmp = 0; + } + memcpy(local + done, &tmp, l); + done += l; + } +} + +// Read thread info from /proc/$pid/status. +// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable, +// these members are set to -1. Returns true iff all three members are +// available. +bool LinuxPtraceDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) { + if (index >= threads_.size()) + return false; + + pid_t tid = threads_[index]; + + assert(info != NULL); + char status_path[NAME_MAX]; + if (!BuildProcPath(status_path, tid, "status")) + return false; + + const int fd = sys_open(status_path, O_RDONLY, 0); + if (fd < 0) + return false; + + LineReader* const line_reader = new(allocator_) LineReader(fd); + const char* line; + unsigned line_len; + + info->ppid = info->tgid = -1; + + while (line_reader->GetNextLine(&line, &line_len)) { + if (my_strncmp("Tgid:\t", line, 6) == 0) { + my_strtoui(&info->tgid, line + 6); + } else if (my_strncmp("PPid:\t", line, 6) == 0) { + my_strtoui(&info->ppid, line + 6); + } + + line_reader->PopLine(line_len); + } + + if (info->ppid == -1 || info->tgid == -1) + return false; + + if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1) { + return false; + } + +#if !defined(__ANDROID__) + if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) { + return false; + } +#endif + +#if defined(__i386) + if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1) + return false; +#endif + +#if defined(__i386) || defined(__x86_64) + for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) { + if (sys_ptrace( + PTRACE_PEEKUSER, tid, + reinterpret_cast (offsetof(struct user, + u_debugreg[0]) + i * + sizeof(debugreg_t)), + &info->dregs[i]) == -1) { + return false; + } + } +#endif + + const uint8_t* stack_pointer; +#if defined(__i386) + memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp)); +#elif defined(__x86_64) + memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp)); +#elif defined(__ARM_EABI__) + memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp)); +#else +#error "This code hasn't been ported to your platform yet." +#endif + + return GetStackInfo(&info->stack, &info->stack_len, + (uintptr_t) stack_pointer); +} + +bool LinuxPtraceDumper::IsPostMortem() const { + return false; +} + +bool LinuxPtraceDumper::ThreadsSuspend() { + if (threads_suspended_) + return true; + for (size_t i = 0; i < threads_.size(); ++i) { + if (!SuspendThread(threads_[i])) { + // If the thread either disappeared before we could attach to it, or if + // it was part of the seccomp sandbox's trusted code, it is OK to + // silently drop it from the minidump. + memmove(&threads_[i], &threads_[i+1], + (threads_.size() - i - 1) * sizeof(threads_[i])); + threads_.resize(threads_.size() - 1); + --i; + } + } + threads_suspended_ = true; + return threads_.size() > 0; +} + +bool LinuxPtraceDumper::ThreadsResume() { + if (!threads_suspended_) + return false; + bool good = true; + for (size_t i = 0; i < threads_.size(); ++i) + good &= ResumeThread(threads_[i]); + threads_suspended_ = false; + return good; +} + +// Parse /proc/$pid/task to list all the threads of the process identified by +// pid. +bool LinuxPtraceDumper::EnumerateThreads() { + char task_path[NAME_MAX]; + if (!BuildProcPath(task_path, pid_, "task")) + return false; + + const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0); + if (fd < 0) + return false; + DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd); + + // The directory may contain duplicate entries which we filter by assuming + // that they are consecutive. + int last_tid = -1; + const char* dent_name; + while (dir_reader->GetNextEntry(&dent_name)) { + if (my_strcmp(dent_name, ".") && + my_strcmp(dent_name, "..")) { + int tid = 0; + if (my_strtoui(&tid, dent_name) && + last_tid != tid) { + last_tid = tid; + threads_.push_back(tid); + } + } + dir_reader->PopEntry(); + } + + sys_close(fd); + return true; +} + +} // namespace google_breakpad diff --git a/src/client/linux/minidump_writer/linux_ptrace_dumper.h b/src/client/linux/minidump_writer/linux_ptrace_dumper.h new file mode 100644 index 00000000..1e9bcfdf --- /dev/null +++ b/src/client/linux/minidump_writer/linux_ptrace_dumper.h @@ -0,0 +1,92 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// linux_ptrace_dumper.h: Define the google_breakpad::LinuxPtraceDumper +// class, which is derived from google_breakpad::LinuxDumper to extract +// information from a crashed process via ptrace. +// This class was originally splitted from google_breakpad::LinuxDumper. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_ + +#include "client/linux/minidump_writer/linux_dumper.h" + +namespace google_breakpad { + +class LinuxPtraceDumper : public LinuxDumper { + public: + // Constructs a dumper for extracting information of a given process + // with a process ID of |pid|. + explicit LinuxPtraceDumper(pid_t pid); + + // Implements LinuxDumper::BuildProcPath(). + // Builds a proc path for a certain pid for a node (/proc//). + // |path| is a character array of at least NAME_MAX bytes to return the + // result. |node| is the final node without any slashes. Returns true on + // success. + virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const; + + // Implements LinuxDumper::CopyFromProcess(). + // Copies content of |length| bytes from a given process |child|, + // starting from |src|, into |dest|. This method uses ptrace to extract + // the content from the target process. + virtual void CopyFromProcess(void* dest, pid_t child, const void* src, + size_t length); + + // Implements LinuxDumper::GetThreadInfoByIndex(). + // Reads information about the |index|-th thread of |threads_|. + // Returns true on success. One must have called |ThreadsSuspend| first. + virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info); + + // Implements LinuxDumper::IsPostMortem(). + // Always returns false to indicate this dumper performs a dump of + // a crashed process via ptrace. + virtual bool IsPostMortem() const; + + // Implements LinuxDumper::ThreadsSuspend(). + // Suspends all threads in the given process. Returns true on success. + virtual bool ThreadsSuspend(); + + // Implements LinuxDumper::ThreadsResume(). + // Resumes all threads in the given process. Returns true on success. + virtual bool ThreadsResume(); + + protected: + // Implements LinuxDumper::EnumerateThreads(). + // Enumerates all threads of the given process into |threads_|. + virtual bool EnumerateThreads(); + + private: + // Set to true if all threads of the crashed process are suspended. + bool threads_suspended_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_HANDLER_LINUX_PTRACE_DUMPER_H_ diff --git a/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc new file mode 100644 index 00000000..84e1974f --- /dev/null +++ b/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc @@ -0,0 +1,430 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// linux_ptrace_dumper_unittest.cc: +// Unit tests for google_breakpad::LinuxPtraceDumoer. +// +// This file was renamed from linux_dumper_unittest.cc and modified due +// to LinuxDumper being splitted into two classes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "breakpad_googletest_includes.h" +#include "client/linux/minidump_writer/linux_ptrace_dumper.h" +#include "common/linux/eintr_wrapper.h" +#include "common/linux/file_id.h" +#include "common/linux/safe_readlink.h" +#include "common/memory.h" + +using std::string; +using namespace google_breakpad; + +namespace { + +typedef testing::Test LinuxPtraceDumperTest; + +string GetHelperBinary() { + // Locate helper binary next to the current binary. + char self_path[PATH_MAX]; + if (!SafeReadLink("/proc/self/exe", self_path)) { + return ""; + } + string helper_path(self_path); + size_t pos = helper_path.rfind('/'); + if (pos == string::npos) { + return ""; + } + helper_path.erase(pos + 1); + helper_path += "linux_dumper_unittest_helper"; + + return helper_path; +} + +} // namespace + +TEST(LinuxPtraceDumperTest, Setup) { + LinuxPtraceDumper dumper(getpid()); +} + +TEST(LinuxPtraceDumperTest, FindMappings) { + LinuxPtraceDumper dumper(getpid()); + ASSERT_TRUE(dumper.Init()); + + ASSERT_TRUE(dumper.FindMapping(reinterpret_cast(getpid))); + ASSERT_TRUE(dumper.FindMapping(reinterpret_cast(printf))); + ASSERT_FALSE(dumper.FindMapping(NULL)); +} + +TEST(LinuxPtraceDumperTest, ThreadList) { + LinuxPtraceDumper dumper(getpid()); + ASSERT_TRUE(dumper.Init()); + + ASSERT_GE(dumper.threads().size(), (size_t)1); + bool found = false; + for (size_t i = 0; i < dumper.threads().size(); ++i) { + if (dumper.threads()[i] == getpid()) { + found = true; + break; + } + } +} + +// Helper stack class to close a file descriptor and unmap +// a mmap'ed mapping. +class StackHelper { + public: + StackHelper(int fd, char* mapping, size_t size) + : fd_(fd), mapping_(mapping), size_(size) {} + ~StackHelper() { + munmap(mapping_, size_); + close(fd_); + } + + private: + int fd_; + char* mapping_; + size_t size_; +}; + +TEST(LinuxPtraceDumperTest, MergedMappings) { + string helper_path(GetHelperBinary()); + if (helper_path.empty()) { + FAIL() << "Couldn't find helper binary"; + exit(1); + } + + // mmap two segments out of the helper binary, one + // enclosed in the other, but with different protections. + const size_t kPageSize = sysconf(_SC_PAGESIZE); + const size_t kMappingSize = 3 * kPageSize; + int fd = open(helper_path.c_str(), O_RDONLY); + ASSERT_NE(-1, fd); + char* mapping = + reinterpret_cast(mmap(NULL, + kMappingSize, + PROT_READ, + MAP_SHARED, + fd, + 0)); + ASSERT_TRUE(mapping); + + const u_int64_t kMappingAddress = reinterpret_cast(mapping); + + // Ensure that things get cleaned up. + StackHelper helper(fd, mapping, kMappingSize); + + // Carve a page out of the first mapping with different permissions. + char* inside_mapping = reinterpret_cast( + mmap(mapping + 2 *kPageSize, + kPageSize, + PROT_NONE, + MAP_SHARED | MAP_FIXED, + fd, + // Map a different offset just to + // better test real-world conditions. + kPageSize)); + ASSERT_TRUE(inside_mapping); + + // Now check that LinuxPtraceDumper interpreted the mappings properly. + LinuxPtraceDumper dumper(getpid()); + ASSERT_TRUE(dumper.Init()); + int mapping_count = 0; + for (unsigned i = 0; i < dumper.mappings().size(); ++i) { + const MappingInfo& mapping = *dumper.mappings()[i]; + if (strcmp(mapping.name, helper_path.c_str()) == 0) { + // This mapping should encompass the entire original mapped + // range. + EXPECT_EQ(kMappingAddress, mapping.start_addr); + EXPECT_EQ(kMappingSize, mapping.size); + EXPECT_EQ(0, mapping.offset); + mapping_count++; + } + } + EXPECT_EQ(1, mapping_count); +} + +TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) { + static const int kNumberOfThreadsInHelperProgram = 5; + char kNumberOfThreadsArgument[2]; + sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram); + + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + pid_t child_pid = fork(); + if (child_pid == 0) { + // In child process. + close(fds[0]); + + string helper_path(GetHelperBinary()); + if (helper_path.empty()) { + FAIL() << "Couldn't find helper binary"; + exit(1); + } + + // Pass the pipe fd and the number of threads as arguments. + char pipe_fd_string[8]; + sprintf(pipe_fd_string, "%d", fds[1]); + execl(helper_path.c_str(), + "linux_dumper_unittest_helper", + pipe_fd_string, + kNumberOfThreadsArgument, + NULL); + // Kill if we get here. + printf("Errno from exec: %d", errno); + FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno); + exit(0); + } + close(fds[1]); + // Wait for the child process to signal that it's ready. + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fds[0]; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); + ASSERT_EQ(1, r); + ASSERT_TRUE(pfd.revents & POLLIN); + uint8_t junk; + read(fds[0], &junk, sizeof(junk)); + close(fds[0]); + + // Child is ready now. + LinuxPtraceDumper dumper(child_pid); + ASSERT_TRUE(dumper.Init()); + EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size()); + EXPECT_TRUE(dumper.ThreadsSuspend()); + + ThreadInfo one_thread; + for (size_t i = 0; i < dumper.threads().size(); ++i) { + EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread)); + // In the helper program, we stored a pointer to the thread id in a + // specific register. Check that we can recover its value. +#if defined(__ARM_EABI__) + pid_t *process_tid_location = (pid_t *)(one_thread.regs.uregs[3]); +#elif defined(__i386) + pid_t *process_tid_location = (pid_t *)(one_thread.regs.ecx); +#elif defined(__x86_64) + pid_t *process_tid_location = (pid_t *)(one_thread.regs.rcx); +#else +#error This test has not been ported to this platform. +#endif + pid_t one_thread_id; + dumper.CopyFromProcess(&one_thread_id, + dumper.threads()[i], + process_tid_location, + 4); + EXPECT_EQ(dumper.threads()[i], one_thread_id); + } + kill(child_pid, SIGKILL); +} + +TEST(LinuxPtraceDumperTest, BuildProcPath) { + const pid_t pid = getpid(); + LinuxPtraceDumper dumper(pid); + + char maps_path[NAME_MAX] = ""; + char maps_path_expected[NAME_MAX]; + snprintf(maps_path_expected, sizeof(maps_path_expected), + "/proc/%d/maps", pid); + EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps")); + EXPECT_STREQ(maps_path_expected, maps_path); + + EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps")); + EXPECT_FALSE(dumper.BuildProcPath(maps_path, 0, "maps")); + EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, "")); + EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL)); + + char long_node[NAME_MAX]; + size_t long_node_len = NAME_MAX - strlen("/proc/123") - 1; + memset(long_node, 'a', long_node_len); + long_node[long_node_len] = '\0'; + EXPECT_FALSE(dumper.BuildProcPath(maps_path, 123, long_node)); +} + +#if !defined(__ARM_EABI__) +// Ensure that the linux-gate VDSO is included in the mapping list. +TEST(LinuxPtraceDumperTest, MappingsIncludeLinuxGate) { + LinuxPtraceDumper dumper(getpid()); + ASSERT_TRUE(dumper.Init()); + + void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid()); + ASSERT_TRUE(linux_gate_loc); + bool found_linux_gate = false; + + const wasteful_vector mappings = dumper.mappings(); + const MappingInfo* mapping; + for (unsigned i = 0; i < mappings.size(); ++i) { + mapping = mappings[i]; + if (!strcmp(mapping->name, kLinuxGateLibraryName)) { + found_linux_gate = true; + break; + } + } + EXPECT_TRUE(found_linux_gate); + EXPECT_EQ(linux_gate_loc, reinterpret_cast(mapping->start_addr)); + EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG)); +} + +// Ensure that the linux-gate VDSO can generate a non-zeroed File ID. +TEST(LinuxPtraceDumperTest, LinuxGateMappingID) { + LinuxPtraceDumper dumper(getpid()); + ASSERT_TRUE(dumper.Init()); + + bool found_linux_gate = false; + const wasteful_vector mappings = dumper.mappings(); + unsigned index = 0; + for (unsigned i = 0; i < mappings.size(); ++i) { + if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) { + found_linux_gate = true; + index = i; + break; + } + } + ASSERT_TRUE(found_linux_gate); + + uint8_t identifier[sizeof(MDGUID)]; + ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index], + true, + index, + identifier)); + uint8_t empty_identifier[sizeof(MDGUID)]; + memset(empty_identifier, 0, sizeof(empty_identifier)); + EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier))); +} + +// Ensure that the linux-gate VDSO can generate a non-zeroed File ID +// from a child process. +TEST(LinuxPtraceDumperTest, LinuxGateMappingIDChild) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + // Fork a child so ptrace works. + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + // Now wait forever for the parent. + char b; + HANDLE_EINTR(read(fds[0], &b, sizeof(b))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + LinuxPtraceDumper dumper(child); + ASSERT_TRUE(dumper.Init()); + + bool found_linux_gate = false; + const wasteful_vector mappings = dumper.mappings(); + unsigned index = 0; + for (unsigned i = 0; i < mappings.size(); ++i) { + if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) { + found_linux_gate = true; + index = i; + break; + } + } + ASSERT_TRUE(found_linux_gate); + + // Need to suspend the child so ptrace actually works. + ASSERT_TRUE(dumper.ThreadsSuspend()); + uint8_t identifier[sizeof(MDGUID)]; + ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index], + true, + index, + identifier)); + uint8_t empty_identifier[sizeof(MDGUID)]; + memset(empty_identifier, 0, sizeof(empty_identifier)); + EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier))); + EXPECT_TRUE(dumper.ThreadsResume()); + close(fds[1]); +} +#endif + +TEST(LinuxPtraceDumperTest, FileIDsMatch) { + // Calculate the File ID of our binary using both + // FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping + // and ensure that we get the same result from both. + char exe_name[PATH_MAX]; + ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name)); + + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + // Fork a child so ptrace works. + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + // Now wait forever for the parent. + char b; + HANDLE_EINTR(read(fds[0], &b, sizeof(b))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + LinuxPtraceDumper dumper(child); + ASSERT_TRUE(dumper.Init()); + const wasteful_vector mappings = dumper.mappings(); + bool found_exe = false; + unsigned i; + for (i = 0; i < mappings.size(); ++i) { + const MappingInfo* mapping = mappings[i]; + if (!strcmp(mapping->name, exe_name)) { + found_exe = true; + break; + } + } + ASSERT_TRUE(found_exe); + + uint8_t identifier1[sizeof(MDGUID)]; + uint8_t identifier2[sizeof(MDGUID)]; + EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i, + identifier1)); + FileID fileid(exe_name); + EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2)); + char identifier_string1[37]; + char identifier_string2[37]; + FileID::ConvertIdentifierToString(identifier1, identifier_string1, + 37); + FileID::ConvertIdentifierToString(identifier2, identifier_string2, + 37); + EXPECT_STREQ(identifier_string1, identifier_string2); + close(fds[1]); +} diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc index 9a3d37fe..c6feabdd 100644 --- a/src/client/linux/minidump_writer/minidump_writer.cc +++ b/src/client/linux/minidump_writer/minidump_writer.cc @@ -74,6 +74,8 @@ #include "client/linux/handler/exception_handler.h" #include "client/linux/minidump_writer/line_reader.h" #include "client/linux/minidump_writer/linux_dumper.h" +#include "client/linux/minidump_writer/linux_core_dumper.h" +#include "client/linux/minidump_writer/linux_ptrace_dumper.h" #include "client/linux/minidump_writer/minidump_extension_linux.h" #include "common/linux/linux_libc_support.h" #include "third_party/lss/linux_syscall_support.h" @@ -372,9 +374,9 @@ class MinidumpWriter { const MappingList& mappings, LinuxDumper* dumper) : filename_(filename), - ucontext_(&context->context), + ucontext_(context ? &context->context : NULL), #if !defined(__ARM_EABI__) - float_state_(&context->float_state), + float_state_(context ? &context->float_state : NULL), #else // TODO: fix this after fixing ExceptionHandler float_state_(NULL), @@ -401,18 +403,25 @@ class MinidumpWriter { struct r_debug* r_debug = NULL; uint32_t dynamic_length = 0; #if !defined(__ANDROID__) - // The Android NDK is missing structure definitions for most of this. - // For now, it's simpler just to skip it. - for (int i = 0;;) { - ElfW(Dyn) dyn; - dynamic_length += sizeof(dyn); - dumper_->CopyFromProcess(&dyn, GetCrashThread(), _DYNAMIC+i++, - sizeof(dyn)); - if (dyn.d_tag == DT_DEBUG) { - r_debug = (struct r_debug*)dyn.d_un.d_ptr; - continue; - } else if (dyn.d_tag == DT_NULL) { - break; + // This code assumes the crashing process is the same as this process and + // may hang or take a long time to complete if not so. + // Thus, we skip this code for a post-mortem based dump. + if (!dumper_->IsPostMortem()) { + // The Android NDK is missing structure definitions for most of this. + // For now, it's simpler just to skip it. + for (int i = 0;;) { + ElfW(Dyn) dyn; + dynamic_length += sizeof(dyn); + // NOTE: Use of _DYNAMIC assumes this is the same process as the + // crashing process. This loop will go forever if it's out of bounds. + dumper_->CopyFromProcess(&dyn, GetCrashThread(), _DYNAMIC+i++, + sizeof(dyn)); + if (dyn.d_tag == DT_DEBUG) { + r_debug = (struct r_debug*)dyn.d_un.d_ptr; + continue; + } else if (dyn.d_tag == DT_NULL) { + break; + } } } #endif @@ -647,7 +656,8 @@ class MinidumpWriter { // we used the actual state of the thread we would find it running in the // signal handler with the alternative stack, which would be deeply // unhelpful. - if ((pid_t)thread.thread_id == GetCrashThread()) { + if (static_cast(thread.thread_id) == GetCrashThread() && + !dumper_->IsPostMortem()) { const void* stack; size_t stack_len; if (!dumper_->GetStackInfo(&stack, &stack_len, GetStackPointer())) @@ -736,6 +746,10 @@ class MinidumpWriter { CPUFillFromThreadInfo(cpu.get(), info); PopSeccompStackFrame(cpu.get(), thread, stack_copy); thread.thread_context = cpu.location(); + if (dumper_->threads()[i] == GetCrashThread()) { + assert(dumper_->IsPostMortem()); + crashing_thread_context_ = cpu.location(); + } } list.CopyIndexAfterObject(i, &thread, sizeof(thread)); @@ -1281,7 +1295,8 @@ class MinidumpWriter { bool WriteProcFile(MDLocationDescriptor* result, pid_t pid, const char* filename) { char buf[NAME_MAX]; - dumper_->BuildProcPath(buf, pid, filename); + if (!dumper_->BuildProcPath(buf, pid, filename)) + return false; return WriteFile(result, buf); } @@ -1312,7 +1327,7 @@ bool WriteMinidump(const char* filename, pid_t crashing_process, return false; const ExceptionHandler::CrashContext* context = reinterpret_cast(blob); - LinuxDumper dumper(crashing_process); + LinuxPtraceDumper dumper(crashing_process); dumper.set_crash_address( reinterpret_cast(context->siginfo.si_addr)); dumper.set_crash_signal(context->siginfo.si_signo); @@ -1323,4 +1338,15 @@ bool WriteMinidump(const char* filename, pid_t crashing_process, return writer.Dump(); } +bool WriteMinidumpFromCore(const char* filename, + const char* core_path, + const char* procfs_override) { + MappingList mappings; + LinuxCoreDumper dumper(0, core_path, procfs_override); + MinidumpWriter writer(filename, NULL, mappings, &dumper); + if (!writer.Init()) + return false; + return writer.Dump(); +} + } // namespace google_breakpad diff --git a/src/client/linux/minidump_writer/minidump_writer.h b/src/client/linux/minidump_writer/minidump_writer.h index 156ca35f..731249e3 100644 --- a/src/client/linux/minidump_writer/minidump_writer.h +++ b/src/client/linux/minidump_writer/minidump_writer.h @@ -62,6 +62,12 @@ bool WriteMinidump(const char* filename, pid_t crashing_process, const void* blob, size_t blob_size, const MappingList& mappings); +// Write a minidump to the filesystem. Same as above, but uses the given +// core file and procfs directory to generate the minidump post mortem. +bool WriteMinidumpFromCore(const char* filename, + const char* core_path, + const char* procfs_override); + } // namespace google_breakpad #endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ diff --git a/src/common/linux/elf_core_dump_unittest.cc b/src/common/linux/elf_core_dump_unittest.cc index 89496ab9..11920f25 100644 --- a/src/common/linux/elf_core_dump_unittest.cc +++ b/src/common/linux/elf_core_dump_unittest.cc @@ -141,7 +141,7 @@ TEST(ElfCoreDumpTest, ValidCoreFile) { // TODO(benchan): Revert to use ASSERT_TRUE once the flakiness in // CrashGenerator is identified and fixed. if (!crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread, - kCrashSignal)) { + kCrashSignal, NULL)) { fprintf(stderr, "ElfCoreDumpTest.ValidCoreFile test is skipped " "due to no core dump generated"); return; diff --git a/src/common/linux/tests/crash_generator.cc b/src/common/linux/tests/crash_generator.cc index fd96e6ca..7274acf9 100644 --- a/src/common/linux/tests/crash_generator.cc +++ b/src/common/linux/tests/crash_generator.cc @@ -144,7 +144,8 @@ bool CrashGenerator::SetCoreFileSizeLimit(rlim_t limit) const { } bool CrashGenerator::CreateChildCrash( - unsigned num_threads, unsigned crash_thread, int crash_signal) { + unsigned num_threads, unsigned crash_thread, int crash_signal, + pid_t* child_pid) { if (num_threads == 0 || crash_thread >= num_threads) return false; @@ -178,6 +179,9 @@ bool CrashGenerator::CreateChildCrash( perror("CrashGenerator: Child process not killed by the expected signal"); return false; } + + if (child_pid) + *child_pid = pid; return true; } diff --git a/src/common/linux/tests/crash_generator.h b/src/common/linux/tests/crash_generator.h index c88a8920..874b15cd 100644 --- a/src/common/linux/tests/crash_generator.h +++ b/src/common/linux/tests/crash_generator.h @@ -70,7 +70,7 @@ class CrashGenerator { // a signal with number |crash_signal| to the |crash_thread|-th thread. // Returns true on success. bool CreateChildCrash(unsigned num_threads, unsigned crash_thread, - int crash_signal); + int crash_signal, pid_t* child_pid); // Creates |num_threads| threads in the child process. void CreateThreadsInChildProcess(unsigned num_threads); diff --git a/src/tools/linux/core2md/core2md.cc b/src/tools/linux/core2md/core2md.cc new file mode 100644 index 00000000..c8060b5e --- /dev/null +++ b/src/tools/linux/core2md/core2md.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// core2md.cc: A utility to convert an ELF core file to a minidump file. + +#include + +#include "client/linux/minidump_writer/minidump_writer.h" + +static int ShowUsage(const char* argv0) { + fprintf(stderr, "Usage: %s \n", argv0); + return 1; +} + +int main(int argc, char *argv[]) { + if (argc != 4) { + return ShowUsage(argv[0]); + } + + const char* core_file = argv[1]; + const char* procfs_dir = argv[2]; + const char* minidump_file = argv[3]; + if (!google_breakpad::WriteMinidumpFromCore(minidump_file, + core_file, + procfs_dir)) { + fprintf(stderr, "Unable to generate minidump.\n"); + return 1; + } + + return 0; +} -- cgit v1.2.1