Repository: chromium/crashpad Branch: main Commit: 631b657792ed Files: 1179 Total size: 7.1 MB Directory structure: gitextract_jbkjhin4/ ├── .clang-format ├── .gitattributes ├── .gitignore ├── .gn ├── .style.yapf ├── .vpython3 ├── AUTHORS ├── BUILD.gn ├── CONTRIBUTORS ├── DEPS ├── LICENSE ├── OWNERS ├── README.md ├── build/ │ ├── BUILD.gn │ ├── BUILDCONFIG.gn │ ├── config/ │ │ └── fuchsia/ │ │ └── gn_configs.gni │ ├── crashpad_buildconfig.gni │ ├── crashpad_fuzzer_test.gni │ ├── fuchsia_envs.py │ ├── install_linux_sysroot.py │ ├── ios/ │ │ ├── Unittest-Info.plist │ │ ├── convert_gn_xcodeproj.py │ │ ├── setup_ios_gn.config │ │ ├── setup_ios_gn.py │ │ ├── xcodescheme-testable.template │ │ └── xcodescheme.template │ ├── run_tests.py │ └── test.gni ├── client/ │ ├── BUILD.gn │ ├── annotation.cc │ ├── annotation.h │ ├── annotation_list.cc │ ├── annotation_list.h │ ├── annotation_list_test.cc │ ├── annotation_test.cc │ ├── client_argv_handling.cc │ ├── client_argv_handling.h │ ├── crash_handler_base_ios.cc │ ├── crash_handler_base_ios.h │ ├── crash_handler_ios.cc │ ├── crash_handler_ios.h │ ├── crash_handler_tvos.cc │ ├── crash_handler_tvos.h │ ├── crash_report_database.cc │ ├── crash_report_database.h │ ├── crash_report_database_generic.cc │ ├── crash_report_database_mac.mm │ ├── crash_report_database_test.cc │ ├── crash_report_database_win.cc │ ├── crashpad_client.h │ ├── crashpad_client_fuchsia.cc │ ├── crashpad_client_ios.cc │ ├── crashpad_client_ios_test.mm │ ├── crashpad_client_linux.cc │ ├── crashpad_client_linux_test.cc │ ├── crashpad_client_mac.cc │ ├── crashpad_client_win.cc │ ├── crashpad_client_win_test.cc │ ├── crashpad_info.cc │ ├── crashpad_info.h │ ├── crashpad_info_note.S │ ├── crashpad_info_test.cc │ ├── ios_handler/ │ │ ├── exception_processor.h │ │ ├── exception_processor.mm │ │ ├── exception_processor_test.mm │ │ ├── in_process_handler.cc │ │ ├── in_process_handler.h │ │ ├── in_process_handler_test.cc │ │ ├── in_process_intermediate_dump_handler.cc │ │ ├── in_process_intermediate_dump_handler.h │ │ ├── in_process_intermediate_dump_handler_test.cc │ │ ├── prune_intermediate_dumps_and_crash_reports_thread.cc │ │ └── prune_intermediate_dumps_and_crash_reports_thread.h │ ├── length_delimited_ring_buffer.h │ ├── length_delimited_ring_buffer_test.cc │ ├── prune_crash_reports.cc │ ├── prune_crash_reports.h │ ├── prune_crash_reports_test.cc │ ├── pthread_create_linux.cc │ ├── ring_buffer_annotation.h │ ├── ring_buffer_annotation_load_test_main.cc │ ├── ring_buffer_annotation_test.cc │ ├── settings.cc │ ├── settings.h │ ├── settings_test.cc │ ├── simple_address_range_bag.h │ ├── simple_address_range_bag_test.cc │ ├── simple_string_dictionary.h │ ├── simple_string_dictionary_test.cc │ ├── simulate_crash.h │ ├── simulate_crash_ios.h │ ├── simulate_crash_linux.h │ ├── simulate_crash_mac.cc │ ├── simulate_crash_mac.h │ ├── simulate_crash_mac_test.cc │ ├── simulate_crash_win.h │ └── upload_behavior_ios.h ├── codereview.settings ├── compat/ │ ├── BUILD.gn │ ├── android/ │ │ ├── dlfcn_internal.cc │ │ ├── dlfcn_internal.h │ │ ├── elf.h │ │ ├── linux/ │ │ │ ├── elf.h │ │ │ ├── prctl.h │ │ │ └── ptrace.h │ │ ├── sched.h │ │ └── sys/ │ │ ├── epoll.cc │ │ ├── epoll.h │ │ ├── mman.h │ │ ├── mman_mmap.cc │ │ ├── syscall.h │ │ └── user.h │ ├── ios/ │ │ └── mach/ │ │ ├── exc.defs │ │ ├── mach_exc.defs │ │ ├── mach_types.defs │ │ ├── machine/ │ │ │ └── machine_types.defs │ │ └── std_types.defs │ ├── linux/ │ │ ├── signal.h │ │ └── sys/ │ │ ├── mman.h │ │ ├── mman_memfd_create.cc │ │ ├── ptrace.h │ │ └── user.h │ ├── mac/ │ │ ├── Availability.h │ │ ├── AvailabilityVersions.h │ │ ├── kern/ │ │ │ └── exc_resource.h │ │ ├── mach/ │ │ │ ├── i386/ │ │ │ │ └── thread_state.h │ │ │ └── mach.h │ │ ├── mach-o/ │ │ │ └── loader.h │ │ └── sys/ │ │ └── resource.h │ ├── non_mac/ │ │ ├── mach/ │ │ │ ├── mach.h │ │ │ ├── machine.h │ │ │ └── vm_prot.h │ │ └── mach-o/ │ │ └── loader.h │ ├── non_win/ │ │ ├── dbghelp.h │ │ ├── minwinbase.h │ │ ├── timezoneapi.h │ │ ├── verrsrc.h │ │ ├── windows.h │ │ └── winnt.h │ └── win/ │ ├── getopt.h │ ├── strings.cc │ ├── strings.h │ ├── sys/ │ │ ├── time.h │ │ └── types.h │ ├── time.cc │ ├── time.h │ ├── winbase.h │ ├── winnt.h │ └── winternl.h ├── doc/ │ ├── .gitignore │ ├── appengine/ │ │ ├── README │ │ ├── go.mod │ │ ├── go.sum │ │ └── src/ │ │ └── crashpad-home/ │ │ ├── app.yaml │ │ └── main.go │ ├── developing.md │ ├── ios_overview_design.md │ ├── man.md │ ├── overview_design.md │ ├── status.md │ └── support/ │ ├── compat.sh │ ├── crashpad.doxy │ ├── crashpad.doxy.h │ ├── crashpad_doxygen.css │ ├── generate.sh │ ├── generate_doxygen.py │ └── generate_git.sh ├── handler/ │ ├── BUILD.gn │ ├── crash_report_upload_rate_limit.cc │ ├── crash_report_upload_rate_limit.h │ ├── crash_report_upload_rate_limit_test.cc │ ├── crash_report_upload_thread.cc │ ├── crash_report_upload_thread.h │ ├── crashpad_handler.md │ ├── crashpad_handler_main.cc │ ├── crashpad_handler_test.cc │ ├── crashpad_handler_test_extended_handler.cc │ ├── handler_main.cc │ ├── handler_main.h │ ├── linux/ │ │ ├── capture_snapshot.cc │ │ ├── capture_snapshot.h │ │ ├── crash_report_exception_handler.cc │ │ ├── crash_report_exception_handler.h │ │ ├── cros_crash_report_exception_handler.cc │ │ ├── cros_crash_report_exception_handler.h │ │ ├── exception_handler_server.cc │ │ ├── exception_handler_server.h │ │ ├── exception_handler_server_test.cc │ │ └── handler_trampoline.cc │ ├── mac/ │ │ ├── crash_report_exception_handler.cc │ │ ├── crash_report_exception_handler.h │ │ ├── exception_handler_server.cc │ │ ├── exception_handler_server.h │ │ ├── file_limit_annotation.cc │ │ └── file_limit_annotation.h │ ├── main.cc │ ├── minidump_to_upload_parameters.cc │ ├── minidump_to_upload_parameters.h │ ├── minidump_to_upload_parameters_test.cc │ ├── prune_crash_reports_thread.cc │ ├── prune_crash_reports_thread.h │ ├── user_stream_data_source.cc │ ├── user_stream_data_source.h │ └── win/ │ ├── .gitattributes │ ├── crash_other_program.cc │ ├── crash_report_exception_handler.cc │ ├── crash_report_exception_handler.h │ ├── crashy_signal.cc │ ├── crashy_test_program.cc │ ├── crashy_test_z7_loader.cc │ ├── fake_handler_that_crashes_at_startup.cc │ ├── fastfail_test_program.cc │ ├── hanging_program.cc │ ├── heap_corrupting_program.cc │ ├── loader_lock_dll.cc │ ├── self_destroying_test_program.cc │ ├── wer/ │ │ ├── BUILD.gn │ │ ├── crashpad_wer.cc │ │ ├── crashpad_wer.def │ │ ├── crashpad_wer.h │ │ ├── crashpad_wer.ver │ │ ├── crashpad_wer_main.cc │ │ └── crashpad_wer_module_unittest.cc │ └── z7_test.cpp ├── infra/ │ └── config/ │ ├── PRESUBMIT.py │ ├── generated/ │ │ ├── commit-queue.cfg │ │ ├── cr-buildbucket.cfg │ │ ├── luci-logdog.cfg │ │ ├── luci-milo.cfg │ │ ├── luci-scheduler.cfg │ │ ├── project.cfg │ │ └── realms.cfg │ └── main.star ├── minidump/ │ ├── BUILD.gn │ ├── minidump_annotation_writer.cc │ ├── minidump_annotation_writer.h │ ├── minidump_annotation_writer_test.cc │ ├── minidump_byte_array_writer.cc │ ├── minidump_byte_array_writer.h │ ├── minidump_byte_array_writer_test.cc │ ├── minidump_context.h │ ├── minidump_context_writer.cc │ ├── minidump_context_writer.h │ ├── minidump_context_writer_test.cc │ ├── minidump_crashpad_info_writer.cc │ ├── minidump_crashpad_info_writer.h │ ├── minidump_crashpad_info_writer_test.cc │ ├── minidump_exception_writer.cc │ ├── minidump_exception_writer.h │ ├── minidump_exception_writer_test.cc │ ├── minidump_extensions.cc │ ├── minidump_extensions.h │ ├── minidump_file_writer.cc │ ├── minidump_file_writer.h │ ├── minidump_file_writer_test.cc │ ├── minidump_handle_writer.cc │ ├── minidump_handle_writer.h │ ├── minidump_handle_writer_test.cc │ ├── minidump_memory_info_writer.cc │ ├── minidump_memory_info_writer.h │ ├── minidump_memory_info_writer_test.cc │ ├── minidump_memory_writer.cc │ ├── minidump_memory_writer.h │ ├── minidump_memory_writer_test.cc │ ├── minidump_misc_info_writer.cc │ ├── minidump_misc_info_writer.h │ ├── minidump_misc_info_writer_test.cc │ ├── minidump_module_crashpad_info_writer.cc │ ├── minidump_module_crashpad_info_writer.h │ ├── minidump_module_crashpad_info_writer_test.cc │ ├── minidump_module_writer.cc │ ├── minidump_module_writer.h │ ├── minidump_module_writer_test.cc │ ├── minidump_rva_list_writer.cc │ ├── minidump_rva_list_writer.h │ ├── minidump_rva_list_writer_test.cc │ ├── minidump_simple_string_dictionary_writer.cc │ ├── minidump_simple_string_dictionary_writer.h │ ├── minidump_simple_string_dictionary_writer_test.cc │ ├── minidump_stream_writer.cc │ ├── minidump_stream_writer.h │ ├── minidump_string_writer.cc │ ├── minidump_string_writer.h │ ├── minidump_string_writer_test.cc │ ├── minidump_system_info_writer.cc │ ├── minidump_system_info_writer.h │ ├── minidump_system_info_writer_test.cc │ ├── minidump_thread_id_map.cc │ ├── minidump_thread_id_map.h │ ├── minidump_thread_id_map_test.cc │ ├── minidump_thread_name_list_writer.cc │ ├── minidump_thread_name_list_writer.h │ ├── minidump_thread_name_list_writer_test.cc │ ├── minidump_thread_writer.cc │ ├── minidump_thread_writer.h │ ├── minidump_thread_writer_test.cc │ ├── minidump_unloaded_module_writer.cc │ ├── minidump_unloaded_module_writer.h │ ├── minidump_unloaded_module_writer_test.cc │ ├── minidump_user_extension_stream_data_source.cc │ ├── minidump_user_extension_stream_data_source.h │ ├── minidump_user_stream_writer.cc │ ├── minidump_user_stream_writer.h │ ├── minidump_user_stream_writer_test.cc │ ├── minidump_writable.cc │ ├── minidump_writable.h │ ├── minidump_writable_test.cc │ ├── minidump_writer_util.cc │ ├── minidump_writer_util.h │ └── test/ │ ├── minidump_byte_array_writer_test_util.cc │ ├── minidump_byte_array_writer_test_util.h │ ├── minidump_context_test_util.cc │ ├── minidump_context_test_util.h │ ├── minidump_file_writer_test_util.cc │ ├── minidump_file_writer_test_util.h │ ├── minidump_memory_writer_test_util.cc │ ├── minidump_memory_writer_test_util.h │ ├── minidump_rva_list_test_util.cc │ ├── minidump_rva_list_test_util.h │ ├── minidump_string_writer_test_util.cc │ ├── minidump_string_writer_test_util.h │ ├── minidump_user_extension_stream_util.cc │ ├── minidump_user_extension_stream_util.h │ ├── minidump_writable_test_util.cc │ └── minidump_writable_test_util.h ├── navbar.md ├── package.h ├── snapshot/ │ ├── BUILD.gn │ ├── annotation_snapshot.cc │ ├── annotation_snapshot.h │ ├── capture_memory.cc │ ├── capture_memory.h │ ├── cpu_architecture.h │ ├── cpu_context.cc │ ├── cpu_context.h │ ├── cpu_context_test.cc │ ├── crashpad_info_client_options.cc │ ├── crashpad_info_client_options.h │ ├── crashpad_info_client_options_test.cc │ ├── crashpad_info_client_options_test_module.cc │ ├── crashpad_info_size_test_module.cc │ ├── crashpad_info_size_test_note.S │ ├── crashpad_types/ │ │ ├── crashpad_info_reader.cc │ │ ├── crashpad_info_reader.h │ │ ├── crashpad_info_reader_test.cc │ │ ├── image_annotation_reader.cc │ │ ├── image_annotation_reader.h │ │ └── image_annotation_reader_test.cc │ ├── elf/ │ │ ├── elf_dynamic_array_reader.cc │ │ ├── elf_dynamic_array_reader.h │ │ ├── elf_image_reader.cc │ │ ├── elf_image_reader.h │ │ ├── elf_image_reader_fuzzer.cc │ │ ├── elf_image_reader_fuzzer_corpus/ │ │ │ ├── .gitattributes │ │ │ └── ret42 │ │ ├── elf_image_reader_test.cc │ │ ├── elf_image_reader_test_note.S │ │ ├── elf_symbol_table_reader.cc │ │ ├── elf_symbol_table_reader.h │ │ ├── module_snapshot_elf.cc │ │ ├── module_snapshot_elf.h │ │ └── test_exported_symbols.sym │ ├── exception_snapshot.h │ ├── fuchsia/ │ │ ├── cpu_context_fuchsia.cc │ │ ├── cpu_context_fuchsia.h │ │ ├── exception_snapshot_fuchsia.cc │ │ ├── exception_snapshot_fuchsia.h │ │ ├── memory_map_fuchsia.cc │ │ ├── memory_map_fuchsia.h │ │ ├── memory_map_region_snapshot_fuchsia.cc │ │ ├── memory_map_region_snapshot_fuchsia.h │ │ ├── process_reader_fuchsia.cc │ │ ├── process_reader_fuchsia.h │ │ ├── process_reader_fuchsia_test.cc │ │ ├── process_snapshot_fuchsia.cc │ │ ├── process_snapshot_fuchsia.h │ │ ├── process_snapshot_fuchsia_test.cc │ │ ├── system_snapshot_fuchsia.cc │ │ ├── system_snapshot_fuchsia.h │ │ ├── thread_snapshot_fuchsia.cc │ │ └── thread_snapshot_fuchsia.h │ ├── handle_snapshot.cc │ ├── handle_snapshot.h │ ├── hash_types_test.cc │ ├── ios/ │ │ ├── exception_snapshot_ios_intermediate_dump.cc │ │ ├── exception_snapshot_ios_intermediate_dump.h │ │ ├── intermediate_dump_reader_util.cc │ │ ├── intermediate_dump_reader_util.h │ │ ├── memory_snapshot_ios_intermediate_dump.cc │ │ ├── memory_snapshot_ios_intermediate_dump.h │ │ ├── memory_snapshot_ios_intermediate_dump_test.cc │ │ ├── module_snapshot_ios_intermediate_dump.cc │ │ ├── module_snapshot_ios_intermediate_dump.h │ │ ├── process_snapshot_ios_intermediate_dump.cc │ │ ├── process_snapshot_ios_intermediate_dump.h │ │ ├── process_snapshot_ios_intermediate_dump_test.cc │ │ ├── system_snapshot_ios_intermediate_dump.cc │ │ ├── system_snapshot_ios_intermediate_dump.h │ │ ├── testdata/ │ │ │ ├── crash-1fa088dda0adb41459d063078a0f384a0bb8eefa │ │ │ ├── crash-5726011582644224 │ │ │ ├── crash-6605504629637120 │ │ │ └── crash-c44acfcbccd8c7a8 │ │ ├── thread_snapshot_ios_intermediate_dump.cc │ │ └── thread_snapshot_ios_intermediate_dump.h │ ├── linux/ │ │ ├── capture_memory_delegate_linux.cc │ │ ├── capture_memory_delegate_linux.h │ │ ├── cpu_context_linux.cc │ │ ├── cpu_context_linux.h │ │ ├── debug_rendezvous.cc │ │ ├── debug_rendezvous.h │ │ ├── debug_rendezvous_test.cc │ │ ├── exception_snapshot_linux.cc │ │ ├── exception_snapshot_linux.h │ │ ├── exception_snapshot_linux_test.cc │ │ ├── process_reader_linux.cc │ │ ├── process_reader_linux.h │ │ ├── process_reader_linux_test.cc │ │ ├── process_snapshot_linux.cc │ │ ├── process_snapshot_linux.h │ │ ├── signal_context.h │ │ ├── system_snapshot_linux.cc │ │ ├── system_snapshot_linux.h │ │ ├── system_snapshot_linux_test.cc │ │ ├── test_modules.cc │ │ ├── test_modules.h │ │ ├── thread_snapshot_linux.cc │ │ └── thread_snapshot_linux.h │ ├── mac/ │ │ ├── cpu_context_mac.cc │ │ ├── cpu_context_mac.h │ │ ├── cpu_context_mac_test.cc │ │ ├── exception_snapshot_mac.cc │ │ ├── exception_snapshot_mac.h │ │ ├── mach_o_image_annotations_reader.cc │ │ ├── mach_o_image_annotations_reader.h │ │ ├── mach_o_image_annotations_reader_test.cc │ │ ├── mach_o_image_annotations_reader_test_module_crashy_initializer.cc │ │ ├── mach_o_image_annotations_reader_test_no_op.cc │ │ ├── mach_o_image_reader.cc │ │ ├── mach_o_image_reader.h │ │ ├── mach_o_image_reader_test.cc │ │ ├── mach_o_image_segment_reader.cc │ │ ├── mach_o_image_segment_reader.h │ │ ├── mach_o_image_segment_reader_test.cc │ │ ├── mach_o_image_symbol_table_reader.cc │ │ ├── mach_o_image_symbol_table_reader.h │ │ ├── module_snapshot_mac.cc │ │ ├── module_snapshot_mac.h │ │ ├── process_reader_mac.cc │ │ ├── process_reader_mac.h │ │ ├── process_reader_mac_test.cc │ │ ├── process_snapshot_mac.cc │ │ ├── process_snapshot_mac.h │ │ ├── process_types/ │ │ │ ├── all.proctype │ │ │ ├── annotation.proctype │ │ │ ├── crashpad_info.proctype │ │ │ ├── crashreporterclient.proctype │ │ │ ├── custom.cc │ │ │ ├── dyld_images.proctype │ │ │ ├── flavors.h │ │ │ ├── internal.h │ │ │ ├── loader.proctype │ │ │ ├── nlist.proctype │ │ │ └── traits.h │ │ ├── process_types.cc │ │ ├── process_types.h │ │ ├── process_types_test.cc │ │ ├── system_snapshot_mac.cc │ │ ├── system_snapshot_mac.h │ │ ├── system_snapshot_mac_test.cc │ │ ├── thread_snapshot_mac.cc │ │ └── thread_snapshot_mac.h │ ├── memory_map_region_snapshot.h │ ├── memory_snapshot.cc │ ├── memory_snapshot.h │ ├── memory_snapshot_generic.h │ ├── memory_snapshot_test.cc │ ├── minidump/ │ │ ├── exception_snapshot_minidump.cc │ │ ├── exception_snapshot_minidump.h │ │ ├── memory_snapshot_minidump.cc │ │ ├── memory_snapshot_minidump.h │ │ ├── minidump_annotation_reader.cc │ │ ├── minidump_annotation_reader.h │ │ ├── minidump_context_converter.cc │ │ ├── minidump_context_converter.h │ │ ├── minidump_simple_string_dictionary_reader.cc │ │ ├── minidump_simple_string_dictionary_reader.h │ │ ├── minidump_stream.h │ │ ├── minidump_string_list_reader.cc │ │ ├── minidump_string_list_reader.h │ │ ├── minidump_string_reader.cc │ │ ├── minidump_string_reader.h │ │ ├── module_snapshot_minidump.cc │ │ ├── module_snapshot_minidump.h │ │ ├── process_snapshot_minidump.cc │ │ ├── process_snapshot_minidump.h │ │ ├── process_snapshot_minidump_test.cc │ │ ├── system_snapshot_minidump.cc │ │ ├── system_snapshot_minidump.h │ │ ├── thread_snapshot_minidump.cc │ │ └── thread_snapshot_minidump.h │ ├── module_snapshot.h │ ├── posix/ │ │ ├── timezone.cc │ │ ├── timezone.h │ │ └── timezone_test.cc │ ├── process_snapshot.h │ ├── sanitized/ │ │ ├── memory_snapshot_sanitized.cc │ │ ├── memory_snapshot_sanitized.h │ │ ├── module_snapshot_sanitized.cc │ │ ├── module_snapshot_sanitized.h │ │ ├── process_snapshot_sanitized.cc │ │ ├── process_snapshot_sanitized.h │ │ ├── process_snapshot_sanitized_test.cc │ │ ├── sanitization_information.cc │ │ ├── sanitization_information.h │ │ ├── sanitization_information_test.cc │ │ ├── thread_snapshot_sanitized.cc │ │ └── thread_snapshot_sanitized.h │ ├── snapshot_constants.h │ ├── system_snapshot.h │ ├── test/ │ │ ├── test_cpu_context.cc │ │ ├── test_cpu_context.h │ │ ├── test_exception_snapshot.cc │ │ ├── test_exception_snapshot.h │ │ ├── test_memory_map_region_snapshot.cc │ │ ├── test_memory_map_region_snapshot.h │ │ ├── test_memory_snapshot.cc │ │ ├── test_memory_snapshot.h │ │ ├── test_module_snapshot.cc │ │ ├── test_module_snapshot.h │ │ ├── test_process_snapshot.cc │ │ ├── test_process_snapshot.h │ │ ├── test_system_snapshot.cc │ │ ├── test_system_snapshot.h │ │ ├── test_thread_snapshot.cc │ │ └── test_thread_snapshot.h │ ├── thread_snapshot.h │ ├── unloaded_module_snapshot.cc │ ├── unloaded_module_snapshot.h │ ├── win/ │ │ ├── capture_memory_delegate_win.cc │ │ ├── capture_memory_delegate_win.h │ │ ├── cpu_context_win.cc │ │ ├── cpu_context_win.h │ │ ├── cpu_context_win_test.cc │ │ ├── crashpad_snapshot_test_annotations.cc │ │ ├── crashpad_snapshot_test_crashing_child.cc │ │ ├── crashpad_snapshot_test_dump_without_crashing.cc │ │ ├── crashpad_snapshot_test_extra_memory_ranges.cc │ │ ├── crashpad_snapshot_test_image_reader.cc │ │ ├── crashpad_snapshot_test_image_reader_module.cc │ │ ├── end_to_end_test.py │ │ ├── exception_snapshot_win.cc │ │ ├── exception_snapshot_win.h │ │ ├── exception_snapshot_win_test.cc │ │ ├── extra_memory_ranges_test.cc │ │ ├── memory_map_region_snapshot_win.cc │ │ ├── memory_map_region_snapshot_win.h │ │ ├── module_snapshot_win.cc │ │ ├── module_snapshot_win.h │ │ ├── module_snapshot_win_test.cc │ │ ├── pe_image_annotations_reader.cc │ │ ├── pe_image_annotations_reader.h │ │ ├── pe_image_reader.cc │ │ ├── pe_image_reader.h │ │ ├── pe_image_reader_test.cc │ │ ├── pe_image_resource_reader.cc │ │ ├── pe_image_resource_reader.h │ │ ├── process_reader_win.cc │ │ ├── process_reader_win.h │ │ ├── process_reader_win_test.cc │ │ ├── process_snapshot_win.cc │ │ ├── process_snapshot_win.h │ │ ├── process_snapshot_win_test.cc │ │ ├── process_subrange_reader.cc │ │ ├── process_subrange_reader.h │ │ ├── system_snapshot_win.cc │ │ ├── system_snapshot_win.h │ │ ├── system_snapshot_win_test.cc │ │ ├── thread_snapshot_win.cc │ │ └── thread_snapshot_win.h │ └── x86/ │ ├── cpuid_reader.cc │ └── cpuid_reader.h ├── test/ │ ├── BUILD.gn │ ├── errors.cc │ ├── errors.h │ ├── file.cc │ ├── file.h │ ├── filesystem.cc │ ├── filesystem.h │ ├── fuchsia_crashpad_tests.cml │ ├── gtest_death.h │ ├── gtest_main.cc │ ├── hex_string.cc │ ├── hex_string.h │ ├── hex_string_test.cc │ ├── ios/ │ │ ├── BUILD.gn │ │ ├── cptest_google_test_runner.mm │ │ ├── cptest_google_test_runner_delegate.h │ │ ├── crash_type_xctest.mm │ │ ├── google_test_setup.h │ │ ├── google_test_setup.mm │ │ └── host/ │ │ ├── BUILD.gn │ │ ├── Info.plist │ │ ├── cptest_application_delegate.h │ │ ├── cptest_application_delegate.mm │ │ ├── cptest_crash_view_controller.h │ │ ├── cptest_crash_view_controller.mm │ │ ├── cptest_shared_object.h │ │ ├── handler_forbidden_allocators.cc │ │ ├── handler_forbidden_allocators.h │ │ └── main.mm │ ├── linux/ │ │ ├── fake_ptrace_connection.cc │ │ ├── fake_ptrace_connection.h │ │ ├── get_tls.cc │ │ └── get_tls.h │ ├── mac/ │ │ ├── dyld.cc │ │ ├── dyld.h │ │ ├── exception_swallower.cc │ │ ├── exception_swallower.h │ │ ├── mach_errors.cc │ │ ├── mach_errors.h │ │ ├── mach_multiprocess.cc │ │ ├── mach_multiprocess.h │ │ └── mach_multiprocess_test.cc │ ├── main_arguments.cc │ ├── main_arguments.h │ ├── main_arguments_test.cc │ ├── multiprocess.h │ ├── multiprocess_exec.cc │ ├── multiprocess_exec.h │ ├── multiprocess_exec_fuchsia.cc │ ├── multiprocess_exec_posix.cc │ ├── multiprocess_exec_test.cc │ ├── multiprocess_exec_test_child.cc │ ├── multiprocess_exec_win.cc │ ├── multiprocess_posix.cc │ ├── multiprocess_posix_test.cc │ ├── process_type.cc │ ├── process_type.h │ ├── scoped_guarded_page.h │ ├── scoped_guarded_page_posix.cc │ ├── scoped_guarded_page_test.cc │ ├── scoped_guarded_page_win.cc │ ├── scoped_module_handle.cc │ ├── scoped_module_handle.h │ ├── scoped_set_thread_name.h │ ├── scoped_set_thread_name_fuchsia.cc │ ├── scoped_set_thread_name_posix.cc │ ├── scoped_set_thread_name_win.cc │ ├── scoped_temp_dir.cc │ ├── scoped_temp_dir.h │ ├── scoped_temp_dir_posix.cc │ ├── scoped_temp_dir_test.cc │ ├── scoped_temp_dir_win.cc │ ├── test_paths.cc │ ├── test_paths.h │ ├── test_paths_test.cc │ ├── test_paths_test_data_root.txt │ └── win/ │ ├── child_launcher.cc │ ├── child_launcher.h │ ├── win_child_process.cc │ ├── win_child_process.h │ ├── win_child_process_test.cc │ ├── win_multiprocess.cc │ ├── win_multiprocess.h │ ├── win_multiprocess_test.cc │ ├── win_multiprocess_with_temp_dir.cc │ └── win_multiprocess_with_temp_dir.h ├── third_party/ │ ├── cpp-httplib/ │ │ ├── BUILD.gn │ │ ├── README.crashpad │ │ └── cpp-httplib/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── httplib.h │ ├── edo/ │ │ ├── BUILD.gn │ │ └── README.crashpad │ ├── fuchsia/ │ │ ├── BUILD.gn │ │ ├── README.crashpad │ │ └── runner.py │ ├── getopt/ │ │ ├── BUILD.gn │ │ ├── LICENSE │ │ ├── README.crashpad │ │ ├── getopt.cc │ │ └── getopt.h │ ├── googletest/ │ │ ├── BUILD.gn │ │ └── README.crashpad │ ├── linux/ │ │ └── README.md │ ├── lss/ │ │ ├── BUILD.gn │ │ ├── README.crashpad │ │ └── lss.h │ ├── mini_chromium/ │ │ ├── BUILD.gn │ │ └── README.crashpad │ ├── ninja/ │ │ ├── README.crashpad │ │ └── ninja │ ├── xnu/ │ │ ├── APPLE_LICENSE │ │ ├── BUILD.gn │ │ ├── EXTERNAL_HEADERS/ │ │ │ └── mach-o/ │ │ │ └── loader.h │ │ ├── README.crashpad │ │ └── osfmk/ │ │ └── mach/ │ │ ├── exc.defs │ │ ├── mach_exc.defs │ │ ├── mach_types.defs │ │ ├── machine/ │ │ │ └── machine_types.defs │ │ └── std_types.defs │ └── zlib/ │ ├── BUILD.gn │ ├── README.crashpad │ └── zlib_crashpad.h ├── tools/ │ ├── BUILD.gn │ ├── base94_encoder.cc │ ├── base94_encoder.md │ ├── crashpad_database_util.cc │ ├── crashpad_database_util.md │ ├── crashpad_http_upload.cc │ ├── crashpad_http_upload.md │ ├── dump_minidump_annotations.cc │ ├── generate_dump.cc │ ├── generate_dump.md │ ├── mac/ │ │ ├── catch_exception_tool.cc │ │ ├── catch_exception_tool.md │ │ ├── exception_port_tool.cc │ │ ├── exception_port_tool.md │ │ ├── on_demand_service_tool.md │ │ ├── on_demand_service_tool.mm │ │ └── sectaskaccess_info.plist │ ├── run_with_crashpad.cc │ ├── run_with_crashpad.md │ ├── tool_support.cc │ └── tool_support.h └── util/ ├── BUILD.gn ├── file/ │ ├── delimited_file_reader.cc │ ├── delimited_file_reader.h │ ├── delimited_file_reader_test.cc │ ├── directory_reader.h │ ├── directory_reader_posix.cc │ ├── directory_reader_test.cc │ ├── directory_reader_win.cc │ ├── file_helper.cc │ ├── file_helper.h │ ├── file_io.cc │ ├── file_io.h │ ├── file_io_posix.cc │ ├── file_io_test.cc │ ├── file_io_win.cc │ ├── file_reader.cc │ ├── file_reader.h │ ├── file_reader_test.cc │ ├── file_seeker.cc │ ├── file_seeker.h │ ├── file_writer.cc │ ├── file_writer.h │ ├── filesystem.h │ ├── filesystem_posix.cc │ ├── filesystem_test.cc │ ├── filesystem_win.cc │ ├── output_stream_file_writer.cc │ ├── output_stream_file_writer.h │ ├── scoped_remove_file.cc │ ├── scoped_remove_file.h │ ├── string_file.cc │ ├── string_file.h │ └── string_file_test.cc ├── fuchsia/ │ ├── koid_utilities.cc │ ├── koid_utilities.h │ ├── scoped_task_suspend.cc │ ├── scoped_task_suspend.h │ └── traits.h ├── ios/ │ ├── ios_intermediate_dump_data.cc │ ├── ios_intermediate_dump_data.h │ ├── ios_intermediate_dump_format.h │ ├── ios_intermediate_dump_interface.cc │ ├── ios_intermediate_dump_interface.h │ ├── ios_intermediate_dump_list.cc │ ├── ios_intermediate_dump_list.h │ ├── ios_intermediate_dump_map.cc │ ├── ios_intermediate_dump_map.h │ ├── ios_intermediate_dump_object.cc │ ├── ios_intermediate_dump_object.h │ ├── ios_intermediate_dump_reader.cc │ ├── ios_intermediate_dump_reader.h │ ├── ios_intermediate_dump_reader_test.cc │ ├── ios_intermediate_dump_writer.cc │ ├── ios_intermediate_dump_writer.h │ ├── ios_intermediate_dump_writer_test.cc │ ├── ios_system_data_collector.h │ ├── ios_system_data_collector.mm │ ├── raw_logging.cc │ ├── raw_logging.h │ ├── scoped_background_task.h │ ├── scoped_background_task.mm │ ├── scoped_vm_map.cc │ ├── scoped_vm_map.h │ ├── scoped_vm_map_test.cc │ ├── scoped_vm_read.cc │ ├── scoped_vm_read.h │ └── scoped_vm_read_test.cc ├── linux/ │ ├── address_types.h │ ├── auxiliary_vector.cc │ ├── auxiliary_vector.h │ ├── auxiliary_vector_test.cc │ ├── checked_linux_address_range.h │ ├── direct_ptrace_connection.cc │ ├── direct_ptrace_connection.h │ ├── exception_handler_client.cc │ ├── exception_handler_client.h │ ├── exception_handler_protocol.cc │ ├── exception_handler_protocol.h │ ├── exception_information.h │ ├── initial_signal_dispositions.cc │ ├── initial_signal_dispositions.h │ ├── memory_map.cc │ ├── memory_map.h │ ├── memory_map_test.cc │ ├── pac_helper.cc │ ├── pac_helper.h │ ├── proc_stat_reader.cc │ ├── proc_stat_reader.h │ ├── proc_stat_reader_test.cc │ ├── proc_task_reader.cc │ ├── proc_task_reader.h │ ├── proc_task_reader_test.cc │ ├── ptrace_broker.cc │ ├── ptrace_broker.h │ ├── ptrace_broker_test.cc │ ├── ptrace_client.cc │ ├── ptrace_client.h │ ├── ptrace_connection.h │ ├── ptracer.cc │ ├── ptracer.h │ ├── ptracer_test.cc │ ├── scoped_pr_set_dumpable.cc │ ├── scoped_pr_set_dumpable.h │ ├── scoped_pr_set_ptracer.cc │ ├── scoped_pr_set_ptracer.h │ ├── scoped_ptrace_attach.cc │ ├── scoped_ptrace_attach.h │ ├── scoped_ptrace_attach_test.cc │ ├── socket.cc │ ├── socket.h │ ├── socket_test.cc │ ├── thread_info.cc │ ├── thread_info.h │ └── traits.h ├── mac/ │ ├── checked_mach_address_range.h │ ├── checked_mach_address_range_test.cc │ ├── launchd.h │ ├── launchd.mm │ ├── launchd_test.mm │ ├── mac_util.cc │ ├── mac_util.h │ ├── mac_util_test.mm │ ├── service_management.cc │ ├── service_management.h │ ├── service_management_test.mm │ ├── sysctl.cc │ ├── sysctl.h │ ├── sysctl_test.cc │ ├── xattr.cc │ ├── xattr.h │ └── xattr_test.cc ├── mach/ │ ├── bootstrap.cc │ ├── bootstrap.h │ ├── bootstrap_test.cc │ ├── child_port.defs │ ├── child_port_handshake.cc │ ├── child_port_handshake.h │ ├── child_port_handshake_test.cc │ ├── child_port_server.cc │ ├── child_port_server.h │ ├── child_port_server_test.cc │ ├── child_port_types.h │ ├── composite_mach_message_server.cc │ ├── composite_mach_message_server.h │ ├── composite_mach_message_server_test.cc │ ├── exc_client_variants.cc │ ├── exc_client_variants.h │ ├── exc_client_variants_test.cc │ ├── exc_server_variants.cc │ ├── exc_server_variants.h │ ├── exc_server_variants_test.cc │ ├── exception_behaviors.cc │ ├── exception_behaviors.h │ ├── exception_behaviors_test.cc │ ├── exception_ports.cc │ ├── exception_ports.h │ ├── exception_ports_test.cc │ ├── exception_types.cc │ ├── exception_types.h │ ├── exception_types_test.cc │ ├── mach_extensions.cc │ ├── mach_extensions.h │ ├── mach_extensions_test.cc │ ├── mach_message.cc │ ├── mach_message.h │ ├── mach_message_server.cc │ ├── mach_message_server.h │ ├── mach_message_server_test.cc │ ├── mach_message_test.cc │ ├── mig.py │ ├── mig_fix.py │ ├── mig_gen.py │ ├── notify_server.cc │ ├── notify_server.h │ ├── notify_server_test.cc │ ├── scoped_task_suspend.cc │ ├── scoped_task_suspend.h │ ├── scoped_task_suspend_test.cc │ ├── symbolic_constants_mach.cc │ ├── symbolic_constants_mach.h │ ├── symbolic_constants_mach_test.cc │ ├── task_for_pid.cc │ └── task_for_pid.h ├── misc/ │ ├── address_sanitizer.h │ ├── address_types.h │ ├── arm64_pac_bti.S │ ├── arraysize.h │ ├── arraysize_test.cc │ ├── as_underlying_type.h │ ├── capture_context.h │ ├── capture_context_linux.S │ ├── capture_context_mac.S │ ├── capture_context_test.cc │ ├── capture_context_test_util.h │ ├── capture_context_test_util_linux.cc │ ├── capture_context_test_util_mac.cc │ ├── capture_context_test_util_win.cc │ ├── capture_context_win.asm │ ├── capture_context_win_arm64.asm │ ├── capture_context_win_arm64.obj │ ├── clock.h │ ├── clock_mac.cc │ ├── clock_posix.cc │ ├── clock_test.cc │ ├── clock_win.cc │ ├── elf_note_types.h │ ├── from_pointer_cast.h │ ├── from_pointer_cast_test.cc │ ├── implicit_cast.h │ ├── initialization_state.h │ ├── initialization_state_dcheck.cc │ ├── initialization_state_dcheck.h │ ├── initialization_state_dcheck_test.cc │ ├── initialization_state_test.cc │ ├── lexing.cc │ ├── lexing.h │ ├── memory_sanitizer.h │ ├── metrics.cc │ ├── metrics.h │ ├── no_cfi_icall.h │ ├── no_cfi_icall_test.cc │ ├── paths.h │ ├── paths_fuchsia.cc │ ├── paths_linux.cc │ ├── paths_mac.cc │ ├── paths_test.cc │ ├── paths_win.cc │ ├── pdb_structures.cc │ ├── pdb_structures.h │ ├── random_string.cc │ ├── random_string.h │ ├── random_string_test.cc │ ├── range_set.cc │ ├── range_set.h │ ├── range_set_test.cc │ ├── reinterpret_bytes.cc │ ├── reinterpret_bytes.h │ ├── reinterpret_bytes_test.cc │ ├── scoped_forbid_return.cc │ ├── scoped_forbid_return.h │ ├── scoped_forbid_return_test.cc │ ├── symbolic_constants_common.h │ ├── time.cc │ ├── time.h │ ├── time_linux.cc │ ├── time_test.cc │ ├── time_win.cc │ ├── tri_state.h │ ├── uuid.cc │ ├── uuid.h │ ├── uuid_test.cc │ ├── zlib.cc │ └── zlib.h ├── net/ │ ├── generate_test_server_key.py │ ├── http_body.cc │ ├── http_body.h │ ├── http_body_gzip.cc │ ├── http_body_gzip.h │ ├── http_body_gzip_test.cc │ ├── http_body_test.cc │ ├── http_body_test_util.cc │ ├── http_body_test_util.h │ ├── http_headers.h │ ├── http_multipart_builder.cc │ ├── http_multipart_builder.h │ ├── http_multipart_builder_test.cc │ ├── http_transport.cc │ ├── http_transport.h │ ├── http_transport_libcurl.cc │ ├── http_transport_mac.mm │ ├── http_transport_socket.cc │ ├── http_transport_test.cc │ ├── http_transport_test_server.cc │ ├── http_transport_win.cc │ ├── testdata/ │ │ ├── ascii_http_body.txt │ │ ├── crashpad_util_test_cert.pem │ │ └── crashpad_util_test_key.pem │ ├── tls.gni │ ├── url.cc │ ├── url.h │ └── url_test.cc ├── numeric/ │ ├── checked_address_range.cc │ ├── checked_address_range.h │ ├── checked_address_range_test.cc │ ├── checked_range.h │ ├── checked_range_test.cc │ ├── checked_vm_address_range.h │ ├── in_range_cast.h │ ├── in_range_cast_test.cc │ ├── int128.h │ ├── int128_test.cc │ └── safe_assignment.h ├── posix/ │ ├── close_multiple.cc │ ├── close_multiple.h │ ├── close_stdio.cc │ ├── close_stdio.h │ ├── drop_privileges.cc │ ├── drop_privileges.h │ ├── process_info.h │ ├── process_info_linux.cc │ ├── process_info_mac.cc │ ├── process_info_test.cc │ ├── scoped_dir.cc │ ├── scoped_dir.h │ ├── scoped_mmap.cc │ ├── scoped_mmap.h │ ├── scoped_mmap_test.cc │ ├── signals.cc │ ├── signals.h │ ├── signals_test.cc │ ├── spawn_subprocess.cc │ ├── spawn_subprocess.h │ ├── symbolic_constants_posix.cc │ ├── symbolic_constants_posix.h │ └── symbolic_constants_posix_test.cc ├── process/ │ ├── process_id.h │ ├── process_memory.cc │ ├── process_memory.h │ ├── process_memory_fuchsia.cc │ ├── process_memory_fuchsia.h │ ├── process_memory_linux.cc │ ├── process_memory_linux.h │ ├── process_memory_mac.cc │ ├── process_memory_mac.h │ ├── process_memory_mac_test.cc │ ├── process_memory_native.h │ ├── process_memory_range.cc │ ├── process_memory_range.h │ ├── process_memory_range_test.cc │ ├── process_memory_sanitized.cc │ ├── process_memory_sanitized.h │ ├── process_memory_sanitized_test.cc │ ├── process_memory_test.cc │ ├── process_memory_win.cc │ └── process_memory_win.h ├── stdlib/ │ ├── aligned_allocator.cc │ ├── aligned_allocator.h │ ├── aligned_allocator_test.cc │ ├── map_insert.h │ ├── map_insert_test.cc │ ├── objc.h │ ├── string_number_conversion.cc │ ├── string_number_conversion.h │ ├── string_number_conversion_test.cc │ ├── strlcpy.cc │ ├── strlcpy.h │ ├── strlcpy_test.cc │ ├── strnlen.cc │ ├── strnlen.h │ ├── strnlen_test.cc │ ├── thread_safe_vector.h │ └── thread_safe_vector_test.cc ├── stream/ │ ├── base94_output_stream.cc │ ├── base94_output_stream.h │ ├── base94_output_stream_test.cc │ ├── file_encoder.cc │ ├── file_encoder.h │ ├── file_encoder_test.cc │ ├── file_output_stream.cc │ ├── file_output_stream.h │ ├── log_output_stream.cc │ ├── log_output_stream.h │ ├── log_output_stream_test.cc │ ├── output_stream_interface.h │ ├── test_output_stream.cc │ ├── test_output_stream.h │ ├── zlib_output_stream.cc │ ├── zlib_output_stream.h │ └── zlib_output_stream_test.cc ├── string/ │ ├── split_string.cc │ ├── split_string.h │ └── split_string_test.cc ├── synchronization/ │ ├── scoped_spin_guard.h │ ├── scoped_spin_guard_test.cc │ ├── semaphore.h │ ├── semaphore_mac.cc │ ├── semaphore_posix.cc │ ├── semaphore_test.cc │ └── semaphore_win.cc ├── thread/ │ ├── stoppable.h │ ├── thread.cc │ ├── thread.h │ ├── thread_log_messages.cc │ ├── thread_log_messages.h │ ├── thread_log_messages_test.cc │ ├── thread_posix.cc │ ├── thread_test.cc │ ├── thread_win.cc │ ├── worker_thread.cc │ ├── worker_thread.h │ └── worker_thread_test.cc └── win/ ├── address_types.h ├── checked_win_address_range.h ├── command_line.cc ├── command_line.h ├── command_line_test.cc ├── context_wrappers.h ├── critical_section_with_debug_info.cc ├── critical_section_with_debug_info.h ├── critical_section_with_debug_info_test.cc ├── exception_codes.h ├── exception_handler_server.cc ├── exception_handler_server.h ├── exception_handler_server_test.cc ├── get_function.cc ├── get_function.h ├── get_function_test.cc ├── get_module_information.cc ├── get_module_information.h ├── handle.cc ├── handle.h ├── handle_test.cc ├── initial_client_data.cc ├── initial_client_data.h ├── initial_client_data_test.cc ├── loader_lock.cc ├── loader_lock.h ├── loader_lock_test.cc ├── loader_lock_test_dll.cc ├── module_version.cc ├── module_version.h ├── nt_internals.cc ├── nt_internals.h ├── ntstatus_logging.cc ├── ntstatus_logging.h ├── process_info.cc ├── process_info.h ├── process_info_test.cc ├── process_info_test_child.cc ├── process_structs.h ├── registration_protocol_win.cc ├── registration_protocol_win.h ├── registration_protocol_win_structs.h ├── registration_protocol_win_test.cc ├── safe_terminate_process.asm ├── safe_terminate_process.h ├── safe_terminate_process_test.cc ├── safe_terminate_process_test_child.cc ├── scoped_handle.cc ├── scoped_handle.h ├── scoped_local_alloc.cc ├── scoped_local_alloc.h ├── scoped_process_suspend.cc ├── scoped_process_suspend.h ├── scoped_process_suspend_test.cc ├── scoped_registry_key.h ├── scoped_set_event.cc ├── scoped_set_event.h ├── session_end_watcher.cc ├── session_end_watcher.h ├── session_end_watcher_test.cc ├── termination_codes.h ├── traits.h └── xp_compat.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ # Copyright 2014 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. { BasedOnStyle: Chromium, AlignTrailingComments: false, BinPackArguments: false, } ================================================ FILE: .gitattributes ================================================ # Copyright 2019 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. *.S text eol=lf *.asm text eol=lf *.c text eol=lf *.cc text eol=lf *.cmx text eol=lf *.css text eol=lf *.defs text eol=lf *.doxy text eol=lf *.gn text eol=lf *.gni text eol=lf *.go text eol=lf *.h text eol=lf *.m text eol=lf *.md text eol=lf *.mm text eol=lf *.pem text eol=lf *.plist text eol=lf *.proctype text eol=lf *.py text eol=lf *.sh text eol=lf *.sym text eol=lf *.txt text eol=lf *.yaml text eol=lf .clang-format text eol=lf .gitattributes text eol=lf .gitignore text eol=lf .vpython text eol=lf /AUTHORS text eol=lf /CONTRIBUTORS text eol=lf /LICENSE text eol=lf /codereview.settings text eol=lf APPLE_LICENSE text eol=lf COPYING.LIB text eol=lf DEPS text eol=lf README text eol=lf README.crashpad text eol=lf *.dat binary *.dll binary *.ico binary *.obj binary *.png binary *.so binary ================================================ FILE: .gitignore ================================================ # Copyright 2014 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Keep sorted *.Makefile *.ninja *.pyc *.target.mk *.xcodeproj *~ .*.sw? .DS_Store .cache .gdb_history .gdbinit /.vscode/ /Makefile /build/fuchsia /out /third_party/edo/edo /third_party/fuchsia-gn-sdk /third_party/fuchsia/.cipd /third_party/fuchsia/clang /third_party/fuchsia/qemu /third_party/fuchsia/sdk /third_party/googletest/googletest /third_party/libfuzzer /third_party/linux/.cipd /third_party/linux/clang /third_party/linux/sysroot /third_party/lss/lss /third_party/mini_chromium/mini_chromium /third_party/ninja/linux /third_party/ninja/mac* /third_party/ninja/ninja.exe /third_party/windows/clang/win-amd64 /third_party/zlib/zlib /xcodebuild tags ================================================ FILE: .gn ================================================ # Copyright 2017 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. buildconfig = "//build/BUILDCONFIG.gn" script_executable = "python3" ================================================ FILE: .style.yapf ================================================ # Copyright 2020 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. [style] based_on_style = google ================================================ FILE: .vpython3 ================================================ # Copyright 2022 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This is a vpython "spec" file. # # It describes patterns for python wheel dependencies of the python scripts. # # Read more about `vpython` and how to modify this file here: # https://chromium.googlesource.com/infra/infra/+/master/doc/users/vpython.md # This is needed for snapshot/win/end_to_end_test.py. wheel: < name: "infra/python/wheels/pywin32/${vpython_platform}" version: "version:300" match_tag: < platform: "win32" > match_tag: < platform: "win_amd64" > > ================================================ FILE: AUTHORS ================================================ # This is the official list of Crashpad authors for copyright purposes. # This file is distinct from the CONTRIBUTORS files. # See the latter for an explanation. # Names should be added to this file as: # Name or Organization # The email address is not required for organizations. Google Inc. Intel Corporation Opera Software ASA Vewd Software AS LG Electronics, Inc. MIPS Technologies, Inc. Darshan Sen Ho Cheung ================================================ FILE: BUILD.gn ================================================ # Copyright 2017 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import("build/crashpad_buildconfig.gni") import("build/test.gni") import("util/net/tls.gni") config("crashpad_config") { include_dirs = [ ".", root_gen_dir, ] } if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { test("crashpad_tests") { deps = [ "client:client_test", "minidump:minidump_test", "snapshot:snapshot_test", "test:googlemock_main", "test:test_test", "util:util_test", ] data_deps = [] if (crashpad_is_in_chromium) { data_deps += [ "//testing/buildbot/filters:crashpad_tests_filters" ] } if (!crashpad_is_ios && !crashpad_is_fuchsia) { deps += [ "handler:handler_test" ] } if (crashpad_is_in_fuchsia) { # TODO(fuchsia:46559): Fix the leaks and remove this. deps += [ "//build/config/sanitizers:suppress-lsan.DO-NOT-USE-THIS" ] # TODO(fxbug.dev/42059784): Remove this once the underlying issue is # addressed. exclude_toolchain_tags = [ "hwasan" ] } if (crashpad_is_android) { use_raw_android_executable = true # crbug.com/418874703 - This is a workaround to propagate the data deps to # //:crashpad_tests__dist for Android build. data_deps += [ "snapshot:crashpad_snapshot_test_both_dt_hash_styles", "snapshot:crashpad_snapshot_test_module", "snapshot:crashpad_snapshot_test_module_large", "snapshot:crashpad_snapshot_test_module_small", "test:crashpad_test_test_multiprocess_exec_test_child", ] copy("crashpad_test_data") { testonly = true sources = [ "test/test_paths_test_data_root.txt", "util/net/testdata/ascii_http_body.txt", "util/net/testdata/binary_http_body.dat", ] outputs = [ "$root_out_dir/crashpad_test_data/{{source}}" ] } deps += [ ":crashpad_test_data" ] extra_dist_files = [ "$root_out_dir/crashpad_handler", "$root_out_dir/crashpad_test_test_multiprocess_exec_test_child", "$root_out_dir/crashpad_test_data", ] } } if (crashpad_is_in_fuchsia) { import("//build/components.gni") fuchsia_test_component("crashpad-test-component") { manifest = "test/fuchsia_crashpad_tests.cml" deps = [ ":crashpad-test-resources", ":crashpad_tests", "snapshot:crashpad_snapshot_test_both_dt_hash_styles", "snapshot:crashpad_snapshot_test_module", "snapshot:crashpad_snapshot_test_module_large", "snapshot:crashpad_snapshot_test_module_small", "test:crashpad_test_test_multiprocess_exec_test_child", ] } fuchsia_test_package("crashpad-test") { test_components = [ ":crashpad-test-component" ] deps = [ "//src/connectivity/network/dns:component", "//src/connectivity/network/netstack:component", ] test_specs = { log_settings = { max_severity = "FATAL" } } } _resource_files = [ "test/test_paths_test_data_root.txt", "util/net/testdata/ascii_http_body.txt", "util/net/testdata/binary_http_body.dat", ] if (crashpad_use_boringssl_for_http_transport_socket) { _resource_files += [ "util/net/testdata/crashpad_util_test_cert.pem", "util/net/testdata/crashpad_util_test_key.pem", ] } _resources = [] foreach(resource_file, _resource_files) { _resource_file_target = string_replace(resource_file, "/", "_") resource("${_resource_file_target}") { sources = [ "${resource_file}" ] outputs = [ "data/${resource_file}" ] } _resources += [ ":${_resource_file_target}" ] } group("crashpad-test-resources") { deps = _resources } group("tests") { testonly = true deps = [ ":crashpad-test" ] } } } else if (crashpad_is_standalone || crashpad_is_external) { test("crashpad_client_test") { deps = [ "client:client_test", "test:googlemock_main", ] } test("crashpad_handler_test") { deps = [ "handler:handler_test", "test:googletest_main", ] if (crashpad_is_ios || crashpad_is_fuchsia) { deps -= [ "handler:handler_test" ] } } test("crashpad_minidump_test") { deps = [ "minidump:minidump_test", "test:googletest_main", ] } test("crashpad_snapshot_test") { deps = [ "snapshot:snapshot_test", "test:googlemock_main", ] } test("crashpad_test_test") { deps = [ "test:googlemock_main", "test:test_test", ] } test("crashpad_util_test") { deps = [ "test:googlemock_main", "util:util_test", ] } } if (crashpad_is_ios) { group("ios_xcuitests") { testonly = true deps = [ "test/ios:all_tests" ] } } ================================================ FILE: CONTRIBUTORS ================================================ # People who have agreed to one of the CLAs and can contribute patches. # The AUTHORS file lists the copyright holders; this file # lists people. For example, Google employees are listed here # but not in AUTHORS, because Google holds the copyright. # # https://developers.google.com/open-source/cla/individual # https://developers.google.com/open-source/cla/corporate # # Names should be added to this file as: # Name Mark Mentovai Robert Sesek Scott Graham Joshua Peraza ================================================ FILE: DEPS ================================================ # Copyright 2014 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. vars = { 'chromium_git': 'https://chromium.googlesource.com', 'gn_version': 'git_revision:5e19d2fb166fbd4f6f32147fbb2f497091a54ad8', # ninja CIPD package version. # https://chrome-infra-packages.appspot.com/p/infra/3pp/tools/ninja 'ninja_version': 'version:2@1.8.2.chromium.3', 'pull_linux_clang': False, 'pull_win_toolchain': False, # Controls whether crashpad/build/ios/setup-ios-gn.py is run as part of # gclient hooks. It is enabled by default for developer's convenience. It can # be disabled with custom_vars (done automatically on the bots). 'run_setup_ios_gn': True, } deps = { 'buildtools': Var('chromium_git') + '/chromium/src/buildtools.git@' + 'efa920ce144e4dc1c1841e73179cd7e23b9f0d5e', 'buildtools/clang_format/script': Var('chromium_git') + '/external/github.com/llvm/llvm-project/clang/tools/clang-format.git@' + 'c912837e0d82b5ca4b6e790b573b3956d3744c1c', 'crashpad/third_party/edo/edo': { 'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git@' + '38e71ff183d76f702db6966fa7236c98831acd80', 'condition': 'checkout_ios', }, 'crashpad/third_party/googletest/googletest': Var('chromium_git') + '/external/github.com/google/googletest@' + '3983f67e32fb3e9294487b9d4f9586efa6e5d088', 'crashpad/third_party/lss/lss': Var('chromium_git') + '/linux-syscall-support.git@' + '9719c1e1e676814c456b55f5f070eabad6709d31', 'crashpad/third_party/mini_chromium/mini_chromium': Var('chromium_git') + '/chromium/mini_chromium@' + '706fce5b1a280a6f2eea69040f67847f9acb65ff', 'crashpad/third_party/libfuzzer/src': Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' + 'fda403cf93ecb8792cb1d061564d89a6553ca020', 'crashpad/third_party/zlib/zlib': Var('chromium_git') + '/chromium/src/third_party/zlib@' + 'fef58692c1d7bec94c4ed3d030a45a1832a9615d', # CIPD packages. 'buildtools/linux64': { 'packages': [ { 'package': 'gn/gn/linux-${{arch}}', 'version': Var('gn_version'), } ], 'dep_type': 'cipd', 'condition': 'host_os == "linux"', }, 'buildtools/mac': { 'packages': [ { 'package': 'gn/gn/mac-${{arch}}', 'version': Var('gn_version'), } ], 'dep_type': 'cipd', 'condition': 'host_os == "mac"', }, 'buildtools/win': { 'packages': [ { 'package': 'gn/gn/windows-amd64', 'version': Var('gn_version'), } ], 'dep_type': 'cipd', 'condition': 'host_os == "win"', }, 'crashpad/build/fuchsia': { 'packages': [ { 'package': 'chromium/fuchsia/test-scripts', 'version': 'latest', } ], 'condition': 'checkout_fuchsia', 'dep_type': 'cipd', }, 'crashpad/third_party/linux/clang/linux-amd64': { 'packages': [ { 'package': 'fuchsia/third_party/clang/linux-amd64', 'version': 'Tpc85d1ZwSlZ6UKl2d96GRUBGNA5JKholOKe24sRDr0C', }, ], 'condition': 'checkout_linux and pull_linux_clang', 'dep_type': 'cipd' }, 'crashpad/third_party/fuchsia/clang/mac-amd64': { 'packages': [ { 'package': 'fuchsia/third_party/clang/mac-amd64', 'version': 'integration', }, ], 'condition': 'checkout_fuchsia and host_os == "mac"', 'dep_type': 'cipd' }, 'crashpad/third_party/fuchsia/clang/linux-amd64': { 'packages': [ { 'package': 'fuchsia/third_party/clang/linux-amd64', 'version': 'integration', }, ], 'condition': 'checkout_fuchsia and host_os == "linux"', 'dep_type': 'cipd' }, 'crashpad/third_party/windows/clang/win-amd64': { 'bucket': 'chromium-browser-clang', 'objects': [ { 'object_name': 'Win/clang-llvmorg-20-init-17108-g29ed6000-2.tar.xz', 'sha256sum': '1c71efd923a91480480d4f31c2fd5f1369e01e14f15776a9454abbce0bc13548', 'size_bytes': 46357580, 'generation': 1737590897363452, }, ], 'condition': 'checkout_win and host_os == "win"', 'dep_type': 'gcs', }, 'crashpad/third_party/fuchsia-gn-sdk': { 'packages': [ { 'package': 'chromium/fuchsia/gn-sdk', 'version': 'latest' }, ], 'condition': 'checkout_fuchsia', 'dep_type': 'cipd' }, 'crashpad/third_party/fuchsia/sdk/linux-amd64': { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', 'version': 'latest' }, ], 'condition': 'checkout_fuchsia and host_os == "linux"', 'dep_type': 'cipd' }, # depot_tools/ninja wrapper calls third_party/ninja/{ninja, ninja.exe}. # crashpad/third_party/ninja/ninja is another wrapper to call linux ninja # or mac ninja. # This allows crashpad developers to work for multiple platforms on the same # machine. 'crashpad/third_party/ninja': { 'packages': [ { 'package': 'infra/3pp/tools/ninja/${{platform}}', 'version': Var('ninja_version'), } ], 'condition': 'host_os == "win"', 'dep_type': 'cipd', }, 'crashpad/third_party/ninja/linux': { 'packages': [ { 'package': 'infra/3pp/tools/ninja/${{platform}}', 'version': Var('ninja_version'), } ], 'condition': 'host_os == "linux"', 'dep_type': 'cipd', }, 'crashpad/third_party/ninja/mac-amd64': { 'packages': [ { 'package': 'infra/3pp/tools/ninja/mac-amd64', 'version': Var('ninja_version'), } ], 'condition': 'host_os == "mac" and host_cpu == "x64"', 'dep_type': 'cipd', }, 'crashpad/third_party/ninja/mac-arm64': { 'packages': [ { 'package': 'infra/3pp/tools/ninja/mac-arm64', 'version': Var('ninja_version'), } ], 'condition': 'host_os == "mac" and host_cpu == "arm64"', 'dep_type': 'cipd', }, 'crashpad/third_party/win/toolchain': { # This package is only updated when the solution in .gclient includes an # entry like: # "custom_vars": { "pull_win_toolchain": True } # This is because the contained bits are not redistributable. 'packages': [ { 'package': 'chrome_internal/third_party/sdk/windows', 'version': 'uploaded:2021-04-28' }, ], 'condition': 'checkout_win and pull_win_toolchain', 'dep_type': 'cipd' }, } hooks = [ { # If using a local clang ("pull_linux_clang" above), also pull down a # sysroot. 'name': 'sysroot_linux', 'pattern': '.', 'condition': 'checkout_linux and pull_linux_clang', 'action': [ 'crashpad/build/install_linux_sysroot.py', ], }, { # Avoid introducing unnecessary PRESUBMIT.py file from build/fuchsia. # Never fail and ignore the error if the file does not exist. 'name': 'Remove the PRESUBMIT.py from build/fuchsia', 'pattern': '.', 'condition': 'checkout_fuchsia', 'action': [ 'rm', '-f', 'crashpad/build/fuchsia/PRESUBMIT.py', ], }, { 'name': 'Generate Fuchsia Build Definitions', 'pattern': '.', 'condition': 'checkout_fuchsia', 'action': [ 'python3', 'crashpad/build/fuchsia_envs.py', 'crashpad/build/fuchsia/gen_build_defs.py' ], }, { 'name': 'setup_gn_ios', 'pattern': '.', 'condition': 'run_setup_ios_gn and checkout_ios', 'action': [ 'python3', 'crashpad/build/ios/setup_ios_gn.py' ], }, ] recursedeps = [ 'buildtools', ] ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: OWNERS ================================================ # Copyright 2025 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. jperaza@chromium.org justincohen@chromium.org lgrey@chromium.org mark@chromium.org pbos@chromium.org wfh@chromium.org ================================================ FILE: README.md ================================================ # Crashpad [Crashpad](https://crashpad.chromium.org/) is a crash-reporting system. ## Documentation * [Project status](doc/status.md) * [Developing Crashpad](doc/developing.md): instructions for getting the source code, building, testing, and contributing to the project. * [Crashpad interface documentation](https://crashpad.chromium.org/doxygen/) * [Crashpad tool man pages](doc/man.md) * [Crashpad overview design](doc/overview_design.md) ## Source Code Crashpad’s source code is hosted in a Git repository at https://chromium.googlesource.com/crashpad/crashpad. ## Other Links * Bugs can be reported at the [Crashpad issue tracker](https://crashpad.chromium.org/bug/). * The [Crashpad bots](https://ci.chromium.org/p/crashpad/g/main/console) perform automated builds and tests. * [crashpad-dev](https://groups.google.com/a/chromium.org/group/crashpad-dev) is the Crashpad developers’ mailing list. ================================================ FILE: build/BUILD.gn ================================================ # Copyright 2017 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # When building in Chromium, these configs is used to set #defines that indicate # whether code is being built standalone, or in Chromium, or potentially in some # other configutation. import("crashpad_buildconfig.gni") config("crashpad_is_in_chromium") { if (crashpad_is_in_chromium) { defines = [ "CRASHPAD_IS_IN_CHROMIUM" ] } } config("crashpad_is_in_fuchsia") { if (crashpad_is_in_fuchsia) { defines = [ "CRASHPAD_IS_IN_FUCHSIA" ] } } config("flock_always_supported_defines") { defines = [ "CRASHPAD_FLOCK_ALWAYS_SUPPORTED=$crashpad_flock_always_supported" ] } group("default_exe_manifest_win") { if (crashpad_is_in_chromium) { deps = [ "//build/win:default_exe_manifest" ] } } config("crashpad_fuzzer_flags") { cflags = [ "-fsanitize=address", "-fsanitize-address-use-after-scope", "-fsanitize=fuzzer", ] ldflags = [ "-fsanitize=address" ] } if (crashpad_is_apple) { group("apple_enable_arc") { # If `crashpad_is_in_chromium`, then because Chromium enables ARC # compilation by default, no special configuration is needed. if (crashpad_is_standalone) { public_configs = [ "//third_party/mini_chromium/mini_chromium/build/config:apple_enable_arc" ] } } } if (crashpad_is_ios) { group("ios_xctest") { if (crashpad_is_in_chromium) { public_configs = [ "//build/config/ios:xctest_config" ] } else if (crashpad_is_standalone) { public_configs = [ "//third_party/mini_chromium/mini_chromium/build/ios:xctest_config", ] } } if (crashpad_is_in_chromium) { import("//build/config/ios/ios_sdk.gni") crashpad_is_ios_app_extension = ios_is_app_extension } else { crashpad_is_ios_app_extension = false } config("crashpad_is_ios_app_extension") { if (crashpad_is_ios_app_extension) { defines = [ "CRASHPAD_IS_IOS_APP_EXTENSION" ] } } } ================================================ FILE: build/BUILDCONFIG.gn ================================================ # Copyright 2017 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Intentionally very minimal, so that Crashpad can build in-tree in a variety of # other projects, unrelated to the variables that are set in those projects' # BUILDCONFIG.gn. Do not add more variables here. Instead, make them available # in build/crashpad_buildconfig.gni if they must be globally available. if (target_os == "") { target_os = host_os } if (current_os == "") { current_os = target_os } if (target_cpu == "") { target_cpu = host_cpu } if (current_cpu == "") { current_cpu = target_cpu } import("//build/crashpad_buildconfig.gni") if (crashpad_is_standalone) { _mini_chromium_dir = "//third_party/mini_chromium/mini_chromium" } else if (crashpad_is_external) { _mini_chromium_dir = "//../../mini_chromium/mini_chromium" } if (current_os == "win") { set_default_toolchain( "$_mini_chromium_dir/build/config:msvc_toolchain_$current_cpu") } else { set_default_toolchain("$_mini_chromium_dir/build/config:gcc_like_toolchain") } declare_args() { # When true, enables the debug configuration, with additional run-time checks # and logging. When false, enables the release configuration, with additional # optimizations. is_debug = false # When true, build all code with -fsanitize=fuzzer, and enable various # *_fuzzer targets. crashpad_use_libfuzzer = false } _default_configs = [ "$_mini_chromium_dir/build/config:default", "$_mini_chromium_dir/build/config:Wexit_time_destructors", "$_mini_chromium_dir/build/config:Wimplicit_fallthrough", ] if (crashpad_use_libfuzzer) { _default_configs += [ "//build/config:crashpad_fuzzer_flags" ] } if (current_os == "fuchsia") { _default_configs += [ "//third_party/fuchsia-gn-sdk/src/config:compiler", "//third_party/fuchsia-gn-sdk/src/config:runtime_library", ] import("//third_party/fuchsia-gn-sdk/src/gn_configs.gni") } _default_executable_configs = _default_configs + [ "$_mini_chromium_dir/build/config:executable", "$_mini_chromium_dir/build/config:win_console", ] set_defaults("source_set") { configs = _default_configs } set_defaults("static_library") { configs = _default_configs } set_defaults("executable") { configs = _default_executable_configs } set_defaults("loadable_module") { configs = _default_configs } set_defaults("shared_library") { configs = _default_configs } set_defaults("test") { configs = _default_executable_configs } ================================================ FILE: build/config/fuchsia/gn_configs.gni ================================================ # Copyright 2024 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This file is copied from # https://crsrc.org/c/build/config/fuchsia/gn_configs.gni?q=gn_configs.gni # with some local modifications to match the crashpad setup. # Path to the fuchsia SDK. This is intended for use in other templates & # rules to reference the contents of the fuchsia SDK. fuchsia_sdk = "//third_party/fuchsia/sdk/linux-amd64" declare_args() { # Specify a readelf_exec path to use. If not specified, the host's system # executable will be used. Passed to populate_build_id_dir.py and # prepare_package_inputs.py via the --readelf-exec flag. # Must be a GN path (not an absolute path) since it is adjusted with # rebase_path(). if (!defined(fuchsia_sdk_readelf_exec)) { fuchsia_sdk_readelf_exec = "" } } ================================================ FILE: build/crashpad_buildconfig.gni ================================================ # Copyright 2017 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. declare_args() { # Determines various flavors of build configuration, and which concrete # targets to use for dependencies. Valid values are "standalone", "chromium", # "fuchsia", "dart" or "external". crashpad_dependencies = "standalone" if (defined(is_fuchsia_tree) && is_fuchsia_tree) { crashpad_dependencies = "fuchsia" } } assert( crashpad_dependencies == "chromium" || crashpad_dependencies == "fuchsia" || crashpad_dependencies == "standalone" || crashpad_dependencies == "external" || crashpad_dependencies == "dart") crashpad_is_in_chromium = crashpad_dependencies == "chromium" crashpad_is_in_fuchsia = crashpad_dependencies == "fuchsia" crashpad_is_in_dart = crashpad_dependencies == "dart" crashpad_is_external = crashpad_dependencies == "external" crashpad_is_standalone = crashpad_dependencies == "standalone" # This is the parent directory that contains the mini_chromium source dir. # This variable is not used when crashpad_is_in_chromium. if (crashpad_is_in_fuchsia) { import("//third_party/crashpad/fuchsia_buildconfig.gni") mini_chromium_source_parent = fuchsia_crashpad_root + "/third_party/mini_chromium" } else { mini_chromium_source_parent = "../third_party/mini_chromium" } # This is the source directory of mini_chromium (what is checked out). _mini_chromium_source_root = "$mini_chromium_source_parent/mini_chromium" # This references the mini_chromium location for importing GN files. if (crashpad_is_external || crashpad_is_in_dart) { mini_chromium_import_root = "../../../$_mini_chromium_source_root" } else if (crashpad_is_in_fuchsia) { mini_chromium_import_root = fuchsia_mini_chromium_root } else { mini_chromium_import_root = _mini_chromium_source_root } if (crashpad_is_in_chromium) { if (is_ios) { # For `target_platform`. import("//build/config/apple/mobile_config.gni") } crashpad_is_mac = is_mac crashpad_is_ios = is_ios crashpad_is_tvos = is_ios && target_platform == "tvos" crashpad_is_apple = is_apple crashpad_is_win = is_win crashpad_is_linux = is_linux || is_chromeos crashpad_is_android = is_android crashpad_is_fuchsia = is_fuchsia crashpad_is_posix = is_posix crashpad_is_clang = is_clang } else { import("$mini_chromium_import_root/build/compiler.gni") import("$mini_chromium_import_root/build/platform.gni") if (mini_chromium_is_ios) { # For `target_platform`. import("$mini_chromium_import_root/build/ios/ios_sdk.gni") } crashpad_is_mac = mini_chromium_is_mac crashpad_is_ios = mini_chromium_is_ios crashpad_is_tvos = crashpad_is_ios && target_platform == "tvos" crashpad_is_apple = mini_chromium_is_apple crashpad_is_win = mini_chromium_is_win crashpad_is_linux = mini_chromium_is_linux crashpad_is_android = mini_chromium_is_android crashpad_is_fuchsia = mini_chromium_is_fuchsia crashpad_is_posix = mini_chromium_is_posix crashpad_is_clang = mini_chromium_is_clang # fuchsia-gn-sdk from chromium uses "is_fuchsia" condition. is_fuchsia = crashpad_is_fuchsia } crashpad_flock_always_supported = !(crashpad_is_android || crashpad_is_fuchsia) template("crashpad_executable") { executable(target_name) { forward_variables_from(invoker, "*", [ "configs", "remove_configs", ]) if (defined(invoker.remove_configs)) { configs -= invoker.remove_configs } if (defined(invoker.configs)) { configs += invoker.configs } if (crashpad_is_in_fuchsia) { conversion_config = [ "//build/config:Wno-conversion" ] if (configs + conversion_config - conversion_config == configs) { # TODO(https://fxbug.dev/42136089): Decide if these are worth enabling. configs += conversion_config } } } } template("crashpad_loadable_module") { loadable_module(target_name) { forward_variables_from(invoker, "*", [ "configs", "remove_configs", ]) if (defined(invoker.remove_configs)) { configs -= invoker.remove_configs } if (defined(invoker.configs)) { configs += invoker.configs } if (crashpad_is_in_fuchsia) { conversion_config = [ "//build/config:Wno-conversion" ] if (configs + conversion_config - conversion_config == configs) { # TODO(https://fxbug.dev/42136089): Decide if these are worth enabling. configs += conversion_config } } } } template("crashpad_static_library") { static_library(target_name) { forward_variables_from(invoker, "*", [ "configs", "remove_configs", ]) if (defined(invoker.remove_configs)) { configs -= invoker.remove_configs } if (defined(invoker.configs)) { configs += invoker.configs } if (crashpad_is_in_fuchsia) { conversion_config = [ "//build/config:Wno-conversion" ] if (configs + conversion_config - conversion_config == configs) { # TODO(https://fxbug.dev/42136089): Decide if these are worth enabling. configs += conversion_config } } } } ================================================ FILE: build/crashpad_fuzzer_test.gni ================================================ # Copyright 2018 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import("crashpad_buildconfig.gni") import("test.gni") if (crashpad_is_in_chromium) { import("//testing/libfuzzer/fuzzer_test.gni") } template("crashpad_fuzzer_test") { if (crashpad_is_standalone && crashpad_use_libfuzzer) { test(target_name) { forward_variables_from(invoker, [ "cflags", "cflags_cc", "check_includes", "defines", "include_dirs", "sources", ]) configs += [ "..:crashpad_config" ] if (defined(invoker.deps)) { deps = invoker.deps } deps += [ "../third_party/libfuzzer" ] if (!defined(invoker.cflags)) { cflags = [] } cflags += [ "-fsanitize=fuzzer" ] } if (defined(invoker.seed_corpus)) { not_needed(invoker, [ "seed_corpus" ]) } } else if (crashpad_is_in_chromium && use_fuzzing_engine) { # Append "crashpad_" to the beginning of the fuzzer's name to make it easier # in Chromium to recognize where fuzzer came from. forward_variables_from(invoker, "*") fuzzer_test("crashpad_" + target_name) { } } else { not_needed(invoker, "*") group(target_name) { } } } ================================================ FILE: build/fuchsia_envs.py ================================================ #!/usr/bin/env python3 # Copyright 2024 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import platform import subprocess import sys def main(args): """ Executes the test-scripts with required environment variables. It acts like /usr/bin/env, but provides some extra functionality to dynamically set up the environment variables. Args: args: the command line arguments without the script name itself. """ os.environ['SRC_ROOT'] = os.path.abspath( os.path.join(os.path.dirname(__file__), '..')) assert platform.system() == 'Linux', 'Unsupported OS ' + platform.system() os.environ['FUCHSIA_SDK_ROOT'] = os.path.join( os.environ['SRC_ROOT'], 'third_party/fuchsia/sdk/linux-amd64/') os.environ['FUCHSIA_GN_SDK_ROOT'] = os.path.join( os.environ['SRC_ROOT'], 'third_party/fuchsia-gn-sdk/src') os.environ['FUCHSIA_READELF'] = os.path.join(os.environ['SRC_ROOT'], 'third_party/fuchsia/clang/linux-amd64/bin/llvm-readelf') return subprocess.run(args).returncode if __name__ == '__main__': sys.exit(main(sys.argv[1:])) ================================================ FILE: build/install_linux_sysroot.py ================================================ #!/usr/bin/env python3 # Copyright 2018 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Various code adapted from: # https://cs.chromium.org/chromium/src/build/linux/sysroot_scripts/install-sysroot.py import os import shutil import subprocess import sys import urllib.request SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) # Sysroot revision from: # https://cs.chromium.org/chromium/src/build/linux/sysroot_scripts/sysroots.json SERVER = 'https://commondatastorage.googleapis.com' PATH = 'chrome-linux-sysroot/toolchain' REVISION = '43a87bbebccad99325fdcf34166295b121ee15c7' FILENAME = 'debian_sid_amd64_sysroot.tar.xz' def main(): url = '%s/%s/%s/%s' % (SERVER, PATH, REVISION, FILENAME) sysroot = os.path.join(SCRIPT_DIR, os.pardir, 'third_party', 'linux', 'sysroot') stamp = os.path.join(sysroot, '.stamp') if os.path.exists(stamp): with open(stamp) as s: if s.read() == url: return print('Installing Debian root image from %s' % url) if os.path.isdir(sysroot): shutil.rmtree(sysroot) os.mkdir(sysroot) tarball = os.path.join(sysroot, FILENAME) print('Downloading %s' % url) for _ in range(3): response = urllib.request.urlopen(url) with open(tarball, 'wb') as f: f.write(response.read()) break else: raise Exception('Failed to download %s' % url) subprocess.check_call(['tar', 'xf', tarball, '-C', sysroot]) os.remove(tarball) with open(stamp, 'w') as s: s.write(url) if __name__ == '__main__': main() sys.exit(0) ================================================ FILE: build/ios/Unittest-Info.plist ================================================ CFBundleIdentifier ${IOS_BUNDLE_ID_PREFIX}.${GTEST_BUNDLE_ID_SUFFIX:rfc1034identifier} UIApplicationDelegate CrashpadUnitTestDelegate ================================================ FILE: build/ios/convert_gn_xcodeproj.py ================================================ #!/usr/bin/env python3 # Copyright 2020 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Convert GN Xcode projects to platform and configuration independent targets. GN generates Xcode projects that build one configuration only. However, typical iOS development involves using the Xcode IDE to toggle the platform and configuration. This script replaces the 'gn' configuration with 'Debug', 'Release' and 'Profile', and changes the ninja invocation to honor these configurations. """ import argparse import collections import copy import filecmp import functools import hashlib import io import json import os import re import shutil import string import subprocess import sys import tempfile import xml.etree.ElementTree LLDBINIT_PATH = '$(PROJECT_DIR)/.lldbinit' PYTHON_RE = re.compile('[ /]python[23]?$') XCTEST_PRODUCT_TYPES = frozenset(( 'com.apple.product-type.bundle.unit-test', 'com.apple.product-type.bundle.ui-testing', )) SCHEME_PRODUCT_TYPES = frozenset(( 'com.apple.product-type.app-extension', 'com.apple.product-type.application', 'com.apple.product-type.framework' )) class Template(string.Template): """A subclass of string.Template that changes delimiter.""" delimiter = '@' @functools.lru_cache def LoadSchemeTemplate(root, name): """Return a string.Template object for scheme file loaded relative to root.""" path = os.path.join(root, 'build', 'ios', name + '.template') with open(path) as file: return Template(file.read()) def CreateIdentifier(str_id): """Return a 24 characters string that can be used as an identifier.""" return hashlib.sha1(str_id.encode("utf-8")).hexdigest()[:24].upper() def GenerateSchemeForTarget(root, project, old_project, name, path, is_test): """Generates the .xcsheme file for target named |name|. The file is generated in the new project schemes directory from a template. If there is an existing previous project, then the old scheme file is copied and the lldbinit setting is set. If lldbinit setting is already correct, the file is not modified, just copied. """ project_name = os.path.basename(project) relative_path = os.path.join('xcshareddata', 'xcschemes', name + '.xcscheme') identifier = CreateIdentifier('%s %s' % (name, path)) scheme_path = os.path.join(project, relative_path) if not os.path.isdir(os.path.dirname(scheme_path)): os.makedirs(os.path.dirname(scheme_path)) substitutions = { 'LLDBINIT_PATH': LLDBINIT_PATH, 'BLUEPRINT_IDENTIFIER': identifier, 'BUILDABLE_NAME': path, 'BLUEPRINT_NAME': name, 'PROJECT_NAME': project_name } if is_test: template = LoadSchemeTemplate(root, 'xcodescheme-testable') substitutions['PATH'] = os.environ['PATH'] else: template = LoadSchemeTemplate(root, 'xcodescheme') old_scheme_path = os.path.join(old_project, relative_path) if os.path.exists(old_scheme_path): tree = xml.etree.ElementTree.parse(old_scheme_path) tree_root = tree.getroot() for reference in tree_root.findall('.//BuildableReference'): for (attr, value) in ( ('BuildableName', path), ('BlueprintName', name), ('BlueprintIdentifier', identifier)): if reference.get(attr) != value: reference.set(attr, value) for child in tree_root: if child.tag not in ('TestAction', 'LaunchAction'): continue if child.get('customLLDBInitFile') != LLDBINIT_PATH: child.set('customLLDBInitFile', LLDBINIT_PATH) if is_test: template_tree = xml.etree.ElementTree.parse( io.StringIO(template.substitute(**substitutions))) template_tree_root = template_tree.getroot() for child in tree_root: if child.tag != 'BuildAction': continue for subchild in list(child): child.remove(subchild) for post_action in template_tree_root.findall('.//PostActions'): child.append(post_action) tree.write(scheme_path, xml_declaration=True, encoding='UTF-8') else: with open(scheme_path, 'w') as scheme_file: scheme_file.write(template.substitute(**substitutions)) class XcodeProject(object): def __init__(self, objects, counter = 0): self.objects = objects self.counter = 0 def AddObject(self, parent_name, obj): while True: self.counter += 1 str_id = "%s %s %d" % (parent_name, obj['isa'], self.counter) new_id = CreateIdentifier(str_id) # Make sure ID is unique. It's possible there could be an id conflict # since this is run after GN runs. if new_id not in self.objects: self.objects[new_id] = obj return new_id def IterObjectsByIsa(self, isa): """Iterates overs objects of the |isa| type.""" for key, obj in self.objects.items(): if obj['isa'] == isa: yield (key, obj) def IterNativeTargetByProductType(self, product_types): """Iterates over PBXNativeTarget objects of any |product_types| types.""" for key, obj in self.IterObjectsByIsa('PBXNativeTarget'): if obj['productType'] in product_types: yield (key, obj) def UpdateBuildScripts(self): """Update build scripts to respect configuration and platforms.""" for key, obj in self.IterObjectsByIsa('PBXShellScriptBuildPhase'): shell_path = obj['shellPath'] shell_code = obj['shellScript'] if shell_path.endswith('/sh'): shell_code = shell_code.replace( 'ninja -C .', 'ninja -C "../${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}"') elif PYTHON_RE.search(shell_path): shell_code = shell_code.replace( '''ninja_params = [ '-C', '.' ]''', '''ninja_params = [ '-C', '../' + os.environ['CONFIGURATION']''' ''' + os.environ['EFFECTIVE_PLATFORM_NAME'] ]''') # Replace the build script in the object. obj['shellScript'] = shell_code def UpdateBuildConfigurations(self, configurations): """Add new configurations, using the first one as default.""" # Create a list with all the objects of interest. This is needed # because objects will be added to/removed from the project upon # iterating this list and python dictionaries cannot be mutated # during iteration. for key, obj in list(self.IterObjectsByIsa('XCConfigurationList')): # Use the first build configuration as template for creating all the # new build configurations. build_config_template = self.objects[obj['buildConfigurations'][0]] build_config_template['buildSettings']['CONFIGURATION_BUILD_DIR'] = \ '$(PROJECT_DIR)/../$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)' # Remove the existing build configurations from the project before # creating the new ones. for build_config_id in obj['buildConfigurations']: del self.objects[build_config_id] obj['buildConfigurations'] = [] for configuration in configurations: build_config = copy.copy(build_config_template) build_config['name'] = configuration build_config_id = self.AddObject('products', build_config) obj['buildConfigurations'].append(build_config_id) def GetHostMappingForXCTests(self): """Returns a dict from targets to the list of their xctests modules.""" mapping = collections.defaultdict(list) for key, obj in self.IterNativeTargetByProductType(XCTEST_PRODUCT_TYPES): build_config_lists_id = obj['buildConfigurationList'] build_configs = self.objects[build_config_lists_id]['buildConfigurations'] # Use the first build configuration to get the name of the host target. # This is arbitrary, but since the build configuration are all identical # after UpdateBuildConfiguration, except for their 'name', it is fine. build_config = self.objects[build_configs[0]] if obj['productType'] == 'com.apple.product-type.bundle.unit-test': # The test_host value will look like this: # `$(BUILD_PRODUCTS_DIR)/host_app_name.app/host_app_name` # # Extract the `host_app_name.app` part as key for the output. test_host_path = build_config['buildSettings']['TEST_HOST'] test_host_name = os.path.basename(os.path.dirname(test_host_path)) else: test_host_name = build_config['buildSettings']['TEST_TARGET_NAME'] test_name = obj['name'] test_path = self.objects[obj['productReference']]['path'] mapping[test_host_name].append((key, test_name, test_path)) return dict(mapping) def check_output(command): """Wrapper around subprocess.check_output that decode output as utf-8.""" return subprocess.check_output(command).decode('utf-8') def CopyFileIfChanged(source_path, target_path): """Copy |source_path| to |target_path| if different.""" target_dir = os.path.dirname(target_path) if not os.path.isdir(target_dir): os.makedirs(target_dir) if not os.path.exists(target_path) or \ not filecmp.cmp(source_path, target_path): shutil.copyfile(source_path, target_path) def CopyTreeIfChanged(source, target): """Copy |source| to |target| recursively; files are copied iff changed.""" if os.path.isfile(source): return CopyFileIfChanged(source, target) if not os.path.isdir(target): os.makedirs(target) for name in os.listdir(source): CopyTreeIfChanged( os.path.join(source, name), os.path.join(target, name)) def LoadXcodeProjectAsJSON(project_dir): """Return Xcode project at |path| as a JSON string.""" return check_output([ 'plutil', '-convert', 'json', '-o', '-', os.path.join(project_dir, 'project.pbxproj')]) def WriteXcodeProject(output_path, json_string): """Save Xcode project to |output_path| as XML.""" with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(json_string.encode("utf-8")) temp_file.flush() subprocess.check_call(['plutil', '-convert', 'xml1', temp_file.name]) CopyFileIfChanged( temp_file.name, os.path.join(output_path, 'project.pbxproj')) def UpdateXcodeProject(project_dir, old_project_dir, configurations, root_dir): """Update inplace Xcode project to support multiple configurations. Args: project_dir: path to the input Xcode project configurations: list of string corresponding to the configurations that need to be supported by the tweaked Xcode projects, must contains at least one value. root_dir: path to the root directory used to find markdown files """ json_data = json.loads(LoadXcodeProjectAsJSON(project_dir)) project = XcodeProject(json_data['objects']) project.UpdateBuildScripts() project.UpdateBuildConfigurations(configurations) mapping = project.GetHostMappingForXCTests() # Generate schemes for application, extensions and framework targets for key, obj in project.IterNativeTargetByProductType(SCHEME_PRODUCT_TYPES): product = project.objects[obj['productReference']] product_path = product['path'] # Do not generate scheme for the XCTests and XXCUITests target app. # Instead, a scheme will be generated for each test modules. tests = mapping.get(product_path, []) + mapping.get(obj['name'], []) if not tests: GenerateSchemeForTarget( root_dir, project_dir, old_project_dir, obj['name'], product_path, False) else: for (_, test_name, test_path) in tests: GenerateSchemeForTarget( root_dir, project_dir, old_project_dir, test_name, test_path, True) root_object = project.objects[json_data['rootObject']] main_group = project.objects[root_object['mainGroup']] sources = None for child_key in main_group['children']: child = project.objects[child_key] if child.get('name') == 'Source' or child.get('name') == 'Sources': sources = child break if sources is None: sources = main_group AddMarkdownToProject(project, root_dir, sources, sources is main_group) SortFileReferencesByName(project, sources, root_object.get('productRefGroup')) objects = collections.OrderedDict(sorted(project.objects.items())) # WriteXcodeProject(project_dir, json.dumps(json_data)) def CreateGroup(project, parent_group, group_name, use_relative_paths): group_object = { 'children': [], 'isa': 'PBXGroup', 'sourceTree': '', } if use_relative_paths: group_object['path'] = group_name else: group_object['name'] = group_name parent_group_name = parent_group.get('name', '') group_object_key = project.AddObject(parent_group_name, group_object) parent_group['children'].append(group_object_key) return group_object class ObjectKey(object): """Wrapper around PBXFileReference and PBXGroup for sorting. A PBXGroup represents a "directory" containing a list of files in an Xcode project; it can contain references to a list of directories or files. A PBXFileReference represents a "file". The type is stored in the object "isa" property as a string. Since we want to sort all directories before all files, the < and > operators are defined so that if "isa" is different, they are sorted in the reverse of alphabetic ordering, otherwise the name (or path) property is checked and compared in alphabetic order. """ def __init__(self, obj, last): self.isa = obj['isa'] if 'name' in obj: self.name = obj['name'] else: self.name = obj['path'] self.last = last def __lt__(self, other): if self.last != other.last: return other.last if self.isa != other.isa: return self.isa > other.isa return self.name < other.name def __gt__(self, other): if self.last != other.last: return self.last if self.isa != other.isa: return self.isa < other.isa return self.name > other.name def __eq__(self, other): return self.isa == other.isa and self.name == other.name def SortFileReferencesByName(project, group_object, products_group_ref): SortFileReferencesByNameWithSortKey( project, group_object, lambda ref: ObjectKey(project.objects[ref], ref == products_group_ref)) def SortFileReferencesByNameWithSortKey(project, group_object, sort_key): group_object['children'].sort(key=sort_key) for key in group_object['children']: child = project.objects[key] if child['isa'] == 'PBXGroup': SortFileReferencesByNameWithSortKey(project, child, sort_key) def AddMarkdownToProject(project, root_dir, group_object, use_relative_paths): list_files_cmd = ['git', '-C', root_dir, 'ls-files', '*.md'] paths = check_output(list_files_cmd).splitlines() ios_internal_dir = os.path.join(root_dir, 'ios_internal') if os.path.exists(ios_internal_dir): list_files_cmd = ['git', '-C', ios_internal_dir, 'ls-files', '*.md'] ios_paths = check_output(list_files_cmd).splitlines() paths.extend([os.path.join("ios_internal", path) for path in ios_paths]) for path in paths: new_markdown_entry = { "fileEncoding": "4", "isa": "PBXFileReference", "lastKnownFileType": "net.daringfireball.markdown", "sourceTree": "" } if use_relative_paths: new_markdown_entry['path'] = os.path.basename(path) else: new_markdown_entry['name'] = os.path.basename(path) new_markdown_entry['path'] = path folder = GetFolderForPath( project, group_object, os.path.dirname(path), use_relative_paths) folder_name = folder.get('name', None) if folder_name is None: folder_name = folder.get('path', 'sources') new_markdown_entry_id = project.AddObject(folder_name, new_markdown_entry) folder['children'].append(new_markdown_entry_id) def GetFolderForPath(project, group_object, path, use_relative_paths): objects = project.objects if not path: return group_object for folder in path.split('/'): children = group_object['children'] new_root = None for child_key in children: child = objects[child_key] if child['isa'] == 'PBXGroup': child_name = child.get('name', None) if child_name is None: child_name = child.get('path') if child_name == folder: new_root = child break if not new_root: # If the folder isn't found we could just cram it into the leaf existing # folder, but that leads to folders with tons of README.md inside. new_root = CreateGroup(project, group_object, folder, use_relative_paths) group_object = new_root return group_object def ConvertGnXcodeProject(root_dir, proj_name, input_dir, output_dir, configs): '''Tweak the Xcode project generated by gn to support multiple configurations. The Xcode projects generated by "gn gen --ide" only supports a single platform and configuration (as the platform and configuration are set per output directory). This method takes as input such projects and add support for multiple configurations and platforms (to allow devs to select them in Xcode). Args: root_dir: directory that is the root of the project proj_name: name of the Xcode project "file" (usually `all.xcodeproj`) input_dir: directory containing the XCode projects created by "gn gen --ide" output_dir: directory where the tweaked Xcode projects will be saved configs: list of string corresponding to the configurations that need to be supported by the tweaked Xcode projects, must contains at least one value. ''' UpdateXcodeProject( os.path.join(input_dir, proj_name), os.path.join(output_dir, proj_name), configs, root_dir) CopyTreeIfChanged(os.path.join(input_dir, proj_name), os.path.join(output_dir, proj_name)) def Main(args): parser = argparse.ArgumentParser( description='Convert GN Xcode projects for iOS.') parser.add_argument( 'input', help='directory containing [product|all] Xcode projects.') parser.add_argument( 'output', help='directory where to generate the iOS configuration.') parser.add_argument( '--add-config', dest='configurations', default=[], action='append', help='configuration to add to the Xcode project') parser.add_argument( '--root', type=os.path.abspath, required=True, help='root directory of the project') parser.add_argument( '--project-name', default='all.xcodeproj', dest='proj_name', help='name of the Xcode project (default: %(default)s)') args = parser.parse_args(args) if not os.path.isdir(args.input): sys.stderr.write('Input directory does not exists.\n') return 1 if args.proj_name not in os.listdir(args.input): sys.stderr.write( 'Input directory does not contain the Xcode project.\n') return 1 if not args.configurations: sys.stderr.write('At least one configuration required, see --add-config.\n') return 1 ConvertGnXcodeProject( args.root, args.proj_name, args.input, args.output, args.configurations) if __name__ == '__main__': sys.exit(Main(sys.argv[1:])) ================================================ FILE: build/ios/setup_ios_gn.config ================================================ # Copyright 2020 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. [xcode] # Controls settings for the generated Xcode project. If jobs is non-zero # it will be passed to the ninja invocation in Xcode project. jobs = 0 [build] # Controls the build output. The only supported values are "64-bit", "32-bit" # and "fat" (for a fat binary supporting both "32-bit" and "64-bit" cpus). arch = "64-bit" [gn_args] # Values in that section will be copied verbatim in the generated args.gn file. target_os = "ios" [filters] # List of target files to pass to --filters argument of gn gen when generating # the Xcode project. By default, list all targets from ios/ and ios_internal/ # and the targets corresponding to the unit tests run on the bots. ================================================ FILE: build/ios/setup_ios_gn.py ================================================ #!/usr/bin/env python3 # Copyright 2020 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import configparser import convert_gn_xcodeproj import errno import io import os import platform import re import shutil import subprocess import sys import tempfile SUPPORTED_TARGETS = ('appletvos', 'appletvsimulator', 'iphoneos', 'iphonesimulator', 'maccatalyst') SUPPORTED_CONFIGS = ('Debug', 'Release', 'Profile', 'Official') # Pattern matching lines from ~/.lldbinit that must not be copied to the # generated .lldbinit file. They match what the user were told to add to # their global ~/.lldbinit file before setup-gn.py was updated to generate # a project specific file and thus must not be copied as they would cause # the settings to be overwritten. LLDBINIT_SKIP_PATTERNS = ( re.compile('^script sys.path\\[:0\\] = \\[\'.*/src/tools/lldb\'\\]$'), re.compile('^script import lldbinit$'), re.compile('^settings append target.source-map .* /google/src/.*$'), ) def HostCpuArch(): '''Returns the arch of the host cpu for GN.''' HOST_CPU_ARCH = { 'arm64': '"arm64"', 'x86_64': '"x64"', } return HOST_CPU_ARCH[platform.machine()] class ConfigParserWithStringInterpolation(configparser.ConfigParser): '''A .ini file parser that supports strings and environment variables.''' ENV_VAR_PATTERN = re.compile(r'\$([A-Za-z0-9_]+)') def values(self, section): return filter( lambda val: val != '', map(lambda kv: self._UnquoteString(self._ExpandEnvVar(kv[1])), configparser.ConfigParser.items(self, section))) def getstring(self, section, option, fallback=''): try: raw_value = self.get(section, option) except configparser.NoOptionError: return fallback return self._UnquoteString(self._ExpandEnvVar(raw_value)) def _UnquoteString(self, string): if not string or string[0] != '"' or string[-1] != '"': return string return string[1:-1] def _ExpandEnvVar(self, value): match = self.ENV_VAR_PATTERN.search(value) if not match: return value name, (begin, end) = match.group(1), match.span(0) prefix, suffix = value[:begin], self._ExpandEnvVar(value[end:]) return prefix + os.environ.get(name, '') + suffix class GnGenerator(object): '''Holds configuration for a build and method to generate gn default files.''' FAT_BUILD_DEFAULT_ARCH = '64-bit' TARGET_CPU_VALUES = { 'appletvos': '"arm64"', 'appletvsimulator': HostCpuArch(), 'iphoneos': '"arm64"', 'iphonesimulator': HostCpuArch(), 'maccatalyst': HostCpuArch(), } TARGET_ENVIRONMENT_VALUES = { 'appletvos': '"device"', 'appletvsimulator': '"simulator"', 'iphoneos': '"device"', 'iphonesimulator': '"simulator"', 'maccatalyst': '"catalyst"' } TARGET_PLATFORM_VALUES = { 'appletvos': '"tvos"', 'appletvsimulator': '"tvos"', 'iphoneos': '"iphoneos"', 'iphonesimulator': '"iphoneos"', 'maccatalyst': '"iphoneos"' } def __init__(self, settings, config, target): assert target in SUPPORTED_TARGETS assert config in SUPPORTED_CONFIGS self._settings = settings self._config = config self._target = target def _GetGnArgs(self): """Build the list of arguments to pass to gn. Returns: A list of tuple containing gn variable names and variable values (it is not a dictionary as the order needs to be preserved). """ args = [] is_debug = self._config == 'Debug' official = self._config == 'Official' is_optim = self._config in ('Profile', 'Official') args.append(('target_os', '"ios"')) args.append(('is_debug', is_debug)) if os.environ.get('FORCE_MAC_TOOLCHAIN', '0') == '1': args.append(('use_system_xcode', False)) args.append(('target_cpu', self.TARGET_CPU_VALUES[self._target])) args.append( ('target_environment', self.TARGET_ENVIRONMENT_VALUES[self._target])) args.append(('target_platform', self.TARGET_PLATFORM_VALUES[self._target])) # Add user overrides after the other configurations so that they can # refer to them and override them. args.extend(self._settings.items('gn_args')) return args def Generate(self, gn_path, proj_name, root_path, build_dir): self.WriteArgsGn(build_dir, xcode_project_name=proj_name) subprocess.check_call(self.GetGnCommand( gn_path, root_path, build_dir, xcode_project_name=proj_name)) def CreateGnRules(self, gn_path, root_path, build_dir): gn_command = self.GetGnCommand(gn_path, root_path, build_dir) self.WriteArgsGn(build_dir) self.WriteBuildNinja(gn_command, build_dir) self.WriteBuildNinjaDeps(build_dir) def WriteArgsGn(self, build_dir, xcode_project_name=None): with open(os.path.join(build_dir, 'args.gn'), 'w') as stream: stream.write('# This file was generated by setup-gn.py. Do not edit\n') stream.write('# but instead use ~/.setup-gn or $repo/.setup-gn files\n') stream.write('# to configure settings.\n') stream.write('\n') if self._target != 'maccatalyst': if self._settings.has_section('$imports$'): for import_rule in self._settings.values('$imports$'): stream.write('import("%s")\n' % import_rule) stream.write('\n') gn_args = self._GetGnArgs() for name, value in gn_args: if isinstance(value, bool): stream.write('%s = %s\n' % (name, str(value).lower())) elif isinstance(value, list): stream.write('%s = [%s' % (name, '\n' if len(value) > 1 else '')) if len(value) == 1: prefix = ' ' suffix = ' ' else: prefix = ' ' suffix = ',\n' for item in value: if isinstance(item, bool): stream.write('%s%s%s' % (prefix, str(item).lower(), suffix)) else: stream.write('%s%s%s' % (prefix, item, suffix)) stream.write(']\n') else: # ConfigParser removes quote around empty string which confuse # `gn gen` so restore them. if not value: value = '""' stream.write('%s = %s\n' % (name, value)) def WriteBuildNinja(self, gn_command, build_dir): with open(os.path.join(build_dir, 'build.ninja'), 'w') as stream: stream.write('ninja_required_version = 1.7.2\n') stream.write('\n') stream.write('rule gn\n') stream.write(' command = %s\n' % NinjaEscapeCommand(gn_command)) stream.write(' description = Regenerating ninja files\n') stream.write('\n') stream.write('build build.ninja: gn\n') stream.write(' generator = 1\n') stream.write(' depfile = build.ninja.d\n') def WriteBuildNinjaDeps(self, build_dir): with open(os.path.join(build_dir, 'build.ninja.d'), 'w') as stream: stream.write('build.ninja: nonexistant_file.gn\n') def GetGnCommand(self, gn_path, src_path, out_path, xcode_project_name=None): gn_command = [ gn_path, '--root=%s' % os.path.realpath(src_path), '-q' ] if xcode_project_name is not None: gn_command.append('--ide=xcode') gn_command.append('--ninja-executable=autoninja') gn_command.append('--xcode-build-system=new') gn_command.append('--xcode-project=%s' % xcode_project_name) gn_command.append('--xcode-additional-files-patterns=*.md') gn_command.append('--xcode-configs=' + ';'.join(SUPPORTED_CONFIGS)) gn_command.append('--xcode-config-build-dir=' '//out/${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}') if self._settings.has_section('filters'): target_filters = self._settings.values('filters') if target_filters: gn_command.append('--filters=%s' % ';'.join(target_filters)) else: gn_command.append('--check') gn_command.append('gen') gn_command.append('//%s' % os.path.relpath(os.path.abspath(out_path), os.path.abspath(src_path))) return gn_command def NinjaNeedEscape(arg): '''Returns True if |arg| needs to be escaped when written to .ninja file.''' return ':' in arg or '*' in arg or ';' in arg def NinjaEscapeCommand(command): '''Escapes |command| in order to write it to .ninja file.''' result = [] for arg in command: if NinjaNeedEscape(arg): arg = arg.replace(':', '$:') arg = arg.replace(';', '\\;') arg = arg.replace('*', '\\*') else: result.append(arg) return ' '.join(result) def FindGn(): '''Returns absolute path to gn binary looking at the PATH env variable.''' for path in os.environ['PATH'].split(os.path.pathsep): gn_path = os.path.join(path, 'gn') if os.path.isfile(gn_path) and os.access(gn_path, os.X_OK): return gn_path return None def GenerateXcodeProject(gn_path, root_dir, proj_name, out_dir, settings): '''Generate Xcode project with Xcode and convert to multi-configurations.''' prefix = os.path.abspath(os.path.join(out_dir, '_temp')) temp_path = tempfile.mkdtemp(prefix=prefix) try: generator = GnGenerator(settings, 'Debug', 'iphonesimulator') generator.Generate(gn_path, proj_name, root_dir, temp_path) convert_gn_xcodeproj.ConvertGnXcodeProject( root_dir, '%s.xcodeproj' % proj_name, os.path.join(temp_path), os.path.join(out_dir, 'build'), SUPPORTED_CONFIGS) finally: if os.path.exists(temp_path): shutil.rmtree(temp_path) def CreateLLDBInitFile(root_dir, out_dir, settings): ''' Generate an .lldbinit file for the project that load the script that fixes the mapping of source files (see docs/ios/build_instructions.md#debugging). ''' with open(os.path.join(out_dir, 'build', '.lldbinit'), 'w') as lldbinit: lldb_script_dir = os.path.join(os.path.abspath(root_dir), 'tools', 'lldb') lldbinit.write('script sys.path[:0] = [\'%s\']\n' % lldb_script_dir) lldbinit.write('script import lldbinit\n') workspace_name = settings.getstring( 'gn_args', 'ios_internal_citc_workspace_name') if workspace_name != '': username = os.environ['USER'] for shortname in ('googlemac', 'third_party', 'blaze-out'): lldbinit.write('settings append target.source-map %s %s\n' % ( shortname, '/google/src/cloud/%s/%s/google3/%s' % ( username, workspace_name, shortname))) # Append the content of //ios/build/tools/lldbinit.defaults if it exists. tools_dir = os.path.join(root_dir, 'ios', 'build', 'tools') defaults_lldbinit_path = os.path.join(tools_dir, 'lldbinit.defaults') if os.path.isfile(defaults_lldbinit_path): with open(defaults_lldbinit_path) as defaults_lldbinit: for line in defaults_lldbinit: lldbinit.write(line) # Append the content of ~/.lldbinit if it exists. Line that look like they # are trying to configure source mapping are skipped as they probably date # back from when setup-gn.py was not generating an .lldbinit file. global_lldbinit_path = os.path.join(os.environ['HOME'], '.lldbinit') if os.path.isfile(global_lldbinit_path): with open(global_lldbinit_path) as global_lldbinit: for line in global_lldbinit: if any(pattern.match(line) for pattern in LLDBINIT_SKIP_PATTERNS): continue lldbinit.write(line) def GenerateGnBuildRules(gn_path, root_dir, out_dir, settings): '''Generates all template configurations for gn.''' for config in SUPPORTED_CONFIGS: for target in SUPPORTED_TARGETS: build_dir = os.path.join(out_dir, '%s-%s' % (config, target)) if not os.path.isdir(build_dir): os.makedirs(build_dir) generator = GnGenerator(settings, config, target) generator.CreateGnRules(gn_path, root_dir, build_dir) def Main(args): default_root = os.path.normpath(os.path.join( os.path.dirname(__file__), os.pardir, os.pardir)) parser = argparse.ArgumentParser( description='Generate build directories for use with gn.') parser.add_argument( 'root', default=default_root, nargs='?', help='root directory where to generate multiple out configurations') parser.add_argument( '--import', action='append', dest='import_rules', default=[], help='path to file defining default gn variables') parser.add_argument( '--gn-path', default=None, help='path to gn binary (default: look up in $PATH)') parser.add_argument( '--build-dir', default='out', help='path where the build should be created (default: %(default)s)') parser.add_argument( '--config-path', default=os.path.expanduser('~/.setup-gn'), help='path to the user config file (default: %(default)s)') parser.add_argument( '--system-config-path', default=os.path.splitext(__file__)[0] + '.config', help='path to the default config file (default: %(default)s)') parser.add_argument( '--project-name', default='all', dest='proj_name', help='name of the generated Xcode project (default: %(default)s)') parser.add_argument( '--no-xcode-project', action='store_true', default=False, help='do not generate the build directory with XCode project') args = parser.parse_args(args) # Load configuration (first global and then any user overrides). settings = ConfigParserWithStringInterpolation() settings.read([ args.system_config_path, args.config_path, ]) # Add private sections corresponding to --import argument. if args.import_rules: settings.add_section('$imports$') for i, import_rule in enumerate(args.import_rules): if not import_rule.startswith('//'): import_rule = '//%s' % os.path.relpath( os.path.abspath(import_rule), os.path.abspath(args.root)) settings.set('$imports$', '$rule%d$' % i, import_rule) # Validate settings. if settings.getstring('build', 'arch') not in ('64-bit', '32-bit', 'fat'): sys.stderr.write('ERROR: invalid value for build.arch: %s\n' % settings.getstring('build', 'arch')) sys.exit(1) # Find path to gn binary either from command-line or in PATH. if args.gn_path: gn_path = args.gn_path else: gn_path = FindGn() if gn_path is None: sys.stderr.write('ERROR: cannot find gn in PATH\n') sys.exit(1) out_dir = os.path.join(args.root, args.build_dir) if not os.path.isdir(out_dir): os.makedirs(out_dir) if not args.no_xcode_project: GenerateXcodeProject(gn_path, args.root, args.proj_name, out_dir, settings) CreateLLDBInitFile(args.root, out_dir, settings) GenerateGnBuildRules(gn_path, args.root, out_dir, settings) if __name__ == '__main__': sys.exit(Main(sys.argv[1:])) ================================================ FILE: build/ios/xcodescheme-testable.template ================================================ ================================================ FILE: build/ios/xcodescheme.template ================================================ ================================================ FILE: build/run_tests.py ================================================ #!/usr/bin/env python3 # Copyright 2014 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import os import posixpath import re import shlex import subprocess import sys import tempfile import uuid CRASHPAD_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir) IS_WINDOWS_HOST = sys.platform.startswith('win') def _FindGNFromBinaryDir(binary_dir): """Attempts to determine the path to a GN binary used to generate the build files in the given binary_dir. This is necessary because `gn` might not be in the path or might be in a non-standard location, particularly on build machines.""" build_ninja = os.path.join(binary_dir, 'build.ninja') if os.path.isfile(build_ninja): with open(build_ninja, 'r') as f: # Look for the always-generated regeneration rule of the form: # # rule gn # command = ... arguments ... # # to extract the gn binary's full path. found_rule_gn = False for line in f: if line.strip() == 'rule gn': found_rule_gn = True continue if found_rule_gn: if len(line) == 0 or line[0] != ' ': return None if line.startswith(' command = '): gn_command_line_parts = line.strip().split(' ') if len(gn_command_line_parts) > 2: return os.path.join(binary_dir, gn_command_line_parts[2]) return None def _GetGNArgument(argument_name, binary_dir): """Returns the value of a given GN argument, or None if it is not explicitly specified.""" gn_path = _FindGNFromBinaryDir(binary_dir) if gn_path: # Look for a GN “target_os”. popen = subprocess.Popen([ gn_path, '--root=' + CRASHPAD_DIR, 'args', binary_dir, '--list=%s' % argument_name, '--short' ], shell=IS_WINDOWS_HOST, stdout=subprocess.PIPE, stderr=open(os.devnull), text=True) value = popen.communicate()[0] if popen.returncode == 0: match = re.match(r'%s = "(.*)"$' % argument_name, value) if match: return match.group(1) return None def _EnableVTProcessingOnWindowsConsole(): """Enables virtual terminal processing for ANSI/VT100-style escape sequences on a Windows console attached to standard output. Returns True on success. Returns False if standard output is not a console or if virtual terminal processing is not supported. The feature was introduced in Windows 10. """ import pywintypes import win32console import winerror stdout_console = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE) try: console_mode = stdout_console.GetConsoleMode() except pywintypes.error as e: if e.winerror == winerror.ERROR_INVALID_HANDLE: # Standard output is not a console. return False raise try: # From . This would be # win32console.ENABLE_VIRTUAL_TERMINAL_PROCESSING, but it’s too new to # be defined there. ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 stdout_console.SetConsoleMode(console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) except pywintypes.error as e: if e.winerror == winerror.ERROR_INVALID_PARAMETER: # ANSI/VT100-style escape sequence processing isn’t supported before # Windows 10. return False raise return True def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line): local_test_path = os.path.join(binary_dir, test) MAYBE_UNSUPPORTED_TESTS = ( 'crashpad_client_test', 'crashpad_handler_test', 'crashpad_minidump_test', 'crashpad_snapshot_test', ) if not os.path.exists(local_test_path) and test in MAYBE_UNSUPPORTED_TESTS: print('This test is not present and may not be supported, skipping') return def _adb(*args): # Flush all of this script’s own buffered stdout output before running # adb, which will likely produce its own output on stdout. sys.stdout.flush() adb_command = ['adb', '-s', android_device] adb_command.extend(args) subprocess.check_call(adb_command, shell=IS_WINDOWS_HOST) def _adb_push(sources, destination): args = list(sources) args.append(destination) _adb('push', *args) def _adb_shell(command_args, env={}): # Build a command to execute via “sh -c” instead of invoking it # directly. Here’s why: # # /system/bin/env isn’t normally present prior to Android 6.0 (M), where # toybox was introduced (Android platform/manifest 9a2c01e8450b). # Instead, set environment variables by using the shell’s internal # “export” command. # # adbd prior to Android 7.0 (N), and the adb client prior to SDK # platform-tools version 24, don’t know how to communicate a shell # command’s exit status. This was added in Android platform/system/core # 606835ae5c4b). With older adb servers and clients, adb will “exit 0” # indicating success even if the command failed on the device. This # makes subprocess.check_call() semantics difficult to implement # directly. As a workaround, have the device send the command’s exit # status over stdout and pick it back up in this function. # # Both workarounds are implemented by giving the device a simple script, # which adbd will run as an “sh -c” argument. adb_command = ['adb', '-s', android_device, 'shell'] script_commands = [] for k, v in env.items(): script_commands.append('export %s=%s' % (shlex.quote(k), shlex.quote(v))) script_commands.extend([ ' '.join(shlex.quote(x) for x in command_args), 'status=${?}', 'echo "status=${status}"', 'exit ${status}' ]) adb_command.append('; '.join(script_commands)) child = subprocess.Popen(adb_command, shell=IS_WINDOWS_HOST, stdin=open(os.devnull), stdout=subprocess.PIPE, text=True) FINAL_LINE_RE = re.compile(r'status=(\d+)$') final_line = None while True: # Use readline so that the test output appears “live” when running. data = child.stdout.readline() if data == '': break if final_line is not None: # It wasn’t really the final line. print(final_line, end='') final_line = None if FINAL_LINE_RE.match(data.rstrip()): final_line = data else: print(data, end='') if final_line is None: # Maybe there was some stderr output after the end of stdout. Old # versions of adb, prior to when the exit status could be # communicated, smush the two together. raise subprocess.CalledProcessError(-1, adb_command) status = int(FINAL_LINE_RE.match(final_line.rstrip()).group(1)) if status != 0: raise subprocess.CalledProcessError(status, adb_command) child.wait() if child.returncode != 0: raise subprocess.CalledProcessError(subprocess.returncode, adb_command) # /system/bin/mktemp isn’t normally present prior to Android 6.0 (M), where # toybox was introduced (Android platform/manifest 9a2c01e8450b). Fake it # with a host-generated name. This won’t retry if the name is in use, but # with 122 bits of randomness, it should be OK. This uses “mkdir” instead of # “mkdir -p”because the latter will not indicate failure if the directory # already exists. device_temp_dir = '/data/local/tmp/%s.%s' % (test, uuid.uuid4().hex) _adb_shell(['mkdir', device_temp_dir]) try: # Specify test dependencies that must be pushed to the device. This # could be determined automatically in a GN build, following the example # used for Fuchsia. Since nothing like that exists for GYP, hard-code it # for supported tests. test_build_artifacts = [test, 'crashpad_handler'] test_data = ['test/test_paths_test_data_root.txt'] if test == 'crashpad_test_test': test_build_artifacts.append( 'crashpad_test_test_multiprocess_exec_test_child') elif test == 'crashpad_util_test': test_data.append('util/net/testdata/') # Establish the directory structure on the device. device_out_dir = posixpath.join(device_temp_dir, 'out') device_mkdirs = [device_out_dir] for source_path in test_data: # A trailing slash could reasonably mean to copy an entire # directory, but will interfere with what’s needed from the path # split. All parent directories of any source_path need to be be # represented in device_mkdirs, but it’s important that no # source_path itself wind up in device_mkdirs, even if source_path # names a directory, because that would cause the “adb push” of the # directory below to behave incorrectly. if source_path.endswith(posixpath.sep): source_path = source_path[:-1] device_source_path = posixpath.join(device_temp_dir, source_path) device_mkdir = posixpath.split(device_source_path)[0] if device_mkdir not in device_mkdirs: device_mkdirs.append(device_mkdir) adb_mkdir_command = ['mkdir', '-p'] adb_mkdir_command.extend(device_mkdirs) _adb_shell(adb_mkdir_command) # Push the test binary and any other build output to the device. local_test_build_artifacts = [] for artifact in test_build_artifacts: local_test_build_artifacts.append(os.path.join( binary_dir, artifact)) _adb_push(local_test_build_artifacts, device_out_dir) # Push test data to the device. for source_path in test_data: _adb_push([os.path.join(CRASHPAD_DIR, source_path)], posixpath.join(device_temp_dir, source_path)) # Run the test on the device. Pass the test data root in the # environment. # # Because the test will not run with its standard output attached to a # pseudo-terminal device, Google Test will not normally enable colored # output, so mimic Google Test’s own logic for deciding whether to # enable color by checking this script’s own standard output connection. # The list of TERM values comes from Google Test’s # googletest/src/gtest.cc testing::internal::ShouldUseColor(). env = {'CRASHPAD_TEST_DATA_ROOT': device_temp_dir} gtest_color = os.environ.get('GTEST_COLOR') if gtest_color in ('auto', None): if (sys.stdout.isatty() and (os.environ.get('TERM') in ('xterm', 'xterm-color', 'xterm-256color', 'screen', 'screen-256color', 'tmux', 'tmux-256color', 'rxvt-unicode', 'rxvt-unicode-256color', 'linux', 'cygwin') or (IS_WINDOWS_HOST and _EnableVTProcessingOnWindowsConsole()))): gtest_color = 'yes' else: gtest_color = 'no' env['GTEST_COLOR'] = gtest_color _adb_shell([posixpath.join(device_out_dir, test)] + extra_command_line, env) finally: _adb_shell(['rm', '-rf', device_temp_dir]) def _RunOnIOSTarget(binary_dir, test, target_platform, is_xcuitest=False, gtest_filter=None): """Runs the given iOS |test| app on a simulator with the default OS version.""" target_platform = target_platform or 'iphoneos' if target_platform == 'iphoneos': dyld_insert_libraries = ( '__PLATFORMS__/iPhoneSimulator.platform/Developer/usr/lib/' 'libXCTestBundleInject.dylib') xcodebuild_platform = 'iOS Simulator' xcodebuild_device_name = 'iPhone 17' elif target_platform == 'tvos': dyld_insert_libraries = ( '__PLATFORMS__/AppleTVSimulator.platform/Developer/usr/lib/' 'libXCTestBundleInject.dylib') xcodebuild_platform = 'tvOS Simulator' xcodebuild_device_name = 'Apple TV 4K (3rd generation)' else: raise ValueError(f'Unexpected target_platform: {target_platform}') # E.g. __TESTROOT__/Debug-iphonesimulator. dyld_framework_path = '__TESTROOT__/%s' % os.path.basename(binary_dir) def xctest(binary_dir, test, gtest_filter=None): """Returns a dict containing the xctestrun data needed to run an XCTest-based test app.""" test_path = os.path.join(CRASHPAD_DIR, binary_dir) module_data = { 'TestBundlePath': os.path.join(test_path, test + '_module.xctest'), 'TestHostPath': os.path.join(test_path, test + '.app'), 'TestingEnvironmentVariables': { 'DYLD_FRAMEWORK_PATH': dyld_framework_path + ':', 'DYLD_INSERT_LIBRARIES': dyld_insert_libraries, 'DYLD_LIBRARY_PATH': dyld_framework_path, 'IDEiPhoneInternalTestBundleName': test + '.app', 'XCInjectBundleInto': '__TESTHOST__/' + test, } } if gtest_filter: module_data['CommandLineArguments'] = [ '--gtest_filter=' + gtest_filter ] return {test: module_data} def xcuitest(binary_dir, test): """Returns a dict containing the xctestrun data needed to run an XCUITest-based test app.""" test_path = os.path.join(CRASHPAD_DIR, binary_dir) runner_path = os.path.join(test_path, test + '_module-Runner.app') bundle_path = os.path.join(runner_path, 'PlugIns', test + '_module.xctest') target_app_path = os.path.join(test_path, test + '.app') module_data = { 'IsUITestBundle': True, 'SystemAttachmentLifetime': 'deleteOnSuccess', 'IsXCTRunnerHostedTestBundle': True, 'TestBundlePath': bundle_path, 'TestHostPath': runner_path, 'UITargetAppPath': target_app_path, 'DependentProductPaths': [ bundle_path, runner_path, target_app_path ], 'TestingEnvironmentVariables': { 'DYLD_FRAMEWORK_PATH': dyld_framework_path + ':', 'DYLD_LIBRARY_PATH': dyld_framework_path, 'XCInjectBundleInto': '__TESTHOST__/' + test + '_module-Runner', }, } return {test: module_data} with tempfile.NamedTemporaryFile() as f: import plistlib xctestrun_path = f.name + ".xctestrun" print(xctestrun_path) command = [ 'xcodebuild', 'test-without-building', '-xctestrun', xctestrun_path, '-destination', f'platform={xcodebuild_platform},name={xcodebuild_device_name}', ] with open(xctestrun_path, 'wb') as fp: if is_xcuitest: plistlib.dump(xcuitest(binary_dir, test), fp) if gtest_filter: command.append('-only-testing:' + test + '/' + gtest_filter) else: plistlib.dump(xctest(binary_dir, test, gtest_filter), fp) subprocess.check_call(command) # This script is primarily used from the waterfall so that the list of tests # that are run is maintained in-tree, rather than in a separate infrastructure # location in the recipe. def main(args): parser = argparse.ArgumentParser(description='Run Crashpad unittests.') parser.add_argument('binary_dir', help='Root of build dir') parser.add_argument('test', nargs='*', help='Specific test(s) to run.') parser.add_argument( '--gtest_filter', help='Google Test filter applied to Google Test binary runs.') args = parser.parse_args() # Tell 64-bit Windows tests where to find 32-bit test executables, for # cross-bitted testing. This relies on the fact that the GYP build by # default uses {Debug,Release} for the 32-bit build and {Debug,Release}_x64 # for the 64-bit build. This is not a universally valid assumption, and if # it’s not met, 64-bit tests that require 32-bit build output will disable # themselves dynamically. if (sys.platform == 'win32' and args.binary_dir.endswith('_x64') and 'CRASHPAD_TEST_32_BIT_OUTPUT' not in os.environ): binary_dir_32 = args.binary_dir[:-4] if os.path.isdir(binary_dir_32): os.environ['CRASHPAD_TEST_32_BIT_OUTPUT'] = binary_dir_32 target_os = _GetGNArgument('target_os', args.binary_dir) is_android = target_os == 'android' is_ios = target_os == 'ios' # |target_platform| is only set for iOS-based platforms. target_platform = _GetGNArgument('target_platform', args.binary_dir) tests = [ 'crashpad_client_test', 'crashpad_handler_test', 'crashpad_minidump_test', 'crashpad_snapshot_test', 'crashpad_test_test', 'crashpad_util_test', ] if is_android: android_device = os.environ.get('ANDROID_DEVICE') if not android_device: adb_devices = subprocess.check_output(['adb', 'devices'], shell=IS_WINDOWS_HOST, text=True) devices = [] for line in adb_devices.splitlines(): line = line if (line == 'List of devices attached' or re.match(r'^\* daemon .+ \*$', line) or line == ''): continue (device, ignore) = line.split('\t') devices.append(device) if len(devices) != 1: print("Please set ANDROID_DEVICE to your device's id", file=sys.stderr) return 2 android_device = devices[0] print('Using autodetected Android device:', android_device) elif is_ios: tests.append('ios_crash_xcuitests') elif IS_WINDOWS_HOST: tests.append('snapshot/win/end_to_end_test.py') if args.test: for t in args.test: if t not in tests: print('Unrecognized test:', t, file=sys.stderr) return 3 tests = args.test for test in tests: print('-' * 80) print(test) print('-' * 80) if test.endswith('.py'): subprocess.check_call([ sys.executable, os.path.join(CRASHPAD_DIR, test), args.binary_dir ]) else: extra_command_line = [] if args.gtest_filter: extra_command_line.append('--gtest_filter=' + args.gtest_filter) if is_android: _RunOnAndroidTarget(args.binary_dir, test, android_device, extra_command_line) elif is_ios: _RunOnIOSTarget(args.binary_dir, test, target_platform, is_xcuitest=test.startswith('ios'), gtest_filter=args.gtest_filter) else: subprocess.check_call([os.path.join(args.binary_dir, test)] + extra_command_line) return 0 if __name__ == '__main__': sys.exit(main(sys.argv[1:])) ================================================ FILE: build/test.gni ================================================ # Copyright 2017 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import("crashpad_buildconfig.gni") if (crashpad_is_in_chromium) { import("//testing/test.gni") } else { template("test") { if (crashpad_is_ios) { import("//third_party/mini_chromium/mini_chromium/build/ios/rules.gni") _launch_image_bundle_target = target_name + "_launch_image" bundle_data(_launch_image_bundle_target) { forward_variables_from(invoker, [ "testonly" ]) sources = [ "//build/ios/Default.png" ] outputs = [ "{{bundle_contents_dir}}/{{source_file_part}}" ] } ios_xctest_test(target_name) { testonly = true xctest_module_target = "//test/ios:google_test_runner" info_plist = "//build/ios/Unittest-Info.plist" extra_substitutions = [ "GTEST_BUNDLE_ID_SUFFIX=$target_name" ] forward_variables_from(invoker, "*") if (!defined(deps)) { deps = [] } deps += [ ":$_launch_image_bundle_target" ] } } else { executable(target_name) { testonly = true forward_variables_from(invoker, "*") } } } } ================================================ FILE: client/BUILD.gn ================================================ # Copyright 2015 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import("../build/crashpad_buildconfig.gni") crashpad_static_library("client") { sources = [ "crashpad_client.h", "prune_crash_reports.cc", "prune_crash_reports.h", "simulate_crash.h", ] if (crashpad_is_mac) { sources += [ "crashpad_client_mac.cc", "simulate_crash_mac.cc", "simulate_crash_mac.h", ] } if (crashpad_is_ios) { sources += [ "crash_handler_base_ios.cc", "crash_handler_base_ios.h", "crashpad_client_ios.cc", "ios_handler/exception_processor.h", "ios_handler/exception_processor.mm", "ios_handler/in_process_handler.cc", "ios_handler/in_process_handler.h", "ios_handler/in_process_intermediate_dump_handler.cc", "ios_handler/in_process_intermediate_dump_handler.h", "ios_handler/prune_intermediate_dumps_and_crash_reports_thread.cc", "ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h", "simulate_crash_ios.h", "upload_behavior_ios.h", ] if (!crashpad_is_tvos) { sources += [ "crash_handler_ios.cc", "crash_handler_ios.h", ] } else { sources += [ "crash_handler_tvos.cc", "crash_handler_tvos.h", ] } } if (crashpad_is_linux || crashpad_is_android) { sources += [ "crashpad_client_linux.cc", "simulate_crash_linux.h", ] } if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { sources += [ "client_argv_handling.cc", "client_argv_handling.h", ] } if (crashpad_is_win) { sources += [ "crashpad_client_win.cc", "simulate_crash_win.h", ] } if (crashpad_is_fuchsia) { sources += [ "crashpad_client_fuchsia.cc" ] } public_configs = [ "..:crashpad_config" ] public_deps = [ ":common", "$mini_chromium_source_parent:base", "../util", ] deps = [ ":common" ] if (crashpad_is_win) { libs = [ "rpcrt4.lib" ] cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union } if (crashpad_is_apple) { deps += [ "../build:apple_enable_arc" ] } if (crashpad_is_ios) { deps += [ "../handler:common", "../minidump", "../snapshot", ] } if (crashpad_is_linux || crashpad_is_android) { deps += [ "../third_party/lss" ] } if (crashpad_is_fuchsia) { deps += [ "../third_party/fuchsia" ] if (crashpad_is_in_fuchsia) { deps += [ "//sdk/lib/fdio" ] } } } static_library("common") { sources = [ "annotation.cc", "annotation.h", "annotation_list.cc", "annotation_list.h", "crash_report_database.cc", "crash_report_database.h", "crashpad_info.cc", "crashpad_info.h", "length_delimited_ring_buffer.h", "ring_buffer_annotation.h", "settings.cc", "settings.h", "simple_address_range_bag.h", "simple_string_dictionary.h", ] if (crashpad_is_apple) { sources += [ "crash_report_database_mac.mm" ] } if (crashpad_is_win) { sources += [ "crash_report_database_win.cc" ] } if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { sources += [ "crash_report_database_generic.cc", "crashpad_info_note.S", ] } public_configs = [ "..:crashpad_config" ] public_deps = [ "$mini_chromium_source_parent:base", "../util", ] deps = [ "../util" ] configs += [ "../build:flock_always_supported_defines" ] if (crashpad_is_apple) { deps += [ "../build:apple_enable_arc" ] } } crashpad_executable("ring_buffer_annotation_load_test") { testonly = true sources = [ "ring_buffer_annotation_load_test_main.cc" ] deps = [ ":client", "$mini_chromium_source_parent:base", "../tools:tool_support", ] } source_set("client_test") { testonly = true sources = [ "annotation_list_test.cc", "annotation_test.cc", "crash_report_database_test.cc", "crashpad_info_test.cc", "length_delimited_ring_buffer_test.cc", "prune_crash_reports_test.cc", "ring_buffer_annotation_test.cc", "settings_test.cc", "simple_address_range_bag_test.cc", "simple_string_dictionary_test.cc", ] if (crashpad_is_mac) { sources += [ "simulate_crash_mac_test.cc" ] } if (crashpad_is_win) { sources += [ "crashpad_client_win_test.cc" ] } if (crashpad_is_ios) { sources += [ "crashpad_client_ios_test.mm", "ios_handler/exception_processor_test.mm", "ios_handler/in_process_handler_test.cc", "ios_handler/in_process_intermediate_dump_handler_test.cc", ] } if (crashpad_is_linux || crashpad_is_android) { sources += [ "crashpad_client_linux_test.cc" ] } deps = [ ":client", "$mini_chromium_source_parent:base", "../compat", "../snapshot", "../test", "../third_party/googletest", "../third_party/googletest:googlemock", "../util", ] if (!crashpad_is_ios && !crashpad_is_fuchsia) { data_deps = [ "../handler:crashpad_handler" ] } if (crashpad_is_apple) { deps += [ "../build:apple_enable_arc" ] } if (crashpad_is_win) { data_deps += [ "../handler:crashpad_handler_console", "../handler/win/wer:crashpad_wer_handler", ] } if (crashpad_is_ios) { deps += [ "../minidump" ] } } if (crashpad_is_linux || crashpad_is_android) { source_set("pthread_create") { sources = [ "pthread_create_linux.cc" ] deps = [ ":client" ] } } ================================================ FILE: client/annotation.cc ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/annotation.h" #include #include "base/check_op.h" #include "client/annotation_list.h" namespace crashpad { static_assert(std::is_standard_layout::value, "Annotation must be POD"); // static constexpr size_t Annotation::kNameMaxLength; constexpr size_t Annotation::kValueMaxSize; void Annotation::SetSize(ValueSizeType size) { DCHECK_LT(size, kValueMaxSize); size_ = size; // Use Register() instead of Get() in case the calling module has not // explicitly initialized the annotation list, to avoid crashing. AnnotationList::Register()->Add(this); } void Annotation::Clear() { size_ = 0; } } // namespace crashpad ================================================ FILE: client/annotation.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_ANNOTATION_H_ #define CRASHPAD_CLIENT_ANNOTATION_H_ #include #include #include #include #include #include #include #include #include "base/check.h" #include "base/numerics/safe_conversions.h" #include "build/build_config.h" #include "util/synchronization/scoped_spin_guard.h" namespace crashpad { #if BUILDFLAG(IS_IOS) namespace internal { class InProcessIntermediateDumpHandler; } // namespace internal #endif class AnnotationList; //! \brief Base class for an annotation, which records a name-value pair of //! arbitrary data when set. //! //! After an annotation is declared, its `value_ptr_` will not be captured in a //! crash report until a call to \a SetSize() specifies how much data from the //! value should be recorded. //! //! Annotations should be declared with static storage duration. //! //! An example declaration and usage: //! //! \code //! // foo.cc: //! //! namespace { //! char g_buffer[1024]; //! crashpad::Annotation g_buffer_annotation( //! crashpad::Annotation::Type::kString, "buffer_head", g_buffer); //! } // namespace //! //! void OnBufferProduced(size_t n) { //! // Capture the head of the buffer, in case we crash when parsing it. //! g_buffer_annotation.SetSize(std::min(64, n)); //! //! // Start parsing the header. //! Frobinate(g_buffer, n); //! } //! \endcode //! //! Annotation objects are not inherently thread-safe. To manipulate them //! from multiple threads, external synchronization must be used. //! //! Annotation objects should never be destroyed. Once they are Set(), they //! are permanently referenced by a global object. class Annotation { public: //! \brief The maximum length of an annotation’s name, in bytes. //! Matches the behavior of Breakpad's SimpleStringDictionary. static constexpr size_t kNameMaxLength = 256; //! \brief The maximum size of an annotation’s value, in bytes. static constexpr size_t kValueMaxSize = 5 * 4096; //! \brief The type used for \a SetSize(). using ValueSizeType = uint32_t; //! \brief The type of data stored in the annotation. enum class Type : uint16_t { //! \brief An invalid annotation. Reserved for internal use. kInvalid = 0, //! \brief A `NUL`-terminated C-string. kString = 1, //! \brief Clients may declare their own custom types by using values //! greater than this. kUserDefinedStart = 0x8000, }; //! \brief Mode used to guard concurrent reads from writes. enum class ConcurrentAccessGuardMode : bool { //! \!brief Annotation does not guard reads from concurrent //! writes. Annotation values can be corrupted if the process crashes //! mid-write and the handler tries to read from the Annotation while //! being written to. kUnguarded = false, //! \!brief Annotation guards reads from concurrent writes using //! ScopedSpinGuard. Clients must use TryCreateScopedSpinGuard() //! before reading or writing the data in this Annotation. kScopedSpinGuard = true, }; //! \brief Creates a user-defined Annotation::Type. //! //! This exists to remove the casting overhead of `enum class`. //! //! \param[in] value A value used to create a user-defined type. //! //! \returns The value added to Type::kUserDefinedStart and casted. constexpr static Type UserDefinedType(uint16_t value) { using UnderlyingType = std::underlying_type::type; // MSVS 2015 doesn't have full C++14 support and complains about local // variables defined in a constexpr function, which is valid. Avoid them // and the also-problematic DCHECK until all the infrastructure is updated: // https://crbug.com/crashpad/201. #if !BUILDFLAG(IS_WIN) || (defined(_MSC_VER) && _MSC_VER >= 1910) const UnderlyingType start = static_cast(Type::kUserDefinedStart); const UnderlyingType user_type = start + value; DCHECK(user_type > start) << "User-defined Type is 0 or overflows"; return static_cast(user_type); #else return static_cast( static_cast(Type::kUserDefinedStart) + value); #endif } //! \brief Constructs a new annotation. //! //! Upon construction, the annotation will not be included in any crash //! reports until \sa SetSize() is called with a value greater than `0`. //! //! \param[in] type The data type of the value of the annotation. //! \param[in] name A `NUL`-terminated C-string name for the annotation. Names //! do not have to be unique, though not all crash processors may handle //! Annotations with the same name. Names should be constexpr data with //! static storage duration. //! \param[in] value_ptr A pointer to the value for the annotation. The //! pointer may not be changed once associated with an annotation, but //! the data may be mutated. constexpr Annotation(Type type, const char name[], void* value_ptr) : Annotation(type, name, value_ptr, ConcurrentAccessGuardMode::kUnguarded) {} Annotation(const Annotation&) = delete; Annotation& operator=(const Annotation&) = delete; //! \brief Specifies the number of bytes in \a value_ptr_ to include when //! generating a crash report. //! //! A size of `0` indicates that no value should be recorded and is the //! equivalent of calling \sa Clear(). //! //! This method does not mutate the data referenced by the annotation, it //! merely updates the annotation system's bookkeeping. //! //! Subclasses of this base class that provide additional Set methods to //! mutate the value of the annotation must call always call this method. //! //! \param[in] size The number of bytes. void SetSize(ValueSizeType size); //! \brief Marks the annotation as cleared, indicating the \a value_ptr_ //! should not be included in a crash report. //! //! This method does not mutate the data referenced by the annotation, it //! merely updates the annotation system's bookkeeping. void Clear(); //! \brief Tests whether the annotation has been set. bool is_set() const { return size_ > 0; } Type type() const { return type_; } ValueSizeType size() const { return size_; } const char* name() const { return name_; } const void* value() const { return value_ptr_; } ConcurrentAccessGuardMode concurrent_access_guard_mode() const { return concurrent_access_guard_mode_; } //! \brief If this Annotation guards concurrent access using ScopedSpinGuard, //! tries to obtain the spin guard and returns the result. //! //! \param[in] timeout_ns The timeout in nanoseconds after which to give up //! trying to obtain the spin guard. //! \return std::nullopt if the spin guard could not be obtained within //! timeout_ns, or the obtained spin guard otherwise. std::optional TryCreateScopedSpinGuard(uint64_t timeout_ns) { // This can't use DCHECK_EQ() because ostream doesn't support // operator<<(bool). DCHECK(concurrent_access_guard_mode_ == ConcurrentAccessGuardMode::kScopedSpinGuard); if (concurrent_access_guard_mode_ == ConcurrentAccessGuardMode::kUnguarded) { return std::nullopt; } return ScopedSpinGuard::TryCreateScopedSpinGuard(timeout_ns, spin_guard_state_); } protected: //! \brief Constructs a new annotation. //! //! Upon construction, the annotation will not be included in any crash //! reports until \sa SetSize() is called with a value greater than `0`. //! //! \param[in] type The data type of the value of the annotation. //! \param[in] name A `NUL`-terminated C-string name for the annotation. Names //! do not have to be unique, though not all crash processors may handle //! Annotations with the same name. Names should be constexpr data with //! static storage duration. //! \param[in] value_ptr A pointer to the value for the annotation. The //! pointer may not be changed once associated with an annotation, but //! the data may be mutated. //! \param[in] concurrent_access_guard_mode Mode used to guard concurrent //! reads from writes. constexpr Annotation(Type type, const char name[], void* value_ptr, ConcurrentAccessGuardMode concurrent_access_guard_mode) : link_node_(nullptr), name_(name), value_ptr_(value_ptr), size_(0), type_(type), concurrent_access_guard_mode_(concurrent_access_guard_mode), spin_guard_state_() {} friend class AnnotationList; #if BUILDFLAG(IS_IOS) friend class internal::InProcessIntermediateDumpHandler; #endif std::atomic& link_node() { return link_node_; } Annotation* GetLinkNode(std::memory_order order = std::memory_order_seq_cst) { return link_node_.load(order); } const Annotation* GetLinkNode( std::memory_order order = std::memory_order_seq_cst) const { return link_node_.load(order); } private: //! \brief Linked list next-node pointer. Accessed only by \sa AnnotationList. //! //! This will be null until the first call to \sa SetSize(), after which the //! presence of the pointer will prevent the node from being added to the //! list again. std::atomic link_node_; const char* const name_; void* const value_ptr_; ValueSizeType size_; const Type type_; //! \brief Mode used to guard concurrent reads from writes. const ConcurrentAccessGuardMode concurrent_access_guard_mode_; SpinGuardState spin_guard_state_; }; //! \brief An \sa Annotation that stores a `NUL`-terminated C-string value. //! //! The storage for the value is allocated by the annotation and the template //! parameter \a MaxSize controls the maxmium length for the value. //! //! It is expected that the string value be valid UTF-8, although this is not //! validated. template class StringAnnotation : public Annotation { public: //! \brief A constructor tag that enables braced initialization in C arrays. //! //! \sa StringAnnotation() enum class Tag { kArray }; //! \brief Constructs a new StringAnnotation with the given \a name. //! //! \param[in] name The Annotation name. constexpr explicit StringAnnotation(const char name[]) : Annotation(Type::kString, name, value_), value_() {} StringAnnotation(const StringAnnotation&) = delete; StringAnnotation& operator=(const StringAnnotation&) = delete; //! \brief Constructs a new StringAnnotation with the given \a name. //! //! This constructor takes the ArrayInitializerTag for use when //! initializing a C array of annotations. The main constructor is //! explicit and cannot be brace-initialized. As an example: //! //! \code //! static crashpad::StringAnnotation<32> annotations[] = { //! {"name-1", crashpad::StringAnnotation<32>::Tag::kArray}, //! {"name-2", crashpad::StringAnnotation<32>::Tag::kArray}, //! {"name-3", crashpad::StringAnnotation<32>::Tag::kArray}, //! }; //! \endcode //! //! \param[in] name The Annotation name. //! \param[in] tag A constructor tag. constexpr StringAnnotation(const char name[], Tag tag) : StringAnnotation(name) {} //! \brief Sets the Annotation's string value. //! //! \param[in] value The `NUL`-terminated C-string value. void Set(const char* value) { strncpy(value_, value, MaxSize); SetSize( std::min(MaxSize, base::saturated_cast(strlen(value)))); } //! \brief Sets the Annotation's string value. //! //! \param[in] string The string value. void Set(std::string_view string) { Annotation::ValueSizeType size = std::min(MaxSize, base::saturated_cast(string.size())); string = string.substr(0, size); std::copy(string.begin(), string.end(), value_); // Check for no embedded `NUL` characters. DCHECK(string.find('\0', /*pos=*/0) == std::string_view::npos) << "embedded NUL"; SetSize(size); } const std::string_view value() const { return std::string_view(value_, size()); } private: // This value is not `NUL`-terminated, since the size is stored by the base // annotation. char value_[MaxSize]; }; } // namespace crashpad #endif // CRASHPAD_CLIENT_ANNOTATION_H_ ================================================ FILE: client/annotation_list.cc ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/annotation_list.h" #include "base/check_op.h" #include "client/crashpad_info.h" namespace crashpad { template T* AnnotationList::IteratorBase::operator*() const { CHECK_NE(curr_, tail_); return curr_; } template T* AnnotationList::IteratorBase::operator->() const { CHECK_NE(curr_, tail_); return curr_; } template AnnotationList::IteratorBase& AnnotationList::IteratorBase::operator++() { CHECK_NE(curr_, tail_); curr_ = curr_->GetLinkNode(); return *this; } template AnnotationList::IteratorBase AnnotationList::IteratorBase::operator++( int) { T* const old_curr = curr_; ++(*this); return IteratorBase(old_curr, tail_); } template bool AnnotationList::IteratorBase::operator!=( const IteratorBase& other) const { return !(*this == other); } template AnnotationList::IteratorBase::IteratorBase(T* head, const Annotation* tail) : curr_(head), tail_(tail) {} template class AnnotationList::IteratorBase; template class AnnotationList::IteratorBase; AnnotationList::AnnotationList() : tail_pointer_(&tail_), head_(Annotation::Type::kInvalid, nullptr, nullptr), tail_(Annotation::Type::kInvalid, nullptr, nullptr) { head_.link_node().store(&tail_); } AnnotationList::~AnnotationList() {} // static AnnotationList* AnnotationList::Get() { return CrashpadInfo::GetCrashpadInfo()->annotations_list(); } // static AnnotationList* AnnotationList::Register() { AnnotationList* list = Get(); if (!list) { list = new AnnotationList(); CrashpadInfo::GetCrashpadInfo()->set_annotations_list(list); } return list; } void AnnotationList::Add(Annotation* annotation) { Annotation* null = nullptr; Annotation* head_next = head_.link_node().load(std::memory_order_relaxed); if (!annotation->link_node().compare_exchange_strong(null, head_next)) { // If |annotation|'s link node is not null, then it has been added to the // list already and no work needs to be done. return; } // Check that the annotation's name is less than the maximum size. This is // done here, since the Annotation constructor must be constexpr and this // path is taken once per annotation. DCHECK_LT(strlen(annotation->name_), Annotation::kNameMaxLength); // Update the head link to point to the new |annotation|. while (!head_.link_node().compare_exchange_weak(head_next, annotation)) { // Another thread has updated the head-next pointer, so try again with the // re-loaded |head_next|. annotation->link_node().store(head_next, std::memory_order_relaxed); } } AnnotationList::Iterator AnnotationList::begin() { return Iterator(head_.GetLinkNode(), tail_pointer_); } AnnotationList::ConstIterator AnnotationList::cbegin() const { return ConstIterator(head_.GetLinkNode(), tail_pointer_); } AnnotationList::Iterator AnnotationList::end() { return Iterator(&tail_, tail_pointer_); } AnnotationList::ConstIterator AnnotationList::cend() const { return ConstIterator(&tail_, tail_pointer_); } } // namespace crashpad ================================================ FILE: client/annotation_list.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_ANNOTATION_LIST_H_ #define CRASHPAD_CLIENT_ANNOTATION_LIST_H_ #include #include "build/build_config.h" #include "client/annotation.h" namespace crashpad { #if BUILDFLAG(IS_IOS) namespace internal { class InProcessIntermediateDumpHandler; } // namespace internal #endif //! \brief A list that contains all the currently set annotations. //! //! An instance of this class must be registered on the \a CrashpadInfo //! structure in order to use the annotations system. Once a list object has //! been registered on the CrashpadInfo, a different instance should not //! be used instead. class AnnotationList { public: AnnotationList(); AnnotationList(const AnnotationList&) = delete; AnnotationList& operator=(const AnnotationList&) = delete; ~AnnotationList(); //! \brief Returns the instance of the list that has been registered on the //! CrashapdInfo structure. static AnnotationList* Get(); //! \brief Returns the instance of the list, creating and registering //! it if one is not already set on the CrashapdInfo structure. static AnnotationList* Register(); //! \brief Adds \a annotation to the global list. This method does not need //! to be called by clients directly. The Annotation object will do so //! automatically. //! //! Once an annotation is added to the list, it is not removed. This is //! because the AnnotationList avoids the use of locks/mutexes, in case it is //! being manipulated in a compromised context. Instead, an Annotation keeps //! track of when it has been cleared, which excludes it from a crash report. //! This design also avoids linear scans of the list when repeatedly setting //! and/or clearing the value. void Add(Annotation* annotation); //! \brief An InputIterator for the AnnotationList. template class IteratorBase { public: using difference_type = signed int; using value_type = T*; using reference = T*; using pointer = void; using iterator_category = std::input_iterator_tag; IteratorBase(const IteratorBase& other) = default; IteratorBase(IteratorBase&& other) = default; ~IteratorBase() = default; IteratorBase& operator=(const IteratorBase& other) = default; IteratorBase& operator=(IteratorBase&& other) = default; T* operator*() const; T* operator->() const; IteratorBase& operator++(); IteratorBase operator++(int); bool operator==(const IteratorBase& other) const { return curr_ == other.curr_; } bool operator!=(const IteratorBase& other) const; private: friend class AnnotationList; IteratorBase(T* head, const Annotation* tail); T* curr_ = nullptr; const Annotation* tail_ = nullptr; }; using Iterator = IteratorBase; using ConstIterator = IteratorBase; //! \brief Returns an iterator to the first element of the annotation list. Iterator begin(); ConstIterator begin() const { return cbegin(); } ConstIterator cbegin() const; //! \brief Returns an iterator past the last element of the annotation list. Iterator end(); ConstIterator end() const { return cend(); } ConstIterator cend() const; protected: #if BUILDFLAG(IS_IOS) friend class internal::InProcessIntermediateDumpHandler; #endif //! \brief Returns a pointer to the tail node. const Annotation* tail_pointer() const { return tail_pointer_; } //! \brief Returns a pointer to the head element. const Annotation* head() const { return &head_; } private: // To make it easier for the handler to locate the dummy tail node, store the // pointer. Placed first for packing. const Annotation* const tail_pointer_; // Dummy linked-list head and tail elements of \a Annotation::Type::kInvalid. Annotation head_; Annotation tail_; }; } // namespace crashpad #endif // CRASHPAD_CLIENT_ANNOTATION_LIST_H_ ================================================ FILE: client/annotation_list_test.cc ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/annotation.h" #include #include #include #include #include #include "base/rand_util.h" #include "client/crashpad_info.h" #include "gtest/gtest.h" #include "util/misc/clock.h" #include "util/thread/thread.h" namespace crashpad { namespace test { namespace { #if (__cplusplus >= 202002L) template requires std::input_iterator void VerifyIsInputIterator(Iterator) {} #else template struct IsLegacyIteratorImpl { static constexpr bool value = std::is_copy_constructible_v && std::is_copy_assignable_v && std::is_destructible_v && std::is_swappable_v && // check that std::iterator_traits has the necessary types (check only one // needed as std::iterator is required to define only if all are defined) !std::is_same_v::reference, void> && std::is_same_v()), Iterator&> && !std::is_same_v()), void>; }; template struct IsLegacyInputIteratorImpl { static constexpr bool value = IsLegacyIteratorImpl::value && std::is_base_of_v< std::input_iterator_tag, typename std::iterator_traits::iterator_category> && std::is_convertible_v() != std::declval()), bool> && std::is_convertible_v() == std::declval()), bool> && std::is_same_v()), typename std::iterator_traits::reference> && std::is_same_v()), Iterator&> && std::is_same_v()++), Iterator> && std::is_same_v())), typename std::iterator_traits::reference>; }; template void VerifyIsInputIterator(Iterator) { static_assert(IsLegacyInputIteratorImpl::value); } #endif TEST(AnnotationListStatic, Register) { ASSERT_FALSE(AnnotationList::Get()); EXPECT_TRUE(AnnotationList::Register()); EXPECT_TRUE(AnnotationList::Get()); EXPECT_EQ(AnnotationList::Get(), AnnotationList::Register()); // This isn't expected usage of the AnnotationList API, but it is necessary // for testing. AnnotationList* list = AnnotationList::Get(); CrashpadInfo::GetCrashpadInfo()->set_annotations_list(nullptr); delete list; EXPECT_FALSE(AnnotationList::Get()); } class AnnotationList : public testing::Test { public: void SetUp() override { CrashpadInfo::GetCrashpadInfo()->set_annotations_list(&annotations_); } void TearDown() override { CrashpadInfo::GetCrashpadInfo()->set_annotations_list(nullptr); } // NOTE: Annotations should be declared at file-scope, but in order to test // them, they are declared as part of the test. These members are public so // they are accessible from global helpers. crashpad::StringAnnotation<8> one_{"First"}; crashpad::StringAnnotation<256> two_{"Second"}; crashpad::StringAnnotation<101> three_{"First"}; protected: using AllAnnotations = std::vector>; AllAnnotations CollectAnnotations() { AllAnnotations annotations; for (Annotation* curr : annotations_) { if (!curr->is_set()) continue; std::string value(static_cast(curr->value()), curr->size()); annotations.push_back(std::make_pair(curr->name(), value)); } return annotations; } bool ContainsNameValue(const AllAnnotations& annotations, const std::string& name, const std::string& value) { return std::find(annotations.begin(), annotations.end(), std::make_pair(name, value)) != annotations.end(); } crashpad::AnnotationList annotations_; }; TEST_F(AnnotationList, SetAndClear) { one_.Set("this is a value longer than 8 bytes"); AllAnnotations annotations = CollectAnnotations(); EXPECT_EQ(1u, annotations.size()); EXPECT_TRUE(ContainsNameValue(annotations, "First", "this is ")); one_.Clear(); EXPECT_EQ(0u, CollectAnnotations().size()); one_.Set("short"); two_.Set(std::string(500, 'A').data()); annotations = CollectAnnotations(); EXPECT_EQ(2u, annotations.size()); EXPECT_EQ(5u, one_.size()); EXPECT_EQ(256u, two_.size()); EXPECT_TRUE(ContainsNameValue(annotations, "First", "short")); EXPECT_TRUE(ContainsNameValue(annotations, "Second", std::string(256, 'A'))); } TEST_F(AnnotationList, DuplicateKeys) { ASSERT_EQ(0u, CollectAnnotations().size()); one_.Set("1"); three_.Set("2"); AllAnnotations annotations = CollectAnnotations(); EXPECT_EQ(2u, annotations.size()); EXPECT_TRUE(ContainsNameValue(annotations, "First", "1")); EXPECT_TRUE(ContainsNameValue(annotations, "First", "2")); one_.Clear(); annotations = CollectAnnotations(); EXPECT_EQ(1u, annotations.size()); } TEST_F(AnnotationList, IteratorSingleAnnotation) { ASSERT_EQ(annotations_.begin(), annotations_.end()); ASSERT_EQ(annotations_.cbegin(), annotations_.cend()); one_.Set("1"); auto iterator = annotations_.begin(); auto const_iterator = annotations_.cbegin(); ASSERT_NE(iterator, annotations_.end()); ASSERT_NE(const_iterator, annotations_.cend()); EXPECT_EQ(*iterator, &one_); EXPECT_EQ(*const_iterator, &one_); ++iterator; ++const_iterator; EXPECT_EQ(iterator, annotations_.end()); EXPECT_EQ(const_iterator, annotations_.cend()); } TEST_F(AnnotationList, IteratorMultipleAnnotationsInserted) { ASSERT_EQ(annotations_.begin(), annotations_.end()); ASSERT_EQ(annotations_.cbegin(), annotations_.cend()); one_.Set("1"); two_.Set("2"); // New annotations are inserted to the beginning of the list. Hence, |two_| // must be the first annotation, followed by |one_|. { auto iterator = annotations_.begin(); auto const_iterator = annotations_.cbegin(); ASSERT_NE(iterator, annotations_.end()); ASSERT_NE(const_iterator, annotations_.cend()); EXPECT_EQ(*iterator, &two_); EXPECT_EQ(*const_iterator, &two_); ++iterator; ++const_iterator; ASSERT_NE(iterator, annotations_.end()); ASSERT_NE(const_iterator, annotations_.cend()); EXPECT_EQ(*iterator, &one_); EXPECT_EQ(*const_iterator, &one_); ++iterator; ++const_iterator; EXPECT_EQ(iterator, annotations_.end()); EXPECT_EQ(const_iterator, annotations_.cend()); } } TEST_F(AnnotationList, IteratorMultipleAnnotationsInsertedAndRemoved) { ASSERT_EQ(annotations_.begin(), annotations_.end()); ASSERT_EQ(annotations_.cbegin(), annotations_.cend()); one_.Set("1"); two_.Set("2"); one_.Clear(); two_.Clear(); // Even after clearing, Annotations are still inserted in the list and // reachable via the iterators. auto iterator = annotations_.begin(); auto const_iterator = annotations_.cbegin(); ASSERT_NE(iterator, annotations_.end()); ASSERT_NE(const_iterator, annotations_.cend()); EXPECT_EQ(*iterator, &two_); EXPECT_EQ(*const_iterator, &two_); ++iterator; ++const_iterator; ASSERT_NE(iterator, annotations_.end()); ASSERT_NE(const_iterator, annotations_.cend()); EXPECT_EQ(*iterator, &one_); EXPECT_EQ(*const_iterator, &one_); ++iterator; ++const_iterator; EXPECT_EQ(iterator, annotations_.end()); EXPECT_EQ(const_iterator, annotations_.cend()); } TEST_F(AnnotationList, IteratorIsInputIterator) { one_.Set("1"); two_.Set("2"); // Check explicitly that the iterators meet the interface of an input // iterator. VerifyIsInputIterator(annotations_.begin()); VerifyIsInputIterator(annotations_.cbegin()); VerifyIsInputIterator(annotations_.end()); VerifyIsInputIterator(annotations_.cend()); // Additionally verify that std::distance accepts the iterators. It requires // the iterators to comply to the input iterator interface. EXPECT_EQ(std::distance(annotations_.begin(), annotations_.end()), 2); EXPECT_EQ(std::distance(annotations_.cbegin(), annotations_.cend()), 2); } class RaceThread : public Thread { public: explicit RaceThread(test::AnnotationList* test) : Thread(), test_(test) {} private: void ThreadMain() override { for (int i = 0; i <= 50; ++i) { if (i % 2 == 0) { test_->three_.Set("three"); test_->two_.Clear(); } else { test_->three_.Clear(); } SleepNanoseconds(base::RandInt(1, 1000)); } } test::AnnotationList* test_; }; TEST_F(AnnotationList, MultipleThreads) { ASSERT_EQ(0u, CollectAnnotations().size()); RaceThread other_thread(this); other_thread.Start(); for (int i = 0; i <= 50; ++i) { if (i % 2 == 0) { one_.Set("one"); two_.Set("two"); } else { one_.Clear(); } SleepNanoseconds(base::RandInt(1, 1000)); } other_thread.Join(); AllAnnotations annotations = CollectAnnotations(); EXPECT_GE(annotations.size(), 2u); EXPECT_LE(annotations.size(), 3u); EXPECT_TRUE(ContainsNameValue(annotations, "First", "one")); EXPECT_TRUE(ContainsNameValue(annotations, "First", "three")); if (annotations.size() == 3) { EXPECT_TRUE(ContainsNameValue(annotations, "Second", "two")); } } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/annotation_test.cc ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/annotation.h" #include #include #include "client/annotation_list.h" #include "client/crashpad_info.h" #include "gtest/gtest.h" #include "test/gtest_death.h" #include "util/misc/clock.h" #include "util/synchronization/scoped_spin_guard.h" #include "util/thread/thread.h" namespace crashpad { namespace test { namespace { class SpinGuardAnnotation final : public Annotation { public: SpinGuardAnnotation(Annotation::Type type, const char name[]) : Annotation(type, name, &value_, ConcurrentAccessGuardMode::kScopedSpinGuard) {} bool Set(bool value, uint64_t spin_guard_timeout_ns) { auto guard = TryCreateScopedSpinGuard(spin_guard_timeout_ns); if (!guard) { return false; } value_ = value; SetSize(sizeof(value_)); return true; } private: bool value_; }; class ScopedSpinGuardUnlockThread final : public Thread { public: ScopedSpinGuardUnlockThread(ScopedSpinGuard scoped_spin_guard, uint64_t sleep_time_ns) : scoped_spin_guard_(std::move(scoped_spin_guard)), sleep_time_ns_(sleep_time_ns) {} private: void ThreadMain() override { SleepNanoseconds(sleep_time_ns_); // Move the ScopedSpinGuard member into a local variable which is // destroyed when ThreadMain() returns. ScopedSpinGuard local_scoped_spin_guard(std::move(scoped_spin_guard_)); // After this point, local_scoped_spin_guard will be destroyed and unlocked. } ScopedSpinGuard scoped_spin_guard_; const uint64_t sleep_time_ns_; }; class Annotation : public testing::Test { public: void SetUp() override { CrashpadInfo::GetCrashpadInfo()->set_annotations_list(&annotations_); } void TearDown() override { CrashpadInfo::GetCrashpadInfo()->set_annotations_list(nullptr); } size_t AnnotationsCount() { size_t result = 0; for (auto* annotation : annotations_) { if (annotation->is_set()) ++result; } return result; } protected: crashpad::AnnotationList annotations_; }; TEST_F(Annotation, Basics) { constexpr crashpad::Annotation::Type kType = crashpad::Annotation::UserDefinedType(1); const char kName[] = "annotation 1"; char buffer[1024]; crashpad::Annotation annotation(kType, kName, buffer); EXPECT_FALSE(annotation.is_set()); EXPECT_EQ(0u, AnnotationsCount()); EXPECT_EQ(kType, annotation.type()); EXPECT_EQ(0u, annotation.size()); EXPECT_EQ(std::string(kName), annotation.name()); EXPECT_EQ(buffer, annotation.value()); annotation.SetSize(10); EXPECT_TRUE(annotation.is_set()); EXPECT_EQ(1u, AnnotationsCount()); EXPECT_EQ(10u, annotation.size()); EXPECT_EQ(&annotation, *annotations_.begin()); annotation.Clear(); EXPECT_FALSE(annotation.is_set()); EXPECT_EQ(0u, AnnotationsCount()); EXPECT_EQ(0u, annotation.size()); } TEST_F(Annotation, StringType) { crashpad::StringAnnotation<5> annotation("name"); EXPECT_FALSE(annotation.is_set()); EXPECT_EQ(crashpad::Annotation::Type::kString, annotation.type()); EXPECT_EQ(0u, annotation.size()); EXPECT_EQ(std::string("name"), annotation.name()); EXPECT_EQ(0u, annotation.value().size()); annotation.Set("test"); EXPECT_TRUE(annotation.is_set()); EXPECT_EQ(1u, AnnotationsCount()); EXPECT_EQ(4u, annotation.size()); EXPECT_EQ("test", annotation.value()); annotation.Set(std::string("loooooooooooong")); EXPECT_TRUE(annotation.is_set()); EXPECT_EQ(1u, AnnotationsCount()); EXPECT_EQ(5u, annotation.size()); EXPECT_EQ("loooo", annotation.value()); } TEST_F(Annotation, BaseAnnotationShouldNotSupportSpinGuard) { char buffer[1024]; crashpad::Annotation annotation( crashpad::Annotation::Type::kString, "no-spin-guard", buffer); EXPECT_EQ(annotation.concurrent_access_guard_mode(), crashpad::Annotation::ConcurrentAccessGuardMode::kUnguarded); #if !DCHECK_IS_ON() // This fails a DCHECK() in debug builds, so only test it when DCHECK() // is not on. EXPECT_EQ(std::nullopt, annotation.TryCreateScopedSpinGuard(0)); #endif } TEST_F(Annotation, CustomAnnotationShouldSupportSpinGuardAndSet) { constexpr crashpad::Annotation::Type kType = crashpad::Annotation::UserDefinedType(1); SpinGuardAnnotation spin_guard_annotation(kType, "spin-guard"); EXPECT_EQ(spin_guard_annotation.concurrent_access_guard_mode(), crashpad::Annotation::ConcurrentAccessGuardMode::kScopedSpinGuard); EXPECT_TRUE(spin_guard_annotation.Set(true, 0)); EXPECT_EQ(1U, spin_guard_annotation.size()); } TEST_F(Annotation, CustomAnnotationSetShouldFailIfRunConcurrently) { constexpr crashpad::Annotation::Type kType = crashpad::Annotation::UserDefinedType(1); SpinGuardAnnotation spin_guard_annotation(kType, "spin-guard"); auto guard = spin_guard_annotation.TryCreateScopedSpinGuard(0); EXPECT_NE(std::nullopt, guard); // This should fail, since the guard is already held and the timeout is 0. EXPECT_FALSE(spin_guard_annotation.Set(true, 0)); } TEST_F(Annotation, CustomAnnotationSetShouldSucceedIfSpinGuardUnlockedAsynchronously) { constexpr crashpad::Annotation::Type kType = crashpad::Annotation::UserDefinedType(1); SpinGuardAnnotation spin_guard_annotation(kType, "spin-guard"); auto guard = spin_guard_annotation.TryCreateScopedSpinGuard(0); EXPECT_NE(std::nullopt, guard); // Pass the guard off to a background thread which unlocks it after 1 ms. constexpr uint64_t kSleepTimeNs = 1000000; // 1 ms ScopedSpinGuardUnlockThread unlock_thread(std::move(guard.value()), kSleepTimeNs); unlock_thread.Start(); // Try to set the annotation with a 100 ms timeout. constexpr uint64_t kSpinGuardTimeoutNanos = 100000000; // 100 ms // This should succeed after 1 ms, since the timeout is much larger than the // time the background thread holds the guard. EXPECT_TRUE(spin_guard_annotation.Set(true, kSpinGuardTimeoutNanos)); unlock_thread.Join(); } TEST(StringAnnotation, ArrayOfString) { static crashpad::StringAnnotation<4> annotations[] = { {"test-1", crashpad::StringAnnotation<4>::Tag::kArray}, {"test-2", crashpad::StringAnnotation<4>::Tag::kArray}, {"test-3", crashpad::StringAnnotation<4>::Tag::kArray}, {"test-4", crashpad::StringAnnotation<4>::Tag::kArray}, }; for (auto& annotation : annotations) { EXPECT_FALSE(annotation.is_set()); } } #if DCHECK_IS_ON() TEST(AnnotationDeathTest, EmbeddedNUL) { crashpad::StringAnnotation<5> annotation("name"); EXPECT_DEATH_CHECK(annotation.Set(std::string("te\0st", 5)), "embedded NUL"); } #endif } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/client_argv_handling.cc ================================================ // Copyright 2018 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/client_argv_handling.h" #include "base/strings/stringprintf.h" namespace crashpad { namespace { std::string FormatArgumentString(const std::string& name, const std::string& value) { return base::StringPrintf("--%s=%s", name.c_str(), value.c_str()); } } // namespace std::vector BuildHandlerArgvStrings( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, const std::vector& attachments) { std::vector argv_strings(1, handler.value()); for (const auto& argument : arguments) { argv_strings.push_back(argument); } if (!database.empty()) { argv_strings.push_back(FormatArgumentString("database", database.value())); } if (!metrics_dir.empty()) { argv_strings.push_back( FormatArgumentString("metrics-dir", metrics_dir.value())); } if (!url.empty()) { argv_strings.push_back(FormatArgumentString("url", url)); } for (const auto& kv : annotations) { argv_strings.push_back( FormatArgumentString("annotation", kv.first + '=' + kv.second)); } for (const auto& attachment : attachments) { argv_strings.push_back( FormatArgumentString("attachment", attachment.value())); } return argv_strings; } void StringVectorToCStringVector(const std::vector& strings, std::vector* c_strings) { c_strings->clear(); c_strings->reserve(strings.size() + 1); for (const auto& str : strings) { c_strings->push_back(str.c_str()); } c_strings->push_back(nullptr); } } // namespace crashpad ================================================ FILE: client/client_argv_handling.h ================================================ // Copyright 2018 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_CLIENT_ARGV_HANDLING_H_ #define CRASHPAD_CLIENT_CLIENT_ARGV_HANDLING_H_ #include #include #include #include "base/files/file_path.h" namespace crashpad { //! \brief Builds a vector of arguments suitable for invoking a handler process //! based on arguments passed to StartHandler-type(). //! //! See StartHandlerAtCrash() for documentation on the input arguments. //! //! \return A vector of arguments suitable for starting the handler with. std::vector BuildHandlerArgvStrings( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, const std::vector& attachments = {}); //! \brief Flattens a string vector into a const char* vector suitable for use //! in an exec() call. //! //! \param[in] strings A vector of string data. This vector must remain valid //! for the lifetime of \a c_strings. //! \param[out] c_strings A vector of pointers to the string data in \a strings. void StringVectorToCStringVector(const std::vector& strings, std::vector* c_strings); } // namespace crashpad #endif // CRASHPAD_CLIENT_CLIENT_ARGV_HANDLING_H_ ================================================ FILE: client/crash_handler_base_ios.cc ================================================ // Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "client/crash_handler_base_ios.h" #include "base/logging.h" #include "util/posix/signals.h" namespace crashpad { CrashHandlerBase::CrashHandlerBase() = default; CrashHandlerBase::~CrashHandlerBase() = default; bool CrashHandlerBase::Initialize( const base::FilePath& database, const std::string& url, const std::map& annotations, internal::InProcessHandler::ProcessPendingReportsObservationCallback callback) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); if (!in_process_handler().Initialize(database, url, annotations, callback)) { LOG(ERROR) << "Unable to initialize Crashpad."; return false; } if (!DoInitialize()) { return false; } INITIALIZATION_STATE_SET_VALID(initialized_); return true; } void CrashHandlerBase::ProcessIntermediateDumps( const std::map& annotations, const UserStreamDataSources* user_stream_sources) { in_process_handler_.ProcessIntermediateDumps(annotations, user_stream_sources); } void CrashHandlerBase::ProcessIntermediateDump( const base::FilePath& file, const std::map& annotations) { in_process_handler_.ProcessIntermediateDump(file, annotations); } void CrashHandlerBase::DumpWithoutCrash(NativeCPUContext* context, bool process_dump) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); base::FilePath path; if (!in_process_handler_.DumpExceptionFromSimulatedMachException( context, kMachExceptionSimulated, &path)) { return; } if (process_dump) { in_process_handler_.ProcessIntermediateDump(path); } } void CrashHandlerBase::DumpWithoutCrashAtPath(NativeCPUContext* context, const base::FilePath& path) { in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath( context, kMachExceptionSimulated, path); } void CrashHandlerBase::StartProcessingPendingReports( UploadBehavior upload_behavior) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); in_process_handler_.StartProcessingPendingReports(upload_behavior); } void CrashHandlerBase::SetExceptionCallbackForTesting(void (*callback)()) { in_process_handler_.SetExceptionCallbackForTesting(callback); } void CrashHandlerBase::HandleAndReraiseSignal(int signo, siginfo_t* siginfo, ucontext_t* context, struct sigaction* old_action) { in_process_handler_.DumpExceptionFromSignal(siginfo, context); // Always call system handler. Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action); } void CrashHandlerBase::HandleUncaughtNSException(const uint64_t* frames, const size_t num_frames) { in_process_handler_.DumpExceptionFromNSExceptionWithFrames(frames, num_frames); // After uncaught exceptions are reported, the system immediately triggers a // call to std::terminate()/abort(). Remove the abort handler so a second // dump isn't generated. CHECK(Signals::InstallDefaultHandler(SIGABRT)); } void CrashHandlerBase::HandleUncaughtNSExceptionWithContext( NativeCPUContext* context) { base::FilePath path; in_process_handler_.DumpExceptionFromSimulatedMachException( context, kMachExceptionFromNSException, &path); // After uncaught exceptions are reported, the system immediately triggers a // call to std::terminate()/abort(). Remove the abort handler so a second // dump isn't generated. CHECK(Signals::InstallDefaultHandler(SIGABRT)); } void CrashHandlerBase::HandleUncaughtNSExceptionWithContextAtPath( NativeCPUContext* context, const base::FilePath& path) { in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath( context, kMachExceptionFromNSException, path); } bool CrashHandlerBase::MoveIntermediateDumpAtPathToPending( const base::FilePath& path) { if (in_process_handler_.MoveIntermediateDumpAtPathToPending(path)) { // After uncaught exceptions are reported, the system immediately triggers // a call to std::terminate()/abort(). Remove the abort handler so a // second dump isn't generated. CHECK(Signals::InstallDefaultHandler(SIGABRT)); return true; } return false; } } // namespace crashpad ================================================ FILE: client/crash_handler_base_ios.h ================================================ // Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef CRASHPAD_CLIENT_CRASH_HANDLER_BASE_IOS_H_ #define CRASHPAD_CLIENT_CRASH_HANDLER_BASE_IOS_H_ #include #include #include #include #include "base/files/file_path.h" #include "client/ios_handler/exception_processor.h" #include "client/ios_handler/in_process_handler.h" #include "client/upload_behavior_ios.h" #include "util/misc/capture_context.h" #include "util/misc/initialization_state_dcheck.h" namespace crashpad { // Base class shared by the iOS and tvOS CrashHandler implementations. class CrashHandlerBase : public ObjcExceptionDelegate { public: CrashHandlerBase(const CrashHandlerBase&) = delete; CrashHandlerBase& operator=(const CrashHandlerBase&) = delete; bool Initialize( const base::FilePath& database, const std::string& url, const std::map& annotations, internal::InProcessHandler::ProcessPendingReportsObservationCallback callback); void ProcessIntermediateDumps( const std::map& annotations, const UserStreamDataSources* user_stream_sources); void ProcessIntermediateDump( const base::FilePath& file, const std::map& annotations); void DumpWithoutCrash(NativeCPUContext* context, bool process_dump); void DumpWithoutCrashAtPath(NativeCPUContext* context, const base::FilePath& path); void StartProcessingPendingReports(UploadBehavior upload_behavior); void SetExceptionCallbackForTesting(void (*callback)()); protected: CrashHandlerBase(); virtual ~CrashHandlerBase(); // Subclasses are expected to install signal handlers and set up Mach ports in // this function. virtual bool DoInitialize() = 0; void HandleAndReraiseSignal(int signo, siginfo_t* siginfo, ucontext_t* context, struct sigaction* old_action); internal::InProcessHandler& in_process_handler() { return in_process_handler_; } private: // ObjcExceptionDelegate overrides: void HandleUncaughtNSException(const uint64_t* frames, const size_t num_frames) override; void HandleUncaughtNSExceptionWithContext(NativeCPUContext* context) override; void HandleUncaughtNSExceptionWithContextAtPath( NativeCPUContext* context, const base::FilePath& path) override; bool MoveIntermediateDumpAtPathToPending(const base::FilePath& path) override; internal::InProcessHandler in_process_handler_; InitializationStateDcheck initialized_; }; } // namespace crashpad #endif // CRASHPAD_CLIENT_CRASH_HANDLER_BASE_IOS_H_ ================================================ FILE: client/crash_handler_ios.cc ================================================ // Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "client/crash_handler_ios.h" #include #include #include #include "base/apple/mach_logging.h" #include "util/ios/raw_logging.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" #include "util/mach/mach_message_server.h" #include "util/posix/signals.h" namespace { bool IsBeingDebugged() { int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; size_t len = sizeof(kinfo_proc); kinfo_proc kern_proc_info; if (sysctl(mib, std::size(mib), &kern_proc_info, &len, nullptr, 0) == 0) { return kern_proc_info.kp_proc.p_flag & P_TRACED; } return false; } } // namespace namespace crashpad { CrashHandler::ThreadSafeScopedMachPortWithReceiveRight:: ThreadSafeScopedMachPortWithReceiveRight() : port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)) {} CrashHandler::ThreadSafeScopedMachPortWithReceiveRight:: ~ThreadSafeScopedMachPortWithReceiveRight() { reset(); } mach_port_t CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::get() { return port_.load(); } void CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::reset() { mach_port_t old_port = port_.exchange(MACH_PORT_NULL); if (old_port == MACH_PORT_NULL) { // Already reset, nothing to do. return; } kern_return_t kr = mach_port_mod_refs( mach_task_self(), old_port, MACH_PORT_RIGHT_RECEIVE, -1); MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) << "ThreadSafeScopedMachPortWithReceiveRight mach_port_mod_refs"; kr = mach_port_deallocate(mach_task_self(), old_port); MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) << "ThreadSafeScopedMachPortWithReceiveRight mach_port_deallocate"; } CrashHandler* CrashHandler::instance_ = nullptr; CrashHandler::CrashHandler() = default; CrashHandler::~CrashHandler() { UninstallObjcExceptionPreprocessor(); // Reset the SIGPIPE handler only if the current handler is the // one installed by DoInitialize(). In other words, if an // application has set its own SIGPIPE handler after initializing // Crashpad, there is no need to change the signal handler here. struct sigaction sa; if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_sigaction == CatchAndReraiseSignalDefaultAction) { Signals::InstallDefaultHandler(SIGPIPE); } Signals::InstallDefaultHandler(SIGABRT); UninstallMachExceptionHandler(); } // static CrashHandler* CrashHandler::Get() { if (!instance_) instance_ = new CrashHandler(); return instance_; } // static void CrashHandler::ResetForTesting() { delete instance_; instance_ = nullptr; } bool CrashHandler::DoInitialize() { if (!InstallMachExceptionHandler() || // xnu turns hardware faults into Mach exceptions, so the only signal // left to register is SIGABRT, which never starts off as a hardware // fault. Installing a handler for other signals would lead to // recording exceptions twice. As a consequence, Crashpad will not // generate intermediate dumps for anything manually calling // raise(SIG*). In practice, this doesn’t actually happen for crash // signals that originate as hardware faults. !Signals::InstallHandler( SIGABRT, CatchAndReraiseSignal, 0, &old_action_)) { return false; } // For applications that haven't ignored or set a handler for SIGPIPE: // It’s OK for an application to set its own SIGPIPE handler (including // SIG_IGN) before initializing Crashpad, because Crashpad will discover the // existing handler and not install its own. // It’s OK for Crashpad to install its own SIGPIPE handler and for the // application to subsequently install its own (including SIG_IGN) // afterwards, because its handler will replace Crashpad’s. // This is useful to cover the default situation where nobody installs a // SIGPIPE handler and the disposition is at SIG_DFL, because SIGPIPE is a // “kill” signal (bsd/sys/signalvar.h sigprop). In that case, without // Crashpad, SIGPIPE results in a silent and unreported kill (and not even // ReportCrash will record it), but developers probably want to be alerted to // the condition. struct sigaction sa; if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_handler == SIG_DFL) { Signals::InstallHandler( SIGPIPE, CatchAndReraiseSignalDefaultAction, 0, nullptr); } InstallObjcExceptionPreprocessor(this); return true; } bool CrashHandler::InstallMachExceptionHandler() { mach_port_t exception_port = exception_port_.get(); if (exception_port == MACH_PORT_NULL) { return false; } kern_return_t kr = mach_port_insert_right(mach_task_self(), exception_port, exception_port, MACH_MSG_TYPE_MAKE_SEND); if (kr != KERN_SUCCESS) { MACH_LOG(ERROR, kr) << "mach_port_insert_right"; return false; } // TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT. // Until then, remove |EXC_MASK_BREAKPOINT| while attached to a debugger. exception_mask_t mask = ExcMaskAll() & ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_RPC_ALERT | EXC_MASK_GUARD | (IsBeingDebugged() ? EXC_MASK_BREAKPOINT : 0)); exception_behavior_t behavior = EXCEPTION_STATE_IDENTITY; if (__builtin_available(iOS 18.0, *)) { behavior = EXCEPTION_STATE_IDENTITY_PROTECTED; } ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); if (!exception_ports.GetExceptionPorts(mask, &original_handlers_) || !exception_ports.SetExceptionPort(mask, exception_port, behavior | MACH_EXCEPTION_CODES, MACHINE_THREAD_STATE)) { return false; } mach_handler_running_ = true; Start(); return true; } void CrashHandler::UninstallMachExceptionHandler() { mach_handler_running_ = false; exception_port_.reset(); Join(); } // Thread: void CrashHandler::ThreadMain() { UniversalMachExcServer universal_mach_exc_server(this); while (mach_handler_running_) { mach_msg_return_t mr = MachMessageServer::Run(&universal_mach_exc_server, exception_port_.get(), MACH_MSG_OPTION_NONE, MachMessageServer::kPersistent, MachMessageServer::kReceiveLargeIgnore, kMachMessageTimeoutWaitIndefinitely); MACH_CHECK( mach_handler_running_ ? mr == MACH_SEND_INVALID_DEST // This shouldn't happen for // exception messages that come // from the kernel itself, but if // something else in-process sends // exception messages and breaks, // handle that case. : (mr == MACH_RCV_PORT_CHANGED || // Port was closed while the // thread was listening. mr == MACH_RCV_INVALID_NAME), // Port was closed before the // thread started listening. mr) << "MachMessageServer::Run"; } } // UniversalMachExcServer::Interface: kern_return_t CrashHandler::CatchMachException( exception_behavior_t behavior, exception_handler_t exception_port, thread_t thread, task_t task, exception_type_t exception, const mach_exception_data_type_t* code, mach_msg_type_number_t code_count, thread_state_flavor_t* flavor, ConstThreadState old_state, mach_msg_type_number_t old_state_count, thread_state_t new_state, mach_msg_type_number_t* new_state_count, const mach_msg_trailer_t* trailer, bool* destroy_complex_request) { *destroy_complex_request = true; // TODO(justincohen): Forward exceptions to original_handlers_ with // UniversalExceptionRaise. // iOS shouldn't have any child processes, but just in case, those will // inherit the task exception ports, and this process isn’t prepared to // handle them if (task != mach_task_self()) { CRASHPAD_RAW_LOG("MachException task != mach_task_self()"); return KERN_FAILURE; } HandleMachException(behavior, thread, exception, code, code_count, *flavor, old_state, old_state_count); // Respond with KERN_FAILURE so the system will continue to handle this // exception. xnu will turn this Mach exception into a signal and take the // default action to terminate the process. However, if sigprocmask is // called before this Mach exception returns (such as by another thread // calling abort, see: Libc-1506.40.4/stdlib/FreeBSD/abort.c), the Mach // exception will be converted into a signal but delivery will be blocked. // Since concurrent exceptions lead to the losing thread sleeping // indefinitely, if the abort thread never returns, the thread that // triggered this Mach exception will repeatedly trap and the process will // never terminate. If the abort thread didn’t have a user-space signal // handler that slept forever, the abort would terminate the process even if // all other signals had been blocked. Instead, unblock all signals // corresponding to all Mach exceptions Crashpad is registered for before // returning KERN_FAILURE. There is still racy behavior possible with this // call to sigprocmask, but the repeated calls to CatchMachException here // will eventually lead to termination. sigset_t unblock_set; sigemptyset(&unblock_set); sigaddset(&unblock_set, SIGILL); // EXC_BAD_INSTRUCTION sigaddset(&unblock_set, SIGTRAP); // EXC_BREAKPOINT sigaddset(&unblock_set, SIGFPE); // EXC_ARITHMETIC sigaddset(&unblock_set, SIGBUS); // EXC_BAD_ACCESS sigaddset(&unblock_set, SIGSEGV); // EXC_BAD_ACCESS if (sigprocmask(SIG_UNBLOCK, &unblock_set, nullptr) != 0) { CRASHPAD_RAW_LOG("sigprocmask"); } return KERN_FAILURE; } void CrashHandler::HandleMachException(exception_behavior_t behavior, thread_t thread, exception_type_t exception, const mach_exception_data_type_t* code, mach_msg_type_number_t code_count, thread_state_flavor_t flavor, ConstThreadState old_state, mach_msg_type_number_t old_state_count) { in_process_handler().DumpExceptionFromMachException(behavior, thread, exception, code, code_count, flavor, old_state, old_state_count); } // static void CrashHandler::CatchAndReraiseSignal(int signo, siginfo_t* siginfo, void* context) { Get()->HandleAndReraiseSignal(signo, siginfo, reinterpret_cast(context), &(Get()->old_action_)); } // static void CrashHandler::CatchAndReraiseSignalDefaultAction(int signo, siginfo_t* siginfo, void* context) { Get()->HandleAndReraiseSignal( signo, siginfo, reinterpret_cast(context), nullptr); } } // namespace crashpad ================================================ FILE: client/crash_handler_ios.h ================================================ // Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef CRASHPAD_CLIENT_CRASH_HANDLER_IOS_H_ #define CRASHPAD_CLIENT_CRASH_HANDLER_IOS_H_ #include #include "client/crash_handler_base_ios.h" #include "util/mach/exc_server_variants.h" #include "util/mach/exception_ports.h" #include "util/thread/thread.h" namespace crashpad { // A base class for signal handler and Mach exception server. class CrashHandler : public Thread, public UniversalMachExcServer::Interface, public CrashHandlerBase { public: CrashHandler(const CrashHandler&) = delete; CrashHandler& operator=(const CrashHandler&) = delete; static CrashHandler* Get(); static void ResetForTesting(); private: // Thread-safe version of `base::apple::ScopedMachReceiveRight` which // allocates the Mach port upon construction and deallocates it upon // destruction. class ThreadSafeScopedMachPortWithReceiveRight { public: ThreadSafeScopedMachPortWithReceiveRight(); ThreadSafeScopedMachPortWithReceiveRight( const ThreadSafeScopedMachPortWithReceiveRight&) = delete; ThreadSafeScopedMachPortWithReceiveRight& operator=( const ThreadSafeScopedMachPortWithReceiveRight&) = delete; ~ThreadSafeScopedMachPortWithReceiveRight(); mach_port_t get(); void reset(); private: std::atomic port_; }; CrashHandler(); ~CrashHandler() override; bool DoInitialize() override; bool InstallMachExceptionHandler(); void UninstallMachExceptionHandler(); // Thread: void ThreadMain() override; // UniversalMachExcServer::Interface: kern_return_t CatchMachException(exception_behavior_t behavior, exception_handler_t exception_port, thread_t thread, task_t task, exception_type_t exception, const mach_exception_data_type_t* code, mach_msg_type_number_t code_count, thread_state_flavor_t* flavor, ConstThreadState old_state, mach_msg_type_number_t old_state_count, thread_state_t new_state, mach_msg_type_number_t* new_state_count, const mach_msg_trailer_t* trailer, bool* destroy_complex_request) override; void HandleMachException(exception_behavior_t behavior, thread_t thread, exception_type_t exception, const mach_exception_data_type_t* code, mach_msg_type_number_t code_count, thread_state_flavor_t flavor, ConstThreadState old_state, mach_msg_type_number_t old_state_count); // The signal handler installed at OS-level. static void CatchAndReraiseSignal(int signo, siginfo_t* siginfo, void* context); static void CatchAndReraiseSignalDefaultAction(int signo, siginfo_t* siginfo, void* context); ThreadSafeScopedMachPortWithReceiveRight exception_port_; ExceptionPorts::ExceptionHandlerVector original_handlers_; struct sigaction old_action_ = {}; static CrashHandler* instance_; std::atomic mach_handler_running_ = false; }; } // namespace crashpad #endif // CRASHPAD_CLIENT_CRASH_HANDLER_IOS_H_ ================================================ FILE: client/crash_handler_tvos.cc ================================================ // Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "client/crash_handler_tvos.h" #include #include "util/posix/signals.h" #include "util/thread/thread.h" namespace crashpad { CrashHandler* CrashHandler::instance_ = nullptr; CrashHandler::CrashHandler() = default; CrashHandler::~CrashHandler() { UninstallObjcExceptionPreprocessor(); for (int signo = 1; signo < NSIG; ++signo) { if (!Signals::IsCrashSignal(signo)) { Signals::RestoreOrResetHandler(signo, old_actions_.ActionForSignal(signo)); } else if (signo == SIGPIPE) { // Reset the SIGPIPE handler only if the current handler is the one // installed by DoInitialize(). In other words, if an application has set // its own SIGPIPE handler after initializing Crashpad, there is no need // to change the signal handler here. struct sigaction sa; if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_sigaction == CatchAndReraiseSignal) { Signals::InstallDefaultHandler(SIGPIPE); } } } } // static CrashHandler* CrashHandler::Get() { if (!instance_) { instance_ = new CrashHandler; } return instance_; } // static void CrashHandler::ResetForTesting() { delete instance_; instance_ = nullptr; } uint64_t CrashHandler::GetThreadIdForTesting() { return Thread::GetThreadIdForTesting(); } bool CrashHandler::DoInitialize() { if (!Signals::InstallCrashHandlers(CatchAndReraiseSignal, /*flags=*/0, &old_actions_)) { return false; } // For applications that haven't ignored or set a handler for SIGPIPE: // It’s OK for an application to set its own SIGPIPE handler (including // SIG_IGN) before initializing Crashpad, because Crashpad will discover the // existing handler and not install its own. // It’s OK for Crashpad to install its own SIGPIPE handler and for the // application to subsequently install its own (including SIG_IGN) // afterwards, because its handler will replace Crashpad’s. // This is useful to cover the default situation where nobody installs a // SIGPIPE handler and the disposition is at SIG_DFL, because SIGPIPE is a // “kill” signal (bsd/sys/signalvar.h sigprop). In that case, without // Crashpad, SIGPIPE results in a silent and unreported kill (and not even // ReportCrash will record it), but developers probably want to be alerted to // the condition. struct sigaction sa; if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_handler == SIG_DFL) { Signals::InstallHandler(SIGPIPE, CatchAndReraiseSignal, 0, nullptr); } InstallObjcExceptionPreprocessor(this); return true; } // static void CrashHandler::CatchAndReraiseSignal(int signo, siginfo_t* siginfo, void* context) { CrashHandler* self = Get(); self->HandleAndReraiseSignal(signo, siginfo, reinterpret_cast(context), self->old_actions_.ActionForSignal(signo)); } } // namespace crashpad ================================================ FILE: client/crash_handler_tvos.h ================================================ // Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef CRASHPAD_CLIENT_CRASH_HANDLER_TVOS_H_ #define CRASHPAD_CLIENT_CRASH_HANDLER_TVOS_H_ #include "client/crash_handler_base_ios.h" #include "util/posix/signals.h" namespace crashpad { // A crash handler based on POSIX signals. // The APIs to handle Mach exceptions are not available to third-party // applications on tvOS. class CrashHandler final : public CrashHandlerBase { public: CrashHandler(const CrashHandler&) = delete; CrashHandler& operator=(const CrashHandler&) = delete; static CrashHandler* Get(); static void ResetForTesting(); uint64_t GetThreadIdForTesting(); private: CrashHandler(); ~CrashHandler() override; bool DoInitialize() override; // The signal handler installed at OS-level. static void CatchAndReraiseSignal(int signo, siginfo_t* siginfo, void* context); Signals::OldActions old_actions_ = {}; static CrashHandler* instance_; }; } // namespace crashpad #endif // CRASHPAD_CLIENT_CRASH_HANDLER_TVOS_H_ ================================================ FILE: client/crash_report_database.cc ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crash_report_database.h" #include #include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "util/file/directory_reader.h" #include "util/file/filesystem.h" namespace crashpad { namespace { constexpr base::FilePath::CharType kAttachmentsDirectory[] = FILE_PATH_LITERAL("attachments"); bool AttachmentNameIsOK(const std::string& name) { for (const char c : name) { if (c != '_' && c != '-' && c != '.' && !isalnum(c)) return false; } return true; } } // namespace CrashReportDatabase::Report::Report() : uuid(), file_path(), id(), creation_time(0), uploaded(false), last_upload_attempt_time(0), upload_attempts(0), upload_explicitly_requested(false), total_size(0u) {} CrashReportDatabase::NewReport::NewReport() : writer_(std::make_unique()), file_remover_(), attachment_writers_(), attachment_removers_(), uuid_(), database_() {} CrashReportDatabase::NewReport::~NewReport() = default; bool CrashReportDatabase::NewReport::Initialize( CrashReportDatabase* database, const base::FilePath& directory, const base::FilePath::StringType& extension) { database_ = database; if (!uuid_.InitializeWithNew()) { return false; } #if BUILDFLAG(IS_WIN) const std::wstring uuid_string = uuid_.ToWString(); #else const std::string uuid_string = uuid_.ToString(); #endif const base::FilePath path = directory.Append(uuid_string + extension); if (!writer_->Open( path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)) { return false; } file_remover_.reset(path); return true; } FileReaderInterface* CrashReportDatabase::NewReport::Reader() { auto reader = std::make_unique(); if (!reader->Open(file_remover_.get())) { return nullptr; } reader_ = std::move(reader); return reader_.get(); } FileWriter* CrashReportDatabase::NewReport::AddAttachment( const std::string& name) { if (!AttachmentNameIsOK(name)) { LOG(ERROR) << "invalid name for attachment " << name; return nullptr; } base::FilePath report_attachments_dir = database_->AttachmentsPath(uuid_); if (!LoggingCreateDirectory( report_attachments_dir, FilePermissions::kOwnerOnly, true)) { return nullptr; } #if BUILDFLAG(IS_WIN) const std::wstring name_string = base::UTF8ToWide(name); #else const std::string name_string = name; #endif base::FilePath attachment_path = report_attachments_dir.Append(name_string); auto writer = std::make_unique(); if (!writer->Open(attachment_path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)) { return nullptr; } attachment_writers_.emplace_back(std::move(writer)); attachment_removers_.emplace_back(ScopedRemoveFile(attachment_path)); return attachment_writers_.back().get(); } void CrashReportDatabase::UploadReport::InitializeAttachments() { base::FilePath report_attachments_dir = database_->AttachmentsPath(uuid); DirectoryReader dir_reader; if (!dir_reader.Open(report_attachments_dir)) { return; } base::FilePath filename; DirectoryReader::Result dir_result; while ((dir_result = dir_reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { const base::FilePath filepath(report_attachments_dir.Append(filename)); std::unique_ptr file_reader(std::make_unique()); if (!file_reader->Open(filepath)) { continue; } attachment_readers_.emplace_back(std::move(file_reader)); #if BUILDFLAG(IS_WIN) const std::string name_string = base::WideToUTF8(filename.value()); #else const std::string name_string = filename.value(); #endif attachment_map_[name_string] = attachment_readers_.back().get(); } } CrashReportDatabase::UploadReport::UploadReport() : Report(), reader_(std::make_unique()), database_(nullptr), attachment_readers_(), attachment_map_(), report_metrics_(false) {} CrashReportDatabase::UploadReport::~UploadReport() { if (database_) { database_->RecordUploadAttempt(this, false, std::string()); } } bool CrashReportDatabase::UploadReport::Initialize(const base::FilePath& path, CrashReportDatabase* db) { database_ = db; InitializeAttachments(); return reader_->Open(path); } CrashReportDatabase::OperationStatus CrashReportDatabase::RecordUploadComplete( std::unique_ptr report_in, const std::string& id) { UploadReport* report = const_cast(report_in.get()); report->database_ = nullptr; return RecordUploadAttempt(report, true, id); } base::FilePath CrashReportDatabase::AttachmentsPath(const UUID& uuid) { #if BUILDFLAG(IS_WIN) const std::wstring uuid_string = uuid.ToWString(); #else const std::string uuid_string = uuid.ToString(); #endif return DatabasePath().Append(kAttachmentsDirectory).Append(uuid_string); } base::FilePath CrashReportDatabase::AttachmentsRootPath() { return DatabasePath().Append(kAttachmentsDirectory); } void CrashReportDatabase::RemoveAttachmentsByUUID(const UUID& uuid) { base::FilePath report_attachment_dir = AttachmentsPath(uuid); if (!IsDirectory(report_attachment_dir, /*allow_symlinks=*/false)) { return; } DirectoryReader reader; if (!reader.Open(report_attachment_dir)) { return; } base::FilePath filename; DirectoryReader::Result result; while ((result = reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { const base::FilePath attachment_path( report_attachment_dir.Append(filename)); LoggingRemoveFile(attachment_path); } LoggingRemoveDirectory(report_attachment_dir); } } // namespace crashpad ================================================ FILE: client/crash_report_database.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_ #define CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_ #include #include #include #include #include #include #include "base/files/file_path.h" #include "util/file/file_io.h" #include "util/file/file_reader.h" #include "util/file/file_writer.h" #include "util/file/scoped_remove_file.h" #include "util/misc/metrics.h" #include "util/misc/uuid.h" namespace crashpad { class Settings; class SettingsReader; //! \brief An interface for managing a collection of crash report files and //! metadata associated with the crash reports. //! //! All Report objects that are returned by this class are logically const. //! They are snapshots of the database at the time the query was run, and the //! data returned is liable to change after the query is executed. //! //! The lifecycle of a crash report has three stages: //! //! 1. New: A crash report is created with PrepareNewCrashReport(), the //! the client then writes the report, and then calls //! FinishedWritingCrashReport() to make the report Pending. //! 2. Pending: The report has been written but has not been locally //! processed, or it was has been brought back from 'Completed' state by //! user request. //! 3. Completed: The report has been locally processed, either by uploading //! it to a collection server and calling RecordUploadComplete(), or by //! calling SkipReportUpload(). class CrashReportDatabase { public: //! \brief A crash report record. //! //! This represents the metadata for a crash report, as well as the location //! of the report itself. A CrashReportDatabase maintains at least this //! information. struct Report { Report(); //! A unique identifier by which this report will always be known to the //! database. UUID uuid; //! The current location of the crash report on the client’s filesystem. //! The location of a crash report may change over time, so the UUID should //! be used as the canonical identifier. base::FilePath file_path; //! An identifier issued to this crash report by a collection server. std::string id; //! The time at which the report was generated. time_t creation_time; //! Whether this crash report was successfully uploaded to a collection //! server. bool uploaded; //! The last timestamp at which an attempt was made to submit this crash //! report to a collection server. If this is zero, then the report has //! never been uploaded. If #uploaded is true, then this timestamp is the //! time at which the report was uploaded, and no other attempts to upload //! this report will be made. time_t last_upload_attempt_time; //! The number of times an attempt was made to submit this report to //! a collection server. If this is more than zero, then //! #last_upload_attempt_time will be set to the timestamp of the most //! recent attempt. int upload_attempts; //! Whether this crash report was explicitly requested by user to be //! uploaded. This can be true only if report is in the 'pending' state. bool upload_explicitly_requested; //! The total size in bytes taken by the report, including any potential //! attachments. uint64_t total_size; }; //! \brief A crash report that is in the process of being written. //! //! An instance of this class should be created via PrepareNewCrashReport(). class NewReport { public: NewReport(); NewReport(const NewReport&) = delete; NewReport& operator=(const NewReport&) = delete; ~NewReport(); //! \brief An open FileWriter with which to write the report. FileWriter* Writer() const { return writer_.get(); } //! \brief Returns a FileReaderInterface to the report, or `nullptr` with a //! message logged. FileReaderInterface* Reader(); //! A unique identifier by which this report will always be known to the //! database. const UUID& ReportID() const { return uuid_; } //! \brief Adds an attachment to the report. //! //! \param[in] name The key and name for the attachment, which will be //! included in the http upload. The attachment will not appear in the //! minidump report. \a name should only use characters from the set //! `[a-zA-Z0-9._-]`. //! \return A FileWriter that the caller should use to write the contents of //! the attachment, or `nullptr` on failure with an error logged. FileWriter* AddAttachment(const std::string& name); private: friend class CrashReportDatabaseGeneric; friend class CrashReportDatabaseMac; friend class CrashReportDatabaseWin; bool Initialize(CrashReportDatabase* database, const base::FilePath& directory, const base::FilePath::StringType& extension); std::unique_ptr writer_; std::unique_ptr reader_; ScopedRemoveFile file_remover_; std::vector> attachment_writers_; std::vector attachment_removers_; UUID uuid_; CrashReportDatabase* database_; }; //! \brief A crash report that is in the process of being uploaded. //! //! An instance of this class should be created via GetReportForUploading(). class UploadReport : public Report { public: UploadReport(); UploadReport(const UploadReport&) = delete; UploadReport& operator=(const UploadReport&) = delete; virtual ~UploadReport(); //! \brief An open FileReader with which to read the report. FileReader* Reader() const { return reader_.get(); } //! \brief Obtains a mapping of names to file readers for any attachments //! for the report. std::map GetAttachments() const { return attachment_map_; } private: friend class CrashReportDatabase; friend class CrashReportDatabaseGeneric; friend class CrashReportDatabaseMac; friend class CrashReportDatabaseWin; bool Initialize(const base::FilePath& path, CrashReportDatabase* database); void InitializeAttachments(); std::unique_ptr reader_; CrashReportDatabase* database_; std::vector> attachment_readers_; std::map attachment_map_; bool report_metrics_; }; //! \brief The result code for operations performed on a database. enum OperationStatus { //! \brief No error occurred. kNoError = 0, //! \brief The report that was requested could not be located. //! //! This may occur when the report is present in the database but not in a //! state appropriate for the requested operation, for example, if //! GetReportForUploading() is called to obtain report that’s already in the //! completed state. kReportNotFound, //! \brief An error occured while performing a file operation on a crash //! report. //! //! A database is responsible for managing both the metadata about a report //! and the actual crash report itself. This error is returned when an //! error occurred when managing the report file. Additional information //! will be logged. kFileSystemError, //! \brief An error occured while recording metadata for a crash report or //! database-wide settings. //! //! A database is responsible for managing both the metadata about a report //! and the actual crash report itself. This error is returned when an //! error occurred when managing the metadata about a crash report or //! database-wide settings. Additional information will be logged. kDatabaseError, //! \brief The operation could not be completed because a concurrent //! operation affecting the report is occurring. kBusyError, //! \brief The report cannot be uploaded by user request as it has already //! been uploaded. kCannotRequestUpload, }; CrashReportDatabase(const CrashReportDatabase&) = delete; CrashReportDatabase& operator=(const CrashReportDatabase&) = delete; virtual ~CrashReportDatabase() {} //! \brief Opens a database of crash reports, possibly creating it. //! //! \param[in] path A path to the database to be created or opened. If the //! database does not yet exist, it will be created if possible. Note that //! for databases implemented as directory structures, existence refers //! solely to the outermost directory. //! //! \return A database object on success, `nullptr` on failure with an error //! logged. //! //! \sa InitializeWithoutCreating static std::unique_ptr Initialize( const base::FilePath& path); //! \brief Opens an existing database of crash reports. //! //! \param[in] path A path to the database to be opened. If the database does //! not yet exist, it will not be created. Note that for databases //! implemented as directory structures, existence refers solely to the //! outermost directory. On such databases, as long as the outermost //! directory is present, this method will create the inner structure. //! //! \return A database object on success, `nullptr` on failure with an error //! logged. //! //! \sa Initialize static std::unique_ptr InitializeWithoutCreating( const base::FilePath& path); //! \brief Given a database path, return a read-only view of its settings. //! //! \param[in] path A path to the database. If the database does not exist, or //! the settings file does not exist, the returned reader will fail its //! read methods. //! //! \return A SettingsReader. static std::unique_ptr GetSettingsReaderForDatabasePath( const base::FilePath& path); //! \brief Returns the Settings object for this database. //! //! \return A weak pointer to the Settings object, which is owned by the //! database. virtual Settings* GetSettings() = 0; //! \brief Creates a record of a new crash report. //! //! Callers should write the crash report using the FileWriter provided. //! Callers should then call FinishedWritingCrashReport() to complete report //! creation. If an error is encountered while writing the crash report, no //! special action needs to be taken. If FinishedWritingCrashReport() is not //! called, the report will be removed from the database when \a report is //! destroyed. //! //! \param[out] report A NewReport object containing a FileWriter with which //! to write the report data. Only valid if this returns #kNoError. //! //! \return The operation status code. virtual OperationStatus PrepareNewCrashReport( std::unique_ptr* report) = 0; //! \brief Informs the database that a crash report has been successfully //! written. //! //! \param[in] report A NewReport obtained with PrepareNewCrashReport(). The //! NewReport object will be invalidated as part of this call. //! \param[out] uuid The UUID of this crash report. //! //! \return The operation status code. virtual OperationStatus FinishedWritingCrashReport( std::unique_ptr report, UUID* uuid) = 0; //! \brief Returns the crash report record for the unique identifier. //! //! \param[in] uuid The crash report record unique identifier. //! \param[out] report A crash report record. Only valid if this returns //! #kNoError. //! //! \return The operation status code. virtual OperationStatus LookUpCrashReport(const UUID& uuid, Report* report) = 0; //! \brief Returns a list of crash report records that have not been uploaded. //! //! \param[out] reports A list of crash report record objects. This must be //! empty on entry. Only valid if this returns #kNoError. //! //! \return The operation status code. virtual OperationStatus GetPendingReports(std::vector* reports) = 0; //! \brief Returns a list of crash report records that have been completed, //! either by being uploaded or by skipping upload. //! //! \param[out] reports A list of crash report record objects. This must be //! empty on entry. Only valid if this returns #kNoError. //! //! \return The operation status code. virtual OperationStatus GetCompletedReports(std::vector* reports) = 0; //! \brief Obtains and locks a report object for uploading to a collection //! server. On iOS the file lock is released and mutual-exclusion is kept //! via a file attribute. //! //! Callers should upload the crash report using the FileReader provided. //! Callers should then call RecordUploadComplete() to record a successful //! upload. If RecordUploadComplete() is not called, the upload attempt will //! be recorded as unsuccessful and the report lock released when \a report is //! destroyed. //! //! On iOS, holding a lock during a slow upload can lead to watchdog kills if //! the app is suspended mid-upload. Instead, if the client can obtain the //! lock, the database sets a lock-time file attribute and releases the lock. //! The attribute is cleared when the upload is completed. The lock-time //! attribute can be used to prevent file access from other processes, or to //! discard reports that likely were terminated mid-upload. //! //! \param[in] uuid The unique identifier for the crash report record. //! \param[out] report A crash report record for the report to be uploaded. //! Only valid if this returns #kNoError. //! \param[in] report_metrics If `false`, metrics will not be recorded for //! this upload attempt when RecordUploadComplete() is called or \a report //! is destroyed. Metadata for the upload attempt will still be recorded //! in the database. //! //! \return The operation status code. virtual OperationStatus GetReportForUploading( const UUID& uuid, std::unique_ptr* report, bool report_metrics = true) = 0; //! \brief Records a successful upload for a report and updates the last //! upload attempt time as returned by //! Settings::GetLastUploadAttemptTime(). //! //! \param[in] report A UploadReport object obtained from //! GetReportForUploading(). The UploadReport object will be invalidated //! and the report unlocked as part of this call. //! \param[in] id The possibly empty identifier assigned to this crash report //! by the collection server. //! //! \return The operation status code. OperationStatus RecordUploadComplete( std::unique_ptr report, const std::string& id); //! \brief Moves a report from the pending state to the completed state, but //! without the report being uploaded. //! //! This can be used if the user has disabled crash report collection, but //! crash generation is still enabled in the product. //! //! \param[in] uuid The unique identifier for the crash report record. //! \param[in] reason The reason the report upload is being skipped for //! metrics tracking purposes. //! //! \return The operation status code. virtual OperationStatus SkipReportUpload( const UUID& uuid, Metrics::CrashSkippedReason reason) = 0; //! \brief Deletes a crash report file and its associated metadata. //! //! \param[in] uuid The UUID of the report to delete. //! //! \return The operation status code. virtual OperationStatus DeleteReport(const UUID& uuid) = 0; //! \brief Marks a crash report as explicitly requested to be uploaded by the //! user and moves it to 'pending' state. //! //! \param[in] uuid The unique identifier for the crash report record. //! //! \return The operation status code. virtual OperationStatus RequestUpload(const UUID& uuid) = 0; //! \brief Cleans the database of expired lockfiles, metadata without report //! files, report files without metadata, and attachments without report //! files. //! //! As the macOS implementation does not use lock or metadata files, the //! cleaning is limited to attachments without report files. //! //! \param[in] lockfile_ttl The number of seconds at which lockfiles or new //! report files are considered expired. //! \return The number of reports cleaned. virtual int CleanDatabase(time_t lockfile_ttl) { return 0; } protected: CrashReportDatabase() = default; //! \brief The path to the database passed to Initialize. //! //! \return The filepath of the database; virtual base::FilePath DatabasePath() = 0; //! \brief Build a filepath for the root attachments directory. //! //! \return The filepath to the attachments directory. base::FilePath AttachmentsRootPath(); //! \brief Build a filepath for the directory for the report to hold //! attachments. //! //! \param[in] uuid The unique identifier for the crash report record. //! //! \return The filepath to the report attachments directory. base::FilePath AttachmentsPath(const UUID& uuid); //! \brief Attempts to remove any attachments associated with the given //! report UUID. There may not be any, so failing is not an error. //! //! \param[in] uuid The unique identifier for the crash report record. void RemoveAttachmentsByUUID(const UUID& uuid); private: //! \brief Adjusts a crash report record’s metadata to account for an upload //! attempt, and updates the last upload attempt time as returned by //! Settings::GetLastUploadAttemptTime(). //! //! \param[in] report The report object obtained from //! GetReportForUploading(). //! \param[in] successful Whether the upload attempt was successful. //! \param[in] id The identifier assigned to this crash report by the //! collection server. Must be empty if \a successful is `false`; may be //! empty if it is `true`. //! //! \return The operation status code. virtual OperationStatus RecordUploadAttempt(UploadReport* report, bool successful, const std::string& id) = 0; }; } // namespace crashpad #endif // CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_ ================================================ FILE: client/crash_report_database_generic.cc ================================================ // Copyright 2018 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crash_report_database.h" #include #include #include #include #include #include #include "base/check_op.h" #include "base/logging.h" #include "build/build_config.h" #include "client/settings.h" #include "util/file/directory_reader.h" #include "util/file/filesystem.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/memory_sanitizer.h" namespace crashpad { namespace { base::FilePath ReplaceFinalExtension( const base::FilePath& path, const base::FilePath::StringType extension) { return base::FilePath(path.RemoveFinalExtension().value() + extension); } UUID UUIDFromReportPath(const base::FilePath& path) { UUID uuid; uuid.InitializeFromString(path.RemoveFinalExtension().BaseName().value()); return uuid; } using OperationStatus = CrashReportDatabase::OperationStatus; constexpr base::FilePath::CharType kSettings[] = FILE_PATH_LITERAL("settings.dat"); constexpr base::FilePath::CharType kCrashReportExtension[] = FILE_PATH_LITERAL(".dmp"); constexpr base::FilePath::CharType kMetadataExtension[] = FILE_PATH_LITERAL(".meta"); constexpr base::FilePath::CharType kLockExtension[] = FILE_PATH_LITERAL(".lock"); constexpr base::FilePath::CharType kNewDirectory[] = FILE_PATH_LITERAL("new"); constexpr base::FilePath::CharType kPendingDirectory[] = FILE_PATH_LITERAL("pending"); constexpr base::FilePath::CharType kCompletedDirectory[] = FILE_PATH_LITERAL("completed"); constexpr const base::FilePath::CharType* kReportDirectories[] = { kNewDirectory, kPendingDirectory, kCompletedDirectory, }; enum { //! \brief Corresponds to uploaded bit of the report state. kAttributeUploaded = 1 << 0, //! \brief Corresponds to upload_explicity_requested bit of the report state. kAttributeUploadExplicitlyRequested = 1 << 1, }; struct ReportMetadata { static constexpr int32_t kVersion = 1; int32_t version = kVersion; int32_t upload_attempts = 0; int64_t last_upload_attempt_time = 0; time_t creation_time = 0; uint8_t attributes = 0; }; // A lock held while using database resources. class ScopedLockFile { public: ScopedLockFile() = default; ScopedLockFile(const ScopedLockFile&) = delete; ScopedLockFile& operator=(const ScopedLockFile&) = delete; ~ScopedLockFile() = default; ScopedLockFile& operator=(ScopedLockFile&& other) { lock_file_.reset(other.lock_file_.release()); return *this; } // Attempt to acquire a lock for the report at report_path. // Return `true` on success, otherwise `false`. bool ResetAcquire(const base::FilePath& report_path) { lock_file_.reset(); base::FilePath lock_path(report_path.RemoveFinalExtension().value() + kLockExtension); ScopedFileHandle lock_fd(LoggingOpenFileForWrite( lock_path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)); if (!lock_fd.is_valid()) { return false; } lock_file_.reset(lock_path); time_t timestamp = time(nullptr); if (!LoggingWriteFile(lock_fd.get(), ×tamp, sizeof(timestamp))) { return false; } return true; } // Returns `true` if the lock is held. bool is_valid() const { return lock_file_.is_valid(); } // Returns `true` if the lockfile at lock_path has expired. static bool IsExpired(const base::FilePath& lock_path, time_t lockfile_ttl) { time_t now = time(nullptr); timespec filetime; if (FileModificationTime(lock_path, &filetime) && filetime.tv_sec > now + lockfile_ttl) { return false; } ScopedFileHandle lock_fd(LoggingOpenFileForReadAndWrite( lock_path, FileWriteMode::kReuseOrFail, FilePermissions::kOwnerOnly)); if (!lock_fd.is_valid()) { return false; } time_t timestamp; if (!LoggingReadFileExactly(lock_fd.get(), ×tamp, sizeof(timestamp))) { return false; } return now >= timestamp + lockfile_ttl; } private: ScopedRemoveFile lock_file_; }; } // namespace class CrashReportDatabaseGeneric : public CrashReportDatabase { public: explicit CrashReportDatabaseGeneric(const base::FilePath& path); CrashReportDatabaseGeneric(const CrashReportDatabaseGeneric&) = delete; CrashReportDatabaseGeneric& operator=(const CrashReportDatabaseGeneric&) = delete; ~CrashReportDatabaseGeneric() override; bool Initialize(bool may_create); // CrashReportDatabase: Settings* GetSettings() override; OperationStatus PrepareNewCrashReport( std::unique_ptr* report) override; OperationStatus FinishedWritingCrashReport(std::unique_ptr report, UUID* uuid) override; OperationStatus LookUpCrashReport(const UUID& uuid, Report* report) override; OperationStatus GetPendingReports(std::vector* reports) override; OperationStatus GetCompletedReports(std::vector* reports) override; OperationStatus GetReportForUploading( const UUID& uuid, std::unique_ptr* report, bool report_metrics) override; OperationStatus SkipReportUpload(const UUID& uuid, Metrics::CrashSkippedReason reason) override; OperationStatus DeleteReport(const UUID& uuid) override; OperationStatus RequestUpload(const UUID& uuid) override; int CleanDatabase(time_t lockfile_ttl) override; base::FilePath DatabasePath() override; private: struct LockfileUploadReport : public UploadReport { ScopedLockFile lock_file; }; enum ReportState : int32_t { kUninitialized = -1, // Being created by a caller of PrepareNewCrashReport(). kNew, // Created by FinishedWritingCrashReport(), but not yet uploaded. kPending, // Upload completed or skipped. kCompleted, // Specifies either kPending or kCompleted. kSearchable, }; // CrashReportDatabase: OperationStatus RecordUploadAttempt(UploadReport* report, bool successful, const std::string& id) override; // Builds a filepath for the report with the specified uuid and state. base::FilePath ReportPath(const UUID& uuid, ReportState state); // Locates the report with id uuid and returns its file path in path and a // lock for the report in lock_file. This method succeeds as long as the // report file exists and the lock can be acquired. No validation is done on // the existence or content of the metadata file. OperationStatus LocateAndLockReport(const UUID& uuid, ReportState state, base::FilePath* path, ScopedLockFile* lock_file); // Locates, locks, and reads the metadata for the report with the specified // uuid and state. This method will fail and may remove reports if invalid // metadata is detected. state may be kPending, kCompleted, or kSearchable. OperationStatus CheckoutReport(const UUID& uuid, ReportState state, base::FilePath* path, ScopedLockFile* lock_file, Report* report); // Reads metadata for all reports in state and returns it in reports. OperationStatus ReportsInState(ReportState state, std::vector* reports); // Cleans lone metadata, reports, or expired locks in a particular state. int CleanReportsInState(ReportState state, time_t lockfile_ttl); // Cleans any attachments that have no associated report in any state. void CleanOrphanedAttachments(); // Reads the metadata for a report from path and returns it in report. bool ReadMetadata(const base::FilePath& path, Report* report); // Wraps ReadMetadata and removes the report from the database on failure. bool CleaningReadMetadata(const base::FilePath& path, Report* report); // Writes metadata for a new report to the filesystem at path. static bool WriteNewMetadata(const base::FilePath& path); // Writes the metadata for report to the filesystem at path. static bool WriteMetadata(const base::FilePath& path, const Report& report); Settings& SettingsInternal() { std::call_once(settings_init_, [this]() { settings_.Initialize(); }); return settings_; } const base::FilePath base_dir_; Settings settings_; std::once_flag settings_init_; InitializationStateDcheck initialized_; }; CrashReportDatabaseGeneric::CrashReportDatabaseGeneric( const base::FilePath& path) : base_dir_(path), settings_(path.Append(kSettings)) {} CrashReportDatabaseGeneric::~CrashReportDatabaseGeneric() = default; bool CrashReportDatabaseGeneric::Initialize(bool may_create) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); if (!IsDirectory(base_dir_, true) && !(may_create && LoggingCreateDirectory(base_dir_, FilePermissions::kOwnerOnly, true))) { return false; } for (const base::FilePath::CharType* subdir : kReportDirectories) { if (!LoggingCreateDirectory( base_dir_.Append(subdir), FilePermissions::kOwnerOnly, true)) { return false; } } if (!LoggingCreateDirectory( AttachmentsRootPath(), FilePermissions::kOwnerOnly, true)) { return false; } INITIALIZATION_STATE_SET_VALID(initialized_); return true; } base::FilePath CrashReportDatabaseGeneric::DatabasePath() { return base_dir_; } Settings* CrashReportDatabaseGeneric::GetSettings() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return &SettingsInternal(); } OperationStatus CrashReportDatabaseGeneric::PrepareNewCrashReport( std::unique_ptr* report) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); auto new_report = std::make_unique(); if (!new_report->Initialize( this, base_dir_.Append(kNewDirectory), kCrashReportExtension)) { return kFileSystemError; } report->reset(new_report.release()); return kNoError; } OperationStatus CrashReportDatabaseGeneric::FinishedWritingCrashReport( std::unique_ptr report, UUID* uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); base::FilePath path = ReportPath(report->ReportID(), kPending); ScopedLockFile lock_file; if (!lock_file.ResetAcquire(path)) { return kBusyError; } if (!WriteNewMetadata(ReplaceFinalExtension(path, kMetadataExtension))) { return kDatabaseError; } FileOffset size = report->Writer()->Seek(0, SEEK_END); report->Writer()->Close(); if (!MoveFileOrDirectory(report->file_remover_.get(), path)) { return kFileSystemError; } // We've moved the report to pending, so it no longer needs to be removed. std::ignore = report->file_remover_.release(); // Close all the attachments and disarm their removers too. for (auto& writer : report->attachment_writers_) { writer->Close(); } for (auto& remover : report->attachment_removers_) { std::ignore = remover.release(); } *uuid = report->ReportID(); Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated); Metrics::CrashReportSize(size); return kNoError; } OperationStatus CrashReportDatabaseGeneric::LookUpCrashReport(const UUID& uuid, Report* report) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); ScopedLockFile lock_file; base::FilePath path; return CheckoutReport(uuid, kSearchable, &path, &lock_file, report); } OperationStatus CrashReportDatabaseGeneric::GetPendingReports( std::vector* reports) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return ReportsInState(kPending, reports); } OperationStatus CrashReportDatabaseGeneric::GetCompletedReports( std::vector* reports) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return ReportsInState(kCompleted, reports); } OperationStatus CrashReportDatabaseGeneric::GetReportForUploading( const UUID& uuid, std::unique_ptr* report, bool report_metrics) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); auto upload_report = std::make_unique(); base::FilePath path; OperationStatus os = CheckoutReport( uuid, kPending, &path, &upload_report->lock_file, upload_report.get()); if (os != kNoError) { return os; } if (!upload_report->Initialize(path, this)) { return kFileSystemError; } upload_report->report_metrics_ = report_metrics; report->reset(upload_report.release()); return kNoError; } OperationStatus CrashReportDatabaseGeneric::SkipReportUpload( const UUID& uuid, Metrics::CrashSkippedReason reason) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); Metrics::CrashUploadSkipped(reason); base::FilePath path; ScopedLockFile lock_file; Report report; OperationStatus os = CheckoutReport(uuid, kPending, &path, &lock_file, &report); if (os != kNoError) { return os; } base::FilePath completed_path(ReportPath(uuid, kCompleted)); ScopedLockFile completed_lock_file; if (!completed_lock_file.ResetAcquire(completed_path)) { return kBusyError; } report.upload_explicitly_requested = false; if (!WriteMetadata(completed_path, report)) { return kDatabaseError; } if (!MoveFileOrDirectory(path, completed_path)) { return kFileSystemError; } if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) { return kDatabaseError; } return kNoError; } OperationStatus CrashReportDatabaseGeneric::DeleteReport(const UUID& uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); base::FilePath path; ScopedLockFile lock_file; OperationStatus os = LocateAndLockReport(uuid, kSearchable, &path, &lock_file); if (os != kNoError) { return os; } if (!LoggingRemoveFile(path)) { return kFileSystemError; } if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) { return kDatabaseError; } RemoveAttachmentsByUUID(uuid); return kNoError; } OperationStatus CrashReportDatabaseGeneric::RequestUpload(const UUID& uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); base::FilePath path; ScopedLockFile lock_file; Report report; OperationStatus os = CheckoutReport(uuid, kSearchable, &path, &lock_file, &report); if (os != kNoError) { return os; } if (report.uploaded) { return kCannotRequestUpload; } report.upload_explicitly_requested = true; base::FilePath pending_path = ReportPath(uuid, kPending); if (!MoveFileOrDirectory(path, pending_path)) { return kFileSystemError; } if (!WriteMetadata(pending_path, report)) { return kDatabaseError; } if (pending_path != path) { if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) { return kDatabaseError; } } Metrics::CrashReportPending(Metrics::PendingReportReason::kUserInitiated); return kNoError; } int CrashReportDatabaseGeneric::CleanDatabase(time_t lockfile_ttl) { int removed = 0; time_t now = time(nullptr); DirectoryReader reader; const base::FilePath new_dir(base_dir_.Append(kNewDirectory)); if (reader.Open(new_dir)) { base::FilePath filename; DirectoryReader::Result result; while ((result = reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { const base::FilePath filepath(new_dir.Append(filename)); timespec filetime; if (!FileModificationTime(filepath, &filetime)) { continue; } if (filetime.tv_sec <= now - lockfile_ttl) { if (LoggingRemoveFile(filepath)) { ++removed; } } } } removed += CleanReportsInState(kPending, lockfile_ttl); removed += CleanReportsInState(kCompleted, lockfile_ttl); CleanOrphanedAttachments(); #if !CRASHPAD_FLOCK_ALWAYS_SUPPORTED base::FilePath settings_path(kSettings); if (Settings::IsLockExpired(settings_path, lockfile_ttl)) { base::FilePath lockfile_path(settings_path.value() + Settings::kLockfileExtension); if (LoggingRemoveFile(lockfile_path)) { ++removed; } } #endif // !CRASHPAD_FLOCK_ALWAYS_SUPPORTED return removed; } OperationStatus CrashReportDatabaseGeneric::RecordUploadAttempt( UploadReport* report, bool successful, const std::string& id) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (report->report_metrics_) { Metrics::CrashUploadAttempted(successful); } time_t now = time(nullptr); report->id = id; report->uploaded = successful; report->last_upload_attempt_time = now; ++report->upload_attempts; base::FilePath report_path(report->file_path); ScopedLockFile lock_file; if (successful) { report->upload_explicitly_requested = false; base::FilePath completed_report_path = ReportPath(report->uuid, kCompleted); if (!lock_file.ResetAcquire(completed_report_path)) { return kBusyError; } report->Reader()->Close(); if (!MoveFileOrDirectory(report_path, completed_report_path)) { return kFileSystemError; } LoggingRemoveFile(ReplaceFinalExtension(report_path, kMetadataExtension)); report_path = completed_report_path; } if (!WriteMetadata(report_path, *report)) { return kDatabaseError; } if (!SettingsInternal().SetLastUploadAttemptTime(now)) { return kDatabaseError; } return kNoError; } base::FilePath CrashReportDatabaseGeneric::ReportPath(const UUID& uuid, ReportState state) { DCHECK_NE(state, kUninitialized); DCHECK_NE(state, kSearchable); #if BUILDFLAG(IS_WIN) const std::wstring uuid_string = uuid.ToWString(); #else const std::string uuid_string = uuid.ToString(); #endif return base_dir_.Append(kReportDirectories[state]) .Append(uuid_string + kCrashReportExtension); } OperationStatus CrashReportDatabaseGeneric::LocateAndLockReport( const UUID& uuid, ReportState desired_state, base::FilePath* path, ScopedLockFile* lock_file) { std::vector searchable_states; if (desired_state == kSearchable) { searchable_states.push_back(kPending); searchable_states.push_back(kCompleted); } else { DCHECK(desired_state == kPending || desired_state == kCompleted); searchable_states.push_back(desired_state); } for (const ReportState state : searchable_states) { base::FilePath local_path(ReportPath(uuid, state)); ScopedLockFile local_lock; if (!local_lock.ResetAcquire(local_path)) { return kBusyError; } if (!IsRegularFile(local_path)) { continue; } *path = local_path; *lock_file = std::move(local_lock); return kNoError; } return kReportNotFound; } OperationStatus CrashReportDatabaseGeneric::CheckoutReport( const UUID& uuid, ReportState state, base::FilePath* path, ScopedLockFile* lock_file, Report* report) { ScopedLockFile local_lock; base::FilePath local_path; OperationStatus os = LocateAndLockReport(uuid, state, &local_path, &local_lock); if (os != kNoError) { return os; } if (!CleaningReadMetadata(local_path, report)) { return kDatabaseError; } *path = local_path; *lock_file = std::move(local_lock); return kNoError; } OperationStatus CrashReportDatabaseGeneric::ReportsInState( ReportState state, std::vector* reports) { DCHECK(reports->empty()); DCHECK_NE(state, kUninitialized); DCHECK_NE(state, kSearchable); DCHECK_NE(state, kNew); const base::FilePath dir_path(base_dir_.Append(kReportDirectories[state])); DirectoryReader reader; if (!reader.Open(dir_path)) { return kDatabaseError; } base::FilePath filename; DirectoryReader::Result result; while ((result = reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { const base::FilePath::StringType extension(filename.FinalExtension()); if (extension.compare(kCrashReportExtension) != 0) { continue; } const base::FilePath filepath(dir_path.Append(filename)); ScopedLockFile lock_file; if (!lock_file.ResetAcquire(filepath)) { continue; } Report report; if (!CleaningReadMetadata(filepath, &report)) { continue; } reports->push_back(report); reports->back().file_path = filepath; } return kNoError; } int CrashReportDatabaseGeneric::CleanReportsInState(ReportState state, time_t lockfile_ttl) { const base::FilePath dir_path(base_dir_.Append(kReportDirectories[state])); DirectoryReader reader; if (!reader.Open(dir_path)) { return 0; } int removed = 0; base::FilePath filename; DirectoryReader::Result result; while ((result = reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { const base::FilePath::StringType extension(filename.FinalExtension()); const base::FilePath filepath(dir_path.Append(filename)); // Remove any report files without metadata. if (extension.compare(kCrashReportExtension) == 0) { const base::FilePath metadata_path( ReplaceFinalExtension(filepath, kMetadataExtension)); ScopedLockFile report_lock; if (report_lock.ResetAcquire(filepath) && !IsRegularFile(metadata_path) && LoggingRemoveFile(filepath)) { ++removed; RemoveAttachmentsByUUID(UUIDFromReportPath(filepath)); } continue; } // Remove any metadata files without report files. if (extension.compare(kMetadataExtension) == 0) { const base::FilePath report_path( ReplaceFinalExtension(filepath, kCrashReportExtension)); ScopedLockFile report_lock; if (report_lock.ResetAcquire(report_path) && !IsRegularFile(report_path) && LoggingRemoveFile(filepath)) { ++removed; RemoveAttachmentsByUUID(UUIDFromReportPath(filepath)); } continue; } // Remove any expired locks only if we can remove the report and metadata. if (extension.compare(kLockExtension) == 0 && ScopedLockFile::IsExpired(filepath, lockfile_ttl)) { const base::FilePath no_ext(filepath.RemoveFinalExtension()); const base::FilePath report_path(no_ext.value() + kCrashReportExtension); const base::FilePath metadata_path(no_ext.value() + kMetadataExtension); if ((IsRegularFile(report_path) && !LoggingRemoveFile(report_path)) || (IsRegularFile(metadata_path) && !LoggingRemoveFile(metadata_path))) { continue; } if (LoggingRemoveFile(filepath)) { ++removed; RemoveAttachmentsByUUID(UUIDFromReportPath(filepath)); } continue; } } return removed; } void CrashReportDatabaseGeneric::CleanOrphanedAttachments() { base::FilePath root_attachments_dir(AttachmentsRootPath()); DirectoryReader reader; if (!reader.Open(root_attachments_dir)) { return; } base::FilePath filename; DirectoryReader::Result result; while ((result = reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { const base::FilePath report_attachment_dir( root_attachments_dir.Append(filename)); if (IsDirectory(report_attachment_dir, false)) { UUID uuid; if (!uuid.InitializeFromString(filename.value())) { LOG(ERROR) << "unexpected attachment dir name " << filename.value(); continue; } // Check to see if the report is being created in "new". base::FilePath new_dir_path = base_dir_.Append(kNewDirectory) .Append(uuid.ToString() + kCrashReportExtension); if (IsRegularFile(new_dir_path)) { continue; } // Check to see if the report is in "pending" or "completed". ScopedLockFile local_lock; base::FilePath local_path; OperationStatus os = LocateAndLockReport(uuid, kSearchable, &local_path, &local_lock); if (os != kReportNotFound) { continue; } // Couldn't find a report, assume these attachments are orphaned. RemoveAttachmentsByUUID(uuid); } } } bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path, Report* report) { const base::FilePath metadata_path( ReplaceFinalExtension(path, kMetadataExtension)); ScopedFileHandle handle(LoggingOpenFileForRead(metadata_path)); if (!handle.is_valid()) { return false; } UUID uuid; if (!uuid.InitializeFromString( path.BaseName().RemoveFinalExtension().value())) { LOG(ERROR) << "Couldn't interpret report uuid"; return false; } ReportMetadata metadata; if (!LoggingReadFileExactly(handle.get(), &metadata, sizeof(metadata))) { return false; } if (metadata.version != ReportMetadata::kVersion) { LOG(ERROR) << "metadata version mismatch"; return false; } if (!LoggingReadToEOF(handle.get(), &report->id)) { return false; } // Seed the total size with the main report size and then add the sizes of any // potential attachments. uint64_t total_size = GetFileSize(path); total_size += GetDirectorySize(AttachmentsPath(uuid)); report->uuid = uuid; report->upload_attempts = metadata.upload_attempts; report->last_upload_attempt_time = metadata.last_upload_attempt_time; report->creation_time = metadata.creation_time; report->uploaded = (metadata.attributes & kAttributeUploaded) != 0; report->upload_explicitly_requested = (metadata.attributes & kAttributeUploadExplicitlyRequested) != 0; report->file_path = path; report->total_size = total_size; return true; } bool CrashReportDatabaseGeneric::CleaningReadMetadata( const base::FilePath& path, Report* report) { if (ReadMetadata(path, report)) { return true; } LoggingRemoveFile(path); LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension)); RemoveAttachmentsByUUID(report->uuid); return false; } // static bool CrashReportDatabaseGeneric::WriteNewMetadata(const base::FilePath& path) { const base::FilePath metadata_path( ReplaceFinalExtension(path, kMetadataExtension)); ScopedFileHandle handle(LoggingOpenFileForWrite(metadata_path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)); if (!handle.is_valid()) { return false; } ReportMetadata metadata; #if defined(MEMORY_SANITIZER) // memset() + re-initialization is required to zero padding bytes for MSan. memset(&metadata, 0, sizeof(metadata)); #endif // defined(MEMORY_SANITIZER) metadata = {}; metadata.creation_time = time(nullptr); return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata)); } // static bool CrashReportDatabaseGeneric::WriteMetadata(const base::FilePath& path, const Report& report) { const base::FilePath metadata_path( ReplaceFinalExtension(path, kMetadataExtension)); ScopedFileHandle handle( LoggingOpenFileForWrite(metadata_path, FileWriteMode::kTruncateOrCreate, FilePermissions::kOwnerOnly)); if (!handle.is_valid()) { return false; } ReportMetadata metadata; #if defined(MEMORY_SANITIZER) // memset() + re-initialization is required to zero padding bytes for MSan. memset(&metadata, 0, sizeof(metadata)); #endif // defined(MEMORY_SANITIZER) metadata = {}; metadata.creation_time = report.creation_time; metadata.last_upload_attempt_time = report.last_upload_attempt_time; metadata.upload_attempts = report.upload_attempts; metadata.attributes = (report.uploaded ? kAttributeUploaded : 0) | (report.upload_explicitly_requested ? kAttributeUploadExplicitlyRequested : 0); return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata)) && LoggingWriteFile(handle.get(), report.id.c_str(), report.id.size()); } // static std::unique_ptr CrashReportDatabase::Initialize( const base::FilePath& path) { auto database = std::make_unique(path); return database->Initialize(true) ? std::move(database) : nullptr; } // static std::unique_ptr CrashReportDatabase::InitializeWithoutCreating(const base::FilePath& path) { auto database = std::make_unique(path); return database->Initialize(false) ? std::move(database) : nullptr; } // static std::unique_ptr CrashReportDatabase::GetSettingsReaderForDatabasePath( const base::FilePath& path) { return std::make_unique(path.Append(kSettings)); } } // namespace crashpad ================================================ FILE: client/crash_report_database_mac.mm ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crash_report_database.h" #import #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base/apple/scoped_nsautorelease_pool.h" #include "base/logging.h" #include "base/posix/eintr_wrapper.h" #include "base/scoped_generic.h" #include "base/strings/strcat.h" #include "base/strings/sys_string_conversions.h" #include "client/settings.h" #include "util/file/directory_reader.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" #include "util/mac/xattr.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/metrics.h" #if BUILDFLAG(IS_IOS) #include "util/ios/scoped_background_task.h" #endif // BUILDFLAG(IS_IOS) namespace crashpad { namespace { constexpr char kWriteDirectory[] = "new"; constexpr char kUploadPendingDirectory[] = "pending"; constexpr char kCompletedDirectory[] = "completed"; constexpr char kSettings[] = "settings.dat"; constexpr std::array kReportDirectories = { kWriteDirectory, kUploadPendingDirectory, kCompletedDirectory, }; constexpr char kCrashReportFileExtension[] = "dmp"; constexpr char kXattrUUID[] = "uuid"; constexpr char kXattrCollectorID[] = "id"; constexpr char kXattrCreationTime[] = "creation_time"; constexpr char kXattrIsUploaded[] = "uploaded"; #if BUILDFLAG(IS_IOS) constexpr char kXattrUploadStartTime[] = "upload_start_time"; #endif constexpr char kXattrLastUploadTime[] = "last_upload_time"; constexpr char kXattrUploadAttemptCount[] = "upload_count"; constexpr char kXattrIsUploadExplicitlyRequested[] = "upload_explicitly_requested"; constexpr char kXattrDatabaseInitialized[] = "initialized"; // Ensures that the node at |path| is a directory. If the |path| refers to a // file, rather than a directory, returns false. Otherwise, returns true, // indicating that |path| already was a directory. bool EnsureDirectoryExists(const base::FilePath& path) { struct stat st; if (stat(path.value().c_str(), &st) != 0) { PLOG(ERROR) << "stat " << path.value(); return false; } if (!S_ISDIR(st.st_mode)) { LOG(ERROR) << "stat " << path.value() << ": not a directory"; return false; } return true; } // Ensures that the node at |path| is a directory, and creates it if it does // not exist. If the |path| refers to a file, rather than a directory, or the // directory could not be created, returns false. Otherwise, returns true, // indicating that |path| already was or now is a directory. bool CreateOrEnsureDirectoryExists(const base::FilePath& path) { if (mkdir(path.value().c_str(), 0755) == 0) { return true; } if (errno != EEXIST) { PLOG(ERROR) << "mkdir " << path.value(); return false; } return EnsureDirectoryExists(path); } // Creates a long database xattr name from the short constant name. These names // have changed, and new_name determines whether the returned xattr name will be // the old name or its new equivalent. std::string XattrNameInternal(std::string_view name, bool new_name) { return base::StrCat({new_name ? "org.chromium.crashpad.database." : "com.googlecode.crashpad.", name}); } } // namespace //! \brief A CrashReportDatabase that uses HFS+ extended attributes to store //! report metadata. //! //! The database maintains three directories of reports: `"new"` to hold crash //! reports that are in the process of being written, `"completed"` to hold //! reports that have been written and are awaiting upload, and `"uploaded"` to //! hold reports successfully uploaded to a collection server. If the user has //! opted out of report collection, reports will still be written and moved //! to the completed directory, but they just will not be uploaded. //! //! The database stores its metadata in extended filesystem attributes. To //! ensure safe access, the report file is locked using `O_EXLOCK` during all //! extended attribute operations. The lock should be obtained using //! ObtainReportLock(). class CrashReportDatabaseMac : public CrashReportDatabase { public: explicit CrashReportDatabaseMac(const base::FilePath& path); CrashReportDatabaseMac(const CrashReportDatabaseMac&) = delete; CrashReportDatabaseMac& operator=(const CrashReportDatabaseMac&) = delete; virtual ~CrashReportDatabaseMac(); bool Initialize(bool may_create); // CrashReportDatabase: Settings* GetSettings() override; OperationStatus PrepareNewCrashReport( std::unique_ptr* report) override; OperationStatus FinishedWritingCrashReport(std::unique_ptr report, UUID* uuid) override; OperationStatus LookUpCrashReport(const UUID& uuid, Report* report) override; OperationStatus GetPendingReports(std::vector* reports) override; OperationStatus GetCompletedReports(std::vector* reports) override; OperationStatus GetReportForUploading( const UUID& uuid, std::unique_ptr* report, bool report_metrics) override; OperationStatus SkipReportUpload(const UUID& uuid, Metrics::CrashSkippedReason reason) override; OperationStatus DeleteReport(const UUID& uuid) override; OperationStatus RequestUpload(const UUID& uuid) override; int CleanDatabase(time_t lockfile_ttl) override; base::FilePath DatabasePath() override; private: // CrashReportDatabase: OperationStatus RecordUploadAttempt(UploadReport* report, bool successful, const std::string& id) override; //! \brief Report states for use with LocateCrashReport(). //! //! ReportState may be considered to be a bitfield. enum ReportState : uint8_t { kReportStateWrite = 1 << 0, // in kWriteDirectory kReportStatePending = 1 << 1, // in kUploadPendingDirectory kReportStateCompleted = 1 << 2, // in kCompletedDirectory kReportStateAny = kReportStateWrite | kReportStatePending | kReportStateCompleted, }; //! \brief A private extension of the Report class that maintains bookkeeping //! information of the database. struct UploadReportMac : public UploadReport { #if BUILDFLAG(IS_IOS) //! \brief Obtain a background task assertion while a flock is in use. //! Ensure this is defined first so it is destroyed last. internal::ScopedBackgroundTask ios_background_task{"UploadReportMac"}; #else //! \brief Stores the flock of the file for the duration of //! GetReportForUploading() and RecordUploadAttempt(). base::ScopedFD lock_fd; #endif // BUILDFLAG(IS_IOS) }; //! \brief Locates a crash report in the database by UUID. //! //! \param[in] uuid The UUID of the crash report to locate. //! \param[in] desired_state The state of the report to locate, composed of //! ReportState values. //! //! \return The full path to the report file, or an empty path if it cannot be //! found. base::FilePath LocateCrashReport(const UUID& uuid, uint8_t desired_state); //! \brief Obtains an exclusive advisory lock on a file. //! //! The flock is used to prevent cross-process concurrent metadata reads or //! writes. While xattrs do not observe the lock, if the lock-then-mutate //! protocol is observed by all clients of the database, it still enforces //! synchronization. //! //! This does not block, and so callers must ensure that the lock is valid //! after calling. //! //! \param[in] path The path of the file to lock. //! //! \return A scoped lock object. If the result is not valid, an error is //! logged. static base::ScopedFD ObtainReportLock(const base::FilePath& path); //! \brief Reads all the database xattrs from a file into a Report. The file //! must be locked with ObtainReportLock. //! //! \param[in] path The path of the report. //! \param[out] report The object into which data will be read. //! //! \return `true` if all the metadata was read successfully, `false` //! otherwise. bool ReadReportMetadataLocked(const base::FilePath& path, Report* report); //! \brief Reads the metadata from all the reports in a database subdirectory. //! Invalid reports are skipped. //! //! \param[in] path The database subdirectory path. //! \param[out] reports An empty vector of reports, which will be filled. //! //! \return The operation status code. OperationStatus ReportsInDirectory(const base::FilePath& path, std::vector* reports); //! \brief Creates a database xattr name from the short constant name. //! //! \param[in] name The short name of the extended attribute. //! //! \return The long name of the extended attribute. std::string XattrName(std::string_view name); //! \brief Marks a report with a given path as completed. //! //! Assumes that the report is locked. //! //! \param[in] report_path The path of the file to mark completed. //! \param[out] out_path The path of the new file. This parameter is optional. //! //! \return The operation status code. CrashReportDatabase::OperationStatus MarkReportCompletedLocked( const base::FilePath& report_path, base::FilePath* out_path); // Cleans any attachments that have no associated report in any state. void CleanOrphanedAttachments(); Settings& SettingsInternal() { std::call_once(settings_init_, [this]() { settings_.Initialize(); }); return settings_; } const base::FilePath base_dir_; Settings settings_; std::once_flag settings_init_; bool xattr_new_names_; InitializationStateDcheck initialized_; }; CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path) : CrashReportDatabase(), base_dir_(path), settings_(path.Append(kSettings)), settings_init_(), xattr_new_names_(false), initialized_() {} CrashReportDatabaseMac::~CrashReportDatabaseMac() {} bool CrashReportDatabaseMac::Initialize(bool may_create) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); // Check if the database already exists. if (may_create) { if (!CreateOrEnsureDirectoryExists(base_dir_)) { return false; } } else if (!EnsureDirectoryExists(base_dir_)) { return false; } // Create the three processing directories for the database. for (const auto& dir : kReportDirectories) { if (!CreateOrEnsureDirectoryExists(base_dir_.Append(dir))) return false; } if (!CreateOrEnsureDirectoryExists(AttachmentsRootPath())) { return false; } // Do an xattr operation as the last step, to ensure the filesystem has // support for them. This xattr also serves as a marker for whether the // database uses old or new xattr names. bool value; if (ReadXattrBool(base_dir_, XattrNameInternal(kXattrDatabaseInitialized, true), &value) == XattrStatus::kOK && value) { xattr_new_names_ = true; } else if (ReadXattrBool(base_dir_, XattrNameInternal(kXattrDatabaseInitialized, false), &value) == XattrStatus::kOK && value) { xattr_new_names_ = false; } else { xattr_new_names_ = true; if (!WriteXattrBool(base_dir_, XattrName(kXattrDatabaseInitialized), true)) return false; } INITIALIZATION_STATE_SET_VALID(initialized_); return true; } base::FilePath CrashReportDatabaseMac::DatabasePath() { return base_dir_; } Settings* CrashReportDatabaseMac::GetSettings() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return &SettingsInternal(); } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::PrepareNewCrashReport( std::unique_ptr* out_report) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr report(new NewReport()); if (!report->Initialize(this, base_dir_.Append(kWriteDirectory), std::string(".") + kCrashReportFileExtension)) { return kFileSystemError; } // TODO(rsesek): Potentially use an fsetxattr() here instead. if (!WriteXattr(report->file_remover_.get(), XattrName(kXattrUUID), report->ReportID().ToString())) { return kDatabaseError; } out_report->reset(report.release()); return kNoError; } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::FinishedWritingCrashReport( std::unique_ptr report, UUID* uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); const base::FilePath& path = report->file_remover_.get(); // Get the report's UUID to return. std::string uuid_string; if (ReadXattr(path, XattrName(kXattrUUID), &uuid_string) != XattrStatus::kOK || !uuid->InitializeFromString(uuid_string)) { LOG(ERROR) << "Failed to read UUID for crash report " << path.value(); return kDatabaseError; } if (*uuid != report->ReportID()) { LOG(ERROR) << "UUID mismatch for crash report " << path.value(); return kDatabaseError; } // Record the creation time of this report. if (!WriteXattrTimeT(path, XattrName(kXattrCreationTime), time(nullptr))) { return kDatabaseError; } FileOffset size = report->Writer()->Seek(0, SEEK_END); // Move the report to its new location for uploading. base::FilePath new_path = base_dir_.Append(kUploadPendingDirectory).Append(path.BaseName()); if (rename(path.value().c_str(), new_path.value().c_str()) != 0) { PLOG(ERROR) << "rename " << path.value() << " to " << new_path.value(); return kFileSystemError; } std::ignore = report->file_remover_.release(); // Close all the attachments and disarm their removers too. for (auto& writer : report->attachment_writers_) { writer->Close(); } for (auto& remover : report->attachment_removers_) { std::ignore = remover.release(); } Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated); Metrics::CrashReportSize(size); return kNoError; } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::LookUpCrashReport(const UUID& uuid, CrashReportDatabase::Report* report) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); base::FilePath path = LocateCrashReport(uuid, kReportStateAny); if (path.empty()) return kReportNotFound; base::ScopedFD lock(ObtainReportLock(path)); if (!lock.is_valid()) return kBusyError; *report = Report(); report->file_path = path; if (!ReadReportMetadataLocked(path, report)) return kDatabaseError; return kNoError; } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::GetPendingReports( std::vector* reports) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return ReportsInDirectory(base_dir_.Append(kUploadPendingDirectory), reports); } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::GetCompletedReports( std::vector* reports) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return ReportsInDirectory(base_dir_.Append(kCompletedDirectory), reports); } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::GetReportForUploading( const UUID& uuid, std::unique_ptr* report, bool report_metrics) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); auto upload_report = std::make_unique(); upload_report->file_path = LocateCrashReport(uuid, kReportStatePending); if (upload_report->file_path.empty()) return kReportNotFound; base::ScopedFD lock(ObtainReportLock(upload_report->file_path)); if (!lock.is_valid()) return kBusyError; if (!ReadReportMetadataLocked(upload_report->file_path, upload_report.get())) return kDatabaseError; #if BUILDFLAG(IS_IOS) time_t upload_start_time = 0; if (ReadXattrTimeT(upload_report->file_path, XattrName(kXattrUploadStartTime), &upload_start_time) == XattrStatus::kOtherError) { return kDatabaseError; } time_t now = time(nullptr); if (upload_start_time) { // If we were able to ObtainReportLock but kXattrUploadStartTime is set, // either another client is uploading this report or a client was terminated // during an upload. CrashReportUploadThread sets the timeout to 20 seconds // for iOS. If kXattrUploadStartTime is less than 5 minutes ago, consider // the report locked and return kBusyError. Otherwise, consider the upload a // failure and skip the report. if (upload_start_time > now - 15 * internal::kUploadReportTimeoutSeconds) { return kBusyError; } else { // SkipReportUpload expects an unlocked report. lock.reset(); CrashReportDatabase::OperationStatus os = SkipReportUpload( upload_report->uuid, Metrics::CrashSkippedReason::kUploadFailed); if (os != kNoError) { return kDatabaseError; } return kReportNotFound; } } if (!WriteXattrTimeT( upload_report->file_path, XattrName(kXattrUploadStartTime), now)) { return kDatabaseError; } #endif if (!upload_report->Initialize(upload_report->file_path, this)) { return kFileSystemError; } upload_report->database_ = this; #if !BUILDFLAG(IS_IOS) upload_report->lock_fd.reset(lock.release()); #endif upload_report->report_metrics_ = report_metrics; report->reset(upload_report.release()); return kNoError; } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::RecordUploadAttempt(UploadReport* report, bool successful, const std::string& id) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (report->report_metrics_) { Metrics::CrashUploadAttempted(successful); } DCHECK(report); DCHECK(successful || id.empty()); base::FilePath report_path = LocateCrashReport(report->uuid, kReportStatePending); if (report_path.empty()) return kReportNotFound; if (successful) { CrashReportDatabase::OperationStatus os = MarkReportCompletedLocked(report_path, &report_path); if (os != kNoError) return os; } #if BUILDFLAG(IS_IOS) if (RemoveXattr(report_path, XattrName(kXattrUploadStartTime)) == XattrStatus::kOtherError) { return kDatabaseError; } #endif if (!WriteXattrBool(report_path, XattrName(kXattrIsUploaded), successful)) { return kDatabaseError; } if (!WriteXattr(report_path, XattrName(kXattrCollectorID), id)) { return kDatabaseError; } time_t now = time(nullptr); if (!WriteXattrTimeT(report_path, XattrName(kXattrLastUploadTime), now)) { return kDatabaseError; } int upload_attempts = 0; std::string name = XattrName(kXattrUploadAttemptCount); if (ReadXattrInt(report_path, name, &upload_attempts) == XattrStatus::kOtherError) { return kDatabaseError; } if (!WriteXattrInt(report_path, name, ++upload_attempts)) { return kDatabaseError; } if (!SettingsInternal().SetLastUploadAttemptTime(now)) { return kDatabaseError; } return kNoError; } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::SkipReportUpload( const UUID& uuid, Metrics::CrashSkippedReason reason) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); Metrics::CrashUploadSkipped(reason); base::FilePath report_path = LocateCrashReport(uuid, kReportStatePending); if (report_path.empty()) return kReportNotFound; base::ScopedFD lock(ObtainReportLock(report_path)); if (!lock.is_valid()) return kBusyError; #if BUILDFLAG(IS_IOS) if (RemoveXattr(report_path, XattrName(kXattrUploadStartTime)) == XattrStatus::kOtherError) { return kDatabaseError; } #endif return MarkReportCompletedLocked(report_path, nullptr); } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::DeleteReport( const UUID& uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); base::FilePath report_path = LocateCrashReport(uuid, kReportStateAny); if (report_path.empty()) return kReportNotFound; base::ScopedFD lock(ObtainReportLock(report_path)); if (!lock.is_valid()) return kBusyError; if (unlink(report_path.value().c_str()) != 0) { PLOG(ERROR) << "unlink " << report_path.value(); return kFileSystemError; } RemoveAttachmentsByUUID(uuid); return kNoError; } base::FilePath CrashReportDatabaseMac::LocateCrashReport( const UUID& uuid, uint8_t desired_state) { const std::string target_uuid = uuid.ToString(); std::vector report_directories; if (desired_state & kReportStateWrite) { report_directories.push_back(kWriteDirectory); } if (desired_state & kReportStatePending) { report_directories.push_back(kUploadPendingDirectory); } if (desired_state & kReportStateCompleted) { report_directories.push_back(kCompletedDirectory); } for (const std::string& report_directory : report_directories) { base::FilePath path = base_dir_.Append(report_directory) .Append(target_uuid + "." + kCrashReportFileExtension); // Test if the path exists. struct stat st; if (lstat(path.value().c_str(), &st)) { continue; } // Check that the UUID of the report matches. std::string uuid_string; if (ReadXattr(path, XattrName(kXattrUUID), &uuid_string) == XattrStatus::kOK && uuid_string == target_uuid) { return path; } } return base::FilePath(); } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::RequestUpload( const UUID& uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); base::FilePath report_path = LocateCrashReport(uuid, kReportStatePending | kReportStateCompleted); if (report_path.empty()) return kReportNotFound; base::ScopedFD lock(ObtainReportLock(report_path)); if (!lock.is_valid()) return kBusyError; // If the crash report has already been uploaded, don't request new upload. bool uploaded = false; XattrStatus status = ReadXattrBool(report_path, XattrName(kXattrIsUploaded), &uploaded); if (status == XattrStatus::kOtherError) return kDatabaseError; if (uploaded) return kCannotRequestUpload; // Mark the crash report as having upload explicitly requested by the user, // and move it to the pending state. if (!WriteXattrBool( report_path, XattrName(kXattrIsUploadExplicitlyRequested), true)) { return kDatabaseError; } base::FilePath new_path = base_dir_.Append(kUploadPendingDirectory).Append(report_path.BaseName()); if (rename(report_path.value().c_str(), new_path.value().c_str()) != 0) { PLOG(ERROR) << "rename " << report_path.value() << " to " << new_path.value(); return kFileSystemError; } Metrics::CrashReportPending(Metrics::PendingReportReason::kUserInitiated); return kNoError; } int CrashReportDatabaseMac::CleanDatabase(time_t lockfile_ttl) { int removed = 0; time_t now = time(nullptr); DirectoryReader reader; const base::FilePath new_dir(base_dir_.Append(kWriteDirectory)); if (reader.Open(new_dir)) { base::FilePath filename; DirectoryReader::Result result; while ((result = reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { const base::FilePath filepath(new_dir.Append(filename)); timespec filetime; if (!FileModificationTime(filepath, &filetime)) { continue; } if (filetime.tv_sec <= now - lockfile_ttl) { if (LoggingRemoveFile(filepath)) { ++removed; } } } } CleanOrphanedAttachments(); return removed; } // static base::ScopedFD CrashReportDatabaseMac::ObtainReportLock( const base::FilePath& path) { int fd = HANDLE_EINTR( open(path.value().c_str(), O_RDONLY | O_NONBLOCK | O_EXLOCK | O_NOCTTY | O_CLOEXEC)); PLOG_IF(ERROR, fd < 0) << "open lock " << path.value(); return base::ScopedFD(fd); } bool CrashReportDatabaseMac::ReadReportMetadataLocked( const base::FilePath& path, Report* report) { std::string uuid_string; if (ReadXattr(path, XattrName(kXattrUUID), &uuid_string) != XattrStatus::kOK || !report->uuid.InitializeFromString(uuid_string)) { return false; } if (ReadXattrTimeT(path, XattrName(kXattrCreationTime), &report->creation_time) != XattrStatus::kOK) { return false; } report->id = std::string(); if (ReadXattr(path, XattrName(kXattrCollectorID), &report->id) == XattrStatus::kOtherError) { return false; } report->uploaded = false; if (ReadXattrBool(path, XattrName(kXattrIsUploaded), &report->uploaded) == XattrStatus::kOtherError) { return false; } report->last_upload_attempt_time = 0; if (ReadXattrTimeT(path, XattrName(kXattrLastUploadTime), &report->last_upload_attempt_time) == XattrStatus::kOtherError) { return false; } report->upload_attempts = 0; if (ReadXattrInt(path, XattrName(kXattrUploadAttemptCount), &report->upload_attempts) == XattrStatus::kOtherError) { return false; } report->upload_explicitly_requested = false; if (ReadXattrBool(path, XattrName(kXattrIsUploadExplicitlyRequested), &report->upload_explicitly_requested) == XattrStatus::kOtherError) { return false; } // Seed the total size with the main report size and then add the sizes of any // potential attachments. uint64_t total_size = GetFileSize(path); total_size += GetDirectorySize(AttachmentsPath(report->uuid)); report->total_size = total_size; return true; } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::ReportsInDirectory( const base::FilePath& path, std::vector* reports) { base::apple::ScopedNSAutoreleasePool pool; DCHECK(reports->empty()); NSError* error = nil; NSArray* paths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:base::SysUTF8ToNSString(path.value()) error:&error]; if (error) { LOG(ERROR) << "Failed to enumerate reports in directory " << path.value() << ": " << [[error description] UTF8String]; return kFileSystemError; } reports->reserve([paths count]); for (NSString* entry in paths) { Report report; report.file_path = path.Append([entry fileSystemRepresentation]); base::ScopedFD lock(ObtainReportLock(report.file_path)); if (!lock.is_valid()) continue; if (!ReadReportMetadataLocked(report.file_path, &report)) { LOG(WARNING) << "Failed to read report metadata for " << report.file_path.value(); continue; } reports->push_back(report); } return kNoError; } std::string CrashReportDatabaseMac::XattrName(std::string_view name) { return XattrNameInternal(name, xattr_new_names_); } CrashReportDatabase::OperationStatus CrashReportDatabaseMac::MarkReportCompletedLocked( const base::FilePath& report_path, base::FilePath* out_path) { if (RemoveXattr(report_path, XattrName(kXattrIsUploadExplicitlyRequested)) == XattrStatus::kOtherError) { return kDatabaseError; } base::FilePath new_path = base_dir_.Append(kCompletedDirectory).Append(report_path.BaseName()); if (rename(report_path.value().c_str(), new_path.value().c_str()) != 0) { PLOG(ERROR) << "rename " << report_path.value() << " to " << new_path.value(); return kFileSystemError; } if (out_path) *out_path = new_path; return kNoError; } void CrashReportDatabaseMac::CleanOrphanedAttachments() { base::FilePath root_attachments_dir(AttachmentsRootPath()); DirectoryReader reader; if (!reader.Open(root_attachments_dir)) { return; } base::FilePath filename; DirectoryReader::Result result; while ((result = reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { const base::FilePath report_attachment_dir( root_attachments_dir.Append(filename)); if (IsDirectory(report_attachment_dir, false)) { UUID uuid; if (!uuid.InitializeFromString(filename.value())) { LOG(ERROR) << "unexpected attachment dir name " << filename.value(); continue; } // Check to see if the report is being created in "new". base::FilePath new_dir_path = base_dir_.Append(kWriteDirectory) .Append(uuid.ToString() + "." + kCrashReportFileExtension); if (IsRegularFile(new_dir_path)) { continue; } // Check to see if the report is in "pending" or "completed". base::FilePath local_path = LocateCrashReport(uuid, kReportStatePending | kReportStateCompleted); if (!local_path.empty()) { continue; } // Couldn't find a report, assume these attachments are orphaned. RemoveAttachmentsByUUID(uuid); } } } namespace { std::unique_ptr InitializeInternal( const base::FilePath& path, bool may_create) { std::unique_ptr database_mac( new CrashReportDatabaseMac(path)); if (!database_mac->Initialize(may_create)) database_mac.reset(); return std::unique_ptr(database_mac.release()); } } // namespace // static std::unique_ptr CrashReportDatabase::Initialize( const base::FilePath& path) { return InitializeInternal(path, true); } // static std::unique_ptr CrashReportDatabase::InitializeWithoutCreating(const base::FilePath& path) { return InitializeInternal(path, false); } // static std::unique_ptr CrashReportDatabase::GetSettingsReaderForDatabasePath( const base::FilePath& path) { return std::make_unique(path.Append(kSettings)); } } // namespace crashpad ================================================ FILE: client/crash_report_database_test.cc ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crash_report_database.h" #include "build/build_config.h" #include "client/settings.h" #include "gtest/gtest.h" #include "test/errors.h" #include "test/file.h" #include "test/filesystem.h" #include "test/scoped_temp_dir.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" #if BUILDFLAG(IS_IOS) #include "util/mac/xattr.h" #endif namespace crashpad { namespace test { namespace { class CrashReportDatabaseTest : public testing::Test { public: CrashReportDatabaseTest() {} CrashReportDatabaseTest(const CrashReportDatabaseTest&) = delete; CrashReportDatabaseTest& operator=(const CrashReportDatabaseTest&) = delete; protected: // testing::Test: void SetUp() override { db_ = CrashReportDatabase::Initialize(path()); ASSERT_TRUE(db_); } void ResetDatabase() { db_.reset(); } CrashReportDatabase* db() { return db_.get(); } base::FilePath path() const { return temp_dir_.path().Append(FILE_PATH_LITERAL("crashpad_test_database")); } void CreateCrashReport(CrashReportDatabase::Report* report) { std::unique_ptr new_report; ASSERT_EQ(db_->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); static constexpr char kTest[] = "test"; ASSERT_TRUE(new_report->Writer()->Write(kTest, sizeof(kTest))); char contents[sizeof(kTest)]; FileReaderInterface* reader = new_report->Reader(); ASSERT_TRUE(reader->ReadExactly(contents, sizeof(contents))); EXPECT_EQ(memcmp(contents, kTest, sizeof(contents)), 0); EXPECT_EQ(reader->ReadExactly(contents, 1), 0); UUID uuid; EXPECT_EQ(db_->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); EXPECT_EQ(db_->LookUpCrashReport(uuid, report), CrashReportDatabase::kNoError); ExpectPreparedCrashReport(*report); } void UploadReport(const UUID& uuid, bool successful, const std::string& id) { Settings* const settings = db_->GetSettings(); ASSERT_TRUE(settings); time_t times[2]; ASSERT_TRUE(settings->GetLastUploadAttemptTime(×[0])); std::unique_ptr report; ASSERT_EQ(db_->GetReportForUploading(uuid, &report), CrashReportDatabase::kNoError); EXPECT_NE(report->uuid, UUID()); EXPECT_FALSE(report->file_path.empty()); EXPECT_TRUE(FileExists(report->file_path)) << report->file_path.value(); EXPECT_GT(report->creation_time, 0); if (successful) { EXPECT_EQ(db_->RecordUploadComplete(std::move(report), id), CrashReportDatabase::kNoError); } else { report.reset(); } ASSERT_TRUE(settings->GetLastUploadAttemptTime(×[1])); EXPECT_NE(times[1], 0); EXPECT_GE(times[1], times[0]); } void ExpectPreparedCrashReport(const CrashReportDatabase::Report& report) { EXPECT_NE(report.uuid, UUID()); EXPECT_FALSE(report.file_path.empty()); EXPECT_TRUE(FileExists(report.file_path)) << report.file_path.value(); EXPECT_TRUE(report.id.empty()); EXPECT_GT(report.creation_time, 0); EXPECT_FALSE(report.uploaded); EXPECT_EQ(report.last_upload_attempt_time, 0); EXPECT_EQ(report.upload_attempts, 0); EXPECT_FALSE(report.upload_explicitly_requested); EXPECT_GE(report.total_size, 0u); } void RelocateDatabase() { ResetDatabase(); temp_dir_.Rename(); SetUp(); } CrashReportDatabase::OperationStatus RequestUpload(const UUID& uuid) { CrashReportDatabase::OperationStatus os = db()->RequestUpload(uuid); CrashReportDatabase::Report report; EXPECT_EQ(db_->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); return os; } private: ScopedTempDir temp_dir_; std::unique_ptr db_; }; TEST_F(CrashReportDatabaseTest, Initialize) { // Initialize the database for the first time, creating it. ASSERT_TRUE(db()); Settings* settings = db()->GetSettings(); UUID client_ids[3]; ASSERT_TRUE(settings->GetClientID(&client_ids[0])); EXPECT_NE(client_ids[0], UUID()); time_t last_upload_attempt_time; ASSERT_TRUE(settings->GetLastUploadAttemptTime(&last_upload_attempt_time)); EXPECT_EQ(last_upload_attempt_time, 0); // Close and reopen the database at the same path. ResetDatabase(); EXPECT_FALSE(db()); auto db = CrashReportDatabase::InitializeWithoutCreating(path()); ASSERT_TRUE(db); settings = db->GetSettings(); ASSERT_TRUE(settings->GetClientID(&client_ids[1])); EXPECT_EQ(client_ids[1], client_ids[0]); ASSERT_TRUE(settings->GetLastUploadAttemptTime(&last_upload_attempt_time)); EXPECT_EQ(last_upload_attempt_time, 0); // Check that the database can also be opened by the method that is permitted // to create it. db = CrashReportDatabase::Initialize(path()); ASSERT_TRUE(db); settings = db->GetSettings(); ASSERT_TRUE(settings->GetClientID(&client_ids[2])); EXPECT_EQ(client_ids[2], client_ids[0]); ASSERT_TRUE(settings->GetLastUploadAttemptTime(&last_upload_attempt_time)); EXPECT_EQ(last_upload_attempt_time, 0); std::vector reports; EXPECT_EQ(db->GetPendingReports(&reports), CrashReportDatabase::kNoError); EXPECT_TRUE(reports.empty()); reports.clear(); EXPECT_EQ(db->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_TRUE(reports.empty()); // InitializeWithoutCreating() shouldn’t create a nonexistent database. base::FilePath non_database_path = path().DirName().Append(FILE_PATH_LITERAL("not_a_database")); db = CrashReportDatabase::InitializeWithoutCreating(non_database_path); EXPECT_FALSE(db); } TEST_F(CrashReportDatabaseTest, Settings) { // Initialize three databases and ensure settings.dat isn't created yet. ASSERT_TRUE(db()); base::FilePath settings_path = path().Append(FILE_PATH_LITERAL("settings.dat")); EXPECT_FALSE(FileExists(settings_path)); std::unique_ptr db2 = CrashReportDatabase::Initialize(path()); ASSERT_TRUE(db2); EXPECT_FALSE(FileExists(settings_path)); std::unique_ptr db3 = CrashReportDatabase::Initialize(path()); ASSERT_TRUE(db3); EXPECT_FALSE(FileExists(settings_path)); // Ensure settings.dat exists after getter. Settings* settings = db3->GetSettings(); ASSERT_TRUE(settings); EXPECT_TRUE(FileExists(settings_path)); time_t last_upload_attempt_time = 42; ASSERT_TRUE(settings->SetLastUploadAttemptTime(last_upload_attempt_time)); // Ensure the first two databases read the same value. ASSERT_TRUE( db2->GetSettings()->GetLastUploadAttemptTime(&last_upload_attempt_time)); EXPECT_EQ(last_upload_attempt_time, 42); ASSERT_TRUE( db()->GetSettings()->GetLastUploadAttemptTime(&last_upload_attempt_time)); EXPECT_EQ(last_upload_attempt_time, 42); } TEST_F(CrashReportDatabaseTest, NewCrashReport) { std::unique_ptr new_report; EXPECT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); UUID expect_uuid = new_report->ReportID(); UUID uuid; EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); EXPECT_EQ(uuid, expect_uuid); CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); ExpectPreparedCrashReport(report); std::vector reports; EXPECT_EQ(db()->GetPendingReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 1u); EXPECT_EQ(reports[0].uuid, report.uuid); reports.clear(); EXPECT_EQ(db()->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_TRUE(reports.empty()); } TEST_F(CrashReportDatabaseTest, LookUpCrashReport) { UUID uuid; { CrashReportDatabase::Report report; CreateCrashReport(&report); uuid = report.uuid; } { CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); EXPECT_EQ(report.uuid, uuid); EXPECT_NE(report.file_path.value().find(path().value()), std::string::npos); EXPECT_EQ(report.id, std::string()); EXPECT_FALSE(report.uploaded); EXPECT_EQ(report.last_upload_attempt_time, 0); EXPECT_EQ(report.upload_attempts, 0); EXPECT_FALSE(report.upload_explicitly_requested); } UploadReport(uuid, true, "test"); { CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); EXPECT_EQ(report.uuid, uuid); EXPECT_NE(report.file_path.value().find(path().value()), std::string::npos); EXPECT_EQ(report.id, "test"); EXPECT_TRUE(report.uploaded); EXPECT_NE(report.last_upload_attempt_time, 0); EXPECT_EQ(report.upload_attempts, 1); EXPECT_FALSE(report.upload_explicitly_requested); } } TEST_F(CrashReportDatabaseTest, RecordUploadAttempt) { std::vector reports(3); CreateCrashReport(&reports[0]); CreateCrashReport(&reports[1]); CreateCrashReport(&reports[2]); // Record two attempts: one successful, one not. UploadReport(reports[1].uuid, false, std::string()); UploadReport(reports[2].uuid, true, "abc123"); std::vector query(3); EXPECT_EQ(db()->LookUpCrashReport(reports[0].uuid, &query[0]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[1].uuid, &query[1]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[2].uuid, &query[2]), CrashReportDatabase::kNoError); EXPECT_EQ(query[0].id, std::string()); EXPECT_EQ(query[1].id, std::string()); EXPECT_EQ(query[2].id, "abc123"); EXPECT_FALSE(query[0].uploaded); EXPECT_FALSE(query[1].uploaded); EXPECT_TRUE(query[2].uploaded); EXPECT_EQ(query[0].last_upload_attempt_time, 0); EXPECT_NE(query[1].last_upload_attempt_time, 0); EXPECT_NE(query[2].last_upload_attempt_time, 0); EXPECT_EQ(query[0].upload_attempts, 0); EXPECT_EQ(query[1].upload_attempts, 1); EXPECT_EQ(query[2].upload_attempts, 1); // Attempt to upload and fail again. UploadReport(reports[1].uuid, false, std::string()); time_t report_2_upload_time = query[2].last_upload_attempt_time; EXPECT_EQ(db()->LookUpCrashReport(reports[0].uuid, &query[0]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[1].uuid, &query[1]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[2].uuid, &query[2]), CrashReportDatabase::kNoError); EXPECT_FALSE(query[0].uploaded); EXPECT_FALSE(query[1].uploaded); EXPECT_TRUE(query[2].uploaded); EXPECT_EQ(query[0].last_upload_attempt_time, 0); EXPECT_GE(query[1].last_upload_attempt_time, report_2_upload_time); EXPECT_EQ(query[2].last_upload_attempt_time, report_2_upload_time); EXPECT_EQ(query[0].upload_attempts, 0); EXPECT_EQ(query[1].upload_attempts, 2); EXPECT_EQ(query[2].upload_attempts, 1); // Third time's the charm: upload and succeed. UploadReport(reports[1].uuid, true, "666hahaha"); time_t report_1_upload_time = query[1].last_upload_attempt_time; EXPECT_EQ(db()->LookUpCrashReport(reports[0].uuid, &query[0]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[1].uuid, &query[1]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[2].uuid, &query[2]), CrashReportDatabase::kNoError); EXPECT_FALSE(query[0].uploaded); EXPECT_TRUE(query[1].uploaded); EXPECT_TRUE(query[2].uploaded); EXPECT_EQ(query[0].last_upload_attempt_time, 0); EXPECT_GE(query[1].last_upload_attempt_time, report_1_upload_time); EXPECT_EQ(query[2].last_upload_attempt_time, report_2_upload_time); EXPECT_EQ(query[0].upload_attempts, 0); EXPECT_EQ(query[1].upload_attempts, 3); EXPECT_EQ(query[2].upload_attempts, 1); } // This test covers both query functions since they are related. TEST_F(CrashReportDatabaseTest, GetCompletedAndNotUploadedReports) { std::vector reports(5); CreateCrashReport(&reports[0]); CreateCrashReport(&reports[1]); CreateCrashReport(&reports[2]); CreateCrashReport(&reports[3]); CreateCrashReport(&reports[4]); const UUID& report_0_uuid = reports[0].uuid; const UUID& report_1_uuid = reports[1].uuid; const UUID& report_2_uuid = reports[2].uuid; const UUID& report_3_uuid = reports[3].uuid; const UUID& report_4_uuid = reports[4].uuid; std::vector pending; EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); std::vector completed; EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); EXPECT_EQ(pending.size(), reports.size()); EXPECT_EQ(completed.size(), 0u); // Upload one report successfully. UploadReport(report_1_uuid, true, "report1"); pending.clear(); EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); completed.clear(); EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); EXPECT_EQ(pending.size(), 4u); ASSERT_EQ(completed.size(), 1u); for (const auto& report : pending) { EXPECT_NE(report.uuid, report_1_uuid); EXPECT_FALSE(report.file_path.empty()); } EXPECT_EQ(completed[0].uuid, report_1_uuid); EXPECT_EQ(completed[0].id, "report1"); EXPECT_EQ(completed[0].uploaded, true); EXPECT_GT(completed[0].last_upload_attempt_time, 0); EXPECT_EQ(completed[0].upload_attempts, 1); // Fail to upload one report. UploadReport(report_2_uuid, false, std::string()); pending.clear(); EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); completed.clear(); EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); EXPECT_EQ(pending.size(), 4u); ASSERT_EQ(completed.size(), 1u); for (const auto& report : pending) { if (report.upload_attempts != 0) { EXPECT_EQ(report.uuid, report_2_uuid); EXPECT_GT(report.last_upload_attempt_time, 0); EXPECT_FALSE(report.uploaded); EXPECT_TRUE(report.id.empty()); } EXPECT_FALSE(report.file_path.empty()); } // Upload a second report. UploadReport(report_4_uuid, true, "report_4"); pending.clear(); EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); completed.clear(); EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); EXPECT_EQ(pending.size(), 3u); ASSERT_EQ(completed.size(), 2u); // Succeed the failed report. UploadReport(report_2_uuid, true, "report 2"); pending.clear(); EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); completed.clear(); EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); EXPECT_EQ(pending.size(), 2u); ASSERT_EQ(completed.size(), 3u); for (const auto& report : pending) { EXPECT_TRUE(report.uuid == report_0_uuid || report.uuid == report_3_uuid); EXPECT_FALSE(report.file_path.empty()); } // Skip upload for one report. EXPECT_EQ(db()->SkipReportUpload( report_3_uuid, Metrics::CrashSkippedReason::kUploadsDisabled), CrashReportDatabase::kNoError); pending.clear(); EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); completed.clear(); EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); ASSERT_EQ(pending.size(), 1u); ASSERT_EQ(completed.size(), 4u); EXPECT_EQ(pending[0].uuid, report_0_uuid); for (const auto& report : completed) { if (report.uuid == report_3_uuid) { EXPECT_FALSE(report.uploaded); EXPECT_EQ(report.upload_attempts, 0); EXPECT_EQ(report.last_upload_attempt_time, 0); } else { EXPECT_TRUE(report.uploaded); EXPECT_GT(report.upload_attempts, 0); EXPECT_GT(report.last_upload_attempt_time, 0); } EXPECT_FALSE(report.file_path.empty()); } } TEST_F(CrashReportDatabaseTest, DuelingUploads) { CrashReportDatabase::Report report; CreateCrashReport(&report); std::unique_ptr upload_report; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report), CrashReportDatabase::kNoError); std::unique_ptr upload_report_2; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report_2), CrashReportDatabase::kBusyError); EXPECT_FALSE(upload_report_2); EXPECT_EQ(db()->RecordUploadComplete(std::move(upload_report), std::string()), CrashReportDatabase::kNoError); } #if BUILDFLAG(IS_IOS) TEST_F(CrashReportDatabaseTest, InterruptedIOSUploads) { CrashReportDatabase::Report report; CreateCrashReport(&report); std::unique_ptr upload_report; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report), CrashReportDatabase::kNoError); // Set upload_start_time to 10 minutes ago. time_t ten_minutes_ago = time(nullptr) - 10 * 60; ASSERT_TRUE( WriteXattrTimeT(report.file_path, "org.chromium.crashpad.database.upload_start_time", ten_minutes_ago)); std::vector reports; EXPECT_EQ(db()->GetPendingReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 1u); reports.clear(); EXPECT_EQ(db()->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_TRUE(reports.empty()); // Getting a stale report will automatically skip it. std::unique_ptr upload_report_2; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report_2), CrashReportDatabase::kReportNotFound); EXPECT_FALSE(upload_report_2); // Confirm report was moved from pending to completed. EXPECT_EQ(db()->GetPendingReports(&reports), CrashReportDatabase::kNoError); EXPECT_TRUE(reports.empty()); EXPECT_EQ(db()->GetCompletedReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 1u); EXPECT_EQ(db()->RecordUploadComplete(std::move(upload_report), std::string()), CrashReportDatabase::kReportNotFound); } #endif TEST_F(CrashReportDatabaseTest, UploadAlreadyUploaded) { CrashReportDatabase::Report report; CreateCrashReport(&report); std::unique_ptr upload_report; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report), CrashReportDatabase::kNoError); EXPECT_EQ(db()->RecordUploadComplete(std::move(upload_report), std::string()), CrashReportDatabase::kNoError); std::unique_ptr upload_report_2; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report_2), CrashReportDatabase::kReportNotFound); EXPECT_FALSE(upload_report_2.get()); } TEST_F(CrashReportDatabaseTest, MoveDatabase) { std::unique_ptr new_report; EXPECT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); UUID uuid; EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); RelocateDatabase(); CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); ExpectPreparedCrashReport(report); } TEST_F(CrashReportDatabaseTest, ReportRemoved) { std::unique_ptr new_report; EXPECT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); UUID uuid; EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); EXPECT_TRUE(LoggingRemoveFile(report.file_path)); EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kReportNotFound); } TEST_F(CrashReportDatabaseTest, DeleteReport) { CrashReportDatabase::Report keep_pending; CrashReportDatabase::Report delete_pending; CrashReportDatabase::Report keep_completed; CrashReportDatabase::Report delete_completed; CreateCrashReport(&keep_pending); CreateCrashReport(&delete_pending); CreateCrashReport(&keep_completed); CreateCrashReport(&delete_completed); EXPECT_TRUE(FileExists(keep_pending.file_path)); EXPECT_TRUE(FileExists(delete_pending.file_path)); EXPECT_TRUE(FileExists(keep_completed.file_path)); EXPECT_TRUE(FileExists(delete_completed.file_path)); UploadReport(keep_completed.uuid, true, "1"); UploadReport(delete_completed.uuid, true, "2"); EXPECT_EQ(db()->LookUpCrashReport(keep_completed.uuid, &keep_completed), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(delete_completed.uuid, &delete_completed), CrashReportDatabase::kNoError); EXPECT_TRUE(FileExists(keep_completed.file_path)); EXPECT_TRUE(FileExists(delete_completed.file_path)); EXPECT_EQ(db()->DeleteReport(delete_pending.uuid), CrashReportDatabase::kNoError); EXPECT_FALSE(FileExists(delete_pending.file_path)); EXPECT_EQ(db()->LookUpCrashReport(delete_pending.uuid, &delete_pending), CrashReportDatabase::kReportNotFound); EXPECT_EQ(db()->DeleteReport(delete_pending.uuid), CrashReportDatabase::kReportNotFound); EXPECT_EQ(db()->DeleteReport(delete_completed.uuid), CrashReportDatabase::kNoError); EXPECT_FALSE(FileExists(delete_completed.file_path)); EXPECT_EQ(db()->LookUpCrashReport(delete_completed.uuid, &delete_completed), CrashReportDatabase::kReportNotFound); EXPECT_EQ(db()->DeleteReport(delete_completed.uuid), CrashReportDatabase::kReportNotFound); EXPECT_EQ(db()->LookUpCrashReport(keep_pending.uuid, &keep_pending), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(keep_completed.uuid, &keep_completed), CrashReportDatabase::kNoError); } TEST_F(CrashReportDatabaseTest, DeleteReportEmptyingDatabase) { CrashReportDatabase::Report report; CreateCrashReport(&report); EXPECT_TRUE(FileExists(report.file_path)); UploadReport(report.uuid, true, "1"); EXPECT_EQ(db()->LookUpCrashReport(report.uuid, &report), CrashReportDatabase::kNoError); EXPECT_TRUE(FileExists(report.file_path)); // This causes an empty database to be written, make sure this is handled. EXPECT_EQ(db()->DeleteReport(report.uuid), CrashReportDatabase::kNoError); EXPECT_FALSE(FileExists(report.file_path)); } TEST_F(CrashReportDatabaseTest, ReadEmptyDatabase) { CrashReportDatabase::Report report; CreateCrashReport(&report); EXPECT_EQ(db()->DeleteReport(report.uuid), CrashReportDatabase::kNoError); // Deleting and the creating another report causes an empty database to be // loaded. Make sure this is handled. CrashReportDatabase::Report report2; CreateCrashReport(&report2); } TEST_F(CrashReportDatabaseTest, RequestUpload) { std::vector reports(2); CreateCrashReport(&reports[0]); CreateCrashReport(&reports[1]); const UUID& report_0_uuid = reports[0].uuid; const UUID& report_1_uuid = reports[1].uuid; // Skipped report gets back to pending state after RequestUpload is called. EXPECT_EQ(db()->SkipReportUpload( report_1_uuid, Metrics::CrashSkippedReason::kUploadsDisabled), CrashReportDatabase::kNoError); std::vector pending_reports; CrashReportDatabase::OperationStatus os = db()->GetPendingReports(&pending_reports); EXPECT_EQ(os, CrashReportDatabase::kNoError); ASSERT_EQ(pending_reports.size(), 1u); EXPECT_EQ(report_0_uuid, pending_reports[0].uuid); pending_reports.clear(); EXPECT_EQ(RequestUpload(report_1_uuid), CrashReportDatabase::kNoError); os = db()->GetPendingReports(&pending_reports); EXPECT_EQ(os, CrashReportDatabase::kNoError); ASSERT_EQ(pending_reports.size(), 2u); // Check individual reports. const CrashReportDatabase::Report* explicitly_requested_report; const CrashReportDatabase::Report* pending_report; if (pending_reports[0].uuid == report_0_uuid) { pending_report = &pending_reports[0]; explicitly_requested_report = &pending_reports[1]; } else { pending_report = &pending_reports[1]; explicitly_requested_report = &pending_reports[0]; } EXPECT_EQ(pending_report->uuid, report_0_uuid); EXPECT_FALSE(pending_report->upload_explicitly_requested); EXPECT_EQ(explicitly_requested_report->uuid, report_1_uuid); EXPECT_TRUE(explicitly_requested_report->upload_explicitly_requested); // Explicitly requested reports will not have upload_explicitly_requested bit // after getting skipped. EXPECT_EQ(db()->SkipReportUpload( report_1_uuid, Metrics::CrashSkippedReason::kUploadsDisabled), CrashReportDatabase::kNoError); CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(report_1_uuid, &report), CrashReportDatabase::kNoError); EXPECT_FALSE(report.upload_explicitly_requested); // Pending report gets correctly affected after RequestUpload is called. pending_reports.clear(); EXPECT_EQ(RequestUpload(report_0_uuid), CrashReportDatabase::kNoError); os = db()->GetPendingReports(&pending_reports); EXPECT_EQ(os, CrashReportDatabase::kNoError); EXPECT_EQ(pending_reports.size(), 1u); EXPECT_EQ(report_0_uuid, pending_reports[0].uuid); EXPECT_TRUE(pending_reports[0].upload_explicitly_requested); // Already uploaded report cannot be requested for the new upload. UploadReport(report_0_uuid, true, "1"); EXPECT_EQ(RequestUpload(report_0_uuid), CrashReportDatabase::kCannotRequestUpload); } TEST_F(CrashReportDatabaseTest, Attachments) { std::unique_ptr new_report; ASSERT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); FileWriter* attach_some_file = new_report->AddAttachment("some_file"); ASSERT_NE(attach_some_file, nullptr); static constexpr char test_data[] = "test data"; attach_some_file->Write(test_data, sizeof(test_data)); FileWriter* failed_attach = new_report->AddAttachment("not/a valid fi!e"); EXPECT_EQ(failed_attach, nullptr); UUID expect_uuid = new_report->ReportID(); UUID uuid; ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); EXPECT_EQ(uuid, expect_uuid); CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); ExpectPreparedCrashReport(report); std::vector reports; EXPECT_EQ(db()->GetPendingReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 1u); EXPECT_EQ(reports[0].uuid, report.uuid); std::unique_ptr upload_report; ASSERT_EQ(db()->GetReportForUploading(reports[0].uuid, &upload_report), CrashReportDatabase::kNoError); std::map result_attachments = upload_report->GetAttachments(); EXPECT_EQ(result_attachments.size(), 1u); EXPECT_NE(result_attachments.find("some_file"), result_attachments.end()); char result_buffer[sizeof(test_data)]; result_attachments["some_file"]->Read(result_buffer, sizeof(result_buffer)); EXPECT_EQ(memcmp(test_data, result_buffer, sizeof(test_data)), 0); } TEST_F(CrashReportDatabaseTest, OrphanedAttachments) { std::unique_ptr new_report; ASSERT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); FileWriter* file1 = new_report->AddAttachment("file1"); ASSERT_NE(file1, nullptr); FileWriter* file2 = new_report->AddAttachment("file2"); ASSERT_NE(file2, nullptr); UUID expect_uuid = new_report->ReportID(); UUID uuid; ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); EXPECT_EQ(uuid, expect_uuid); #if BUILDFLAG(IS_WIN) const std::wstring uuid_string = uuid.ToWString(); #else const std::string uuid_string = uuid.ToString(); #endif const base::FilePath report_attachments_dir( path().Append(FILE_PATH_LITERAL("attachments")).Append(uuid_string)); const base::FilePath file_path1( report_attachments_dir.Append(FILE_PATH_LITERAL("file1"))); const base::FilePath file_path2( report_attachments_dir.Append(FILE_PATH_LITERAL("file2"))); CrashReportDatabase::Report report; ASSERT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); // Cleaning a consistent database does not remove the attachements. EXPECT_EQ(db()->CleanDatabase(0), 0); EXPECT_TRUE(FileExists(file_path1)); EXPECT_TRUE(FileExists(file_path1)); ASSERT_TRUE(LoggingRemoveFile(report.file_path)); #if !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_WIN) // CrashReportDatabaseMac stores metadata in xattrs and does not have .meta // files. // CrashReportDatabaseWin stores metadata in a global metadata file and not // per report. ASSERT_TRUE(LoggingRemoveFile(base::FilePath( report.file_path.RemoveFinalExtension().value() + ".meta"))); #endif ASSERT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kReportNotFound); EXPECT_TRUE(FileExists(file_path1)); EXPECT_TRUE(FileExists(file_path1)); #if BUILDFLAG(IS_WIN) // On Windows, reports removed from metadata are counted, even if the file // is not on the disk. EXPECT_EQ(db()->CleanDatabase(0), 1); #else EXPECT_EQ(db()->CleanDatabase(0), 0); #endif EXPECT_FALSE(FileExists(file_path1)); EXPECT_FALSE(FileExists(file_path2)); EXPECT_FALSE(FileExists(report_attachments_dir)); } // This test uses knowledge of the database format to break it, so it only // applies to the unfified database implementation. #if !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_WIN) TEST_F(CrashReportDatabaseTest, CleanBrokenDatabase) { // Remove report files if metadata goes missing. CrashReportDatabase::Report report; ASSERT_NO_FATAL_FAILURE(CreateCrashReport(&report)); const base::FilePath metadata( report.file_path.RemoveFinalExtension().value() + FILE_PATH_LITERAL(".meta")); ASSERT_TRUE(PathExists(report.file_path)); ASSERT_TRUE(PathExists(metadata)); ASSERT_TRUE(LoggingRemoveFile(metadata)); EXPECT_EQ(db()->CleanDatabase(0), 1); EXPECT_FALSE(PathExists(report.file_path)); EXPECT_FALSE(PathExists(metadata)); // Remove metadata files if reports go missing. ASSERT_NO_FATAL_FAILURE(CreateCrashReport(&report)); const base::FilePath metadata2( report.file_path.RemoveFinalExtension().value() + FILE_PATH_LITERAL(".meta")); ASSERT_TRUE(PathExists(report.file_path)); ASSERT_TRUE(PathExists(metadata2)); ASSERT_TRUE(LoggingRemoveFile(report.file_path)); EXPECT_EQ(db()->CleanDatabase(0), 1); EXPECT_FALSE(PathExists(report.file_path)); EXPECT_FALSE(PathExists(metadata2)); // Remove stale new files. std::unique_ptr new_report; EXPECT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); new_report->Writer()->Close(); EXPECT_EQ(db()->CleanDatabase(0), 1); // Remove stale lock files and their associated reports. ASSERT_NO_FATAL_FAILURE(CreateCrashReport(&report)); const base::FilePath metadata3( report.file_path.RemoveFinalExtension().value() + FILE_PATH_LITERAL(".meta")); ASSERT_TRUE(PathExists(report.file_path)); ASSERT_TRUE(PathExists(metadata3)); const base::FilePath lockpath( report.file_path.RemoveFinalExtension().value() + FILE_PATH_LITERAL(".lock")); ScopedFileHandle handle(LoggingOpenFileForWrite( lockpath, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)); ASSERT_TRUE(handle.is_valid()); time_t expired_timestamp = time(nullptr) - 60 * 60 * 24 * 3; ASSERT_TRUE(LoggingWriteFile( handle.get(), &expired_timestamp, sizeof(expired_timestamp))); ASSERT_TRUE(LoggingCloseFile(handle.release())); EXPECT_EQ(db()->CleanDatabase(0), 1); EXPECT_FALSE(PathExists(report.file_path)); EXPECT_FALSE(PathExists(metadata3)); } #endif // !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_WIN) TEST_F(CrashReportDatabaseTest, TotalSize_MainReportOnly) { std::unique_ptr new_report; ASSERT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); // Main report. static constexpr char main_report_data[] = "dlbvandslhb"; ASSERT_TRUE( new_report->Writer()->Write(main_report_data, sizeof(main_report_data))); UUID uuid; ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); CrashReportDatabase::Report report; ASSERT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); EXPECT_EQ(report.total_size, sizeof(main_report_data)); } TEST_F(CrashReportDatabaseTest, GetReportSize_RightSizeWithAttachments) { std::unique_ptr new_report; ASSERT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); // Main report. static constexpr char main_report_data[] = "dlbvandslhb"; ASSERT_TRUE( new_report->Writer()->Write(main_report_data, sizeof(main_report_data))); // First attachment. FileWriter* attachment_1 = new_report->AddAttachment("my_attachment_1"); ASSERT_NE(attachment_1, nullptr); static constexpr char attachment_1_data[] = "vKDnidhvbiudshoihbvdsoiuh nhh"; attachment_1->Write(attachment_1_data, sizeof(attachment_1_data)); // Second attachment. FileWriter* attachment_2 = new_report->AddAttachment("my_attachment_2"); ASSERT_NE(attachment_2, nullptr); static constexpr char attachment_2_data[] = "sgvsvgusiyguysigfkhpmo-["; attachment_2->Write(attachment_2_data, sizeof(attachment_2_data)); UUID uuid; ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); CrashReportDatabase::Report report; ASSERT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); EXPECT_EQ(report.total_size, sizeof(main_report_data) + sizeof(attachment_1_data) + sizeof(attachment_2_data)); } TEST_F(CrashReportDatabaseTest, InitializeFromLargerFileRetainsClientId) { // Initialize the database for the first time, creating it. ASSERT_TRUE(db()); const base::FilePath settings_path = path().Append(FILE_PATH_LITERAL("settings.dat")); EXPECT_FALSE(FileExists(settings_path)); Settings* settings = db()->GetSettings(); ASSERT_TRUE(settings); EXPECT_TRUE(FileExists(settings_path)); UUID client_id; ASSERT_TRUE(settings->GetClientID(&client_id)); EXPECT_NE(client_id, UUID()); // Close and reopen the database at the same path. ResetDatabase(); EXPECT_FALSE(db()); EXPECT_TRUE(FileExists(settings_path)); // Append some data, to ensure that we can open settings even if a future // version has added additional field to Settings (forwards compatible). FileWriter settings_writer; ASSERT_TRUE(settings_writer.Open( settings_path, FileWriteMode::kReuseOrFail, FilePermissions::kOwnerOnly)); ASSERT_NE(settings_writer.Seek(0, SEEK_END), 0); constexpr uint64_t extra_garbage = 0xBADF00D; ASSERT_TRUE(settings_writer.Write(&extra_garbage, sizeof(extra_garbage))); settings_writer.Close(); auto db = CrashReportDatabase::InitializeWithoutCreating(path()); ASSERT_TRUE(db); settings = db->GetSettings(); ASSERT_TRUE(settings); // Make sure that the reopened settings retained the original client id and // wasn't recreated. UUID reopened_client_id; ASSERT_TRUE(settings->GetClientID(&reopened_client_id)); EXPECT_EQ(client_id, reopened_client_id); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/crash_report_database_win.cc ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crash_report_database.h" #include #include #include #include #include #include #include #include #include #include "base/check_op.h" #include "base/logging.h" #include "base/numerics/safe_math.h" #include "base/strings/utf_string_conversions.h" #include "client/settings.h" #include "util/file/directory_reader.h" #include "util/file/filesystem.h" #include "util/misc/implicit_cast.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/metrics.h" namespace crashpad { namespace { constexpr wchar_t kReportsDirectory[] = L"reports"; constexpr wchar_t kMetadataFileName[] = L"metadata"; constexpr wchar_t kSettings[] = L"settings.dat"; constexpr wchar_t kCrashReportFileExtension[] = L"dmp"; constexpr uint32_t kMetadataFileHeaderMagic = 'CPAD'; constexpr uint32_t kMetadataFileVersion = 1; using OperationStatus = CrashReportDatabase::OperationStatus; // Helpers --------------------------------------------------------------------- // Adds a string to the string table and returns the byte index where it was // added. uint32_t AddStringToTable(std::string* string_table, const std::string& str) { uint32_t offset = base::checked_cast(string_table->size()); *string_table += str; *string_table += '\0'; return offset; } // Converts |str| to UTF8, adds the result to the string table and returns the // byte index where it was added. uint32_t AddStringToTable(std::string* string_table, const std::wstring& str) { return AddStringToTable(string_table, base::WideToUTF8(str)); } // Reads from the current file position to EOF and returns as a string of bytes. std::string ReadRestOfFileAsString(FileHandle file) { FileOffset read_from = LoggingSeekFile(file, 0, SEEK_CUR); FileOffset end = LoggingSeekFile(file, 0, SEEK_END); FileOffset original = LoggingSeekFile(file, read_from, SEEK_SET); if (read_from == -1 || end == -1 || original == -1 || read_from == end) return std::string(); DCHECK_EQ(read_from, original); DCHECK_GT(end, read_from); size_t data_length = static_cast(end - read_from); std::string buffer(data_length, '\0'); return LoggingReadFileExactly(file, &buffer[0], data_length) ? buffer : std::string(); } bool UUIDFromReportPath(const base::FilePath& path, UUID* uuid) { return uuid->InitializeFromString( path.RemoveFinalExtension().BaseName().value()); } // Helper structures, and conversions ------------------------------------------ // The format of the on disk metadata file is a MetadataFileHeader, followed by // a number of fixed size records of MetadataFileReportRecord, followed by a // string table in UTF8 format, where each string is \0 terminated. struct MetadataFileHeader { uint32_t magic; uint32_t version; uint32_t num_records; uint32_t padding; }; struct ReportDisk; enum class ReportState { //! \brief Created and filled out by caller, owned by database. kPending, //! \brief In the process of uploading, owned by caller. kUploading, //! \brief Upload completed or skipped, owned by database. kCompleted, }; enum { //! \brief Corresponds to uploaded bit of the report state. kAttributeUploaded = 1 << 0, //! \brief Corresponds to upload_explicity_requested bit of the report state. kAttributeUploadExplicitlyRequested = 1 << 1, }; struct MetadataFileReportRecord { // Note that this default constructor does no initialization. It is used only // to create an array of records that are immediately initialized by reading // from disk in Metadata::Read(). MetadataFileReportRecord() {} // Constructs from a ReportDisk, adding to |string_table| and storing indices // as strings into that table. MetadataFileReportRecord(const ReportDisk& report, std::string* string_table); UUID uuid; // UUID is a 16 byte, standard layout structure. uint32_t file_path_index; // Index into string table. File name is relative // to the reports directory when on disk. uint32_t id_index; // Index into string table. int64_t creation_time; // Holds a time_t. int64_t last_upload_attempt_time; // Holds a time_t. int32_t upload_attempts; int32_t state; // A ReportState. uint8_t attributes; // Bitfield of kAttribute*. uint8_t padding[7]; }; //! \brief A private extension of the Report class that includes additional data //! that's stored on disk in the metadata file. struct ReportDisk : public CrashReportDatabase::Report { ReportDisk(const MetadataFileReportRecord& record, const base::FilePath& report_dir, const std::string& string_table); ReportDisk(const UUID& uuid, const base::FilePath& path, time_t creation_tim, ReportState state); //! \brief The current state of the report. ReportState state; }; MetadataFileReportRecord::MetadataFileReportRecord(const ReportDisk& report, std::string* string_table) : uuid(report.uuid), file_path_index( AddStringToTable(string_table, report.file_path.BaseName().value())), id_index(AddStringToTable(string_table, report.id)), creation_time(report.creation_time), last_upload_attempt_time(report.last_upload_attempt_time), upload_attempts(report.upload_attempts), state(static_cast(report.state)), attributes((report.uploaded ? kAttributeUploaded : 0) | (report.upload_explicitly_requested ? kAttributeUploadExplicitlyRequested : 0)) { memset(&padding, 0, sizeof(padding)); } ReportDisk::ReportDisk(const MetadataFileReportRecord& record, const base::FilePath& report_dir, const std::string& string_table) : Report() { uuid = record.uuid; file_path = report_dir.Append( base::UTF8ToWide(&string_table[record.file_path_index])); id = &string_table[record.id_index]; creation_time = record.creation_time; last_upload_attempt_time = record.last_upload_attempt_time; upload_attempts = record.upload_attempts; state = static_cast(record.state); uploaded = (record.attributes & kAttributeUploaded) != 0; upload_explicitly_requested = (record.attributes & kAttributeUploadExplicitlyRequested) != 0; } ReportDisk::ReportDisk(const UUID& uuid, const base::FilePath& path, time_t creation_time, ReportState state) : Report() { this->uuid = uuid; this->file_path = path; this->creation_time = creation_time; this->state = state; } // Metadata -------------------------------------------------------------------- //! \brief Manages the metadata for the set of reports, handling serialization //! to disk, and queries. class Metadata { public: Metadata(const Metadata&) = delete; Metadata& operator=(const Metadata&) = delete; //! \brief Writes any changes if necessary, unlocks and closes the file //! handle. ~Metadata(); static std::unique_ptr Create( const base::FilePath& metadata_file, const base::FilePath& report_dir, const base::FilePath& attachments_dir); //! \brief Adds a new report to the set. //! //! \param[in] new_report_disk The record to add. The #state field must be set //! to kPending. void AddNewRecord(const ReportDisk& new_report_disk); //! \brief Finds all reports in a given state. The \a reports vector is only //! valid when CrashReportDatabase::kNoError is returned. //! //! \param[in] desired_state The state to match. //! \param[out] reports Matching reports, must be empty on entry. OperationStatus FindReports( ReportState desired_state, std::vector* reports) const; //! \brief Finds the report matching the given UUID. //! //! The returned report is only valid if CrashReportDatabase::kNoError is //! returned. //! //! \param[in] uuid The report identifier. //! \param[out] report_disk The found report, valid only if //! CrashReportDatabase::kNoError is returned. Ownership is not //! transferred to the caller, and the report may not be modified. OperationStatus FindSingleReport(const UUID& uuid, const ReportDisk** report_disk) const; //! \brief Finds a single report matching the given UUID and in the desired //! state, and returns a mutable ReportDisk* if found. //! //! This marks the metadata as dirty, and on destruction, changes will be //! written to disk via Write(). //! //! \return #kNoError on success. #kReportNotFound if there was no report with //! the specified UUID, or if the report was not in the specified state //! and was not uploading. #kBusyError if the report was not in the //! specified state and was uploading. OperationStatus FindSingleReportAndMarkDirty(const UUID& uuid, ReportState desired_state, ReportDisk** report_disk); //! \brief Removes a report from the metadata database, without touching the //! on-disk file. //! //! The returned report is only valid if CrashReportDatabase::kNoError is //! returned. This will mark the database as dirty. Future metadata //! operations for this report will not succeed. //! //! \param[in] uuid The report identifier to remove. //! \param[out] report_path The found report's file_path, valid only if //! CrashReportDatabase::kNoError is returned. OperationStatus DeleteReport(const UUID& uuid, base::FilePath* report_path); //! \brief Removes reports from the metadata database, that don't have //! corresponding report files. //! //! \return number of metadata entries removed int CleanDatabase(); private: Metadata(FileHandle handle, const base::FilePath& report_dir, const base::FilePath& attachments_dir); bool Rewind(); void Read(); void Write(); //! \brief Confirms that the corresponding report actually exists on disk //! (that is, the dump file has not been removed), and that the report is //! in the given state. static OperationStatus VerifyReport(const ReportDisk& report_disk, ReportState desired_state); //! \brief Confirms that the corresponding report actually exists on disk //! (that is, the dump file has not been removed). static OperationStatus VerifyReportAnyState(const ReportDisk& report_disk); ScopedFileHandle handle_; const base::FilePath report_dir_; const base::FilePath attachments_dir_; bool dirty_; //! \brief `true` when a Write() is required on destruction. std::vector reports_; }; Metadata::~Metadata() { if (dirty_) Write(); // Not actually async, UnlockFileEx requires the Offset fields. OVERLAPPED overlapped = {0}; if (!UnlockFileEx(handle_.get(), 0, MAXDWORD, MAXDWORD, &overlapped)) PLOG(ERROR) << "UnlockFileEx"; } // static std::unique_ptr Metadata::Create( const base::FilePath& metadata_file, const base::FilePath& report_dir, const base::FilePath& attachments_dir) { // It is important that dwShareMode be non-zero so that concurrent access to // this file results in a successful open. This allows us to get to LockFileEx // which then blocks to guard access. FileHandle handle = CreateFile(metadata_file.value().c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (handle == kInvalidFileHandle) return std::unique_ptr(); // Not actually async, LockFileEx requires the Offset fields. OVERLAPPED overlapped = {0}; if (!LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &overlapped)) { PLOG(ERROR) << "LockFileEx"; return std::unique_ptr(); } std::unique_ptr metadata( new Metadata(handle, report_dir, attachments_dir)); // If Read() fails, for whatever reason (corruption, etc.) metadata will not // have been modified and will be in a clean empty state. We continue on and // return an empty database to hopefully recover. This means that existing // crash reports have been orphaned. metadata->Read(); return metadata; } void Metadata::AddNewRecord(const ReportDisk& new_report_disk) { DCHECK(new_report_disk.state == ReportState::kPending); reports_.push_back(new_report_disk); dirty_ = true; } OperationStatus Metadata::FindReports( ReportState desired_state, std::vector* reports) const { DCHECK(reports->empty()); for (const auto& report : reports_) { if (report.state == desired_state && VerifyReport(report, desired_state) == CrashReportDatabase::kNoError) { reports->push_back(report); } } return CrashReportDatabase::kNoError; } OperationStatus Metadata::FindSingleReport( const UUID& uuid, const ReportDisk** out_report) const { auto report_iter = std::find_if( reports_.begin(), reports_.end(), [uuid](const ReportDisk& report) { return report.uuid == uuid; }); if (report_iter == reports_.end()) return CrashReportDatabase::kReportNotFound; OperationStatus os = VerifyReportAnyState(*report_iter); if (os == CrashReportDatabase::kNoError) *out_report = &*report_iter; return os; } OperationStatus Metadata::FindSingleReportAndMarkDirty( const UUID& uuid, ReportState desired_state, ReportDisk** report_disk) { auto report_iter = std::find_if( reports_.begin(), reports_.end(), [uuid](const ReportDisk& report) { return report.uuid == uuid; }); if (report_iter == reports_.end()) return CrashReportDatabase::kReportNotFound; OperationStatus os = VerifyReport(*report_iter, desired_state); if (os == CrashReportDatabase::kNoError) { dirty_ = true; *report_disk = &*report_iter; } return os; } OperationStatus Metadata::DeleteReport(const UUID& uuid, base::FilePath* report_path) { auto report_iter = std::find_if( reports_.begin(), reports_.end(), [uuid](const ReportDisk& report) { return report.uuid == uuid; }); if (report_iter == reports_.end()) return CrashReportDatabase::kReportNotFound; *report_path = report_iter->file_path; reports_.erase(report_iter); dirty_ = true; return CrashReportDatabase::kNoError; } int Metadata::CleanDatabase() { int removed = 0; for (auto report_iter = reports_.begin(); report_iter != reports_.end();) { if (!IsRegularFile(report_iter->file_path)) { report_iter = reports_.erase(report_iter); ++removed; dirty_ = true; } else { ++report_iter; } } return removed; } Metadata::Metadata(FileHandle handle, const base::FilePath& report_dir, const base::FilePath& attachments_dir) : handle_(handle), report_dir_(report_dir), attachments_dir_(attachments_dir), dirty_(false), reports_() {} bool Metadata::Rewind() { FileOffset result = LoggingSeekFile(handle_.get(), 0, SEEK_SET); DCHECK_EQ(result, 0); return result == 0; } void Metadata::Read() { FileOffset length = LoggingSeekFile(handle_.get(), 0, SEEK_END); if (length <= 0) // Failed, or empty: Abort. return; if (!Rewind()) { LOG(ERROR) << "failed to rewind to read"; return; } MetadataFileHeader header; if (!LoggingReadFileExactly(handle_.get(), &header, sizeof(header))) { LOG(ERROR) << "failed to read header"; return; } if (header.magic != kMetadataFileHeaderMagic || header.version != kMetadataFileVersion) { LOG(ERROR) << "unexpected header"; return; } base::CheckedNumeric records_size = base::CheckedNumeric(header.num_records) * static_cast(sizeof(MetadataFileReportRecord)); if (!records_size.IsValid()) { LOG(ERROR) << "record size out of range"; return; } std::vector reports; if (header.num_records > 0) { std::vector records(header.num_records); if (!LoggingReadFileExactly( handle_.get(), &records[0], records_size.ValueOrDie())) { LOG(ERROR) << "failed to read records"; return; } std::string string_table = ReadRestOfFileAsString(handle_.get()); if (string_table.empty() || string_table.back() != '\0') { LOG(ERROR) << "bad string table"; return; } for (const auto& record : records) { if (record.file_path_index >= string_table.size() || record.id_index >= string_table.size()) { LOG(ERROR) << "invalid string table index"; return; } ReportDisk report_disk(record, report_dir_, string_table); report_disk.total_size = GetFileSize(report_disk.file_path); base::FilePath report_attachment_dir = attachments_dir_.Append(report_disk.uuid.ToWString()); report_disk.total_size += GetDirectorySize(report_attachment_dir); reports.push_back(report_disk); } } reports_.swap(reports); } void Metadata::Write() { if (!Rewind()) { LOG(ERROR) << "failed to rewind to write"; return; } // Truncate to ensure that a partial write doesn't cause a mix of old and new // data causing an incorrect interpretation on read. if (!SetEndOfFile(handle_.get())) { PLOG(ERROR) << "failed to truncate"; return; } size_t num_records = reports_.size(); // Fill and write out the header. MetadataFileHeader header = {0}; header.magic = kMetadataFileHeaderMagic; header.version = kMetadataFileVersion; header.num_records = base::checked_cast(num_records); if (!LoggingWriteFile(handle_.get(), &header, sizeof(header))) { LOG(ERROR) << "failed to write header"; return; } if (num_records == 0) return; // Build the records and string table we're going to write. std::string string_table; std::vector records; records.reserve(num_records); for (const auto& report : reports_) { const base::FilePath& path = report.file_path; if (path.DirName() != report_dir_) { LOG(ERROR) << path << " expected to start with " << report_dir_; return; } records.push_back(MetadataFileReportRecord(report, &string_table)); } if (!LoggingWriteFile(handle_.get(), &records[0], records.size() * sizeof(MetadataFileReportRecord))) { LOG(ERROR) << "failed to write records"; return; } if (!LoggingWriteFile( handle_.get(), string_table.c_str(), string_table.size())) { LOG(ERROR) << "failed to write string table"; return; } } // static OperationStatus Metadata::VerifyReportAnyState(const ReportDisk& report_disk) { DWORD fileattr = GetFileAttributes(report_disk.file_path.value().c_str()); if (fileattr == INVALID_FILE_ATTRIBUTES) return CrashReportDatabase::kReportNotFound; return (fileattr & FILE_ATTRIBUTE_DIRECTORY) ? CrashReportDatabase::kFileSystemError : CrashReportDatabase::kNoError; } // static OperationStatus Metadata::VerifyReport(const ReportDisk& report_disk, ReportState desired_state) { if (report_disk.state == desired_state) { return VerifyReportAnyState(report_disk); } return report_disk.state == ReportState::kUploading ? CrashReportDatabase::kBusyError : CrashReportDatabase::kReportNotFound; } bool EnsureDirectory(const base::FilePath& path) { DWORD fileattr = GetFileAttributes(path.value().c_str()); if (fileattr == INVALID_FILE_ATTRIBUTES) { PLOG(ERROR) << "GetFileAttributes " << path; return false; } if ((fileattr & FILE_ATTRIBUTE_DIRECTORY) == 0) { LOG(ERROR) << "GetFileAttributes " << path << ": not a directory"; return false; } return true; } //! \brief Ensures that the node at path is a directory, and creates it if it //! does not exist. //! //! \return If the path points to a file, rather than a directory, or the //! directory could not be created, returns `false`. Otherwise, returns //! `true`, indicating that path already was or now is a directory. bool CreateDirectoryIfNecessary(const base::FilePath& path) { if (CreateDirectory(path.value().c_str(), nullptr)) return true; if (GetLastError() != ERROR_ALREADY_EXISTS) { PLOG(ERROR) << "CreateDirectory " << base::WideToUTF8(path.value()); return false; } return EnsureDirectory(path); } } // namespace // CrashReportDatabaseWin ------------------------------------------------------ class CrashReportDatabaseWin : public CrashReportDatabase { public: explicit CrashReportDatabaseWin(const base::FilePath& path); CrashReportDatabaseWin(const CrashReportDatabaseWin&) = delete; CrashReportDatabaseWin& operator=(const CrashReportDatabaseWin&) = delete; ~CrashReportDatabaseWin() override; bool Initialize(bool may_create); // CrashReportDatabase: Settings* GetSettings() override; OperationStatus PrepareNewCrashReport( std::unique_ptr* report) override; OperationStatus FinishedWritingCrashReport(std::unique_ptr report, UUID* uuid) override; OperationStatus LookUpCrashReport(const UUID& uuid, Report* report) override; OperationStatus GetPendingReports(std::vector* reports) override; OperationStatus GetCompletedReports(std::vector* reports) override; OperationStatus GetReportForUploading( const UUID& uuid, std::unique_ptr* report, bool report_metrics) override; OperationStatus SkipReportUpload(const UUID& uuid, Metrics::CrashSkippedReason reason) override; OperationStatus DeleteReport(const UUID& uuid) override; OperationStatus RequestUpload(const UUID& uuid) override; int CleanDatabase(time_t lockfile_ttl) override; base::FilePath DatabasePath() override; private: // CrashReportDatabase: OperationStatus RecordUploadAttempt(UploadReport* report, bool successful, const std::string& id) override; // Cleans any attachments that have no associated report. void CleanOrphanedAttachments(); std::unique_ptr AcquireMetadata(); Settings& SettingsInternal() { std::call_once(settings_init_, [this]() { settings_.Initialize(); }); return settings_; } const base::FilePath base_dir_; Settings settings_; std::once_flag settings_init_; InitializationStateDcheck initialized_; }; CrashReportDatabaseWin::CrashReportDatabaseWin(const base::FilePath& path) : CrashReportDatabase(), base_dir_(path), settings_(path.Append(kSettings)), settings_init_(), initialized_() {} CrashReportDatabaseWin::~CrashReportDatabaseWin() { } bool CrashReportDatabaseWin::Initialize(bool may_create) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); // Ensure the database directory exists. if (may_create) { if (!CreateDirectoryIfNecessary(base_dir_)) return false; } else if (!EnsureDirectory(base_dir_)) { return false; } // Ensure that the report subdirectory exists. if (!CreateDirectoryIfNecessary(base_dir_.Append(kReportsDirectory))) return false; if (!CreateDirectoryIfNecessary(AttachmentsRootPath())) return false; INITIALIZATION_STATE_SET_VALID(initialized_); return true; } base::FilePath CrashReportDatabaseWin::DatabasePath() { return base_dir_; } Settings* CrashReportDatabaseWin::GetSettings() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return &SettingsInternal(); } OperationStatus CrashReportDatabaseWin::PrepareNewCrashReport( std::unique_ptr* report) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr new_report(new NewReport()); if (!new_report->Initialize(this, base_dir_.Append(kReportsDirectory), std::wstring(L".") + kCrashReportFileExtension)) { return kFileSystemError; } report->reset(new_report.release()); return kNoError; } OperationStatus CrashReportDatabaseWin::FinishedWritingCrashReport( std::unique_ptr report, UUID* uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr metadata(AcquireMetadata()); if (!metadata) return kDatabaseError; metadata->AddNewRecord(ReportDisk(report->ReportID(), report->file_remover_.get(), time(nullptr), ReportState::kPending)); std::ignore = report->file_remover_.release(); // Close all the attachments and disarm their removers too. for (auto& writer : report->attachment_writers_) { writer->Close(); } for (auto& remover : report->attachment_removers_) { std::ignore = remover.release(); } *uuid = report->ReportID(); Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated); Metrics::CrashReportSize(report->Writer()->Seek(0, SEEK_END)); return kNoError; } OperationStatus CrashReportDatabaseWin::LookUpCrashReport(const UUID& uuid, Report* report) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr metadata(AcquireMetadata()); if (!metadata) return kDatabaseError; // Find and return a copy of the matching report. const ReportDisk* report_disk; OperationStatus os = metadata->FindSingleReport(uuid, &report_disk); if (os == kNoError) *report = *report_disk; return os; } OperationStatus CrashReportDatabaseWin::GetPendingReports( std::vector* reports) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr metadata(AcquireMetadata()); return metadata ? metadata->FindReports(ReportState::kPending, reports) : kDatabaseError; } OperationStatus CrashReportDatabaseWin::GetCompletedReports( std::vector* reports) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr metadata(AcquireMetadata()); return metadata ? metadata->FindReports(ReportState::kCompleted, reports) : kDatabaseError; } OperationStatus CrashReportDatabaseWin::GetReportForUploading( const UUID& uuid, std::unique_ptr* report, bool report_metrics) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr metadata(AcquireMetadata()); if (!metadata) return kDatabaseError; ReportDisk* report_disk; OperationStatus os = metadata->FindSingleReportAndMarkDirty( uuid, ReportState::kPending, &report_disk); if (os == kNoError) { report_disk->state = ReportState::kUploading; auto upload_report = std::make_unique(); *implicit_cast(upload_report.get()) = *report_disk; if (!upload_report->Initialize(upload_report->file_path, this)) { return kFileSystemError; } upload_report->report_metrics_ = report_metrics; report->reset(upload_report.release()); } return os; } OperationStatus CrashReportDatabaseWin::RecordUploadAttempt( UploadReport* report, bool successful, const std::string& id) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (report->report_metrics_) { Metrics::CrashUploadAttempted(successful); } std::unique_ptr metadata(AcquireMetadata()); if (!metadata) return kDatabaseError; ReportDisk* report_disk; OperationStatus os = metadata->FindSingleReportAndMarkDirty( report->uuid, ReportState::kUploading, &report_disk); if (os != kNoError) return os; time_t now = time(nullptr); report_disk->uploaded = successful; report_disk->id = id; report_disk->last_upload_attempt_time = now; report_disk->upload_attempts++; if (successful) { report_disk->state = ReportState::kCompleted; report_disk->upload_explicitly_requested = false; } else { report_disk->state = ReportState::kPending; report_disk->upload_explicitly_requested = report->upload_explicitly_requested; } if (!SettingsInternal().SetLastUploadAttemptTime(now)) return kDatabaseError; return kNoError; } OperationStatus CrashReportDatabaseWin::DeleteReport(const UUID& uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr metadata(AcquireMetadata()); if (!metadata) return kDatabaseError; base::FilePath report_path; OperationStatus os = metadata->DeleteReport(uuid, &report_path); if (os != kNoError) return os; if (!DeleteFile(report_path.value().c_str())) { PLOG(ERROR) << "DeleteFile " << report_path; return kFileSystemError; } RemoveAttachmentsByUUID(uuid); return kNoError; } OperationStatus CrashReportDatabaseWin::SkipReportUpload( const UUID& uuid, Metrics::CrashSkippedReason reason) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); Metrics::CrashUploadSkipped(reason); std::unique_ptr metadata(AcquireMetadata()); if (!metadata) return kDatabaseError; ReportDisk* report_disk; OperationStatus os = metadata->FindSingleReportAndMarkDirty( uuid, ReportState::kPending, &report_disk); if (os == kNoError) { report_disk->state = ReportState::kCompleted; report_disk->upload_explicitly_requested = false; } return os; } std::unique_ptr CrashReportDatabaseWin::AcquireMetadata() { base::FilePath metadata_file = base_dir_.Append(kMetadataFileName); return Metadata::Create(metadata_file, base_dir_.Append(kReportsDirectory), AttachmentsRootPath()); } OperationStatus CrashReportDatabaseWin::RequestUpload(const UUID& uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr metadata(AcquireMetadata()); if (!metadata) return kDatabaseError; ReportDisk* report_disk; // TODO(gayane): Search for the report only once regardless of its state. OperationStatus os = metadata->FindSingleReportAndMarkDirty( uuid, ReportState::kCompleted, &report_disk); if (os == kReportNotFound) { os = metadata->FindSingleReportAndMarkDirty( uuid, ReportState::kPending, &report_disk); } if (os != kNoError) return os; // If the crash report has already been uploaded, don't request new upload. if (report_disk->uploaded) return kCannotRequestUpload; // Mark the crash report as having upload explicitly requested by the user, // and move it to the pending state. report_disk->upload_explicitly_requested = true; report_disk->state = ReportState::kPending; Metrics::CrashReportPending(Metrics::PendingReportReason::kUserInitiated); return kNoError; } int CrashReportDatabaseWin::CleanDatabase(time_t lockfile_ttl) { int removed = 0; const base::FilePath dir_path(base_dir_.Append(kReportsDirectory)); DirectoryReader reader; if (!reader.Open(dir_path)) { return removed; } base::FilePath filename; DirectoryReader::Result result; time_t now = time(nullptr); std::unique_ptr metadata(AcquireMetadata()); if (!metadata) { return removed; } // Remove old reports without metadata. while ((result = reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { timespec filetime; const base::FilePath report_path(dir_path.Append(filename)); if (!FileModificationTime(report_path, &filetime) || filetime.tv_sec > now - lockfile_ttl) { continue; } const ReportDisk* report_disk; UUID uuid; bool is_uuid = UUIDFromReportPath(report_path, &uuid); // ignore files whose base name is not uuid if (!is_uuid) { continue; } OperationStatus os = metadata->FindSingleReport(uuid, &report_disk); if (os == OperationStatus::kReportNotFound) { if (LoggingRemoveFile(report_path)) { ++removed; RemoveAttachmentsByUUID(uuid); } continue; } } // Remove any metadata records without report files. removed += metadata->CleanDatabase(); CleanOrphanedAttachments(); return removed; } void CrashReportDatabaseWin::CleanOrphanedAttachments() { base::FilePath root_attachments_dir = AttachmentsRootPath(); DirectoryReader reader; if (!reader.Open(root_attachments_dir)) { return; } base::FilePath filename; DirectoryReader::Result result; base::FilePath reports_dir = base_dir_.Append(kReportsDirectory); while ((result = reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { const base::FilePath path(root_attachments_dir.Append(filename)); if (IsDirectory(path, false)) { UUID uuid; if (!uuid.InitializeFromString(filename.value())) { LOG(ERROR) << "unexpected attachment dir name " << filename; continue; } // Remove attachments if corresponding report doesn't exist. base::FilePath report_path = reports_dir.Append( uuid.ToWString() + L"." + kCrashReportFileExtension); if (!IsRegularFile(report_path)) { RemoveAttachmentsByUUID(uuid); } } } } namespace { std::unique_ptr InitializeInternal( const base::FilePath& path, bool may_create) { std::unique_ptr database_win( new CrashReportDatabaseWin(path)); return database_win->Initialize(may_create) ? std::move(database_win) : std::unique_ptr(); } } // namespace // static std::unique_ptr CrashReportDatabase::Initialize( const base::FilePath& path) { return InitializeInternal(path, true); } // static std::unique_ptr CrashReportDatabase::InitializeWithoutCreating(const base::FilePath& path) { return InitializeInternal(path, false); } // static std::unique_ptr CrashReportDatabase::GetSettingsReaderForDatabasePath( const base::FilePath& path) { return std::make_unique(path.Append(kSettings)); } } // namespace crashpad ================================================ FILE: client/crashpad_client.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_CRASHPAD_CLIENT_H_ #define CRASHPAD_CLIENT_CRASHPAD_CLIENT_H_ #include #include #include #include #include #include #include "base/files/file_path.h" #include "build/build_config.h" #include "util/file/file_io.h" #if !BUILDFLAG(IS_FUCHSIA) #include "util/misc/capture_context.h" #endif // !BUILDFLAG(IS_FUCHSIA) #if BUILDFLAG(IS_APPLE) #include "base/apple/scoped_mach_port.h" #elif BUILDFLAG(IS_WIN) #include #include "util/win/scoped_handle.h" #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) #include #include #endif #if BUILDFLAG(IS_IOS) #include "client/upload_behavior_ios.h" #include "handler/user_stream_data_source.h" // nogncheck #endif namespace crashpad { //! \brief The primary interface for an application to have Crashpad monitor //! it for crashes. class CrashpadClient { public: CrashpadClient(); CrashpadClient(const CrashpadClient&) = delete; CrashpadClient& operator=(const CrashpadClient&) = delete; ~CrashpadClient(); //! \brief Starts a Crashpad handler process, performing any necessary //! handshake to configure it. //! //! This method directs crashes to the Crashpad handler. On macOS, this is //! applicable to this process and all subsequent child processes. On Windows, //! child processes must also register by using SetHandlerIPCPipe(). //! //! On macOS, this method starts a Crashpad handler and obtains a Mach send //! right corresponding to a receive right held by the handler process. The //! handler process runs an exception server on this port. This method sets //! the task’s exception port for `EXC_CRASH`, `EXC_RESOURCE`, and `EXC_GUARD` //! exceptions to the Mach send right obtained. The handler will be installed //! with behavior `EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES` and thread //! state flavor `MACHINE_THREAD_STATE`. Exception ports are inherited, so a //! Crashpad handler started here will remain the handler for any child //! processes created after StartHandler() is called. These child processes do //! not need to call StartHandler() or be aware of Crashpad in any way. The //! Crashpad handler will receive crashes from child processes that have //! inherited it as their exception handler even after the process that called //! StartHandler() exits. //! //! On Windows, if \a asynchronous_start is `true`, this function will not //! directly call `CreateProcess()`, making it suitable for use in a //! `DllMain()`. In that case, the handler is started from a background //! thread, deferring the handler's startup. Nevertheless, regardless of the //! value of \a asynchronous_start, after calling this method, the global //! unhandled exception filter is set up, and all crashes will be handled by //! Crashpad. Optionally, use WaitForHandlerStart() to join with the //! background thread and retrieve the status of handler startup. //! //! On Fuchsia, this method binds to the exception port of the current default //! job, and starts a Crashpad handler to monitor that port. //! //! On Linux, this method starts a Crashpad handler, connected to this process //! via an `AF_UNIX` socket pair and installs signal handlers to request crash //! dumps on the client's socket end. //! //! \param[in] handler The path to a Crashpad handler executable. //! \param[in] database The path to a Crashpad database. The handler will be //! started with this path as its `--database` argument. //! \param[in] metrics_dir The path to an already existing directory where //! metrics files can be stored. The handler will be started with this //! path as its `--metrics-dir` argument. //! \param[in] url The URL of an upload server. The handler will be started //! with this URL as its `--url` argument. //! \param[in] annotations Process annotations to set in each crash report. //! The handler will be started with an `--annotation` argument for each //! element in this map. //! \param[in] arguments Additional arguments to pass to the Crashpad handler. //! Arguments passed in other parameters and arguments required to perform //! the handshake are the responsibility of this method, and must not be //! specified in this parameter. //! \param[in] restartable If `true`, the handler will be restarted if it //! dies, if this behavior is supported. This option is not available on //! all platforms, and does not function on all OS versions. If it is //! not supported, it will be ignored. //! \param[out] asynchronous_start If `true`, the handler will be started from //! a background thread. Optionally, WaitForHandlerStart() can be used at //! a suitable time to retreive the result of background startup. This //! option is only used on Windows. //! \param[in] attachments Vector that stores file paths that should be //! captured with each report at the time of the crash. //! //! \return `true` on success, `false` on failure with a message logged. bool StartHandler(const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, bool restartable, bool asynchronous_start, const std::vector& attachments = {}); #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \ DOXYGEN //! \brief Retrieve the socket and process ID for the handler. //! //! `StartHandler()` must have successfully been called before calling this //! method. //! //! \param[out] sock The socket connected to the handler, if not `nullptr`. //! \param[out] pid The handler's process ID, if not `nullptr`. //! \return `true` on success. Otherwise `false` with a message logged. static bool GetHandlerSocket(int* sock, pid_t* pid); //! \brief Sets the socket to a presumably-running Crashpad handler process //! which was started with StartHandler(). //! //! This method installs a signal handler to request crash dumps on \a sock. //! //! \param[in] sock A socket connected to a Crashpad handler. //! \param[in] pid The process ID of the handler, used to set the handler as //! this process' ptracer. 0 indicates it is not necessary to set the //! handler as this process' ptracer. -1 indicates that the handler's //! process ID should be determined by communicating over the socket. bool SetHandlerSocket(ScopedFileHandle sock, pid_t pid); //! \brief Uses `sigaltstack()` to allocate a signal stack for the calling //! thread. //! //! This method allocates an alternate stack to handle signals delivered to //! the calling thread and should be called early in the lifetime of each //! thread. Installing an alternate stack allows signals to be delivered in //! the event that the call stack's stack pointer points to invalid memory, //! as in the case of stack overflow. //! //! This method is called automatically by SetHandlerSocket() and //! the various StartHandler() methods. It is harmless to call multiple times. //! A new signal stack will be allocated only if there is no existing stack or //! the existing stack is too small. The stack will be automatically freed //! when the thread exits. //! //! An application might choose to diligently call this method from the start //! routine for each thread, call it from a `pthread_create()` wrapper which //! the application provides, or link the provided "client:pthread_create" //! target. //! //! \return `true` on success. Otherwise `false` with a message logged. static bool InitializeSignalStackForThread(); #endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || // BUILDFLAG(IS_CHROMEOS) || DOXYGEN #if BUILDFLAG(IS_ANDROID) || DOXYGEN //! \brief Installs a signal handler to execute `/system/bin/app_process` and //! load a Java class in response to a crash. //! //! \param[in] class_name The fully qualified class name to load, which must //! define a `main()` method to be invoked by `app_process`. Arguments //! will be passed to this method as though it were the Crashpad handler. //! This class is expected to load a native library defining //! crashpad::HandlerMain() and pass the arguments to it. //! \param[in] env A vector of environment variables of the form `var=value` //! defining the environment in which to execute `app_process`. If this //! value is `nullptr`, the application's environment at the time of the //! crash will be used. //! \param[in] database The path to a Crashpad database. The handler will be //! started with this path as its `--database` argument. //! \param[in] metrics_dir The path to an already existing directory where //! metrics files can be stored. The handler will be started with this //! path as its `--metrics-dir` argument. //! \param[in] url The URL of an upload server. The handler will be started //! with this URL as its `--url` argument. //! \param[in] annotations Process annotations to set in each crash report. //! The handler will be started with an `--annotation` argument for each //! element in this map. //! \param[in] arguments Additional arguments to pass to the Crashpad handler. //! Arguments passed in other parameters and arguments required to perform //! the handshake are the responsibility of this method, and must not be //! specified in this parameter. //! //! \return `true` on success, `false` on failure with a message logged. bool StartJavaHandlerAtCrash( const std::string& class_name, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments); //! \brief Executes `/system/bin/app_process` and loads a Java class. //! //! \param[in] class_name The fully qualified class name to load, which must //! define a `main()` method to be invoked by `app_process`. Arguments //! will be passed to this method as though it were the Crashpad handler. //! This class is expected to load a native library defining //! crashpad::HandlerMain() and pass the arguments to it. //! \param[in] env A vector of environment variables of the form `var=value` //! defining the environment in which to execute `app_process`. If this //! value is `nullptr`, the application's current environment will be //! used. //! \param[in] database The path to a Crashpad database. The handler will be //! started with this path as its `--database` argument. //! \param[in] metrics_dir The path to an already existing directory where //! metrics files can be stored. The handler will be started with this //! path as its `--metrics-dir` argument. //! \param[in] url The URL of an upload server. The handler will be started //! with this URL as its `--url` argument. //! \param[in] annotations Process annotations to set in each crash report. //! The handler will be started with an `--annotation` argument for each //! element in this map. //! \param[in] arguments Additional arguments to pass to the Crashpad handler. //! Arguments passed in other parameters and arguments required to perform //! the handshake are the responsibility of this method, and must not be //! specified in this parameter. //! \param[in] socket The server end of a socket pair. The client end should //! be used with an ExceptionHandlerClient. //! //! \return `true` on success, `false` on failure with a message logged. static bool StartJavaHandlerForClient( const std::string& class_name, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket); //! \brief Installs a signal handler to start a Crashpad handler process by //! loading it with `/system/bin/linker`. //! //! This method is only supported by Android Q+. //! //! \param[in] handler_trampoline The path to a Crashpad handler trampoline //! executable, possibly located within an apk, e.g. //! "/data/app/myapk.apk!/myabi/libcrashpad_handler_trampoline.so". //! \param[in] handler_library The name of a library exporting the symbol //! `CrashpadHandlerMain()`. The path to this library must be present in //! `LD_LIBRARY_PATH`. //! \param[in] is_64_bit `true` if \a handler_trampoline and \a //! handler_library are 64-bit objects. They must have the same bitness. //! \param[in] env A vector of environment variables of the form `var=value` //! defining the environment in which to execute `app_process`. If this //! value is `nullptr`, the application's environment at the time of the //! crash will be used. //! \param[in] database The path to a Crashpad database. The handler will be //! started with this path as its `--database` argument. //! \param[in] metrics_dir The path to an already existing directory where //! metrics files can be stored. The handler will be started with this //! path as its `--metrics-dir` argument. //! \param[in] url The URL of an upload server. The handler will be started //! with this URL as its `--url` argument. //! \param[in] annotations Process annotations to set in each crash report. //! The handler will be started with an `--annotation` argument for each //! element in this map. //! \param[in] arguments Additional arguments to pass to the Crashpad handler. //! Arguments passed in other parameters and arguments required to perform //! the handshake are the responsibility of this method, and must not be //! specified in this parameter. //! //! \return `true` on success, `false` on failure with a message logged. bool StartHandlerWithLinkerAtCrash( const std::string& handler_trampoline, const std::string& handler_library, bool is_64_bit, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments); //! \brief Starts a Crashpad handler process with an initial client by loading //! it with `/system/bin/linker`. //! //! This method is only supported by Android Q+. //! //! \param[in] handler_trampoline The path to a Crashpad handler trampoline //! executable, possibly located within an apk, e.g. //! "/data/app/myapk.apk!/myabi/libcrashpad_handler_trampoline.so". //! \param[in] handler_library The name of a library exporting the symbol //! `CrashpadHandlerMain()`. The path to this library must be present in //! `LD_LIBRARY_PATH`. //! \param[in] is_64_bit `true` if \a handler_trampoline and \a //! handler_library are 64-bit objects. They must have the same bitness. //! \param[in] env A vector of environment variables of the form `var=value` //! defining the environment in which to execute `app_process`. If this //! value is `nullptr`, the application's current environment will be //! used. //! \param[in] database The path to a Crashpad database. The handler will be //! started with this path as its `--database` argument. //! \param[in] metrics_dir The path to an already existing directory where //! metrics files can be stored. The handler will be started with this //! path as its `--metrics-dir` argument. //! \param[in] url The URL of an upload server. The handler will be started //! with this URL as its `--url` argument. //! \param[in] annotations Process annotations to set in each crash report. //! The handler will be started with an `--annotation` argument for each //! element in this map. //! \param[in] arguments Additional arguments to pass to the Crashpad handler. //! Arguments passed in other parameters and arguments required to perform //! the handshake are the responsibility of this method, and must not be //! specified in this parameter. //! \param[in] socket The server end of a socket pair. The client end should //! be used with an ExceptionHandlerClient. //! //! \return `true` on success, `false` on failure with a message logged. static bool StartHandlerWithLinkerForClient( const std::string& handler_trampoline, const std::string& handler_library, bool is_64_bit, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket); #endif // BUILDFLAG(IS_ANDROID) || DOXYGEN #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS) || \ DOXYGEN //! \brief Installs a signal handler to launch a handler process in reponse to //! a crash. //! //! The handler process will create a crash dump for this process and exit. //! //! \param[in] handler The path to a Crashpad handler executable. //! \param[in] database The path to a Crashpad database. The handler will be //! started with this path as its `--database` argument. //! \param[in] metrics_dir The path to an already existing directory where //! metrics files can be stored. The handler will be started with this //! path as its `--metrics-dir` argument. //! \param[in] url The URL of an upload server. The handler will be started //! with this URL as its `--url` argument. //! \param[in] annotations Process annotations to set in each crash report. //! The handler will be started with an `--annotation` argument for each //! element in this map. //! \param[in] arguments Additional arguments to pass to the Crashpad handler. //! Arguments passed in other parameters and arguments required to perform //! the handshake are the responsibility of this method, and must not be //! specified in this parameter. //! \param[in] attachments Attachment paths to pass to the Crashpad handler. //! The handler will be started with an `--attachment` argument for each //! path in this vector. //! //! \return `true` on success, `false` on failure with a message logged. bool StartHandlerAtCrash( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, const std::vector& attachments = {}); //! \brief Starts a handler process with an initial client. //! //! This method allows a process to launch the handler process on behalf of //! another process. //! //! \param[in] handler The path to a Crashpad handler executable. //! \param[in] database The path to a Crashpad database. The handler will be //! started with this path as its `--database` argument. //! \param[in] metrics_dir The path to an already existing directory where //! metrics files can be stored. The handler will be started with this //! path as its `--metrics-dir` argument. //! \param[in] url The URL of an upload server. The handler will be started //! with this URL as its `--url` argument. //! \param[in] annotations Process annotations to set in each crash report. //! The handler will be started with an `--annotation` argument for each //! element in this map. //! \param[in] arguments Additional arguments to pass to the Crashpad handler. //! Arguments passed in other parameters and arguments required to perform //! the handshake are the responsibility of this method, and must not be //! specified in this parameter. //! \param[in] socket The server end of a socket pair. The client end should //! be used with an ExceptionHandlerClient. //! //! \return `true` on success, `false` on failure with a message logged. static bool StartHandlerForClient( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket); //! \brief Requests that the handler capture a dump even though there hasn't //! been a crash. //! //! A handler must have already been installed before calling this method. //! //! TODO(jperaza): Floating point information in the context is zeroed out //! until CaptureContext() supports collecting that information. //! //! \param[in] context A NativeCPUContext, generally captured by //! CaptureContext() or similar. static void DumpWithoutCrash(NativeCPUContext* context); //! \brief Disables any installed crash handler, not including any //! FirstChanceHandler and crashes the current process. //! //! \param[in] message A message to be logged before crashing. [[noreturn]] static void CrashWithoutDump(const std::string& message); //! \brief The type for custom handlers installed by clients. using FirstChanceHandler = bool (*)(int, siginfo_t*, ucontext_t*); //! \brief Installs a custom crash signal handler which runs before the //! currently installed Crashpad handler. //! //! Handling signals appropriately can be tricky and use of this method //! should be avoided, if possible. //! //! A handler must have already been installed before calling this method. //! //! The custom handler runs in a signal handler context and must be safe for //! that purpose. //! //! If the custom handler returns `true`, the signal is considered handled and //! the signal handler returns. Otherwise, the currently installed Crashpad //! signal handler is run. //! //! \param[in] handler The custom crash signal handler to install. static void SetFirstChanceExceptionHandler(FirstChanceHandler handler); //! \brief Installs a custom crash signal handler which runs after the //! currently installed Crashpad handler. //! //! Handling signals appropriately can be tricky and use of this method //! should be avoided, if possible. //! //! A handler must have already been installed before calling this method. //! //! The custom handler runs in a signal handler context and must be safe for //! that purpose. //! //! If the custom handler returns `true`, the signal is not reraised. //! //! \param[in] handler The custom crash signal handler to install. static void SetLastChanceExceptionHandler(bool (*handler)(int, siginfo_t*, ucontext_t*)); //! \brief Configures a set of signals that shouldn't have Crashpad signal //! handlers installed. //! //! This method should be called before calling StartHandler(), //! SetHandlerSocket(), or other methods that install Crashpad signal //! handlers. //! //! \param[in] unhandled_signals The set of unhandled signals void SetUnhandledSignals(const std::set& unhandled_signals); #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) || // BUILDFLAG(IS_CHROMEOS) || DOXYGEN #if BUILDFLAG(IS_IOS) || DOXYGEN //! \brief Observation callback invoked each time this object finishes //! processing and attempting to upload on-disk crash reports (whether or //! not the uploads succeeded). //! //! This callback is copied into this object. Any references or pointers //! inside must outlive this object. //! //! The callback might be invoked on a background thread, so clients must //! synchronize appropriately. using ProcessPendingReportsObservationCallback = std::function; //! \brief Configures the process to direct its crashes to the iOS in-process //! Crashpad handler. //! //! This method is only defined on iOS. //! //! \param[in] database The path to a Crashpad database. //! \param[in] url The URL of an upload server. //! \param[in] annotations Process annotations to set in each crash report. //! \param[in] callback Optional callback invoked zero or more times //! on a background thread each time the handler finishes //! processing and attempting to upload on-disk crash reports. //! If this callback is empty, it is not invoked. //! \return `true` on success, `false` on failure with a message logged. static bool StartCrashpadInProcessHandler( const base::FilePath& database, const std::string& url, const std::map& annotations, ProcessPendingReportsObservationCallback callback); //! \brief Requests that the handler convert intermediate dumps into //! minidumps and trigger an upload if possible. //! //! A handler must have already been installed before calling this method. //! This method should be called when an application is ready to start //! processing previously created intermediate dumps. Processing will block, //! so this should not be called on the main UI thread. No intermediate dumps //! will be processed until this method is called. //! //! \param[in] annotations Process annotations to set in each crash report. //! Useful when adding crash annotations detected on the next run after a //! crash but before upload. //! \param[in] user_stream_sources An optional vector containing the //! extensibility data sources to call on crash. Each time a minidump is //! created, the sources are called in turn. Any streams returned are //! added to the minidump. static void ProcessIntermediateDumps( const std::map& annotations = {}, const UserStreamDataSources* user_stream_sources = nullptr); //! \brief Requests that the handler convert a single intermediate dump at \a //! file generated by DumpWithoutCrashAndDeferProcessingAtPath into a //! minidump and trigger an upload if possible. //! //! A handler must have already been installed before calling this method. //! This method should be called when an application is ready to start //! processing previously created intermediate dumps. Processing will block, //! so this should not be called on the main UI thread. //! //! \param[in] file The intermediate dump to process. //! \param[in] annotations Process annotations to set in each crash report. //! Useful when adding crash annotations detected on the next run after a //! crash but before upload. static void ProcessIntermediateDump( const base::FilePath& file, const std::map& annotations = {}); //! \brief Requests that the handler begin in-process uploading of any //! pending reports. //! //! Once called the handler will start looking for pending reports to upload //! on another thread. This method does not block. //! //! A handler must have already been installed before calling this method. //! //! \param[in] upload_behavior Controls when the upload thread will run and //! process pending reports. By default, only uploads pending reports //! when the application is active. static void StartProcessingPendingReports( UploadBehavior upload_behavior = UploadBehavior::kUploadWhenAppIsActive); //! \brief Requests that the handler capture an intermediate dump even though //! there hasn't been a crash. The intermediate dump will be converted //! to a mindump immediately. If StartProcessingPendingReports() has been //! called, this will also trigger an upload. //! //! For internal use only. Clients should use CRASHPAD_SIMULATE_CRASH(). //! //! A handler must have already been installed before calling this method. //! //! \param[in] context A NativeCPUContext, generally captured by //! CaptureContext() or similar. static void DumpWithoutCrash(NativeCPUContext* context); //! \brief Requests that the handler capture an intermediate dump even though //! there hasn't been a crash. The intermediate dump will not be converted //! to a mindump until ProcessIntermediateDumps() is called. //! //! For internal use only. Clients should use //! CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING(). //! //! A handler must have already been installed before calling this method. //! //! \param[in] context A NativeCPUContext, generally captured by //! CaptureContext() or similar. static void DumpWithoutCrashAndDeferProcessing(NativeCPUContext* context); //! \brief Requests that the handler capture an intermediate dump and store it //! in path, even though there hasn't been a crash. The intermediate dump //! will not be converted to a mindump until ProcessIntermediateDump() is //! called. //! //! For internal use only. Clients should use //! CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING_AT_PATH(). //! //! A handler must have already been installed before calling this method. //! //! \param[in] context A NativeCPUContext, generally captured by //! CaptureContext() or similar. //! \param[in] path The path for writing the intermediate dump. static void DumpWithoutCrashAndDeferProcessingAtPath( NativeCPUContext* context, const base::FilePath path); //! \brief Unregister the Crashpad client. Intended to be used by tests so //! multiple Crashpad clients can be started and stopped. Not expected to //! be used in a shipping application. static void ResetForTesting(); //! \brief Inject a callback into the Mach exception and signal handling //! mechanisms. Intended to be used by tests to trigger a reentrant // exception. static void SetExceptionCallbackForTesting(void (*callback)()); //! \brief Returns the thread id of the Mach exception thread, used by tests. static uint64_t GetThreadIdForTesting(); #endif #if BUILDFLAG(IS_APPLE) || DOXYGEN //! \brief Sets the process’ crash handler to a Mach service registered with //! the bootstrap server. //! //! This method is only defined on macOS. //! //! See StartHandler() for more detail on how the port and handler are //! configured. //! //! \param[in] service_name The service name of a Crashpad exception handler //! service previously registered with the bootstrap server. //! //! \return `true` on success, `false` on failure with a message logged. bool SetHandlerMachService(const std::string& service_name); //! \brief Sets the process’ crash handler to a Mach port. //! //! This method is only defined on macOS. //! //! See StartHandler() for more detail on how the port and handler are //! configured. //! //! \param[in] exception_port An `exception_port_t` corresponding to a //! Crashpad exception handler service. //! //! \return `true` on success, `false` on failure with a message logged. bool SetHandlerMachPort(base::apple::ScopedMachSendRight exception_port); //! \brief Retrieves a send right to the process’ crash handler Mach port. //! //! This method is only defined on macOS. //! //! This method can be used to obtain the crash handler Mach port when a //! Crashpad client process wishes to provide a send right to this port to //! another process. The IPC mechanism used to convey the right is under the //! application’s control. If the other process wishes to become a client of //! the same crash handler, it can provide the transferred right to //! SetHandlerMachPort(). //! //! See StartHandler() for more detail on how the port and handler are //! configured. //! //! \return The Mach port set by SetHandlerMachPort(), possibly indirectly by //! a call to another method such as StartHandler() or //! SetHandlerMachService(). This method must only be called after a //! successful call to one of those methods. `MACH_PORT_NULL` on failure //! with a message logged. base::apple::ScopedMachSendRight GetHandlerMachPort() const; #endif #if BUILDFLAG(IS_WIN) || DOXYGEN //! \brief Sets the IPC pipe of a presumably-running Crashpad handler process //! which was started with StartHandler() or by other compatible means //! and does an IPC message exchange to register this process with the //! handler. Crashes will be serviced once this method returns. //! //! This method is only defined on Windows. //! //! This method sets the unhandled exception handler to a local //! function that when reached will "signal and wait" for the crash handler //! process to create the dump. //! //! \param[in] ipc_pipe The full name of the crash handler IPC pipe. This is //! a string of the form `"\\.\pipe\NAME"`. //! //! \return `true` on success and `false` on failure. bool SetHandlerIPCPipe(const std::wstring& ipc_pipe); //! \brief Retrieves the IPC pipe name used to register with the Crashpad //! handler. //! //! This method is only defined on Windows. //! //! This method retrieves the IPC pipe name set by SetHandlerIPCPipe(), or a //! suitable IPC pipe name chosen by StartHandler(). It must only be called //! after a successful call to one of those methods. It is intended to be used //! to obtain the IPC pipe name so that it may be passed to other processes, //! so that they may register with an existing Crashpad handler by calling //! SetHandlerIPCPipe(). //! //! \return The full name of the crash handler IPC pipe, a string of the form //! `"\\.\pipe\NAME"`. std::wstring GetHandlerIPCPipe() const; //! \brief When `asynchronous_start` is used with StartHandler(), this method //! can be used to block until the handler launch has been completed to //! retrieve status information. //! //! This method should not be used unless `asynchronous_start` was `true`. //! //! \param[in] timeout_ms The number of milliseconds to wait for a result from //! the background launch, or `0xffffffff` to block indefinitely. //! //! \return `true` if the hander startup succeeded, `false` otherwise, and an //! error message will have been logged. bool WaitForHandlerStart(unsigned int timeout_ms); //! \brief Register a DLL using WerRegisterExceptionModule(). //! //! This method should only be called after a successful call to //! SetHandlerIPCPipe() or StartHandler(). The registration is valid for the //! lifetime of this object. //! //! \param[in] full_path The full path to the DLL that will be registered. //! The DLL path should also be set in an appropriate //! `Windows Error Reporting` registry key. //! //! \return `true` if the DLL was registered. Note: Windows just stashes the //! path somewhere so this returns `true` even if the DLL is not yet //! set in an appropriate registry key, or does not exist. bool RegisterWerModule(const std::wstring& full_path); //! \brief Requests that the handler capture a dump even though there hasn't //! been a crash. //! //! \param[in] context A `CONTEXT`, generally captured by CaptureContext() or //! similar. static void DumpWithoutCrash(const CONTEXT& context); //! \brief Requests that the handler capture a dump using the given \a //! exception_pointers to get the `EXCEPTION_RECORD` and `CONTEXT`. //! //! This function is not necessary in general usage as an unhandled exception //! filter is installed by StartHandler() or SetHandlerIPCPipe(). //! //! \param[in] exception_pointers An `EXCEPTION_POINTERS`, as would generally //! passed to an unhandled exception filter. static void DumpAndCrash(EXCEPTION_POINTERS* exception_pointers); //! \brief Requests that the handler capture a dump of a different process. //! //! The target process must be an already-registered Crashpad client. An //! exception will be triggered in the target process, and the regular dump //! mechanism used. This function will block until the exception in the target //! process has been handled by the Crashpad handler. //! //! This function is unavailable when running on Windows XP and will return //! `false`. //! //! \param[in] process A `HANDLE` identifying the process to be dumped. //! \param[in] blame_thread If non-null, a `HANDLE` valid in the caller's //! process, referring to a thread in the target process. If this is //! supplied, instead of the exception referring to the location where the //! exception was injected, an exception record will be fabricated that //! refers to the current location of the given thread. //! \param[in] exception_code If \a blame_thread is non-null, this will be //! used as the exception code in the exception record. //! //! \return `true` if the exception was triggered successfully. static bool DumpAndCrashTargetProcess(HANDLE process, HANDLE blame_thread, DWORD exception_code); #endif #if BUILDFLAG(IS_APPLE) || DOXYGEN //! \brief Configures the process to direct its crashes to the default handler //! for the operating system. //! //! On macOS, this sets the task’s exception port as in SetHandlerMachPort(), //! but the exception handler used is obtained from //! SystemCrashReporterHandler(). If the system’s crash reporter handler //! cannot be determined or set, the task’s exception ports for crash-type //! exceptions are cleared. //! //! Use of this function is strongly discouraged. //! //! \warning After a call to this function, Crashpad will no longer monitor //! the process for crashes until a subsequent call to //! SetHandlerMachPort(). //! //! \note This is provided as a static function to allow it to be used in //! situations where a CrashpadClient object is not otherwise available. //! This may be useful when a child process inherits its parent’s Crashpad //! handler, but wants to sever this tie. static void UseSystemDefaultHandler(); #endif #if BUILDFLAG(IS_CHROMEOS) //! \brief Sets a timestamp on the signal handler to be passed on to //! crashpad_handler and then eventually Chrome OS's crash_reporter. //! //! \note This method is used by clients that use `StartHandler()` to start //! a handler and not by clients that use any other handler starting //! methods. static void SetCrashLoopBefore(uint64_t crash_loop_before_time); #endif private: #if BUILDFLAG(IS_WIN) || DOXYGEN //! \brief Registers process handlers for the client. void RegisterHandlers(); #endif #if BUILDFLAG(IS_APPLE) base::apple::ScopedMachSendRight exception_port_; #elif BUILDFLAG(IS_WIN) std::wstring ipc_pipe_; ScopedKernelHANDLE handler_start_thread_; ScopedVectoredExceptionRegistration vectored_handler_; #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) std::set unhandled_signals_; #endif // BUILDFLAG(IS_APPLE) }; } // namespace crashpad #endif // CRASHPAD_CLIENT_CRASHPAD_CLIENT_H_ ================================================ FILE: client/crashpad_client_fuchsia.cc ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crashpad_client.h" #include #include #include #include #include #include "base/check_op.h" #include "base/fuchsia/fuchsia_logging.h" #include "base/logging.h" #include "client/client_argv_handling.h" namespace crashpad { CrashpadClient::CrashpadClient() {} CrashpadClient::~CrashpadClient() {} bool CrashpadClient::StartHandler( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, bool restartable, bool asynchronous_start, const std::vector& attachments) { DCHECK(attachments.empty()); // Attachments are not implemented on Fuchsia yet. DCHECK_EQ(restartable, false); // Not used on Fuchsia. DCHECK_EQ(asynchronous_start, false); // Not used on Fuchsia. std::vector argv_strings = BuildHandlerArgvStrings( handler, database, metrics_dir, url, annotations, arguments); std::vector argv; StringVectorToCStringVector(argv_strings, &argv); // Set up handles to send to the spawned process: // 0. PA_USER0 job // 1. PA_USER0 exception channel // // Currently it is assumed that this process's default job handle is the // exception channel that should be monitored. In the future, it might be // useful for this to be configurable by the client. zx::job job; zx_status_t status = zx::job::default_job()->duplicate(ZX_RIGHT_SAME_RIGHTS, &job); if (status != ZX_OK) { ZX_LOG(ERROR, status) << "zx_handle_duplicate"; return false; } zx::channel exception_channel; status = job.create_exception_channel(0, &exception_channel); if (status != ZX_OK) { ZX_LOG(ERROR, status) << "zx_task_create_exception_channel"; return false; } constexpr size_t kActionCount = 2; fdio_spawn_action_t actions[] = { {.action = FDIO_SPAWN_ACTION_ADD_HANDLE, .h = {.id = PA_HND(PA_USER0, 0), .handle = job.release()}}, {.action = FDIO_SPAWN_ACTION_ADD_HANDLE, .h = {.id = PA_HND(PA_USER0, 1), .handle = exception_channel.release()}}, }; char error_message[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; zx::process child; // TODO(scottmg): https://crashpad.chromium.org/bug/196, FDIO_SPAWN_CLONE_ALL // is useful during bringup, but should probably be made minimal for real // usage. status = fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, argv[0], argv.data(), nullptr, kActionCount, actions, child.reset_and_get_address(), error_message); if (status != ZX_OK) { ZX_LOG(ERROR, status) << "fdio_spawn_etc: " << error_message; return false; } return true; } } // namespace crashpad ================================================ FILE: client/crashpad_client_ios.cc ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crashpad_client.h" #include "build/buildflag.h" #include "client/ios_handler/in_process_handler.h" #if !BUILDFLAG(IS_IOS_TVOS) #include "client/crash_handler_ios.h" #else #include "client/crash_handler_tvos.h" #endif namespace crashpad { CrashpadClient::CrashpadClient() {} CrashpadClient::~CrashpadClient() {} // static bool CrashpadClient::StartCrashpadInProcessHandler( const base::FilePath& database, const std::string& url, const std::map& annotations, ProcessPendingReportsObservationCallback callback) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); return crash_handler->Initialize(database, url, annotations, callback); } // static void CrashpadClient::ProcessIntermediateDumps( const std::map& annotations, const UserStreamDataSources* user_stream_sources) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->ProcessIntermediateDumps(annotations, user_stream_sources); } // static void CrashpadClient::ProcessIntermediateDump( const base::FilePath& file, const std::map& annotations) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->ProcessIntermediateDump(file, annotations); } // static void CrashpadClient::StartProcessingPendingReports( UploadBehavior upload_behavior) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->StartProcessingPendingReports(upload_behavior); } // static void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->DumpWithoutCrash(context, /*process_dump=*/true); } // static void CrashpadClient::DumpWithoutCrashAndDeferProcessing( NativeCPUContext* context) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->DumpWithoutCrash(context, /*process_dump=*/false); } // static void CrashpadClient::DumpWithoutCrashAndDeferProcessingAtPath( NativeCPUContext* context, const base::FilePath path) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->DumpWithoutCrashAtPath(context, path); } void CrashpadClient::ResetForTesting() { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->ResetForTesting(); } void CrashpadClient::SetExceptionCallbackForTesting(void (*callback)()) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->SetExceptionCallbackForTesting(callback); } uint64_t CrashpadClient::GetThreadIdForTesting() { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); return crash_handler->GetThreadIdForTesting(); } } // namespace crashpad ================================================ FILE: client/crashpad_client_ios_test.mm ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crashpad_client.h" #import #include #include #include "base/strings/sys_string_conversions.h" #include "client/crash_report_database.h" #include "client/ios_handler/exception_processor.h" #include "client/simulate_crash.h" #include "gtest/gtest.h" #include "test/scoped_temp_dir.h" #include "testing/platform_test.h" #include "util/thread/thread.h" namespace crashpad { namespace test { namespace { class CrashpadIOSClient : public PlatformTest { protected: // testing::Test: void SetUp() override { ASSERT_TRUE(client_.StartCrashpadInProcessHandler( base::FilePath(database_dir.path()), "", {}, CrashpadClient::ProcessPendingReportsObservationCallback())); database_ = CrashReportDatabase::Initialize(database_dir.path()); } void TearDown() override { client_.ResetForTesting(); } auto& Client() { return client_; } auto& Database() { return database_; } private: std::unique_ptr database_; CrashpadClient client_; ScopedTempDir database_dir; }; TEST_F(CrashpadIOSClient, DumpWithoutCrash) { std::vector reports; EXPECT_EQ(Database()->GetPendingReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 0u); CRASHPAD_SIMULATE_CRASH(); EXPECT_EQ(Database()->GetPendingReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 1u); } TEST_F(CrashpadIOSClient, DumpWithoutCrashAndDefer) { std::vector reports; CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING(); EXPECT_EQ(Database()->GetPendingReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 0u); Client().ProcessIntermediateDumps(); EXPECT_EQ(Database()->GetPendingReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 1u); } TEST_F(CrashpadIOSClient, DumpWithoutCrashAndDeferAtPath) { std::vector reports; ScopedTempDir crash_dir; UUID uuid; uuid.InitializeWithNew(); CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING_AT_PATH( crash_dir.path().Append(uuid.ToString())); EXPECT_EQ(Database()->GetPendingReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 0u); NSError* error = nil; NSArray* paths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:base::SysUTF8ToNSString( crash_dir.path().value()) error:&error]; ASSERT_EQ([paths count], 1u); Client().ProcessIntermediateDump( crash_dir.path().Append([paths[0] fileSystemRepresentation])); reports.clear(); EXPECT_EQ(Database()->GetPendingReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 1u); } class RaceThread : public Thread { public: explicit RaceThread() : Thread() {} private: void ThreadMain() override { for (int i = 0; i < 10; ++i) { CRASHPAD_SIMULATE_CRASH(); } } }; TEST_F(CrashpadIOSClient, MultipleThreadsSimulateCrash) { RaceThread race_threads[2]; for (RaceThread& race_thread : race_threads) { race_thread.Start(); } for (int i = 0; i < 10; ++i) { CRASHPAD_SIMULATE_CRASH(); } for (RaceThread& race_thread : race_threads) { race_thread.Join(); } std::vector reports; ASSERT_EQ(Database()->GetPendingReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 30u); } // This test is covered by a similar XCUITest, but for development purposes it's // sometimes easier and faster to run in Google Test. However, there's no way // to correctly run this in Google Test. Leave the test here, disabled, for use // during development only. TEST_F(CrashpadIOSClient, DISABLED_ThrowNSException) { [NSException raise:@"GoogleTestNSException" format:@"ThrowException"]; } // This test is covered by a similar XCUITest, but for development purposes it's // sometimes easier and faster to run in Google Test. However, there's no way // to correctly run this in Google Test. Leave the test here, disabled, for use // during development only. TEST_F(CrashpadIOSClient, DISABLED_ThrowException) { std::vector empty_vector; std::ignore = empty_vector.at(42); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/crashpad_client_linux.cc ================================================ // Copyright 2018 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crashpad_client.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base/check_op.h" #include "base/logging.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "client/client_argv_handling.h" #include "third_party/lss/lss.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" #include "util/linux/exception_handler_client.h" #include "util/linux/exception_information.h" #include "util/linux/scoped_pr_set_dumpable.h" #include "util/linux/scoped_pr_set_ptracer.h" #include "util/linux/socket.h" #include "util/misc/address_sanitizer.h" #include "util/misc/from_pointer_cast.h" #include "util/posix/scoped_mmap.h" #include "util/posix/signals.h" #include "util/posix/spawn_subprocess.h" namespace crashpad { namespace { std::string FormatArgumentInt(const std::string& name, int value) { return base::StringPrintf("--%s=%d", name.c_str(), value); } std::string FormatArgumentAddress(const std::string& name, const void* addr) { return base::StringPrintf("--%s=%p", name.c_str(), addr); } #if BUILDFLAG(IS_ANDROID) std::vector BuildAppProcessArgs( const std::string& class_name, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket) { #if defined(ARCH_CPU_64_BITS) static constexpr char kAppProcess[] = "/system/bin/app_process64"; #else static constexpr char kAppProcess[] = "/system/bin/app_process32"; #endif std::vector argv; argv.push_back(kAppProcess); argv.push_back("/system/bin"); argv.push_back("--application"); argv.push_back(class_name); std::vector handler_argv = BuildHandlerArgvStrings(base::FilePath(kAppProcess), database, metrics_dir, url, annotations, arguments); if (socket != kInvalidFileHandle) { handler_argv.push_back(FormatArgumentInt("initial-client-fd", socket)); } argv.insert(argv.end(), handler_argv.begin(), handler_argv.end()); return argv; } std::vector BuildArgsToLaunchWithLinker( const std::string& handler_trampoline, const std::string& handler_library, bool is_64_bit, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket) { std::vector argv; if (is_64_bit) { argv.push_back("/system/bin/linker64"); } else { argv.push_back("/system/bin/linker"); } argv.push_back(handler_trampoline); argv.push_back(handler_library); std::vector handler_argv = BuildHandlerArgvStrings( base::FilePath(), database, metrics_dir, url, annotations, arguments); if (socket != kInvalidFileHandle) { handler_argv.push_back(FormatArgumentInt("initial-client-fd", socket)); } argv.insert(argv.end(), handler_argv.begin() + 1, handler_argv.end()); return argv; } #endif // BUILDFLAG(IS_ANDROID) using LastChanceHandler = bool (*)(int, siginfo_t*, ucontext_t*); // A base class for Crashpad signal handler implementations. class SignalHandler { public: SignalHandler(const SignalHandler&) = delete; SignalHandler& operator=(const SignalHandler&) = delete; // Returns the currently installed signal hander. May be `nullptr` if no // handler has been installed. static SignalHandler* Get() { return handler_; } // Disables any installed Crashpad signal handler. If a crash signal is // received, any previously installed (non-Crashpad) signal handler will be // restored and the signal reraised. static void Disable() { if (!handler_->disabled_.test_and_set()) { handler_->WakeThreads(); } } void SetFirstChanceHandler(CrashpadClient::FirstChanceHandler handler) { first_chance_handler_ = handler; } void SetLastChanceExceptionHandler(LastChanceHandler handler) { last_chance_handler_ = handler; } // The base implementation for all signal handlers, suitable for calling // directly to simulate signal delivery. void HandleCrash(int signo, siginfo_t* siginfo, void* context) { exception_information_.siginfo_address = FromPointerCast( siginfo); exception_information_.context_address = FromPointerCast( context); exception_information_.thread_id = sys_gettid(); ScopedPrSetDumpable set_dumpable(false); HandleCrashImpl(); } protected: SignalHandler() = default; ~SignalHandler() = default; bool Install(const std::set* unhandled_signals) { bool signal_stack_initialized = CrashpadClient::InitializeSignalStackForThread(); DCHECK(signal_stack_initialized); DCHECK(!handler_); handler_ = this; return Signals::InstallCrashHandlers(HandleOrReraiseSignal, SA_ONSTACK | SA_EXPOSE_TAGBITS, &old_actions_, unhandled_signals); } const ExceptionInformation& GetExceptionInfo() { return exception_information_; } virtual void HandleCrashImpl() = 0; private: static constexpr int32_t kDumpNotDone = 0; static constexpr int32_t kDumpDone = 1; // The signal handler installed at OS-level. static void HandleOrReraiseSignal(int signo, siginfo_t* siginfo, void* context) { if (handler_->first_chance_handler_ && handler_->first_chance_handler_( signo, siginfo, static_cast(context))) { return; } // Only handle the first fatal signal observed. If another thread receives a // crash signal, it waits for the first dump to complete instead of // requesting another. if (!handler_->disabled_.test_and_set()) { handler_->HandleCrash(signo, siginfo, context); handler_->WakeThreads(); if (handler_->last_chance_handler_ && handler_->last_chance_handler_( signo, siginfo, static_cast(context))) { return; } } else { // Processes on Android normally have several chained signal handlers that // co-operate to report crashes. e.g. WebView will have this signal // handler installed, the app embedding WebView may have a signal handler // installed, and Bionic will have a signal handler. Each signal handler // runs in succession, possibly managed by libsigchain. This wait is // intended to avoid ill-effects from multiple signal handlers from // different layers (possibly all trying to use ptrace()) from running // simultaneously. It does not block forever so that in most conditions, // those signal handlers will still have a chance to run and ensures // process termination in case the first crashing thread crashes again in // its signal handler. Though less typical, this situation also occurs on // other Linuxes, e.g. to produce in-process stack traces for debug // builds. handler_->WaitForDumpDone(); } Signals::RestoreHandlerAndReraiseSignalOnReturn( siginfo, handler_->old_actions_.ActionForSignal(signo)); } void WaitForDumpDone() { kernel_timespec timeout; timeout.tv_sec = 5; timeout.tv_nsec = 0; sys_futex(&dump_done_futex_, FUTEX_WAIT_PRIVATE, kDumpNotDone, &timeout, nullptr, 0); } void WakeThreads() { dump_done_futex_ = kDumpDone; sys_futex( &dump_done_futex_, FUTEX_WAKE_PRIVATE, INT_MAX, nullptr, nullptr, 0); } Signals::OldActions old_actions_ = {}; ExceptionInformation exception_information_ = {}; CrashpadClient::FirstChanceHandler first_chance_handler_ = nullptr; LastChanceHandler last_chance_handler_ = nullptr; int32_t dump_done_futex_ = kDumpNotDone; #if !defined(__cpp_lib_atomic_value_initialization) || \ __cpp_lib_atomic_value_initialization < 201911L std::atomic_flag disabled_ = ATOMIC_FLAG_INIT; #else std::atomic_flag disabled_; #endif static SignalHandler* handler_; }; SignalHandler* SignalHandler::handler_ = nullptr; // Launches a single use handler to snapshot this process. class LaunchAtCrashHandler : public SignalHandler { public: LaunchAtCrashHandler(const LaunchAtCrashHandler&) = delete; LaunchAtCrashHandler& operator=(const LaunchAtCrashHandler&) = delete; static LaunchAtCrashHandler* Get() { static LaunchAtCrashHandler* instance = new LaunchAtCrashHandler(); return instance; } bool Initialize(std::vector* argv_in, const std::vector* envp, const std::set* unhandled_signals) { argv_strings_.swap(*argv_in); if (envp) { envp_strings_ = *envp; StringVectorToCStringVector(envp_strings_, &envp_); set_envp_ = true; } argv_strings_.push_back(FormatArgumentAddress("trace-parent-with-exception", &GetExceptionInfo())); StringVectorToCStringVector(argv_strings_, &argv_); return Install(unhandled_signals); } void HandleCrashImpl() override { ScopedPrSetPtracer set_ptracer(sys_getpid(), /* may_log= */ false); pid_t pid = fork(); if (pid < 0) { return; } if (pid == 0) { if (set_envp_) { execve(argv_[0], const_cast(argv_.data()), const_cast(envp_.data())); } else { execv(argv_[0], const_cast(argv_.data())); } _exit(EXIT_FAILURE); } int status; waitpid(pid, &status, 0); } private: LaunchAtCrashHandler() = default; ~LaunchAtCrashHandler() = delete; std::vector argv_strings_; std::vector argv_; std::vector envp_strings_; std::vector envp_; bool set_envp_ = false; }; class RequestCrashDumpHandler : public SignalHandler { public: RequestCrashDumpHandler(const RequestCrashDumpHandler&) = delete; RequestCrashDumpHandler& operator=(const RequestCrashDumpHandler&) = delete; static RequestCrashDumpHandler* Get() { static RequestCrashDumpHandler* instance = new RequestCrashDumpHandler(); return instance; } // pid < 0 indicates the handler pid should be determined by communicating // over the socket. // pid == 0 indicates it is not necessary to set the handler as this process' // ptracer. e.g. if the handler has CAP_SYS_PTRACE or if this process is in a // user namespace and the handler's uid matches the uid of the process that // created the namespace. // pid > 0 directly indicates what the handler's pid is expected to be, so // retrieving this information from the handler is not necessary. bool Initialize(ScopedFileHandle sock, pid_t pid, const std::set* unhandled_signals) { ExceptionHandlerClient client(sock.get(), true); if (pid < 0) { ucred creds; if (!client.GetHandlerCredentials(&creds)) { return false; } pid = creds.pid; } if (pid > 0) { pthread_atfork(nullptr, nullptr, SetPtracerAtFork); if (prctl(PR_SET_PTRACER, pid, 0, 0, 0) != 0) { PLOG(WARNING) << "prctl"; } } sock_to_handler_.reset(sock.release()); handler_pid_ = pid; return Install(unhandled_signals); } bool GetHandlerSocket(int* sock, pid_t* pid) { if (!sock_to_handler_.is_valid()) { return false; } if (sock) { *sock = sock_to_handler_.get(); } if (pid) { *pid = handler_pid_; } return true; } void HandleCrashImpl() override { // Attempt to set the ptracer again, in case a crash occurs after a fork, // before SetPtracerAtFork() has been called. Ignore errors because the // system call may be disallowed if the sandbox is engaged. if (handler_pid_ > 0) { sys_prctl(PR_SET_PTRACER, handler_pid_, 0, 0, 0); } ExceptionHandlerProtocol::ClientInformation info = {}; info.exception_information_address = FromPointerCast(&GetExceptionInfo()); #if BUILDFLAG(IS_CHROMEOS) info.crash_loop_before_time = crash_loop_before_time_; #endif ExceptionHandlerClient client(sock_to_handler_.get(), true); client.RequestCrashDump(info); } #if BUILDFLAG(IS_CHROMEOS) void SetCrashLoopBefore(uint64_t crash_loop_before_time) { crash_loop_before_time_ = crash_loop_before_time; } #endif private: RequestCrashDumpHandler() = default; ~RequestCrashDumpHandler() = delete; static void SetPtracerAtFork() { auto handler = RequestCrashDumpHandler::Get(); if (handler->handler_pid_ > 0 && prctl(PR_SET_PTRACER, handler->handler_pid_, 0, 0, 0) != 0) { PLOG(WARNING) << "prctl"; } } ScopedFileHandle sock_to_handler_; pid_t handler_pid_ = -1; #if BUILDFLAG(IS_CHROMEOS) // An optional UNIX timestamp passed to us from Chrome. // This will pass to crashpad_handler and then to Chrome OS crash_reporter. // This should really be a time_t, but it's basically an opaque value (we // don't anything with it except pass it along). uint64_t crash_loop_before_time_ = 0; #endif }; } // namespace CrashpadClient::CrashpadClient() {} CrashpadClient::~CrashpadClient() {} bool CrashpadClient::StartHandler( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, bool restartable, bool asynchronous_start, const std::vector& attachments) { DCHECK(!asynchronous_start); ScopedFileHandle client_sock, handler_sock; if (!UnixCredentialSocket::CreateCredentialSocketpair(&client_sock, &handler_sock)) { return false; } std::vector argv = BuildHandlerArgvStrings( handler, database, metrics_dir, url, annotations, arguments, attachments); argv.push_back(FormatArgumentInt("initial-client-fd", handler_sock.get())); argv.push_back("--shared-client-connection"); if (!SpawnSubprocess(argv, nullptr, handler_sock.get(), false, nullptr)) { return false; } handler_sock.reset(); pid_t handler_pid = -1; if (!IsRegularFile(base::FilePath("/proc/sys/kernel/yama/ptrace_scope"))) { handler_pid = 0; } auto signal_handler = RequestCrashDumpHandler::Get(); return signal_handler->Initialize( std::move(client_sock), handler_pid, &unhandled_signals_); } #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) // static bool CrashpadClient::GetHandlerSocket(int* sock, pid_t* pid) { auto signal_handler = RequestCrashDumpHandler::Get(); return signal_handler->GetHandlerSocket(sock, pid); } bool CrashpadClient::SetHandlerSocket(ScopedFileHandle sock, pid_t pid) { auto signal_handler = RequestCrashDumpHandler::Get(); return signal_handler->Initialize(std::move(sock), pid, &unhandled_signals_); } // static bool CrashpadClient::InitializeSignalStackForThread() { stack_t stack; if (sigaltstack(nullptr, &stack) != 0) { PLOG(ERROR) << "sigaltstack"; return false; } DCHECK_EQ(stack.ss_flags & SS_ONSTACK, 0); const size_t page_size = getpagesize(); #if defined(ADDRESS_SANITIZER) const size_t kStackSize = 2 * ((SIGSTKSZ + page_size - 1) & ~(page_size - 1)); #else const size_t kStackSize = (SIGSTKSZ + page_size - 1) & ~(page_size - 1); #endif // ADDRESS_SANITIZER if (stack.ss_flags & SS_DISABLE || stack.ss_size < kStackSize) { const size_t kGuardPageSize = page_size; const size_t kStackAllocSize = kStackSize + 2 * kGuardPageSize; static void (*stack_destructor)(void*) = [](void* stack_mem) { const size_t page_size = getpagesize(); const size_t kGuardPageSize = page_size; #if defined(ADDRESS_SANITIZER) const size_t kStackSize = 2 * ((SIGSTKSZ + page_size - 1) & ~(page_size - 1)); #else const size_t kStackSize = (SIGSTKSZ + page_size - 1) & ~(page_size - 1); #endif // ADDRESS_SANITIZER const size_t kStackAllocSize = kStackSize + 2 * kGuardPageSize; stack_t stack; stack.ss_flags = SS_DISABLE; if (sigaltstack(&stack, &stack) != 0) { PLOG(ERROR) << "sigaltstack"; } else if (stack.ss_sp != static_cast(stack_mem) + kGuardPageSize) { PLOG_IF(ERROR, sigaltstack(&stack, nullptr) != 0) << "sigaltstack"; } if (munmap(stack_mem, kStackAllocSize) != 0) { PLOG(ERROR) << "munmap"; } }; static pthread_key_t stack_key; static int key_error = []() { errno = pthread_key_create(&stack_key, stack_destructor); PLOG_IF(ERROR, errno) << "pthread_key_create"; return errno; }(); if (key_error) { return false; } auto old_stack = static_cast(pthread_getspecific(stack_key)); if (old_stack) { stack.ss_sp = old_stack + kGuardPageSize; } else { ScopedMmap stack_mem; if (!stack_mem.ResetMmap(nullptr, kStackAllocSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) { return false; } if (mprotect(stack_mem.addr_as() + kGuardPageSize, kStackSize, PROT_READ | PROT_WRITE) != 0) { PLOG(ERROR) << "mprotect"; return false; } stack.ss_sp = stack_mem.addr_as() + kGuardPageSize; errno = pthread_setspecific(stack_key, stack_mem.release()); PCHECK(errno == 0) << "pthread_setspecific"; } stack.ss_size = kStackSize; stack.ss_flags = (stack.ss_flags & SS_DISABLE) ? 0 : stack.ss_flags & SS_AUTODISARM; if (sigaltstack(&stack, nullptr) != 0) { PLOG(ERROR) << "sigaltstack"; return false; } } return true; } #endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || // BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_ANDROID) bool CrashpadClient::StartJavaHandlerAtCrash( const std::string& class_name, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments) { std::vector argv = BuildAppProcessArgs(class_name, database, metrics_dir, url, annotations, arguments, kInvalidFileHandle); auto signal_handler = LaunchAtCrashHandler::Get(); return signal_handler->Initialize(&argv, env, &unhandled_signals_); } // static bool CrashpadClient::StartJavaHandlerForClient( const std::string& class_name, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket) { std::vector argv = BuildAppProcessArgs( class_name, database, metrics_dir, url, annotations, arguments, socket); return SpawnSubprocess(argv, env, socket, false, nullptr); } bool CrashpadClient::StartHandlerWithLinkerAtCrash( const std::string& handler_trampoline, const std::string& handler_library, bool is_64_bit, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments) { std::vector argv = BuildArgsToLaunchWithLinker(handler_trampoline, handler_library, is_64_bit, database, metrics_dir, url, annotations, arguments, kInvalidFileHandle); auto signal_handler = LaunchAtCrashHandler::Get(); return signal_handler->Initialize(&argv, env, &unhandled_signals_); } // static bool CrashpadClient::StartHandlerWithLinkerForClient( const std::string& handler_trampoline, const std::string& handler_library, bool is_64_bit, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket) { std::vector argv = BuildArgsToLaunchWithLinker(handler_trampoline, handler_library, is_64_bit, database, metrics_dir, url, annotations, arguments, socket); return SpawnSubprocess(argv, env, socket, false, nullptr); } #endif bool CrashpadClient::StartHandlerAtCrash( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, const std::vector& attachments) { std::vector argv = BuildHandlerArgvStrings( handler, database, metrics_dir, url, annotations, arguments, attachments); auto signal_handler = LaunchAtCrashHandler::Get(); return signal_handler->Initialize(&argv, nullptr, &unhandled_signals_); } // static bool CrashpadClient::StartHandlerForClient( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket) { std::vector argv = BuildHandlerArgvStrings( handler, database, metrics_dir, url, annotations, arguments); argv.push_back(FormatArgumentInt("initial-client-fd", socket)); return SpawnSubprocess(argv, nullptr, socket, true, nullptr); } // static void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { if (!SignalHandler::Get()) { DLOG(ERROR) << "Crashpad isn't enabled"; return; } #if defined(ARCH_CPU_ARMEL) memset(context->uc_regspace, 0, sizeof(context->uc_regspace)); #elif defined(ARCH_CPU_ARM64) memset(context->uc_mcontext.__reserved, 0, sizeof(context->uc_mcontext.__reserved)); #endif siginfo_t siginfo; siginfo.si_signo = Signals::kSimulatedSigno; siginfo.si_errno = 0; siginfo.si_code = 0; SignalHandler::Get()->HandleCrash( siginfo.si_signo, &siginfo, reinterpret_cast(context)); } // static void CrashpadClient::CrashWithoutDump(const std::string& message) { SignalHandler::Disable(); LOG(FATAL) << message; } // static void CrashpadClient::SetFirstChanceExceptionHandler( FirstChanceHandler handler) { DCHECK(SignalHandler::Get()); SignalHandler::Get()->SetFirstChanceHandler(handler); } // static void CrashpadClient::SetLastChanceExceptionHandler(LastChanceHandler handler) { DCHECK(SignalHandler::Get()); SignalHandler::Get()->SetLastChanceExceptionHandler(handler); } void CrashpadClient::SetUnhandledSignals(const std::set& signals) { DCHECK(!SignalHandler::Get()); unhandled_signals_ = signals; } #if BUILDFLAG(IS_CHROMEOS) // static void CrashpadClient::SetCrashLoopBefore(uint64_t crash_loop_before_time) { auto request_crash_dump_handler = RequestCrashDumpHandler::Get(); request_crash_dump_handler->SetCrashLoopBefore(crash_loop_before_time); } #endif } // namespace crashpad ================================================ FILE: client/crashpad_client_linux_test.cc ================================================ // Copyright 2018 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crashpad_client.h" #include #include #include #include #include #include #include #include #include "base/check_op.h" #include "build/build_config.h" #include "client/annotation.h" #include "client/annotation_list.h" #include "client/crash_report_database.h" #include "client/crashpad_info.h" #include "client/simulate_crash.h" #include "gtest/gtest.h" #include "snapshot/annotation_snapshot.h" #include "snapshot/minidump/process_snapshot_minidump.h" #include "snapshot/sanitized/sanitization_information.h" #include "test/errors.h" #include "test/multiprocess.h" #include "test/multiprocess_exec.h" #include "test/scoped_temp_dir.h" #include "test/test_paths.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" #include "util/linux/exception_handler_client.h" #include "util/linux/exception_information.h" #include "util/linux/socket.h" #include "util/misc/address_sanitizer.h" #include "util/misc/address_types.h" #include "util/misc/from_pointer_cast.h" #include "util/misc/memory_sanitizer.h" #include "util/posix/scoped_mmap.h" #include "util/posix/signals.h" #include "util/thread/thread.h" #if BUILDFLAG(IS_ANDROID) #include #include "dlfcn_internal.h" // Normally this comes from set_abort_message.h, but only at API level 21. extern "C" void android_set_abort_message(const char* msg) __attribute__((weak)); #endif namespace crashpad { namespace test { namespace { enum class CrashType : uint32_t { kSimulated, kBuiltinTrap, kInfiniteRecursion, kSegvWithTagBits, // kFakeSegv is meant to simulate a MTE segv error. kFakeSegv, }; struct StartHandlerForSelfTestOptions { bool start_handler_at_crash; bool set_first_chance_handler; bool set_last_chance_handler; bool crash_non_main_thread; bool client_uses_signals; bool gather_indirectly_referenced_memory; CrashType crash_type; }; class StartHandlerForSelfTest : public testing::TestWithParam< std::tuple> { public: StartHandlerForSelfTest() = default; StartHandlerForSelfTest(const StartHandlerForSelfTest&) = delete; StartHandlerForSelfTest& operator=(const StartHandlerForSelfTest&) = delete; ~StartHandlerForSelfTest() = default; void SetUp() override { // MSAN requires that padding bytes have been initialized for structs that // are written to files. memset(&options_, 0, sizeof(options_)); std::tie(options_.start_handler_at_crash, options_.set_first_chance_handler, options_.set_last_chance_handler, options_.crash_non_main_thread, options_.client_uses_signals, options_.gather_indirectly_referenced_memory, options_.crash_type) = GetParam(); } const StartHandlerForSelfTestOptions& Options() const { return options_; } private: StartHandlerForSelfTestOptions options_; }; bool InstallHandler(CrashpadClient* client, bool start_at_crash, const base::FilePath& handler_path, const base::FilePath& database_path, const std::vector& attachments) { return start_at_crash ? client->StartHandlerAtCrash(handler_path, database_path, base::FilePath(), "", std::map(), std::vector(), attachments) : client->StartHandler(handler_path, database_path, base::FilePath(), "", std::map(), std::vector(), false, false, attachments); } constexpr char kTestAnnotationName[] = "name_of_annotation"; constexpr char kTestAnnotationValue[] = "value_of_annotation"; constexpr char kTestAttachmentName[] = "test_attachment"; constexpr char kTestAttachmentContent[] = "attachment_content"; #if BUILDFLAG(IS_ANDROID) constexpr char kTestAbortMessage[] = "test abort message"; #endif void ValidateAttachment(const CrashReportDatabase::UploadReport* report) { auto attachments = report->GetAttachments(); ASSERT_EQ(attachments.size(), 1u); char buf[sizeof(kTestAttachmentContent)]; attachments.at(kTestAttachmentName)->Read(buf, sizeof(buf)); ASSERT_EQ(memcmp(kTestAttachmentContent, buf, sizeof(kTestAttachmentContent)), 0); } void ValidateExtraMemory(const StartHandlerForSelfTestOptions& options, const ProcessSnapshotMinidump& minidump) { // Verify that if we have an exception, then the code around the instruction // pointer is included in the extra memory. const ExceptionSnapshot* exception = minidump.Exception(); if (exception == nullptr) return; uint64_t pc = exception->Context()->InstructionPointer(); std::vector snippets = minidump.ExtraMemory(); bool pc_found = false; for (const MemorySnapshot* snippet : snippets) { uint64_t start = snippet->Address(); uint64_t end = start + snippet->Size(); if (pc >= start && pc < end) { pc_found = true; break; } } EXPECT_EQ(pc_found, options.gather_indirectly_referenced_memory); if (options.crash_type == CrashType::kSegvWithTagBits) { EXPECT_EQ(exception->ExceptionAddress(), 0xefull << 56); } } void ValidateDump(const StartHandlerForSelfTestOptions& options, const CrashReportDatabase::UploadReport* report) { ProcessSnapshotMinidump minidump_snapshot; ASSERT_TRUE(minidump_snapshot.Initialize(report->Reader())); #if BUILDFLAG(IS_ANDROID) // This part of the test requires Q. The API level on Q devices will be 28 // until the API is finalized, so we can't check API level yet. For now, test // for the presence of a libc symbol which was introduced in Q. if (crashpad::internal::Dlsym(RTLD_DEFAULT, "android_fdsan_close_with_tag")) { const auto& annotations = minidump_snapshot.AnnotationsSimpleMap(); auto abort_message = annotations.find("abort_message"); ASSERT_NE(annotations.end(), abort_message); EXPECT_EQ(kTestAbortMessage, abort_message->second); } #endif ValidateAttachment(report); ValidateExtraMemory(options, minidump_snapshot); for (const ModuleSnapshot* module : minidump_snapshot.Modules()) { for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) { if (static_cast(annotation.type) != Annotation::Type::kString) { continue; } if (annotation.name == kTestAnnotationName) { std::string value( reinterpret_cast(annotation.value.data()), annotation.value.size()); EXPECT_EQ(value, kTestAnnotationValue); return; } } } ADD_FAILURE(); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winfinite-recursion" // Clang (masquerading as gcc) is too smart, and removes the recursion // otherwise. May need to change if either clang or another compiler becomes // smarter. #if defined(COMPILER_GCC) __attribute__((noinline)) #endif #if defined(__clang__) __attribute__((optnone)) #endif int RecurseInfinitely(int* ptr) { int buf[1 << 20]; return *ptr + RecurseInfinitely(buf); } #pragma clang diagnostic pop sigjmp_buf do_crash_sigjmp_env; bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) { siglongjmp(do_crash_sigjmp_env, 1); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code-return" return true; #pragma clang diagnostic pop } bool HandleCrashSuccessfullyAfterReporting(int, siginfo_t*, ucontext_t*) { return true; } void DoCrash(const StartHandlerForSelfTestOptions& options, CrashpadClient* client) { if (sigsetjmp(do_crash_sigjmp_env, 1) != 0) { return; } switch (options.crash_type) { case CrashType::kSimulated: { CRASHPAD_SIMULATE_CRASH(); break; } case CrashType::kBuiltinTrap: { __builtin_trap(); } case CrashType::kInfiniteRecursion: { int val = 42; exit(RecurseInfinitely(&val)); } case CrashType::kSegvWithTagBits: { volatile char* x = nullptr; #ifdef __aarch64__ x += 0xefull << 56; #endif // __aarch64__ *x; break; } case CrashType::kFakeSegv: { // With a regular SIGSEGV like null dereference, the signal gets reraised // automatically, causing HandleOrReraiseSignal() to be called a second // time, terminating the process with the signal regardless of the last // chance handler. raise(SIGSEGV); break; } } } class ScopedAltSignalStack { public: ScopedAltSignalStack() = default; ScopedAltSignalStack(const ScopedAltSignalStack&) = delete; ScopedAltSignalStack& operator=(const ScopedAltSignalStack&) = delete; ~ScopedAltSignalStack() { if (stack_mem_.is_valid()) { stack_t stack; stack.ss_flags = SS_DISABLE; if (sigaltstack(&stack, nullptr) != 0) { ADD_FAILURE() << ErrnoMessage("sigaltstack"); } } } void Initialize() { ScopedMmap local_stack_mem; const size_t stack_size = MINSIGSTKSZ; ASSERT_TRUE(local_stack_mem.ResetMmap(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); stack_t stack; stack.ss_sp = local_stack_mem.addr(); stack.ss_size = stack_size; stack.ss_flags = 0; ASSERT_EQ(sigaltstack(&stack, nullptr), 0) << ErrnoMessage("sigaltstack"); stack_mem_.ResetAddrLen(local_stack_mem.release(), stack_size); } private: ScopedMmap stack_mem_; }; class CrashThread : public Thread { public: CrashThread(const StartHandlerForSelfTestOptions& options, CrashpadClient* client) : client_signal_stack_(), options_(options), client_(client) {} CrashThread(const CrashThread&) = delete; CrashThread& operator=(const CrashThread&) = delete; private: void ThreadMain() override { // It is only necessary to call InitializeSignalStackForThread() once, but // should be harmless to call multiple times and durable against the client // using sigaltstack() either before or after it is called. CrashpadClient::InitializeSignalStackForThread(); if (options_.client_uses_signals) { client_signal_stack_.Initialize(); } CrashpadClient::InitializeSignalStackForThread(); DoCrash(options_, client_); } ScopedAltSignalStack client_signal_stack_; const StartHandlerForSelfTestOptions& options_; CrashpadClient* client_; }; CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) { FileHandle in = StdioFileHandle(StdioStream::kStandardInput); VMSize temp_dir_length; CheckedReadFileExactly(in, &temp_dir_length, sizeof(temp_dir_length)); std::string temp_dir(temp_dir_length, '\0'); CheckedReadFileExactly(in, &temp_dir[0], temp_dir_length); StartHandlerForSelfTestOptions options; CheckedReadFileExactly(in, &options, sizeof(options)); ScopedAltSignalStack client_signal_stack; if (options.client_uses_signals) { client_signal_stack.Initialize(); static Signals::OldActions old_actions; static Signals::Handler client_handler = [](int signo, siginfo_t* siginfo, void*) { FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); char c = 0; WriteFile(out, &c, sizeof(c)); Signals::RestoreHandlerAndReraiseSignalOnReturn( siginfo, old_actions.ActionForSignal(signo)); }; CHECK(Signals::InstallCrashHandlers( client_handler, SA_ONSTACK, &old_actions)); } if (options.gather_indirectly_referenced_memory) { CrashpadInfo::GetCrashpadInfo()->set_gather_indirectly_referenced_memory( TriState::kEnabled, 1024 * 1024 * 4); } base::FilePath handler_path = TestPaths::Executable().DirName().Append( FILE_PATH_LITERAL("crashpad_handler")); crashpad::AnnotationList::Register(); static StringAnnotation<32> test_annotation(kTestAnnotationName); test_annotation.Set(kTestAnnotationValue); const std::vector attachments = { base::FilePath(temp_dir).Append(kTestAttachmentName)}; crashpad::CrashpadClient client; if (!InstallHandler(&client, options.start_handler_at_crash, handler_path, base::FilePath(temp_dir), attachments)) { return EXIT_FAILURE; } if (options.set_first_chance_handler) { client.SetFirstChanceExceptionHandler(HandleCrashSuccessfully); } if (options.set_last_chance_handler) { client.SetLastChanceExceptionHandler(HandleCrashSuccessfullyAfterReporting); } #if BUILDFLAG(IS_ANDROID) if (android_set_abort_message) { android_set_abort_message(kTestAbortMessage); } #endif if (options.crash_non_main_thread) { CrashThread thread(options, &client); thread.Start(); thread.Join(); } else { DoCrash(options, &client); } return EXIT_SUCCESS; } class StartHandlerForSelfInChildTest : public MultiprocessExec { public: StartHandlerForSelfInChildTest(const StartHandlerForSelfTestOptions& options) : MultiprocessExec(), options_(options) { SetChildTestMainFunction("StartHandlerForSelfTestChild"); if (!options.set_first_chance_handler) { switch (options.crash_type) { case CrashType::kSimulated: // kTerminationNormal, EXIT_SUCCESS break; case CrashType::kBuiltinTrap: SetExpectedChildTerminationBuiltinTrap(); break; case CrashType::kInfiniteRecursion: SetExpectedChildTermination(TerminationReason::kTerminationSignal, SIGSEGV); break; case CrashType::kSegvWithTagBits: SetExpectedChildTermination(TerminationReason::kTerminationSignal, SIGSEGV); break; case CrashType::kFakeSegv: if (!options.set_last_chance_handler) { SetExpectedChildTermination(TerminationReason::kTerminationSignal, SIGSEGV); } else { SetExpectedChildTermination(TerminationReason::kTerminationNormal, EXIT_SUCCESS); } break; } } } StartHandlerForSelfInChildTest(const StartHandlerForSelfInChildTest&) = delete; StartHandlerForSelfInChildTest& operator=( const StartHandlerForSelfInChildTest&) = delete; private: void MultiprocessParent() override { ScopedTempDir temp_dir; VMSize temp_dir_length = temp_dir.path().value().size(); ASSERT_TRUE(LoggingWriteFile( WritePipeHandle(), &temp_dir_length, sizeof(temp_dir_length))); ASSERT_TRUE(LoggingWriteFile( WritePipeHandle(), temp_dir.path().value().data(), temp_dir_length)); ASSERT_TRUE( LoggingWriteFile(WritePipeHandle(), &options_, sizeof(options_))); FileWriter writer; base::FilePath test_attachment_path = temp_dir.path().Append(kTestAttachmentName); bool is_created = writer.Open(test_attachment_path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly); ASSERT_TRUE(is_created); writer.Write(kTestAttachmentContent, sizeof(kTestAttachmentContent)); writer.Close(); if (options_.client_uses_signals && !options_.set_first_chance_handler && options_.crash_type != CrashType::kSimulated && // The last chance handler will prevent the client handler from being // called if crash type is kFakeSegv. (!options_.set_last_chance_handler || options_.crash_type != CrashType::kFakeSegv)) { // Wait for child's client signal handler. char c; EXPECT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, sizeof(c))); } // Wait for child to finish. CheckedReadFileAtEOF(ReadPipeHandle()); auto database = CrashReportDatabase::Initialize(temp_dir.path()); ASSERT_TRUE(database); std::vector reports; ASSERT_EQ(database->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 0u); reports.clear(); ASSERT_EQ(database->GetPendingReports(&reports), CrashReportDatabase::kNoError); bool report_expected = !options_.set_first_chance_handler || options_.crash_type == CrashType::kSimulated; ASSERT_EQ(reports.size(), report_expected ? 1u : 0u); if (!report_expected) { return; } std::unique_ptr report; ASSERT_EQ(database->GetReportForUploading(reports[0].uuid, &report), CrashReportDatabase::kNoError); ValidateDump(options_, report.get()); } StartHandlerForSelfTestOptions options_; }; TEST_P(StartHandlerForSelfTest, StartHandlerInChild) { #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ defined(UNDEFINED_SANITIZER) if (Options().crash_type == CrashType::kInfiniteRecursion) { GTEST_SKIP(); } #endif // defined(ADDRESS_SANITIZER) // kFakeSegv does raise(SIGSEGV) to simulate a MTE error which is a SEGSEGV // that doesn't get reraised automatically, but this causes the child process // to flakily terminate normally on some bots (e.g. android-nougat-x86-rel) // for some reason so this is skipped. if (!Options().set_last_chance_handler && Options().crash_type == CrashType::kFakeSegv) { GTEST_SKIP(); } if (Options().crash_type == CrashType::kSegvWithTagBits) { #if !defined(ARCH_CPU_ARM64) GTEST_SKIP() << "Testing for tag bits only exists on aarch64."; #else struct utsname uname_info; ASSERT_EQ(uname(&uname_info), 0); ASSERT_NE(uname_info.release, nullptr); char* release = uname_info.release; unsigned major = strtoul(release, &release, 10); ASSERT_EQ(*release++, '.'); unsigned minor = strtoul(release, nullptr, 10); if (major < 5 || (major == 5 && minor < 11)) { GTEST_SKIP() << "Linux kernel v" << uname_info.release << " does not support SA_EXPOSE_TAGBITS"; } #endif // !defined(ARCH_CPU_ARM64) } StartHandlerForSelfInChildTest test(Options()); test.Run(); } INSTANTIATE_TEST_SUITE_P( StartHandlerForSelfTestSuite, StartHandlerForSelfTest, testing::Combine(testing::Bool(), testing::Bool(), testing::Bool(), testing::Bool(), testing::Bool(), testing::Bool(), testing::Values(CrashType::kSimulated, CrashType::kBuiltinTrap, CrashType::kInfiniteRecursion, CrashType::kSegvWithTagBits, CrashType::kFakeSegv))); // Test state for starting the handler for another process. class StartHandlerForClientTest { public: StartHandlerForClientTest() = default; StartHandlerForClientTest(const StartHandlerForClientTest&) = delete; StartHandlerForClientTest& operator=(const StartHandlerForClientTest&) = delete; ~StartHandlerForClientTest() = default; bool Initialize(bool sanitize) { sanitize_ = sanitize; return UnixCredentialSocket::CreateCredentialSocketpair(&client_sock_, &server_sock_); } bool StartHandlerOnDemand() { char c; if (!LoggingReadFileExactly(server_sock_.get(), &c, sizeof(c))) { ADD_FAILURE(); return false; } base::FilePath handler_path = TestPaths::Executable().DirName().Append( FILE_PATH_LITERAL("crashpad_handler")); CrashpadClient client; if (!client.StartHandlerForClient(handler_path, temp_dir_.path(), base::FilePath(), "", std::map(), std::vector(), server_sock_.get())) { ADD_FAILURE(); return false; } return true; } void ExpectReport() { auto database = CrashReportDatabase::InitializeWithoutCreating(temp_dir_.path()); ASSERT_TRUE(database); std::vector reports; ASSERT_EQ(database->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 0u); reports.clear(); ASSERT_EQ(database->GetPendingReports(&reports), CrashReportDatabase::kNoError); if (sanitize_) { EXPECT_EQ(reports.size(), 0u); } else { EXPECT_EQ(reports.size(), 1u); } } bool InstallHandler() { auto signal_handler = SandboxedHandler::Get(); return signal_handler->Initialize(client_sock_.get(), sanitize_); } private: // A signal handler that defers handler process startup to another, presumably // more privileged, process. class SandboxedHandler { public: SandboxedHandler(const SandboxedHandler&) = delete; SandboxedHandler& operator=(const SandboxedHandler&) = delete; static SandboxedHandler* Get() { static SandboxedHandler* instance = new SandboxedHandler(); return instance; } bool Initialize(FileHandle client_sock, bool sanitize) { client_sock_ = client_sock; sanitize_ = sanitize; return Signals::InstallCrashHandlers(HandleCrash, 0, nullptr); } private: SandboxedHandler() = default; ~SandboxedHandler() = delete; static void HandleCrash(int signo, siginfo_t* siginfo, void* context) { auto state = Get(); char c = 0; CHECK(LoggingWriteFile(state->client_sock_, &c, sizeof(c))); ExceptionInformation exception_information; exception_information.siginfo_address = FromPointerCast( siginfo); exception_information.context_address = FromPointerCast( context); exception_information.thread_id = syscall(SYS_gettid); ExceptionHandlerProtocol::ClientInformation info; info.exception_information_address = FromPointerCast( &exception_information); SanitizationInformation sanitization_info = {}; if (state->sanitize_) { info.sanitization_information_address = FromPointerCast(&sanitization_info); // Target a non-module address to prevent a crash dump. sanitization_info.target_module_address = FromPointerCast(&sanitization_info); } ExceptionHandlerClient handler_client(state->client_sock_, false); CHECK_EQ(handler_client.RequestCrashDump(info), 0); Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); } FileHandle client_sock_; bool sanitize_; }; ScopedTempDir temp_dir_; ScopedFileHandle client_sock_; ScopedFileHandle server_sock_; bool sanitize_; }; // Tests starting the handler for a child process. class StartHandlerForChildTest : public Multiprocess { public: StartHandlerForChildTest() = default; StartHandlerForChildTest(const StartHandlerForChildTest&) = delete; StartHandlerForChildTest& operator=(const StartHandlerForChildTest&) = delete; ~StartHandlerForChildTest() = default; bool Initialize(bool sanitize) { SetExpectedChildTerminationBuiltinTrap(); return test_state_.Initialize(sanitize); } private: void MultiprocessParent() { ASSERT_TRUE(test_state_.StartHandlerOnDemand()); // Wait for chlid to finish. CheckedReadFileAtEOF(ReadPipeHandle()); test_state_.ExpectReport(); } [[noreturn]] void MultiprocessChild() { CHECK(test_state_.InstallHandler()); __builtin_trap(); } StartHandlerForClientTest test_state_; }; TEST(CrashpadClient, StartHandlerForChild) { StartHandlerForChildTest test; ASSERT_TRUE(test.Initialize(/* sanitize= */ false)); test.Run(); } TEST(CrashpadClient, SanitizedChild) { StartHandlerForChildTest test; ASSERT_TRUE(test.Initialize(/* sanitize= */ true)); test.Run(); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/crashpad_client_mac.cc ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crashpad_client.h" #include #include #include #include #include #include #include #include #include "base/apple/mach_logging.h" #include "base/check_op.h" #include "base/logging.h" #include "base/strings/stringprintf.h" #include "util/mac/mac_util.h" #include "util/mach/bootstrap.h" #include "util/mach/child_port_handshake.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" #include "util/mach/notify_server.h" #include "util/misc/clock.h" #include "util/misc/implicit_cast.h" #include "util/posix/spawn_subprocess.h" namespace crashpad { namespace { std::string FormatArgumentString(const std::string& name, const std::string& value) { return base::StringPrintf("--%s=%s", name.c_str(), value.c_str()); } std::string FormatArgumentInt(const std::string& name, int value) { return base::StringPrintf("--%s=%d", name.c_str(), value); } // Set the exception handler for EXC_CRASH, EXC_RESOURCE, and EXC_GUARD. // // EXC_CRASH is how most crashes are received. Most other exception types such // as EXC_BAD_ACCESS are delivered to a host-level exception handler in the // kernel where they are converted to POSIX signals. See 10.9.5 // xnu-2422.115.4/bsd/uxkern/ux_exception.c catch_mach_exception_raise(). If a // core-generating signal (triggered through this hardware mechanism or a // software mechanism such as abort() sending SIGABRT) is unhandled and the // process exits, or if the process is killed with SIGKILL for code-signing // reasons, an EXC_CRASH exception will be sent. See 10.9.5 // xnu-2422.115.4/bsd/kern/kern_exit.c proc_prepareexit(). // // EXC_RESOURCE and EXC_GUARD (pre-macOS 13) do not become signals or EXC_CRASH // exceptions. The host-level exception handler in the kernel does not receive // these exception types, and even if it did, it would not map them to signals. // Instead, the first Mach service loaded by the root (process ID 1) launchd // with a boolean “ExceptionServer” property in its job dictionary (regardless // of its value) or with any subdictionary property will become the host-level // exception handler for EXC_CRASH, EXC_RESOURCE, and EXC_GUARD. See 10.9.5 // launchd-842.92.1/src/core.c job_setup_exception_port(). Normally, this job is // com.apple.ReportCrash.Root, the systemwide Apple Crash Reporter. Since it is // impossible to receive EXC_RESOURCE and EXC_GUARD exceptions through the // EXC_CRASH mechanism, an exception handler must be registered for them by name // if it is to receive these exception types. The default task-level handler for // these exception types is set by launchd in a similar manner. // // EXC_MASK_RESOURCE and EXC_MASK_GUARD are not available on all systems, and // the kernel will reject attempts to use them if it does not understand them, // so AND them with ExcMaskValid(). EXC_MASK_CRASH is always supported. bool SetCrashExceptionPorts(exception_handler_t exception_handler) { ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); exception_mask_t mask = EXC_MASK_CRASH | EXC_MASK_RESOURCE; if (MacOSVersionNumber() < 13'00'00) { // EXC_GUARD is delivered as an EXC_CRASH macOS 13 and later. mask |= EXC_MASK_GUARD; } return exception_ports.SetExceptionPort( mask & ExcMaskValid(), exception_handler, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, MACHINE_THREAD_STATE); } class ScopedPthreadAttrDestroy { public: explicit ScopedPthreadAttrDestroy(pthread_attr_t* pthread_attr) : pthread_attr_(pthread_attr) { } ScopedPthreadAttrDestroy(const ScopedPthreadAttrDestroy&) = delete; ScopedPthreadAttrDestroy& operator=(const ScopedPthreadAttrDestroy&) = delete; ~ScopedPthreadAttrDestroy() { errno = pthread_attr_destroy(pthread_attr_); PLOG_IF(WARNING, errno != 0) << "pthread_attr_destroy"; } private: pthread_attr_t* pthread_attr_; }; //! \brief Starts a Crashpad handler, possibly restarting it if it dies. class HandlerStarter final : public NotifyServer::DefaultInterface { public: HandlerStarter(const HandlerStarter&) = delete; HandlerStarter& operator=(const HandlerStarter&) = delete; ~HandlerStarter() {} //! \brief Starts a Crashpad handler initially, as opposed to starting it for //! subsequent restarts. //! //! All parameters are as in CrashpadClient::StartHandler(). //! //! \return On success, a send right to the Crashpad handler that has been //! started. On failure, `MACH_PORT_NULL` with a message logged. static base::apple::ScopedMachSendRight InitialStart( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, bool restartable, const std::vector& attachments) { base::apple::ScopedMachReceiveRight receive_right( NewMachPort(MACH_PORT_RIGHT_RECEIVE)); if (!receive_right.is_valid()) { return base::apple::ScopedMachSendRight(); } mach_port_t port; mach_msg_type_name_t right_type; kern_return_t kr = mach_port_extract_right(mach_task_self(), receive_right.get(), MACH_MSG_TYPE_MAKE_SEND, &port, &right_type); if (kr != KERN_SUCCESS) { MACH_LOG(ERROR, kr) << "mach_port_extract_right"; return base::apple::ScopedMachSendRight(); } base::apple::ScopedMachSendRight send_right(port); DCHECK_EQ(port, receive_right.get()); DCHECK_EQ(right_type, implicit_cast(MACH_MSG_TYPE_PORT_SEND)); std::unique_ptr handler_restarter; if (restartable) { handler_restarter.reset(new HandlerStarter()); if (!handler_restarter->notify_port_.is_valid()) { // This is an error that NewMachPort() would have logged. Proceed anyway // without the ability to restart. handler_restarter.reset(); } } if (!CommonStart(handler, database, metrics_dir, url, annotations, arguments, std::move(receive_right), handler_restarter.get(), false, attachments)) { return base::apple::ScopedMachSendRight(); } if (handler_restarter && handler_restarter->StartRestartThread( handler, database, metrics_dir, url, annotations, arguments, attachments)) { // The thread owns the object now. std::ignore = handler_restarter.release(); } // If StartRestartThread() failed, proceed without the ability to restart. // handler_restarter will be released when this function returns. return send_right; } // NotifyServer::DefaultInterface: kern_return_t DoMachNotifyPortDestroyed(notify_port_t notify, mach_port_t rights, const mach_msg_trailer_t* trailer, bool* destroy_request) override { // The receive right corresponding to this process’ crash exception port is // now owned by this process. Any crashes that occur before the receive // right is moved to a new handler process will cause the process to hang in // an unkillable state prior to OS X 10.10. if (notify != notify_port_) { LOG(WARNING) << "notify port mismatch"; return KERN_FAILURE; } // If CommonStart() fails, the receive right will die, and this will just // be called again for another try. CommonStart(handler_, database_, metrics_dir_, url_, annotations_, arguments_, base::apple::ScopedMachReceiveRight(rights), this, true, attachments_); return KERN_SUCCESS; } private: HandlerStarter() : NotifyServer::DefaultInterface(), handler_(), database_(), metrics_dir_(), url_(), annotations_(), arguments_(), notify_port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)), last_start_time_(0) { } //! \brief Starts a Crashpad handler. //! //! All parameters are as in CrashpadClient::StartHandler(), with these //! additions: //! //! \param[in] receive_right The receive right to move to the Crashpad //! handler. The handler will use this receive right to run its exception //! server. //! \param[in] handler_restarter If CrashpadClient::StartHandler() was invoked //! with \a restartable set to `true`, this is the restart state object. //! Otherwise, this is `nullptr`. //! \param[in] restart If CrashpadClient::StartHandler() was invoked with \a //! restartable set to `true` and CommonStart() is being called to restart //! a previously-started handler, this is `true`. Otherwise, this is //! `false`. //! //! \return `true` on success, `false` on failure, with a message logged. //! Failures include failure to start the handler process and failure to //! rendezvous with it via ChildPortHandshake. static bool CommonStart(const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, base::apple::ScopedMachReceiveRight receive_right, HandlerStarter* handler_restarter, bool restart, const std::vector& attachments) { DCHECK(!restart || handler_restarter); if (handler_restarter) { // The port-destroyed notification must be requested each time. It uses // a send-once right, so once the notification is received, it won’t be // sent again unless re-requested. mach_port_t previous; kern_return_t kr = mach_port_request_notification(mach_task_self(), receive_right.get(), MACH_NOTIFY_PORT_DESTROYED, 0, handler_restarter->notify_port_.get(), MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous); if (kr != KERN_SUCCESS) { MACH_LOG(WARNING, kr) << "mach_port_request_notification"; // This will cause the restart thread to terminate after this restart // attempt. There’s no longer any need for it, because no more // port-destroyed notifications can be delivered. handler_restarter->notify_port_.reset(); } else { base::apple::ScopedMachSendRight previous_owner(previous); DCHECK(restart || !previous_owner.is_valid()); } if (restart) { // If the handler was ever started before, don’t restart it too quickly. constexpr uint64_t kNanosecondsPerSecond = 1E9; constexpr uint64_t kMinimumStartInterval = 1 * kNanosecondsPerSecond; const uint64_t earliest_next_start_time = handler_restarter->last_start_time_ + kMinimumStartInterval; const uint64_t now_time = ClockMonotonicNanoseconds(); if (earliest_next_start_time > now_time) { const uint64_t sleep_time = earliest_next_start_time - now_time; LOG(INFO) << "restarting handler" << base::StringPrintf(" in %.3fs", static_cast(sleep_time) / kNanosecondsPerSecond); SleepNanoseconds(earliest_next_start_time - now_time); } else { LOG(INFO) << "restarting handler"; } } handler_restarter->last_start_time_ = ClockMonotonicNanoseconds(); } ChildPortHandshake child_port_handshake; base::ScopedFD server_write_fd = child_port_handshake.ServerWriteFD(); // Use handler as argv[0], followed by arguments directed by this method’s // parameters and a --handshake-fd argument. |arguments| are added first so // that if it erroneously contains an argument such as --url, the actual // |url| argument passed to this method will supersede it. In normal // command-line processing, the last parameter wins in the case of a // conflict. std::vector argv(1, handler.value()); argv.reserve(1 + arguments.size() + 2 + annotations.size() + 1); for (const std::string& argument : arguments) { argv.push_back(argument); } if (!database.value().empty()) { argv.push_back(FormatArgumentString("database", database.value())); } if (!metrics_dir.value().empty()) { argv.push_back(FormatArgumentString("metrics-dir", metrics_dir.value())); } if (!url.empty()) { argv.push_back(FormatArgumentString("url", url)); } for (const auto& kv : annotations) { argv.push_back( FormatArgumentString("annotation", kv.first + '=' + kv.second)); } for (const auto& attachment : attachments) { argv.push_back(FormatArgumentString("attachment", attachment.value())); } argv.push_back(FormatArgumentInt("handshake-fd", server_write_fd.get())); // When restarting, reset the system default crash handler first. Otherwise, // the crash exception port in the handler will have been inherited from // this parent process, which was probably using the exception server now // being restarted. The handler can’t monitor itself for its own crashes via // this interface. if (!SpawnSubprocess( argv, nullptr, server_write_fd.get(), true, restart ? CrashpadClient::UseSystemDefaultHandler : nullptr)) { return false; } // Close the write side of the pipe, so that the handler process is the only // process that can write to it. server_write_fd.reset(); // Rendezvous with the handler running in the grandchild process. if (!child_port_handshake.RunClient(receive_right.get(), MACH_MSG_TYPE_MOVE_RECEIVE)) { return false; } std::ignore = receive_right.release(); return true; } bool StartRestartThread(const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, const std::vector& attachments) { handler_ = handler; database_ = database; metrics_dir_ = metrics_dir; url_ = url; annotations_ = annotations; arguments_ = arguments; attachments_ = attachments; pthread_attr_t pthread_attr; errno = pthread_attr_init(&pthread_attr); if (errno != 0) { PLOG(WARNING) << "pthread_attr_init"; return false; } ScopedPthreadAttrDestroy pthread_attr_owner(&pthread_attr); errno = pthread_attr_setdetachstate(&pthread_attr, PTHREAD_CREATE_DETACHED); if (errno != 0) { PLOG(WARNING) << "pthread_attr_setdetachstate"; return false; } pthread_t pthread; errno = pthread_create(&pthread, &pthread_attr, RestartThreadMain, this); if (errno != 0) { PLOG(WARNING) << "pthread_create"; return false; } return true; } static void* RestartThreadMain(void* argument) { HandlerStarter* self = reinterpret_cast(argument); NotifyServer notify_server(self); mach_msg_return_t mr; do { mr = MachMessageServer::Run(¬ify_server, self->notify_port_.get(), 0, MachMessageServer::kPersistent, MachMessageServer::kReceiveLargeError, kMachMessageTimeoutWaitIndefinitely); MACH_LOG_IF(ERROR, mr != MACH_MSG_SUCCESS, mr) << "MachMessageServer::Run"; } while (self->notify_port_.is_valid() && mr == MACH_MSG_SUCCESS); delete self; return nullptr; } base::FilePath handler_; base::FilePath database_; base::FilePath metrics_dir_; std::string url_; std::map annotations_; std::vector arguments_; std::vector attachments_; base::apple::ScopedMachReceiveRight notify_port_; uint64_t last_start_time_; }; } // namespace CrashpadClient::CrashpadClient() : exception_port_(MACH_PORT_NULL) { } CrashpadClient::~CrashpadClient() { } bool CrashpadClient::StartHandler( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, bool restartable, bool asynchronous_start, const std::vector& attachments) { // The “restartable” behavior can only be selected on OS X 10.10 and later. In // previous OS versions, if the initial client were to crash while attempting // to restart the handler, it would become an unkillable process. base::apple::ScopedMachSendRight exception_port(HandlerStarter::InitialStart( handler, database, metrics_dir, url, annotations, arguments, restartable && (__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10 || MacOSVersionNumber() >= 10'10'00), attachments)); if (!exception_port.is_valid()) { return false; } SetHandlerMachPort(std::move(exception_port)); return true; } bool CrashpadClient::SetHandlerMachService(const std::string& service_name) { base::apple::ScopedMachSendRight exception_port( BootstrapLookUp(service_name)); if (!exception_port.is_valid()) { return false; } SetHandlerMachPort(std::move(exception_port)); return true; } bool CrashpadClient::SetHandlerMachPort( base::apple::ScopedMachSendRight exception_port) { DCHECK(!exception_port_.is_valid()); DCHECK(exception_port.is_valid()); if (!SetCrashExceptionPorts(exception_port.get())) { return false; } exception_port_.swap(exception_port); return true; } base::apple::ScopedMachSendRight CrashpadClient::GetHandlerMachPort() const { DCHECK(exception_port_.is_valid()); // For the purposes of this method, only return a port set by // SetHandlerMachPort(). // // It would be possible to use task_get_exception_ports() to look up the // EXC_CRASH task exception port, but that’s probably not what users of this // interface really want. If CrashpadClient is asked for the handler Mach // port, it should only return a port that it knows about by virtue of having // set it. It shouldn’t return any EXC_CRASH task exception port in effect if // SetHandlerMachPort() was never called, and it shouldn’t return any // EXC_CRASH task exception port that might be set by other code after // SetHandlerMachPort() is called. // // The caller is accepting its own new ScopedMachSendRight, so increment the // reference count of the underlying right. kern_return_t kr = mach_port_mod_refs( mach_task_self(), exception_port_.get(), MACH_PORT_RIGHT_SEND, 1); if (kr != KERN_SUCCESS) { MACH_LOG(ERROR, kr) << "mach_port_mod_refs"; return base::apple::ScopedMachSendRight(MACH_PORT_NULL); } return base::apple::ScopedMachSendRight(exception_port_.get()); } // static void CrashpadClient::UseSystemDefaultHandler() { base::apple::ScopedMachSendRight system_crash_reporter_handler( SystemCrashReporterHandler()); // Proceed even if SystemCrashReporterHandler() failed, setting MACH_PORT_NULL // to clear the current exception ports. if (!SetCrashExceptionPorts(system_crash_reporter_handler.get())) { SetCrashExceptionPorts(MACH_PORT_NULL); } } } // namespace crashpad ================================================ FILE: client/crashpad_client_win.cc ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crashpad_client.h" #include #include #include #include #include #include #include #include #include "base/check_op.h" #include "base/logging.h" #include "base/scoped_generic.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/lock.h" #include "util/file/file_io.h" #include "util/misc/capture_context.h" #include "util/misc/from_pointer_cast.h" #include "util/misc/random_string.h" #include "util/win/address_types.h" #include "util/win/command_line.h" #include "util/win/context_wrappers.h" #include "util/win/critical_section_with_debug_info.h" #include "util/win/exception_codes.h" #include "util/win/get_function.h" #include "util/win/handle.h" #include "util/win/initial_client_data.h" #include "util/win/loader_lock.h" #include "util/win/nt_internals.h" #include "util/win/ntstatus_logging.h" #include "util/win/process_info.h" #include "util/win/registration_protocol_win.h" #include "util/win/safe_terminate_process.h" #include "util/win/scoped_process_suspend.h" #include "util/win/termination_codes.h" #include "util/win/xp_compat.h" namespace crashpad { namespace { // This handle is never closed. This is used to signal to the server that a dump // should be taken in the event of a crash. HANDLE g_signal_exception = INVALID_HANDLE_VALUE; // Where we store the exception information that the crash handler reads. ExceptionInformation g_crash_exception_information; // Guards multiple simultaneous calls to DumpWithoutCrash() in the client. // This is leaked. base::Lock* g_non_crash_dump_lock; // Where we store a pointer to the context information when taking a non-crash // dump. ExceptionInformation g_non_crash_exception_information; // Context for the out-of-process exception handler module and holds non-crash // dump handles. Handles are never closed once created. WerRegistration g_wer_registration = {WerRegistration::kWerRegistrationVersion, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, false, nullptr, {0}, {0}, {0}}; enum class StartupState : int { kNotReady = 0, // This must be value 0 because it is the initial value of a // global AtomicWord. kSucceeded = 1, // The CreateProcess() for the handler succeeded. kFailed = 2, // The handler failed to start. }; // This is a tri-state of type StartupState. It starts at 0 == kNotReady, and // when the handler is known to have started successfully, or failed to start // the value will be updated. The unhandled exception filter will not proceed // until one of those two cases happens. std::atomic g_handler_startup_state; // A CRITICAL_SECTION initialized with // RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO to force it to be allocated with a // valid .DebugInfo field. The address of this critical section is given to the // handler. All critical sections with debug info are linked in a doubly-linked // list, so this allows the handler to capture all of them. CRITICAL_SECTION g_critical_section_with_debug_info; void SetHandlerStartupState(StartupState state) { DCHECK(state == StartupState::kSucceeded || state == StartupState::kFailed); g_handler_startup_state.store(state, std::memory_order_release); } StartupState BlockUntilHandlerStartedOrFailed() { // Wait until we know the handler has either succeeded or failed to start. StartupState startup_state; while ((startup_state = g_handler_startup_state.load( std::memory_order_acquire)) == StartupState::kNotReady) { Sleep(1); } return startup_state; } #if defined(ADDRESS_SANITIZER) extern "C" LONG __asan_unhandled_exception_filter(EXCEPTION_POINTERS* info); #endif LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { #if defined(ADDRESS_SANITIZER) // In ASan builds, delegate to the ASan exception filter. LONG status = __asan_unhandled_exception_filter(exception_pointers); if (status != EXCEPTION_CONTINUE_SEARCH) return status; #endif if (BlockUntilHandlerStartedOrFailed() == StartupState::kFailed) { // If we know for certain that the handler has failed to start, then abort // here, rather than trying to signal to a handler that will never arrive, // and then sleeping unnecessarily. LOG(ERROR) << "crash server failed to launch, self-terminating"; SafeTerminateProcess(GetCurrentProcess(), kTerminationCodeCrashNoDump); return EXCEPTION_CONTINUE_SEARCH; } // Otherwise, we know the handler startup has succeeded, and we can continue. // Tracks whether a thread has already entered UnhandledExceptionHandler. static std::atomic have_crashed; // This is a per-process handler. While this handler is being invoked, other // threads are still executing as usual, so multiple threads could enter at // the same time. Because we're in a crashing state, we shouldn't be doing // anything that might cause allocations, call into kernel mode, etc. So, we // don't want to take a critical section here to avoid simultaneous access to // the global exception pointers in ExceptionInformation. Because the crash // handler will record all threads, it's fine to simply have the second and // subsequent entrants block here. They will soon be suspended by the crash // handler, and then the entire process will be terminated below. This means // that we won't save the exception pointers from the second and further // crashes, but contention here is very unlikely, and we'll still have a stack // that's blocked at this location. if (have_crashed.exchange(true)) { SleepEx(INFINITE, false); } // Otherwise, we're the first thread, so record the exception pointer and // signal the crash handler. g_crash_exception_information.thread_id = GetCurrentThreadId(); g_crash_exception_information.exception_pointers = FromPointerCast(exception_pointers); // Now signal the crash server, which will take a dump and then terminate us // when it's complete. SetEvent(g_signal_exception); // Time to wait for the handler to create a dump. constexpr DWORD kMillisecondsUntilTerminate = 60 * 1000; // Sleep for a while to allow it to process us. Eventually, we terminate // ourselves in case the crash server is gone, so that we don't leave zombies // around. This would ideally never happen. Sleep(kMillisecondsUntilTerminate); LOG(ERROR) << "crash server did not respond, self-terminating"; SafeTerminateProcess(GetCurrentProcess(), kTerminationCodeCrashNoDump); return EXCEPTION_CONTINUE_SEARCH; } #if !defined(ADDRESS_SANITIZER) LONG WINAPI HandleHeapCorruption(EXCEPTION_POINTERS* exception_pointers) { if (exception_pointers->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION) { return UnhandledExceptionHandler(exception_pointers); } return EXCEPTION_CONTINUE_SEARCH; } #endif void HandleAbortSignal(int signum) { DCHECK_EQ(signum, SIGABRT); CONTEXT context; CaptureContext(&context); EXCEPTION_RECORD record = {}; record.ExceptionCode = STATUS_FATAL_APP_EXIT; record.ExceptionFlags = EXCEPTION_NONCONTINUABLE; record.ExceptionAddress = ProgramCounterFromCONTEXT(&context); EXCEPTION_POINTERS exception_pointers; exception_pointers.ContextRecord = &context; exception_pointers.ExceptionRecord = &record; UnhandledExceptionHandler(&exception_pointers); } std::wstring FormatArgumentString(const std::string& name, const std::wstring& value) { return std::wstring(L"--") + base::UTF8ToWide(name) + L"=" + value; } struct ScopedProcThreadAttributeListTraits { static PPROC_THREAD_ATTRIBUTE_LIST InvalidValue() { return nullptr; } static void Free(PPROC_THREAD_ATTRIBUTE_LIST proc_thread_attribute_list) { // This is able to use GET_FUNCTION_REQUIRED() instead of GET_FUNCTION() // because it will only be called if InitializeProcThreadAttributeList() and // UpdateProcThreadAttribute() are present. static const auto delete_proc_thread_attribute_list = GET_FUNCTION_REQUIRED(L"kernel32.dll", ::DeleteProcThreadAttributeList); delete_proc_thread_attribute_list(proc_thread_attribute_list); } }; using ScopedProcThreadAttributeList = base::ScopedGeneric; bool IsInheritableHandle(HANDLE handle) { if (!handle || handle == INVALID_HANDLE_VALUE) return false; // File handles (FILE_TYPE_DISK) and pipe handles (FILE_TYPE_PIPE) are known // to be inheritable. Console handles (FILE_TYPE_CHAR) are not inheritable via // PROC_THREAD_ATTRIBUTE_HANDLE_LIST. See // https://crashpad.chromium.org/bug/77. DWORD handle_type = GetFileType(handle); return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE; } // Adds |handle| to |handle_list| if it appears valid, and is not already in // |handle_list|. // // Invalid handles (including INVALID_HANDLE_VALUE and null handles) cannot be // added to a PPROC_THREAD_ATTRIBUTE_LIST’s PROC_THREAD_ATTRIBUTE_HANDLE_LIST. // If INVALID_HANDLE_VALUE appears, CreateProcess() will fail with // ERROR_INVALID_PARAMETER. If a null handle appears, the child process will // silently not inherit any handles. // // Use this function to add handles with uncertain validities. void AddHandleToListIfValidAndInheritable(std::vector* handle_list, HANDLE handle) { // There doesn't seem to be any documentation of this, but if there's a handle // duplicated in this list, CreateProcess() fails with // ERROR_INVALID_PARAMETER. if (IsInheritableHandle(handle) && std::find(handle_list->begin(), handle_list->end(), handle) == handle_list->end()) { handle_list->push_back(handle); } } void AddUint32(std::vector* data_vector, uint32_t data) { data_vector->push_back(static_cast(data & 0xff)); data_vector->push_back(static_cast((data & 0xff00) >> 8)); data_vector->push_back(static_cast((data & 0xff0000) >> 16)); data_vector->push_back(static_cast((data & 0xff000000) >> 24)); } void AddUint64(std::vector* data_vector, uint64_t data) { AddUint32(data_vector, static_cast(data & 0xffffffffULL)); AddUint32(data_vector, static_cast((data & 0xffffffff00000000ULL) >> 32)); } //! \brief Creates a randomized pipe name to listen for client registrations //! on and returns its name. //! //! \param[out] pipe_name The pipe name that will be listened on. //! \param[out] pipe_handle The first pipe instance corresponding for the pipe. void CreatePipe(std::wstring* pipe_name, ScopedFileHANDLE* pipe_instance) { int tries = 5; std::string pipe_name_base = base::StringPrintf( #if defined(WINDOWS_UWP) "\\\\.\\pipe\\LOCAL\\crashpad_%lu_", #else "\\\\.\\pipe\\crashpad_%lu_", #endif GetCurrentProcessId()); do { *pipe_name = base::UTF8ToWide(pipe_name_base + RandomString()); pipe_instance->reset(CreateNamedPipeInstance(*pipe_name, true)); // CreateNamedPipe() is documented as setting the error to // ERROR_ACCESS_DENIED if FILE_FLAG_FIRST_PIPE_INSTANCE is specified and the // pipe name is already in use. However it may set the error to other codes // such as ERROR_PIPE_BUSY (if the pipe already exists and has reached its // maximum instance count) or ERROR_INVALID_PARAMETER (if the pipe already // exists and its attributes differ from those specified to // CreateNamedPipe()). Some of these errors may be ambiguous: for example, // ERROR_INVALID_PARAMETER may also occur if CreateNamedPipe() is called // incorrectly even in the absence of an existing pipe by the same name. // Rather than chasing down all of the possible errors that might indicate // that a pipe name is already in use, retry up to a few times on any error. } while (!pipe_instance->is_valid() && --tries); PCHECK(pipe_instance->is_valid()) << "CreateNamedPipe"; } struct BackgroundHandlerStartThreadData { BackgroundHandlerStartThreadData( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, const std::vector& attachments, const std::wstring& ipc_pipe, ScopedFileHANDLE ipc_pipe_handle) : handler(handler), database(database), metrics_dir(metrics_dir), url(url), annotations(annotations), arguments(arguments), attachments(attachments), ipc_pipe(ipc_pipe), ipc_pipe_handle(std::move(ipc_pipe_handle)) {} base::FilePath handler; base::FilePath database; base::FilePath metrics_dir; std::string url; std::map annotations; std::vector arguments; std::vector attachments; std::wstring ipc_pipe; ScopedFileHANDLE ipc_pipe_handle; }; // Ensures that SetHandlerStartupState() is called on scope exit. Assumes // failure, and on success, SetSuccessful() should be called. class ScopedCallSetHandlerStartupState { public: ScopedCallSetHandlerStartupState() : successful_(false) {} ScopedCallSetHandlerStartupState(const ScopedCallSetHandlerStartupState&) = delete; ScopedCallSetHandlerStartupState& operator=( const ScopedCallSetHandlerStartupState&) = delete; ~ScopedCallSetHandlerStartupState() { SetHandlerStartupState(successful_ ? StartupState::kSucceeded : StartupState::kFailed); } void SetSuccessful() { successful_ = true; } private: bool successful_; }; bool StartHandlerProcess( std::unique_ptr data) { CHECK(!IsThreadInLoaderLock()); ScopedCallSetHandlerStartupState scoped_startup_state_caller; std::wstring command_line; AppendCommandLineArgument(data->handler.value(), &command_line); for (const std::string& argument : data->arguments) { AppendCommandLineArgument(base::UTF8ToWide(argument), &command_line); } if (!data->database.value().empty()) { AppendCommandLineArgument( FormatArgumentString("database", data->database.value()), &command_line); } if (!data->metrics_dir.value().empty()) { AppendCommandLineArgument( FormatArgumentString("metrics-dir", data->metrics_dir.value()), &command_line); } if (!data->url.empty()) { AppendCommandLineArgument( FormatArgumentString("url", base::UTF8ToWide(data->url)), &command_line); } for (const auto& kv : data->annotations) { AppendCommandLineArgument( FormatArgumentString("annotation", base::UTF8ToWide(kv.first + '=' + kv.second)), &command_line); } for (const base::FilePath& attachment : data->attachments) { AppendCommandLineArgument( FormatArgumentString("attachment", attachment.value()), &command_line); } ScopedKernelHANDLE this_process( OpenProcess(kXPProcessAllAccess, true, GetCurrentProcessId())); if (!this_process.is_valid()) { PLOG(ERROR) << "OpenProcess"; return false; } InitialClientData initial_client_data( g_signal_exception, g_wer_registration.dump_without_crashing, g_wer_registration.dump_completed, data->ipc_pipe_handle.get(), this_process.get(), FromPointerCast(&g_crash_exception_information), FromPointerCast(&g_non_crash_exception_information), FromPointerCast(&g_critical_section_with_debug_info)); AppendCommandLineArgument( base::UTF8ToWide(std::string("--initial-client-data=") + initial_client_data.StringRepresentation()), &command_line); BOOL rv; DWORD creation_flags; STARTUPINFOEX startup_info = {}; startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK; startup_info.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup_info.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); startup_info.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); std::vector handle_list; std::unique_ptr proc_thread_attribute_list_storage; ScopedProcThreadAttributeList proc_thread_attribute_list_owner; static const auto initialize_proc_thread_attribute_list = GET_FUNCTION(L"kernel32.dll", ::InitializeProcThreadAttributeList); static const auto update_proc_thread_attribute = initialize_proc_thread_attribute_list ? GET_FUNCTION(L"kernel32.dll", ::UpdateProcThreadAttribute) : nullptr; if (!initialize_proc_thread_attribute_list || !update_proc_thread_attribute) { // The OS doesn’t allow handle inheritance to be restricted, so the handler // will inherit every inheritable handle. creation_flags = 0; startup_info.StartupInfo.cb = sizeof(startup_info.StartupInfo); } else { // Restrict handle inheritance to just those needed in the handler. creation_flags = EXTENDED_STARTUPINFO_PRESENT; startup_info.StartupInfo.cb = sizeof(startup_info); SIZE_T size; rv = initialize_proc_thread_attribute_list(nullptr, 1, 0, &size); if (rv) { LOG(ERROR) << "InitializeProcThreadAttributeList (size) succeeded, " "expected failure"; return false; } else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { PLOG(ERROR) << "InitializeProcThreadAttributeList (size)"; return false; } proc_thread_attribute_list_storage.reset(new uint8_t[size]); startup_info.lpAttributeList = reinterpret_cast( proc_thread_attribute_list_storage.get()); rv = initialize_proc_thread_attribute_list( startup_info.lpAttributeList, 1, 0, &size); if (!rv) { PLOG(ERROR) << "InitializeProcThreadAttributeList"; return false; } proc_thread_attribute_list_owner.reset(startup_info.lpAttributeList); handle_list.reserve(8); handle_list.push_back(g_signal_exception); handle_list.push_back(g_wer_registration.dump_without_crashing); handle_list.push_back(g_wer_registration.dump_completed); handle_list.push_back(data->ipc_pipe_handle.get()); handle_list.push_back(this_process.get()); AddHandleToListIfValidAndInheritable(&handle_list, startup_info.StartupInfo.hStdInput); AddHandleToListIfValidAndInheritable(&handle_list, startup_info.StartupInfo.hStdOutput); AddHandleToListIfValidAndInheritable(&handle_list, startup_info.StartupInfo.hStdError); rv = update_proc_thread_attribute( startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handle_list[0], handle_list.size() * sizeof(handle_list[0]), nullptr, nullptr); if (!rv) { PLOG(ERROR) << "UpdateProcThreadAttribute"; return false; } } // If the embedded crashpad handler is being started via an entry point in a // DLL (the handler executable is rundll32.exe), then don't pass // the application name to CreateProcess as this appears to generate an // invalid command line where the first argument needed by rundll32 is not in // the correct format as required in: // https://support.microsoft.com/en-ca/help/164787/info-windows-rundll-and-rundll32-interface const std::wstring_view kRunDll32Exe(L"rundll32.exe"); bool is_embedded_in_dll = false; if (data->handler.value().size() >= kRunDll32Exe.size() && _wcsicmp(data->handler.value() .substr(data->handler.value().size() - kRunDll32Exe.size()) .c_str(), kRunDll32Exe.data()) == 0) { is_embedded_in_dll = true; } PROCESS_INFORMATION process_info; rv = CreateProcess( is_embedded_in_dll ? nullptr : data->handler.value().c_str(), &command_line[0], nullptr, nullptr, true, creation_flags, nullptr, nullptr, &startup_info.StartupInfo, &process_info); if (!rv) { PLOG(ERROR) << "CreateProcess"; return false; } rv = CloseHandle(process_info.hThread); PLOG_IF(WARNING, !rv) << "CloseHandle thread"; rv = CloseHandle(process_info.hProcess); PLOG_IF(WARNING, !rv) << "CloseHandle process"; // It is important to close our side of the pipe here before confirming that // we can communicate with the server. By doing so, the only remaining copy of // the server side of the pipe belongs to the exception handler process we // just spawned. Otherwise, the pipe will continue to exist indefinitely, so // the connection loop will not detect that it will never be serviced. data->ipc_pipe_handle.reset(); // Confirm that the server is waiting for connections before continuing. ClientToServerMessage message = {}; message.type = ClientToServerMessage::kPing; ServerToClientMessage response = {}; if (!SendToCrashHandlerServer(data->ipc_pipe, message, &response)) { return false; } scoped_startup_state_caller.SetSuccessful(); return true; } DWORD WINAPI BackgroundHandlerStartThreadProc(void* data) { std::unique_ptr data_as_ptr( reinterpret_cast(data)); return StartHandlerProcess(std::move(data_as_ptr)) ? 0 : 1; } void CommonInProcessInitialization() { // We create this dummy CRITICAL_SECTION with the // RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO flag set to have an entry point // into the doubly-linked list of RTL_CRITICAL_SECTION_DEBUG objects. This // allows us to walk the list at crash time to gather data for !locks. A // debugger would instead inspect ntdll!RtlCriticalSectionList to get the head // of the list. But that is not an exported symbol, so on an arbitrary client // machine, we don't have a way of getting that pointer. InitializeCriticalSectionWithDebugInfoIfPossible( &g_critical_section_with_debug_info); g_non_crash_dump_lock = new base::Lock(); } } // namespace CrashpadClient::CrashpadClient() : ipc_pipe_(), handler_start_thread_(), vectored_handler_() {} CrashpadClient::~CrashpadClient() {} bool CrashpadClient::StartHandler( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, bool restartable, bool asynchronous_start, const std::vector& attachments) { DCHECK(ipc_pipe_.empty()); // Both the pipe and the signalling events have to be created on the main // thread (not the spawning thread) so that they're valid after we return from // this function. ScopedFileHANDLE ipc_pipe_handle; CreatePipe(&ipc_pipe_, &ipc_pipe_handle); SECURITY_ATTRIBUTES security_attributes = {0}; security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); security_attributes.bInheritHandle = true; g_signal_exception = CreateEvent(&security_attributes, false /* auto reset */, false, nullptr); g_wer_registration.dump_without_crashing = CreateEvent(&security_attributes, false /* auto reset */, false, nullptr); g_wer_registration.dump_completed = CreateEvent(&security_attributes, false /* auto reset */, false, nullptr); CommonInProcessInitialization(); RegisterHandlers(); auto data = new BackgroundHandlerStartThreadData(handler, database, metrics_dir, url, annotations, arguments, attachments, ipc_pipe_, std::move(ipc_pipe_handle)); if (asynchronous_start) { // It is important that the current thread not be synchronized with the // thread that is created here. StartHandler() needs to be callable inside a // DllMain(). In that case, the background thread will not start until the // current DllMain() completes, which would cause deadlock if it was waited // upon. handler_start_thread_.reset(CreateThread(nullptr, 0, &BackgroundHandlerStartThreadProc, reinterpret_cast(data), 0, nullptr)); if (!handler_start_thread_.is_valid()) { PLOG(ERROR) << "CreateThread"; SetHandlerStartupState(StartupState::kFailed); return false; } // In asynchronous mode, we can't report on the overall success or failure // of initialization at this point. return true; } else { return StartHandlerProcess( std::unique_ptr(data)); } } void CrashpadClient::RegisterHandlers() { SetUnhandledExceptionFilter(&UnhandledExceptionHandler); // Windows swallows heap corruption failures but we can intercept them with // a vectored exception handler. Note that a vectored exception handler is // not compatible with or generally helpful in ASAN builds (ASAN inserts a // bad dereference at the beginning of the handler, leading to recursive // invocation of the handler). #if !defined(ADDRESS_SANITIZER) PVOID handler = AddVectoredExceptionHandler(true, HandleHeapCorruption); vectored_handler_.reset(handler); #endif // The Windows CRT's signal.h lists: // - SIGINT // - SIGILL // - SIGFPE // - SIGSEGV // - SIGTERM // - SIGBREAK // - SIGABRT // SIGILL and SIGTERM are documented as not being generated. SIGBREAK and // SIGINT are for Ctrl-Break and Ctrl-C, and aren't something for which // capturing a dump is warranted. SIGFPE and SIGSEGV are captured as regular // exceptions through the unhandled exception filter. This leaves SIGABRT. In // the standard CRT, abort() is implemented as a synchronous call to the // SIGABRT signal handler if installed, but after doing so, the unhandled // exception filter is not triggered (it instead __fastfail()s). So, register // to handle SIGABRT to catch abort() calls, as client code might use this and // expect it to cause a crash dump. This will only work when the abort() // that's called in client code is the same (or has the same behavior) as the // one in use here. void (*rv)(int) = signal(SIGABRT, HandleAbortSignal); DCHECK_NE(rv, SIG_ERR); } bool CrashpadClient::SetHandlerIPCPipe(const std::wstring& ipc_pipe) { DCHECK(ipc_pipe_.empty()); DCHECK(!ipc_pipe.empty()); ipc_pipe_ = ipc_pipe; DCHECK(!ipc_pipe_.empty()); DCHECK_EQ(g_signal_exception, INVALID_HANDLE_VALUE); DCHECK_EQ(g_wer_registration.dump_without_crashing, INVALID_HANDLE_VALUE); DCHECK_EQ(g_wer_registration.dump_completed, INVALID_HANDLE_VALUE); DCHECK(!g_critical_section_with_debug_info.DebugInfo); DCHECK(!g_non_crash_dump_lock); ClientToServerMessage message; memset(&message, 0, sizeof(message)); message.type = ClientToServerMessage::kRegister; message.registration.version = RegistrationRequest::kMessageVersion; message.registration.client_process_id = GetCurrentProcessId(); message.registration.crash_exception_information = FromPointerCast(&g_crash_exception_information); message.registration.non_crash_exception_information = FromPointerCast(&g_non_crash_exception_information); CommonInProcessInitialization(); message.registration.critical_section_address = FromPointerCast(&g_critical_section_with_debug_info); ServerToClientMessage response = {}; if (!SendToCrashHandlerServer(ipc_pipe_, message, &response)) { return false; } SetHandlerStartupState(StartupState::kSucceeded); RegisterHandlers(); // The server returns these already duplicated to be valid in this process. g_signal_exception = IntToHandle(response.registration.request_crash_dump_event); g_wer_registration.dump_without_crashing = IntToHandle(response.registration.request_non_crash_dump_event); g_wer_registration.dump_completed = IntToHandle(response.registration.non_crash_dump_completed_event); return true; } std::wstring CrashpadClient::GetHandlerIPCPipe() const { DCHECK(!ipc_pipe_.empty()); return ipc_pipe_; } bool CrashpadClient::WaitForHandlerStart(unsigned int timeout_ms) { DCHECK(handler_start_thread_.is_valid()); DWORD result = WaitForSingleObject(handler_start_thread_.get(), timeout_ms); if (result == WAIT_TIMEOUT) { LOG(ERROR) << "WaitForSingleObject timed out"; return false; } else if (result == WAIT_ABANDONED) { LOG(ERROR) << "WaitForSingleObject abandoned"; return false; } else if (result != WAIT_OBJECT_0) { PLOG(ERROR) << "WaitForSingleObject"; return false; } DWORD exit_code; if (!GetExitCodeThread(handler_start_thread_.get(), &exit_code)) { PLOG(ERROR) << "GetExitCodeThread"; return false; } handler_start_thread_.reset(); return exit_code == 0; } bool CrashpadClient::RegisterWerModule(const std::wstring& path) { if (g_wer_registration.dump_completed == INVALID_HANDLE_VALUE || g_wer_registration.dump_without_crashing == INVALID_HANDLE_VALUE) { LOG(ERROR) << "not connected"; return false; } // We cannot point (*context).exception_pointers to our pointers yet as it // might get used for other non-crash dumps. g_wer_registration.crashpad_exception_info = &g_non_crash_exception_information; // we can point these as we are the only users. g_wer_registration.pointers.ExceptionRecord = &g_wer_registration.exception; g_wer_registration.pointers.ContextRecord = &g_wer_registration.context; HRESULT res = WerRegisterRuntimeExceptionModule(path.c_str(), &g_wer_registration); return res == S_OK; } // static void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) { if (g_wer_registration.dump_without_crashing == INVALID_HANDLE_VALUE || g_wer_registration.dump_completed == INVALID_HANDLE_VALUE) { LOG(ERROR) << "not connected"; return; } if (BlockUntilHandlerStartedOrFailed() == StartupState::kFailed) { // If we know for certain that the handler has failed to start, then abort // here, as we would otherwise wait indefinitely for the // g_wer_registration.dump_completed event that would never be signalled. LOG(ERROR) << "crash server failed to launch, no dump captured"; return; } // In the non-crashing case, we aren't concerned about avoiding calls into // Win32 APIs, so just use regular locking here in case of multiple threads // calling this function. If a crash occurs while we're in here, the worst // that can happen is that the server captures a partial dump for this path // because another thread’s crash processing finished and the process was // terminated before this thread’s non-crash processing could be completed. base::AutoLock lock(*g_non_crash_dump_lock); // Create a fake EXCEPTION_POINTERS to give the handler something to work // with. EXCEPTION_POINTERS exception_pointers = {}; // This is logically const, but EXCEPTION_POINTERS does not declare it as // const, so we have to cast that away from the argument. exception_pointers.ContextRecord = const_cast(&context); // We include a fake exception and use a code of '0x517a7ed' (something like // "simulated") so that it's relatively obvious in windbg that it's not // actually an exception. Most values in // https://msdn.microsoft.com/library/aa363082.aspx have some of the top // nibble set, so we make sure to pick a value that doesn't, so as to be // unlikely to conflict. constexpr uint32_t kSimulatedExceptionCode = 0x517a7ed; EXCEPTION_RECORD record = {}; record.ExceptionCode = kSimulatedExceptionCode; record.ExceptionAddress = ProgramCounterFromCONTEXT(&context); exception_pointers.ExceptionRecord = &record; g_non_crash_exception_information.thread_id = GetCurrentThreadId(); g_non_crash_exception_information.exception_pointers = FromPointerCast(&exception_pointers); g_wer_registration.in_dump_without_crashing = true; bool set_event_result = !!SetEvent(g_wer_registration.dump_without_crashing); PLOG_IF(ERROR, !set_event_result) << "SetEvent"; DWORD wfso_result = WaitForSingleObject(g_wer_registration.dump_completed, INFINITE); PLOG_IF(ERROR, wfso_result != WAIT_OBJECT_0) << "WaitForSingleObject"; g_wer_registration.in_dump_without_crashing = false; } // static void CrashpadClient::DumpAndCrash(EXCEPTION_POINTERS* exception_pointers) { if (g_signal_exception == INVALID_HANDLE_VALUE) { LOG(ERROR) << "not connected"; SafeTerminateProcess(GetCurrentProcess(), kTerminationCodeNotConnectedToHandler); return; } // We don't need to check for handler startup here, as // UnhandledExceptionHandler() necessarily does that. UnhandledExceptionHandler(exception_pointers); } // static bool CrashpadClient::DumpAndCrashTargetProcess(HANDLE process, HANDLE blame_thread, DWORD exception_code) { // Confirm we're on Vista or later. const DWORD version = GetVersion(); const DWORD major_version = LOBYTE(LOWORD(version)); if (major_version < 6) { LOG(ERROR) << "unavailable before Vista"; return false; } // Confirm that our bitness is the same as the process we're crashing. ProcessInfo process_info; if (!process_info.Initialize(process)) { LOG(ERROR) << "ProcessInfo::Initialize"; return false; } #if defined(ARCH_CPU_64_BITS) if (!process_info.Is64Bit()) { LOG(ERROR) << "DumpAndCrashTargetProcess currently not supported x64->x86"; return false; } #endif // ARCH_CPU_64_BITS ScopedProcessSuspend suspend(process); // If no thread handle was provided, or the thread has already exited, we pass // 0 to the handler, which indicates no fake exception record to be created. DWORD thread_id = 0; if (blame_thread) { // Now that we've suspended the process, if our thread hasn't exited, we // know we're relatively safe to pass the thread id through. if (WaitForSingleObject(blame_thread, 0) == WAIT_TIMEOUT) { static const auto get_thread_id = GET_FUNCTION_REQUIRED(L"kernel32.dll", ::GetThreadId); thread_id = get_thread_id(blame_thread); } } constexpr size_t kInjectBufferSize = 4 * 1024; WinVMAddress inject_memory = FromPointerCast(VirtualAllocEx(process, nullptr, kInjectBufferSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); if (!inject_memory) { PLOG(ERROR) << "VirtualAllocEx"; return false; } // Because we're the same bitness as our target, we can rely kernel32 being // loaded at the same address in our process as the target, and just look up // its address here. WinVMAddress raise_exception_address = FromPointerCast(&RaiseException); WinVMAddress code_entry_point = 0; std::vector data_to_write; if (process_info.Is64Bit()) { // Data written is first, the data for the 4th argument (lpArguments) to // RaiseException(). A two element array: // // DWORD64: thread_id // DWORD64: exception_code // // Following that, code which sets the arguments to RaiseException() and // then calls it: // // mov r9, // mov r8d, 2 ; nNumberOfArguments // mov edx, 1 ; dwExceptionFlags = EXCEPTION_NONCONTINUABLE // mov ecx, 0xcca11ed ; dwExceptionCode, interpreted specially by the // ; handler. // jmp // // Note that the first three arguments to RaiseException() are DWORDs even // on x64, so only the 4th argument (a pointer) is a full-width register. // // We also don't need to set up a stack or use call, since the only // registers modified are volatile ones, and we can just jmp straight to // RaiseException(). // The data array. AddUint64(&data_to_write, thread_id); AddUint64(&data_to_write, exception_code); // The thread entry point. code_entry_point = inject_memory + data_to_write.size(); // r9 = pointer to data. data_to_write.push_back(0x49); data_to_write.push_back(0xb9); AddUint64(&data_to_write, inject_memory); // r8d = 2 for nNumberOfArguments. data_to_write.push_back(0x41); data_to_write.push_back(0xb8); AddUint32(&data_to_write, 2); // edx = 1 for dwExceptionFlags. data_to_write.push_back(0xba); AddUint32(&data_to_write, 1); // ecx = kTriggeredExceptionCode for dwExceptionCode. data_to_write.push_back(0xb9); AddUint32(&data_to_write, ExceptionCodes::kTriggeredExceptionCode); // jmp to RaiseException() via rax. data_to_write.push_back(0x48); // mov rax, imm. data_to_write.push_back(0xb8); AddUint64(&data_to_write, raise_exception_address); data_to_write.push_back(0xff); // jmp rax. data_to_write.push_back(0xe0); } else { // Data written is first, the data for the 4th argument (lpArguments) to // RaiseException(). A two element array: // // DWORD: thread_id // DWORD: exception_code // // Following that, code which pushes our arguments to RaiseException() and // then calls it: // // push // push 2 ; nNumberOfArguments // push 1 ; dwExceptionFlags = EXCEPTION_NONCONTINUABLE // push 0xcca11ed ; dwExceptionCode, interpreted specially by the handler. // call // ud2 ; Generate invalid opcode to make sure we still crash if we return // ; for some reason. // // No need to clean up the stack, as RaiseException() is __stdcall. // The data array. AddUint32(&data_to_write, thread_id); AddUint32(&data_to_write, exception_code); // The thread entry point. code_entry_point = inject_memory + data_to_write.size(); // Push data address. data_to_write.push_back(0x68); AddUint32(&data_to_write, static_cast(inject_memory)); // Push 2 for nNumberOfArguments. data_to_write.push_back(0x6a); data_to_write.push_back(2); // Push 1 for dwExceptionCode. data_to_write.push_back(0x6a); data_to_write.push_back(1); // Push dwExceptionFlags. data_to_write.push_back(0x68); AddUint32(&data_to_write, kTriggeredExceptionCode); // Relative call to RaiseException(). int64_t relative_address_to_raise_exception = raise_exception_address - (inject_memory + data_to_write.size() + 5); data_to_write.push_back(0xe8); AddUint32(&data_to_write, static_cast(relative_address_to_raise_exception)); // ud2. data_to_write.push_back(0x0f); data_to_write.push_back(0x0b); } DCHECK_LT(data_to_write.size(), kInjectBufferSize); SIZE_T bytes_written; if (!WriteProcessMemory(process, reinterpret_cast(inject_memory), data_to_write.data(), data_to_write.size(), &bytes_written)) { PLOG(ERROR) << "WriteProcessMemory"; return false; } if (bytes_written != data_to_write.size()) { LOG(ERROR) << "WriteProcessMemory unexpected number of bytes"; return false; } if (!FlushInstructionCache( process, reinterpret_cast(inject_memory), bytes_written)) { PLOG(ERROR) << "FlushInstructionCache"; return false; } DWORD old_protect; if (!VirtualProtectEx(process, reinterpret_cast(inject_memory), kInjectBufferSize, PAGE_EXECUTE_READ, &old_protect)) { PLOG(ERROR) << "VirtualProtectEx"; return false; } // Cause an exception in the target process by creating a thread which calls // RaiseException with our arguments above. Note that we cannot get away with // using DebugBreakProcess() (nothing happens unless a debugger is attached) // and we cannot get away with CreateRemoteThread() because it doesn't work if // the target is hung waiting for the loader lock. We use NtCreateThreadEx() // with the SKIP_THREAD_ATTACH flag, which skips various notifications, // letting this cause an exception, even when the target is stuck in the // loader lock. HANDLE injected_thread; // This is what DebugBreakProcess() uses. constexpr size_t kStackSize = 0x4000; NTSTATUS status = NtCreateThreadEx(&injected_thread, STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL, nullptr, process, reinterpret_cast(code_entry_point), nullptr, THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH, 0, kStackSize, 0, nullptr); if (!NT_SUCCESS(status)) { NTSTATUS_LOG(ERROR, status) << "NtCreateThreadEx"; return false; } // The injected thread raises an exception and ultimately results in process // termination. The suspension must be made aware that the process may be // terminating, otherwise it’ll log an extraneous error. suspend.TolerateTermination(); bool result = true; if (WaitForSingleObject(injected_thread, 60 * 1000) != WAIT_OBJECT_0) { PLOG(ERROR) << "WaitForSingleObject"; result = false; } status = NtClose(injected_thread); if (!NT_SUCCESS(status)) { NTSTATUS_LOG(ERROR, status) << "NtClose"; result = false; } return result; } } // namespace crashpad ================================================ FILE: client/crashpad_client_win_test.cc ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crashpad_client.h" #include #include "base/files/file_path.h" #include "gtest/gtest.h" #include "test/test_paths.h" #include "test/scoped_temp_dir.h" #include "test/win/win_multiprocess.h" #include "test/win/win_multiprocess_with_temp_dir.h" #include "util/win/scoped_handle.h" #include "util/win/termination_codes.h" namespace crashpad { namespace test { namespace { void StartAndUseHandler(const base::FilePath& temp_dir) { base::FilePath handler_path = TestPaths::Executable().DirName().Append( FILE_PATH_LITERAL("crashpad_handler.com")); CrashpadClient client; ASSERT_TRUE(client.StartHandler(handler_path, temp_dir, base::FilePath(), "", std::map(), std::vector(), true, true)); ASSERT_TRUE(client.WaitForHandlerStart(INFINITE)); } class StartWithInvalidHandles final : public WinMultiprocessWithTempDir { public: StartWithInvalidHandles() : WinMultiprocessWithTempDir() {} ~StartWithInvalidHandles() {} private: void WinMultiprocessParent() override {} void WinMultiprocessChild() override { HANDLE original_stdout = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE original_stderr = GetStdHandle(STD_ERROR_HANDLE); SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); StartAndUseHandler(GetTempDirPath()); SetStdHandle(STD_OUTPUT_HANDLE, original_stdout); SetStdHandle(STD_ERROR_HANDLE, original_stderr); } }; TEST(CrashpadClient, StartWithInvalidHandles) { WinMultiprocessWithTempDir::Run(); } class StartWithSameStdoutStderr final : public WinMultiprocessWithTempDir { public: StartWithSameStdoutStderr() : WinMultiprocessWithTempDir() {} ~StartWithSameStdoutStderr() {} private: void WinMultiprocessParent() override {} void WinMultiprocessChild() override { HANDLE original_stdout = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE original_stderr = GetStdHandle(STD_ERROR_HANDLE); SetStdHandle(STD_OUTPUT_HANDLE, original_stderr); StartAndUseHandler(GetTempDirPath()); SetStdHandle(STD_OUTPUT_HANDLE, original_stdout); } }; TEST(CrashpadClient, StartWithSameStdoutStderr) { WinMultiprocessWithTempDir::Run(); } void StartAndUseBrokenHandler(CrashpadClient* client) { ScopedTempDir temp_dir; base::FilePath handler_path = TestPaths::Executable().DirName().Append( FILE_PATH_LITERAL("fake_handler_that_crashes_at_startup.exe")); ASSERT_TRUE(client->StartHandler(handler_path, temp_dir.path(), base::FilePath(), "", std::map(), std::vector(), false, true)); } class HandlerLaunchFailureCrash : public WinMultiprocess { public: HandlerLaunchFailureCrash() : WinMultiprocess() {} private: void WinMultiprocessParent() override { SetExpectedChildExitCode(crashpad::kTerminationCodeCrashNoDump); } void WinMultiprocessChild() override { CrashpadClient client; StartAndUseBrokenHandler(&client); __debugbreak(); exit(0); } }; #if defined(ADDRESS_SANITIZER) // https://crbug.com/845011 #define MAYBE_HandlerLaunchFailureCrash DISABLED_HandlerLaunchFailureCrash #else #define MAYBE_HandlerLaunchFailureCrash HandlerLaunchFailureCrash #endif TEST(CrashpadClient, MAYBE_HandlerLaunchFailureCrash) { WinMultiprocess::Run(); } class HandlerLaunchFailureDumpAndCrash : public WinMultiprocess { public: HandlerLaunchFailureDumpAndCrash() : WinMultiprocess() {} private: void WinMultiprocessParent() override { SetExpectedChildExitCode(crashpad::kTerminationCodeCrashNoDump); } void WinMultiprocessChild() override { CrashpadClient client; StartAndUseBrokenHandler(&client); // We don't need to fill this out as we're only checking that we're // terminated with the correct failure code. EXCEPTION_POINTERS info = {}; client.DumpAndCrash(&info); exit(0); } }; #if defined(ADDRESS_SANITIZER) // https://crbug.com/845011 #define MAYBE_HandlerLaunchFailureDumpAndCrash \ DISABLED_HandlerLaunchFailureDumpAndCrash #else #define MAYBE_HandlerLaunchFailureDumpAndCrash HandlerLaunchFailureDumpAndCrash #endif TEST(CrashpadClient, MAYBE_HandlerLaunchFailureDumpAndCrash) { WinMultiprocess::Run(); } class HandlerLaunchFailureDumpWithoutCrash : public WinMultiprocess { public: HandlerLaunchFailureDumpWithoutCrash() : WinMultiprocess() {} private: void WinMultiprocessParent() override { // DumpWithoutCrash() normally blocks indefinitely. There's no return value, // but confirm that it exits cleanly because it'll return right away if the // handler didn't start. SetExpectedChildExitCode(0); } void WinMultiprocessChild() override { CrashpadClient client; StartAndUseBrokenHandler(&client); // We don't need to fill this out as we're only checking that we're // terminated with the correct failure code. CONTEXT context = {}; client.DumpWithoutCrash(context); exit(0); } }; TEST(CrashpadClient, HandlerLaunchFailureDumpWithoutCrash) { WinMultiprocess::Run(); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/crashpad_info.cc ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crashpad_info.h" #include #include "base/numerics/safe_conversions.h" #include "build/build_config.h" #include "util/misc/address_sanitizer.h" #include "util/misc/from_pointer_cast.h" #if BUILDFLAG(IS_APPLE) #include #endif namespace { // Don’t change this when simply adding fields. Readers will size-check the // structure and ignore fields they’re aware of when not present, as well as // fields they’re not aware of. Only change this when introducing an // incompatible layout, with the understanding that existing readers will not // understand new versions. constexpr uint32_t kCrashpadInfoVersion = 1; // Creates a `UserDataMinidumpStreamListEntry` with the given fields, and // returns a pointer to it. The caller takes ownership of the returned object. crashpad::internal::UserDataMinidumpStreamListEntry* CreateListEntry( uint64_t next, uint32_t stream_type, const void* data, size_t size) { auto to_be_added = new crashpad::internal::UserDataMinidumpStreamListEntry(); to_be_added->next = next; to_be_added->stream_type = stream_type; to_be_added->base_address = crashpad::FromPointerCast(data); to_be_added->size = base::checked_cast(size); return to_be_added; } } // namespace namespace crashpad { static_assert(std::is_standard_layout::value, "CrashpadInfo must be standard layout"); // This structure needs to be stored somewhere that is easy to find without // external information. // // It isn’t placed in an unnamed namespace: hopefully, this will catch attempts // to place multiple copies of this structure into the same module. If that’s // attempted, and the name of the symbol is the same in each translation unit, // it will result in a linker error, which is better than having multiple // structures show up. // // This may result in a static module initializer in debug-mode builds, but // because it’s POD, no code should need to run to initialize this under // release-mode optimization. #if BUILDFLAG(IS_POSIX) __attribute__(( #if BUILDFLAG(IS_APPLE) // Put the structure in a well-known section name where it can be easily // found without having to consult the symbol table. section(SEG_DATA ",crashpad_info"), #endif #if defined(ADDRESS_SANITIZER) // AddressSanitizer would add a trailing red zone of at least 32 bytes, // which would be reflected in the size of the custom section. This confuses // MachOImageReader::GetCrashpadInfo(), which finds that the section’s size // disagrees with the structure’s size_ field. By specifying an alignment // greater than the red zone size, the red zone will be suppressed. aligned(64), #endif // defined(ADDRESS_SANITIZER) // There's no need to expose this as a public symbol from the symbol table. // All accesses from the outside can locate the well-known section name. visibility("hidden"), // The “used” attribute prevents the structure from being dead-stripped. used)) #elif BUILDFLAG(IS_WIN) // Put the struct in a section name CPADinfo where it can be found without the // symbol table. #pragma section("CPADinfo", read, write) __declspec(allocate("CPADinfo")) #else // !BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_WIN) #error Port #endif // !BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_WIN) CrashpadInfo g_crashpad_info; extern "C" int* CRASHPAD_NOTE_REFERENCE; // static CrashpadInfo* CrashpadInfo::GetCrashpadInfo() { #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) || \ BUILDFLAG(IS_FUCHSIA) // This otherwise-unused reference is used so that any module that // references GetCrashpadInfo() will also include the note in the // .note.crashpad.info section. That note in turn contains the address of // g_crashpad_info. This allows the module reader to find the CrashpadInfo // structure without requiring the use of the dynamic symbol table. static volatile int* pointer_to_note_section = CRASHPAD_NOTE_REFERENCE; (void)pointer_to_note_section; #endif return &g_crashpad_info; } CrashpadInfo::CrashpadInfo() : signature_(kSignature), size_(sizeof(*this)), version_(kCrashpadInfoVersion), indirectly_referenced_memory_cap_(0), padding_0_(0), crashpad_handler_behavior_(TriState::kUnset), system_crash_reporter_forwarding_(TriState::kUnset), gather_indirectly_referenced_memory_(TriState::kUnset), padding_1_(0), extra_memory_ranges_(nullptr), simple_annotations_(nullptr), user_data_minidump_stream_head_(nullptr), annotations_list_(nullptr) #if BUILDFLAG(IS_IOS) , intermediate_dump_extra_memory_ranges_(nullptr) #endif { } UserDataMinidumpStreamHandle* CrashpadInfo::AddUserDataMinidumpStream( uint32_t stream_type, const void* data, size_t size) { user_data_minidump_stream_head_ = CreateListEntry( crashpad::FromPointerCast(user_data_minidump_stream_head_), stream_type, data, size); return user_data_minidump_stream_head_; } UserDataMinidumpStreamHandle* CrashpadInfo::UpdateUserDataMinidumpStream( UserDataMinidumpStreamHandle* stream_to_update, uint32_t stream_type, const void* data, size_t size) { // Create a new stream that points to the node `stream_to_update` points to. const auto new_stream = CreateListEntry(stream_to_update->next, stream_type, data, size); // If `stream_to_update` is head of the list, replace the head with // `new_stream`. if (stream_to_update == user_data_minidump_stream_head_) { user_data_minidump_stream_head_ = new_stream; } else { // Otherwise, find the node before `stream_to_update`, and make it point to // `new_stream` instead. auto current = user_data_minidump_stream_head_; while (current) { auto next = reinterpret_cast( current->next); if (next == stream_to_update) { current->next = FromPointerCast(new_stream); break; } current = next; } CHECK(current) << "Tried to update a UserDataMinidumpStream that doesn't exist"; } delete stream_to_update; return new_stream; } } // namespace crashpad ================================================ FILE: client/crashpad_info.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_CRASHPAD_INFO_H_ #define CRASHPAD_CLIENT_CRASHPAD_INFO_H_ #include #include "build/build_config.h" #include "client/annotation_list.h" #include "client/simple_address_range_bag.h" #include "client/simple_string_dictionary.h" #include "util/misc/tri_state.h" #if BUILDFLAG(IS_WIN) #include #endif // BUILDFLAG(IS_WIN) namespace crashpad { namespace internal { #if BUILDFLAG(IS_IOS) class InProcessIntermediateDumpHandler; #endif //! \brief A linked list of blocks representing custom streams in the minidump, //! with addresses (and size) stored as uint64_t to simplify reading from //! the handler process. struct UserDataMinidumpStreamListEntry { //! \brief The address of the next entry in the linked list. uint64_t next; //! \brief The base address of the memory block in the target process' address //! space that represents the user data stream. uint64_t base_address; //! \brief The size of memory block in the target process' address space that //! represents the user data stream. uint64_t size; //! \brief The stream type identifier. uint32_t stream_type; }; } // namespace internal using UserDataMinidumpStreamHandle = internal::UserDataMinidumpStreamListEntry; //! \brief A structure that can be used by a Crashpad-enabled program to //! provide information to the Crashpad crash handler. //! //! It is possible for one CrashpadInfo structure to appear in each loaded code //! module in a process, but from the perspective of the user of the client //! interface, there is only one global CrashpadInfo structure, located in the //! module that contains the client interface code. struct CrashpadInfo { public: //! \brief Returns the global CrashpadInfo structure. static CrashpadInfo* GetCrashpadInfo(); CrashpadInfo(); CrashpadInfo(const CrashpadInfo&) = delete; CrashpadInfo& operator=(const CrashpadInfo&) = delete; //! \brief Sets the bag of extra memory ranges to be included in the snapshot. //! //! Extra memory ranges may exist in \a address_range_bag at the time that //! this method is called, or they may be added, removed, or modified in \a //! address_range_bag after this method is called. //! //! TODO(scottmg) This is currently only supported on Windows and iOS. //! //! \param[in] address_range_bag A bag of address ranges. The CrashpadInfo //! object does not take ownership of the SimpleAddressRangeBag object. //! It is the caller’s responsibility to ensure that this pointer remains //! valid while it is in effect for a CrashpadInfo object. //! //! \sa extra_memory_ranges() void set_extra_memory_ranges(SimpleAddressRangeBag* address_range_bag) { extra_memory_ranges_ = address_range_bag; } //! \return The simple extra memory ranges SimpleAddressRangeBag object. //! //! \sa set_extra_memory_ranges() SimpleAddressRangeBag* extra_memory_ranges() const { return extra_memory_ranges_; } #if BUILDFLAG(IS_IOS) //! \brief Sets the bag of extra memory ranges to be included in the iOS //! intermediate dump. This memory is not included in the minidump. //! //! Extra memory ranges may exist in \a address_range_bag at the time that //! this method is called, or they may be added, removed, or modified in \a //! address_range_bag after this method is called. //! //! This is only supported on iOS. //! //! \param[in] address_range_bag A bag of address ranges. The CrashpadInfo //! object does not take ownership of the SimpleAddressRangeBag object. //! It is the caller’s responsibility to ensure that this pointer remains //! valid while it is in effect for a CrashpadInfo object. //! //! \sa extra_memory_ranges() void set_intermediate_dump_extra_memory_ranges( SimpleAddressRangeBag* address_range_bag) { intermediate_dump_extra_memory_ranges_ = address_range_bag; } //! \return The simple extra memory ranges SimpleAddressRangeBag object. //! //! \sa set_extra_memory_ranges() SimpleAddressRangeBag* intermediate_dump_extra_memory_ranges() const { return intermediate_dump_extra_memory_ranges_; } #endif //! \brief Sets the simple annotations dictionary. //! //! Simple annotations set on a CrashpadInfo structure are interpreted by //! Crashpad as module-level annotations. //! //! Annotations may exist in \a simple_annotations at the time that this //! method is called, or they may be added, removed, or modified in \a //! simple_annotations after this method is called. //! //! \param[in] simple_annotations A dictionary that maps string keys to string //! values. The CrashpadInfo object does not take ownership of the //! SimpleStringDictionary object. It is the caller’s responsibility to //! ensure that this pointer remains valid while it is in effect for a //! CrashpadInfo object. //! //! \sa simple_annotations() void set_simple_annotations(SimpleStringDictionary* simple_annotations) { simple_annotations_ = simple_annotations; } //! \return The simple annotations dictionary. //! //! \sa set_simple_annotations() SimpleStringDictionary* simple_annotations() const { return simple_annotations_; } //! \brief Sets the annotations list. //! //! Unlike the \a simple_annotations structure, the \a annotations can //! typed data and it is not limited to a dictionary form. Annotations are //! interpreted by Crashpad as module-level annotations. //! //! Annotations may exist in \a list at the time that this method is called, //! or they may be added, removed, or modified in \a list after this method is //! called. //! //! \param[in] list A list of set Annotation objects that maintain arbitrary, //! typed key-value state. The CrashpadInfo object does not take ownership //! of the AnnotationsList object. It is the caller’s responsibility to //! ensure that this pointer remains valid while it is in effect for a //! CrashpadInfo object. //! //! \sa annotations_list() //! \sa AnnotationList::Register() void set_annotations_list(AnnotationList* list) { annotations_list_ = list; } //! \return The annotations list. //! //! \sa set_annotations_list() //! \sa AnnotationList::Get() //! \sa AnnotationList::Register() AnnotationList* annotations_list() const { return annotations_list_; } //! \brief Enables or disables Crashpad handler processing. //! //! When handling an exception, the Crashpad handler will scan all modules in //! a process. The first one that has a CrashpadInfo structure populated with //! a value other than TriState::kUnset for this field will dictate whether //! the handler is functional or not. If all modules with a CrashpadInfo //! structure specify TriState::kUnset, the handler will be enabled. If //! disabled, the Crashpad handler will still run and receive exceptions, but //! will not take any action on an exception on its own behalf, except for the //! action necessary to determine that it has been disabled. //! //! The Crashpad handler should not normally be disabled. More commonly, it is //! appropriate to disable crash report upload by calling //! Settings::SetUploadsEnabled(). void set_crashpad_handler_behavior(TriState crashpad_handler_behavior) { crashpad_handler_behavior_ = crashpad_handler_behavior; } //! \brief Enables or disables Crashpad forwarding of exceptions to the //! system’s crash reporter. //! //! When handling an exception, the Crashpad handler will scan all modules in //! a process. The first one that has a CrashpadInfo structure populated with //! a value other than TriState::kUnset for this field will dictate whether //! the exception is forwarded to the system’s crash reporter. If all modules //! with a CrashpadInfo structure specify TriState::kUnset, forwarding will be //! enabled. Unless disabled, forwarding may still occur if the Crashpad //! handler is disabled by SetCrashpadHandlerState(). Even when forwarding is //! enabled, the Crashpad handler may choose not to forward all exceptions to //! the system’s crash reporter in cases where it has reason to believe that //! the system’s crash reporter would not normally have handled the exception //! in Crashpad’s absence. void set_system_crash_reporter_forwarding( TriState system_crash_reporter_forwarding) { system_crash_reporter_forwarding_ = system_crash_reporter_forwarding; } //! \brief Enables or disables Crashpad capturing indirectly referenced memory //! in the minidump. //! //! When handling an exception, the Crashpad handler will scan all modules in //! a process. The first one that has a CrashpadInfo structure populated with //! a value other than TriState::kUnset for this field will dictate whether //! the extra memory is captured. //! //! This causes Crashpad to include pages of data referenced by locals or //! other stack memory. Turning this on can increase the size of the minidump //! significantly. //! //! \param[in] gather_indirectly_referenced_memory Whether extra memory should //! be gathered. //! \param[in] limit The amount of memory in bytes after which no more //! indirectly gathered memory should be captured. This value is only used //! when \a gather_indirectly_referenced_memory is TriState::kEnabled. void set_gather_indirectly_referenced_memory( TriState gather_indirectly_referenced_memory, uint32_t limit) { gather_indirectly_referenced_memory_ = gather_indirectly_referenced_memory; indirectly_referenced_memory_cap_ = limit; } //! \brief Adds a custom stream to the minidump. //! //! The memory block referenced by \a data and \a size will added to the //! minidump as separate stream with type \a stream_type. The memory referred //! to by \a data and \a size is owned by the caller and must remain valid //! while it is in effect for the CrashpadInfo object. //! //! Note that streams will appear in the minidump in the reverse order to //! which they are added. //! //! TODO(scottmg) This is currently not supported on Mac. //! //! \param[in] stream_type The stream type identifier to use. This should be //! normally be larger than `MINIDUMP_STREAM_TYPE::LastReservedStream` //! which is `0xffff`. //! \param[in] data The base pointer of the stream data. //! \param[in] size The size of the stream data. //! \return A handle to the added stream, for use in calling //! UpdateUserDataMinidumpStream() if needed. UserDataMinidumpStreamHandle* AddUserDataMinidumpStream(uint32_t stream_type, const void* data, size_t size); //! \brief Replaces the given stream with an updated stream. //! //! Creates a new memory block referencing the given \a data and \a size with //! type \a stream_type. The memory referred to be \a data and \a size is //! owned by the caller and must remain valid while it is in effect for the //! CrashpadInfo object. //! //! Frees \a stream_to_update and returns a new handle to the updated stream. //! //! \param[in] stream_to_update A handle to the stream to be updated, received //! from either AddUserDataMinidumpStream() or previous calls to this //! function. //! \param[in] stream_type The stream type identifier to use. This should be //! normally be larger than `MINIDUMP_STREAM_TYPE::LastReservedStream` //! which is `0xffff`. //! \param[in] data The base pointer of the stream data. //! \param[in] size The size of the stream data. //! \return A handle to the new memory block that references the updated data, //! for use in calling this method again if needed. UserDataMinidumpStreamHandle* UpdateUserDataMinidumpStream( UserDataMinidumpStreamHandle* stream_to_update, uint32_t stream_type, const void* data, size_t size); internal::UserDataMinidumpStreamListEntry* GetUserDataMinidumpStreamHeadForTesting() { return user_data_minidump_stream_head_; } enum : uint32_t { kSignature = 'CPad', }; protected: #if BUILDFLAG(IS_IOS) friend class internal::InProcessIntermediateDumpHandler; #endif uint32_t signature() const { return signature_; } uint32_t version() const { return version_; } uint32_t size() const { return size_; } private: // The compiler won’t necessarily see anyone using these fields, but it // shouldn’t warn about that. These fields aren’t intended for use by the // process they’re found in, they’re supposed to be read by the crash // reporting process. #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-private-field" #endif // Fields present in version 1, subject to a check of the size_ field: uint32_t signature_; // kSignature uint32_t size_; // The size of the entire CrashpadInfo structure. uint32_t version_; // kCrashpadInfoVersion uint32_t indirectly_referenced_memory_cap_; uint32_t padding_0_; TriState crashpad_handler_behavior_; TriState system_crash_reporter_forwarding_; TriState gather_indirectly_referenced_memory_; uint8_t padding_1_; SimpleAddressRangeBag* extra_memory_ranges_; // weak SimpleStringDictionary* simple_annotations_; // weak internal::UserDataMinidumpStreamListEntry* user_data_minidump_stream_head_; AnnotationList* annotations_list_; // weak #if BUILDFLAG(IS_IOS) SimpleAddressRangeBag* intermediate_dump_extra_memory_ranges_; // weak #endif // It’s generally safe to add new fields without changing // kCrashpadInfoVersion, because readers should check size_ and ignore fields // that aren’t present, as well as unknown fields. // // Adding fields? Consider snapshot/crashpad_info_size_test_module.cc too. #if defined(__clang__) #pragma clang diagnostic pop #endif }; } // namespace crashpad #endif // CRASHPAD_CLIENT_CRASHPAD_INFO_H_ ================================================ FILE: client/crashpad_info_note.S ================================================ // Copyright 2018 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This note section is used on ELF platforms to give ElfImageReader a method // of finding the instance of CrashpadInfo g_crashpad_info without requiring // that symbol to be in the dynamic symbol table. #include "util/misc/elf_note_types.h" #include "util/misc/arm64_pac_bti.S" // namespace crashpad { // CrashpadInfo g_crashpad_info; // } // namespace crashpad #define CRASHPAD_INFO_SYMBOL _ZN8crashpad15g_crashpad_infoE #define NOTE_ALIGN 4 // This section must be "a"llocated so that it appears in the final binary at // runtime. The reference to CRASHPAD_INFO_SYMBOL uses an offset relative to // this note to avoid making this note writable, which triggers a bug in GNU // ld, or adding text relocations which require the target system to allow // making text segments writable. https://crbug.com/crashpad/260. .section .note.crashpad.info,"a",%note .balign NOTE_ALIGN CRASHPAD_NOTE: .long name_end - name // namesz .long desc_end - desc // descsz .long CRASHPAD_ELF_NOTE_TYPE_CRASHPAD_INFO // type name: .asciz CRASHPAD_ELF_NOTE_NAME name_end: .balign NOTE_ALIGN desc: #if defined(__LP64__) .quad CRASHPAD_INFO_SYMBOL - desc #else .long CRASHPAD_INFO_SYMBOL - desc #endif // __LP64__ desc_end: .size CRASHPAD_NOTE, .-CRASHPAD_NOTE // CRASHPAD_NOTE can't be referenced directly by GetCrashpadInfo() because the // relocation used to make the reference may require that the address be // 8-byte aligned and notes must have 4-byte alignment. .section .rodata,"a",%progbits .balign 8 # .globl indicates that it's available to link against other .o files. .hidden # indicates that it will not appear in the executable's symbol table. .globl CRASHPAD_NOTE_REFERENCE .hidden CRASHPAD_NOTE_REFERENCE .type CRASHPAD_NOTE_REFERENCE, %object CRASHPAD_NOTE_REFERENCE: // The value of this quad isn't important. It exists to reference // CRASHPAD_NOTE, causing the linker to include the note into the binary // linking Crashpad. The subtraction from |name| is a convenience to allow the // value to be computed statically. .quad name - CRASHPAD_NOTE ================================================ FILE: client/crashpad_info_test.cc ================================================ // Copyright 2024 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crashpad_info.h" #include #include "gtest/gtest.h" namespace crashpad { namespace test { namespace { constexpr uint32_t kTestStreamType = 0x33333; class CrashpadInfoTest : public testing::Test { protected: CrashpadInfo& crashpad_info() { return crashpad_info_; } // Returns the current head of the list of streams in `crashpad_info_`. Note // that the returned pointer is invalidated if a stream is added or updated. internal::UserDataMinidumpStreamListEntry* GetCurrentHead() { return crashpad_info().GetUserDataMinidumpStreamHeadForTesting(); } // Returns a pointer to the next node in the list after the given `node`. internal::UserDataMinidumpStreamListEntry* GetNext( internal::UserDataMinidumpStreamListEntry* node) { return reinterpret_cast( node->next); } internal::UserDataMinidumpStreamListEntry* initial_head() { return initial_head_; } internal::UserDataMinidumpStreamListEntry* initial_tail() { return initial_tail_; } private: void SetUp() override { ASSERT_EQ(nullptr, GetCurrentHead()); // Create a simple test list with the structure // `initial_head_` -> `initial_tail_`. initial_tail_ = AddStream(0x11111, kInitialTailData); initial_head_ = AddStream(0x22222, kInitialHeadData); // Validate the list's contents. auto current = GetCurrentHead(); ASSERT_EQ(initial_head_, current); ASSERT_EQ(kInitialHeadData, reinterpret_cast(current->base_address)); current = GetNext(current); ASSERT_EQ(initial_tail_, current); ASSERT_EQ(nullptr, GetNext(current)); } void TearDown() override { // Free the list. The list lives until process exit in production, but must // be freed in tests as multiple tests run in the same process. auto current = GetCurrentHead(); while (current) { auto next = GetNext(current); delete current; current = next; } } internal::UserDataMinidumpStreamListEntry* AddStream(uint32_t stream_type, const char* data) { return reinterpret_cast( crashpad_info().AddUserDataMinidumpStream( stream_type, data, strlen(data))); } CrashpadInfo crashpad_info_; static constexpr char kInitialHeadData[] = "head"; static constexpr char kInitialTailData[] = "tail"; internal::UserDataMinidumpStreamListEntry* initial_head_ = nullptr; internal::UserDataMinidumpStreamListEntry* initial_tail_ = nullptr; }; // Tests that updating the head of the list updates the head pointer, the new // head contains the updated data, and the updated node points to the next node. TEST_F(CrashpadInfoTest, UpdateUserDataMinidumpStreamHead) { const std::string new_data = "this is a new string"; const auto new_entry = crashpad_info().UpdateUserDataMinidumpStream( initial_head(), kTestStreamType, new_data.data(), new_data.size()); const auto head = GetCurrentHead(); EXPECT_EQ(new_entry, head); EXPECT_EQ(new_data.data(), reinterpret_cast(head->base_address)); EXPECT_EQ(new_data.size(), head->size); EXPECT_EQ(kTestStreamType, head->stream_type); EXPECT_EQ(initial_tail(), GetNext(head)); } // Tests that updating the tail of the list results in a tail pointing to // nullptr, and that the node before the updated node points to it. TEST_F(CrashpadInfoTest, UpdateUserDataMinidumpStreamTail) { const std::string new_data = "new"; const auto new_entry = crashpad_info().UpdateUserDataMinidumpStream( initial_tail(), kTestStreamType, new_data.data(), new_data.size()); const auto tail = GetNext(GetCurrentHead()); EXPECT_EQ(new_entry, tail); EXPECT_EQ(nullptr, GetNext(tail)); } // Tests that the handle returned from updating an entry is usable for updating // the entry again. TEST_F(CrashpadInfoTest, UpdateUserDataMinidumpStreamMultipleTimes) { // Update the entry at the head; the updated entry should become the new head. const std::string new_data = "new"; const auto new_entry_1 = crashpad_info().UpdateUserDataMinidumpStream( initial_head(), kTestStreamType, new_data.data(), new_data.size()); EXPECT_EQ(new_entry_1, GetCurrentHead()); // Update the updated entry again; another new entry should replace it as // head. const auto new_entry_2 = crashpad_info().UpdateUserDataMinidumpStream( new_entry_1, kTestStreamType, new_data.data(), new_data.size()); EXPECT_NE(new_entry_1, new_entry_2); EXPECT_EQ(new_entry_2, GetCurrentHead()); EXPECT_EQ(initial_tail(), GetNext(GetCurrentHead())); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/ios_handler/exception_processor.h ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_UTIL_IOS_EXCEPTION_PROCESSOR_H_ #define CRASHPAD_UTIL_IOS_EXCEPTION_PROCESSOR_H_ #include "base/files/file_path.h" #include "util/misc/capture_context.h" namespace crashpad { //! \brief An interface for notifying the CrashpadClient of NSExceptions. class ObjcExceptionDelegate { public: //! \brief The exception processor detected an exception as it was thrown and //! captured the cpu context. //! //! \param context The cpu context of the thread throwing an exception. virtual void HandleUncaughtNSExceptionWithContext( NativeCPUContext* context) = 0; //! \brief The exception processor did not detect the exception as it was //! thrown, and instead caught the exception via the //! NSUncaughtExceptionHandler. //! //! \param frames An array of call stack frame addresses. //! \param num_frames The number of frames in |frames|. virtual void HandleUncaughtNSException(const uint64_t* frames, const size_t num_frames) = 0; //! \brief Generate an intermediate dump from an NSException caught with its //! associated CPU context. Because the method for intercepting //! exceptions is imperfect, write the the intermediate dump to a //! temporary location specified by \a path. If the NSException matches //! the one used in the UncaughtExceptionHandler, call //! MoveIntermediateDumpAtPathToPending to move to the proper Crashpad //! database pending location. //! //! \param[in] context The cpu context of the thread throwing an exception. //! \param[in] path Path to write the intermediate dump. virtual void HandleUncaughtNSExceptionWithContextAtPath( NativeCPUContext* context, const base::FilePath& path) = 0; //! \brief Moves an intermediate dump to the pending directory. This is meant //! to be used by the UncaughtExceptionHandler, when the NSException //! caught by the preprocessor matches the UncaughtExceptionHandler. //! //! \param[in] path Path to the specific intermediate dump. virtual bool MoveIntermediateDumpAtPathToPending( const base::FilePath& path) = 0; protected: ~ObjcExceptionDelegate() {} }; //! \brief Installs the Objective-C exception preprocessor. //! //! When code raises an Objective-C exception, unwind the stack looking for //! any exception handlers. If an exception handler is encountered, test to //! see if it is a function known to be a catch-and-rethrow 'sinkhole' exception //! handler. Various routines in UIKit do this, and they obscure the //! crashing stack, since the original throw location is no longer present //! on the stack (just the re-throw) when Crashpad captures the crash //! report. In the case of sinkholes, trigger an immediate exception to //! capture the original stack. //! //! This should be installed at the same time the CrashpadClient installs the //! signal handler. It should only be installed once. void InstallObjcExceptionPreprocessor(ObjcExceptionDelegate* delegate); //! \brief Uninstalls the Objective-C exception preprocessor. Expected to be //! used by tests only. void UninstallObjcExceptionPreprocessor(); } // namespace crashpad #endif // CRASHPAD_UTIL_IOS_EXCEPTION_PROCESSOR_H_ ================================================ FILE: client/ios_handler/exception_processor.mm ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // std::unexpected_handler is deprecated starting in C++11, and removed in // C++17. But macOS versions we run on still ship it. This define makes // std::unexpected_handler reappear. If that define ever stops working, // we hopefully no longer run on macOS versions that still have it. // (...or we'll have to define it in this file instead of getting it from // ). This define must before all includes. #define _LIBCPP_ENABLE_CXX17_REMOVED_UNEXPECTED_FUNCTIONS #include "client/ios_handler/exception_processor.h" #include #import #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base/format_macros.h" #include "base/logging.h" #include "base/memory/free_deleter.h" #include "base/numerics/safe_conversions.h" #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" #include "build/build_config.h" #include "client/annotation.h" namespace crashpad { namespace { // From 10.15.0 objc4-779.1/runtime/objc-exception.mm. struct objc_typeinfo { const void* const* vtable; const char* name; Class cls_unremapped; }; struct objc_exception { id __unsafe_unretained obj; objc_typeinfo tinfo; }; // From 10.15.0 objc4-779.1/runtime/objc-abi.h. extern "C" const void* const objc_ehtype_vtable[]; // https://github.com/llvm/llvm-project/blob/09dc884eb2e4/libcxxabi/src/cxa_exception.h static const uint64_t kOurExceptionClass = 0x434c4e47432b2b00; struct __cxa_exception { #if defined(ARCH_CPU_64_BITS) void* reserve; size_t referenceCount; #endif std::type_info* exceptionType; void (*exceptionDestructor)(void*); std::unexpected_handler unexpectedHandler; std::terminate_handler terminateHandler; __cxa_exception* nextException; int handlerCount; int handlerSwitchValue; const unsigned char* actionRecord; const unsigned char* languageSpecificData; void* catchTemp; void* adjustedPtr; #if !defined(ARCH_CPU_64_BITS) size_t referenceCount; #endif _Unwind_Exception unwindHeader; }; int LoggingUnwStep(unw_cursor_t* cursor) { int rv = unw_step(cursor); if (rv < 0) { LOG(ERROR) << "unw_step: " << rv; } return rv; } std::string FormatStackTrace(const std::vector& addresses, size_t max_length) { std::string stack_string; for (uint64_t address : addresses) { std::string address_string = base::StringPrintf("0x%" PRIx64, address); if (stack_string.size() + address_string.size() > max_length) break; stack_string += address_string + " "; } if (!stack_string.empty() && stack_string.back() == ' ') { stack_string.resize(stack_string.size() - 1); } return stack_string; } std::string GetTraceString() { std::vector addresses; unw_context_t context; unw_getcontext(&context); unw_cursor_t cursor; unw_init_local(&cursor, &context); while (LoggingUnwStep(&cursor) > 0) { unw_word_t ip = 0; unw_get_reg(&cursor, UNW_REG_IP, &ip); addresses.push_back(ip); } return FormatStackTrace(addresses, 1024); } static void SetNSExceptionAnnotations(NSException* exception, std::string& name, std::string& reason) { @try { name = base::SysNSStringToUTF8(exception.name); static StringAnnotation<256> nameKey("exceptionName"); nameKey.Set(name); } @catch (id name_exception) { LOG(ERROR) << "Unable to read uncaught Objective-C exception name."; } @try { reason = base::SysNSStringToUTF8(exception.reason); static StringAnnotation<1024> reasonKey("exceptionReason"); reasonKey.Set(reason); } @catch (id reason_exception) { LOG(ERROR) << "Unable to read uncaught Objective-C exception reason."; } @try { if (exception.userInfo) { static StringAnnotation<1024> userInfoKey("exceptionUserInfo"); userInfoKey.Set(base::SysNSStringToUTF8( [NSString stringWithFormat:@"%@", exception.userInfo])); } } @catch (id user_info_exception) { LOG(ERROR) << "Unable to read uncaught Objective-C exception user info."; } } //! \brief Helper class to own the complex types used by the Objective-C //! exception preprocessor. class ExceptionPreprocessorState { public: ExceptionPreprocessorState(const ExceptionPreprocessorState&) = delete; ExceptionPreprocessorState& operator=(const ExceptionPreprocessorState&) = delete; static ExceptionPreprocessorState* Get() { static ExceptionPreprocessorState* instance = []() { return new ExceptionPreprocessorState(); }(); return instance; } // Writes an intermediate dumps to a temporary location to be used by the // final UncaughtExceptionHandler and notifies the preprocessor chain. id HandleUncaughtException(NativeCPUContext* cpu_context, id exception) { // If this isn't the first time the preprocessor has detected an uncaught // NSException, note this in the second intermediate dump. objc_exception_preprocessor next_preprocessor = next_preprocessor_; static bool handled_first_exception; if (handled_first_exception) { static StringAnnotation<5> name_key("MultipleHandledUncaughtNSException"); name_key.Set("true"); // Unregister so we stop getting in the way of the exception processor if // we aren't correctly identifying sinkholes. The final uncaught exception // handler is still active. objc_setExceptionPreprocessor(next_preprocessor_); next_preprocessor_ = nullptr; } handled_first_exception = true; // Use tmp/ for this intermediate dump path. Normally these dumps are // written to the "pending-serialized-ios-dump" folder and are eligable for // the next pass to convert pending intermediate dumps to minidump files. // Since this intermediate dump isn't eligable until the uncaught handler, // use tmp/. base::FilePath path(base::SysNSStringToUTF8([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]])); exception_delegate_->HandleUncaughtNSExceptionWithContextAtPath(cpu_context, path); last_handled_intermediate_dump_ = path; return next_preprocessor ? next_preprocessor(exception) : exception; } // If the PreprocessException already captured this exception via // HANDLE_UNCAUGHT_NSEXCEPTION. Move last_handled_intermediate_dump_ to // the pending intermediate dump directory and return true. Otherwise the // preprocessor didn't catch anything, so pass the frames or just the context // to the exception_delegate. void FinalizeUncaughtNSException(id exception) { if (last_exception() == (__bridge void*)exception && !last_handled_intermediate_dump_.empty() && exception_delegate_->MoveIntermediateDumpAtPathToPending( last_handled_intermediate_dump_)) { last_handled_intermediate_dump_ = base::FilePath(); return; } std::string name, reason; NSArray* address_array = nil; if ([exception isKindOfClass:[NSException class]]) { SetNSExceptionAnnotations(exception, name, reason); address_array = [exception callStackReturnAddresses]; } if ([address_array count] > 0) { static StringAnnotation<256> name_key("UncaughtNSException"); name_key.Set("true"); std::vector addresses; for (NSNumber* address in address_array) addresses.push_back([address unsignedLongLongValue]); exception_delegate_->HandleUncaughtNSException(&addresses[0], addresses.size()); } else { LOG(WARNING) << "Uncaught Objective-C exception name: " << name << " reason: " << reason << " with no " << " -callStackReturnAddresses."; NativeCPUContext cpu_context; CaptureContext(&cpu_context); exception_delegate_->HandleUncaughtNSExceptionWithContext(&cpu_context); } } id MaybeCallNextPreprocessor(id exception) { return next_preprocessor_ ? next_preprocessor_(exception) : exception; } // Register the objc_setExceptionPreprocessor and NSUncaughtExceptionHandler. void Install(ObjcExceptionDelegate* delegate); // Restore the objc_setExceptionPreprocessor and NSUncaughtExceptionHandler. void Uninstall(); void* last_exception() { return last_exception_; } void set_last_exception(void* exception) { last_exception_ = exception; } private: ExceptionPreprocessorState() = default; ~ExceptionPreprocessorState() = default; // Location of the intermediate dump generated after an exception triggered // HANDLE_UNCAUGHT_NSEXCEPTION. base::FilePath last_handled_intermediate_dump_; // Recorded last NSException pointer in case the exception is caught and // thrown again (without using objc_exception_rethrow) as an // unsafe_unretained reference. Stored as a void* as the only safe // operation is pointer comparison. std::atomic last_exception_ = nil; ObjcExceptionDelegate* exception_delegate_ = nullptr; objc_exception_preprocessor next_preprocessor_ = nullptr; NSUncaughtExceptionHandler* next_uncaught_exception_handler_ = nullptr; }; static void ObjcUncaughtExceptionHandler(NSException* exception) { ExceptionPreprocessorState::Get()->FinalizeUncaughtNSException(exception); } // This function is used to make it clear to the crash processor that an // uncaught NSException was recorded here. static __attribute__((noinline)) id HANDLE_UNCAUGHT_NSEXCEPTION( id exception, const char* sinkhole) { std::string name, reason; if ([exception isKindOfClass:[NSException class]]) { SetNSExceptionAnnotations(exception, name, reason); } LOG(WARNING) << "Handling Objective-C exception name: " << name << " reason: " << reason << " with sinkhole: " << sinkhole; NativeCPUContext cpu_context{}; CaptureContext(&cpu_context); ExceptionPreprocessorState* preprocessor_state = ExceptionPreprocessorState::Get(); return preprocessor_state->HandleUncaughtException(&cpu_context, exception); } // Returns true if |path| equals |sinkhole| on device. Simulator paths prepend // much of Xcode's internal structure, so check that |path| ends with |sinkhole| // for simulator. bool ModulePathMatchesSinkhole(const char* path, const char* sinkhole) { #if TARGET_OS_SIMULATOR size_t path_length = strlen(path); size_t sinkhole_length = strlen(sinkhole); if (sinkhole_length > path_length) return false; return strncmp(path + path_length - sinkhole_length, sinkhole, sinkhole_length) == 0; #else return strcmp(path, sinkhole) == 0; #endif } //! \brief Helper to release memory from calls to __cxa_allocate_exception. class ScopedException { public: explicit ScopedException(objc_exception* exception) : exception_(exception) {} ScopedException(const ScopedException&) = delete; ScopedException& operator=(const ScopedException&) = delete; ~ScopedException() { __cxxabiv1::__cxa_free_exception(exception_); } private: objc_exception* exception_; // weak }; id ObjcExceptionPreprocessor(id exception) { // Some sinkholes don't use objc_exception_rethrow when they should, which // would otherwise prevent the exception_preprocessor from getting called // again. Because of this, track the most recently seen exception and // ignore it. ExceptionPreprocessorState* preprocessor_state = ExceptionPreprocessorState::Get(); if (preprocessor_state->last_exception() == (__bridge void*)exception) { return preprocessor_state->MaybeCallNextPreprocessor(exception); } preprocessor_state->set_last_exception((__bridge void*)exception); static bool seen_first_exception; static StringAnnotation<256> firstexception("firstexception"); static StringAnnotation<256> lastexception("lastexception"); static StringAnnotation<1024> firstexception_bt("firstexception_bt"); static StringAnnotation<1024> lastexception_bt("lastexception_bt"); auto* key = seen_first_exception ? &lastexception : &firstexception; auto* bt_key = seen_first_exception ? &lastexception_bt : &firstexception_bt; if ([exception isKindOfClass:[NSException class]]) { NSString* value = [NSString stringWithFormat:@"%@ reason %@", [exception name], [exception reason]]; key->Set(base::SysNSStringToUTF8(value)); } else { key->Set(base::SysNSStringToUTF8([exception description])); } // This exception preprocessor runs prior to the one in libobjc, which sets // the -[NSException callStackReturnAddresses]. bt_key->Set(GetTraceString()); seen_first_exception = true; // Unwind the stack looking for any exception handlers. If an exception // handler is encountered, test to see if it is a function known to catch- // and-rethrow as a "top-level" exception handler. Various routines in // Cocoa/UIKit do this, and it obscures the crashing stack, since the original // throw location is no longer present on the stack (just the re-throw) when // Crashpad captures the crash report. unw_context_t context; unw_getcontext(&context); unw_cursor_t cursor; unw_init_local(&cursor, &context); static const void* this_base_address = []() -> const void* { Dl_info dl_info; if (!dladdr(reinterpret_cast(&ObjcExceptionPreprocessor), &dl_info)) { LOG(ERROR) << "dladdr: " << dlerror(); return nullptr; } return dl_info.dli_fbase; }(); // Generate an exception_header for the __personality_routine. // From 10.15.0 objc4-779.1/runtime/objc-exception.mm objc_exception_throw. objc_exception* exception_objc = reinterpret_cast( __cxxabiv1::__cxa_allocate_exception(sizeof(objc_exception))); ScopedException exception_objc_owner(exception_objc); exception_objc->obj = exception; exception_objc->tinfo.vtable = objc_ehtype_vtable + 2; exception_objc->tinfo.name = object_getClassName(exception); exception_objc->tinfo.cls_unremapped = object_getClass(exception); // https://github.com/llvm/llvm-project/blob/c5d2746fbea7/libcxxabi/src/cxa_exception.cpp // __cxa_throw __cxa_exception* exception_header = reinterpret_cast<__cxa_exception*>(exception_objc) - 1; exception_header->unexpectedHandler = std::get_unexpected(); exception_header->terminateHandler = std::get_terminate(); exception_header->exceptionType = reinterpret_cast(&exception_objc->tinfo); exception_header->unwindHeader.exception_class = kOurExceptionClass; bool handler_found = false; while (LoggingUnwStep(&cursor) > 0) { unw_proc_info_t frame_info; if (unw_get_proc_info(&cursor, &frame_info) != UNW_ESUCCESS) { continue; } if (frame_info.handler == 0) { continue; } // Check to see if the handler is really an exception handler. #if defined(__IPHONE_14_5) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_5 using personality_routine = _Unwind_Personality_Fn; #else using personality_routine = __personality_routine; #endif personality_routine p = reinterpret_cast(frame_info.handler); // From 10.15.0 libunwind-35.4/src/UnwindLevel1.c. _Unwind_Reason_Code personalityResult = (*p)( 1, _UA_SEARCH_PHASE, exception_header->unwindHeader.exception_class, reinterpret_cast<_Unwind_Exception*>(&exception_header->unwindHeader), reinterpret_cast<_Unwind_Context*>(&cursor)); switch (personalityResult) { case _URC_HANDLER_FOUND: break; case _URC_CONTINUE_UNWIND: continue; default: break; } char proc_name[512]; unw_word_t offset; if (unw_get_proc_name(&cursor, proc_name, sizeof(proc_name), &offset) != UNW_ESUCCESS) { // The symbol has no name, so see if it belongs to the same image as // this function. Dl_info dl_info; if (dladdr(reinterpret_cast(frame_info.start_ip), &dl_info)) { if (dl_info.dli_fbase == this_base_address) { // This is a handler in our image, so allow it to run. handler_found = true; break; } } // This handler does not belong to us, so continue the search. continue; } // Check if the function is one that is known to obscure (by way of // catch-and-rethrow) exception stack traces. If it is, sinkhole it // by crashing here at the point of throw. static constexpr const char* kExceptionSymbolNameSinkholes[] = { // The two CF symbol names will also be captured by the CoreFoundation // library path check below, but for completeness they are listed here, // since they appear unredacted. "CFRunLoopRunSpecific", "_CFXNotificationPost", "__NSFireDelayedPerform", // If this exception is going to end up at EHFrame, record the uncaught // exception instead. "_ZN4base3mac15CallWithEHFrameEU13block_pointerFvvE", }; for (const char* sinkhole : kExceptionSymbolNameSinkholes) { if (strcmp(sinkhole, proc_name) == 0) { return HANDLE_UNCAUGHT_NSEXCEPTION(exception, sinkhole); } } // On iOS, function names are often reported as "", although they // do appear when attached to the debugger. When this happens, use the path // of the image to determine if the handler is an exception sinkhole. static constexpr const char* kExceptionLibraryPathSinkholes[] = { // Everything in this library is a sinkhole, specifically // _dispatch_client_callout. Both are needed here depending on whether // the debugger is attached (introspection only appears when a simulator // is attached to a debugger). "/usr/lib/system/introspection/libdispatch.dylib", "/usr/lib/system/libdispatch.dylib", // __CFRunLoopDoTimers and __CFRunLoopRun are sinkholes. Consider also // checking that a few frames up is CFRunLoopRunSpecific(). "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", }; Dl_info dl_info; if (dladdr(reinterpret_cast(frame_info.start_ip), &dl_info) != 0) { for (const char* sinkhole : kExceptionLibraryPathSinkholes) { if (ModulePathMatchesSinkhole(dl_info.dli_fname, sinkhole)) { return HANDLE_UNCAUGHT_NSEXCEPTION(exception, sinkhole); } } // Another set of iOS redacted sinkholes appear in CoreAutoLayout. // However, this is often called by client code, so it's unsafe to simply // handle an uncaught nsexception here. Instead, skip the frame and // continue searching for either a handler that belongs to us, or another // sinkhole. See: // -[NSISEngine // performModifications:withUnsatisfiableConstraintsHandler:]: // -[NSISEngine withBehaviors:performModifications:] // +[NSLayoutConstraintParser // constraintsWithVisualFormat:options:metrics:views:]: static constexpr const char* kCoreAutoLayoutSinkhole = "/System/Library/PrivateFrameworks/CoreAutoLayout.framework/" "CoreAutoLayout"; if (ModulePathMatchesSinkhole(dl_info.dli_fname, kCoreAutoLayoutSinkhole)) { continue; } } // Some sinkholes are harder to find. _UIGestureEnvironmentUpdate // in UIKitCore is an example. UIKitCore can't be added to // kExceptionLibraryPathSinkholes because it uses Objective-C exceptions // internally and also has has non-sinkhole handlers. While all the // calling methods in UIKit are marked starting in iOS14, it's // currently true that all callers to _UIGestureEnvironmentUpdate are within // UIWindow sendEvent -> UIGestureEnvironment. That means a very hacky way // to detect this is to check if the calling (2x) method IMP is within the // range of all UIWindow methods. static constexpr const char kUIKitCorePath[] = "/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore"; if (ModulePathMatchesSinkhole(dl_info.dli_fname, kUIKitCorePath)) { unw_proc_info_t caller_frame_info; if (LoggingUnwStep(&cursor) > 0 && unw_get_proc_info(&cursor, &caller_frame_info) == UNW_ESUCCESS && LoggingUnwStep(&cursor) > 0 && unw_get_proc_info(&cursor, &caller_frame_info) == UNW_ESUCCESS) { auto uiwindowimp_lambda = [](IMP* max) { IMP min = *max = nullptr; unsigned int method_count = 0; std::unique_ptr method_list( class_copyMethodList(NSClassFromString(@"UIWindow"), &method_count)); if (method_count > 0) { min = *max = method_getImplementation(method_list[0]); for (unsigned int method_index = 1; method_index < method_count; method_index++) { IMP method_imp = method_getImplementation(method_list[method_index]); *max = std::max(method_imp, *max); min = std::min(method_imp, min); } } return min; }; static IMP uiwindow_max_imp; static IMP uiwindow_min_imp = uiwindowimp_lambda(&uiwindow_max_imp); if (uiwindow_min_imp && uiwindow_max_imp && caller_frame_info.start_ip >= reinterpret_cast(uiwindow_min_imp) && caller_frame_info.start_ip <= reinterpret_cast(uiwindow_max_imp)) { return HANDLE_UNCAUGHT_NSEXCEPTION(exception, "_UIGestureEnvironmentUpdate"); } } } handler_found = true; break; } // If no handler is found, __cxa_throw would call failed_throw and terminate. // See: // https://github.com/llvm/llvm-project/blob/c5d2746fbea7/libcxxabi/src/cxa_exception.cpp // __cxa_throw. Instead, call HANDLE_UNCAUGHT_NSEXCEPTION so the exception // name and reason are properly recorded. if (!handler_found) { return HANDLE_UNCAUGHT_NSEXCEPTION(exception, "__cxa_throw"); } // Forward to the next preprocessor. return preprocessor_state->MaybeCallNextPreprocessor(exception); } void ExceptionPreprocessorState::Install(ObjcExceptionDelegate* delegate) { DCHECK(!next_preprocessor_); exception_delegate_ = delegate; // Preprocessor. next_preprocessor_ = objc_setExceptionPreprocessor(&ObjcExceptionPreprocessor); // Uncaught processor. next_uncaught_exception_handler_ = NSGetUncaughtExceptionHandler(); NSSetUncaughtExceptionHandler(&ObjcUncaughtExceptionHandler); } void ExceptionPreprocessorState::Uninstall() { DCHECK(next_preprocessor_); objc_setExceptionPreprocessor(next_preprocessor_); next_preprocessor_ = nullptr; NSSetUncaughtExceptionHandler(next_uncaught_exception_handler_); next_uncaught_exception_handler_ = nullptr; exception_delegate_ = nullptr; } } // namespace void InstallObjcExceptionPreprocessor(ObjcExceptionDelegate* delegate) { ExceptionPreprocessorState::Get()->Install(delegate); } void UninstallObjcExceptionPreprocessor() { ExceptionPreprocessorState::Get()->Uninstall(); } } // namespace crashpad ================================================ FILE: client/ios_handler/exception_processor_test.mm ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import #include #include #include "gtest/gtest.h" #include "testing/platform_test.h" namespace crashpad { namespace test { namespace { using IOSExceptionProcessor = PlatformTest; TEST_F(IOSExceptionProcessor, SelectorExists) { IMP init_imp = class_getMethodImplementation(NSClassFromString(@"UIGestureEnvironment"), NSSelectorFromString(@"init")); IMP destruct_imp = class_getMethodImplementation(NSClassFromString(@"UIGestureEnvironment"), NSSelectorFromString(@".cxx_destruct")); // From 10.15.0 objc4-779.1/runtime/objc-class.mm // class_getMethodImplementation returns nil or _objc_msgForward on failure. ASSERT_TRUE(init_imp); EXPECT_NE(init_imp, _objc_msgForward); ASSERT_TRUE(destruct_imp); EXPECT_NE(destruct_imp, _objc_msgForward); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/ios_handler/in_process_handler.cc ================================================ // Copyright 2021 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/ios_handler/in_process_handler.h" #include #include #include #include "base/logging.h" #include "client/ios_handler/in_process_intermediate_dump_handler.h" #include "client/prune_crash_reports.h" #include "client/settings.h" #include "minidump/minidump_file_writer.h" #include "util/file/directory_reader.h" #include "util/file/filesystem.h" #include "util/ios/raw_logging.h" namespace { // Creates directory at |path|. bool CreateDirectory(const base::FilePath& path) { if (mkdir(path.value().c_str(), 0755) == 0) { return true; } if (errno != EEXIST) { PLOG(ERROR) << "mkdir " << path.value(); return false; } return true; } // The file extension used to indicate a file is locked. constexpr char kLockedExtension[] = ".locked"; // The seperator used to break the bundle id (e.g. com.chromium.ios) from the // uuid in the intermediate dump file name. constexpr char kBundleSeperator[] = "@"; // Zero-ed codes used by kMachExceptionFromNSException and // kMachExceptionSimulated. constexpr mach_exception_data_type_t kEmulatedMachExceptionCodes[2] = {}; } // namespace namespace crashpad { namespace internal { InProcessHandler::InProcessHandler() = default; InProcessHandler::~InProcessHandler() { if (cached_writer_) { cached_writer_->Close(); } UpdatePruneAndUploadThreads(false, UploadBehavior::kUploadWhenAppIsActive); } bool InProcessHandler::Initialize( const base::FilePath& database, const std::string& url, const std::map& annotations, ProcessPendingReportsObservationCallback callback) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); annotations_ = annotations; database_ = CrashReportDatabase::Initialize(database); if (!database_) { return false; } bundle_identifier_and_seperator_ = system_data_.BundleIdentifier() + kBundleSeperator; if (!url.empty()) { // TODO(scottmg): options.rate_limit should be removed when we have a // configurable database setting to control upload limiting. // See https://crashpad.chromium.org/bug/23. CrashReportUploadThread::Options upload_thread_options; upload_thread_options.rate_limit = false; upload_thread_options.upload_gzip = true; upload_thread_options.watch_pending_reports = true; upload_thread_options.identify_client_via_url = true; upload_thread_.reset(new CrashReportUploadThread( database_.get(), url, upload_thread_options, callback)); } if (!CreateDirectory(database)) return false; static constexpr char kPendingSerializediOSDump[] = "pending-serialized-ios-dump"; base_dir_ = database.Append(kPendingSerializediOSDump); if (!CreateDirectory(base_dir_)) return false; bool is_app_extension = system_data_.IsExtension(); prune_thread_.reset(new PruneIntermediateDumpsAndCrashReportsThread( database_.get(), PruneCondition::GetDefault(), base_dir_, bundle_identifier_and_seperator_, is_app_extension)); if (is_app_extension || system_data_.IsApplicationActive()) prune_thread_->Start(); if (!is_app_extension) { system_data_.SetActiveApplicationCallback([this](bool active) { dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ UpdatePruneAndUploadThreads(active, UploadBehavior::kUploadWhenAppIsActive); }); }); } base::FilePath cached_writer_path = NewLockedFilePath(); cached_writer_ = CreateWriterWithPath(cached_writer_path); if (!cached_writer_.get()) return false; // Cache the locked and unlocked path here so no allocations are needed during // any exceptions. cached_writer_path_ = cached_writer_path.value(); cached_writer_unlocked_path_ = cached_writer_path.RemoveFinalExtension().value(); INITIALIZATION_STATE_SET_VALID(initialized_); return true; } void InProcessHandler::DumpExceptionFromSignal(siginfo_t* siginfo, ucontext_t* context) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); ScopedLockedWriter writer(GetCachedWriter(), cached_writer_path_.c_str(), cached_writer_unlocked_path_.c_str()); if (!writer.GetWriter()) { CRASHPAD_RAW_LOG("Cannot DumpExceptionFromSignal without writer"); return; } if (exception_callback_for_testing_) { exception_callback_for_testing_(); } ScopedReport report(writer.GetWriter(), system_data_, annotations_); InProcessIntermediateDumpHandler::WriteExceptionFromSignal( writer.GetWriter(), system_data_, siginfo, context); } void InProcessHandler::DumpExceptionFromMachException( exception_behavior_t behavior, thread_t thread, exception_type_t exception, const mach_exception_data_type_t* code, mach_msg_type_number_t code_count, thread_state_flavor_t flavor, ConstThreadState old_state, mach_msg_type_number_t old_state_count) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); ScopedLockedWriter writer(GetCachedWriter(), cached_writer_path_.c_str(), cached_writer_unlocked_path_.c_str()); if (!writer.GetWriter()) { CRASHPAD_RAW_LOG("Cannot DumpExceptionFromMachException without writer"); return; } if (exception_callback_for_testing_) { exception_callback_for_testing_(); } ScopedReport report(writer.GetWriter(), system_data_, annotations_); InProcessIntermediateDumpHandler::WriteExceptionFromMachException( writer.GetWriter(), behavior, thread, exception, code, code_count, flavor, old_state, old_state_count); } void InProcessHandler::DumpExceptionFromNSExceptionWithFrames( const uint64_t* frames, const size_t num_frames) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); ScopedLockedWriter writer(GetCachedWriter(), cached_writer_path_.c_str(), cached_writer_unlocked_path_.c_str()); if (!writer.GetWriter()) { CRASHPAD_RAW_LOG( "Cannot DumpExceptionFromNSExceptionWithFrames without writer"); return; } ScopedReport report( writer.GetWriter(), system_data_, annotations_, frames, num_frames); InProcessIntermediateDumpHandler::WriteExceptionFromNSException( writer.GetWriter()); } bool InProcessHandler::DumpExceptionFromSimulatedMachException( const NativeCPUContext* context, exception_type_t exception, base::FilePath* path) { base::FilePath locked_path = NewLockedFilePath(); *path = locked_path.RemoveFinalExtension(); return DumpExceptionFromSimulatedMachExceptionAtPath( context, exception, locked_path); } bool InProcessHandler::DumpExceptionFromSimulatedMachExceptionAtPath( const NativeCPUContext* context, exception_type_t exception, const base::FilePath& path) { // This does not use the cached writer. It's expected that simulated // exceptions can be called multiple times and there is no expectation that // the application is in an unsafe state, or will be terminated after this // call. std::unique_ptr unsafe_writer = CreateWriterWithPath(path); base::FilePath writer_path_unlocked = path.RemoveFinalExtension(); ScopedLockedWriter writer(unsafe_writer.get(), path.value().c_str(), writer_path_unlocked.value().c_str()); if (!writer.GetWriter()) { CRASHPAD_RAW_LOG( "Cannot DumpExceptionFromSimulatedMachExceptionAtPath without writer"); return false; } ScopedReport report(writer.GetWriter(), system_data_, annotations_); InProcessIntermediateDumpHandler::WriteExceptionFromMachException( writer.GetWriter(), MACH_EXCEPTION_CODES, mach_thread_self(), exception, kEmulatedMachExceptionCodes, std::size(kEmulatedMachExceptionCodes), MACHINE_THREAD_STATE, reinterpret_cast(context), MACHINE_THREAD_STATE_COUNT); return true; } bool InProcessHandler::MoveIntermediateDumpAtPathToPending( const base::FilePath& path) { base::FilePath new_path_unlocked = NewLockedFilePath().RemoveFinalExtension(); return MoveFileOrDirectory(path, new_path_unlocked); } void InProcessHandler::ProcessIntermediateDumps( const std::map& annotations, const UserStreamDataSources* user_stream_sources) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); for (auto& file : PendingFiles()) ProcessIntermediateDump(file, annotations, user_stream_sources); } void InProcessHandler::ProcessIntermediateDump( const base::FilePath& file, const std::map& annotations, const UserStreamDataSources* user_stream_sources) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); ProcessSnapshotIOSIntermediateDump process_snapshot; if (process_snapshot.InitializeWithFilePath(file, annotations)) { SaveSnapshot(process_snapshot, user_stream_sources); } } void InProcessHandler::StartProcessingPendingReports( UploadBehavior upload_behavior) { if (!upload_thread_) return; upload_thread_enabled_ = true; // This may be a no-op if IsApplicationActive is false, as it is not safe to // start the upload thread when in the background (due to the potential for // flocked files in shared containers). // TODO(crbug.com/crashpad/400): Consider moving prune and upload thread to // BackgroundTasks and/or NSURLSession. This might allow uploads to continue // in the background. UpdatePruneAndUploadThreads(system_data_.IsApplicationActive(), upload_behavior); } void InProcessHandler::UpdatePruneAndUploadThreads( bool active, UploadBehavior upload_behavior) { base::AutoLock lock_owner(prune_and_upload_lock_); // TODO(crbug.com/crashpad/400): Consider moving prune and upload thread to // BackgroundTasks and/or NSURLSession. This might allow uploads to continue // in the background. bool threads_should_run; switch (upload_behavior) { case UploadBehavior::kUploadWhenAppIsActive: threads_should_run = active; break; case UploadBehavior::kUploadImmediately: threads_should_run = true; break; } if (threads_should_run) { if (!prune_thread_->is_running()) prune_thread_->Start(); if (upload_thread_enabled_ && !upload_thread_->is_running()) { upload_thread_->Start(); } } else { if (prune_thread_->is_running()) prune_thread_->Stop(); if (upload_thread_enabled_ && upload_thread_->is_running()) upload_thread_->Stop(); } } void InProcessHandler::SaveSnapshot( ProcessSnapshotIOSIntermediateDump& process_snapshot, const UserStreamDataSources* user_stream_sources) { std::unique_ptr new_report; CrashReportDatabase::OperationStatus database_status = database_->PrepareNewCrashReport(&new_report); if (database_status != CrashReportDatabase::kNoError) { Metrics::ExceptionCaptureResult( Metrics::CaptureResult::kPrepareNewCrashReportFailed); return; } process_snapshot.SetReportID(new_report->ReportID()); UUID client_id; Settings* const settings = database_->GetSettings(); if (settings && settings->GetClientID(&client_id)) { process_snapshot.SetClientID(client_id); } MinidumpFileWriter minidump; minidump.InitializeFromSnapshot(&process_snapshot); AddUserExtensionStreams(user_stream_sources, &process_snapshot, &minidump); if (!minidump.WriteEverything(new_report->Writer())) { Metrics::ExceptionCaptureResult( Metrics::CaptureResult::kMinidumpWriteFailed); return; } UUID uuid; database_status = database_->FinishedWritingCrashReport(std::move(new_report), &uuid); if (database_status != CrashReportDatabase::kNoError) { Metrics::ExceptionCaptureResult( Metrics::CaptureResult::kFinishedWritingCrashReportFailed); return; } if (upload_thread_) { upload_thread_->ReportPending(uuid); } } std::vector InProcessHandler::PendingFiles() { DirectoryReader reader; std::vector files; if (!reader.Open(base_dir_)) { return files; } base::FilePath file; DirectoryReader::Result result; // Because the intermediate dump directory is expected to be shared, // mitigate any spamming by limiting this to |kMaxPendingFiles|. constexpr size_t kMaxPendingFiles = 20; // Track other application bundles separately, so they don't spam our // intermediate dumps into never getting processed. std::vector other_files; base::FilePath cached_writer_path(cached_writer_path_); while ((result = reader.NextFile(&file)) == DirectoryReader::Result::kSuccess) { // Don't try to process files marked as 'locked' from a different bundle id. bool bundle_match = file.value().compare(0, bundle_identifier_and_seperator_.size(), bundle_identifier_and_seperator_) == 0; if (!bundle_match && file.FinalExtension() == kLockedExtension) { continue; } // Never process the current cached writer path. file = base_dir_.Append(file); if (file == cached_writer_path) continue; // Otherwise, include any other unlocked, or locked files matching // |bundle_identifier_and_seperator_|. if (bundle_match) { files.push_back(file); if (files.size() >= kMaxPendingFiles) return files; } else { other_files.push_back(file); } } auto end_iterator = other_files.begin() + std::min(kMaxPendingFiles - files.size(), other_files.size()); files.insert(files.end(), other_files.begin(), end_iterator); return files; } IOSIntermediateDumpWriter* InProcessHandler::GetCachedWriter() { static_assert( std::atomic::is_always_lock_free, "std::atomic_compare_exchange_strong uint64_t may not be signal-safe"); uint64_t thread_self; // This is only safe when passing pthread_self(), otherwise this can lock. pthread_threadid_np(pthread_self(), &thread_self); uint64_t expected = 0; if (!std::atomic_compare_exchange_strong( &exception_thread_id_, &expected, thread_self)) { if (expected == thread_self) { // Another exception came in from this thread, which means it's likely // that our own handler crashed. We could open up a new intermediate dump // and try to save this dump, but we could end up endlessly writing dumps. // Instead, give up. } else { // Another thread is handling a crash. Sleep forever. while (1) { sleep(std::numeric_limits::max()); } } return nullptr; } return cached_writer_.get(); } std::unique_ptr InProcessHandler::CreateWriterWithPath(const base::FilePath& writer_path) { std::unique_ptr writer = std::make_unique(); if (!writer->Open(writer_path)) { DLOG(ERROR) << "Unable to open intermediate dump file: " << writer_path.value(); return nullptr; } return writer; } const base::FilePath InProcessHandler::NewLockedFilePath() { UUID uuid; uuid.InitializeWithNew(); const std::string file_string = bundle_identifier_and_seperator_ + uuid.ToString() + kLockedExtension; return base_dir_.Append(file_string); } InProcessHandler::ScopedReport::ScopedReport( IOSIntermediateDumpWriter* writer, const IOSSystemDataCollector& system_data, const std::map& annotations, const uint64_t* frames, const size_t num_frames) : writer_(writer), frames_(frames), num_frames_(num_frames), rootMap_(writer) { DCHECK(writer); // Grab the report creation time before writing the report. uint64_t report_time_nanos = ClockMonotonicNanoseconds(); InProcessIntermediateDumpHandler::WriteHeader(writer); InProcessIntermediateDumpHandler::WriteProcessInfo(writer, annotations); InProcessIntermediateDumpHandler::WriteSystemInfo( writer, system_data, report_time_nanos); } InProcessHandler::ScopedReport::~ScopedReport() { // Write threads and modules last (after the exception itself is written by // DumpExceptionFrom*.) InProcessIntermediateDumpHandler::WriteThreadInfo( writer_, frames_, num_frames_); InProcessIntermediateDumpHandler::WriteModuleInfo(writer_); } InProcessHandler::ScopedLockedWriter::ScopedLockedWriter( IOSIntermediateDumpWriter* writer, const char* writer_path, const char* writer_unlocked_path) : writer_path_(writer_path), writer_unlocked_path_(writer_unlocked_path), writer_(writer) {} InProcessHandler::ScopedLockedWriter::~ScopedLockedWriter() { if (!writer_) return; writer_->Close(); if (rename(writer_path_, writer_unlocked_path_) != 0) { CRASHPAD_RAW_LOG("Could not remove locked extension."); CRASHPAD_RAW_LOG(writer_path_); CRASHPAD_RAW_LOG(writer_unlocked_path_); } } } // namespace internal } // namespace crashpad ================================================ FILE: client/ios_handler/in_process_handler.h ================================================ // Copyright 2021 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_IN_PROCESS_HANDLER_H_ #define CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_IN_PROCESS_HANDLER_H_ #include #include #include #include #include #include #include #include "base/files/file_path.h" #include "base/synchronization/lock.h" #include "client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h" #include "client/upload_behavior_ios.h" #include "handler/crash_report_upload_thread.h" #include "handler/user_stream_data_source.h" #include "snapshot/ios/process_snapshot_ios_intermediate_dump.h" #include "util/ios/ios_intermediate_dump_writer.h" #include "util/ios/ios_system_data_collector.h" #include "util/mach/mach_extensions.h" #include "util/misc/capture_context.h" #include "util/misc/initialization_state_dcheck.h" namespace crashpad { namespace internal { //! \brief Manage intermediate minidump generation, and own the crash report //! upload thread and database. class InProcessHandler { public: InProcessHandler(); ~InProcessHandler(); InProcessHandler(const InProcessHandler&) = delete; InProcessHandler& operator=(const InProcessHandler&) = delete; //! \brief Observation callback invoked each time this object finishes //! processing and attempting to upload on-disk crash reports (whether or //! not the uploads succeeded). //! //! This callback is copied into this object. Any references or pointers //! inside must outlive this object. //! //! The callback might be invoked on a background thread, so clients must //! synchronize appropriately. using ProcessPendingReportsObservationCallback = std::function; //! \brief Initializes the in-process handler. //! //! This method must be called only once, and must be successfully called //! before any other method in this class may be called. //! //! \param[in] database The path to a Crashpad database. //! \param[in] url The URL of an upload server. //! \param[in] annotations Process annotations to set in each crash report. //! \param[in] callback Optional callback invoked zero or more times //! on a background thread each time this object finishes //! processing and attempting to upload on-disk crash reports. //! \return `true` if a handler to a pending intermediate dump could be //! opened. bool Initialize(const base::FilePath& database, const std::string& url, const std::map& annotations, ProcessPendingReportsObservationCallback callback = ProcessPendingReportsObservationCallback()); //! \brief Generate an intermediate dump from a signal handler exception. //! Writes the dump with the cached writer does not allow concurrent //! exceptions to be written. It is expected the system will terminate //! the application after this call. //! //! \param[in] siginfo A pointer to a `siginfo_t` object received by a signal //! handler. //! \param[in] context A pointer to a `ucontext_t` object received by a //! signal. void DumpExceptionFromSignal(siginfo_t* siginfo, ucontext_t* context); //! \brief Generate an intermediate dump from a mach exception. Writes the //! dump with the cached writer does not allow concurrent exceptions to be //! written. It is expected the system will terminate the application //! after this call. //! //! \param[in] behavior //! \param[in] thread //! \param[in] exception //! \param[in] code //! \param[in] code_count //! \param[in,out] flavor //! \param[in] old_state //! \param[in] old_state_count void DumpExceptionFromMachException(exception_behavior_t behavior, thread_t thread, exception_type_t exception, const mach_exception_data_type_t* code, mach_msg_type_number_t code_count, thread_state_flavor_t flavor, ConstThreadState old_state, mach_msg_type_number_t old_state_count); //! \brief Generate an intermediate dump from an uncaught NSException. //! //! When the ObjcExceptionPreprocessor does not detect an NSException as it is //! thrown, the last-chance uncaught exception handler passes a list of call //! stack frame addresses. Record them in the intermediate dump so a minidump //! with a 'fake' call stack is generated. Writes the dump with the cached //! writer does not allow concurrent exceptions to be written. It is expected //! the system will terminate the application after this call. //! //! \param[in] frames An array of call stack frame addresses. //! \param[in] num_frames The number of frames in |frames|. void DumpExceptionFromNSExceptionWithFrames(const uint64_t* frames, const size_t num_frames); //! \brief Generate a simulated intermediate dump similar to a Mach exception //! in the same base directory as other exceptions. Does not use the //! cached writer. //! //! \param[in] context A pointer to a NativeCPUContext object for this //! simulated exception. //! \param[in] exception //! \param[out] path The path of the intermediate dump generated. //! \return `true` if the pending intermediate dump could be written. bool DumpExceptionFromSimulatedMachException(const NativeCPUContext* context, exception_type_t exception, base::FilePath* path); //! \brief Generate a simulated intermediate dump similar to a Mach exception //! at a specific path. Does not use the cached writer. //! //! \param[in] context A pointer to a NativeCPUContext object for this //! simulated exception. //! \param[in] exception //! \param[in] path Path to where the intermediate dump should be written. //! \return `true` if the pending intermediate dump could be written. bool DumpExceptionFromSimulatedMachExceptionAtPath( const NativeCPUContext* context, exception_type_t exception, const base::FilePath& path); //! \brief Moves an intermediate dump to the pending directory. This is meant //! to be used by the UncaughtExceptionHandler, when NSException caught //! by the preprocessor matches the UncaughtExceptionHandler. //! //! \param[in] path Path to the specific intermediate dump. bool MoveIntermediateDumpAtPathToPending(const base::FilePath& path); //! \brief Requests that the handler convert all intermediate dumps into //! minidumps and trigger an upload if possible. //! //! \param[in] annotations Process annotations to set in each crash report. //! \param[in] user_stream_sources An optional vector containing the //! extensibility data sources to call on crash. Each time a minidump is //! created, the sources are called in turn. Any streams returned are //! added to the minidump. void ProcessIntermediateDumps( const std::map& annotations, const UserStreamDataSources* user_stream_sources); //! \brief Requests that the handler convert a specific intermediate dump into //! a minidump and trigger an upload if possible. //! //! \param[in] path Path to the specific intermediate dump. //! \param[in] annotations Process annotations to set in each crash report. //! \param[in] user_stream_sources An optional vector containing the //! extensibility data sources to call on crash. Each time a minidump is //! created, the sources are called in turn. Any streams returned are //! added to the minidump. void ProcessIntermediateDump( const base::FilePath& path, const std::map& annotations = {}, const UserStreamDataSources* user_stream_sources = {}); //! \brief Requests that the handler begin in-process uploading of any //! pending reports. //! //! \param[in] upload_behavior Controls when the upload thread will run and //! process pending reports. By default, only uploads pending reports //! when the application is active. void StartProcessingPendingReports( UploadBehavior upload_behavior = UploadBehavior::kUploadWhenAppIsActive); //! \brief Inject a callback into the Mach exception and signal handling //! mechanisms. Intended to be used by tests to trigger a reentrant // exception. void SetExceptionCallbackForTesting(void (*callback)()) { exception_callback_for_testing_ = callback; } private: //! \brief Helper to start and end intermediate reports. class ScopedReport { public: ScopedReport(IOSIntermediateDumpWriter* writer, const IOSSystemDataCollector& system_data, const std::map& annotations, const uint64_t* frames = nullptr, const size_t num_frames = 0); ~ScopedReport(); ScopedReport(const ScopedReport&) = delete; ScopedReport& operator=(const ScopedReport&) = delete; private: IOSIntermediateDumpWriter* writer_; const uint64_t* frames_; const size_t num_frames_; IOSIntermediateDumpWriter::ScopedRootMap rootMap_; }; //! \brief Helper to manage closing the intermediate dump writer and unlocking //! the dump file (renaming the file) after the report is written. class ScopedLockedWriter { public: ScopedLockedWriter(IOSIntermediateDumpWriter* writer, const char* writer_path, const char* writer_unlocked_path); //! \brief Close the writer_ and rename to the file with path without the //! .locked extension. ~ScopedLockedWriter(); ScopedLockedWriter(const ScopedLockedWriter&) = delete; ScopedLockedWriter& operator=(const ScopedLockedWriter&) = delete; IOSIntermediateDumpWriter* GetWriter() { return writer_; } private: const char* writer_path_; const char* writer_unlocked_path_; IOSIntermediateDumpWriter* writer_; }; //! \brief Manage the prune and upload thread when the active state changes. //! //! \param[in] active `true` if the application is actively running in the //! foreground, `false` otherwise. //! \param[in] upload_behavior Controls when the upload thread will run and //! process pending reports. void UpdatePruneAndUploadThreads(bool active, UploadBehavior upload_behavior); //! \brief Writes a minidump to the Crashpad database from the //! \a process_snapshot, and triggers the upload_thread_ if started. //! \param[in] user_stream_sources An optional vector containing the //! extensibility data sources to call on crash. Each time a minidump is //! created, the sources are called in turn. Any streams returned are //! added to the minidump. void SaveSnapshot(ProcessSnapshotIOSIntermediateDump& process_snapshot, const UserStreamDataSources* user_stream_sources = {}); //! \brief Process a maximum of 20 pending intermediate dumps. Dumps named //! with our bundle id get first priority to prevent spamming. std::vector PendingFiles(); //! \brief Lock access to the cached intermediate dump writer from //! concurrent signal, Mach exception and uncaught NSExceptions so that //! the first exception wins. If the same thread triggers another //! reentrant exception, ignore it. If a different thread triggers a //! concurrent exception, sleep indefinitely. IOSIntermediateDumpWriter* GetCachedWriter(); //! \brief Open a new intermediate dump writer from \a writer_path. std::unique_ptr CreateWriterWithPath( const base::FilePath& writer_path); //! \brief Generates a new file path to be used by an intermediate dump //! writer built from base_dir_,, bundle_identifier_and_seperator_, a new //! UUID, with a .locked extension. const base::FilePath NewLockedFilePath(); // Intended to be used by tests triggering a reentrant exception. Called // in DumpExceptionFromMachException and DumpExceptionFromSignal after // acquiring the cached_writer_. void (*exception_callback_for_testing_)() = nullptr; // Used to synchronize access to UpdatePruneAndUploadThreads(). base::Lock prune_and_upload_lock_; std::atomic_bool upload_thread_enabled_ = false; std::map annotations_; base::FilePath base_dir_; std::string cached_writer_path_; std::string cached_writer_unlocked_path_; std::unique_ptr cached_writer_; std::atomic exception_thread_id_ = 0; std::unique_ptr upload_thread_; std::unique_ptr prune_thread_; std::unique_ptr database_; std::string bundle_identifier_and_seperator_; IOSSystemDataCollector system_data_; InitializationStateDcheck initialized_; }; } // namespace internal } // namespace crashpad #endif // CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_IN_PROCESS_HANDLER_H_ ================================================ FILE: client/ios_handler/in_process_handler_test.cc ================================================ // Copyright 2021 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/ios_handler/in_process_handler.h" #include "gtest/gtest.h" #include "test/scoped_temp_dir.h" #include "test/test_paths.h" #include "util/file/directory_reader.h" #include "util/file/filesystem.h" namespace crashpad { namespace test { namespace { bool CreateFile(const base::FilePath& file) { ScopedFileHandle fd(LoggingOpenFileForWrite( file, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)); EXPECT_TRUE(fd.is_valid()); return fd.is_valid(); } class InProcessHandlerTest : public testing::Test { protected: // testing::Test: void SetUp() override { ASSERT_TRUE(in_process_handler_.Initialize(temp_dir_.path(), "", {})); pending_dir_ = temp_dir_.path().Append("pending-serialized-ios-dump"); bundle_identifier_and_seperator_ = system_data_.BundleIdentifier() + "@"; } const auto& path() const { return pending_dir_; } auto& handler() { return in_process_handler_; } void CreateFiles(int files, int other_files) { base::FilePath::StringType file_prepend = FILE_PATH_LITERAL(bundle_identifier_and_seperator_); base::FilePath::StringType file_name = FILE_PATH_LITERAL("file"); for (int i = 0; i < files; i++) { std::string i_str = std::to_string(i); base::FilePath file(file_prepend + file_name + i_str); CreateFile(path().Append(file)); } for (int i = 0; i < other_files; i++) { std::string i_str = std::to_string(i); base::FilePath file(file_name + i_str); CreateFile(path().Append(file)); } } void VerifyRemainingFileCount(int expected_files_count, int expected_other_files_count) { DirectoryReader reader; ASSERT_TRUE(reader.Open(path())); DirectoryReader::Result result; base::FilePath filename; int files_count = 0; int other_files_count = 0; while ((result = reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { bool bundle_match = filename.value().compare(0, bundle_identifier_and_seperator_.size(), bundle_identifier_and_seperator_) == 0; if (bundle_match) { files_count++; } else { other_files_count++; } } EXPECT_EQ(expected_files_count, files_count); EXPECT_EQ(expected_other_files_count, other_files_count); } void ClearFiles() { DirectoryReader reader; ASSERT_TRUE(reader.Open(path())); DirectoryReader::Result result; base::FilePath filename; while ((result = reader.NextFile(&filename)) == DirectoryReader::Result::kSuccess) { LoggingRemoveFile(path().Append(filename)); } } private: ScopedTempDir temp_dir_; base::FilePath pending_dir_; std::string bundle_identifier_and_seperator_; internal::IOSSystemDataCollector system_data_; internal::InProcessHandler in_process_handler_; }; TEST_F(InProcessHandlerTest, TestPendingFileLimit) { // Clear this first to blow away the pending file held by InProcessHandler. ClearFiles(); // Only process other app files. CreateFiles(0, 20); handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(0, 0); ClearFiles(); // Only process our app files. CreateFiles(20, 20); handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(0, 20); ClearFiles(); // Process all of our files and 10 remaining. CreateFiles(10, 30); handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(0, 20); ClearFiles(); // Process 20 our files, leaving 10 remaining, and all other files remaining. CreateFiles(30, 10); handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(10, 10); ClearFiles(); CreateFiles(0, 0); handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(0, 0); ClearFiles(); CreateFiles(10, 0); handler().ProcessIntermediateDumps({}, nullptr); VerifyRemainingFileCount(0, 0); ClearFiles(); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/ios_handler/in_process_intermediate_dump_handler.cc ================================================ // Copyright 2021 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/ios_handler/in_process_intermediate_dump_handler.h" #include #include #include #include #include #include #include #include #include "base/check_op.h" #include "build/build_config.h" #include "snapshot/snapshot_constants.h" #include "util/ios/ios_intermediate_dump_writer.h" #include "util/ios/raw_logging.h" #include "util/ios/scoped_vm_map.h" #include "util/ios/scoped_vm_read.h" #include "util/synchronization/scoped_spin_guard.h" namespace crashpad { namespace internal { namespace { #if defined(ARCH_CPU_X86_64) const thread_state_flavor_t kThreadStateFlavor = x86_THREAD_STATE64; const thread_state_flavor_t kFloatStateFlavor = x86_FLOAT_STATE64; const thread_state_flavor_t kDebugStateFlavor = x86_DEBUG_STATE64; using thread_state_type = x86_thread_state64_t; #elif defined(ARCH_CPU_ARM64) const thread_state_flavor_t kThreadStateFlavor = ARM_THREAD_STATE64; const thread_state_flavor_t kFloatStateFlavor = ARM_NEON_STATE64; const thread_state_flavor_t kDebugStateFlavor = ARM_DEBUG_STATE64; using thread_state_type = arm_thread_state64_t; #endif // From snapshot/mac/process_types/crashreporterclient.proctype struct crashreporter_annotations_t { // Version 4 uint64_t version; uint64_t message; uint64_t signature_string; uint64_t backtrace; uint64_t message2; uint64_t thread; uint64_t dialog_mode; // Version 5 uint64_t abort_cause; // The structure here is only defined through version 5, although version 7 is // also known, and contains an additional 264 bytes from this point. Version 7 // was first used in iOS 26. The version 7 extension is not defined here // because it would cause the reader to attempt to read the full length of a // version 7 structure, even in cases where only a version 5 structure is // present at runtime, as it will be in iOS versions before 26. // // If it ever becomes necessary to read fields beyond version 5 (or whatever // the minimum structure version that would be encountered at runtime is at // that time in the future), the reader will need to take additional care to // consult the `version` field and only read as much of the structure as is // appropriate for the indicated version. The macOS implementation implements // this logic. }; //! \brief Manage memory and ports after calling `task_threads`. class ScopedTaskThreads { public: explicit ScopedTaskThreads(thread_act_array_t threads, mach_msg_type_number_t thread_count) : threads_(threads), thread_count_(thread_count) {} ScopedTaskThreads(const ScopedTaskThreads&) = delete; ScopedTaskThreads& operator=(const ScopedTaskThreads&) = delete; ~ScopedTaskThreads() { for (uint32_t thread_index = 0; thread_index < thread_count_; ++thread_index) { mach_port_deallocate(mach_task_self(), threads_[thread_index]); } vm_deallocate(mach_task_self(), reinterpret_cast(threads_), sizeof(thread_t) * thread_count_); } private: thread_act_array_t threads_; mach_msg_type_number_t thread_count_; }; //! \brief Log \a key as a string. void WriteError(IntermediateDumpKey key) { CRASHPAD_RAW_LOG("Unable to write key"); switch (key) { // clang-format off #define CASE_KEY(Name, Value) \ case IntermediateDumpKey::Name: \ CRASHPAD_RAW_LOG(#Name); \ break; INTERMEDIATE_DUMP_KEYS(CASE_KEY) #undef CASE_KEY // clang-format on } } //! \brief Call AddProperty with raw error log. //! //! \param[in] writer The dump writer //! \param[in] key The key to write. //! \param[in] value Memory to be written. //! \param[in] count Length of \a value. template void WriteProperty(IOSIntermediateDumpWriter* writer, IntermediateDumpKey key, const T* value, size_t count = 1) { if (!writer->AddProperty(key, value, count)) WriteError(key); } //! \brief Call AddPropertyBytes with raw error log. //! //! \param[in] writer The dump writer //! \param[in] key The key to write. //! \param[in] value Memory to be written. //! \param[in] value_length Length of \a data. void WritePropertyBytes(IOSIntermediateDumpWriter* writer, IntermediateDumpKey key, const void* value, size_t value_length) { if (!writer->AddPropertyBytes(key, value, value_length)) WriteError(key); } //! \brief Call AddPropertyCString with raw error log. //! //! \param[in] writer The dump writer //! \param[in] key The key to write. //! \param[in] max_length The maximum string length. //! \param[in] value Memory to be written. void WritePropertyCString(IOSIntermediateDumpWriter* writer, IntermediateDumpKey key, size_t max_length, const char* value) { if (!writer->AddPropertyCString(key, max_length, value)) WriteError(key); } kern_return_t MachVMRegionRecurseDeepest(task_t task, vm_address_t* address, vm_size_t* size, natural_t* depth, vm_prot_t* protection, unsigned int* user_tag) { vm_region_submap_short_info_64 submap_info; mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; while (true) { // Note: vm_region_recurse() would be fine here, but it does not provide // VM_REGION_SUBMAP_SHORT_INFO_COUNT. kern_return_t kr = vm_region_recurse_64( task, address, size, depth, reinterpret_cast(&submap_info), &count); if (kr != KERN_SUCCESS) { CRASHPAD_RAW_LOG_ERROR(kr, "vm_region_recurse_64"); return kr; } if (!submap_info.is_submap) { *protection = submap_info.protection; *user_tag = submap_info.user_tag; return KERN_SUCCESS; } ++*depth; } } //! \brief Adjusts the region for the red zone, if the ABI requires one. //! //! This method performs red zone calculation for CalculateStackRegion(). Its //! parameters are local variables used within that method, and may be //! modified as needed. //! //! Where a red zone is required, the region of memory captured for a thread’s //! stack will be extended to include the red zone below the stack pointer, //! provided that such memory is mapped, readable, and has the correct user //! tag value. If these conditions cannot be met fully, as much of the red //! zone will be captured as is possible while meeting these conditions. //! //! \param[in,out] start_address The base address of the region to begin //! capturing stack memory from. On entry, \a start_address is the stack //! pointer. On return, \a start_address may be decreased to encompass a //! red zone. //! \param[in,out] region_base The base address of the region that contains //! stack memory. This is distinct from \a start_address in that \a //! region_base will be page-aligned. On entry, \a region_base is the //! base address of a region that contains \a start_address. On return, //! if \a start_address is decremented and is outside of the region //! originally described by \a region_base, \a region_base will also be //! decremented appropriately. //! \param[in,out] region_size The size of the region that contains stack //! memory. This region begins at \a region_base. On return, if \a //! region_base is decremented, \a region_size will be incremented //! appropriately. //! \param[in] user_tag The Mach VM system’s user tag for the region described //! by the initial values of \a region_base and \a region_size. The red //! zone will only be allowed to extend out of the region described by //! these initial values if the user tag is appropriate for stack memory //! and the expanded region has the same user tag value. void LocateRedZone(vm_address_t* const start_address, vm_address_t* const region_base, vm_address_t* const region_size, const unsigned int user_tag) { // x86_64 has a red zone. See AMD64 ABI 0.99.8, // https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/uploads/01de35b2c8adc7545de52604cc45d942/x86-64-psABI-2021-05-20.pdf#page=23. // section 3.2.2, “The Stack Frame”. // So does ARM64, // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Respect-the-Stacks-Red-Zone // section "Respect the Stack’s Red Zone". constexpr vm_size_t kRedZoneSize = 128; vm_address_t red_zone_base = *start_address >= kRedZoneSize ? *start_address - kRedZoneSize : 0; bool red_zone_ok = false; if (red_zone_base >= *region_base) { // The red zone is within the region already discovered. red_zone_ok = true; } else if (red_zone_base < *region_base && user_tag == VM_MEMORY_STACK) { // Probe to see if there’s a region immediately below the one already // discovered. vm_address_t red_zone_region_base = red_zone_base; vm_size_t red_zone_region_size; natural_t red_zone_depth = 0; vm_prot_t red_zone_protection; unsigned int red_zone_user_tag; kern_return_t kr = MachVMRegionRecurseDeepest(mach_task_self(), &red_zone_region_base, &red_zone_region_size, &red_zone_depth, &red_zone_protection, &red_zone_user_tag); if (kr != KERN_SUCCESS) { *start_address = *region_base; } else if (red_zone_region_base + red_zone_region_size == *region_base && (red_zone_protection & VM_PROT_READ) != 0 && red_zone_user_tag == user_tag) { // The region containing the red zone is immediately below the region // already found, it’s readable (not the guard region), and it has the // same user tag as the region already found, so merge them. red_zone_ok = true; *region_base -= red_zone_region_size; *region_size += red_zone_region_size; } } if (red_zone_ok) { // Begin capturing from the base of the red zone (but not the entire // region that encompasses the red zone). *start_address = red_zone_base; } else { // The red zone would go lower into another region in memory, but no // region was found. Memory can only be captured to an address as low as // the base address of the region already found. *start_address = *region_base; } } //! \brief Calculates the base address and size of the region used as a //! thread’s stack. //! //! The region returned by this method may be formed by merging multiple //! adjacent regions in a process’ memory map if appropriate. The base address //! of the returned region may be lower than the \a stack_pointer passed in //! when the ABI mandates a red zone below the stack pointer. //! //! \param[in] stack_pointer The stack pointer, referring to the top (lowest //! address) of a thread’s stack. //! \param[out] stack_region_size The size of the memory region used as the //! thread’s stack. //! //! \return The base address (lowest address) of the memory region used as the //! thread’s stack. vm_address_t CalculateStackRegion(vm_address_t stack_pointer, vm_size_t* stack_region_size) { // For pthreads, it may be possible to compute the stack region based on the // internal _pthread::stackaddr and _pthread::stacksize. The _pthread struct // for a thread can be located at TSD slot 0, or the known offsets of // stackaddr and stacksize from the TSD area could be used. vm_address_t region_base = stack_pointer; vm_size_t region_size; natural_t depth = 0; vm_prot_t protection; unsigned int user_tag; kern_return_t kr = MachVMRegionRecurseDeepest(mach_task_self(), ®ion_base, ®ion_size, &depth, &protection, &user_tag); if (kr != KERN_SUCCESS) { CRASHPAD_RAW_LOG_ERROR(kr, "MachVMRegionRecurseDeepest"); *stack_region_size = 0; return 0; } if (region_base > stack_pointer) { // There’s nothing mapped at the stack pointer’s address. Something may have // trashed the stack pointer. Note that this shouldn’t happen for a normal // stack guard region violation because the guard region is mapped but has // VM_PROT_NONE protection. *stack_region_size = 0; return 0; } vm_address_t start_address = stack_pointer; if ((protection & VM_PROT_READ) == 0) { // If the region isn’t readable, the stack pointer probably points to the // guard region. Don’t include it as part of the stack, and don’t include // anything at any lower memory address. The code below may still possibly // find the real stack region at a memory address higher than this region. start_address = region_base + region_size; } else { // If the ABI requires a red zone, adjust the region to include it if // possible. LocateRedZone(&start_address, ®ion_base, ®ion_size, user_tag); // Regardless of whether the ABI requires a red zone, capture up to // kExtraCaptureSize additional bytes of stack, but only if present in the // region that was already found. constexpr vm_size_t kExtraCaptureSize = 128; start_address = std::max(start_address >= kExtraCaptureSize ? start_address - kExtraCaptureSize : start_address, region_base); // Align start_address to a 16-byte boundary, which can help readers by // ensuring that data is aligned properly. This could page-align instead, // but that might be wasteful. constexpr vm_size_t kDesiredAlignment = 16; start_address &= ~(kDesiredAlignment - 1); DCHECK_GE(start_address, region_base); } region_size -= (start_address - region_base); region_base = start_address; vm_size_t total_region_size = region_size; // The stack region may have gotten split up into multiple abutting regions. // Try to coalesce them. This frequently happens for the main thread’s stack // when setrlimit(RLIMIT_STACK, …) is called. It may also happen if a region // is split up due to an mprotect() or vm_protect() call. // // Stack regions created by the kernel and the pthreads library will be marked // with the VM_MEMORY_STACK user tag. Scanning for multiple adjacent regions // with the same tag should find an entire stack region. Checking that the // protection on individual regions is not VM_PROT_NONE should guarantee that // this algorithm doesn’t collect map entries belonging to another thread’s // stack: well-behaved stacks (such as those created by the kernel and the // pthreads library) have VM_PROT_NONE guard regions at their low-address // ends. // // Other stack regions may not be so well-behaved and thus if user_tag is not // VM_MEMORY_STACK, the single region that was found is used as-is without // trying to merge it with other adjacent regions. if (user_tag == VM_MEMORY_STACK) { vm_address_t try_address = region_base; vm_address_t original_try_address; while (try_address += region_size, original_try_address = try_address, (kr = MachVMRegionRecurseDeepest(mach_task_self(), &try_address, ®ion_size, &depth, &protection, &user_tag) == KERN_SUCCESS) && try_address == original_try_address && (protection & VM_PROT_READ) != 0 && user_tag == VM_MEMORY_STACK) { total_region_size += region_size; } if (kr != KERN_SUCCESS && kr != KERN_INVALID_ADDRESS) { // Tolerate KERN_INVALID_ADDRESS because it will be returned when there // are no more regions in the map at or above the specified |try_address|. CRASHPAD_RAW_LOG_ERROR(kr, "MachVMRegionRecurseDeepest"); } } *stack_region_size = total_region_size; return region_base; } //! \brief Write data around \a address to intermediate dump. Must be called //! from within a ScopedArray. void MaybeCaptureMemoryAround(IOSIntermediateDumpWriter* writer, uint64_t address) { constexpr uint64_t non_address_offset = 0x10000; if (address < non_address_offset) return; constexpr uint64_t max_address = std::numeric_limits::max(); if (address > max_address - non_address_offset) return; constexpr uint64_t kRegisterByteOffset = 128; const uint64_t target = address - kRegisterByteOffset; constexpr uint64_t size = 512; static_assert(kRegisterByteOffset <= size / 2, "negative offset too large"); IOSIntermediateDumpWriter::ScopedArrayMap memory_region(writer); WriteProperty( writer, IntermediateDumpKey::kThreadContextMemoryRegionAddress, &target); // Don't use WritePropertyBytes, this one will fail regularly if |target| // cannot be read. writer->AddPropertyBytes(IntermediateDumpKey::kThreadContextMemoryRegionData, reinterpret_cast(target), size); } void CaptureMemoryPointedToByThreadState(IOSIntermediateDumpWriter* writer, thread_state_type thread_state) { IOSIntermediateDumpWriter::ScopedArray memory_regions( writer, IntermediateDumpKey::kThreadContextMemoryRegions); #if defined(ARCH_CPU_X86_64) MaybeCaptureMemoryAround(writer, thread_state.__rax); MaybeCaptureMemoryAround(writer, thread_state.__rbx); MaybeCaptureMemoryAround(writer, thread_state.__rcx); MaybeCaptureMemoryAround(writer, thread_state.__rdx); MaybeCaptureMemoryAround(writer, thread_state.__rdi); MaybeCaptureMemoryAround(writer, thread_state.__rsi); MaybeCaptureMemoryAround(writer, thread_state.__rbp); MaybeCaptureMemoryAround(writer, thread_state.__r8); MaybeCaptureMemoryAround(writer, thread_state.__r9); MaybeCaptureMemoryAround(writer, thread_state.__r10); MaybeCaptureMemoryAround(writer, thread_state.__r11); MaybeCaptureMemoryAround(writer, thread_state.__r12); MaybeCaptureMemoryAround(writer, thread_state.__r13); MaybeCaptureMemoryAround(writer, thread_state.__r14); MaybeCaptureMemoryAround(writer, thread_state.__r15); MaybeCaptureMemoryAround(writer, thread_state.__rip); #elif defined(ARCH_CPU_ARM_FAMILY) MaybeCaptureMemoryAround(writer, arm_thread_state64_get_pc(thread_state)); for (size_t i = 0; i < std::size(thread_state.__x); ++i) { MaybeCaptureMemoryAround(writer, thread_state.__x[i]); } #endif } void WriteCrashpadSimpleAnnotationsDictionary(IOSIntermediateDumpWriter* writer, CrashpadInfo* crashpad_info) { if (!crashpad_info->simple_annotations()) return; ScopedVMRead simple_annotations; if (!simple_annotations.Read(crashpad_info->simple_annotations())) { CRASHPAD_RAW_LOG("Unable to read simple annotations."); return; } const size_t count = simple_annotations->GetCount(); if (!count) return; IOSIntermediateDumpWriter::ScopedArray annotations_array( writer, IntermediateDumpKey::kAnnotationsSimpleMap); SimpleStringDictionary::Entry* entries = reinterpret_cast( simple_annotations.get()); for (size_t index = 0; index < count; index++) { IOSIntermediateDumpWriter::ScopedArrayMap annotation_map(writer); const auto& entry = entries[index]; size_t key_length = strnlen(entry.key, sizeof(entry.key)); WritePropertyBytes(writer, IntermediateDumpKey::kAnnotationName, reinterpret_cast(entry.key), key_length); size_t value_length = strnlen(entry.value, sizeof(entry.value)); WritePropertyBytes(writer, IntermediateDumpKey::kAnnotationValue, reinterpret_cast(entry.value), value_length); } } void WriteAppleCrashReporterAnnotations( IOSIntermediateDumpWriter* writer, crashreporter_annotations_t* crash_info) { // It seems prudent to enforce some limit. Different users of // CRSetCrashLogMessage and CRSetCrashLogMessage2, apparently the private // functions used to set message and message2, use // different buffer lengths. dyld-1231.3 libdyld/dyld_process_info.cpp has // `static char sCrashReporterInfo[4096]`, which seems like a reasonable // limit. constexpr size_t kMaxMessageSize = 4096; IOSIntermediateDumpWriter::ScopedMap annotation_map( writer, IntermediateDumpKey::kAnnotationsCrashInfo); if (crash_info->message) { const size_t message_len = strnlen( reinterpret_cast(crash_info->message), kMaxMessageSize); WritePropertyBytes(writer, IntermediateDumpKey::kAnnotationsCrashInfoMessage1, reinterpret_cast(crash_info->message), message_len); } if (crash_info->message2) { const size_t message_len = strnlen( reinterpret_cast(crash_info->message2), kMaxMessageSize); WritePropertyBytes(writer, IntermediateDumpKey::kAnnotationsCrashInfoMessage2, reinterpret_cast(crash_info->message2), message_len); } } } // namespace // static void InProcessIntermediateDumpHandler::WriteHeader( IOSIntermediateDumpWriter* writer) { static constexpr uint8_t version = 1; WriteProperty(writer, IntermediateDumpKey::kVersion, &version); } // static void InProcessIntermediateDumpHandler::WriteProcessInfo( IOSIntermediateDumpWriter* writer, const std::map& annotations) { IOSIntermediateDumpWriter::ScopedMap process_map( writer, IntermediateDumpKey::kProcessInfo); timeval snapshot_time; if (gettimeofday(&snapshot_time, nullptr) == 0) { WriteProperty(writer, IntermediateDumpKey::kSnapshotTime, &snapshot_time); } else { CRASHPAD_RAW_LOG("gettimeofday"); } // Used by pid, parent pid and snapshot time. kinfo_proc kern_proc_info; int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; size_t len = sizeof(kern_proc_info); if (sysctl(mib, std::size(mib), &kern_proc_info, &len, nullptr, 0) == 0) { WriteProperty( writer, IntermediateDumpKey::kPID, &kern_proc_info.kp_proc.p_pid); WriteProperty(writer, IntermediateDumpKey::kParentPID, &kern_proc_info.kp_eproc.e_ppid); WriteProperty(writer, IntermediateDumpKey::kStartTime, &kern_proc_info.kp_proc.p_starttime); } else { CRASHPAD_RAW_LOG("sysctl kern_proc_info"); } // Used by user time and system time. mach_task_basic_info task_basic_info; mach_msg_type_number_t task_basic_info_count = MACH_TASK_BASIC_INFO_COUNT; kern_return_t kr = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, reinterpret_cast(&task_basic_info), &task_basic_info_count); if (kr == KERN_SUCCESS) { IOSIntermediateDumpWriter::ScopedMap task_info( writer, IntermediateDumpKey::kTaskBasicInfo); WriteProperty( writer, IntermediateDumpKey::kUserTime, &task_basic_info.user_time); WriteProperty( writer, IntermediateDumpKey::kSystemTime, &task_basic_info.system_time); } else { CRASHPAD_RAW_LOG("task_info task_basic_info"); } task_thread_times_info_data_t task_thread_times; mach_msg_type_number_t task_thread_times_count = TASK_THREAD_TIMES_INFO_COUNT; kr = task_info(mach_task_self(), TASK_THREAD_TIMES_INFO, reinterpret_cast(&task_thread_times), &task_thread_times_count); if (kr == KERN_SUCCESS) { IOSIntermediateDumpWriter::ScopedMap task_thread_times_map( writer, IntermediateDumpKey::kTaskThreadTimes); WriteProperty( writer, IntermediateDumpKey::kUserTime, &task_thread_times.user_time); WriteProperty(writer, IntermediateDumpKey::kSystemTime, &task_thread_times.system_time); } else { CRASHPAD_RAW_LOG("task_info thread_times_info"); } if (!annotations.empty()) { IOSIntermediateDumpWriter::ScopedArray simple_annotations_array( writer, IntermediateDumpKey::kAnnotationsSimpleMap); for (const auto& annotation_pair : annotations) { const std::string& key = annotation_pair.first; const std::string& value = annotation_pair.second; IOSIntermediateDumpWriter::ScopedArrayMap annotation_map(writer); WriteProperty(writer, IntermediateDumpKey::kAnnotationName, key.c_str(), key.length()); WriteProperty(writer, IntermediateDumpKey::kAnnotationValue, value.c_str(), value.length()); } } } // static void InProcessIntermediateDumpHandler::WriteSystemInfo( IOSIntermediateDumpWriter* writer, const IOSSystemDataCollector& system_data, uint64_t report_time_nanos) { IOSIntermediateDumpWriter::ScopedMap system_map( writer, IntermediateDumpKey::kSystemInfo); const std::string& machine_description = system_data.MachineDescription(); WriteProperty(writer, IntermediateDumpKey::kMachineDescription, machine_description.c_str(), machine_description.length()); int os_version_major; int os_version_minor; int os_version_bugfix; system_data.OSVersion( &os_version_major, &os_version_minor, &os_version_bugfix); WriteProperty( writer, IntermediateDumpKey::kOSVersionMajor, &os_version_major); WriteProperty( writer, IntermediateDumpKey::kOSVersionMinor, &os_version_minor); WriteProperty( writer, IntermediateDumpKey::kOSVersionBugfix, &os_version_bugfix); const std::string& os_version_build = system_data.Build(); WriteProperty(writer, IntermediateDumpKey::kOSVersionBuild, os_version_build.c_str(), os_version_build.length()); int cpu_count = system_data.ProcessorCount(); WriteProperty(writer, IntermediateDumpKey::kCpuCount, &cpu_count); const std::string& cpu_vendor = system_data.CPUVendor(); WriteProperty(writer, IntermediateDumpKey::kCpuVendor, cpu_vendor.c_str(), cpu_vendor.length()); bool has_daylight_saving_time = system_data.HasDaylightSavingTime(); WriteProperty(writer, IntermediateDumpKey::kHasDaylightSavingTime, &has_daylight_saving_time); bool is_daylight_saving_time = system_data.IsDaylightSavingTime(); WriteProperty(writer, IntermediateDumpKey::kIsDaylightSavingTime, &is_daylight_saving_time); int standard_offset_seconds = system_data.StandardOffsetSeconds(); WriteProperty(writer, IntermediateDumpKey::kStandardOffsetSeconds, &standard_offset_seconds); int daylight_offset_seconds = system_data.DaylightOffsetSeconds(); WriteProperty(writer, IntermediateDumpKey::kDaylightOffsetSeconds, &daylight_offset_seconds); const std::string& standard_name = system_data.StandardName(); WriteProperty(writer, IntermediateDumpKey::kStandardName, standard_name.c_str(), standard_name.length()); const std::string& daylight_name = system_data.DaylightName(); WriteProperty(writer, IntermediateDumpKey::kDaylightName, daylight_name.c_str(), daylight_name.length()); uint64_t address_mask = system_data.AddressMask(); WriteProperty(writer, IntermediateDumpKey::kAddressMask, &address_mask); vm_size_t page_size; host_page_size(mach_host_self(), &page_size); WriteProperty(writer, IntermediateDumpKey::kPageSize, &page_size); mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t); vm_statistics_data_t vm_stat; kern_return_t kr = host_statistics(mach_host_self(), HOST_VM_INFO, reinterpret_cast(&vm_stat), &host_size); if (kr == KERN_SUCCESS) { IOSIntermediateDumpWriter::ScopedMap vm_stat_map( writer, IntermediateDumpKey::kVMStat); WriteProperty(writer, IntermediateDumpKey::kActive, &vm_stat.active_count); WriteProperty( writer, IntermediateDumpKey::kInactive, &vm_stat.inactive_count); WriteProperty(writer, IntermediateDumpKey::kWired, &vm_stat.wire_count); WriteProperty(writer, IntermediateDumpKey::kFree, &vm_stat.free_count); } else { CRASHPAD_RAW_LOG("host_statistics"); } uint64_t crashpad_uptime_nanos = report_time_nanos - system_data.InitializationTime(); WriteProperty( writer, IntermediateDumpKey::kCrashpadUptime, &crashpad_uptime_nanos); } // static void InProcessIntermediateDumpHandler::WriteThreadInfo( IOSIntermediateDumpWriter* writer, const uint64_t* frames, const size_t num_frames) { IOSIntermediateDumpWriter::ScopedArray thread_array( writer, IntermediateDumpKey::kThreads); // Exception thread ID. #if defined(ARCH_CPU_ARM64) uint64_t exception_thread_id = 0; #endif thread_identifier_info identifier_info; mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; kern_return_t kr = thread_info(mach_thread_self(), THREAD_IDENTIFIER_INFO, reinterpret_cast(&identifier_info), &count); if (kr == KERN_SUCCESS) { #if defined(ARCH_CPU_ARM64) exception_thread_id = identifier_info.thread_id; #endif } else { CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_IDENTIFIER_INFO"); } mach_msg_type_number_t thread_count = 0; thread_act_array_t threads; kr = task_threads(mach_task_self(), &threads, &thread_count); if (kr != KERN_SUCCESS) { CRASHPAD_RAW_LOG_ERROR(kr, "task_threads"); } ScopedTaskThreads threads_vm_owner(threads, thread_count); for (uint32_t thread_index = 0; thread_index < thread_count; ++thread_index) { IOSIntermediateDumpWriter::ScopedArrayMap thread_map(writer); thread_t thread = threads[thread_index]; thread_basic_info basic_info; count = THREAD_BASIC_INFO_COUNT; kr = thread_info(thread, THREAD_BASIC_INFO, reinterpret_cast(&basic_info), &count); if (kr == KERN_SUCCESS) { WriteProperty(writer, IntermediateDumpKey::kSuspendCount, &basic_info.suspend_count); } else { CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_BASIC_INFO"); } thread_extended_info extended_info; count = THREAD_EXTENDED_INFO_COUNT; kr = thread_info(thread, THREAD_EXTENDED_INFO, reinterpret_cast(&extended_info), &count); if (kr == KERN_SUCCESS) { WritePropertyBytes( writer, IntermediateDumpKey::kThreadName, reinterpret_cast(extended_info.pth_name), strnlen(extended_info.pth_name, sizeof(extended_info.pth_name))); } else { CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_EXTENDED_INFO"); } thread_precedence_policy precedence; count = THREAD_PRECEDENCE_POLICY_COUNT; boolean_t get_default = FALSE; kr = thread_policy_get(thread, THREAD_PRECEDENCE_POLICY, reinterpret_cast(&precedence), &count, &get_default); if (kr == KERN_SUCCESS) { WriteProperty( writer, IntermediateDumpKey::kPriority, &precedence.importance); } else { CRASHPAD_RAW_LOG_ERROR(kr, "thread_policy_get"); } // Thread ID. #if defined(ARCH_CPU_ARM64) uint64_t thread_id; #endif count = THREAD_IDENTIFIER_INFO_COUNT; kr = thread_info(thread, THREAD_IDENTIFIER_INFO, reinterpret_cast(&identifier_info), &count); if (kr == KERN_SUCCESS) { #if defined(ARCH_CPU_ARM64) thread_id = identifier_info.thread_id; #endif WriteProperty( writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id); WriteProperty(writer, IntermediateDumpKey::kThreadDataAddress, &identifier_info.thread_handle); } else { CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_IDENTIFIER_INFO"); } // thread_snapshot_ios_intermediate_dump::GenerateStackMemoryFromFrames is // only implemented for arm64, so no x86_64 block here. #if defined(ARCH_CPU_ARM64) // For uncaught NSExceptions, use the frames passed from the system rather // than the current thread state. if (num_frames > 0 && exception_thread_id == thread_id) { WriteProperty(writer, IntermediateDumpKey::kThreadUncaughtNSExceptionFrames, frames, num_frames); continue; } #endif #if defined(ARCH_CPU_X86_64) x86_thread_state64_t thread_state; x86_float_state64_t float_state; x86_debug_state64_t debug_state; mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT; mach_msg_type_number_t float_state_count = x86_FLOAT_STATE64_COUNT; mach_msg_type_number_t debug_state_count = x86_DEBUG_STATE64_COUNT; #elif defined(ARCH_CPU_ARM64) arm_thread_state64_t thread_state; arm_neon_state64_t float_state; arm_debug_state64_t debug_state; mach_msg_type_number_t thread_state_count = ARM_THREAD_STATE64_COUNT; mach_msg_type_number_t float_state_count = ARM_NEON_STATE64_COUNT; mach_msg_type_number_t debug_state_count = ARM_DEBUG_STATE64_COUNT; #endif kr = thread_get_state(thread, kThreadStateFlavor, reinterpret_cast(&thread_state), &thread_state_count); if (kr != KERN_SUCCESS) { CRASHPAD_RAW_LOG_ERROR(kr, "thread_get_state::kThreadStateFlavor"); } WriteProperty(writer, IntermediateDumpKey::kThreadState, &thread_state); kr = thread_get_state(thread, kFloatStateFlavor, reinterpret_cast(&float_state), &float_state_count); if (kr != KERN_SUCCESS) { CRASHPAD_RAW_LOG_ERROR(kr, "thread_get_state::kFloatStateFlavor"); } WriteProperty(writer, IntermediateDumpKey::kFloatState, &float_state); kr = thread_get_state(thread, kDebugStateFlavor, reinterpret_cast(&debug_state), &debug_state_count); if (kr != KERN_SUCCESS) { CRASHPAD_RAW_LOG_ERROR(kr, "thread_get_state::kDebugStateFlavor"); } WriteProperty(writer, IntermediateDumpKey::kDebugState, &debug_state); #if defined(ARCH_CPU_X86_64) vm_address_t stack_pointer = thread_state.__rsp; #elif defined(ARCH_CPU_ARM64) vm_address_t stack_pointer = arm_thread_state64_get_sp(thread_state); #endif vm_size_t stack_region_size; const vm_address_t stack_region_address = CalculateStackRegion(stack_pointer, &stack_region_size); WriteProperty(writer, IntermediateDumpKey::kStackRegionAddress, &stack_region_address); WritePropertyBytes(writer, IntermediateDumpKey::kStackRegionData, reinterpret_cast(stack_region_address), stack_region_size); // Grab extra memory from context. CaptureMemoryPointedToByThreadState(writer, thread_state); } } // static void InProcessIntermediateDumpHandler::WriteModuleInfo( IOSIntermediateDumpWriter* writer) { #ifndef ARCH_CPU_64_BITS #error Only 64-bit Mach-O is supported #endif IOSIntermediateDumpWriter::ScopedArray module_array( writer, IntermediateDumpKey::kModules); task_dyld_info_data_t dyld_info; mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; kern_return_t kr = task_info(mach_task_self(), TASK_DYLD_INFO, reinterpret_cast(&dyld_info), &count); if (kr != KERN_SUCCESS) { CRASHPAD_RAW_LOG_ERROR(kr, "task_info"); } ScopedVMRead image_infos; if (!image_infos.Read(dyld_info.all_image_info_addr)) { CRASHPAD_RAW_LOG("Unable to dyld_info.all_image_info_addr"); return; } uint32_t image_count = image_infos->infoArrayCount; const dyld_image_info* image_array = image_infos->infoArray; for (int32_t image_index = image_count - 1; image_index >= 0; --image_index) { IOSIntermediateDumpWriter::ScopedArrayMap modules(writer); ScopedVMRead image; if (!image.Read(&image_array[image_index])) { CRASHPAD_RAW_LOG("Unable to dyld_image_info"); continue; } if (image->imageFilePath) { WritePropertyCString( writer, IntermediateDumpKey::kName, PATH_MAX, image->imageFilePath); } uint64_t address = FromPointerCast(image->imageLoadAddress); WriteProperty(writer, IntermediateDumpKey::kAddress, &address); WriteProperty( writer, IntermediateDumpKey::kTimestamp, &image->imageFileModDate); WriteModuleInfoAtAddress(writer, address, false /*is_dyld=false*/); } { IOSIntermediateDumpWriter::ScopedArrayMap modules(writer); if (image_infos->dyldPath) { WritePropertyCString( writer, IntermediateDumpKey::kName, PATH_MAX, image_infos->dyldPath); } uint64_t address = FromPointerCast(image_infos->dyldImageLoadAddress); WriteProperty(writer, IntermediateDumpKey::kAddress, &address); WriteModuleInfoAtAddress(writer, address, true /*is_dyld=true*/); } } // static void InProcessIntermediateDumpHandler::WriteExceptionFromSignal( IOSIntermediateDumpWriter* writer, const IOSSystemDataCollector& system_data, siginfo_t* siginfo, ucontext_t* context) { IOSIntermediateDumpWriter::ScopedMap signal_exception_map( writer, IntermediateDumpKey::kSignalException); WriteProperty(writer, IntermediateDumpKey::kSignalNumber, &siginfo->si_signo); WriteProperty(writer, IntermediateDumpKey::kSignalCode, &siginfo->si_code); WriteProperty(writer, IntermediateDumpKey::kSignalAddress, &siginfo->si_addr); #if defined(ARCH_CPU_X86_64) x86_thread_state64_t thread_state = context->uc_mcontext->__ss; x86_float_state64_t float_state = context->uc_mcontext->__fs; #elif defined(ARCH_CPU_ARM64) arm_thread_state64_t thread_state = context->uc_mcontext->__ss; arm_neon_state64_t float_state = context->uc_mcontext->__ns; #else #error Port to your CPU architecture #endif WriteProperty(writer, IntermediateDumpKey::kThreadState, &thread_state); WriteProperty(writer, IntermediateDumpKey::kFloatState, &float_state); CaptureMemoryPointedToByThreadState(writer, thread_state); // Thread ID. thread_identifier_info identifier_info; mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; kern_return_t kr = thread_info(mach_thread_self(), THREAD_IDENTIFIER_INFO, reinterpret_cast(&identifier_info), &count); if (kr == KERN_SUCCESS) { WriteProperty( writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id); } else { CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::self"); } } // static void InProcessIntermediateDumpHandler::WriteExceptionFromMachException( IOSIntermediateDumpWriter* writer, exception_behavior_t behavior, thread_t exception_thread, exception_type_t exception, const mach_exception_data_type_t* code, mach_msg_type_number_t code_count, thread_state_flavor_t flavor, ConstThreadState state, mach_msg_type_number_t state_count) { IOSIntermediateDumpWriter::ScopedMap mach_exception_map( writer, IntermediateDumpKey::kMachException); WriteProperty(writer, IntermediateDumpKey::kException, &exception); WriteProperty(writer, IntermediateDumpKey::kCodes, code, code_count); WriteProperty(writer, IntermediateDumpKey::kFlavor, &flavor); WritePropertyBytes(writer, IntermediateDumpKey::kState, state, state_count * sizeof(uint32_t)); thread_identifier_info identifier_info; mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; kern_return_t kr = thread_info(exception_thread, THREAD_IDENTIFIER_INFO, reinterpret_cast(&identifier_info), &count); if (kr == KERN_SUCCESS) { WriteProperty( writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id); } else { CRASHPAD_RAW_LOG_ERROR(kr, "thread_info"); } } // static void InProcessIntermediateDumpHandler::WriteExceptionFromNSException( IOSIntermediateDumpWriter* writer) { IOSIntermediateDumpWriter::ScopedMap nsexception_map( writer, IntermediateDumpKey::kNSException); thread_identifier_info identifier_info; mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; kern_return_t kr = thread_info(mach_thread_self(), THREAD_IDENTIFIER_INFO, reinterpret_cast(&identifier_info), &count); if (kr == KERN_SUCCESS) { WriteProperty( writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id); } else { CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::self"); } } void InProcessIntermediateDumpHandler::WriteModuleInfoAtAddress( IOSIntermediateDumpWriter* writer, uint64_t address, bool is_dyld) { ScopedVMRead header; if (!header.Read(address) || header->magic != MH_MAGIC_64) { CRASHPAD_RAW_LOG("Invalid module header"); return; } const load_command* unsafe_command_ptr = reinterpret_cast( reinterpret_cast(address) + 1); // Rather than using an individual ScopedVMRead for each load_command, load // the entire block of commands at once. ScopedVMRead all_commands; if (!all_commands.Read(unsafe_command_ptr, header->sizeofcmds)) { CRASHPAD_RAW_LOG("Unable to read module load_commands."); return; } // All the *_vm_read_ptr variables in the load_command loop below have been // vm_read in `all_commands` above, and may be dereferenced without additional // ScopedVMReads. const load_command* command_vm_read_ptr = reinterpret_cast(all_commands.get()); // Make sure that the basic load command structure doesn’t overflow the // space allotted for load commands, as well as iterating through ncmds. vm_size_t slide = 0; for (uint32_t cmd_index = 0, cumulative_cmd_size = 0; cmd_index < header->ncmds && cumulative_cmd_size < header->sizeofcmds; ++cmd_index) { if (command_vm_read_ptr->cmd == LC_SEGMENT_64) { const segment_command_64* segment_vm_read_ptr = reinterpret_cast(command_vm_read_ptr); if (strcmp(segment_vm_read_ptr->segname, SEG_TEXT) == 0) { WriteProperty( writer, IntermediateDumpKey::kSize, &segment_vm_read_ptr->vmsize); slide = address - segment_vm_read_ptr->vmaddr; } else if (strcmp(segment_vm_read_ptr->segname, SEG_DATA) == 0 || // dyld puts __crash_info in __DATA_DIRTY. strcmp(segment_vm_read_ptr->segname, "__DATA_DIRTY") == 0) { WriteDataSegmentAnnotations(writer, segment_vm_read_ptr, slide); } } else if (command_vm_read_ptr->cmd == LC_ID_DYLIB) { const dylib_command* dylib_vm_read_ptr = reinterpret_cast(command_vm_read_ptr); WriteProperty(writer, IntermediateDumpKey::kDylibCurrentVersion, &dylib_vm_read_ptr->dylib.current_version); } else if (command_vm_read_ptr->cmd == LC_SOURCE_VERSION) { const source_version_command* source_version_vm_read_ptr = reinterpret_cast(command_vm_read_ptr); WriteProperty(writer, IntermediateDumpKey::kSourceVersion, &source_version_vm_read_ptr->version); } else if (command_vm_read_ptr->cmd == LC_UUID) { const uuid_command* uuid_vm_read_ptr = reinterpret_cast(command_vm_read_ptr); WriteProperty( writer, IntermediateDumpKey::kUUID, &uuid_vm_read_ptr->uuid); } cumulative_cmd_size += command_vm_read_ptr->cmdsize; command_vm_read_ptr = reinterpret_cast( reinterpret_cast(command_vm_read_ptr) + command_vm_read_ptr->cmdsize); } WriteProperty(writer, IntermediateDumpKey::kFileType, &header->filetype); } void InProcessIntermediateDumpHandler::WriteDataSegmentAnnotations( IOSIntermediateDumpWriter* writer, const segment_command_64* segment_vm_read_ptr, vm_size_t slide) { const section_64* section_vm_read_ptr = reinterpret_cast( reinterpret_cast(segment_vm_read_ptr) + sizeof(segment_command_64)); for (uint32_t sect_index = 0; sect_index <= segment_vm_read_ptr->nsects; ++sect_index) { if (strcmp(section_vm_read_ptr->sectname, "crashpad_info") == 0) { if (section_vm_read_ptr->size >= sizeof(CrashpadInfo)) { ScopedVMRead crashpad_info; if (crashpad_info.Read(section_vm_read_ptr->addr + slide) && crashpad_info->size() <= section_vm_read_ptr->size && crashpad_info->size() >= sizeof(CrashpadInfo) && crashpad_info->signature() == CrashpadInfo::kSignature && crashpad_info->version() == 1) { WriteCrashpadAnnotationsList(writer, crashpad_info.get()); WriteCrashpadSimpleAnnotationsDictionary(writer, crashpad_info.get()); WriteCrashpadExtraMemoryRanges(writer, crashpad_info.get()); WriteCrashpadIntermediateDumpExtraMemoryRanges(writer, crashpad_info.get()); } } } else if (strcmp(section_vm_read_ptr->sectname, "__crash_info") == 0) { if (section_vm_read_ptr->size >= sizeof(crashreporter_annotations_t)) { ScopedVMRead crash_info; if (crash_info.Read(section_vm_read_ptr->addr + slide) && (crash_info->version == 4 || crash_info->version == 5 || crash_info->version == 7)) { WriteAppleCrashReporterAnnotations(writer, crash_info.get()); } } } section_vm_read_ptr = reinterpret_cast( reinterpret_cast(section_vm_read_ptr) + sizeof(section_64)); } } void InProcessIntermediateDumpHandler::WriteCrashpadAnnotationsList( IOSIntermediateDumpWriter* writer, CrashpadInfo* crashpad_info) { if (!crashpad_info->annotations_list()) { return; } ScopedVMRead annotation_list; if (!annotation_list.Read(crashpad_info->annotations_list())) { CRASHPAD_RAW_LOG("Unable to read annotations list object"); return; } IOSIntermediateDumpWriter::ScopedArray annotations_array( writer, IntermediateDumpKey::kAnnotationObjects); ScopedVMRead current; // Use vm_read() to ensure that the linked-list AnnotationList head (which is // a dummy node of type kInvalid) is valid and copy its memory into a // newly-allocated buffer. // // In the case where the pointer has been clobbered or the memory range is not // readable, skip reading all the Annotations. if (!current.Read(annotation_list->head())) { CRASHPAD_RAW_LOG("Unable to read annotation"); return; } for (size_t index = 0; current->link_node() != annotation_list.get()->tail_pointer() && index < kMaxNumberOfAnnotations; ++index) { ScopedVMRead node; // Like above, use vm_read() to ensure that the node in the linked list is // valid and copy its memory into a newly-allocated buffer. // // In the case where the pointer has been clobbered or the memory range is // not readable, skip reading this and all further Annotations. if (!node.Read(current->link_node())) { CRASHPAD_RAW_LOG("Unable to read annotation"); return; } current.Read(current->link_node()); if (node->size() == 0) continue; if (node->size() > Annotation::kValueMaxSize) { CRASHPAD_RAW_LOG("Incorrect annotation length"); continue; } // For Annotations which support guarding reads from concurrent writes, map // their memory read-write using vm_remap(), then declare a ScopedSpinGuard // which lives for the duration of the read. ScopedVMMap mapped_node; std::optional annotation_guard; if (node->concurrent_access_guard_mode() == Annotation::ConcurrentAccessGuardMode::kScopedSpinGuard) { constexpr vm_prot_t kDesiredProtection = VM_PROT_WRITE | VM_PROT_READ; if (!mapped_node.Map(node.get()) || (mapped_node.CurrentProtection() & kDesiredProtection) != kDesiredProtection) { CRASHPAD_RAW_LOG("Unable to map annotation"); // Skip this annotation rather than giving up entirely, since the linked // node should still be valid. continue; } // TODO(https://crbug.com/crashpad/438): Pass down a `params` object into // this method to optionally enable a timeout here. constexpr uint64_t kTimeoutNanoseconds = 0; annotation_guard = mapped_node->TryCreateScopedSpinGuard(kTimeoutNanoseconds); if (!annotation_guard) { // This is expected if the process is writing to the Annotation, so // don't log here and skip the annotation. continue; } } IOSIntermediateDumpWriter::ScopedArrayMap annotation_map(writer); WritePropertyCString(writer, IntermediateDumpKey::kAnnotationName, Annotation::kNameMaxLength, reinterpret_cast(node->name())); WritePropertyBytes(writer, IntermediateDumpKey::kAnnotationValue, reinterpret_cast(node->value()), node->size()); Annotation::Type type = node->type(); WritePropertyBytes(writer, IntermediateDumpKey::kAnnotationType, reinterpret_cast(&type), sizeof(type)); } } void InProcessIntermediateDumpHandler::WriteCrashpadExtraMemoryRanges( IOSIntermediateDumpWriter* writer, CrashpadInfo* crashpad_info) { if (!crashpad_info->extra_memory_ranges()) { return; } ScopedVMRead extra_memory_ranges; if (!extra_memory_ranges.Read(crashpad_info->extra_memory_ranges())) { CRASHPAD_RAW_LOG("Unable to read extra memory ranges object"); return; } IOSIntermediateDumpWriter::ScopedArray module_extra_memory_regions_array( writer, IntermediateDumpKey::kModuleExtraMemoryRegions); SimpleAddressRangeBag::Iterator iterator(*(extra_memory_ranges.get())); while (const SimpleAddressRangeBag::Entry* entry = iterator.Next()) { const uint64_t& address = entry->base; const uint64_t& size = entry->size; IOSIntermediateDumpWriter::ScopedArrayMap memory_region_map(writer); WriteProperty( writer, IntermediateDumpKey::kModuleExtraMemoryRegionAddress, &address); WritePropertyBytes(writer, IntermediateDumpKey::kModuleExtraMemoryRegionData, reinterpret_cast(address), size); } } void InProcessIntermediateDumpHandler:: WriteCrashpadIntermediateDumpExtraMemoryRanges( IOSIntermediateDumpWriter* writer, CrashpadInfo* crashpad_info) { if (!crashpad_info->intermediate_dump_extra_memory_ranges()) { return; } ScopedVMRead intermediate_dump_extra_memory; if (!intermediate_dump_extra_memory.Read( crashpad_info->intermediate_dump_extra_memory_ranges())) { CRASHPAD_RAW_LOG( "Unable to read intermediate dump extra memory ranges object"); return; } IOSIntermediateDumpWriter::ScopedArray module_extra_memory_regions_array( writer, IntermediateDumpKey::kModuleIntermediateDumpExtraMemoryRegions); SimpleAddressRangeBag::Iterator iterator( *(intermediate_dump_extra_memory.get())); while (const SimpleAddressRangeBag::Entry* entry = iterator.Next()) { const uint64_t& address = entry->base; const uint64_t& size = entry->size; IOSIntermediateDumpWriter::ScopedArrayMap memory_region_map(writer); WriteProperty( writer, IntermediateDumpKey::kModuleIntermediateDumpExtraMemoryRegionAddress, &address); WritePropertyBytes( writer, IntermediateDumpKey::kModuleIntermediateDumpExtraMemoryRegionData, reinterpret_cast(address), size); } } } // namespace internal } // namespace crashpad ================================================ FILE: client/ios_handler/in_process_intermediate_dump_handler.h ================================================ // Copyright 2021 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_INTERMEDIATE_DUMP_HANDLER_H_ #define CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_INTERMEDIATE_DUMP_HANDLER_H_ #include #include #include #include #include "client/crashpad_info.h" #include "util/ios/ios_intermediate_dump_writer.h" #include "util/ios/ios_system_data_collector.h" #include "util/mach/mach_extensions.h" namespace crashpad { namespace internal { //! \brief Dump all in-process data to iOS intermediate dump. //! Note: All methods are `RUNS-DURING-CRASH`. class InProcessIntermediateDumpHandler final { public: InProcessIntermediateDumpHandler() = delete; InProcessIntermediateDumpHandler(const InProcessIntermediateDumpHandler&) = delete; InProcessIntermediateDumpHandler& operator=( const InProcessIntermediateDumpHandler&) = delete; //! \brief Set kVersion to 1. //! //! \param[in] writer The dump writer static void WriteHeader(IOSIntermediateDumpWriter* writer); //! \brief Write ProcessSnapshot data to the intermediate dump. //! //! \param[in] writer The dump writer //! \param[in] annotations The simple map annotations. static void WriteProcessInfo( IOSIntermediateDumpWriter* writer, const std::map& annotations); //! \brief Write SystemSnapshot data to the intermediate dump. //! //! \param[in] writer The dump writer //! \param[in] system_data An object containing various system data points. //! \param[in] report_time_nanos Report creation time in nanoseconds as //! returned by ClockMonotonicNanoseconds(). static void WriteSystemInfo(IOSIntermediateDumpWriter* writer, const IOSSystemDataCollector& system_data, uint64_t report_time_nanos); //! \brief Write ThreadSnapshot data to the intermediate dump. //! //! For uncaught NSExceptions, \a frames and \a num_frames will be added to //! the intermediate dump for the exception thread. Otherwise, or for the //! remaining threads, use `thread_get_state`. //! //! \param[in] writer The dump writer //! \param[in] frames An array of callstack return addresses. //! \param[in] num_frames The number of callstack return address in \a frames. static void WriteThreadInfo(IOSIntermediateDumpWriter* writer, const uint64_t* frames, const size_t num_frames); //! \brief Write ModuleSnapshot data to the intermediate dump. //! //! This includes both modules and annotations. //! //! \param[in] writer The dump writer static void WriteModuleInfo(IOSIntermediateDumpWriter* writer); //! \brief Write an ExceptionSnapshot from a signal to the intermediate dump. //! //! Only one of the WriteExceptionFromSignal, WriteExceptionFromMachException //! and WriteExceptionFromNSException should be called per intermediate dump. //! //! \param[in] writer The dump writer //! \param[in] system_data An object containing various system data points. //! \param[in] siginfo A pointer to a `siginfo_t` object received by a signal //! handler. //! \param[in] context A pointer to a `ucontext_t` object received by a //! signal. static void WriteExceptionFromSignal( IOSIntermediateDumpWriter* writer, const IOSSystemDataCollector& system_data, siginfo_t* siginfo, ucontext_t* context); //! \brief Write an ExceptionSnapshot from a mach exception to the //! intermediate dump. //! //! Only one of the WriteExceptionFromSignal, WriteExceptionFromMachException //! and WriteExceptionFromNSException should be called per intermediate dump. //! //! \param[in] writer The dump writer //! \param[in] behavior //! \param[in] thread //! \param[in] exception //! \param[in] code //! \param[in] code_count //! \param[in] flavor //! \param[in] old_state //! \param[in] old_state_count static void WriteExceptionFromMachException( IOSIntermediateDumpWriter* writer, exception_behavior_t behavior, thread_t thread, exception_type_t exception, const mach_exception_data_type_t* code, mach_msg_type_number_t code_count, thread_state_flavor_t flavor, ConstThreadState old_state, mach_msg_type_number_t old_state_count); //! \brief Write an ExceptionSnapshot from an NSException to the //! intermediate dump. //! //! Only one of the WriteExceptionFromSignal, WriteExceptionFromMachException //! and WriteExceptionFromNSException should be called per intermediate dump. //! //! \param[in] writer The dump writer static void WriteExceptionFromNSException(IOSIntermediateDumpWriter* writer); private: //! \brief Parse and extract module and annotation information from header. static void WriteModuleInfoAtAddress(IOSIntermediateDumpWriter* writer, uint64_t address, bool is_dyld); //! \brief Extract and write Apple crashreporter_annotations_t data and //! Crashpad annotations. Note that \a segment_vm_read_ptr has already //! been read via vm_read and may be dereferenced without a ScopedVMRead. static void WriteDataSegmentAnnotations( IOSIntermediateDumpWriter* writer, const segment_command_64* segment_vm_read_ptr, vm_size_t slide); //! \brief Write Crashpad annotations list. static void WriteCrashpadAnnotationsList(IOSIntermediateDumpWriter* writer, CrashpadInfo* crashpad_info); //! \brief Write Crashpad extra memory data. static void WriteCrashpadExtraMemoryRanges(IOSIntermediateDumpWriter* writer, CrashpadInfo* crashpad_info); //! \brief Write Crashpad intermediate dump extra memory data. static void WriteCrashpadIntermediateDumpExtraMemoryRanges( IOSIntermediateDumpWriter* writer, CrashpadInfo* crashpad_info); }; } // namespace internal } // namespace crashpad #endif // CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_INTERMEDIATE_DUMP_HANDLER_H_ ================================================ FILE: client/ios_handler/in_process_intermediate_dump_handler_test.cc ================================================ // Copyright 2021 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/ios_handler/in_process_intermediate_dump_handler.h" #include #include #include #include #include "base/files/file_path.h" #include "build/build_config.h" #include "client/annotation.h" #include "client/annotation_list.h" #include "client/crashpad_info.h" #include "client/simple_string_dictionary.h" #include "gtest/gtest.h" #include "minidump/minidump_file_writer.h" #include "snapshot/ios/process_snapshot_ios_intermediate_dump.h" #include "snapshot/minidump/process_snapshot_minidump.h" #include "test/scoped_set_thread_name.h" #include "test/scoped_temp_dir.h" #include "test/test_paths.h" #include "util/file/filesystem.h" #include "util/misc/capture_context.h" namespace crashpad { namespace test { namespace { using internal::InProcessIntermediateDumpHandler; class ReadToString : public crashpad::MemorySnapshot::Delegate { public: std::string result; bool MemorySnapshotDelegateRead(void* data, size_t size) override { result = std::string(reinterpret_cast(data), size); return true; } }; class InProcessIntermediateDumpHandlerTest : public testing::Test { protected: // testing::Test: void SetUp() override { path_ = temp_dir_.path().Append("dump_file"); writer_ = std::make_unique(); EXPECT_TRUE(writer_->Open(path_)); ASSERT_TRUE(IsRegularFile(path_)); } void TearDown() override { EXPECT_TRUE(writer_->Close()); writer_.reset(); EXPECT_FALSE(IsRegularFile(path_)); } void WriteReportAndCloseWriter() { { internal::IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer_.get()); InProcessIntermediateDumpHandler::WriteHeader(writer_.get()); InProcessIntermediateDumpHandler::WriteProcessInfo( writer_.get(), {{"before_dump", "pre"}}); InProcessIntermediateDumpHandler::WriteSystemInfo( writer_.get(), system_data_, ClockMonotonicNanoseconds()); InProcessIntermediateDumpHandler::WriteThreadInfo(writer_.get(), 0, 0); InProcessIntermediateDumpHandler::WriteModuleInfo(writer_.get()); } EXPECT_TRUE(writer_->Close()); } void WriteMachException() { crashpad::NativeCPUContext cpu_context; crashpad::CaptureContext(&cpu_context); const mach_exception_data_type_t code[2] = {}; static constexpr int kSimulatedException = -1; InProcessIntermediateDumpHandler::WriteExceptionFromMachException( writer_.get(), MACH_EXCEPTION_CODES, mach_thread_self(), kSimulatedException, code, std::size(code), MACHINE_THREAD_STATE, reinterpret_cast(&cpu_context), MACHINE_THREAD_STATE_COUNT); } const auto& path() const { return path_; } auto writer() const { return writer_.get(); } #if TARGET_OS_SIMULATOR // macOS 14.0 is 23A344, macOS 13.6.5 is 22G621, so if the first two // characters in the kern.osversion are > 22, this build will reproduce the // simulator bug in crbug.com/328282286 // This now reproduces on macOS 15.4 24E248 as well for iOS17 simulators. bool HasMacOSBrokeDYLDTaskInfo() { if (__builtin_available(iOS 18, *)) { return false; } if (std::stoi(system_data_.Build().substr(0, 2)) >= 24) { return true; } if (__builtin_available(iOS 17, *)) { return false; } return std::stoi(system_data_.Build().substr(0, 2)) > 22; } #endif private: std::unique_ptr writer_; internal::IOSSystemDataCollector system_data_; ScopedTempDir temp_dir_; base::FilePath path_; }; TEST_F(InProcessIntermediateDumpHandlerTest, TestSystem) { WriteReportAndCloseWriter(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {})); // Snpahot const SystemSnapshot* system = process_snapshot.System(); ASSERT_NE(system, nullptr); #if defined(ARCH_CPU_X86_64) EXPECT_EQ(system->GetCPUArchitecture(), kCPUArchitectureX86_64); #elif defined(ARCH_CPU_ARM64) EXPECT_EQ(system->GetCPUArchitecture(), kCPUArchitectureARM64); #else #error Port to your CPU architecture #endif #if TARGET_OS_SIMULATOR EXPECT_EQ(system->MachineDescription().substr(0, 13), std::string("iOS Simulator")); #elif TARGET_OS_IPHONE utsname uts; ASSERT_EQ(uname(&uts), 0); EXPECT_STREQ(system->MachineDescription().c_str(), uts.machine); #endif EXPECT_EQ(system->GetOperatingSystem(), SystemSnapshot::kOperatingSystemIOS); } TEST_F(InProcessIntermediateDumpHandlerTest, TestAnnotations) { #if TARGET_OS_SIMULATOR // This test will fail on =14.3 or // =15.4 due to a bug in Simulator. // crbug.com/328282286 if (HasMacOSBrokeDYLDTaskInfo()) { // For TearDown. ASSERT_TRUE(LoggingRemoveFile(path())); return; } #endif // This is “leaked” to crashpad_info. crashpad::SimpleStringDictionary* simple_annotations = new crashpad::SimpleStringDictionary(); simple_annotations->SetKeyValue("#TEST# pad", "break"); simple_annotations->SetKeyValue("#TEST# key", "value"); simple_annotations->SetKeyValue("#TEST# pad", "crash"); simple_annotations->SetKeyValue("#TEST# x", "y"); simple_annotations->SetKeyValue("#TEST# longer", "shorter"); simple_annotations->SetKeyValue("#TEST# empty_value", ""); crashpad::CrashpadInfo* crashpad_info = crashpad::CrashpadInfo::GetCrashpadInfo(); crashpad_info->set_simple_annotations(simple_annotations); crashpad::AnnotationList::Register(); // This is “leaked” to crashpad_info. static crashpad::StringAnnotation<32> test_annotation_one{"#TEST# one"}; static crashpad::StringAnnotation<32> test_annotation_two{"#TEST# two"}; static crashpad::StringAnnotation<32> test_annotation_three{ "#TEST# same-name"}; static crashpad::StringAnnotation<32> test_annotation_four{ "#TEST# same-name"}; test_annotation_one.Set("moocow"); test_annotation_two.Set("this will be cleared"); test_annotation_three.Set("same-name 3"); test_annotation_four.Set("same-name 4"); test_annotation_two.Clear(); WriteReportAndCloseWriter(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.InitializeWithFilePath( path(), {{"after_dump", "post"}})); auto process_map = process_snapshot.AnnotationsSimpleMap(); EXPECT_EQ(process_map.size(), 3u); EXPECT_EQ(process_map["before_dump"], "pre"); EXPECT_EQ(process_map["after_dump"], "post"); EXPECT_TRUE(process_map.find("crashpad_uptime_ns") != process_map.end()); std::map all_annotations_simple_map; std::vector all_annotations; for (const auto* module : process_snapshot.Modules()) { std::map module_annotations_simple_map = module->AnnotationsSimpleMap(); all_annotations_simple_map.insert(module_annotations_simple_map.begin(), module_annotations_simple_map.end()); std::vector annotations = module->AnnotationObjects(); all_annotations.insert( all_annotations.end(), annotations.begin(), annotations.end()); } EXPECT_EQ(all_annotations_simple_map.size(), 5u); EXPECT_EQ(all_annotations_simple_map["#TEST# pad"], "crash"); EXPECT_EQ(all_annotations_simple_map["#TEST# key"], "value"); EXPECT_EQ(all_annotations_simple_map["#TEST# x"], "y"); EXPECT_EQ(all_annotations_simple_map["#TEST# longer"], "shorter"); EXPECT_EQ(all_annotations_simple_map["#TEST# empty_value"], ""); bool saw_same_name_3 = false, saw_same_name_4 = false; for (const auto& annotation : all_annotations) { EXPECT_EQ(annotation.type, static_cast(Annotation::Type::kString)); std::string value(reinterpret_cast(annotation.value.data()), annotation.value.size()); if (annotation.name == "#TEST# one") { EXPECT_EQ(value, "moocow"); } else if (annotation.name == "#TEST# same-name") { if (value == "same-name 3") { EXPECT_FALSE(saw_same_name_3); saw_same_name_3 = true; } else if (value == "same-name 4") { EXPECT_FALSE(saw_same_name_4); saw_same_name_4 = true; } else { ADD_FAILURE() << "unexpected annotation value " << value; } } else { ADD_FAILURE() << "unexpected annotation " << annotation.name; } } } TEST_F(InProcessIntermediateDumpHandlerTest, TestExtraMemoryRanges) { #if TARGET_OS_SIMULATOR // This test will fail on =14.3 or // =15.4 due to a bug in Simulator. // crbug.com/328282286 if (HasMacOSBrokeDYLDTaskInfo()) { // For TearDown. ASSERT_TRUE(LoggingRemoveFile(path())); return; } #endif // Put the string on the heap so the memory doesn't coalesce with the stack. std::unique_ptr someExtraMemoryString( new std::string("extra memory range")); crashpad::SimpleAddressRangeBag* ios_extra_ranges = new crashpad::SimpleAddressRangeBag(); crashpad::CrashpadInfo::GetCrashpadInfo()->set_extra_memory_ranges( ios_extra_ranges); ios_extra_ranges->Insert((void*)someExtraMemoryString->c_str(), 18); WriteReportAndCloseWriter(); crashpad::CrashpadInfo::GetCrashpadInfo()->set_extra_memory_ranges(nullptr); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {})); ASSERT_EQ(process_snapshot.ExtraMemory().size(), 1LU); auto memory = process_snapshot.ExtraMemory()[0]; EXPECT_EQ(memory->Address(), reinterpret_cast(someExtraMemoryString->c_str())); EXPECT_EQ(memory->Size(), 18LU); ReadToString delegate; ASSERT_TRUE(memory->Read(&delegate)); EXPECT_EQ(delegate.result, someExtraMemoryString->c_str()); StringFile string_file; MinidumpFileWriter minidump_file_writer; minidump_file_writer.InitializeFromSnapshot(&process_snapshot); ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); ProcessSnapshotMinidump process_snapshot_minidump; EXPECT_TRUE(process_snapshot_minidump.Initialize(&string_file)); bool found; for (auto minidump_memory : process_snapshot_minidump.ExtraMemory()) { if (minidump_memory->Address() == reinterpret_cast(someExtraMemoryString->c_str()) && minidump_memory->Size() == 18LU) { found = true; break; } } EXPECT_TRUE(found); } TEST_F(InProcessIntermediateDumpHandlerTest, TestIntermediateDumpExtraMemoryRanges) { #if TARGET_OS_SIMULATOR // This test will fail on =14.3 or // =15.4 due to a bug in Simulator. // crbug.com/328282286 if (HasMacOSBrokeDYLDTaskInfo()) { // For TearDown. ASSERT_TRUE(LoggingRemoveFile(path())); return; } #endif // Put the string on the heap so the memory doesn't coalesce with the stack. std::unique_ptr someExtraMemoryString( new std::string("extra memory range")); crashpad::SimpleAddressRangeBag* ios_extra_ranges = new crashpad::SimpleAddressRangeBag(); crashpad::CrashpadInfo::GetCrashpadInfo() ->set_intermediate_dump_extra_memory_ranges(ios_extra_ranges); ios_extra_ranges->Insert((void*)someExtraMemoryString->c_str(), 18); WriteReportAndCloseWriter(); crashpad::CrashpadInfo::GetCrashpadInfo() ->set_intermediate_dump_extra_memory_ranges(nullptr); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {})); ASSERT_EQ(process_snapshot.IntermediateDumpExtraMemory().size(), 1LU); auto memory = process_snapshot.IntermediateDumpExtraMemory()[0]; EXPECT_EQ(memory->Address(), reinterpret_cast(someExtraMemoryString->c_str())); EXPECT_EQ(memory->Size(), 18LU); ReadToString delegate; ASSERT_TRUE(memory->Read(&delegate)); EXPECT_EQ(delegate.result, someExtraMemoryString->c_str()); StringFile string_file; MinidumpFileWriter minidump_file_writer; minidump_file_writer.InitializeFromSnapshot(&process_snapshot); ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); ProcessSnapshotMinidump process_snapshot_minidump; EXPECT_TRUE(process_snapshot_minidump.Initialize(&string_file)); for (auto minidump_memory : process_snapshot_minidump.ExtraMemory()) { EXPECT_FALSE( minidump_memory->Address() == reinterpret_cast(someExtraMemoryString->c_str()) && minidump_memory->Size() == 18LU); } } TEST_F(InProcessIntermediateDumpHandlerTest, TestThreads) { const ScopedSetThreadName scoped_set_thread_name("TestThreads"); WriteReportAndCloseWriter(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {})); const auto& threads = process_snapshot.Threads(); ASSERT_GT(threads.size(), 0u); thread_identifier_info identifier_info; mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; ASSERT_EQ(thread_info(mach_thread_self(), THREAD_IDENTIFIER_INFO, reinterpret_cast(&identifier_info), &count), 0); EXPECT_EQ(threads[0]->ThreadID(), identifier_info.thread_id); EXPECT_EQ(threads[0]->ThreadName(), "TestThreads"); } TEST_F(InProcessIntermediateDumpHandlerTest, TestProcess) { WriteReportAndCloseWriter(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {})); EXPECT_EQ(process_snapshot.ProcessID(), getpid()); } TEST_F(InProcessIntermediateDumpHandlerTest, TestMachException) { WriteReportAndCloseWriter(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {})); } TEST_F(InProcessIntermediateDumpHandlerTest, TestSignalException) { WriteReportAndCloseWriter(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {})); } TEST_F(InProcessIntermediateDumpHandlerTest, TestNSException) { WriteReportAndCloseWriter(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {})); } } // namespace TEST_F(InProcessIntermediateDumpHandlerTest, TestCaptureMemoryPointedToByThreadState) { char test_buffer[1024]; memset(test_buffer, 'A', sizeof(test_buffer)); test_buffer[128] = 'B'; // Use a std::atomic and a std::thread to simulate an arbitrary thread with a // known register state. std::atomic wait_for_main_thread(true); std::atomic thread_started(false); std::string thread_name("CaptureMemoryThread"); std::thread t([&]() { pthread_setname_np(thread_name.c_str()); void* ptr = test_buffer + 128; // Force the compiler to store our pointer in a designated register rather // than on the stack #if defined(ARCH_CPU_ARM64) register void* reg_ptr asm("x20") = ptr; #elif defined(ARCH_CPU_X86_64) register void* reg_ptr asm("r12") = ptr; #endif thread_started = true; while (wait_for_main_thread.load(std::memory_order_relaxed)) { // Dummy operation to prevent the optimizer from discarding our register // assignment. asm volatile("" : : "r"(reg_ptr)); } }); while (!thread_started.load(std::memory_order_relaxed)) { std::this_thread::yield(); } WriteReportAndCloseWriter(); wait_for_main_thread = false; t.join(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {})); bool found_our_buffer = false; for (const auto* thread : process_snapshot.Threads()) { if (thread->ThreadName() == thread_name) { for (const auto* memory : thread->ExtraMemory()) { if (memory->Address() == reinterpret_cast(test_buffer)) { found_our_buffer = true; EXPECT_EQ(memory->Size(), 512u); ReadToString delegate; ASSERT_TRUE(memory->Read(&delegate)); EXPECT_EQ(delegate.result.size(), 512u); EXPECT_EQ(delegate.result[128], 'B'); EXPECT_EQ(delegate.result[129], 'A'); } } } } EXPECT_TRUE(found_our_buffer); } } // namespace test } // namespace crashpad ================================================ FILE: client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.cc ================================================ // Copyright 2021 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h" #include #include "client/prune_crash_reports.h" #include "util/file/directory_reader.h" #include "util/file/filesystem.h" #include "util/ios/scoped_background_task.h" namespace crashpad { namespace { // The file extension used to indicate a file is locked. constexpr char kLockedExtension[] = ".locked"; // Prune onces a day. constexpr time_t prune_interval = 60 * 60 * 24; // If the client finds a locked file matching it's own bundle id, unlock it // after 24 hours. constexpr time_t matching_bundle_locked_ttl = 60 * 60 * 24; // Unlock any locked intermediate dump after 60 days. constexpr time_t max_locked_ttl = 60 * 60 * 24 * 60; // The initial thread delay for applications. Delay the thread's file i/o to // not interfere with application startup. constexpr double app_delay = 60; // The initial thread delay for app extensions. Because iOS extensions are often // very short lived, do not wait the full |app_delay|, and instead use a shorter // time. constexpr double extension_delay = 5; //! \brief Unlocks old intermediate dumps. //! //! This function can unlock (remove the .locked extension) intermediate dumps //! that are either too old to be useful, or are likely leftover dumps from //! clean app exits. //! //! \param[in] pending_path The path to any locked intermediate dump files. //! \param[in] bundle_identifier_and_seperator The identifier for this client, //! used to determine when locked files are considered stale. void UnlockOldIntermediateDumps(base::FilePath pending_path, std::string bundle_identifier_and_seperator) { DirectoryReader reader; std::vector files; if (!reader.Open(pending_path)) { return; } base::FilePath file; DirectoryReader::Result result; while ((result = reader.NextFile(&file)) == DirectoryReader::Result::kSuccess) { if (file.FinalExtension() != kLockedExtension) continue; const base::FilePath file_path(pending_path.Append(file)); timespec file_time; time_t now = time(nullptr); if (!FileModificationTime(file_path, &file_time)) { continue; } if ((file.value().compare(0, bundle_identifier_and_seperator.size(), bundle_identifier_and_seperator) == 0 && file_time.tv_sec <= now - matching_bundle_locked_ttl) || (file_time.tv_sec <= now - max_locked_ttl)) { base::FilePath new_path = file_path.RemoveFinalExtension(); MoveFileOrDirectory(file_path, new_path); continue; } } } } // namespace PruneIntermediateDumpsAndCrashReportsThread:: PruneIntermediateDumpsAndCrashReportsThread( CrashReportDatabase* database, std::unique_ptr condition, base::FilePath pending_path, std::string bundle_identifier_and_seperator, bool is_extension) : thread_(prune_interval, this), condition_(std::move(condition)), pending_path_(pending_path), bundle_identifier_and_seperator_(bundle_identifier_and_seperator), initial_work_delay_(is_extension ? extension_delay : app_delay), last_start_time_(0), database_(database) {} PruneIntermediateDumpsAndCrashReportsThread:: ~PruneIntermediateDumpsAndCrashReportsThread() {} void PruneIntermediateDumpsAndCrashReportsThread::Start() { thread_.Start(initial_work_delay_); } void PruneIntermediateDumpsAndCrashReportsThread::Stop() { thread_.Stop(); } void PruneIntermediateDumpsAndCrashReportsThread::DoWork( const WorkerThread* thread) { // This thread may be stopped and started a number of times throughout the // lifetime of the process to prevent 0xdead10cc kills (see // crbug.com/crashpad/400), but it should only run once per prune_interval // after initial_work_delay_. time_t now = time(nullptr); if (now - last_start_time_ < prune_interval) return; last_start_time_ = now; internal::ScopedBackgroundTask scoper("PruneThread"); database_->CleanDatabase(60 * 60 * 24 * 3); // Here and below, respect Stop() being called after each task. if (!thread_.is_running()) return; PruneCrashReportDatabase(database_, condition_.get()); if (!thread_.is_running()) return; if (!clean_old_intermediate_dumps_) { clean_old_intermediate_dumps_ = true; UnlockOldIntermediateDumps(pending_path_, bundle_identifier_and_seperator_); } } } // namespace crashpad ================================================ FILE: client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h ================================================ // Copyright 2021 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_HANDLER_PRUNE_CRASH_REPORTS_THREAD_H_ #define CRASHPAD_HANDLER_PRUNE_CRASH_REPORTS_THREAD_H_ #include #include "base/files/file_path.h" #include "util/thread/stoppable.h" #include "util/thread/worker_thread.h" namespace crashpad { class CrashReportDatabase; class PruneCondition; //! \brief A thread that periodically prunes crash reports from the database //! using the specified condition, and any leftover locked intermediate //! dumps. //! //! After the thread is started, the database is pruned using the condition //! every 24 hours. Upon calling Start(), the thread waits before performing //! the initial prune operation. //! //! Locked intermediate dump files are unlocked only once, not periodically. //! Locked dumps that match this bundle id can be unlocked if they are over a //! day old. Otherwise, unlock dumps that are over 60 days old. class PruneIntermediateDumpsAndCrashReportsThread : public WorkerThread::Delegate, public Stoppable { public: //! \brief Constructs a new object. //! //! \param[in] database The database to prune crash reports from. //! \param[in] condition The condition used to evaluate crash reports for //! pruning. //! \param[in] pending_path The path to any locked intermediate dump files. //! \param[in] bundle_identifier_and_seperator The identifier for this client, //! used to determine when locked files are considered stale, with a //! seperator at the end to allow for substring searches. //! \param[in] is_extension Whether the process is an app extension. If //! `true`, the inital prune will occur after a 5-second delay. If //! `false`, the initial prune will occur after a 60-second delay. PruneIntermediateDumpsAndCrashReportsThread( CrashReportDatabase* database, std::unique_ptr condition, base::FilePath pending_path, std::string bundle_identifier_and_seperator, bool is_extension); PruneIntermediateDumpsAndCrashReportsThread( const PruneIntermediateDumpsAndCrashReportsThread&) = delete; PruneIntermediateDumpsAndCrashReportsThread& operator=( const PruneIntermediateDumpsAndCrashReportsThread&) = delete; ~PruneIntermediateDumpsAndCrashReportsThread(); // Stoppable: //! \brief Starts a dedicated pruning thread. //! //! The thread waits before running the initial prune, so as to not interfere //! with any startup-related IO performed by the client. //! //! This method may only be be called on a newly-constructed object or after //! a call to Stop(). void Start() override; //! \brief Stops the pruning thread. //! //! This method must only be called after Start(). If Start() has been called, //! this method must be called before destroying an object of this class. //! //! This method may be called from any thread other than the pruning thread. //! It is expected to only be called from the same thread that called Start(). void Stop() override; //! \return `true` if the thread is running, `false` if it is not. bool is_running() const { return thread_.is_running(); } private: // WorkerThread::Delegate: void DoWork(const WorkerThread* thread) override; WorkerThread thread_; std::unique_ptr condition_; base::FilePath pending_path_; std::string bundle_identifier_and_seperator_; bool clean_old_intermediate_dumps_; double initial_work_delay_; time_t last_start_time_; CrashReportDatabase* database_; // weak }; } // namespace crashpad #endif // CRASHPAD_HANDLER_PRUNE_CRASH_REPORTS_THREAD_H_ ================================================ FILE: client/length_delimited_ring_buffer.h ================================================ // Copyright 2023 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_LENGTH_DELIMITED_RING_BUFFER_H_ #define CRASHPAD_CLIENT_LENGTH_DELIMITED_RING_BUFFER_H_ #include #include #include #include #include #include #include #include #include "base/numerics/safe_math.h" namespace crashpad { //! \brief Capacity of a `RingBufferData`, in bytes. using RingBufferCapacity = uint32_t; namespace internal { //! \brief Default capacity of `RingBufferData`, in bytes. inline constexpr RingBufferCapacity kDefaultRingBufferDataCapacity = 8192; //! \brief A tuple holding the current range of bytes which can be read from or //! have been written to. struct Range final { //! \brief The offset into a `RingBufferData` at which a `Range` begins. using Offset = uint32_t; //! \brief The length inside a `RingBufferData` of a `Range` of data. using Length = uint32_t; Offset offset; Length length; }; // This struct is persisted to disk, so its size must not change. static_assert(sizeof(Range) == 8, "struct Range is not packed on this platform"); //! \brief The number of bits encoded in each byte of a Base 128-encoded varint. inline constexpr int kBase128ByteValueBits = 7; //! \!brief Calculates the length in bytes of `value` encoded using //! little-endian Base 128 varint encoding. //! \sa https://developers.google.com/protocol-buffers/docs/encoding#varints //! //! `LengthDelimitedRingBufferWriter` uses varint-encoded delimiters to enable //! zero-copy deserialization of the ringbuffer's contents when storing //! protobufs inside the ringbuffer, e.g. via //! `google::protobuf::util::ParseDelimitedFromZeroCopyStream()` or similar. //! //! \sa //! https://github.com/protocolbuffers/protobuf/blob/3202b9da88ceb75b65bbabaf4033c95e872f828d/src/google/protobuf/util/delimited_message_util.h#L85 //! \sa //! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/zero_copy_stream_impl_lite.h#L68 //! \sa //! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/coded_stream.h#L171 //! //! \!param[in] value Value to be encoded in Base 128 varint encoding. //! \!return The length in bytes of `value` in Base 128 varint encoding. template constexpr Range::Length Base128VarintEncodedLength(IntegerType value) { static_assert(std::is_unsigned::value); Range::Length size = 1; while (value >= 0x80) { value >>= kBase128ByteValueBits; size++; } return size; } // Note that std::array capacity is a size_t, not a RingBufferCapacity. template using RingBufferArray = std::array; //! \return The size of the `RingBufferArray` as a `Range::Length`. template constexpr Range::Length RingBufferArraySize( const RingBufferArray& ring_buffer_data) { static_assert(ArrayCapacity <= std::numeric_limits::max()); return static_cast(ring_buffer_data.size()); } //! \brief Reads data from the ring buffer into a target buffer. //! \param[in] ring_buffer_data The ring buffer to read. //! \param[in,out] ring_buffer_read_range The range of the data available //! to read. Upon return, set to the remaining range of data available //! to read, if any. //! \param[in] target_buffer Buffer into which data will be written. //! \param[in] target_buffer_length Number of bytes to write into //! `target_buffer`. //! //! \return `true` if the read succeeded, `false` otherwise. On success, updates //! `ring_buffer_read_range` to reflect the bytes consumed. //! //! The bytes can wrap around the end of the ring buffer, in which case the read //! continues at the beginning of the ring buffer (if the ring buffer is long //! enough). template bool ReadBytesFromRingBuffer(const RingBufferArrayType& ring_buffer_data, internal::Range& ring_buffer_read_range, uint8_t* target_buffer, Range::Length target_buffer_length) { if (target_buffer_length > ring_buffer_read_range.length) { return false; } if (target_buffer_length == 0) { return true; } const Range::Length initial_read_length = std::min( target_buffer_length, RingBufferArraySize(ring_buffer_data) - ring_buffer_read_range.offset); memcpy(target_buffer, &ring_buffer_data[ring_buffer_read_range.offset], initial_read_length); if (initial_read_length < target_buffer_length) { const Range::Length remaining_read_length = target_buffer_length - initial_read_length; memcpy(target_buffer + initial_read_length, &ring_buffer_data[0], remaining_read_length); } ring_buffer_read_range.offset = (ring_buffer_read_range.offset + target_buffer_length) % RingBufferArraySize(ring_buffer_data); ring_buffer_read_range.length -= target_buffer_length; return true; } //! \brief Reads a single little-endian Base 128 varint-encoded integer from //! the ring buffer. //! \param[in] ring_buffer_data The ring buffer to read. //! \param[in,out] ring_buffer_read_range The range of the data available //! to read. Upon return, set to the remaining range of data available //! to read, if any. //! \param[out] result Upon success, set to the decoded value read from the //! buffer. //! //! \return The length in bytes of the varint if the read succeeded, //! `std::nullopt` otherwise. On success, updates `ring_buffer_read_range` //! to reflect the bytes available to read. //! //! The varint can wrap around the end of the ring buffer, in which case the //! read continues at the beginning of the ring buffer (if the ring buffer is //! long enough). template std::optional ReadBase128VarintFromRingBuffer( const RingBufferArrayType& ring_buffer_data, internal::Range& ring_buffer_read_range, IntegerType& result) { static_assert(std::is_unsigned::value); result = 0; uint8_t cur_varint_byte = 0; constexpr uint8_t kValueMask = 0x7f; constexpr uint8_t kContinuationMask = 0x80; Range::Length length = 0; do { if (!ReadBytesFromRingBuffer( ring_buffer_data, ring_buffer_read_range, &cur_varint_byte, 1)) { // No capacity remaining in `ring_buffer_read_range` to read the varint. return std::nullopt; } IntegerType cur_varint_value = static_cast(cur_varint_byte & kValueMask); // This is equivalent to: // // result |= (cur_varint_value << (length * kBase128ByteValueBits)); // // but checks the result at each step for overflow, which handles two types // of invalid input: // // 1) Too many bytes with kContinuationMask set (e.g., trying to encode 6 // bytes worth of data in a 32-bit value) // 2) Too many bits in the final byte (e.g., the 5th byte for a 32-bit value // has bits 33 and 34 set) IntegerType next_result_bits; if (!base::CheckLsh(cur_varint_value, length * kBase128ByteValueBits) .AssignIfValid(&next_result_bits)) { return std::nullopt; } result |= next_result_bits; ++length; } while ((cur_varint_byte & kContinuationMask) == kContinuationMask); return length; } //! \brief Writes data from the source buffer into the ring buffer. //! \param[in] source_buffer Buffer from which data will be read. //! \param[in] source_buffer_length The length in bytes of `source_buffer`. //! \param[in] ring_buffer_data The ring buffer into which data will be read. //! \param[in,out] ring_buffer_write_range The range of the data available //! to write. Upon return, set to the remaining range of data available //! to write, if any. //! //! \return `true` if write read succeeded, `false` otherwise. On success, //! updates //! `ring_buffer_write_range` to reflect the bytes written. //! //! The bytes can wrap around the end of the ring buffer, in which case the //! write continues at the beginning of the ring buffer (if the ring buffer is //! long enough). template bool WriteBytesToRingBuffer(const uint8_t* const source_buffer, Range::Length source_buffer_length, RingBufferArrayType& ring_buffer_data, internal::Range& ring_buffer_write_range) { const Range::Length ring_buffer_bytes_remaining = RingBufferArraySize(ring_buffer_data) - ring_buffer_write_range.length; if (source_buffer_length > ring_buffer_bytes_remaining) { return false; } const Range::Length initial_write_length = std::min( source_buffer_length, RingBufferArraySize(ring_buffer_data) - ring_buffer_write_range.offset); memcpy(&ring_buffer_data[ring_buffer_write_range.offset], source_buffer, initial_write_length); if (initial_write_length < source_buffer_length) { const Range::Length remaining_write_length = source_buffer_length - initial_write_length; memcpy(&ring_buffer_data[0], source_buffer + initial_write_length, remaining_write_length); } ring_buffer_write_range.offset = (ring_buffer_write_range.offset + source_buffer_length) % RingBufferArraySize(ring_buffer_data); ring_buffer_write_range.length -= source_buffer_length; return true; } //! \brief Writes a single Base 128 varint-encoded little-endian unsigned //! integer into the ring buffer. //! \param[in] value The value to encode and write into the ring buffer. //! \param[in] ring_buffer_data The ring buffer into which to write. //! \param[in,out] ring_buffer_write_range The range of the data available //! to write. Upon return, set to the remaining range of data available //! to write, if any. //! //! \return The length in bytes of the varint if the write succeeded, //! `std::nullopt` otherwise. On success, updates `write_buffer_read_range` //! to reflect the range available to write, if any. //! //! The varint can wrap around the end of the ring buffer, in which case the //! write continues at the beginning of the ring buffer (if the ring buffer is //! long enough). template std::optional WriteBase128VarintToRingBuffer( IntegerType value, RingBufferArrayType& ring_buffer_data, internal::Range& ring_buffer_write_range) { static_assert(std::is_unsigned::value); uint8_t cur_varint_byte; constexpr uint8_t kValueMask = 0x7f; constexpr uint8_t kContinuationMask = 0x80; // Every varint encodes to at least 1 byte of data. int length = 1; while (value > kValueMask) { cur_varint_byte = (static_cast(value) & kValueMask) | kContinuationMask; if (!WriteBytesToRingBuffer( &cur_varint_byte, 1, ring_buffer_data, ring_buffer_write_range)) { return std::nullopt; } value >>= kBase128ByteValueBits; ++length; } cur_varint_byte = static_cast(value); if (!WriteBytesToRingBuffer( &cur_varint_byte, 1, ring_buffer_data, ring_buffer_write_range)) { return std::nullopt; } return length; } } // namespace internal //! \brief Storage for a ring buffer which can hold up to //! `RingBufferCapacity` //! bytes of Base 128-varint delimited variable-length items. //! //! This struct contains a header immediately followed by the ring buffer //! data. The current read offset and length are stored in `header.data_range`. //! //! The structure of this object is: //! //! `|magic|version|data_offset|data_length|ring_buffer_data|` //! //! To write data to this object, see `LengthDelimitedRingBufferWriter`. //! To read data from this object, see `LengthDelimitedRingBufferReader`. //! //! The bytes of this structure are suitable for direct serialization from //! memory to disk, e.g. as a crashpad::Annotation. template struct RingBufferData final { RingBufferData() = default; RingBufferData(RingBufferData&) = delete; RingBufferData& operator=(RingBufferData&) = delete; //! \brief The type of the array holding the data in this object. using RingBufferArrayType = internal::RingBufferArray; //! \brief The type of the size in bytes of operations on this object. using SizeType = internal::Range::Length; //! \brief Attempts to overwrite the contents of this object by deserializing //! the buffer into this object. //! \param[in] buffer The bytes to deserialize into this object. //! \param[in] length The length in bytes of `buffer`. //! //! \return `true` if the buffer was a valid RingBufferData and this object //! has enough capacity to store its bytes, `false` otherwise. bool DeserializeFromBuffer(const void* buffer, SizeType length) { if (length < sizeof(header) || length > sizeof(header) + sizeof(data)) { return false; } const Header* other_header = reinterpret_cast(buffer); if (other_header->magic != kMagic || other_header->version != kVersion) { return false; } header.data_range = other_header->data_range; const uint8_t* other_ring_buffer_bytes = reinterpret_cast(buffer) + sizeof(*other_header); const SizeType other_ring_buffer_len = length - sizeof(*other_header); memcpy(&data[0], other_ring_buffer_bytes, other_ring_buffer_len); return true; } //! \return The current length in bytes of the data written to the ring //! buffer. SizeType GetRingBufferLength() const { internal::Range data_range = header.data_range; return sizeof(header) + std::min(internal::RingBufferArraySize(data), data_range.offset + data_range.length); } //! \brief Resets the state of the ring buffer (e.g., for testing). void ResetForTesting() { header.data_range = {0, 0}; } //! \brief The magic signature of the ring buffer. static constexpr uint32_t kMagic = 0xcab00d1e; //! \brief The version of the ring buffer. static constexpr uint32_t kVersion = 1; //! \brief A header containing metadata preceding the ring buffer data. struct Header final { Header() : magic(kMagic), version(kVersion), data_range({0, 0}) {} //! \brief The fixed magic value identifying this as a ring buffer. const uint32_t magic; //! \brief The version of this ring buffer data. const uint32_t version; //! \brief The range of readable data in the ring buffer. internal::Range data_range; }; //! \brief The header containing ring buffer metadata. Header header; //! \brief The bytes of the ring buffer data. RingBufferArrayType data; // This struct is persisted to disk, so its size must not change. static_assert(sizeof(Header) == 16); static_assert(Capacity <= std::numeric_limits::max()); }; // Ensure the ring buffer is packed correctly at its default capacity. static_assert( sizeof(RingBufferData) == 16 + internal::kDefaultRingBufferDataCapacity); // Allow just `RingBufferData foo;` to be declared without template arguments // using C++17 class template argument deduction. template < RingBufferCapacity Capacity = internal::kDefaultRingBufferDataCapacity> RingBufferData() -> RingBufferData; //! \brief Reads variable-length data buffers from a `RingBufferData`, //! delimited by Base128 varint-encoded length delimiters. //! //! Holds a reference to a `RingBufferData` with the capacity to hold //! `RingBufferDataType::size()` bytes of variable-length buffers each //! preceded by its length (encoded as a Base128 length varint). //! //! Provides reading capabilities via `Pop()`. template class LengthDelimitedRingBufferReader final { public: //! \brief Constructs a reader which holds a reference to `ring_buffer`. //! \param[in] ring_buffer The ring buffer from which data will be read. //! This object must outlive the lifetime of `ring_buffer`. constexpr explicit LengthDelimitedRingBufferReader( RingBufferDataType& ring_buffer) : ring_buffer_(ring_buffer), data_range_(ring_buffer_.header.data_range) {} LengthDelimitedRingBufferReader(const LengthDelimitedRingBufferReader&) = delete; LengthDelimitedRingBufferReader& operator=( const LengthDelimitedRingBufferReader&) = delete; //! \brief Pops off the next buffer from the front of the ring buffer. //! //! \param[in] target_buffer On success, the buffer into which data will //! be read. //! \return On success, returns `true` and advances `ring_buffer.data_range` //! past the end of the buffer read. Otherwise, returns `false`. bool Pop(std::vector& target_buffer) { return PopWithRange(target_buffer, data_range_); } //! \brief Resets the state of the reader (e.g., for testing). void ResetForTesting() { data_range_ = {0, 0}; } private: //! \brief Pops off the next buffer from the front of the ring buffer. //! \param[in] target_buffer On success, the buffer into which data will //! be read. //! \param[in,out] data_range The range of data available to read. //! On success, updated to the remaining range avilable to read. //! \return On success, returns `true` and advances `ring_buffer.data_range` //! past the end of the buffer read. Otherwise, returns `false`. bool PopWithRange(std::vector& target_buffer, internal::Range& data_range) { internal::Range::Length buffer_length; if (!ReadBase128VarintFromRingBuffer( ring_buffer_.data, data_range, buffer_length)) { return false; } if (buffer_length == 0) { // A zero-length buffer means the buffer was truncated in the middle of a // Push(). return false; } const auto previous_target_buffer_size = target_buffer.size(); target_buffer.resize(previous_target_buffer_size + buffer_length); if (!ReadBytesFromRingBuffer(ring_buffer_.data, data_range, &target_buffer[previous_target_buffer_size], buffer_length)) { return false; } return true; } //! \brief Reference to the ring buffer from which data is read. const RingBufferDataType& ring_buffer_; //! \brief Range of data currently available to read. internal::Range data_range_; }; // Allow just `LengthDelimitedRingBufferReader reader(foo);` to be declared // without template arguments using C++17 class template argument deduction. template LengthDelimitedRingBufferReader(RingBufferDataType&) -> LengthDelimitedRingBufferReader; //! \brief Writes variable-length data buffers to a `RingBufferData`, //! delimited by Base128 varint-encoded length delimiters. //! //! Holds a reference to a `RingBufferData` with the capacity to hold //! `RingBufferDataType::size()` bytes of variable-length buffers each //! preceded by its length (encoded as a Base128 length varint). //! //! Provides writing capabilities via `Push()`. template class LengthDelimitedRingBufferWriter final { public: //! \brief Constructs a writer which holds a reference to `ring_buffer`. //! \param[in] ring_buffer The ring buffer into which data will be written. //! This object must outlive the lifetime of `ring_buffer`. constexpr explicit LengthDelimitedRingBufferWriter( RingBufferDataType& ring_buffer) : ring_buffer_(ring_buffer), ring_buffer_write_offset_(0) {} LengthDelimitedRingBufferWriter(const LengthDelimitedRingBufferWriter&) = delete; LengthDelimitedRingBufferWriter& operator=( const LengthDelimitedRingBufferWriter&) = delete; //! \brief Writes data to the ring buffer. //! //! If there is not enough room remaining in the ring buffer to store the new //! data, old data will be removed from the ring buffer in FIFO order until //! there is room for the new data. //! //! \param[in] buffer The data to be written. //! \param[in] buffer_length The lengh of `buffer`, in bytes. //! \return On success, returns `true`, updates `ring_buffer.data_range` //! to reflect the remaining data available to read, and updates //! `ring_buffer_write_offset_` to reflec the current write positionl. //! Otherwise, returns `false`. bool Push(const void* const buffer, typename RingBufferDataType::SizeType buffer_length) { if (buffer_length == 0) { // Pushing a zero-length buffer is not allowed // (`LengthDelimitedRingBufferWriter` reserves that to represent a // temporarily truncated item below). return false; } const internal::Range::Length buffer_varint_encoded_length = internal::Base128VarintEncodedLength(buffer_length); const internal::Range::Length bytes_needed = buffer_varint_encoded_length + buffer_length; if (bytes_needed > ring_buffer_.data.size()) { return false; } // If needed, move the readable region forward one buffer at a time to make // room for `buffer_length` bytes of new data. auto readable_data_range = ring_buffer_.header.data_range; internal::Range::Length bytes_available = internal::RingBufferArraySize(ring_buffer_.data) - readable_data_range.length; while (bytes_available < bytes_needed) { internal::Range::Length bytes_to_skip; auto varint_length = ReadBase128VarintFromRingBuffer( ring_buffer_.data, readable_data_range, bytes_to_skip); if (!varint_length.has_value()) { return false; } // Skip past the next entry including its prepended varint length. readable_data_range.offset = (readable_data_range.offset + bytes_to_skip) % internal::RingBufferArraySize(ring_buffer_.data); readable_data_range.length -= bytes_to_skip; bytes_available += varint_length.value() + bytes_to_skip; } // Write the varint containing `buffer_length` to the current write // position. internal::Range write_range = { ring_buffer_write_offset_, bytes_needed, }; internal::WriteBase128VarintToRingBuffer( buffer_length, ring_buffer_.data, write_range); // Next, write the bytes from `buffer`. internal::WriteBytesToRingBuffer( reinterpret_cast(buffer), buffer_length, ring_buffer_.data, write_range); // Finally, update the write position and read data range taking into // account any items skipped to make room plus the new buffer's varint // length and the new buffer's length. ring_buffer_write_offset_ = write_range.offset; const internal::Range final_data_range = { readable_data_range.offset, readable_data_range.length + bytes_needed, }; ring_buffer_.header.data_range = final_data_range; return true; } //! \brief Resets the state of the ring buffer and writer (e.g., for testing). void ResetForTesting() { ring_buffer_.ResetForTesting(); ring_buffer_write_offset_ = 0; } private: //! \brief Reference to the ring buffer from which data is written. RingBufferDataType& ring_buffer_; // \brief Current write position next time `Push()` is invoked. internal::Range::Offset ring_buffer_write_offset_; }; // Allow just `LengthDelimitedRingBufferWriter writer(foo);` to be declared // without template arguments using C++17 class template argument deduction. template LengthDelimitedRingBufferWriter(RingBufferDataType&) -> LengthDelimitedRingBufferWriter; } // namespace crashpad #endif // CRASHPAD_CLIENT_LENGTH_DELIMITED_RING_BUFFER_H_ ================================================ FILE: client/length_delimited_ring_buffer_test.cc ================================================ // Copyright 2023 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/length_delimited_ring_buffer.h" #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" namespace crashpad { namespace test { namespace { using testing::Eq; using testing::IsFalse; using testing::IsTrue; // Buffer with magic 0xcab00d1e, version 1, read_pos 0, length 3, and 3 bytes of // data (1 varint length, 2 bytes data) constexpr char kValidBufferSize3[] = "\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x02\x42" "\x23"; constexpr size_t kValidBufferSize3Len = sizeof(kValidBufferSize3) - 1; // Remove trailing NUL. // Buffer with magic 0xcab00d1e, version 8, read_pos 0, length 3, and 3 bytes of // data (1 varint length, 2 bytes data). constexpr char kInvalidVersionBuffer[] = "\x1e\x0d\xb0\xca\x08\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x02\xab" "\xcd"; constexpr size_t kInvalidVersionBufferLen = sizeof(kInvalidVersionBuffer) - 1; // Remove trailing NUL. // Buffer representing process which crashed while in the middle of a Push() // operation, with a previously-Push()ed buffer whose length was zeroed out at // the start. constexpr char kMidCrashBuffer[] = "\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x42" "\x23"; constexpr size_t kMidCrashBufferLen = sizeof(kMidCrashBuffer) - 1; // Remove trailing NUL. constexpr uint8_t kHello[] = {0x68, 0x65, 0x6c, 0x6c, 0x6f}; // Invalid buffer containing malformed varint in data payload (Base 128 varint // with length 6, which would represent a data length > 32 bits). constexpr char kInvalidBase128VarintBuffer[] = "\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x80\x80" "\x80\x80\x80\x01"; constexpr size_t kInvalidBase128VarintBufferLen = sizeof(kInvalidBase128VarintBuffer) - 1; // Remove trailing NUL. // Invalid buffer containing malformed varint in data payload (Base 128 varint // with length 5 but bits 33 and 34 set, which would represent a data length > // 32 bits). constexpr char kInvalidBase128VarintBits33And34SetBuffer[] = "\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x80\x80" "\x80\x80\x60"; constexpr size_t kInvalidBase128VarintBits33And34SetBufferLen = sizeof(kInvalidBase128VarintBits33And34SetBuffer) - 1; // Remove trailing NUL. // Invalid buffer containing too-short data payload (varint length indicates // payload length is 4 but payload only contains 3 bytes). constexpr char kInvalidPayloadBufferTooShort[] = "\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04" "\x42\x42\x42"; constexpr size_t kInvalidPayloadBufferTooShortLen = sizeof(kInvalidPayloadBufferTooShort) - 1; // Remove trailing NUL. TEST(LengthDelimitedRingBufferTest, RingBufferDataShouldStartWithMagicAndVersion) { RingBufferData ring_buffer; const void* ring_buffer_bytes = static_cast(&ring_buffer); EXPECT_THAT(memcmp(ring_buffer_bytes, "\x1e\x0d\xb0\xca\x01\x00\x00\x00", 8), Eq(0)); } TEST(LengthDelimitedRingBufferTest, EmptyBufferSizeShouldIncludeHeaderInRingBufferLength) { RingBufferData ring_buffer; EXPECT_THAT(ring_buffer.GetRingBufferLength(), Eq(16U)); // 4 for uint32 magic, 4 for uint32 version, 4 for // uint32 read_pos, 4 for uint32 length } TEST(LengthDelimitedRingBufferTest, NonEmptyBufferSizeShouldIncludeHeaderAndData) { RingBufferData ring_buffer; LengthDelimitedRingBufferWriter writer(ring_buffer); ASSERT_THAT(writer.Push(kHello, sizeof(kHello)), IsTrue()); EXPECT_THAT(ring_buffer.GetRingBufferLength(), Eq(22U)); // 16 for header, 1 for varint length, 5 for data } TEST(LengthDelimitedRingBufferTest, PopOnEmptyBufferShouldFail) { RingBufferData ring_buffer; LengthDelimitedRingBufferReader reader(ring_buffer); std::vector result; EXPECT_THAT(reader.Pop(result), IsFalse()); } TEST(LengthDelimitedRingBufferTest, PushZeroLengthShouldFail) { RingBufferData ring_buffer; LengthDelimitedRingBufferWriter writer(ring_buffer); ASSERT_THAT(writer.Push(nullptr, 0), IsFalse()); } TEST(LengthDelimitedRingBufferTest, PushExactlyBufferSizeThenPopShouldSucceed) { RingBufferData ring_buffer; LengthDelimitedRingBufferWriter writer(ring_buffer); ASSERT_THAT(writer.Push(kHello, sizeof(kHello)), IsTrue()); LengthDelimitedRingBufferReader reader(ring_buffer); std::vector result; EXPECT_THAT(reader.Pop(result), IsTrue()); const std::vector expected_first = {0x68, 0x65, 0x6c, 0x6c, 0x6f}; EXPECT_THAT(result, Eq(expected_first)); } TEST(LengthDelimitedRingBufferTest, PushLargerThanBufferSizeShouldFail) { RingBufferData<4> ring_buffer; LengthDelimitedRingBufferWriter writer(ring_buffer); EXPECT_THAT(writer.Push(kHello, sizeof(kHello)), IsFalse()); } TEST(LengthDelimitedRingBufferTest, PushUntilFullThenPopUntilEmptyShouldReturnInFIFOOrder) { RingBufferData<4> ring_buffer; LengthDelimitedRingBufferWriter writer(ring_buffer); constexpr uint8_t a = 0x41; EXPECT_THAT(writer.Push(&a, sizeof(a)), IsTrue()); // Writes 2 bytes (1 for length) constexpr uint8_t b = 0x42; EXPECT_THAT(writer.Push(&b, sizeof(b)), IsTrue()); // Writes 2 bytes (1 for length) LengthDelimitedRingBufferReader reader(ring_buffer); std::vector first; EXPECT_THAT(reader.Pop(first), IsTrue()); const std::vector expected_first = {0x41}; EXPECT_THAT(first, Eq(expected_first)); std::vector second; EXPECT_THAT(reader.Pop(second), IsTrue()); const std::vector expected_second = {0x42}; EXPECT_THAT(second, Eq(expected_second)); std::vector empty; EXPECT_THAT(reader.Pop(empty), IsFalse()); } TEST(LengthDelimitedRingBufferTest, PushThenPopBuffersOfDifferingLengthsShouldReturnBuffers) { RingBufferData<5> ring_buffer; LengthDelimitedRingBufferWriter writer(ring_buffer); constexpr uint8_t ab[2] = {0x41, 0x42}; EXPECT_THAT(writer.Push(ab, sizeof(ab)), IsTrue()); // Writes 3 bytes (1 for length) constexpr uint8_t c = 0x43; EXPECT_THAT(writer.Push(&c, sizeof(c)), IsTrue()); // Writes 2 bytes (1 for length) LengthDelimitedRingBufferReader reader(ring_buffer); std::vector first; EXPECT_THAT(reader.Pop(first), IsTrue()); const std::vector expected_first = {0x41, 0x42}; EXPECT_THAT(first, Eq(expected_first)); std::vector second; EXPECT_THAT(reader.Pop(second), IsTrue()); const std::vector expected_second = {0x43}; EXPECT_THAT(second, Eq(expected_second)); std::vector empty; EXPECT_THAT(reader.Pop(empty), IsFalse()); } TEST(LengthDelimitedRingBufferDataTest, PushOnFullBufferShouldOverwriteOldest) { RingBufferData<4> ring_buffer; LengthDelimitedRingBufferWriter writer(ring_buffer); constexpr uint8_t a = 0x41; EXPECT_THAT(writer.Push(&a, sizeof(a)), IsTrue()); // Writes 2 bytes (1 for length) constexpr uint8_t b = 0x42; EXPECT_THAT(writer.Push(&b, sizeof(b)), IsTrue()); // Writes 2 bytes (1 for length) constexpr uint8_t c = 0x43; EXPECT_THAT(writer.Push(&c, sizeof(c)), IsTrue()); // Should overwrite "A" LengthDelimitedRingBufferReader reader(ring_buffer); std::vector first; EXPECT_THAT(reader.Pop(first), IsTrue()); const std::vector expected_first = {uint8_t{0x42}}; EXPECT_THAT(first, Eq(expected_first)); std::vector second; EXPECT_THAT(reader.Pop(second), IsTrue()); const std::vector expected_second = {uint8_t{0x43}}; EXPECT_THAT(second, Eq(expected_second)); } TEST(LengthDelimitedRingBufferDataTest, PushOnFullBufferShouldOverwriteMultipleOldest) { RingBufferData<4> ring_buffer; LengthDelimitedRingBufferWriter writer(ring_buffer); constexpr uint8_t a = 0x41; EXPECT_THAT(writer.Push(&a, sizeof(a)), IsTrue()); // Writes 2 bytes (1 for length) constexpr uint8_t b = 0x42; EXPECT_THAT(writer.Push(&b, sizeof(b)), IsTrue()); // Writes 2 bytes (1 for length) constexpr uint8_t cd[] = {0x43, 0x44}; EXPECT_THAT(writer.Push(cd, sizeof(cd)), IsTrue()); // Needs 3 bytes; should overwrite "A" and "B" LengthDelimitedRingBufferReader reader(ring_buffer); std::vector first; EXPECT_THAT(reader.Pop(first), IsTrue()); const std::vector expected_first = {0x43, 0x44}; EXPECT_THAT(first, Eq(expected_first)); std::vector empty; EXPECT_THAT(reader.Pop(empty), IsFalse()); } TEST(LengthDelimitedRingBufferDataTest, PushThenPopWithLengthVarintTwoBytes) { RingBufferData ring_buffer; decltype(ring_buffer)::SizeType size = 150; std::string s(size, 'X'); LengthDelimitedRingBufferWriter writer(ring_buffer); ASSERT_THAT(writer.Push(reinterpret_cast(s.c_str()), size), IsTrue()); LengthDelimitedRingBufferReader reader(ring_buffer); std::vector first; EXPECT_THAT(reader.Pop(first), IsTrue()); std::string result(reinterpret_cast(first.data()), first.size()); EXPECT_THAT(result, Eq(s)); } TEST(LengthDelimitedRingBufferDataTest, DeserializeFromTooShortShouldFail) { RingBufferData<1> ring_buffer; EXPECT_THAT(ring_buffer.DeserializeFromBuffer(nullptr, 0), IsFalse()); } TEST(LengthDelimitedRingBufferDataTest, DeserializeFromTooLongShouldFail) { RingBufferData<1> ring_buffer; // This buffer is size 3; it won't fit in the template arg (size 1). EXPECT_THAT(ring_buffer.DeserializeFromBuffer( reinterpret_cast(kValidBufferSize3), kValidBufferSize3Len), IsFalse()); } TEST(LengthDelimitedRingBufferDataTest, DeserializeFromInvalidVersionShouldFail) { RingBufferData<3> ring_buffer; EXPECT_THAT(ring_buffer.DeserializeFromBuffer( reinterpret_cast(kInvalidVersionBuffer), kInvalidVersionBufferLen), IsFalse()); } TEST(LengthDelimitedRingBufferDataTest, DeserializeFromInvalidVarintLengthShouldSucceedButPopShouldFail) { RingBufferData ring_buffer; EXPECT_THAT(ring_buffer.DeserializeFromBuffer( reinterpret_cast(kInvalidBase128VarintBuffer), kInvalidBase128VarintBufferLen), IsTrue()); LengthDelimitedRingBufferReader reader(ring_buffer); std::vector data; EXPECT_THAT(reader.Pop(data), IsFalse()); } TEST(LengthDelimitedRingBufferDataTest, DeserializeFromInvalidVarintBitsShouldSucceedButPopShouldFail) { RingBufferData ring_buffer; EXPECT_THAT(ring_buffer.DeserializeFromBuffer( reinterpret_cast( kInvalidBase128VarintBits33And34SetBuffer), kInvalidBase128VarintBits33And34SetBufferLen), IsTrue()); LengthDelimitedRingBufferReader reader(ring_buffer); std::vector data; EXPECT_THAT(reader.Pop(data), IsFalse()); } TEST(LengthDelimitedRingBufferDataTest, DeserializeFromInvalidPayloadBufferTooShortShouldSucceedButPopShouldFail) { RingBufferData ring_buffer; EXPECT_THAT( ring_buffer.DeserializeFromBuffer( reinterpret_cast(kInvalidPayloadBufferTooShort), kInvalidPayloadBufferTooShortLen), IsTrue()); LengthDelimitedRingBufferReader reader(ring_buffer); std::vector data; EXPECT_THAT(reader.Pop(data), IsFalse()); } TEST(LengthDelimitedRingBufferDataTest, DeserializeFromFullBufferShouldSucceed) { RingBufferData<3> ring_buffer; EXPECT_THAT(ring_buffer.DeserializeFromBuffer( reinterpret_cast(kValidBufferSize3), kValidBufferSize3Len), IsTrue()); LengthDelimitedRingBufferReader reader(ring_buffer); std::vector data; EXPECT_THAT(reader.Pop(data), IsTrue()); const std::vector expected = {0x42, 0x23}; EXPECT_THAT(data, Eq(expected)); } TEST(LengthDelimitedRingBufferDataTest, DeserializeFromMidCrashBufferShouldSucceedButSubsequentPopShouldFail) { RingBufferData ring_buffer; EXPECT_THAT(ring_buffer.DeserializeFromBuffer( reinterpret_cast(kMidCrashBuffer), kMidCrashBufferLen), IsTrue()); LengthDelimitedRingBufferReader reader(ring_buffer); // Pop should fail since the length was written to be 0. std::vector data; EXPECT_THAT(reader.Pop(data), IsFalse()); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/prune_crash_reports.cc ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/prune_crash_reports.h" #include #include #include #include #include "base/logging.h" #include "base/notreached.h" #include "build/build_config.h" namespace crashpad { size_t PruneCrashReportDatabase(CrashReportDatabase* database, PruneCondition* condition) { std::vector all_reports; CrashReportDatabase::OperationStatus status; status = database->GetPendingReports(&all_reports); if (status != CrashReportDatabase::kNoError) { LOG(ERROR) << "PruneCrashReportDatabase: Failed to get pending reports"; return 0; } std::vector completed_reports; status = database->GetCompletedReports(&completed_reports); if (status != CrashReportDatabase::kNoError) { LOG(ERROR) << "PruneCrashReportDatabase: Failed to get completed reports"; return 0; } all_reports.insert(all_reports.end(), completed_reports.begin(), completed_reports.end()); std::sort(all_reports.begin(), all_reports.end(), [](const CrashReportDatabase::Report& lhs, const CrashReportDatabase::Report& rhs) { return lhs.creation_time > rhs.creation_time; }); size_t num_pruned = 0; for (const auto& report : all_reports) { if (condition->ShouldPruneReport(report)) { status = database->DeleteReport(report.uuid); if (status != CrashReportDatabase::kNoError) { LOG(ERROR) << "Database Pruning: Failed to remove report " << report.uuid.ToString(); } else { num_pruned++; } } } return num_pruned; // TODO(rsesek): For databases that do not use a directory structure, it is // possible for the metadata sidecar to become corrupted and thus leave // orphaned crash report files on-disk. https://crashpad.chromium.org/bug/66 } // static std::unique_ptr PruneCondition::GetDefault() { // DatabaseSizePruneCondition must be the LHS so that it is always evaluated, // due to the short-circuting behavior of BinaryPruneCondition. return std::make_unique( BinaryPruneCondition::OR, new DatabaseSizePruneCondition(1024 * 128), new AgePruneCondition(365)); } static const time_t kSecondsInDay = 60 * 60 * 24; AgePruneCondition::AgePruneCondition(int max_age_in_days) : oldest_report_time_( ((time(nullptr) - (max_age_in_days * kSecondsInDay)) / kSecondsInDay) * kSecondsInDay) {} AgePruneCondition::~AgePruneCondition() {} bool AgePruneCondition::ShouldPruneReport( const CrashReportDatabase::Report& report) { return report.creation_time < oldest_report_time_; } DatabaseSizePruneCondition::DatabaseSizePruneCondition(size_t max_size_in_kb) : max_size_in_kb_(max_size_in_kb), measured_size_in_kb_(0) {} DatabaseSizePruneCondition::~DatabaseSizePruneCondition() {} bool DatabaseSizePruneCondition::ShouldPruneReport( const CrashReportDatabase::Report& report) { // Round up fractional KB to the next 1-KB boundary. measured_size_in_kb_ += static_cast((report.total_size + 1023) / 1024); return measured_size_in_kb_ > max_size_in_kb_; } BinaryPruneCondition::BinaryPruneCondition( Operator op, PruneCondition* lhs, PruneCondition* rhs) : op_(op), lhs_(lhs), rhs_(rhs) {} BinaryPruneCondition::~BinaryPruneCondition() {} bool BinaryPruneCondition::ShouldPruneReport( const CrashReportDatabase::Report& report) { switch (op_) { case AND: return lhs_->ShouldPruneReport(report) && rhs_->ShouldPruneReport(report); case OR: return lhs_->ShouldPruneReport(report) || rhs_->ShouldPruneReport(report); } NOTREACHED(); } } // namespace crashpad ================================================ FILE: client/prune_crash_reports.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_PRUNE_CRASH_REPORTS_H_ #define CRASHPAD_CLIENT_PRUNE_CRASH_REPORTS_H_ #include #include #include #include "client/crash_report_database.h" namespace crashpad { class PruneCondition; //! \brief Deletes crash reports from \a database that match \a condition. //! //! This function can be used to remove old or large reports from the database. //! The \a condition will be evaluated against each report in the \a database, //! sorted in descending order by CrashReportDatabase::Report::creation_time. //! This guarantee allows conditions to be stateful. //! //! \param[in] database The database from which crash reports will be deleted. //! \param[in] condition The condition against which all reports in the database //! will be evaluated. //! //! \return The number of deleted crash reports. size_t PruneCrashReportDatabase(CrashReportDatabase* database, PruneCondition* condition); std::unique_ptr GetDefaultDatabasePruneCondition(); //! \brief An abstract base class for evaluating crash reports for deletion. //! //! When passed to PruneCrashReportDatabase(), each crash report in the //! database will be evaluated according to ShouldPruneReport(). The reports //! are evaluated serially in descending sort order by //! CrashReportDatabase::Report::creation_time. class PruneCondition { public: //! \brief Returns a sensible default condition for removing obsolete crash //! reports. //! //! The default is to keep reports for one year or a maximum database size //! of 128 MB. //! //! \return A PruneCondition for use with PruneCrashReportDatabase(). static std::unique_ptr GetDefault(); virtual ~PruneCondition() {} //! \brief Evaluates a crash report for deletion. //! //! \param[in] report The crash report to evaluate. //! //! \return `true` if the crash report should be deleted, `false` if it //! should be kept. virtual bool ShouldPruneReport(const CrashReportDatabase::Report& report) = 0; }; //! \brief A PruneCondition that deletes reports older than the specified number //! days. class AgePruneCondition final : public PruneCondition { public: //! \brief Creates a PruneCondition based on Report::creation_time. //! //! \param[in] max_age_in_days Reports created more than this many days ago //! will be deleted. explicit AgePruneCondition(int max_age_in_days); AgePruneCondition(const AgePruneCondition&) = delete; AgePruneCondition& operator=(const AgePruneCondition&) = delete; ~AgePruneCondition(); bool ShouldPruneReport(const CrashReportDatabase::Report& report) override; private: const time_t oldest_report_time_; }; //! \brief A PruneCondition that deletes older reports to keep the total //! Crashpad database size under the specified limit. class DatabaseSizePruneCondition final : public PruneCondition { public: //! \brief Creates a PruneCondition that will keep newer reports, until the //! sum of the size of all reports is not smaller than \a max_size_in_kb. //! After the limit is reached, older reports will be pruned. //! //! \param[in] max_size_in_kb The maximum number of kilobytes that all crash //! reports should consume. explicit DatabaseSizePruneCondition(size_t max_size_in_kb); DatabaseSizePruneCondition(const DatabaseSizePruneCondition&) = delete; DatabaseSizePruneCondition& operator=(const DatabaseSizePruneCondition&) = delete; ~DatabaseSizePruneCondition(); bool ShouldPruneReport(const CrashReportDatabase::Report& report) override; private: const size_t max_size_in_kb_; size_t measured_size_in_kb_; }; //! \brief A PruneCondition that conjoins two other PruneConditions. class BinaryPruneCondition final : public PruneCondition { public: enum Operator { AND, OR, }; //! \brief Evaluates two sub-conditions according to the specified logical //! operator. //! //! This implements left-to-right evaluation. For Operator::AND, this means //! if the \a lhs is `false`, the \a rhs will not be consulted. Similarly, //! with Operator::OR, if the \a lhs is `true`, the \a rhs will not be //! consulted. //! //! \param[in] op The logical operator to apply on \a lhs and \a rhs. //! \param[in] lhs The left-hand side of \a op. This class takes ownership. //! \param[in] rhs The right-hand side of \a op. This class takes ownership. BinaryPruneCondition(Operator op, PruneCondition* lhs, PruneCondition* rhs); BinaryPruneCondition(const BinaryPruneCondition&) = delete; BinaryPruneCondition& operator=(const BinaryPruneCondition&) = delete; ~BinaryPruneCondition(); bool ShouldPruneReport(const CrashReportDatabase::Report& report) override; private: const Operator op_; std::unique_ptr lhs_; std::unique_ptr rhs_; }; } // namespace crashpad #endif // CRASHPAD_CLIENT_PRUNE_CRASH_REPORTS_H_ ================================================ FILE: client/prune_crash_reports_test.cc ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/prune_crash_reports.h" #include #include #include #include #include #include #include #include "base/numerics/safe_conversions.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "test/scoped_temp_dir.h" #include "util/file/file_io.h" namespace crashpad { namespace test { namespace { class MockDatabase : public CrashReportDatabase { public: // CrashReportDatabase: MOCK_METHOD(Settings*, GetSettings, (), (override)); MOCK_METHOD(OperationStatus, PrepareNewCrashReport, (std::unique_ptr*), (override)); MOCK_METHOD(OperationStatus, LookUpCrashReport, (const UUID&, Report*), (override)); MOCK_METHOD(OperationStatus, GetPendingReports, (std::vector*), (override)); MOCK_METHOD(OperationStatus, GetCompletedReports, (std::vector*), (override)); MOCK_METHOD(OperationStatus, GetReportForUploading, (const UUID&, std::unique_ptr*, bool report_metrics), (override)); MOCK_METHOD(OperationStatus, RecordUploadAttempt, (UploadReport*, bool, const std::string&), (override)); MOCK_METHOD(OperationStatus, SkipReportUpload, (const UUID&, Metrics::CrashSkippedReason), (override)); MOCK_METHOD(OperationStatus, DeleteReport, (const UUID&), (override)); MOCK_METHOD(OperationStatus, RequestUpload, (const UUID&), (override)); MOCK_METHOD(base::FilePath, DatabasePath, (), (override)); // Google Mock doesn't support mocking methods with non-copyable types such as // unique_ptr. OperationStatus FinishedWritingCrashReport(std::unique_ptr report, UUID* uuid) override { return kNoError; } }; time_t NDaysAgo(int num_days) { return time(nullptr) - (num_days * 60 * 60 * 24); } TEST(PruneCrashReports, AgeCondition) { CrashReportDatabase::Report report_80_days; report_80_days.creation_time = NDaysAgo(80); CrashReportDatabase::Report report_10_days; report_10_days.creation_time = NDaysAgo(10); CrashReportDatabase::Report report_30_days; report_30_days.creation_time = NDaysAgo(30); AgePruneCondition condition(30); EXPECT_TRUE(condition.ShouldPruneReport(report_80_days)); EXPECT_FALSE(condition.ShouldPruneReport(report_10_days)); EXPECT_FALSE(condition.ShouldPruneReport(report_30_days)); } TEST(PruneCrashReports, SizeCondition) { CrashReportDatabase::Report report_1k; report_1k.total_size = 1024u; CrashReportDatabase::Report report_3k; report_3k.total_size = 1024u * 3u; CrashReportDatabase::Report report_unset_size; { DatabaseSizePruneCondition condition(/*max_size_in_kb=*/1); // |report_1k| should not be pruned as the cumulated size is not past 1kB // yet. EXPECT_FALSE(condition.ShouldPruneReport(report_1k)); // |report_3k| should be pruned as the cumulated size is now past 1kB. EXPECT_TRUE(condition.ShouldPruneReport(report_3k)); } { DatabaseSizePruneCondition condition(/*max_size_in_kb=*/1); // |report_3k| should be pruned as the cumulated size is already past 1kB. EXPECT_TRUE(condition.ShouldPruneReport(report_3k)); } { DatabaseSizePruneCondition condition(/*max_size_in_kb=*/6); // |report_3k| should not be pruned as the cumulated size is not past 6kB // yet. EXPECT_FALSE(condition.ShouldPruneReport(report_3k)); // |report_3k| should not be pruned as the cumulated size is not past 6kB // yet. EXPECT_FALSE(condition.ShouldPruneReport(report_3k)); // |report_1k| should be pruned as the cumulated size is now past 6kB. EXPECT_TRUE(condition.ShouldPruneReport(report_1k)); } { DatabaseSizePruneCondition condition(/*max_size_in_kb=*/0); // |report_unset_size| should not be pruned as its size is 0, regardless of // how many times we try to prune it. EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size)); EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size)); EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size)); EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size)); EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size)); // |report_1k| should be pruned as the cumulated size is now past 0kB. EXPECT_TRUE(condition.ShouldPruneReport(report_1k)); } } class StaticCondition final : public PruneCondition { public: explicit StaticCondition(bool value) : value_(value), did_execute_(false) {} StaticCondition(const StaticCondition&) = delete; StaticCondition& operator=(const StaticCondition&) = delete; ~StaticCondition() {} bool ShouldPruneReport(const CrashReportDatabase::Report& report) override { did_execute_ = true; return value_; } bool did_execute() const { return did_execute_; } private: const bool value_; bool did_execute_; }; TEST(PruneCrashReports, BinaryCondition) { static constexpr struct { const char* name; BinaryPruneCondition::Operator op; bool lhs_value; bool rhs_value; bool cond_result; bool lhs_executed; bool rhs_executed; } kTests[] = { // clang-format off {"false && false", BinaryPruneCondition::AND, false, false, false, true, false}, {"false && true", BinaryPruneCondition::AND, false, true, false, true, false}, {"true && false", BinaryPruneCondition::AND, true, false, false, true, true}, {"true && true", BinaryPruneCondition::AND, true, true, true, true, true}, {"false || false", BinaryPruneCondition::OR, false, false, false, true, true}, {"false || true", BinaryPruneCondition::OR, false, true, true, true, true}, {"true || false", BinaryPruneCondition::OR, true, false, true, true, false}, {"true || true", BinaryPruneCondition::OR, true, true, true, true, false}, // clang-format on }; for (const auto& test : kTests) { SCOPED_TRACE(test.name); auto lhs = new StaticCondition(test.lhs_value); auto rhs = new StaticCondition(test.rhs_value); BinaryPruneCondition condition(test.op, lhs, rhs); CrashReportDatabase::Report report; EXPECT_EQ(condition.ShouldPruneReport(report), test.cond_result); EXPECT_EQ(lhs->did_execute(), test.lhs_executed); EXPECT_EQ(rhs->did_execute(), test.rhs_executed); } } MATCHER_P(TestUUID, data_1, "") { return arg.data_1 == data_1; } TEST(PruneCrashReports, PruneOrder) { using ::testing::_; using ::testing::DoAll; using ::testing::Return; using ::testing::SetArgPointee; const size_t kNumReports = 10; std::vector reports; for (size_t i = 0; i < kNumReports; ++i) { CrashReportDatabase::Report temp; temp.uuid.data_1 = static_cast(i); temp.creation_time = NDaysAgo(static_cast(i) * 10); reports.push_back(temp); } std::mt19937 urng(std::random_device{}()); std::shuffle(reports.begin(), reports.end(), urng); std::vector pending_reports(reports.begin(), reports.begin() + 5); std::vector completed_reports( reports.begin() + 5, reports.end()); MockDatabase db; EXPECT_CALL(db, GetPendingReports(_)) .WillOnce(DoAll(SetArgPointee<0>(pending_reports), Return(CrashReportDatabase::kNoError))); EXPECT_CALL(db, GetCompletedReports(_)) .WillOnce(DoAll(SetArgPointee<0>(completed_reports), Return(CrashReportDatabase::kNoError))); for (size_t i = 0; i < reports.size(); ++i) { EXPECT_CALL(db, DeleteReport(TestUUID(i))) .WillOnce(Return(CrashReportDatabase::kNoError)); } StaticCondition delete_all(true); EXPECT_EQ(PruneCrashReportDatabase(&db, &delete_all), kNumReports); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/pthread_create_linux.cc ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include "base/check.h" #include "base/logging.h" #include "client/crashpad_client.h" #include "util/misc/no_cfi_icall.h" namespace { using StartRoutineType = void* (*)(void*); struct StartParams { StartRoutineType start_routine; void* arg; }; void* InitializeSignalStackAndStart(StartParams* params) { crashpad::CrashpadClient::InitializeSignalStackForThread(); crashpad::NoCfiIcall start_routine(params->start_routine); void* arg = params->arg; delete params; return start_routine(arg); } } // namespace extern "C" { __attribute__((visibility("default"))) int pthread_create( pthread_t* thread, const pthread_attr_t* attr, StartRoutineType start_routine, void* arg) { static const crashpad::NoCfiIcall next_pthread_create([]() { const auto next_pthread_create = dlsym(RTLD_NEXT, "pthread_create"); CHECK(next_pthread_create) << "dlsym: " << dlerror(); return next_pthread_create; }()); StartParams* params = new StartParams; params->start_routine = start_routine; params->arg = arg; int result = next_pthread_create( thread, attr, reinterpret_cast(InitializeSignalStackAndStart), params); if (result != 0) { delete params; } return result; } } // extern "C" ================================================ FILE: client/ring_buffer_annotation.h ================================================ // Copyright 2023 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_RING_BUFFER_ANNOTATION_H_ #define CRASHPAD_CLIENT_RING_BUFFER_ANNOTATION_H_ #include #include "client/annotation.h" #include "client/length_delimited_ring_buffer.h" namespace crashpad { //! \brief Capacity of `RingBufferAnnotation`, in bytes. using RingBufferAnnotationCapacity = RingBufferCapacity; namespace internal { //! \brief Default capacity of `RingBufferAnnotation`, in bytes. inline constexpr RingBufferAnnotationCapacity kDefaultRingBufferAnnotationCapacity = 8192; } // namespace internal //! \brief An `Annotation` which wraps a `LengthDelimitedRingBuffer` //! of up to `Capacity` bytes in length. //! //! Supports writing variable-length data via `Push()`. When the ring buffer is //! full, it will drop old data items in FIFO order until enough space is //! available for the write. //! //! Supports guarding concurrent reads from writes via `ScopedSpinGuard`, so //! writing to this object is thread-safe. //! //! Clients which read this `Annotation`'s memory can optionally invoke //! `TryCreateScopedSpinGuard()` on this object to ensure any pending write //! finishes before the memory is read. //! //! Each item in this ring buffer is delimited by its length encoded in //! little-endian Base 128 varint encoding. //! //! `RingBufferAnnotation` uses varint-encoded delimiters to enable //! zero-copy deserialization of the ringbuffer's contents when storing //! protobufs inside the ringbuffer, e.g. via //! `google::protobuf::util::ParseDelimitedFromZeroCopyStream()` or similar. //! //! \sa //! https://github.com/protocolbuffers/protobuf/blob/3202b9da88ceb75b65bbabaf4033c95e872f828d/src/google/protobuf/util/delimited_message_util.h#L85 //! \sa //! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/zero_copy_stream_impl_lite.h#L68 //! \sa //! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/coded_stream.h#L171 //! //! To deserialize the items stored in this annotation, use //! `LengthDelimitedRingBufferReader`. template class RingBufferAnnotation final : public Annotation { public: //! \brief Constructs a `RingBufferAnnotation`. //! \param[in] type A unique identifier for the type of data in the ring //! buffer. //! \param[in] name The name of the annotation. constexpr RingBufferAnnotation(Annotation::Type type, const char name[]) : Annotation(type, name, reinterpret_cast(&ring_buffer_data_), ConcurrentAccessGuardMode::kScopedSpinGuard), ring_buffer_data_(), ring_buffer_writer_(ring_buffer_data_) {} RingBufferAnnotation(const RingBufferAnnotation&) = delete; RingBufferAnnotation& operator=(const RingBufferAnnotation&) = delete; RingBufferAnnotation(RingBufferAnnotation&&) = default; RingBufferAnnotation& operator=(RingBufferAnnotation&&) = default; //! \brief Pushes data onto this annotation's ring buffer. //! //! If the ring buffer does not have enough space to store `buffer_length` //! bytes of data, old data items are dropped in FIFO order until //! enough space is available to store the new data. bool Push(const void* const buffer, RingBufferAnnotationCapacity buffer_length) { // Use a zero timeout so the operation immediately fails if another thread // or process is currently reading this Annotation. constexpr uint64_t kSpinGuardTimeoutNanoseconds = 0; auto spin_guard = TryCreateScopedSpinGuard(kSpinGuardTimeoutNanoseconds); if (!spin_guard) { return false; } bool success = ring_buffer_writer_.Push(buffer, buffer_length); if (success) { SetSize(ring_buffer_data_.GetRingBufferLength()); } return success; } //! \brief Reset the annotation (e.g., for testing). //! This method is not thread-safe. void ResetForTesting() { ring_buffer_data_.ResetForTesting(); ring_buffer_writer_.ResetForTesting(); } private: using RingBufferWriter = LengthDelimitedRingBufferWriter>; //! \brief The ring buffer data stored in this Anotation. RingBufferData ring_buffer_data_; //! \brief The writer which wraps `ring_buffer_data_`. RingBufferWriter ring_buffer_writer_; }; // Allow just `RingBufferAnnotation foo;` to be declared without template // arguments using C++17 class template argument deduction. template RingBufferAnnotation(Annotation::Type type, const char name[]) -> RingBufferAnnotation; } // namespace crashpad #endif // CRASHPAD_CLIENT_RING_BUFFER_ANNOTATION_H_ ================================================ FILE: client/ring_buffer_annotation_load_test_main.cc ================================================ // Copyright 2023 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base/notreached.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "client/annotation.h" #include "client/length_delimited_ring_buffer.h" #include "client/ring_buffer_annotation.h" #include "tools/tool_support.h" #include "util/stdlib/string_number_conversion.h" #include "util/synchronization/scoped_spin_guard.h" #include "util/thread/thread.h" #if BUILDFLAG(IS_WIN) #include #else #include #endif // BUILDFLAG(IS_WIN) namespace crashpad { namespace test { namespace { constexpr Annotation::Type kRingBufferLoadTestType = Annotation::UserDefinedType(0x0042); std::atomic g_should_exit = false; struct RingBufferAnnotationSnapshotParams final { enum class Mode { kUseScopedSpinGuard = 1, kDoNotUseSpinGuard = 2, }; Mode mode = Mode::kUseScopedSpinGuard; using Duration = std::chrono::duration; Duration producer_thread_min_run_duration = std::chrono::milliseconds(1); Duration producer_thread_max_run_duration = std::chrono::milliseconds(10); Duration producer_thread_sleep_duration = std::chrono::nanoseconds(10); Duration consumer_thread_min_run_duration = std::chrono::milliseconds(5); Duration consumer_thread_max_run_duration = std::chrono::milliseconds(100); Duration quiesce_timeout = std::chrono::microseconds(500); uint64_t num_loops = std::numeric_limits::max(); std::optional main_thread_run_duration = std::nullopt; }; template class RingBufferAnnotationSnapshot final { using RingBufferAnnotationType = RingBufferAnnotation; struct State final { State() : ring_buffer_annotation(kRingBufferLoadTestType, "ring-buffer-load-test"), ring_buffer_ready(false), producer_thread_running(false), producer_thread_finished(false), consumer_thread_finished(false), should_exit(false) {} State(const State&) = delete; State& operator=(const State&) = delete; RingBufferAnnotationType ring_buffer_annotation; bool ring_buffer_ready; bool producer_thread_running; bool producer_thread_finished; bool consumer_thread_finished; bool should_exit; }; class Thread final : public crashpad::Thread { public: Thread(std::function thread_main) : thread_main_(std::move(thread_main)) {} private: void ThreadMain() override { thread_main_(); } const std::function thread_main_; }; public: RingBufferAnnotationSnapshot(const RingBufferAnnotationSnapshotParams& params) : params_(params), main_loop_thread_([this]() { MainLoopThreadMain(); }), producer_thread_([this]() { ProducerThreadMain(); }), consumer_thread_([this]() { ConsumerThreadMain(); }), mutex_(), state_changed_condition_(), state_() {} RingBufferAnnotationSnapshot(const RingBufferAnnotationSnapshot&) = delete; RingBufferAnnotationSnapshot& operator=(const RingBufferAnnotationSnapshot&) = delete; void Start() { main_loop_thread_.Start(); producer_thread_.Start(); consumer_thread_.Start(); } void Stop() { consumer_thread_.Join(); producer_thread_.Join(); main_loop_thread_.Join(); } private: void MainLoopThreadMain() { std::chrono::steady_clock::time_point main_thread_end_time; if (params_.main_thread_run_duration) { main_thread_end_time = std::chrono::steady_clock::now() + *params_.main_thread_run_duration; } else { main_thread_end_time = std::chrono::steady_clock::time_point::max(); } for (uint64_t i = 0; i < params_.num_loops && std::chrono::steady_clock::now() < main_thread_end_time; i++) { { std::unique_lock start_lock(mutex_); state_.ring_buffer_annotation.ResetForTesting(); state_.ring_buffer_ready = true; state_changed_condition_.notify_all(); } { std::unique_lock lock(mutex_); state_changed_condition_.wait(lock, [this] { return state_.producer_thread_finished && state_.consumer_thread_finished; }); state_.ring_buffer_ready = false; if (g_should_exit) { printf("Exiting on Control-C.\n"); break; } printf("."); fflush(stdout); state_changed_condition_.notify_all(); } } state_.should_exit = true; state_changed_condition_.notify_all(); } void ProducerThreadMain() { while (true) { { std::unique_lock lock(mutex_); state_changed_condition_.wait(lock, [this] { return state_.should_exit || state_.ring_buffer_ready; }); if (state_.should_exit) { return; } state_.producer_thread_running = true; state_.producer_thread_finished = false; state_changed_condition_.notify_all(); } auto min_run_duration_micros = std::chrono::duration_cast( params_.producer_thread_min_run_duration); auto max_run_duration_micros = std::chrono::duration_cast( params_.producer_thread_max_run_duration); std::uniform_int_distribution run_duration_distribution(min_run_duration_micros.count(), max_run_duration_micros.count()); static thread_local std::mt19937 random_number_generator; auto run_duration = std::chrono::microseconds( run_duration_distribution(random_number_generator)); auto end_time = std::chrono::steady_clock::now() + run_duration; uint64_t next_value = 0; while (std::chrono::steady_clock::now() < end_time) { if (!Produce(next_value++)) { // The consumer thread interrupted this. break; } } { std::unique_lock lock(mutex_); state_changed_condition_.wait( lock, [this] { return state_.consumer_thread_finished; }); state_.producer_thread_running = false; state_.producer_thread_finished = true; state_changed_condition_.notify_all(); } } } bool Produce(uint64_t value) { std::string hex_value = base::StringPrintf("0x%08" PRIx64, value); if (!state_.ring_buffer_annotation.Push( hex_value.data(), static_cast(hex_value.size()))) { fprintf(stderr, "Ignoring failed call to Push(0x%" PRIx64 ") (ScopedSpinGuard was held by snapshot thread)\n", value); return false; } return true; } void ConsumerThreadMain() { while (true) { { std::unique_lock lock(mutex_); state_changed_condition_.wait(lock, [this] { return state_.should_exit || (state_.ring_buffer_ready && state_.producer_thread_running); }); if (state_.should_exit) { return; } state_.consumer_thread_finished = false; state_changed_condition_.notify_all(); } auto min_run_duration_micros = std::chrono::duration_cast( params_.consumer_thread_min_run_duration); auto max_run_duration_micros = std::chrono::duration_cast( params_.consumer_thread_max_run_duration); std::uniform_int_distribution run_duration_distribution(min_run_duration_micros.count(), max_run_duration_micros.count()); static thread_local std::mt19937 random_number_generator; auto run_duration = std::chrono::microseconds( run_duration_distribution(random_number_generator)); auto end_time = std::chrono::steady_clock::now() + run_duration; while (std::chrono::steady_clock::now() < end_time) { constexpr uint64_t kSleepTimeNs = 10000; // 10 us SleepNanoseconds(kSleepTimeNs); } Snapshot(); { std::unique_lock lock(mutex_); state_.consumer_thread_finished = true; state_.ring_buffer_ready = false; state_changed_condition_.notify_all(); } } } void Snapshot() { int64_t timeout_ns = static_cast( std::chrono::duration_cast( params_.quiesce_timeout) .count()); uint8_t serialized_ring_buffer[sizeof(state_.ring_buffer_annotation)]; Annotation::ValueSizeType ring_buffer_size; { std::optional scoped_spin_guard; if (params_.mode == RingBufferAnnotationSnapshotParams::Mode::kUseScopedSpinGuard) { scoped_spin_guard = state_.ring_buffer_annotation.TryCreateScopedSpinGuard(timeout_ns); } if (params_.mode == RingBufferAnnotationSnapshotParams::Mode::kUseScopedSpinGuard && !scoped_spin_guard) { fprintf(stderr, "Could not quiesce writes within %" PRIi64 " ns\n", timeout_ns); abort(); } ring_buffer_size = state_.ring_buffer_annotation.size(); memcpy(&serialized_ring_buffer[0], state_.ring_buffer_annotation.value(), ring_buffer_size); } RingBufferData ring_buffer; if (!ring_buffer.DeserializeFromBuffer(serialized_ring_buffer, ring_buffer_size)) { fprintf(stderr, "Could not deserialize ring buffer\n"); abort(); } LengthDelimitedRingBufferReader ring_buffer_reader(ring_buffer); int value = std::numeric_limits::max(); std::vector bytes; while (ring_buffer_reader.Pop(bytes)) { int next_value; std::string_view str(reinterpret_cast(&bytes[0]), bytes.size()); if (!base::HexStringToInt(str, &next_value)) { fprintf(stderr, "Couldn't parse value: [%.*s]\n", base::checked_cast(bytes.size()), bytes.data()); abort(); } if (value == std::numeric_limits::max()) { // First value in buffer. } else if (value + 1 != next_value) { fprintf(stderr, "Expected value 0x%08x, got 0x%08x\n", value + 1, next_value); abort(); } value = next_value; bytes.clear(); } } const RingBufferAnnotationSnapshotParams params_; Thread main_loop_thread_; Thread producer_thread_; Thread consumer_thread_; std::mutex mutex_; // Fired whenever `state_` changes. std::condition_variable state_changed_condition_; // Protected by `mutex_`. State state_; }; void Usage(const base::FilePath& me) { // clang-format off fprintf(stderr, "Usage: %" PRFilePath " [OPTION]...\n" "Runs a load test for concurrent I/O to RingBufferAnnotation.\n" "\n" "By default, enables the annotation spin guard and runs indefinitely\n" "until interrupted (e.g., with Control-C or SIGINT).\n" "\n" " -d,--disable-spin-guard Disables the annotation spin guard\n" " (the test is expected to crash in this case)\n" " -n,--num-loops=N Runs the test for N iterations, not indefinitely\n" " -s,--duration-secs=SECS Runs the test for SECS seconds, not indefinitely\n", me.value().c_str()); // clang-format on ToolSupport::UsageTail(me); } int TestMain(int argc, char** argv) { const base::FilePath argv0( ToolSupport::CommandLineArgumentToFilePathStringType(argv[0])); const base::FilePath me(argv0.BaseName()); #if BUILDFLAG(IS_WIN) auto handler_routine = [](DWORD type) -> BOOL { if (type == CTRL_C_EVENT) { g_should_exit = true; return TRUE; } return FALSE; }; if (!SetConsoleCtrlHandler(handler_routine, /*Add=*/TRUE)) { fprintf(stderr, "Couldn't set Control-C handler\n"); return EXIT_FAILURE; } #else signal(SIGINT, [](int signal) { g_should_exit = true; }); #endif // BUILDFLAG(IS_WIN) RingBufferAnnotationSnapshotParams params; enum OptionFlags { // "Short" (single-character) options. kOptionDisableSpinGuard = 'd', kOptionNumLoops = 'n', kOptionDurationSecs = 's', // Standard options. kOptionHelp = -2, kOptionVersion = -3, }; static constexpr option long_options[] = { {"disable-spin-guard", no_argument, nullptr, kOptionDisableSpinGuard}, {"num-loops", required_argument, nullptr, kOptionNumLoops}, {"duration-secs", required_argument, nullptr, kOptionDurationSecs}, {"help", no_argument, nullptr, kOptionHelp}, {"version", no_argument, nullptr, kOptionVersion}, {nullptr, 0, nullptr, 0}, }; int opt; while ((opt = getopt_long(argc, argv, "dn:s:", long_options, nullptr)) != -1) { switch (opt) { case kOptionDisableSpinGuard: printf("Disabling spin guard logic (this test will fail!)\n"); params.mode = RingBufferAnnotationSnapshotParams::Mode::kDoNotUseSpinGuard; break; case kOptionNumLoops: { std::string num_loops(optarg); uint64_t num_loops_value; if (!StringToNumber(num_loops, &num_loops_value)) { ToolSupport::UsageHint(me, "--num-loops requires integer value"); return EXIT_FAILURE; } params.num_loops = num_loops_value; break; } case kOptionDurationSecs: { std::string duration_secs(optarg); uint64_t duration_secs_value; if (!StringToNumber(duration_secs, &duration_secs_value)) { ToolSupport::UsageHint(me, "--duration-secs requires integer value"); return EXIT_FAILURE; } params.main_thread_run_duration = std::chrono::seconds(duration_secs_value); break; } case kOptionHelp: Usage(me); return EXIT_SUCCESS; case kOptionVersion: ToolSupport::Version(me); return EXIT_SUCCESS; default: ToolSupport::UsageHint(me, nullptr); return EXIT_FAILURE; } } RingBufferAnnotationSnapshot<8192> test_producer_snapshot(params); printf("Starting test (Control-C to exit)...\n"); test_producer_snapshot.Start(); test_producer_snapshot.Stop(); printf("Test finished.\n"); return EXIT_SUCCESS; } } // namespace } // namespace test } // namespace crashpad #if BUILDFLAG(IS_POSIX) int main(int argc, char** argv) { return crashpad::test::TestMain(argc, argv); } #elif BUILDFLAG(IS_WIN) int wmain(int argc, wchar_t* argv[]) { return crashpad::ToolSupport::Wmain(argc, argv, crashpad::test::TestMain); } #endif ================================================ FILE: client/ring_buffer_annotation_test.cc ================================================ // Copyright 2023 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/ring_buffer_annotation.h" #include "client/length_delimited_ring_buffer.h" #include #include #include "client/annotation_list.h" #include "client/crashpad_info.h" #include "gtest/gtest.h" #include "test/gtest_death.h" namespace crashpad { namespace test { namespace { constexpr uint32_t kRingBufferHeaderSize = 16; constexpr uint32_t kLengthDelimiter1ByteSize = 1; class RingBufferAnnotationTest : public testing::Test { public: void SetUp() override { CrashpadInfo::GetCrashpadInfo()->set_annotations_list(&annotations_); } void TearDown() override { CrashpadInfo::GetCrashpadInfo()->set_annotations_list(nullptr); } size_t AnnotationsCount() { size_t result = 0; for (auto* annotation : annotations_) { if (annotation->is_set()) ++result; } return result; } protected: AnnotationList annotations_; }; TEST_F(RingBufferAnnotationTest, Basics) { constexpr Annotation::Type kType = Annotation::UserDefinedType(1); constexpr char kName[] = "annotation 1"; RingBufferAnnotation annotation(kType, kName); EXPECT_FALSE(annotation.is_set()); EXPECT_EQ(0u, AnnotationsCount()); EXPECT_EQ(kType, annotation.type()); EXPECT_EQ(0u, annotation.size()); EXPECT_EQ(std::string(kName), annotation.name()); EXPECT_TRUE( annotation.Push(reinterpret_cast("0123456789"), 10)); EXPECT_TRUE(annotation.is_set()); EXPECT_EQ(1u, AnnotationsCount()); constexpr Annotation::ValueSizeType kExpectedSize = kRingBufferHeaderSize + kLengthDelimiter1ByteSize + 10u; EXPECT_EQ(kExpectedSize, annotation.size()); EXPECT_EQ(&annotation, *annotations_.begin()); RingBufferData data; EXPECT_TRUE( data.DeserializeFromBuffer(annotation.value(), annotation.size())); EXPECT_EQ(kExpectedSize, data.GetRingBufferLength()); std::vector popped_value; LengthDelimitedRingBufferReader reader(data); EXPECT_TRUE(reader.Pop(popped_value)); const std::vector expected = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; EXPECT_EQ(expected, popped_value); annotation.Clear(); EXPECT_FALSE(annotation.is_set()); EXPECT_EQ(0u, AnnotationsCount()); EXPECT_EQ(0u, annotation.size()); } TEST_F(RingBufferAnnotationTest, MultiplePushesWithoutWrapping) { constexpr Annotation::Type kType = Annotation::UserDefinedType(1); constexpr char kName[] = "annotation 1"; RingBufferAnnotation annotation(kType, kName); EXPECT_TRUE( annotation.Push(reinterpret_cast("0123456789"), 10)); EXPECT_TRUE(annotation.Push(reinterpret_cast("ABCDEF"), 6)); EXPECT_TRUE(annotation.is_set()); EXPECT_EQ(1u, AnnotationsCount()); constexpr Annotation::ValueSizeType kExpectedSize = kRingBufferHeaderSize + kLengthDelimiter1ByteSize + 10u + kLengthDelimiter1ByteSize + 6u; EXPECT_EQ(kExpectedSize, annotation.size()); EXPECT_EQ(&annotation, *annotations_.begin()); RingBufferData data; EXPECT_TRUE( data.DeserializeFromBuffer(annotation.value(), annotation.size())); EXPECT_EQ(kExpectedSize, data.GetRingBufferLength()); std::vector popped_value; LengthDelimitedRingBufferReader reader(data); EXPECT_TRUE(reader.Pop(popped_value)); const std::vector expected1 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; EXPECT_EQ(expected1, popped_value); popped_value.clear(); EXPECT_TRUE(reader.Pop(popped_value)); const std::vector expected2 = {'A', 'B', 'C', 'D', 'E', 'F'}; EXPECT_EQ(expected2, popped_value); } TEST_F(RingBufferAnnotationTest, MultiplePushCallsWithWrappingShouldOverwriteInFIFOOrder) { constexpr Annotation::Type kType = Annotation::UserDefinedType(1); constexpr char kName[] = "annotation 1"; RingBufferAnnotation<10> annotation(kType, kName); // Each Push() call will push 1 byte for the varint 128-encoded length, // then the number of bytes specified. constexpr char kFirst[] = "AAA"; constexpr char kSecond[] = "BBB"; constexpr char kThird[] = "CCC"; // This takes up bytes 0-3 of the 10-byte RingBufferAnnotation. ASSERT_TRUE(annotation.Push(reinterpret_cast(kFirst), 3)); // This takes up bytes 4-7 of the 10-byte RingBufferAnnotation. ASSERT_TRUE(annotation.Push(reinterpret_cast(kSecond), 3)); // This should wrap around the end of the array and overwrite kFirst since it // needs 4 bytes but there are only 2 left. ASSERT_TRUE(annotation.Push(reinterpret_cast(kThird), 3)); // The size of the annotation should include the header and the full 10 bytes // of the ring buffer, since the third write wrapped around the end. ASSERT_EQ(kRingBufferHeaderSize + 10u, annotation.size()); // This data size needs to match the size in the RingBufferAnnotation above. RingBufferData<10> data; ASSERT_TRUE( data.DeserializeFromBuffer(annotation.value(), annotation.size())); std::vector popped_value; LengthDelimitedRingBufferReader reader(data); ASSERT_TRUE(reader.Pop(popped_value)); // "AAA" has been overwritten, so the first thing popped should be "BBB". const std::vector expected_b = {'B', 'B', 'B'}; EXPECT_EQ(expected_b, popped_value); popped_value.clear(); ASSERT_TRUE(reader.Pop(popped_value)); const std::vector expected_c = {'C', 'C', 'C'}; EXPECT_EQ(expected_c, popped_value); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/settings.cc ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/settings.h" #include #include #include #include "base/logging.h" #include "base/notreached.h" #include "base/posix/eintr_wrapper.h" #include "build/build_config.h" #include "util/file/filesystem.h" #include "util/numeric/in_range_cast.h" namespace crashpad { #if !CRASHPAD_FLOCK_ALWAYS_SUPPORTED Settings::ScopedLockedFileHandle::ScopedLockedFileHandle() : handle_(kInvalidFileHandle), lockfile_path_() { } Settings::ScopedLockedFileHandle::ScopedLockedFileHandle( FileHandle handle, const base::FilePath& lockfile_path) : handle_(handle), lockfile_path_(lockfile_path) { } Settings::ScopedLockedFileHandle::ScopedLockedFileHandle( ScopedLockedFileHandle&& other) : handle_(other.handle_), lockfile_path_(other.lockfile_path_) { other.handle_ = kInvalidFileHandle; other.lockfile_path_ = base::FilePath(); } Settings::ScopedLockedFileHandle& Settings::ScopedLockedFileHandle::operator=( ScopedLockedFileHandle&& other) { handle_ = other.handle_; lockfile_path_ = other.lockfile_path_; other.handle_ = kInvalidFileHandle; other.lockfile_path_ = base::FilePath(); return *this; } Settings::ScopedLockedFileHandle::~ScopedLockedFileHandle() { Destroy(); } void Settings::ScopedLockedFileHandle::Destroy() { if (handle_ != kInvalidFileHandle) { CheckedCloseFile(handle_); } if (!lockfile_path_.empty()) { const bool success = LoggingRemoveFile(lockfile_path_); DCHECK(success); } } #else // !CRASHPAD_FLOCK_ALWAYS_SUPPORTED #if BUILDFLAG(IS_IOS) Settings::ScopedLockedFileHandle::ScopedLockedFileHandle( const FileHandle& value) : ScopedGeneric(value) { ios_background_task_ = std::make_unique( "ScopedLockedFileHandle"); } Settings::ScopedLockedFileHandle::ScopedLockedFileHandle( Settings::ScopedLockedFileHandle&& rvalue) { ios_background_task_.reset(rvalue.ios_background_task_.release()); reset(rvalue.release()); } Settings::ScopedLockedFileHandle& Settings::ScopedLockedFileHandle::operator=( Settings::ScopedLockedFileHandle&& rvalue) { ios_background_task_.reset(rvalue.ios_background_task_.release()); reset(rvalue.release()); return *this; } Settings::ScopedLockedFileHandle::~ScopedLockedFileHandle() { // Call reset() to ensure the lock is released before the ios_background_task. reset(); } #endif // BUILDFLAG(IS_IOS) namespace internal { // static void ScopedLockedFileHandleTraits::Free(FileHandle handle) { if (handle != kInvalidFileHandle) { LoggingUnlockFile(handle); CheckedCloseFile(handle); } } } // namespace internal #endif // BUILDFLAG(IS_FUCHSIA) struct SettingsReader::Data { static constexpr uint32_t kSettingsMagic = 'CPds'; // Version number only used for incompatible changes to Data. Do not change // this when adding additional fields at the end. Modifying `kSettingsVersion` // will wipe away the entire struct when reading from other versions. static constexpr uint32_t kSettingsVersion = 1; enum Options : uint32_t { kUploadsEnabled = 1 << 0, }; Data() : magic(kSettingsMagic), version(kSettingsVersion), options(0), padding_0(0), last_upload_attempt_time(0), client_id() {} uint32_t magic; uint32_t version; uint32_t options; uint32_t padding_0; int64_t last_upload_attempt_time; // time_t UUID client_id; }; SettingsReader::SettingsReader(const base::FilePath& file_path) : SettingsReader(file_path, InitializationState::kStateValid) {} SettingsReader::SettingsReader(const base::FilePath& file_path, InitializationState::State state) : file_path_(file_path), initialized_(state) {} SettingsReader::~SettingsReader() = default; bool SettingsReader::GetClientID(UUID* client_id) { DCHECK(initialized().is_valid()); Data settings; if (!OpenAndReadSettings(&settings)) return false; *client_id = settings.client_id; return true; } bool SettingsReader::GetUploadsEnabled(bool* enabled) { DCHECK(initialized().is_valid()); Data settings; if (!OpenAndReadSettings(&settings)) return false; *enabled = (settings.options & Data::Options::kUploadsEnabled) != 0; return true; } bool SettingsReader::GetLastUploadAttemptTime(time_t* time) { DCHECK(initialized().is_valid()); Data settings; if (!OpenAndReadSettings(&settings)) return false; *time = InRangeCast(settings.last_upload_attempt_time, std::numeric_limits::max()); return true; } bool SettingsReader::OpenAndReadSettings(Data* out_data) { ScopedFileHandle handle(LoggingOpenFileForRead(file_path_)); return handle.is_valid() && ReadSettings(handle.get(), out_data, true); } bool SettingsReader::ReadSettings(FileHandle handle, Data* out_data, bool log_read_error) { if (LoggingSeekFile(handle, 0, SEEK_SET) != 0) { return false; } // This clears `out_data` so that any bytes not read from disk are zero // initialized. This is expected when reading from an older settings file with // fewer fields. memset(out_data, 0, sizeof(*out_data)); const FileOperationResult read_result = log_read_error ? LoggingReadFileUntil(handle, out_data, sizeof(*out_data)) : ReadFileUntil(handle, out_data, sizeof(*out_data)); if (read_result <= 0) { return false; } // Newer versions of crashpad may add fields to Data, but all versions have // the data members up to `client_id`. Do not attempt to understand a smaller // struct read. const size_t min_size = offsetof(Data, client_id) + sizeof(out_data->client_id); if (static_cast(read_result) < min_size) { LOG(ERROR) << "Settings file too small: minimum " << min_size << ", observed " << read_result; return false; } if (out_data->magic != Data::kSettingsMagic) { LOG(ERROR) << "Settings magic is not " << Data::kSettingsMagic; return false; } if (out_data->version != Data::kSettingsVersion) { LOG(ERROR) << "Settings version is not " << Data::kSettingsVersion; return false; } return true; } Settings::Settings(const base::FilePath& path) : SettingsReader(path, InitializationState::kStateUninitialized) {} Settings::~Settings() = default; bool Settings::Initialize() { DCHECK(initialized().is_uninitialized()); initialized().set_invalid(); Data settings; if (!OpenForWritingAndReadSettings(&settings).is_valid()) return false; initialized().set_valid(); return true; } bool Settings::SetUploadsEnabled(bool enabled) { DCHECK(initialized().is_valid()); Data settings; ScopedLockedFileHandle handle = OpenForWritingAndReadSettings(&settings); if (!handle.is_valid()) return false; if (enabled) settings.options |= Data::Options::kUploadsEnabled; else settings.options &= ~Data::Options::kUploadsEnabled; return WriteSettings(handle.get(), settings); } bool Settings::SetLastUploadAttemptTime(time_t time) { DCHECK(initialized().is_valid()); Data settings; ScopedLockedFileHandle handle = OpenForWritingAndReadSettings(&settings); if (!handle.is_valid()) return false; settings.last_upload_attempt_time = InRangeCast(time, 0); return WriteSettings(handle.get(), settings); } #if !CRASHPAD_FLOCK_ALWAYS_SUPPORTED // static bool Settings::IsLockExpired(const base::FilePath& file_path, time_t lockfile_ttl) { time_t now = time(nullptr); base::FilePath lock_path(file_path.value() + Settings::kLockfileExtension); ScopedFileHandle lock_fd(LoggingOpenFileForRead(lock_path)); time_t lock_timestamp; if (!LoggingReadFileExactly( lock_fd.get(), &lock_timestamp, sizeof(lock_timestamp))) { return false; } return now >= lock_timestamp + lockfile_ttl; } #endif // !CRASHPAD_FLOCK_ALWAYS_SUPPORTED // static Settings::ScopedLockedFileHandle Settings::MakeScopedLockedFileHandle( const internal::MakeScopedLockedFileHandleOptions& options, FileLocking locking, const base::FilePath& file_path) { #if !CRASHPAD_FLOCK_ALWAYS_SUPPORTED base::FilePath lockfile_path(file_path.value() + Settings::kLockfileExtension); ScopedFileHandle lockfile_scoped( LoggingOpenFileForWrite(lockfile_path, FileWriteMode::kCreateOrFail, FilePermissions::kWorldReadable)); if (!lockfile_scoped.is_valid()) { return ScopedLockedFileHandle(); } time_t now = time(nullptr); if (!LoggingWriteFile(lockfile_scoped.get(), &now, sizeof(now))) { return ScopedLockedFileHandle(); } ScopedFileHandle scoped(GetHandleFromOptions(file_path, options)); if (scoped.is_valid()) { return ScopedLockedFileHandle(scoped.release(), lockfile_path); } bool success = LoggingRemoveFile(lockfile_path); DCHECK(success); return ScopedLockedFileHandle(); #else ScopedFileHandle scoped(GetHandleFromOptions(file_path, options)); // It's important to create the ScopedLockedFileHandle before calling // LoggingLockFile on iOS, so a ScopedBackgroundTask is created *before* // the LoggingLockFile call below. ScopedLockedFileHandle handle(kInvalidFileHandle); if (scoped.is_valid()) { if (LoggingLockFile( scoped.get(), locking, FileLockingBlocking::kBlocking) != FileLockingResult::kSuccess) { scoped.reset(); } } handle.reset(scoped.release()); return handle; #endif // !CRASHPAD_FLOCK_ALWAYS_SUPPORTED } // static FileHandle Settings::GetHandleFromOptions( const base::FilePath& file_path, const internal::MakeScopedLockedFileHandleOptions& options) { switch (options.function_enum) { case internal::FileOpenFunction::kLoggingOpenFileForRead: return LoggingOpenFileForRead(file_path); case internal::FileOpenFunction::kLoggingOpenFileForReadAndWrite: return LoggingOpenFileForReadAndWrite( file_path, options.mode, options.permissions); case internal::FileOpenFunction::kOpenFileForReadAndWrite: return OpenFileForReadAndWrite( file_path, options.mode, options.permissions); } NOTREACHED(); } Settings::ScopedLockedFileHandle Settings::OpenForReading() const { internal::MakeScopedLockedFileHandleOptions options; options.function_enum = internal::FileOpenFunction::kLoggingOpenFileForRead; return MakeScopedLockedFileHandle(options, FileLocking::kShared, file_path()); } Settings::ScopedLockedFileHandle Settings::OpenForReadingAndWriting( FileWriteMode mode, bool log_open_error) { DCHECK(mode != FileWriteMode::kTruncateOrCreate); internal::MakeScopedLockedFileHandleOptions options; options.mode = mode; options.permissions = FilePermissions::kOwnerOnly; if (log_open_error) { options.function_enum = internal::FileOpenFunction::kLoggingOpenFileForReadAndWrite; } else { options.function_enum = internal::FileOpenFunction::kOpenFileForReadAndWrite; } return MakeScopedLockedFileHandle( options, FileLocking::kExclusive, file_path()); } bool Settings::OpenAndReadSettings(Data* out_data) { // Because this implementation distinguishes between a failure to open and a // failure to read the settings file, it cannot simply invoke // SettingsReader::OpenAndReadSettings. ScopedLockedFileHandle handle = OpenForReading(); if (!handle.is_valid()) return false; if (ReadSettings(handle.get(), out_data, true)) return true; // The settings file is corrupt, so reinitialize it. handle.reset(); // The settings failed to be read, so re-initialize them. return RecoverSettings(kInvalidFileHandle, out_data); } Settings::ScopedLockedFileHandle Settings::OpenForWritingAndReadSettings( Data* out_data) { ScopedLockedFileHandle handle; bool created = false; if (!initialized().is_valid()) { // If this object is initializing, it hasn’t seen a settings file already, // so go easy on errors. Creating a new settings file for the first time // shouldn’t spew log messages. // // First, try to use an existing settings file. handle = OpenForReadingAndWriting(FileWriteMode::kReuseOrFail, false); if (!handle.is_valid()) { // Create a new settings file if it didn’t already exist. handle = OpenForReadingAndWriting(FileWriteMode::kCreateOrFail, false); if (handle.is_valid()) { created = true; } // There may have been a race to create the file, and something else may // have won. There will be one more attempt to try to open or create the // file below. } } if (!handle.is_valid()) { // Either the object is initialized, meaning it’s already seen a valid // settings file, or the object is initializing and none of the above // attempts to create the settings file succeeded. Either way, this is the // last chance for success, so if this fails, log a message. handle = OpenForReadingAndWriting(FileWriteMode::kReuseOrCreate, true); } if (!handle.is_valid()) return ScopedLockedFileHandle(); // Attempt reading the settings even if the file is known to have just been // created. The file-create and file-lock operations don’t occur atomically, // and something else may have written the settings before this invocation // took the lock. If the settings file was definitely just created, though, // don’t log any read errors. The expected non-race behavior in this case is a // zero-length read, with ReadSettings() failing. if (!ReadSettings(handle.get(), out_data, !created)) { if (!RecoverSettings(handle.get(), out_data)) return ScopedLockedFileHandle(); } return handle; } bool Settings::WriteSettings(FileHandle handle, const Data& data) { if (LoggingSeekFile(handle, 0, SEEK_SET) != 0) return false; if (!LoggingTruncateFile(handle)) return false; return LoggingWriteFile(handle, &data, sizeof(Data)); } bool Settings::RecoverSettings(FileHandle handle, Data* out_data) { ScopedLockedFileHandle scoped_handle; if (handle == kInvalidFileHandle) { scoped_handle = OpenForReadingAndWriting(FileWriteMode::kReuseOrCreate, true); handle = scoped_handle.get(); // Test if the file has already been recovered now that the exclusive lock // is held. if (ReadSettings(handle, out_data, true)) return true; } if (handle == kInvalidFileHandle) { LOG(ERROR) << "Invalid file handle"; return false; } if (!InitializeSettings(handle)) return false; return ReadSettings(handle, out_data, true); } bool Settings::InitializeSettings(FileHandle handle) { Data settings; if (!settings.client_id.InitializeWithNew()) return false; return WriteSettings(handle, settings); } } // namespace crashpad ================================================ FILE: client/settings.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_SETTINGS_H_ #define CRASHPAD_CLIENT_SETTINGS_H_ #include #include "base/files/file_path.h" #include "base/scoped_generic.h" #include "build/build_config.h" #include "util/file/file_io.h" #include "util/misc/initialization_state.h" #include "util/misc/uuid.h" #if BUILDFLAG(IS_IOS) #include "util/ios/scoped_background_task.h" #endif // BUILDFLAG(IS_IOS) namespace crashpad { namespace internal { struct ScopedLockedFileHandleTraits { static FileHandle InvalidValue() { return kInvalidFileHandle; } static void Free(FileHandle handle); }; enum class FileOpenFunction { kLoggingOpenFileForRead, kLoggingOpenFileForReadAndWrite, kOpenFileForReadAndWrite, }; struct MakeScopedLockedFileHandleOptions { FileOpenFunction function_enum; FileWriteMode mode; FilePermissions permissions; }; // TODO(mark): The timeout should be configurable by the client. #if BUILDFLAG(IS_IOS) // iOS background assertions only last 30 seconds, keep the timeout shorter. constexpr double kUploadReportTimeoutSeconds = 20; #else constexpr double kUploadReportTimeoutSeconds = 60; #endif } // namespace internal //! \brief A class for accessing the settings of a CrashReportDatabase. //! //! SettingsReader does not participate in file locking. //! //! This class must not be instantiated directly, but rather an instance of it //! should be retrieved via //! CrashReportDatabase::GetSettingsReaderForDatabasePath. class SettingsReader { public: explicit SettingsReader(const base::FilePath& path); SettingsReader(const SettingsReader&) = delete; SettingsReader& operator=(const SettingsReader&) = delete; virtual ~SettingsReader(); //! \brief Retrieves the immutable identifier for this client, which is used //! on a server to locate all crash reports from a specific Crashpad //! database. //! //! \param[out] client_id The unique client identifier. //! //! \return On success, returns `true`, otherwise returns `false` with an //! error logged. bool GetClientID(UUID* client_id); //! \brief Retrieves the user’s preference for submitting crash reports to a //! collection server. //! //! The default value is `false`. //! //! \note //! This setting is ignored if --use-cros-crash-reporter is present //! (which it will be if invoked by Chrome on ChromeOS). //! //! \param[out] enabled Whether crash reports should be uploaded. //! //! \return On success, returns `true`, otherwise returns `false` with an //! error logged. bool GetUploadsEnabled(bool* enabled); //! \brief Retrieves the last time at which a report was attempted to be //! uploaded. //! //! The default value is `0` if it has never been set before. //! //! \param[out] time The last time at which a report was uploaded. //! //! \return On success, returns `true`, otherwise returns `false` with an //! error logged. bool GetLastUploadAttemptTime(time_t* time); protected: struct Data; SettingsReader(const base::FilePath& path, InitializationState::State state); // Opens the settings file and reads the data. If that fails, an error will // be logged and the function will return false. virtual bool OpenAndReadSettings(Data* out_data); // Reads the settings from |handle|. Logs an error and returns false on // failure. // // If |log_read_error| is false, nothing will be logged for a read error, but // this method will still return false. This is intended to be used to // suppress error messages when attempting to read a newly created settings // file. bool ReadSettings(FileHandle handle, Data* out_data, bool log_read_error); const base::FilePath& file_path() const { return file_path_; } InitializationState& initialized() { return initialized_; } private: const base::FilePath file_path_; InitializationState initialized_; }; //! \brief An interface for accessing and modifying the settings of a //! CrashReportDatabase. //! //! This class must not be instantiated directly, but rather an instance of it //! should be retrieved via CrashReportDatabase::GetSettings(). //! //! Settings will lock files prior to reading or writing them, and respect //! existing locks. class Settings final : public SettingsReader { public: static inline constexpr char kLockfileExtension[] = ".__lock__"; explicit Settings(const base::FilePath& file_path); ~Settings(); //! \brief Initializes the settings data store. //! //! This method must be called only once, and must be successfully called //! before any other method in this class may be called. //! //! \return `true` if the data store was initialized successfully, otherwise //! `false` with an error logged. bool Initialize(); //! \brief Sets the user’s preference for submitting crash reports to a //! collection server. //! //! \param[in] enabled Whether crash reports should be uploaded. //! //! \return On success, returns `true`, otherwise returns `false` with an //! error logged. bool SetUploadsEnabled(bool enabled); //! \brief Sets the last time at which a report was attempted to be uploaded. //! //! This is only meant to be used internally by the CrashReportDatabase. //! //! \param[in] time The last time at which a report was uploaded. //! //! \return On success, returns `true`, otherwise returns `false` with an //! error logged. bool SetLastUploadAttemptTime(time_t time); #if !CRASHPAD_FLOCK_ALWAYS_SUPPORTED //! \brief Returns whether the lockfile for a file is expired. //! //! This could be part of ScopedLockedFileHandle, but this needs to be //! public while ScopedLockedFileHandle is private to Settings. //! //! \param[in] file_path The path to the file whose lockfile will be checked. //! \param[in] lockfile_ttl How long the lockfile has to live before expiring. //! //! \return `true` if the lock for the file is expired, otherwise `false`. static bool IsLockExpired(const base::FilePath& file_path, time_t lockfile_ttl); #endif // !CRASHPAD_FLOCK_ALWAYS_SUPPORTED private: // This must be constructed with MakeScopedLockedFileHandle(). It both unlocks // and closes the file on destruction. Note that on Fuchsia, this handle DOES // NOT offer correct operation, only an attempt to DCHECK if racy behavior is // detected. #if !CRASHPAD_FLOCK_ALWAYS_SUPPORTED struct ScopedLockedFileHandle { public: ScopedLockedFileHandle(); ScopedLockedFileHandle(FileHandle handle, const base::FilePath& lockfile_path); ScopedLockedFileHandle(ScopedLockedFileHandle&& other); ScopedLockedFileHandle& operator=(ScopedLockedFileHandle&& other); ScopedLockedFileHandle(const ScopedLockedFileHandle&) = delete; ScopedLockedFileHandle& operator=(const ScopedLockedFileHandle&) = delete; ~ScopedLockedFileHandle(); // These mirror the non-Fuchsia ScopedLockedFileHandle via ScopedGeneric so // that calling code can pretend this implementation is the same. bool is_valid() const { return handle_ != kInvalidFileHandle; } FileHandle get() { return handle_; } void reset() { Destroy(); handle_ = kInvalidFileHandle; lockfile_path_ = base::FilePath(); } private: void Destroy(); FileHandle handle_; base::FilePath lockfile_path_; }; #elif BUILDFLAG(IS_IOS) // iOS needs to use ScopedBackgroundTask anytime a file lock is used. class ScopedLockedFileHandle : public base::ScopedGeneric { public: using base::ScopedGeneric< FileHandle, internal::ScopedLockedFileHandleTraits>::ScopedGeneric; ScopedLockedFileHandle(const FileHandle& value); ScopedLockedFileHandle(ScopedLockedFileHandle&& rvalue); ScopedLockedFileHandle& operator=(ScopedLockedFileHandle&& rvalue); ~ScopedLockedFileHandle(); private: std::unique_ptr ios_background_task_; }; #else using ScopedLockedFileHandle = base::ScopedGeneric; #endif // !CRASHPAD_FLOCK_ALWAYS_SUPPORTED static ScopedLockedFileHandle MakeScopedLockedFileHandle( const internal::MakeScopedLockedFileHandleOptions& options, FileLocking locking, const base::FilePath& file_path); static FileHandle GetHandleFromOptions( const base::FilePath& file_path, const internal::MakeScopedLockedFileHandleOptions& options); // Opens the settings file for reading. On error, logs a message and returns // the invalid handle. ScopedLockedFileHandle OpenForReading() const; // Opens the settings file for reading and writing. On error, logs a message // and returns the invalid handle. |mode| determines how the file will be // opened. |mode| must not be FileWriteMode::kTruncateOrCreate. // // If |log_open_error| is false, nothing will be logged for an error // encountered when attempting to open the file, but this method will still // return false. This is intended to be used to suppress error messages when // attempting to create a new settings file when multiple attempts are made. ScopedLockedFileHandle OpenForReadingAndWriting(FileWriteMode mode, bool log_open_error); // Opens the settings file and reads the data. If that fails, an error will // be logged and the settings will be recovered and re-initialized. If that // also fails, returns false with additional log data from recovery. bool OpenAndReadSettings(Data* out_data) override; // Opens the settings file for writing and reads the data. If reading fails, // recovery is attempted. Returns the opened file handle on success, or the // invalid file handle on failure, with an error logged. ScopedLockedFileHandle OpenForWritingAndReadSettings(Data* out_data); // Writes the settings to |handle|. Logs an error and returns false on // failure. This does not perform recovery. // // |handle| must be the result of OpenForReadingAndWriting(). bool WriteSettings(FileHandle handle, const Data& data); // Recovers the settings file by re-initializing the data. If |handle| is the // invalid handle, this will open the file; if it is not, then it must be the // result of OpenForReadingAndWriting(). If the invalid handle is passed, the // caller must not be holding the handle. The new settings data are stored in // |out_data|. Returns true on success and false on failure, with an error // logged. bool RecoverSettings(FileHandle handle, Data* out_data); // Initializes a settings file and writes the data to |handle|. Returns true // on success and false on failure, with an error logged. // // |handle| must be the result of OpenForReadingAndWriting(). bool InitializeSettings(FileHandle handle); }; } // namespace crashpad #endif // CRASHPAD_CLIENT_SETTINGS_H_ ================================================ FILE: client/settings_test.cc ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/settings.h" #include "build/build_config.h" #include "gtest/gtest.h" #include "test/errors.h" #include "test/scoped_temp_dir.h" #include "util/file/file_io.h" namespace crashpad { namespace test { namespace { class SettingsTest : public testing::Test { public: SettingsTest() : temp_dir_(), settings_path_(temp_dir_.path().Append(FILE_PATH_LITERAL("settings"))), settings_(settings_path_) {} SettingsTest(const SettingsTest&) = delete; SettingsTest& operator=(const SettingsTest&) = delete; base::FilePath settings_path() { return settings_path_; } Settings* settings() { return &settings_; } void InitializeBadFile() { ScopedFileHandle handle( LoggingOpenFileForWrite(settings_path(), FileWriteMode::kTruncateOrCreate, FilePermissions::kWorldReadable)); ASSERT_TRUE(handle.is_valid()); static constexpr char kBuf[] = "test bad file"; ASSERT_TRUE(LoggingWriteFile(handle.get(), kBuf, sizeof(kBuf))); handle.reset(); } protected: // testing::Test: void SetUp() override { ASSERT_TRUE(settings()->Initialize()); } private: const ScopedTempDir temp_dir_; const base::FilePath settings_path_; Settings settings_; }; TEST_F(SettingsTest, ClientID) { UUID client_id; EXPECT_TRUE(settings()->GetClientID(&client_id)); EXPECT_NE(client_id, UUID()); Settings local_settings(settings_path()); EXPECT_TRUE(local_settings.Initialize()); UUID actual; EXPECT_TRUE(local_settings.GetClientID(&actual)); EXPECT_EQ(actual, client_id); } TEST_F(SettingsTest, UploadsEnabled) { bool enabled = true; // Default value is false. EXPECT_TRUE(settings()->GetUploadsEnabled(&enabled)); EXPECT_FALSE(enabled); EXPECT_TRUE(settings()->SetUploadsEnabled(true)); EXPECT_TRUE(settings()->GetUploadsEnabled(&enabled)); EXPECT_TRUE(enabled); Settings local_settings(settings_path()); EXPECT_TRUE(local_settings.Initialize()); enabled = false; EXPECT_TRUE(local_settings.GetUploadsEnabled(&enabled)); EXPECT_TRUE(enabled); EXPECT_TRUE(settings()->SetUploadsEnabled(false)); EXPECT_TRUE(settings()->GetUploadsEnabled(&enabled)); EXPECT_FALSE(enabled); enabled = true; EXPECT_TRUE(local_settings.GetUploadsEnabled(&enabled)); EXPECT_FALSE(enabled); } TEST_F(SettingsTest, LastUploadAttemptTime) { time_t actual = -1; EXPECT_TRUE(settings()->GetLastUploadAttemptTime(&actual)); // Default value is 0. EXPECT_EQ(actual, 0); const time_t expected = time(nullptr); EXPECT_TRUE(settings()->SetLastUploadAttemptTime(expected)); EXPECT_TRUE(settings()->GetLastUploadAttemptTime(&actual)); EXPECT_EQ(actual, expected); Settings local_settings(settings_path()); EXPECT_TRUE(local_settings.Initialize()); actual = -1; EXPECT_TRUE(local_settings.GetLastUploadAttemptTime(&actual)); EXPECT_EQ(actual, expected); } TEST_F(SettingsTest, ReadOnly) { { // A SettingsReader can read an existing setting... SettingsReader reader(settings_path()); bool enabled = true; // Default value is false. EXPECT_TRUE(reader.GetUploadsEnabled(&enabled)); EXPECT_FALSE(enabled); settings()->SetUploadsEnabled(true); EXPECT_TRUE(reader.GetUploadsEnabled(&enabled)); EXPECT_TRUE(enabled); } { // ...but not one that doesn't exist. base::FilePath bad_path = settings_path().DirName().Append(FILE_PATH_LITERAL("does_not_exist")); SettingsReader reader(bad_path); bool enabled = true; EXPECT_FALSE(reader.GetUploadsEnabled(&enabled)); } } // The following tests write a corrupt settings file and test the recovery // operation. TEST_F(SettingsTest, BadFileOnInitialize) { InitializeBadFile(); Settings settings(settings_path()); EXPECT_TRUE(settings.Initialize()); } TEST_F(SettingsTest, BadFileOnGet) { InitializeBadFile(); UUID client_id; EXPECT_TRUE(settings()->GetClientID(&client_id)); EXPECT_NE(client_id, UUID()); Settings local_settings(settings_path()); EXPECT_TRUE(local_settings.Initialize()); UUID actual; EXPECT_TRUE(local_settings.GetClientID(&actual)); EXPECT_EQ(actual, client_id); } TEST_F(SettingsTest, BadFileOnSet) { InitializeBadFile(); EXPECT_TRUE(settings()->SetUploadsEnabled(true)); bool enabled = false; EXPECT_TRUE(settings()->GetUploadsEnabled(&enabled)); EXPECT_TRUE(enabled); } TEST_F(SettingsTest, UnlinkFile) { UUID client_id; EXPECT_TRUE(settings()->GetClientID(&client_id)); EXPECT_TRUE(settings()->SetUploadsEnabled(true)); EXPECT_TRUE(settings()->SetLastUploadAttemptTime(time(nullptr))); #if BUILDFLAG(IS_WIN) EXPECT_EQ(_wunlink(settings_path().value().c_str()), 0) << ErrnoMessage("_wunlink"); #else EXPECT_EQ(unlink(settings_path().value().c_str()), 0) << ErrnoMessage("unlink"); #endif Settings local_settings(settings_path()); EXPECT_TRUE(local_settings.Initialize()); UUID new_client_id; EXPECT_TRUE(local_settings.GetClientID(&new_client_id)); EXPECT_NE(new_client_id, client_id); // Check that all values are reset. bool enabled = true; EXPECT_TRUE(local_settings.GetUploadsEnabled(&enabled)); EXPECT_FALSE(enabled); time_t time = -1; EXPECT_TRUE(local_settings.GetLastUploadAttemptTime(&time)); EXPECT_EQ(time, 0); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/simple_address_range_bag.h ================================================ // Copyright 2016 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_SIMPLE_ADDRESS_RANGE_BAG_H_ #define CRASHPAD_CLIENT_SIMPLE_ADDRESS_RANGE_BAG_H_ #include #include #include #include "base/check_op.h" #include "base/logging.h" #include "base/numerics/safe_conversions.h" #include "util/misc/from_pointer_cast.h" #include "util/numeric/checked_range.h" namespace crashpad { //! \brief A bag implementation using a fixed amount of storage, so that it does //! not perform any dynamic allocations for its operations. //! //! The actual bag storage (TSimpleAddressRangeBag::Entry) is POD, so that it //! can be transmitted over various IPC mechanisms. template class TSimpleAddressRangeBag { public: //! Constant and publicly accessible version of the template parameter. static const size_t num_entries = NumEntries; //! \brief A single entry in the bag. struct Entry { //! \brief The base address of the range. uint64_t base; //! \brief The size of the range in bytes. uint64_t size; //! \brief Returns the validity of the entry. //! //! If #base and #size are both zero, the entry is considered inactive, and //! this method returns `false`. Otherwise, returns `true`. bool is_active() const { return base != 0 || size != 0; } }; //! \brief An iterator to traverse all of the active entries in a //! TSimpleAddressRangeBag. class Iterator { public: explicit Iterator(const TSimpleAddressRangeBag& bag) : bag_(bag), current_(0) { } Iterator(const Iterator&) = delete; Iterator& operator=(const Iterator&) = delete; //! \brief Returns the next entry in the bag, or `nullptr` if at the end of //! the collection. const Entry* Next() { while (current_ < bag_.num_entries) { const Entry* entry = &bag_.entries_[current_++]; if (entry->is_active()) { return entry; } } return nullptr; } private: const TSimpleAddressRangeBag& bag_; size_t current_; }; TSimpleAddressRangeBag() : entries_() { } TSimpleAddressRangeBag(const TSimpleAddressRangeBag& other) { *this = other; } TSimpleAddressRangeBag& operator=(const TSimpleAddressRangeBag& other) { memcpy(entries_, other.entries_, sizeof(entries_)); return *this; } //! \brief Returns the number of active entries. The upper limit for this is //! \a NumEntries. size_t GetCount() const { size_t count = 0; for (size_t i = 0; i < num_entries; ++i) { if (entries_[i].is_active()) { ++count; } } return count; } //! \brief Inserts the given range into the bag. Duplicates and overlapping //! ranges are supported and allowed, but not coalesced. //! //! \param[in] range The range to be inserted. The range must have either a //! non-zero base address or size. //! //! \return `true` if there was space to insert the range into the bag, //! otherwise `false` with an error logged. bool Insert(CheckedRange range) { DCHECK(range.base() != 0 || range.size() != 0); for (size_t i = 0; i < num_entries; ++i) { if (!entries_[i].is_active()) { entries_[i].base = range.base(); entries_[i].size = range.size(); return true; } } LOG(ERROR) << "no space available to insert range"; return false; } //! \brief Inserts the given range into the bag. Duplicates and overlapping //! ranges are supported and allowed, but not coalesced. //! //! \param[in] base The base of the range to be inserted. May not be null. //! \param[in] size The size of the range to be inserted. May not be zero. //! //! \return `true` if there was space to insert the range into the bag, //! otherwise `false` with an error logged. bool Insert(void* base, size_t size) { DCHECK(base != nullptr); DCHECK_NE(0u, size); return Insert(CheckedRange(FromPointerCast(base), base::checked_cast(size))); } //! \brief Removes the given range from the bag. //! //! \param[in] range The range to be removed. The range must have either a //! non-zero base address or size. //! //! \return `true` if the range was found and removed, otherwise `false` with //! an error logged. bool Remove(CheckedRange range) { DCHECK(range.base() != 0 || range.size() != 0); for (size_t i = 0; i < num_entries; ++i) { if (entries_[i].base == range.base() && entries_[i].size == range.size()) { entries_[i].base = entries_[i].size = 0; return true; } } LOG(ERROR) << "did not find range to remove"; return false; } //! \brief Removes the given range from the bag. //! //! \param[in] base The base of the range to be removed. May not be null. //! \param[in] size The size of the range to be removed. May not be zero. //! //! \return `true` if the range was found and removed, otherwise `false` with //! an error logged. bool Remove(void* base, size_t size) { DCHECK(base != nullptr); DCHECK_NE(0u, size); return Remove(CheckedRange(FromPointerCast(base), base::checked_cast(size))); } private: Entry entries_[NumEntries]; }; //! \brief A TSimpleAddressRangeBag with default template parameters. using SimpleAddressRangeBag = TSimpleAddressRangeBag<64>; static_assert(std::is_standard_layout::value, "SimpleAddressRangeBag must be standard layout"); } // namespace crashpad #endif // CRASHPAD_CLIENT_SIMPLE_ADDRESS_RANGE_BAG_H_ ================================================ FILE: client/simple_address_range_bag_test.cc ================================================ // Copyright 2016 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/simple_address_range_bag.h" #include "gtest/gtest.h" #include "test/gtest_death.h" namespace crashpad { namespace test { namespace { TEST(SimpleAddressRangeBag, Entry) { using TestBag = TSimpleAddressRangeBag<15>; TestBag bag; const TestBag::Entry* entry = TestBag::Iterator(bag).Next(); EXPECT_FALSE(entry); bag.Insert(reinterpret_cast(0x1000), 200); entry = TestBag::Iterator(bag).Next(); ASSERT_TRUE(entry); EXPECT_EQ(0x1000u, entry->base); EXPECT_EQ(200u, entry->size); bag.Remove(reinterpret_cast(0x1000), 200); EXPECT_FALSE(entry->is_active()); EXPECT_EQ(0u, entry->base); EXPECT_EQ(0u, entry->size); } TEST(SimpleAddressRangeBag, SimpleAddressRangeBag) { SimpleAddressRangeBag bag; EXPECT_TRUE(bag.Insert(reinterpret_cast(0x1000), 10)); EXPECT_TRUE(bag.Insert(reinterpret_cast(0x2000), 20)); EXPECT_TRUE(bag.Insert(CheckedRange(0x3000, 30))); EXPECT_EQ(3u, bag.GetCount()); // Duplicates added too. EXPECT_TRUE(bag.Insert(CheckedRange(0x3000, 30))); EXPECT_TRUE(bag.Insert(CheckedRange(0x3000, 30))); EXPECT_EQ(5u, bag.GetCount()); // Can be removed 3 times, but not the 4th time. EXPECT_TRUE(bag.Remove(CheckedRange(0x3000, 30))); EXPECT_TRUE(bag.Remove(CheckedRange(0x3000, 30))); EXPECT_TRUE(bag.Remove(CheckedRange(0x3000, 30))); EXPECT_EQ(2u, bag.GetCount()); EXPECT_FALSE(bag.Remove(CheckedRange(0x3000, 30))); EXPECT_EQ(2u, bag.GetCount()); EXPECT_TRUE(bag.Remove(reinterpret_cast(0x1000), 10)); EXPECT_TRUE(bag.Remove(reinterpret_cast(0x2000), 20)); EXPECT_EQ(0u, bag.GetCount()); } TEST(SimpleAddressRangeBag, CopyAndAssign) { TSimpleAddressRangeBag<10> bag; EXPECT_TRUE(bag.Insert(CheckedRange(1, 2))); EXPECT_TRUE(bag.Insert(CheckedRange(3, 4))); EXPECT_TRUE(bag.Insert(CheckedRange(5, 6))); EXPECT_TRUE(bag.Remove(CheckedRange(3, 4))); EXPECT_EQ(bag.GetCount(), 2u); // Test copy. TSimpleAddressRangeBag<10> bag_copy(bag); EXPECT_EQ(bag_copy.GetCount(), 2u); EXPECT_TRUE(bag_copy.Remove(CheckedRange(1, 2))); EXPECT_TRUE(bag_copy.Remove(CheckedRange(5, 6))); EXPECT_EQ(bag_copy.GetCount(), 0u); EXPECT_EQ(bag.GetCount(), 2u); // Test assign. TSimpleAddressRangeBag<10> bag_assign; bag_assign = bag; EXPECT_EQ(bag_assign.GetCount(), 2u); EXPECT_TRUE(bag_assign.Remove(CheckedRange(1, 2))); EXPECT_TRUE(bag_assign.Remove(CheckedRange(5, 6))); EXPECT_EQ(bag_assign.GetCount(), 0u); EXPECT_EQ(bag.GetCount(), 2u); } // Running out of space shouldn't crash. TEST(SimpleAddressRangeBag, OutOfSpace) { TSimpleAddressRangeBag<2> bag; EXPECT_TRUE(bag.Insert(CheckedRange(1, 2))); EXPECT_TRUE(bag.Insert(CheckedRange(3, 4))); EXPECT_FALSE(bag.Insert(CheckedRange(5, 6))); EXPECT_EQ(bag.GetCount(), 2u); EXPECT_FALSE(bag.Remove(CheckedRange(5, 6))); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/simple_string_dictionary.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_SIMPLE_STRING_DICTIONARY_H_ #define CRASHPAD_CLIENT_SIMPLE_STRING_DICTIONARY_H_ #include #include #include #include #include #include "base/check_op.h" #include "util/misc/implicit_cast.h" namespace crashpad { //! \brief A map/dictionary collection implementation using a fixed amount of //! storage, so that it does not perform any dynamic allocations for its //! operations. //! //! The actual map storage (TSimpleStringDictionary::Entry) is guaranteed to be //! POD, so that it can be transmitted over various IPC mechanisms. //! //! The template parameters control the amount of storage used for the key, //! value, and map. The \a KeySize and \a ValueSize are measured in bytes, not //! glyphs, and include space for a trailing `NUL` byte. This gives space for //! `KeySize - 1` and `ValueSize - 1` characters in an entry. \a NumEntries is //! the total number of entries that will fit in the map. template class TSimpleStringDictionary { public: //! \brief Constant and publicly accessible versions of the template //! parameters. //! \{ static const size_t key_size = KeySize; static const size_t value_size = ValueSize; static const size_t num_entries = NumEntries; //! \} //! \brief A single entry in the map. struct Entry { //! \brief The entry’s key. //! //! This string is always `NUL`-terminated. If this is a 0-length //! `NUL`-terminated string, the entry is inactive. char key[KeySize]; //! \brief The entry’s value. //! //! This string is always `NUL`-terminated. char value[ValueSize]; //! \brief Returns the validity of the entry. //! //! If #key is an empty string, the entry is considered inactive, and this //! method returns `false`. Otherwise, returns `true`. bool is_active() const { return key[0] != '\0'; } }; //! \brief An iterator to traverse all of the active entries in a //! TSimpleStringDictionary. class Iterator { public: explicit Iterator(const TSimpleStringDictionary& map) : map_(map), current_(0) { } Iterator(const Iterator&) = delete; Iterator& operator=(const Iterator&) = delete; //! \brief Returns the next entry in the map, or `nullptr` if at the end of //! the collection. const Entry* Next() { while (current_ < map_.num_entries) { const Entry* entry = &map_.entries_[current_++]; if (entry->is_active()) { return entry; } } return nullptr; } private: const TSimpleStringDictionary& map_; size_t current_; }; TSimpleStringDictionary() : entries_() { } TSimpleStringDictionary(const TSimpleStringDictionary& other) { *this = other; } TSimpleStringDictionary& operator=(const TSimpleStringDictionary& other) { memcpy(entries_, other.entries_, sizeof(entries_)); return *this; } //! \brief Returns the number of active key/value pairs. The upper limit for //! this is \a NumEntries. size_t GetCount() const { size_t count = 0; for (size_t i = 0; i < num_entries; ++i) { if (entries_[i].is_active()) { ++count; } } return count; } //! \brief Given \a key, returns its corresponding value. //! //! \param[in] key The key to look up. This must not be `nullptr`, nor an //! empty string. It must not contain embedded `NUL`s. //! //! \return The corresponding value for \a key, or if \a key is not found, //! `nullptr`. const char* GetValueForKey(std::string_view key) const { DCHECK(key.data()); DCHECK(key.size()); DCHECK_EQ(key.find('\0', 0), std::string_view::npos); if (!key.data() || !key.size()) { return nullptr; } const Entry* entry = GetConstEntryForKey(key); if (!entry) { return nullptr; } return entry->value; } //! \brief Stores \a value into \a key, replacing the existing value if \a key //! is already present. //! //! If \a key is not yet in the map and the map is already full (containing //! \a NumEntries active entries), this operation silently fails. //! //! \param[in] key The key to store. This must not be `nullptr`, nor an empty //! string. It must not contain embedded `NUL`s. //! \param[in] value The value to store. If `nullptr`, \a key is removed from //! the map. Must not contain embedded `NUL`s. void SetKeyValue(std::string_view key, std::string_view value) { if (!value.data()) { RemoveKey(key); return; } DCHECK(key.data()); DCHECK(key.size()); DCHECK_EQ(key.find('\0', 0), std::string_view::npos); if (!key.data() || !key.size()) { return; } // |key| must not be an empty string. DCHECK_NE(key[0], '\0'); if (key[0] == '\0') { return; } // |value| must not contain embedded NULs. DCHECK_EQ(value.find('\0', 0), std::string_view::npos); Entry* entry = GetEntryForKey(key); // If it does not yet exist, attempt to insert it. if (!entry) { for (size_t i = 0; i < num_entries; ++i) { if (!entries_[i].is_active()) { entry = &entries_[i]; SetFromStringView(key, entry->key, key_size); break; } } } // If the map is out of space, |entry| will be nullptr. if (!entry) { return; } #ifndef NDEBUG // Sanity check that the key only appears once. int count = 0; for (size_t i = 0; i < num_entries; ++i) { if (EntryKeyEquals(key, entries_[i])) { ++count; } } DCHECK_EQ(count, 1); #endif SetFromStringView(value, entry->value, value_size); } //! \brief Removes \a key from the map. //! //! If \a key is not found, this is a no-op. //! //! \param[in] key The key of the entry to remove. This must not be `nullptr`, //! nor an empty string. It must not contain embedded `NUL`s. void RemoveKey(std::string_view key) { DCHECK(key.data()); DCHECK(key.size()); DCHECK_EQ(key.find('\0', 0), std::string_view::npos); if (!key.data() || !key.size()) { return; } Entry* entry = GetEntryForKey(key); if (entry) { entry->key[0] = '\0'; entry->value[0] = '\0'; } DCHECK_EQ(GetEntryForKey(key), implicit_cast(nullptr)); } private: static void SetFromStringView(std::string_view src, char* dst, size_t dst_size) { size_t copy_len = std::min(dst_size - 1, src.size()); src.copy(dst, copy_len); dst[copy_len] = '\0'; } static bool EntryKeyEquals(std::string_view key, const Entry& entry) { if (key.size() >= KeySize) return false; // Test for a NUL terminator and early out if it's absent. if (entry.key[key.size()] != '\0') return false; // As there's a NUL terminator at the right position in the entries // string, strncmp can do the rest. return strncmp(key.data(), entry.key, key.size()) == 0; } const Entry* GetConstEntryForKey(std::string_view key) const { for (size_t i = 0; i < num_entries; ++i) { if (EntryKeyEquals(key, entries_[i])) { return &entries_[i]; } } return nullptr; } Entry* GetEntryForKey(std::string_view key) { return const_cast(GetConstEntryForKey(key)); } Entry entries_[NumEntries]; }; //! \brief A TSimpleStringDictionary with default template parameters. //! //! For historical reasons this specialized version is available with the same //! size factors as a previous implementation. using SimpleStringDictionary = TSimpleStringDictionary<256, 256, 64>; static_assert(std::is_standard_layout::value, "SimpleStringDictionary must be standard layout"); } // namespace crashpad #endif // CRASHPAD_CLIENT_SIMPLE_STRING_DICTIONARY_H_ ================================================ FILE: client/simple_string_dictionary_test.cc ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/simple_string_dictionary.h" #include #include "base/check_op.h" #include "gtest/gtest.h" #include "test/gtest_death.h" namespace crashpad { namespace test { namespace { TEST(SimpleStringDictionary, Entry) { using TestMap = TSimpleStringDictionary<5, 9, 15>; TestMap map; const TestMap::Entry* entry = TestMap::Iterator(map).Next(); EXPECT_FALSE(entry); // Try setting a key/value and then verify. map.SetKeyValue("key1", "value1"); entry = TestMap::Iterator(map).Next(); ASSERT_TRUE(entry); EXPECT_STREQ(entry->key, "key1"); EXPECT_STREQ(entry->value, "value1"); // Try setting a new value. map.SetKeyValue("key1", "value3"); EXPECT_STREQ(entry->value, "value3"); // Make sure the key didn't change. EXPECT_STREQ(entry->key, "key1"); // Clear the entry and verify the key and value are empty strings. map.RemoveKey("key1"); EXPECT_FALSE(entry->is_active()); EXPECT_EQ(0u, strlen(entry->key)); EXPECT_EQ(0u, strlen(entry->value)); } TEST(SimpleStringDictionary, SimpleStringDictionary) { // Make a new dictionary SimpleStringDictionary dict; // Set three distinct values on three keys dict.SetKeyValue("key1", "value1"); dict.SetKeyValue("key2", "value2"); dict.SetKeyValue("key3", "value3"); EXPECT_NE(dict.GetValueForKey("key1"), "value1"); EXPECT_NE(dict.GetValueForKey("key2"), "value2"); EXPECT_NE(dict.GetValueForKey("key3"), "value3"); EXPECT_EQ(3u, dict.GetCount()); // try an unknown key EXPECT_FALSE(dict.GetValueForKey("key4")); // Remove a key dict.RemoveKey("key3"); // Now make sure it's not there anymore EXPECT_FALSE(dict.GetValueForKey("key3")); // Remove by setting value to nullptr dict.SetKeyValue("key2", std::string_view(nullptr, 0)); // Now make sure it's not there anymore EXPECT_FALSE(dict.GetValueForKey("key2")); } TEST(SimpleStringDictionary, CopyAndAssign) { TSimpleStringDictionary<10, 10, 10> map; map.SetKeyValue("one", "a"); map.SetKeyValue("two", "b"); map.SetKeyValue("three", "c"); map.RemoveKey("two"); EXPECT_EQ(map.GetCount(), 2u); // Test copy. TSimpleStringDictionary<10, 10, 10> map_copy(map); EXPECT_EQ(map_copy.GetCount(), 2u); EXPECT_STREQ("a", map_copy.GetValueForKey("one")); EXPECT_STREQ("c", map_copy.GetValueForKey("three")); map_copy.SetKeyValue("four", "d"); EXPECT_STREQ("d", map_copy.GetValueForKey("four")); EXPECT_FALSE(map.GetValueForKey("four")); // Test assign. TSimpleStringDictionary<10, 10, 10> map_assign; map_assign = map; EXPECT_EQ(map_assign.GetCount(), 2u); EXPECT_STREQ("a", map_assign.GetValueForKey("one")); EXPECT_STREQ("c", map_assign.GetValueForKey("three")); map_assign.SetKeyValue("four", "d"); EXPECT_STREQ("d", map_assign.GetValueForKey("four")); EXPECT_FALSE(map.GetValueForKey("four")); map.RemoveKey("one"); EXPECT_FALSE(map.GetValueForKey("one")); EXPECT_STREQ("a", map_copy.GetValueForKey("one")); EXPECT_STREQ("a", map_assign.GetValueForKey("one")); } // Add a bunch of values to the dictionary, remove some entries in the middle, // and then add more. TEST(SimpleStringDictionary, Iterator) { SimpleStringDictionary dict; char key[SimpleStringDictionary::key_size]; char value[SimpleStringDictionary::value_size]; constexpr int kDictionaryCapacity = SimpleStringDictionary::num_entries; constexpr int kPartitionIndex = kDictionaryCapacity - 5; // We assume at least this size in the tests below ASSERT_GE(kDictionaryCapacity, 64); // We'll keep track of the number of key/value pairs we think should be in the // dictionary int expected_dictionary_size = 0; // Set a bunch of key/value pairs like key0/value0, key1/value1, ... for (int i = 0; i < kPartitionIndex; ++i) { ASSERT_LT(snprintf(key, sizeof(key), "key%d", i), static_cast(sizeof(key))); ASSERT_LT(snprintf(value, sizeof(value), "value%d", i), static_cast(sizeof(value))); dict.SetKeyValue(key, value); } expected_dictionary_size = kPartitionIndex; // set a couple of the keys twice (with the same value) - should be nop dict.SetKeyValue("key2", "value2"); dict.SetKeyValue("key4", "value4"); dict.SetKeyValue("key15", "value15"); // Remove some random elements in the middle dict.RemoveKey("key7"); dict.RemoveKey("key18"); dict.RemoveKey("key23"); dict.RemoveKey("key31"); expected_dictionary_size -= 4; // we just removed four key/value pairs // Set some more key/value pairs like key59/value59, key60/value60, ... for (int i = kPartitionIndex; i < kDictionaryCapacity; ++i) { ASSERT_LT(snprintf(key, sizeof(key), "key%d", i), static_cast(sizeof(key))); ASSERT_LT(snprintf(value, sizeof(value), "value%d", i), static_cast(sizeof(value))); dict.SetKeyValue(key, value); } expected_dictionary_size += kDictionaryCapacity - kPartitionIndex; // Now create an iterator on the dictionary SimpleStringDictionary::Iterator iter(dict); // We then verify that it iterates through exactly the number of key/value // pairs we expect, and that they match one-for-one with what we would expect. // The ordering of the iteration does not matter... // used to keep track of number of occurrences found for key/value pairs int count[kDictionaryCapacity]; memset(count, 0, sizeof(count)); int total_count = 0; for (;;) { const SimpleStringDictionary::Entry* entry = iter.Next(); if (!entry) break; total_count++; // Extract key_number from a string of the form key int key_number; sscanf(entry->key, "key%d", &key_number); // Extract value_number from a string of the form value int value_number; sscanf(entry->value, "value%d", &value_number); // The value number should equal the key number since that's how we set them EXPECT_EQ(value_number, key_number); // Key and value numbers should be in proper range: 0 <= key_number < // kDictionaryCapacity bool key_in_good_range = key_number >= 0 && key_number < kDictionaryCapacity; bool value_in_good_range = value_number >= 0 && value_number < kDictionaryCapacity; EXPECT_TRUE(key_in_good_range); EXPECT_TRUE(value_in_good_range); if (key_in_good_range && value_in_good_range) { ++count[key_number]; } } // Make sure each of the key/value pairs showed up exactly one time, except // for the ones which we removed. for (size_t i = 0; i < kDictionaryCapacity; ++i) { // Skip over key7, key18, key23, and key31, since we removed them if (!(i == 7 || i == 18 || i == 23 || i == 31)) { EXPECT_EQ(1, count[i]); } } // Make sure the number of iterations matches the expected dictionary size. EXPECT_EQ(total_count, expected_dictionary_size); } TEST(SimpleStringDictionary, AddRemove) { TSimpleStringDictionary<5, 7, 6> map; map.SetKeyValue("rob", "ert"); map.SetKeyValue("mike", "pink"); map.SetKeyValue("mark", "allays"); EXPECT_EQ(map.GetCount(), 3u); EXPECT_STREQ("ert", map.GetValueForKey("rob")); EXPECT_STREQ("pink", map.GetValueForKey("mike")); EXPECT_STREQ("allays", map.GetValueForKey("mark")); map.RemoveKey("mike"); EXPECT_EQ(map.GetCount(), 2u); EXPECT_FALSE(map.GetValueForKey("mike")); map.SetKeyValue("mark", "mal"); EXPECT_EQ(map.GetCount(), 2u); EXPECT_STREQ("mal", map.GetValueForKey("mark")); map.RemoveKey("mark"); EXPECT_EQ(map.GetCount(), 1u); EXPECT_FALSE(map.GetValueForKey("mark")); } // Running out of space shouldn't crash. TEST(SimpleStringDictionary, OutOfSpace) { TSimpleStringDictionary<3, 2, 2> map; map.SetKeyValue("a", "1"); map.SetKeyValue("b", "2"); map.SetKeyValue("c", "3"); EXPECT_EQ(map.GetCount(), 2u); EXPECT_FALSE(map.GetValueForKey("c")); } #if DCHECK_IS_ON() TEST(SimpleStringDictionaryDeathTest, SetKeyValueWithNullKey) { TSimpleStringDictionary<4, 6, 6> map; ASSERT_DEATH_CHECK(map.SetKeyValue(std::string_view(nullptr, 0), "hello"), "key"); } TEST(SimpleStringDictionaryDeathTest, GetValueForKeyWithNullKey) { TSimpleStringDictionary<4, 6, 6> map; map.SetKeyValue("hi", "there"); ASSERT_DEATH_CHECK(map.GetValueForKey(std::string_view(nullptr, 0)), "key"); EXPECT_STREQ("there", map.GetValueForKey("hi")); } #endif // The tests above, without DEATH_CHECK assertions. TEST(SimpleStringDictionaryDeathTest, GetValueForKeyWithoutNullKey) { TSimpleStringDictionary<4, 6, 6> map; map.SetKeyValue("hi", "there"); EXPECT_STREQ("there", map.GetValueForKey("hi")); map.RemoveKey("hi"); EXPECT_EQ(map.GetCount(), 0u); } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/simulate_crash.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_SIMULATE_CRASH_H_ #define CRASHPAD_CLIENT_SIMULATE_CRASH_H_ #include "build/build_config.h" // IWYU pragma: begin_exports #if BUILDFLAG(IS_MAC) #include "client/simulate_crash_mac.h" #elif BUILDFLAG(IS_IOS) #include "client/simulate_crash_ios.h" #elif BUILDFLAG(IS_WIN) #include "client/simulate_crash_win.h" #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) #include "client/simulate_crash_linux.h" #endif // IWYU pragma: end_exports #endif // CRASHPAD_CLIENT_SIMULATE_CRASH_H_ ================================================ FILE: client/simulate_crash_ios.h ================================================ // Copyright 2021 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_SIMULATE_CRASH_IOS_H_ #define CRASHPAD_CLIENT_SIMULATE_CRASH_IOS_H_ #include "client/crashpad_client.h" #include "util/misc/capture_context.h" //! \file //! \brief Captures the CPU context and creates a minidump dump without an //! exception. The minidump will immediately become eligible for further //! processing, including upload. //! //! \sa CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING #define CRASHPAD_SIMULATE_CRASH() \ do { \ crashpad::NativeCPUContext cpu_context; \ crashpad::CaptureContext(&cpu_context); \ crashpad::CrashpadClient::DumpWithoutCrash(&cpu_context); \ } while (false) //! \brief Captures the CPU context and captures an intermediate dump without an //! exception. Does not convert the intermediate dump into a minidump. //! //! Deferring processing is useful when the application may be in an unstable //! state, such as during a hang. //! //! \sa CRASHPAD_SIMULATE_CRASH #define CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING() \ do { \ crashpad::NativeCPUContext cpu_context; \ crashpad::CaptureContext(&cpu_context); \ crashpad::CrashpadClient::DumpWithoutCrashAndDeferProcessing( \ &cpu_context); \ } while (false) #define CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING_AT_PATH(path) \ do { \ crashpad::NativeCPUContext cpu_context; \ crashpad::CaptureContext(&cpu_context); \ crashpad::CrashpadClient::DumpWithoutCrashAndDeferProcessingAtPath( \ &cpu_context, path); \ } while (false) #endif // CRASHPAD_CLIENT_SIMULATE_CRASH_IOS_H_ ================================================ FILE: client/simulate_crash_linux.h ================================================ // Copyright 2018 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_SIMULATE_CRASH_LINUX_H_ #define CRASHPAD_CLIENT_SIMULATE_CRASH_LINUX_H_ #include "client/crashpad_client.h" #include "util/misc/capture_context.h" //! \file //! \brief Captures the CPU context and simulates an exception without crashing. #define CRASHPAD_SIMULATE_CRASH() \ do { \ crashpad::NativeCPUContext simulate_crash_cpu_context; \ crashpad::CaptureContext(&simulate_crash_cpu_context); \ crashpad::CrashpadClient::DumpWithoutCrash(&simulate_crash_cpu_context); \ } while (false) #endif // CRASHPAD_CLIENT_SIMULATE_CRASH_LINUX_H_ ================================================ FILE: client/simulate_crash_mac.cc ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/simulate_crash_mac.h" #include #include #include #include "base/apple/mach_logging.h" #include "base/apple/scoped_mach_port.h" #include "base/check_op.h" #include "base/logging.h" #include "build/build_config.h" #include "util/mach/exc_client_variants.h" #include "util/mach/exception_behaviors.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" #include "util/misc/implicit_cast.h" namespace crashpad { namespace { //! \brief Sends an exception message to an exception port in accordance with //! the behavior and thread state flavor it’s registered to receive. //! //! \param[in] thread, task, exception, code, code_count These parameters will //! be passed to the exception handler as appropriate. //! \param[in] cpu_context The value to use for the thread state, if \a behavior //! indicates that the handler should receive a thread state and if the //! supplied thread state matches or can be converted to \a flavor. If \a //! behavior requires a thread state but this argument cannot be converted //! to match \a flavor, `thread_get_state()` will be called to obtain a //! suitable thread state value. //! \param[in] handler The Mach exception handler to deliver the exception to. //! \param[in] set_state If `true` and \a behavior indicates that the handler //! should receive and return a thread state, a new thread state will be set //! by `thread_set_state()` upon successful completion of the exception //! handler. If `false`, this will be suppressed, even when \a behavior //! indicates that the handler receives and returns a thread state. //! //! \return `true` if the exception was delivered to the handler and the handler //! indicated success. `false` otherwise, with a warning message logged. bool DeliverException(thread_t thread, task_t task, exception_type_t exception, const mach_exception_data_t code, mach_msg_type_number_t code_count, const NativeCPUContext& cpu_context, const ExceptionPorts::ExceptionHandler& handler, bool set_state) { kern_return_t kr; bool handler_wants_state = ExceptionBehaviorHasState(handler.behavior); if (!handler_wants_state) { // Regardless of the passed-in value of |set_state|, if the handler won’t be // dealing with any state at all, no state should be set. set_state = false; } // old_state is only used if the context already captured doesn’t match (or // can’t be converted to) what’s registered for the handler. thread_state_data_t old_state; thread_state_flavor_t flavor = handler.flavor; ConstThreadState state; mach_msg_type_number_t state_count; switch (flavor) { #if defined(ARCH_CPU_X86_FAMILY) case x86_THREAD_STATE: state = reinterpret_cast(&cpu_context); state_count = x86_THREAD_STATE_COUNT; break; #if defined(ARCH_CPU_X86) case x86_THREAD_STATE32: state = reinterpret_cast(&cpu_context.uts.ts32); state_count = cpu_context.tsh.count; break; #elif defined(ARCH_CPU_X86_64) case x86_THREAD_STATE64: state = reinterpret_cast(&cpu_context.uts.ts64); state_count = cpu_context.tsh.count; break; #endif #elif defined(ARCH_CPU_ARM64) case ARM_UNIFIED_THREAD_STATE: state = reinterpret_cast(&cpu_context); state_count = ARM_UNIFIED_THREAD_STATE_COUNT; break; case ARM_THREAD_STATE64: state = reinterpret_cast(&cpu_context.ts_64); state_count = cpu_context.ash.count; break; #else #error Port to your CPU architecture #endif case THREAD_STATE_NONE: // This is only acceptable if the handler doesn’t have one of the “state” // behaviors. Otherwise, if the kernel were attempting to send an // exception message to this port, it would call thread_getstatus() (known // outside the kernel as thread_get_state()) which would fail because // THREAD_STATE_NONE is not a valid state to get. See 10.9.5 // xnu-2422.115.4/osfmk/kern/exception.c exception_deliver() and // xnu-2422.115.4/osfmk/i386/pcb.c machine_thread_get_state(). if (!handler_wants_state) { state = nullptr; state_count = 0; break; } LOG(WARNING) << "exception handler has unexpected state flavor" << flavor; return false; default: if (!handler_wants_state) { // Don’t bother getting any thread state if the handler’s not actually // going to use it. state = nullptr; state_count = 0; } else { state = old_state; state_count = THREAD_STATE_MAX; kr = thread_get_state(thread, flavor, old_state, &state_count); if (kr != KERN_SUCCESS) { MACH_LOG(WARNING, kr) << "thread_get_state"; return false; } } break; } // new_state is supposed to be an out parameter only, but in case the handler // doesn't touch it, make sure it's initialized to a valid thread state. // Otherwise, the thread_set_state() call below would set a garbage thread // state. thread_state_data_t new_state; size_t state_size = sizeof(natural_t) * std::min(state_count, implicit_cast(THREAD_STATE_MAX)); memcpy(new_state, state, state_size); mach_msg_type_number_t new_state_count = THREAD_STATE_MAX; kr = UniversalExceptionRaise(handler.behavior, handler.port, thread, task, exception, code, code_count, &flavor, state, state_count, new_state, &new_state_count); // The kernel treats a return value of MACH_RCV_PORT_DIED as successful, // although it will not set a new thread state in that case. See 10.9.5 // xnu-2422.115.4/osfmk/kern/exception.c exception_deliver(), and the more // elaborate comment at util/mach/exc_server_variants.h // ExcServerSuccessfulReturnValue(). Duplicate that behavior. bool success = kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED; MACH_LOG_IF(WARNING, !success, kr) << "UniversalExceptionRaise"; if (kr == KERN_SUCCESS && set_state) { kr = thread_set_state(thread, flavor, new_state, new_state_count); MACH_LOG_IF(WARNING, kr != KERN_SUCCESS, kr) << "thread_set_state"; } return success; } } // namespace void SimulateCrash(const NativeCPUContext& cpu_context) { #if defined(ARCH_CPU_X86) DCHECK_EQ(implicit_cast(cpu_context.tsh.flavor), implicit_cast(x86_THREAD_STATE32)); DCHECK_EQ(implicit_cast(cpu_context.tsh.count), x86_THREAD_STATE32_COUNT); #elif defined(ARCH_CPU_X86_64) DCHECK_EQ(implicit_cast(cpu_context.tsh.flavor), implicit_cast(x86_THREAD_STATE64)); DCHECK_EQ(implicit_cast(cpu_context.tsh.count), x86_THREAD_STATE64_COUNT); #elif defined(ARCH_CPU_ARM64) DCHECK_EQ(implicit_cast(cpu_context.ash.flavor), implicit_cast(ARM_THREAD_STATE64)); DCHECK_EQ(implicit_cast(cpu_context.ash.count), ARM_THREAD_STATE64_COUNT); #else #error Port to your CPU architecture #endif base::apple::ScopedMachSendRight thread(mach_thread_self()); exception_type_t exception = kMachExceptionSimulated; mach_exception_data_type_t codes[] = {0, 0}; mach_msg_type_number_t code_count = std::size(codes); // Look up the handler for EXC_CRASH exceptions in the same way that the // kernel would: try a thread handler, then a task handler, and finally a host // handler. 10.9.5 xnu-2422.115.4/osfmk/kern/exception.c exception_triage(). static constexpr ExceptionPorts::TargetType kTargetTypes[] = { ExceptionPorts::kTargetTypeThread, ExceptionPorts::kTargetTypeTask, // This is not expected to succeed, because mach_host_self() doesn’t // return the host_priv port to non-root users, and this is the port // that’s required for host_get_exception_ports(). // // See 10.9.5 xnu-2422.115.4/bsd/kern/kern_prot.c set_security_token(), // xnu-2422.115.4/osfmk/kern/task.c host_security_set_task_token(), and // xnu-2422.115.4/osfmk/kern/ipc_host.c host_get_exception_ports(). ExceptionPorts::kTargetTypeHost, }; bool success = false; for (size_t target_type_index = 0; !success && target_type_index < std::size(kTargetTypes); ++target_type_index) { ExceptionPorts::ExceptionHandlerVector handlers; ExceptionPorts exception_ports(kTargetTypes[target_type_index], MACH_PORT_NULL); if (exception_ports.GetExceptionPorts(EXC_MASK_CRASH, &handlers)) { DCHECK_LE(handlers.size(), 1u); if (handlers.size() == 1) { DCHECK(handlers[0].mask & EXC_MASK_CRASH); success = DeliverException(thread.get(), mach_task_self(), exception, codes, code_count, cpu_context, handlers[0], false); } } } LOG_IF(WARNING, !success) << "SimulateCrash did not find an appropriate exception handler"; } } // namespace crashpad ================================================ FILE: client/simulate_crash_mac.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_SIMULATE_CRASH_MAC_H_ #define CRASHPAD_CLIENT_SIMULATE_CRASH_MAC_H_ #include #include "util/misc/capture_context.h" //! \file namespace crashpad { //! \brief Simulates a exception without crashing. //! //! This function searches for an `EXC_CRASH` handler in the same manner that //! the kernel does, and sends it an exception message to that handler in the //! format that the handler expects, considering the behavior and thread state //! flavor that are registered for it. The exception sent to the handler will be //! ::kMachExceptionSimulated, not `EXC_CRASH`. //! //! Typically, the CRASHPAD_SIMULATE_CRASH() macro will be used in preference to //! this function, because it combines the context-capture operation with the //! raising of a simulated exception. //! //! This function returns normally after the exception message is processed. If //! no valid handler was found, or no handler processed the exception //! successfully, a warning will be logged, but these conditions are not //! considered fatal. //! //! \param[in] cpu_context The thread state to pass to the exception handler as //! the exception context, provided that it is compatible with the thread //! state flavor that the exception handler accepts. If it is not //! compatible, the correct thread state for the handler will be obtained by //! calling `thread_get_state()`. void SimulateCrash(const NativeCPUContext& cpu_context); } // namespace crashpad //! \brief Captures the CPU context and simulates an exception without crashing. #define CRASHPAD_SIMULATE_CRASH() \ do { \ crashpad::NativeCPUContext cpu_context; \ crashpad::CaptureContext(&cpu_context); \ crashpad::SimulateCrash(cpu_context); \ } while (false) #endif // CRASHPAD_CLIENT_SIMULATE_CRASH_MAC_H_ ================================================ FILE: client/simulate_crash_mac_test.cc ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/simulate_crash.h" #include #include #include #include #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" #include "test/mac/mach_errors.h" #include "test/mac/mach_multiprocess.h" #include "util/mach/exc_server_variants.h" #include "util/mach/exception_behaviors.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" #include "util/mach/mach_message_server.h" #include "util/mach/symbolic_constants_mach.h" #include "util/misc/implicit_cast.h" namespace crashpad { namespace test { namespace { class TestSimulateCrashMac final : public MachMultiprocess, public UniversalMachExcServer::Interface { public: // Defines which targets the child should set an EXC_CRASH exception handler // for. enum ExceptionPortsTarget { // The child should clear its EXC_CRASH handler for both its task and thread // targets. SimulateCrash() will attempt to deliver the exception to the // host target, which will fail if not running as root. In any case, the // parent should not expect to receive any exception message from the child. kExceptionPortsTargetNone = 0, // The child will set an EXC_CRASH handler for its task target, and clear it // for its thread target. The parent runs an exception server to receive // the child’s simulated crash message. kExceptionPortsTargetTask, // The child will set an EXC_CRASH handler for its thread target, and clear // it for its task target. The parent runs an exception server to receive // the child’s simulated crash message. kExceptionPortsTargetThread, // The child sets an EXC_CRASH handler for both its task and thread targets. // The parent runs an exception server to receive the message expected to be // delivered to the thread target, but returns an error code. The child will // then fall back to trying the server registered for the task target, // sending a second message to the parent. The server in the parent will // handle this one successfully. kExceptionPortsTargetBoth, }; TestSimulateCrashMac(ExceptionPortsTarget target, exception_behavior_t behavior, thread_state_flavor_t flavor) : MachMultiprocess(), UniversalMachExcServer::Interface(), target_(target), behavior_(behavior), flavor_(flavor), succeed_(true) { } TestSimulateCrashMac(const TestSimulateCrashMac&) = delete; TestSimulateCrashMac& operator=(const TestSimulateCrashMac&) = delete; ~TestSimulateCrashMac() {} // UniversalMachExcServer::Interface: kern_return_t CatchMachException(exception_behavior_t behavior, exception_handler_t exception_port, thread_t thread, task_t task, exception_type_t exception, const mach_exception_data_type_t* code, mach_msg_type_number_t code_count, thread_state_flavor_t* flavor, ConstThreadState old_state, mach_msg_type_number_t old_state_count, thread_state_t new_state, mach_msg_type_number_t* new_state_count, const mach_msg_trailer_t* trailer, bool* destroy_complex_request) override { *destroy_complex_request = true; // Check the entire exception message, because most or all of it was // generated by SimulateCrash() instead of the kernel. EXPECT_EQ(behavior, behavior_); EXPECT_EQ(exception_port, LocalPort()); if (ExceptionBehaviorHasIdentity(behavior)) { EXPECT_NE(thread, THREAD_NULL); EXPECT_EQ(task, ChildTask()); } else { EXPECT_EQ(thread, THREAD_NULL); EXPECT_EQ(task, TASK_NULL); } EXPECT_EQ(exception, kMachExceptionSimulated); EXPECT_EQ(code_count, 2u); if (code_count >= 1) { EXPECT_EQ(code[0], 0); } if (code_count >= 2) { EXPECT_EQ(code[1], 0); } if (!ExceptionBehaviorHasState(behavior)) { EXPECT_EQ(*flavor, THREAD_STATE_NONE); } else { EXPECT_EQ(*flavor, flavor_); switch (*flavor) { #if defined(ARCH_CPU_X86_FAMILY) case x86_THREAD_STATE: { EXPECT_EQ(old_state_count, x86_THREAD_STATE_COUNT); const x86_thread_state* state = reinterpret_cast(old_state); switch (state->tsh.flavor) { case x86_THREAD_STATE32: EXPECT_EQ(implicit_cast(state->tsh.count), implicit_cast(x86_THREAD_STATE32_COUNT)); break; case x86_THREAD_STATE64: EXPECT_EQ(implicit_cast(state->tsh.count), implicit_cast(x86_THREAD_STATE64_COUNT)); break; default: ADD_FAILURE() << "unexpected tsh.flavor " << state->tsh.flavor; break; } break; } case x86_FLOAT_STATE: { EXPECT_EQ(old_state_count, x86_FLOAT_STATE_COUNT); const x86_float_state* state = reinterpret_cast(old_state); switch (state->fsh.flavor) { case x86_FLOAT_STATE32: EXPECT_EQ(implicit_cast(state->fsh.count), implicit_cast(x86_FLOAT_STATE32_COUNT)); break; case x86_FLOAT_STATE64: EXPECT_EQ(implicit_cast(state->fsh.count), implicit_cast(x86_FLOAT_STATE64_COUNT)); break; default: ADD_FAILURE() << "unexpected fsh.flavor " << state->fsh.flavor; break; } break; } case x86_DEBUG_STATE: { EXPECT_EQ(old_state_count, x86_DEBUG_STATE_COUNT); const x86_debug_state* state = reinterpret_cast(old_state); switch (state->dsh.flavor) { case x86_DEBUG_STATE32: EXPECT_EQ(implicit_cast(state->dsh.count), implicit_cast(x86_DEBUG_STATE32_COUNT)); break; case x86_DEBUG_STATE64: EXPECT_EQ(implicit_cast(state->dsh.count), implicit_cast(x86_DEBUG_STATE64_COUNT)); break; default: ADD_FAILURE() << "unexpected dsh.flavor " << state->dsh.flavor; break; } break; } case x86_THREAD_STATE32: EXPECT_EQ(old_state_count, x86_THREAD_STATE32_COUNT); break; case x86_FLOAT_STATE32: EXPECT_EQ(old_state_count, x86_FLOAT_STATE32_COUNT); break; case x86_DEBUG_STATE32: EXPECT_EQ(old_state_count, x86_DEBUG_STATE32_COUNT); break; case x86_THREAD_STATE64: EXPECT_EQ(old_state_count, x86_THREAD_STATE64_COUNT); break; case x86_FLOAT_STATE64: EXPECT_EQ(old_state_count, x86_FLOAT_STATE64_COUNT); break; case x86_DEBUG_STATE64: EXPECT_EQ(old_state_count, x86_DEBUG_STATE64_COUNT); break; #elif defined(ARCH_CPU_ARM64) case ARM_UNIFIED_THREAD_STATE: { EXPECT_EQ(old_state_count, ARM_UNIFIED_THREAD_STATE_COUNT); const arm_unified_thread_state* state = reinterpret_cast(old_state); EXPECT_EQ(state->ash.flavor, implicit_cast(ARM_THREAD_STATE64)); if (state->ash.flavor == ARM_THREAD_STATE64) { EXPECT_EQ(state->ash.count, implicit_cast(ARM_THREAD_STATE64_COUNT)); } break; } case ARM_THREAD_STATE64: EXPECT_EQ(old_state_count, ARM_THREAD_STATE64_COUNT); break; case ARM_NEON_STATE64: EXPECT_EQ(old_state_count, ARM_NEON_STATE64_COUNT); break; case ARM_DEBUG_STATE64: EXPECT_EQ(old_state_count, ARM_DEBUG_STATE64_COUNT); break; #else #error Port to your CPU architecture #endif default: ADD_FAILURE() << "unexpected flavor " << *flavor; break; } // Attempt to set a garbage thread state, which would cause the child to // crash inside SimulateCrash() if it actually succeeded. This tests that // SimulateCrash() ignores new_state instead of attempting to set the // state as the kernel would do. This operates in conjunction with the // |true| argument to ExcServerSuccessfulReturnValue() below. *new_state_count = old_state_count; size_t new_state_size = sizeof(natural_t) * old_state_count; memset(new_state, 0xa5, new_state_size); } if (!succeed_) { // The client has registered EXC_CRASH handlers for both its thread and // task targets, and sent a simulated exception message to its // thread-level EXC_CRASH handler. To test that it will fall back to // trying the task-level EXC_CRASH handler, return a failure code, which // should cause SimulateCrash() to try the next target. EXPECT_EQ(target_, kExceptionPortsTargetBoth); return KERN_ABORTED; } ExcServerCopyState( behavior, old_state, old_state_count, new_state, new_state_count); return ExcServerSuccessfulReturnValue(exception, behavior, true); } private: // MachMultiprocess: void MachMultiprocessParent() override { if (target_ == kExceptionPortsTargetNone) { // The child does not have any EXC_CRASH handlers registered for its // thread or task targets, so no exception message is expected to be // generated. Don’t run the server at all. return; } UniversalMachExcServer universal_mach_exc_server(this); mach_msg_return_t mr; if (target_ == kExceptionPortsTargetBoth) { // The client has registered EXC_CRASH handlers for both its thread and // task targets. Run a server that will return a failure code when the // exception message is sent to the thread target, which will cause the // client to fall back to the task target and send another message. succeed_ = false; mr = MachMessageServer::Run(&universal_mach_exc_server, LocalPort(), MACH_MSG_OPTION_NONE, MachMessageServer::kOneShot, MachMessageServer::kReceiveLargeError, kMachMessageTimeoutWaitIndefinitely); EXPECT_EQ(mr, MACH_MSG_SUCCESS) << MachErrorMessage(mr, "MachMessageServer::Run"); } succeed_ = true; mr = MachMessageServer::Run(&universal_mach_exc_server, LocalPort(), MACH_MSG_OPTION_NONE, MachMessageServer::kOneShot, MachMessageServer::kReceiveLargeError, kMachMessageTimeoutWaitIndefinitely); EXPECT_EQ(mr, MACH_MSG_SUCCESS) << MachErrorMessage(mr, "MachMessageServer::Run"); } void MachMultiprocessChild() override { bool task_valid = target_ == kExceptionPortsTargetTask || target_ == kExceptionPortsTargetBoth; ExceptionPorts task_exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); ASSERT_TRUE(task_exception_ports.SetExceptionPort( EXC_MASK_CRASH, task_valid ? RemotePort() : MACH_PORT_NULL, behavior_, flavor_)); bool thread_valid = target_ == kExceptionPortsTargetThread || target_ == kExceptionPortsTargetBoth; ExceptionPorts thread_exception_ports(ExceptionPorts::kTargetTypeThread, THREAD_NULL); ASSERT_TRUE(thread_exception_ports.SetExceptionPort( EXC_MASK_CRASH, thread_valid ? RemotePort() : MACH_PORT_NULL, behavior_, flavor_)); CRASHPAD_SIMULATE_CRASH(); } ExceptionPortsTarget target_; exception_behavior_t behavior_; thread_state_flavor_t flavor_; bool succeed_; }; TEST(SimulateCrash, SimulateCrash) { static constexpr TestSimulateCrashMac::ExceptionPortsTarget kTargets[] = { TestSimulateCrashMac::kExceptionPortsTargetNone, TestSimulateCrashMac::kExceptionPortsTargetTask, TestSimulateCrashMac::kExceptionPortsTargetThread, TestSimulateCrashMac::kExceptionPortsTargetBoth, }; static constexpr exception_behavior_t kBehaviors[] = { EXCEPTION_DEFAULT, EXCEPTION_STATE, EXCEPTION_STATE_IDENTITY, EXCEPTION_DEFAULT | kMachExceptionCodes, EXCEPTION_STATE | kMachExceptionCodes, EXCEPTION_STATE_IDENTITY | kMachExceptionCodes, }; static constexpr thread_state_flavor_t kFlavors[] = { #if defined(ARCH_CPU_X86_FAMILY) x86_THREAD_STATE, x86_FLOAT_STATE, x86_DEBUG_STATE, #if defined(ARCH_CPU_X86) x86_THREAD_STATE32, x86_FLOAT_STATE32, x86_DEBUG_STATE32, #elif defined(ARCH_CPU_X86_64) x86_THREAD_STATE64, x86_FLOAT_STATE64, x86_DEBUG_STATE64, #endif #elif defined(ARCH_CPU_ARM64) ARM_UNIFIED_THREAD_STATE, ARM_THREAD_STATE64, ARM_NEON_STATE64, ARM_DEBUG_STATE64, #else #error Port to your CPU architecture #endif }; for (size_t target_index = 0; target_index < std::size(kTargets); ++target_index) { TestSimulateCrashMac::ExceptionPortsTarget target = kTargets[target_index]; SCOPED_TRACE(base::StringPrintf( "target_index %zu, target %d", target_index, target)); for (size_t behavior_index = 0; behavior_index < std::size(kBehaviors); ++behavior_index) { exception_behavior_t behavior = kBehaviors[behavior_index]; SCOPED_TRACE(base::StringPrintf( "behavior_index %zu, behavior %s", behavior_index, ExceptionBehaviorToString(behavior, kUseFullName | kUnknownIsNumeric) .c_str())); if (!ExceptionBehaviorHasState(behavior)) { TestSimulateCrashMac test_simulate_crash_mac( target, behavior, THREAD_STATE_NONE); test_simulate_crash_mac.Run(); } else { for (size_t flavor_index = 0; flavor_index < std::size(kFlavors); ++flavor_index) { thread_state_flavor_t flavor = kFlavors[flavor_index]; SCOPED_TRACE(base::StringPrintf( "flavor_index %zu, flavor %s", flavor_index, ThreadStateFlavorToString( flavor, kUseFullName | kUnknownIsNumeric).c_str())); TestSimulateCrashMac test_simulate_crash_mac( target, behavior, flavor); test_simulate_crash_mac.Run(); } } } } } } // namespace } // namespace test } // namespace crashpad ================================================ FILE: client/simulate_crash_win.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_SIMULATE_CRASH_WIN_H_ #define CRASHPAD_CLIENT_SIMULATE_CRASH_WIN_H_ #include #include "client/crashpad_client.h" #include "util/misc/capture_context.h" //! \file //! \brief Captures the CPU context and captures a dump without an exception. #define CRASHPAD_SIMULATE_CRASH() \ do { \ /* Not "context" to avoid variable shadowing warnings. */ \ CONTEXT simulate_crash_cpu_context; \ crashpad::CaptureContext(&simulate_crash_cpu_context); \ crashpad::CrashpadClient::DumpWithoutCrash(simulate_crash_cpu_context); \ } while (false) #endif // CRASHPAD_CLIENT_SIMULATE_CRASH_WIN_H_ ================================================ FILE: client/upload_behavior_ios.h ================================================ // Copyright 2022 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_UPLOAD_BEHAVIOR_IOS_H_ #define CRASHPAD_CLIENT_UPLOAD_BEHAVIOR_IOS_H_ namespace crashpad { //! \brief Enum to control upload behavior when processing pending reports. enum class UploadBehavior { //! \brief Only upload reports while the application is active (e.g., in the //! foreground). kUploadWhenAppIsActive = 1, //! \brief Upload reports immediately, regardless of whether or not the //! application is active. kUploadImmediately = 2, }; } // namespace crashpad #endif // CRASHPAD_CLIENT_UPLOAD_BEHAVIOR_IOS_H_ ================================================ FILE: codereview.settings ================================================ # Copyright 2014 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. GERRIT_HOST: True CODE_REVIEW_SERVER: https://chromium-review.googlesource.com/ VIEW_VC: https://chromium.googlesource.com/crashpad/crashpad/+/ PROJECT: crashpad ================================================ FILE: compat/BUILD.gn ================================================ # Copyright 2015 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import("../build/crashpad_buildconfig.gni") config("compat_config") { include_dirs = [] if (crashpad_is_apple) { include_dirs += [ "mac" ] } if (crashpad_is_ios) { include_dirs += [ "ios" ] } if (crashpad_is_linux || crashpad_is_android) { include_dirs += [ "linux" ] } if (crashpad_is_android) { include_dirs += [ "android" ] } if (crashpad_is_win) { include_dirs += [ "win" ] } else { include_dirs += [ "non_win" ] } } template("compat_target") { if (crashpad_is_apple) { # There are no sources to compile, which doesn’t mix will with a # static_library. group(target_name) { forward_variables_from(invoker, "*") not_needed([ "configs" ]) } } else { crashpad_static_library(target_name) { forward_variables_from(invoker, "*", [ "configs" ]) if (!defined(configs)) { configs = [] } if (defined(invoker.configs)) { configs += invoker.configs } } } } compat_target("compat") { sources = [] if (crashpad_is_apple) { sources += [ "mac/Availability.h", "mac/AvailabilityVersions.h", "mac/kern/exc_resource.h", "mac/mach-o/loader.h", "mac/mach/i386/thread_state.h", "mac/mach/mach.h", "mac/sys/resource.h", ] } else { sources += [ "non_mac/mach/mach.h" ] } if (crashpad_is_ios) { sources += [ "ios/mach/exc.defs", "ios/mach/mach_exc.defs", "ios/mach/mach_types.defs", "ios/mach/machine/machine_types.defs", "ios/mach/std_types.defs", ] } if (crashpad_is_linux || crashpad_is_android) { sources += [ "linux/signal.h", "linux/sys/mman.h", "linux/sys/mman_memfd_create.cc", "linux/sys/ptrace.h", "linux/sys/user.h", ] } if (crashpad_is_android) { sources += [ "android/dlfcn_internal.cc", "android/dlfcn_internal.h", "android/elf.h", "android/linux/elf.h", "android/linux/prctl.h", "android/linux/ptrace.h", "android/sched.h", "android/sys/epoll.cc", "android/sys/epoll.h", "android/sys/mman.h", "android/sys/mman_mmap.cc", "android/sys/syscall.h", "android/sys/user.h", ] } if (crashpad_is_win) { sources += [ "win/getopt.h", "win/strings.cc", "win/strings.h", "win/sys/types.h", "win/time.cc", "win/time.h", "win/winbase.h", "win/winnt.h", "win/winternl.h", ] } else { sources += [ "non_win/dbghelp.h", "non_win/minwinbase.h", "non_win/timezoneapi.h", "non_win/verrsrc.h", "non_win/windows.h", "non_win/winnt.h", ] } public_configs = [ ":compat_config", "..:crashpad_config", ] if (!crashpad_is_win) { public_deps = [ "$mini_chromium_source_parent:base" ] } deps = [ "../util:no_cfi_icall" ] if (crashpad_is_win) { deps += [ "../third_party/getopt" ] } } ================================================ FILE: compat/android/dlfcn_internal.cc ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "dlfcn_internal.h" #include #include #include #include #include #include #include #include #include #include #include namespace crashpad { namespace internal { // KitKat supports API levels up to 20. #if __ANDROID_API__ < 21 namespace { class ScopedSigactionRestore { public: ScopedSigactionRestore() : old_action_(), signo_(-1), valid_(false) {} ~ScopedSigactionRestore() { Reset(); } bool Reset() { bool result = true; if (valid_) { result = sigaction(signo_, &old_action_, nullptr) == 0; if (!result) { PrintErrmsg(errno); } } valid_ = false; signo_ = -1; return result; } bool ResetAndInstallHandler(int signo, void (*handler)(int, siginfo_t*, void*)) { Reset(); struct sigaction act; act.sa_sigaction = handler; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; if (sigaction(signo, &act, &old_action_) != 0) { PrintErrmsg(errno); return false; } signo_ = signo; valid_ = true; return true; } private: void PrintErrmsg(int err) { char errmsg[256]; if (strerror_r(err, errmsg, sizeof(errmsg)) != 0) { snprintf(errmsg, sizeof(errmsg), "%s:%d: Couldn't set errmsg for %d: %d", __FILE__, __LINE__, err, errno); return; } fprintf(stderr, "%s:%d: sigaction: %s", __FILE__, __LINE__, errmsg); } struct sigaction old_action_; int signo_; bool valid_; }; bool IsKitKat() { char prop_buf[PROP_VALUE_MAX]; int length = __system_property_get("ro.build.version.sdk", prop_buf); if (length <= 0) { fprintf(stderr, "%s:%d: Couldn't get version", __FILE__, __LINE__); // It's safer to assume this is KitKat and execute dlsym with a signal // handler installed. return true; } if (strcmp(prop_buf, "19") == 0 || strcmp(prop_buf, "20") == 0) { return true; } return false; } class ScopedSetTID { public: explicit ScopedSetTID(pid_t* tid) : tid_(tid) { *tid_ = syscall(SYS_gettid); } ~ScopedSetTID() { *tid_ = -1; } private: pid_t* tid_; }; sigjmp_buf dlsym_sigjmp_env; pid_t dlsym_tid = -1; void HandleSIGFPE(int signo, siginfo_t* siginfo, void* context) { if (siginfo->si_code != FPE_INTDIV || syscall(SYS_gettid) != dlsym_tid) { return; } siglongjmp(dlsym_sigjmp_env, 1); } } // namespace void* Dlsym(void* handle, const char* symbol) { if (!IsKitKat()) { return dlsym(handle, symbol); } static std::mutex* signal_handler_mutex = new std::mutex(); std::lock_guard lock(*signal_handler_mutex); ScopedSetTID set_tid(&dlsym_tid); ScopedSigactionRestore sig_restore; if (!sig_restore.ResetAndInstallHandler(SIGFPE, HandleSIGFPE)) { return nullptr; } if (sigsetjmp(dlsym_sigjmp_env, 1) != 0) { return nullptr; } return dlsym(handle, symbol); } #else void* Dlsym(void* handle, const char* symbol) { return dlsym(handle, symbol); } #endif // __ANDROID_API__ < 21 } // namespace internal } // namespace crashpad ================================================ FILE: compat/android/dlfcn_internal.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_ANDROID_DLFCN_INTERNAL_H_ #define CRASHPAD_COMPAT_ANDROID_DLFCN_INTERNAL_H_ namespace crashpad { namespace internal { //! \brief Provide a wrapper for `dlsym`. //! //! dlsym on Android KitKat (4.4.*) raises SIGFPE when searching for a //! non-existent symbol. This wrapper avoids crashing in this circumstance. //! https://code.google.com/p/android/issues/detail?id=61799 //! //! The parameters and return value for this function are the same as for //! `dlsym`, but a return value for `dlerror` may not be set in the event of an //! error. void* Dlsym(void* handle, const char* symbol); } // namespace internal } // namespace crashpad #endif // CRASHPAD_COMPAT_ANDROID_DLFCN_INTERNAL_H_ ================================================ FILE: compat/android/elf.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_ANDROID_ELF_H_ #define CRASHPAD_COMPAT_ANDROID_ELF_H_ #include_next #include #if !defined(ELF32_ST_VISIBILITY) #define ELF32_ST_VISIBILITY(other) ((other) & 0x3) #endif #if !defined(ELF64_ST_VISIBILITY) #define ELF64_ST_VISIBILITY(other) ELF32_ST_VISIBILITY(other) #endif // Android 5.0.0 (API 21) NDK #if !defined(STT_COMMON) #define STT_COMMON 5 #endif #if !defined(STT_TLS) #define STT_TLS 6 #endif // ELF note header types are normally provided by . While unified // headers include in , traditional headers do not, prior // to API 21. and can't both be included in the same // translation unit due to collisions, so we provide these types here. #if __ANDROID_API__ < 21 && !defined(__ANDROID_API_N__) typedef struct { Elf32_Word n_namesz; Elf32_Word n_descsz; Elf32_Word n_type; } Elf32_Nhdr; typedef struct { Elf64_Word n_namesz; Elf64_Word n_descsz; Elf64_Word n_type; } Elf64_Nhdr; #endif // __ANDROID_API__ < 21 && !defined(NT_PRSTATUS) #endif // CRASHPAD_COMPAT_ANDROID_ELF_H_ ================================================ FILE: compat/android/linux/elf.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_ANDROID_LINUX_ELF_H_ #define CRASHPAD_COMPAT_ANDROID_LINUX_ELF_H_ #include_next // Android 5.0.0 (API 21) NDK #if defined(__i386__) || defined(__x86_64__) #if !defined(NT_386_TLS) #define NT_386_TLS 0x200 #endif #endif // __i386__ || __x86_64__ #if defined(__ARMEL__) || defined(__aarch64__) #if !defined(NT_ARM_VFP) #define NT_ARM_VFP 0x400 #endif #if !defined(NT_ARM_TLS) #define NT_ARM_TLS 0x401 #endif #endif // __ARMEL__ || __aarch64__ #endif // CRASHPAD_COMPAT_ANDROID_LINUX_ELF_H_ ================================================ FILE: compat/android/linux/prctl.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_ANDROID_LINUX_PRCTL_H_ #define CRASHPAD_COMPAT_ANDROID_LINUX_PRCTL_H_ #include_next // Android 5.0.0 (API 21) NDK #if !defined(PR_SET_PTRACER) #define PR_SET_PTRACER 0x59616d61 #endif #endif // CRASHPAD_COMPAT_ANDROID_LINUX_PRCTL_H_ ================================================ FILE: compat/android/linux/ptrace.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_ANDROID_LINUX_PTRACE_H_ #define CRASHPAD_COMPAT_ANDROID_LINUX_PTRACE_H_ #include_next // Android 5.0.0 (API 21) NDK #if !defined(PTRACE_GETREGSET) #define PTRACE_GETREGSET 0x4204 #endif #endif // CRASHPAD_COMPAT_ANDROID_LINUX_PTRACE_H_ ================================================ FILE: compat/android/sched.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_ANDROID_SCHED_H_ #define CRASHPAD_COMPAT_ANDROID_SCHED_H_ #include_next // Android 5.0.0 (API 21) NDK #if !defined(SCHED_BATCH) #define SCHED_BATCH 3 #endif #if !defined(SCHED_IDLE) #define SCHED_IDLE 5 #endif #endif // CRASHPAD_COMPAT_ANDROID_SCHED_H_ ================================================ FILE: compat/android/sys/epoll.cc ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include "dlfcn_internal.h" #include "util/misc/no_cfi_icall.h" #if __ANDROID_API__ < 21 extern "C" { int epoll_create1(int flags) { static const crashpad::NoCfiIcall epoll_create1_p( crashpad::internal::Dlsym(RTLD_DEFAULT, "epoll_create1")); return epoll_create1_p ? epoll_create1_p(flags) : syscall(SYS_epoll_create1, flags); } } // extern "C" #endif // __ANDROID_API__ < 21 ================================================ FILE: compat/android/sys/epoll.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_ANDROID_SYS_EPOLL_H_ #define CRASHPAD_COMPAT_ANDROID_SYS_EPOLL_H_ #include_next #include #include // This is missing from traditional headers before API 21. #if !defined(EPOLLRDHUP) #define EPOLLRDHUP 0x00002000 #endif // EPOLL_CLOEXEC is undefined in traditional headers before API 21 and removed // from unified headers at API levels < 21 as a means to indicate that // epoll_create1 is missing from the C library, but the raw system call should // still be available. #if !defined(EPOLL_CLOEXEC) #define EPOLL_CLOEXEC O_CLOEXEC #endif #if __ANDROID_API__ < 21 #ifdef __cplusplus extern "C" { #endif int epoll_create1(int flags); #ifdef __cplusplus } // extern "C" #endif #endif // __ANDROID_API__ < 21 #endif // CRASHPAD_COMPAT_ANDROID_SYS_EPOLL_H_ ================================================ FILE: compat/android/sys/mman.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_ANDROID_SYS_MMAN_H_ #define CRASHPAD_COMPAT_ANDROID_SYS_MMAN_H_ #include_next #include #include // There’s no mmap() wrapper compatible with a 64-bit off_t for 32-bit code // until API 21 (Android 5.0/“Lollipop”). A custom mmap() wrapper is provided // here. Note that this scenario is only possible with NDK unified headers. // // https://android.googlesource.com/platform/bionic/+/0bfcbaf4d069e005d6e959d97f8d11c77722b70d/docs/32-bit-abi.md#is-32_bit-1 #if defined(__USE_FILE_OFFSET64) && __ANDROID_API__ < 21 #ifdef __cplusplus extern "C" { #endif void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset); #ifdef __cplusplus } // extern "C" #endif #endif // defined(__USE_FILE_OFFSET64) && __ANDROID_API__ < 21 #endif // CRASHPAD_COMPAT_ANDROID_SYS_MMAN_H_ ================================================ FILE: compat/android/sys/mman_mmap.cc ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include "dlfcn_internal.h" #include "util/misc/no_cfi_icall.h" #if defined(__USE_FILE_OFFSET64) && __ANDROID_API__ < 21 // Bionic has provided a wrapper for __mmap2() since the beginning of time. See // bionic/libc/SYSCALLS.TXT in any Android version. extern "C" void* __mmap2(void* addr, size_t size, int prot, int flags, int fd, size_t pgoff); namespace { template T Align(T value, size_t alignment) { return (value + alignment - 1) & ~(alignment - 1); } // Adapted from Android 8.0.0 bionic/libc/bionic/mmap.cpp. void* LocalMmap64(void* addr, size_t size, int prot, int flags, int fd, off64_t offset) { constexpr int kMmap2Shift = 12; if (offset < 0 || (offset & ((1UL << kMmap2Shift) - 1)) != 0) { errno = EINVAL; return MAP_FAILED; } const size_t rounded = Align(size, getpagesize()); if (rounded < size || rounded > PTRDIFF_MAX) { errno = ENOMEM; return MAP_FAILED; } const bool is_private_anonymous = (flags & (MAP_PRIVATE | MAP_ANONYMOUS)) == (MAP_PRIVATE | MAP_ANONYMOUS); const bool is_stack_or_grows_down = (flags & (MAP_STACK | MAP_GROWSDOWN)) != 0; void* const result = __mmap2(addr, size, prot, flags, fd, offset >> kMmap2Shift); static bool kernel_has_MADV_MERGEABLE = true; if (result != MAP_FAILED && kernel_has_MADV_MERGEABLE && is_private_anonymous && !is_stack_or_grows_down) { const int saved_errno = errno; const int rc = madvise(result, size, MADV_MERGEABLE); if (rc == -1 && errno == EINVAL) { kernel_has_MADV_MERGEABLE = false; } errno = saved_errno; } return result; } } // namespace extern "C" { void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset) { // Use the system’s mmap64() wrapper if available. It will be available on // Android 5.0 (“Lollipop”) and later. static const crashpad::NoCfiIcall mmap64( crashpad::internal::Dlsym(RTLD_DEFAULT, "mmap64")); if (mmap64) { return mmap64(addr, size, prot, flags, fd, offset); } // Otherwise, use the local implementation, which should amount to exactly the // same thing. return LocalMmap64(addr, size, prot, flags, fd, offset); } } // extern "C" #endif // defined(__USE_FILE_OFFSET64) && __ANDROID_API__ < 21 ================================================ FILE: compat/android/sys/syscall.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_ANDROID_SYS_SYSCALL_H_ #define CRASHPAD_COMPAT_ANDROID_SYS_SYSCALL_H_ #include_next // Android 5.0.0 (API 21) NDK #if !defined(SYS_epoll_create1) #define SYS_epoll_create1 __NR_epoll_create1 #endif #if !defined(SYS_gettid) #define SYS_gettid __NR_gettid #endif #if !defined(SYS_timer_create) #define SYS_timer_create __NR_timer_create #endif #if !defined(SYS_timer_getoverrun) #define SYS_timer_getoverrun __NR_timer_getoverrun #endif #if !defined(SYS_timer_settime) #define SYS_timer_settime __NR_timer_settime #endif #endif // CRASHPAD_COMPAT_ANDROID_SYS_SYSCALL_H_ ================================================ FILE: compat/android/sys/user.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_ANDROID_SYS_USER_H_ #define CRASHPAD_COMPAT_ANDROID_SYS_USER_H_ // This is needed for traditional headers. #include #include_next #endif // CRASHPAD_COMPAT_ANDROID_SYS_USER_H_ ================================================ FILE: compat/ios/mach/exc.defs ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_IOS_MACH_EXC_DEFS_ #define CRASHPAD_COMPAT_IOS_MACH_EXC_DEFS_ #include "third_party/xnu/osfmk/mach/exc.defs" #endif // CRASHPAD_COMPAT_IOS_MACH_EXC_DEFS_ ================================================ FILE: compat/ios/mach/mach_exc.defs ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_IOS_MACH_MACH_EXC_DEFS_ #define CRASHPAD_COMPAT_IOS_MACH_MACH_EXC_DEFS_ #include "third_party/xnu/osfmk/mach/mach_exc.defs" #endif // CRASHPAD_COMPAT_IOS_MACH_MACH_EXC_DEFS_ ================================================ FILE: compat/ios/mach/mach_types.defs ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_IOS_MACH_MACH_TYPES_DEFS_ #define CRASHPAD_COMPAT_IOS_MACH_MACH_TYPES_DEFS_ #include "third_party/xnu/osfmk/mach/mach_types.defs" #endif // CRASHPAD_COMPAT_IOS_MACH_MACH_TYPES_DEFS_ ================================================ FILE: compat/ios/mach/machine/machine_types.defs ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_IOS_MACH_MACHINE_MACHINE_TYPES_DEFS_ #define CRASHPAD_COMPAT_IOS_MACH_MACHINE_MACHINE_TYPES_DEFS_ #include "third_party/xnu/osfmk/mach/machine/machine_types.defs" #endif // CRASHPAD_COMPAT_IOS_MACH_MACHINE_MACHINE_TYPES_DEFS_ ================================================ FILE: compat/ios/mach/std_types.defs ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_IOS_MACH_STD_TYPES_DEFS_ #define CRASHPAD_COMPAT_IOS_MACH_STD_TYPES_DEFS_ #include "third_party/xnu/osfmk/mach/std_types.defs" #endif // CRASHPAD_COMPAT_IOS_MACH_STD_TYPES_DEFS_ ================================================ FILE: compat/linux/signal.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_LINUX_SIGNAL_H_ #define CRASHPAD_COMPAT_LINUX_SIGNAL_H_ #include_next // Missing from glibc and bionic #if !defined(SS_AUTODISARM) #define SS_AUTODISARM (1u << 31) #endif // Linux Kernel >= 5.11 flag for `sigaction::sa_flags`. Missing in headers from // earlier versions of Linux. #if !defined(SA_EXPOSE_TAGBITS) #define SA_EXPOSE_TAGBITS 0x00000800 #endif // Missing from glibc and bionic-x86_64 #if defined(__x86_64__) || defined(__i386__) #if !defined(X86_FXSR_MAGIC) #define X86_FXSR_MAGIC 0x0000 #endif #endif // __x86_64__ || __i386__ #if defined(__aarch64__) || defined(__arm__) #if !defined(FPSIMD_MAGIC) #define FPSIMD_MAGIC 0x46508001 #endif #if !defined(ESR_MAGIC) #define ESR_MAGIC 0x45535201 #endif #if !defined(EXTRA_MAGIC) #define EXTRA_MAGIC 0x45585401 #endif #if !defined(VFP_MAGIC) #define VFP_MAGIC 0x56465001 #endif #if !defined(CRUNCH_MAGIC) #define CRUNCH_MAGIC 0x5065cf03 #endif #if !defined(DUMMY_MAGIC) #define DUMMY_MAGIC 0xb0d9ed01 #endif #if !defined(IWMMXT_MAGIC) #define IWMMXT_MAGIC 0x12ef842a #endif #endif // __aarch64__ || __arm__ #endif // CRASHPAD_COMPAT_LINUX_SIGNAL_H_ ================================================ FILE: compat/linux/sys/mman.h ================================================ // Copyright 2019 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_LINUX_SYS_MMAN_H_ #define CRASHPAD_COMPAT_LINUX_SYS_MMAN_H_ #include_next #include // There's no memfd_create() wrapper before glibc 2.27. // This can't select for glibc < 2.27 because linux-chromeos-rel bots build this // code using a sysroot which has glibc 2.27, but then run it on Ubuntu 16.04, // which doesn't. #if defined(__GLIBC__) #ifdef __cplusplus extern "C" { #endif int memfd_create(const char* name, unsigned int flags) __THROW; #ifdef __cplusplus } // extern "C" #endif #endif // __GLIBC__ #endif // CRASHPAD_COMPAT_LINUX_SYS_MMAN_H_ ================================================ FILE: compat/linux/sys/mman_memfd_create.cc ================================================ // Copyright 2019 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include "util/misc/no_cfi_icall.h" #if defined(__GLIBC__) extern "C" { int memfd_create(const char* name, unsigned int flags) __THROW { static const crashpad::NoCfiIcall next_memfd_create( dlsym(RTLD_NEXT, "memfd_create")); return next_memfd_create ? next_memfd_create(name, flags) : syscall(SYS_memfd_create, name, flags); } } // extern "C" #endif // __GLIBC__ ================================================ FILE: compat/linux/sys/ptrace.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_LINUX_SYS_PTRACE_H_ #define CRASHPAD_COMPAT_LINUX_SYS_PTRACE_H_ #include_next #include // https://sourceware.org/bugzilla/show_bug.cgi?id=22433 #if !defined(PTRACE_GET_THREAD_AREA) && !defined(PT_GET_THREAD_AREA) && \ defined(__GLIBC__) #if defined(__i386__) || defined(__x86_64__) static constexpr __ptrace_request PTRACE_GET_THREAD_AREA = static_cast<__ptrace_request>(25); #define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA // https://bugs.chromium.org/p/chromium/issues/detail?id=873168 #elif defined(__arm__) || (defined(__aarch64__) && __GLIBC_PREREQ(2,28)) static constexpr __ptrace_request PTRACE_GET_THREAD_AREA = static_cast<__ptrace_request>(22); #define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA #elif defined(__mips__) static constexpr __ptrace_request PTRACE_GET_THREAD_AREA = static_cast<__ptrace_request>(25); #define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA static constexpr __ptrace_request PTRACE_GET_THREAD_AREA_3264 = static_cast<__ptrace_request>(0xc4); #define PTRACE_GET_THREAD_AREA_3264 PTRACE_GET_THREAD_AREA_3264 #endif #endif // !PTRACE_GET_THREAD_AREA && !PT_GET_THREAD_AREA && defined(__GLIBC__) // https://sourceware.org/bugzilla/show_bug.cgi?id=22433 #if !defined(PTRACE_GETVFPREGS) && !defined(PT_GETVFPREGS) && \ defined(__GLIBC__) && (defined(__arm__) || defined(__aarch64__)) static constexpr __ptrace_request PTRACE_GETVFPREGS = static_cast<__ptrace_request>(27); #define PTRACE_GETVFPREGS PTRACE_GETVFPREGS #endif #endif // CRASHPAD_COMPAT_LINUX_SYS_PTRACE_H_ ================================================ FILE: compat/linux/sys/user.h ================================================ // Copyright 2018 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_LINUX_SYS_USER_H_ #define CRASHPAD_COMPAT_LINUX_SYS_USER_H_ #include_next #include // glibc for 64-bit ARM uses different names for these structs prior to 2.20. // However, Debian's glibc 2.19-8 backported the change so it's not sufficient // to only test the version. user_pt_regs and user_fpsimd_state are actually // defined in so we use the include guard here. #if defined(__aarch64__) && defined(__GLIBC__) #if !__GLIBC_PREREQ(2, 20) && defined(__ASM_PTRACE_H) using user_regs_struct = user_pt_regs; using user_fpsimd_struct = user_fpsimd_state; #endif #endif #endif // CRASHPAD_COMPAT_LINUX_SYS_USER_H_ ================================================ FILE: compat/mac/Availability.h ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_MAC_AVAILABILITY_H_ #define CRASHPAD_COMPAT_MAC_AVAILABILITY_H_ // Until the 10.15 SDK, the contents of was in-line in // , but since then, it was broken out into its own header. // This compat version of allows these macros to always appear // to be provided by the new header, , even when an // older SDK is in use. #include_next #include #endif // CRASHPAD_COMPAT_MAC_AVAILABILITY_H_ ================================================ FILE: compat/mac/AvailabilityVersions.h ================================================ // Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_MAC_AVAILABILITYVERSIONS_H_ #define CRASHPAD_COMPAT_MAC_AVAILABILITYVERSIONS_H_ #if __has_include_next() #include_next #endif // 10.7 SDK #ifndef __MAC_10_7 #define __MAC_10_7 1070 #endif // 10.8 SDK #ifndef __MAC_10_8 #define __MAC_10_8 1080 #endif // 10.9 SDK #ifndef __MAC_10_9 #define __MAC_10_9 1090 #endif // 10.10 SDK #ifndef __MAC_10_10 #define __MAC_10_10 101000 #endif // 10.11 SDK #ifndef __MAC_10_11 #define __MAC_10_11 101100 #endif // 10.12 SDK #ifndef __MAC_10_12 #define __MAC_10_12 101200 #endif // 10.13 SDK #ifndef __MAC_10_13 #define __MAC_10_13 101300 #endif #ifndef __MAC_10_13_4 #define __MAC_10_13_4 101304 #endif // 10.14 SDK #ifndef __MAC_10_14 #define __MAC_10_14 101400 #endif // 10.15 SDK #ifndef __MAC_10_15 #define __MAC_10_15 101500 #endif // 11.0 SDK #ifndef __MAC_10_16 #define __MAC_10_16 101600 #endif #ifndef __MAC_11_0 #define __MAC_11_0 110000 #endif // 12.0 SDK #ifndef __MAC_12_0 #define __MAC_12_0 120000 #endif #endif // CRASHPAD_COMPAT_MAC_AVAILABILITYVERSIONS_H_ ================================================ FILE: compat/mac/kern/exc_resource.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_MAC_KERN_EXC_RESOURCE_H_ #define CRASHPAD_COMPAT_MAC_KERN_EXC_RESOURCE_H_ #if __has_include_next() #include_next #endif // 10.9 SDK #ifndef EXC_RESOURCE_DECODE_RESOURCE_TYPE #define EXC_RESOURCE_DECODE_RESOURCE_TYPE(code) (((code) >> 61) & 0x7ull) #endif #ifndef EXC_RESOURCE_DECODE_FLAVOR #define EXC_RESOURCE_DECODE_FLAVOR(code) (((code) >> 58) & 0x7ull) #endif #ifndef RESOURCE_TYPE_CPU #define RESOURCE_TYPE_CPU 1 #endif #ifndef RESOURCE_TYPE_WAKEUPS #define RESOURCE_TYPE_WAKEUPS 2 #endif #ifndef RESOURCE_TYPE_MEMORY #define RESOURCE_TYPE_MEMORY 3 #endif #ifndef FLAVOR_CPU_MONITOR #define FLAVOR_CPU_MONITOR 1 #endif #ifndef FLAVOR_WAKEUPS_MONITOR #define FLAVOR_WAKEUPS_MONITOR 1 #endif #ifndef FLAVOR_HIGH_WATERMARK #define FLAVOR_HIGH_WATERMARK 1 #endif // 10.10 SDK #ifndef FLAVOR_CPU_MONITOR_FATAL #define FLAVOR_CPU_MONITOR_FATAL 2 #endif // 10.12 SDK #ifndef RESOURCE_TYPE_IO #define RESOURCE_TYPE_IO 4 #endif #ifndef FLAVOR_IO_PHYSICAL_WRITES #define FLAVOR_IO_PHYSICAL_WRITES 1 #endif #ifndef FLAVOR_IO_LOGICAL_WRITES #define FLAVOR_IO_LOGICAL_WRITES 2 #endif #endif // CRASHPAD_COMPAT_MAC_KERN_EXC_RESOURCE_H_ ================================================ FILE: compat/mac/mach/i386/thread_state.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_MAC_MACH_I386_THREAD_STATE_H_ #define CRASHPAD_COMPAT_MAC_MACH_I386_THREAD_STATE_H_ #include_next // 10.13 SDK // // This was defined as 244 in the 10.7 through 10.12 SDKs, and 144 previously. #if I386_THREAD_STATE_MAX < 614 #undef I386_THREAD_STATE_MAX #define I386_THREAD_STATE_MAX (614) #endif #endif // CRASHPAD_COMPAT_MAC_MACH_I386_THREAD_STATE_H_ ================================================ FILE: compat/mac/mach/mach.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_MAC_MACH_MACH_H_ #define CRASHPAD_COMPAT_MAC_MACH_MACH_H_ #include_next // // 10.8 SDK #ifndef EXC_RESOURCE #define EXC_RESOURCE 11 #endif #ifndef EXC_MASK_RESOURCE #define EXC_MASK_RESOURCE (1 << EXC_RESOURCE) #endif // 10.9 SDK #ifndef EXC_GUARD #define EXC_GUARD 12 #endif #ifndef EXC_MASK_GUARD #define EXC_MASK_GUARD (1 << EXC_GUARD) #endif // 10.11 SDK #ifndef EXC_CORPSE_NOTIFY #define EXC_CORPSE_NOTIFY 13 #endif #ifndef EXC_MASK_CORPSE_NOTIFY #define EXC_MASK_CORPSE_NOTIFY (1 << EXC_CORPSE_NOTIFY) #endif // Don’t expose EXC_MASK_ALL at all, because its definition varies with SDK, and // older kernels will reject values that they don’t understand. Instead, use // crashpad::ExcMaskAll(), which computes the correct value of EXC_MASK_ALL for // the running system. #undef EXC_MASK_ALL #if defined(__i386__) || defined(__x86_64__) // // 10.11 SDK #if EXC_TYPES_COUNT > 14 // Definition varies with SDK #error Update this file for new exception types #elif EXC_TYPES_COUNT != 14 #undef EXC_TYPES_COUNT #define EXC_TYPES_COUNT 14 #endif // // 10.6 SDK // // Earlier versions of this SDK didn’t have AVX definitions. They didn’t appear // until the version of the 10.6 SDK that shipped with Xcode 4.2, although // versions of this SDK appeared with Xcode releases as early as Xcode 3.2. // Similarly, the kernel didn’t handle AVX state until Mac OS X 10.6.8 // (xnu-1504.15.3) and presumably the hardware-specific versions of Mac OS X // 10.6.7 intended to run on processors with AVX. #ifndef x86_AVX_STATE32 #define x86_AVX_STATE32 16 #endif #ifndef x86_AVX_STATE64 #define x86_AVX_STATE64 17 #endif // 10.8 SDK #ifndef x86_AVX_STATE #define x86_AVX_STATE 18 #endif // 10.13 SDK #ifndef x86_AVX512_STATE32 #define x86_AVX512_STATE32 19 #endif #ifndef x86_AVX512_STATE64 #define x86_AVX512_STATE64 20 #endif #ifndef x86_AVX512_STATE #define x86_AVX512_STATE 21 #endif #endif // defined(__i386__) || defined(__x86_64__) // // 10.8 SDK #ifndef THREAD_STATE_FLAVOR_LIST_10_9 #define THREAD_STATE_FLAVOR_LIST_10_9 129 #endif #endif // CRASHPAD_COMPAT_MAC_MACH_MACH_H_ ================================================ FILE: compat/mac/mach-o/loader.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_MAC_MACH_O_LOADER_H_ #define CRASHPAD_COMPAT_MAC_MACH_O_LOADER_H_ #include_next // 10.7 SDK #ifndef S_THREAD_LOCAL_ZEROFILL #define S_THREAD_LOCAL_ZEROFILL 0x12 #endif // 10.8 SDK #ifndef LC_SOURCE_VERSION #define LC_SOURCE_VERSION 0x2a #endif #endif // CRASHPAD_COMPAT_MAC_MACH_O_LOADER_H_ ================================================ FILE: compat/mac/sys/resource.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_MAC_SYS_RESOURCE_H_ #define CRASHPAD_COMPAT_MAC_SYS_RESOURCE_H_ #include_next // 10.9 SDK #ifndef WAKEMON_MAKE_FATAL #define WAKEMON_MAKE_FATAL 0x10 #endif #endif // CRASHPAD_COMPAT_MAC_SYS_RESOURCE_H_ ================================================ FILE: compat/non_mac/mach/mach.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_NON_MAC_MACH_MACH_H_ #define CRASHPAD_COMPAT_NON_MAC_MACH_MACH_H_ //! \file // //! \anchor EXC_x //! \name EXC_* //! //! \brief Mach exception type definitions. //! \{ #define EXC_BAD_ACCESS 1 #define EXC_BAD_INSTRUCTION 2 #define EXC_ARITHMETIC 3 #define EXC_EMULATION 4 #define EXC_SOFTWARE 5 #define EXC_BREAKPOINT 6 #define EXC_SYSCALL 7 #define EXC_MACH_SYSCALL 8 #define EXC_RPC_ALERT 9 #define EXC_CRASH 10 #define EXC_RESOURCE 11 #define EXC_GUARD 12 #define EXC_CORPSE_NOTIFY 13 #define EXC_TYPES_COUNT 14 //! \} #endif // CRASHPAD_COMPAT_NON_MAC_MACH_MACH_H_ ================================================ FILE: compat/non_mac/mach/machine.h ================================================ // Copyright 2019 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_NON_MAC_MACH_MACHINE_H_ #define CRASHPAD_COMPAT_NON_MAC_MACH_MACHINE_H_ typedef int cpu_type_t; typedef int cpu_subtype_t; #endif // CRASHPAD_COMPAT_NON_MAC_MACH_MACHINE_H_ ================================================ FILE: compat/non_mac/mach/vm_prot.h ================================================ // Copyright 2019 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_NON_MAC_MACH_VM_PROT_H_ #define CRASHPAD_COMPAT_NON_MAC_MACH_VM_PROT_H_ typedef int vm_prot_t; #endif // CRASHPAD_COMPAT_NON_MAC_MACH_VM_PROT_H_ ================================================ FILE: compat/non_mac/mach-o/loader.h ================================================ // Copyright 2019 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_NON_MAC_MACH_O_LOADER_H_ #define CRASHPAD_COMPAT_NON_MAC_MACH_O_LOADER_H_ #include "third_party/xnu/EXTERNAL_HEADERS/mach-o/loader.h" #endif // CRASHPAD_COMPAT_NON_MAC_MACH_O_LOADER_H_ ================================================ FILE: compat/non_win/dbghelp.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_NON_WIN_DBGHELP_H_ #define CRASHPAD_COMPAT_NON_WIN_DBGHELP_H_ #include #include "compat/non_win/timezoneapi.h" #include "compat/non_win/verrsrc.h" #include "compat/non_win/winnt.h" //! \file //! \brief The magic number for a minidump file, stored in //! MINIDUMP_HEADER::Signature. //! //! A hex dump of a little-endian minidump file will begin with the string //! “MDMP”. #define MINIDUMP_SIGNATURE ('PMDM') // 0x4d444d50 //! \brief The version of a minidump file, stored in MINIDUMP_HEADER::Version. #define MINIDUMP_VERSION (42899) //! \brief An offset within a minidump file, relative to the start of its //! MINIDUMP_HEADER. //! //! RVA stands for “relative virtual address”. Within a minidump file, RVAs are //! used as pointers to link structures together. //! //! \sa MINIDUMP_LOCATION_DESCRIPTOR typedef uint32_t RVA; //! \brief A 64-bit offset within a minidump file, relative to the start of its //! MINIDUMP_HEADER. //! //! RVA stands for “relative virtual address”. Within a minidump file, RVAs are //! used as pointers to link structures together. //! //! \sa MINIDUMP_LOCATION_DESCRIPTOR64 typedef uint64_t RVA64; //! \brief A pointer to a structure or union within a minidump file. struct __attribute__((packed, aligned(4))) MINIDUMP_LOCATION_DESCRIPTOR { //! \brief The size of the referenced structure or union, in bytes. uint32_t DataSize; //! \brief The relative virtual address of the structure or union within the //! minidump file. RVA Rva; }; //! \brief A 64-bit pointer to a structure or union within a minidump file. struct __attribute__((packed, aligned(4))) MINIDUMP_LOCATION_DESCRIPTOR64 { //! \brief The size of the referenced structure or union, in bytes. uint64_t DataSize; //! \brief The 64-bit relative virtual address of the structure or union //! within the minidump file. RVA64 Rva; }; //! \brief A pointer to a snapshot of a region of memory contained within a //! minidump file. //! //! \sa MINIDUMP_MEMORY_LIST struct __attribute__((packed, aligned(4))) MINIDUMP_MEMORY_DESCRIPTOR { //! \brief The base address of the memory region in the address space of the //! process that the minidump file contains a snapshot of. uint64_t StartOfMemoryRange; //! \brief The contents of the memory region. MINIDUMP_LOCATION_DESCRIPTOR Memory; }; //! \brief The top-level structure identifying a minidump file. //! //! This structure contains a pointer to the stream directory, a second-level //! structure which in turn contains pointers to third-level structures //! (“streams”) containing the data within the minidump file. This structure //! also contains the minidump file’s magic numbers, and other bookkeeping data. //! //! This structure must be present at the beginning of a minidump file (at ::RVA //! 0). struct __attribute__((packed, aligned(4))) MINIDUMP_HEADER { //! \brief The minidump file format magic number, ::MINIDUMP_SIGNATURE. uint32_t Signature; //! \brief The minidump file format version number, ::MINIDUMP_VERSION. uint32_t Version; //! \brief The number of MINIDUMP_DIRECTORY elements present in the directory //! referenced by #StreamDirectoryRva. uint32_t NumberOfStreams; //! \brief A pointer to an array of MINIDUMP_DIRECTORY structures that //! identify all of the streams within this minidump file. The array has //! #NumberOfStreams elements present. RVA StreamDirectoryRva; //! \brief The minidump file’s checksum. This can be `0`, and in practice, `0` //! is the only value that has ever been seen in this field. uint32_t CheckSum; //! \brief The time that the minidump file was generated, in `time_t` format, //! the number of seconds since the POSIX epoch. uint32_t TimeDateStamp; //! \brief A bitfield containing members of ::MINIDUMP_TYPE, describing the //! types of data carried within this minidump file. uint64_t Flags; }; //! \brief A pointer to a stream within a minidump file. //! //! Each stream present in a minidump file will have a corresponding //! MINIDUMP_DIRECTORY entry in the stream directory referenced by //! MINIDUMP_HEADER::StreamDirectoryRva. struct __attribute__((packed, aligned(4))) MINIDUMP_DIRECTORY { //! \brief The type of stream referenced, a value of ::MINIDUMP_STREAM_TYPE. uint32_t StreamType; //! \brief A pointer to the stream data within the minidump file. MINIDUMP_LOCATION_DESCRIPTOR Location; }; //! \brief A variable-length UTF-16-encoded string carried within a minidump //! file. //! //! The UTF-16 string is stored as UTF-16LE or UTF-16BE according to the byte //! ordering of the minidump file itself. //! //! \sa crashpad::MinidumpUTF8String struct __attribute__((packed, aligned(4))) MINIDUMP_STRING { //! \brief The length of the #Buffer field in bytes, not including the `NUL` //! terminator. //! //! \note This field is interpreted as a byte count, not a count of UTF-16 //! code units or Unicode code points. uint32_t Length; //! \brief The string, encoded in UTF-16, and terminated with a UTF-16 `NUL` //! code unit (two `NUL` bytes). char16_t Buffer[0]; }; //! \brief Minidump stream type values for MINIDUMP_DIRECTORY::StreamType. Each //! stream structure has a corresponding stream type value to identify it. //! //! \sa crashpad::MinidumpStreamType enum MINIDUMP_STREAM_TYPE { //! \brief The stream type for MINIDUMP_THREAD_LIST. ThreadListStream = 3, //! \brief The stream type for MINIDUMP_MODULE_LIST. ModuleListStream = 4, //! \brief The stream type for MINIDUMP_MEMORY_LIST. MemoryListStream = 5, //! \brief The stream type for MINIDUMP_EXCEPTION_STREAM. ExceptionStream = 6, //! \brief The stream type for MINIDUMP_SYSTEM_INFO. SystemInfoStream = 7, //! \brief The stream contains information about active `HANDLE`s. HandleDataStream = 12, //! \brief The stream type for MINIDUMP_UNLOADED_MODULE_LIST. UnloadedModuleListStream = 14, //! \brief The stream type for MINIDUMP_MISC_INFO, MINIDUMP_MISC_INFO_2, //! MINIDUMP_MISC_INFO_3, MINIDUMP_MISC_INFO_4, and MINIDUMP_MISC_INFO_5. //! //! More recent versions of this stream are supersets of earlier versions. //! //! The exact version of the stream that is present is implied by the stream’s //! size. Furthermore, this stream contains a field, //! MINIDUMP_MISC_INFO::Flags1, that indicates which data is present and //! valid. MiscInfoStream = 15, //! \brief The stream type for MINIDUMP_MEMORY_INFO_LIST. MemoryInfoListStream = 16, //! \brief The stream type for MINIDUMP_THREAD_NAME_LIST. ThreadNamesStream = 24, //! \brief Values greater than this value will not be used by the system //! and can be used for custom user data streams. LastReservedStream = 0xffff, }; //! \brief Information about the CPU (or CPUs) that ran the process that the //! minidump file contains a snapshot of. //! //! This union only appears as MINIDUMP_SYSTEM_INFO::Cpu. Its interpretation is //! controlled by MINIDUMP_SYSTEM_INFO::ProcessorArchitecture. union __attribute__((packed, aligned(4))) CPU_INFORMATION { //! \brief Information about 32-bit x86 CPUs, or x86_64 CPUs when running //! 32-bit x86 processes. struct __attribute__((packed, aligned(4))) { //! \brief The CPU’s vendor identification string as encoded in `cpuid 0` //! `ebx`, `edx`, and `ecx`, represented as it appears in these //! registers. //! //! For Intel CPUs, `[0]` will encode “Genu”, `[1]` will encode “ineI”, and //! `[2]` will encode “ntel”, for a vendor ID string “GenuineIntel”. //! //! \note The Windows documentation incorrectly states that these fields are //! to be interpreted as `cpuid 0` `eax`, `ebx`, and `ecx`. uint32_t VendorId[3]; //! \brief Family, model, and stepping ID values as encoded in `cpuid 1` //! `eax`. uint32_t VersionInformation; //! \brief A bitfield containing supported CPU capabilities as encoded in //! `cpuid 1` `edx`. uint32_t FeatureInformation; //! \brief A bitfield containing supported CPU capabalities as encoded in //! `cpuid 0x80000001` `edx`. //! //! This field is only valid if #VendorId identifies the CPU vendor as //! “AuthenticAMD” or "HygonGenuine". uint32_t AMDExtendedCpuFeatures; } X86CpuInfo; //! \brief Information about non-x86 CPUs, and x86_64 CPUs when not running //! 32-bit x86 processes. struct __attribute__((packed, aligned(4))) { //! \brief Bitfields containing supported CPU capabilities as identified by //! bits corresponding to \ref PF_x "PF_*" values passed to //! `IsProcessorFeaturePresent()`. uint64_t ProcessorFeatures[2]; } OtherCpuInfo; }; //! \brief Information about the system that hosted the process that the //! minidump file contains a snapshot of. struct __attribute__((packed, aligned(4))) MINIDUMP_SYSTEM_INFO { // The next 4 fields are from the SYSTEM_INFO structure returned by // GetSystemInfo(). //! \brief The system’s CPU architecture. This may be a \ref //! PROCESSOR_ARCHITECTURE_x "PROCESSOR_ARCHITECTURE_*" value, or a member //! of crashpad::MinidumpCPUArchitecture. //! //! In some cases, a system may be able to run processes of multiple specific //! architecture types. For example, systems based on 64-bit architectures //! such as x86_64 are often able to run 32-bit code of another architecture //! in the same family, such as 32-bit x86. On these systems, this field will //! identify the architecture of the process that the minidump file contains a //! snapshot of. uint16_t ProcessorArchitecture; //! \brief General CPU version information. //! //! The precise interpretation of this field is specific to each CPU //! architecture. For x86-family CPUs (including x86_64 and 32-bit x86), this //! field contains the CPU family ID value from `cpuid 1` `eax`, adjusted to //! take the extended family ID into account. uint16_t ProcessorLevel; //! \brief Specific CPU version information. //! //! The precise interpretation of this field is specific to each CPU //! architecture. For x86-family CPUs (including x86_64 and 32-bit x86), this //! field contains values obtained from `cpuid 1` `eax`: the high byte //! contains the CPU model ID value adjusted to take the extended model ID //! into account, and the low byte contains the CPU stepping ID value. uint16_t ProcessorRevision; //! \brief The total number of CPUs present in the system. uint8_t NumberOfProcessors; // The next 7 fields are from the OSVERSIONINFOEX structure returned by // GetVersionEx(). //! \brief The system’s operating system type, which distinguishes between //! “desktop” or “workstation” systems and “server” systems. This may be a //! \ref VER_NT_x "VER_NT_*" value, or a member of //! crashpad::MinidumpOSType. uint8_t ProductType; //! \brief The system’s operating system version number’s first (major) //! component. //! //! - For Windows 7 (NT 6.1) SP1, version 6.1.7601, this would be `6`. //! - For macOS 10.12.1, this would be `10`. uint32_t MajorVersion; //! \brief The system’s operating system version number’s second (minor) //! component. //! //! - For Windows 7 (NT 6.1) SP1, version 6.1.7601, this would be `1`. //! - For macOS 10.12.1, this would be `12`. uint32_t MinorVersion; //! \brief The system’s operating system version number’s third (build or //! patch) component. //! //! - For Windows 7 (NT 6.1) SP1, version 6.1.7601, this would be `7601`. //! - For macOS 10.12.1, this would be `1`. uint32_t BuildNumber; //! \brief The system’s operating system family. This may be a \ref //! VER_PLATFORM_x "VER_PLATFORM_*" value, or a member of //! crashpad::MinidumpOS. uint32_t PlatformId; //! \brief ::RVA of a MINIDUMP_STRING containing operating system-specific //! version information. //! //! This field further identifies an operating system version beyond its //! version number fields. Historically, “CSD” stands for “corrective service //! diskette.” //! //! - On Windows, this is the name of the installed operating system service //! pack, such as “Service Pack 1”. If no service pack is installed, this //! field references an empty string. //! - On macOS, this is the operating system build number from `sw_vers //! -buildVersion`. For macOS 10.12.1 on most hardware types, this would //! be `16B2657`. //! - On Linux and other Unix-like systems, this is the kernel version from //! `uname -srvm`, possibly with additional information appended. On //! Android, the `ro.build.fingerprint` system property is appended. RVA CSDVersionRva; //! \brief A bitfield identifying products installed on the system. This is //! composed of \ref VER_SUITE_x "VER_SUITE_*" values. //! //! This field is Windows-specific, and has no meaning on other operating //! systems. uint16_t SuiteMask; uint16_t Reserved2; //! \brief Information about the system’s CPUs. //! //! This field is a union. Which of its members should be expressed is //! controlled by the #ProcessorArchitecture field. If it is set to //! crashpad::kMinidumpCPUArchitectureX86, the CPU_INFORMATION::X86CpuInfo //! field is expressed. Otherwise, the CPU_INFORMATION::OtherCpuInfo field is //! expressed. //! //! \note Older Breakpad implementations produce minidump files that express //! CPU_INFORMATION::X86CpuInfo when #ProcessorArchitecture is set to //! crashpad::kMinidumpCPUArchitectureAMD64. Minidump files produced by //! `dbghelp.dll` on Windows express CPU_INFORMATION::OtherCpuInfo in this //! case. CPU_INFORMATION Cpu; }; //! \brief Information about a specific thread within the process. //! //! \sa MINIDUMP_THREAD_LIST struct __attribute__((packed, aligned(4))) MINIDUMP_THREAD { //! \brief The thread’s ID. This may be referenced by //! MINIDUMP_EXCEPTION_STREAM::ThreadId. uint32_t ThreadId; //! \brief The thread’s suspend count. //! //! This field will be `0` if the thread is schedulable (not suspended). uint32_t SuspendCount; //! \brief The thread’s priority class. //! //! On Windows, this is a `*_PRIORITY_CLASS` value. `NORMAL_PRIORITY_CLASS` //! has value `0x20`; higher priority classes have higher values. uint32_t PriorityClass; //! \brief The thread’s priority level. //! //! On Windows, this is a `THREAD_PRIORITY_*` value. `THREAD_PRIORITY_NORMAL` //! has value `0`; higher priorities have higher values, and lower priorities //! have lower (negative) values. uint32_t Priority; //! \brief The address of the thread’s thread environment block in the address //! space of the process that the minidump file contains a snapshot of. //! //! The thread environment block contains thread-local data. //! //! A MINIDUMP_MEMORY_DESCRIPTOR may be present in the MINIDUMP_MEMORY_LIST //! stream containing the thread-local data pointed to by this field. uint64_t Teb; //! \brief A snapshot of the thread’s stack. //! //! A MINIDUMP_MEMORY_DESCRIPTOR may be present in the MINIDUMP_MEMORY_LIST //! stream containing a pointer to the same memory range referenced by this //! field. MINIDUMP_MEMORY_DESCRIPTOR Stack; //! \brief A pointer to a CPU-specific CONTEXT structure containing the //! thread’s context at the time the snapshot was taken. //! //! If the minidump file was generated as a result of an exception taken on //! this thread, this field may identify a different context than the //! exception context. For these minidump files, a MINIDUMP_EXCEPTION_STREAM //! stream will be present, and the context contained within that stream will //! be the exception context. //! //! The interpretation of the context structure is dependent on the CPU //! architecture identified by MINIDUMP_SYSTEM_INFO::ProcessorArchitecture. //! For crashpad::kMinidumpCPUArchitectureX86, this will be //! crashpad::MinidumpContextX86. For crashpad::kMinidumpCPUArchitectureAMD64, //! this will be crashpad::MinidumpContextAMD64. MINIDUMP_LOCATION_DESCRIPTOR ThreadContext; }; //! \brief Information about all threads within the process. struct __attribute__((packed, aligned(4))) MINIDUMP_THREAD_LIST { //! \brief The number of threads present in the #Threads array. uint32_t NumberOfThreads; //! \brief Structures identifying each thread within the process. MINIDUMP_THREAD Threads[0]; }; //! \brief Information about an exception that occurred in the process. struct __attribute__((packed, aligned(4))) MINIDUMP_EXCEPTION { //! \brief The top-level exception code identifying the exception, in //! operating system-specific values. //! //! For macOS minidumps, this will be an \ref EXC_x "EXC_*" exception type, //! such as `EXC_BAD_ACCESS`. `EXC_CRASH` will not appear here for exceptions //! processed as `EXC_CRASH` when generated from another preceding exception: //! the original exception code will appear instead. The exception type as it //! was received will appear at index 0 of #ExceptionInformation. //! //! For Windows minidumps, this will be an `EXCEPTION_*` exception type, such //! as `EXCEPTION_ACCESS_VIOLATION`. //! //! \note This field is named ExceptionCode, but what is known as the //! “exception code” on macOS/Mach is actually stored in the //! #ExceptionFlags field of a minidump file. //! //! \todo Document the possible values by OS. There may be OS-specific enums //! in minidump_extensions.h. uint32_t ExceptionCode; //! \brief Additional exception flags that further identify the exception, in //! operating system-specific values. //! //! For macOS minidumps, this will be the value of the exception code at index //! 0 as received by a Mach exception handler, except: //! * For exception type `EXC_CRASH` generated from another preceding //! exception, the original exception code will appear here, not the code //! as received by the Mach exception handler. //! * For exception types `EXC_RESOURCE` and `EXC_GUARD`, the high 32 bits of //! the code received by the Mach exception handler will appear here. //! //! In all cases for macOS minidumps, the code as it was received by the Mach //! exception handler will appear at index 1 of #ExceptionInformation. //! //! For Windows minidumps, this will either be `0` if the exception is //! continuable, or `EXCEPTION_NONCONTINUABLE` to indicate a noncontinuable //! exception. //! //! \todo Document the possible values by OS. There may be OS-specific enums //! in minidump_extensions.h. uint32_t ExceptionFlags; //! \brief An address, in the address space of the process that this minidump //! file contains a snapshot of, of another MINIDUMP_EXCEPTION. This field //! is used for nested exceptions. uint64_t ExceptionRecord; //! \brief The address that caused the exception. //! //! This may be the address that caused a fault on data access, or it may be //! the instruction pointer that contained an offending instruction. uint64_t ExceptionAddress; //! \brief The number of valid elements in #ExceptionInformation. uint32_t NumberParameters; uint32_t __unusedAlignment; //! \brief Additional information about the exception, specific to the //! operating system and possibly the #ExceptionCode. //! //! For macOS minidumps, this will contain the exception type as received by a //! Mach exception handler and the values of the `codes[0]` and `codes[1]` //! (exception code and subcode) parameters supplied to the Mach exception //! handler. Unlike #ExceptionCode and #ExceptionFlags, the values received by //! a Mach exception handler are used directly here even for the `EXC_CRASH`, //! `EXC_RESOURCE`, and `EXC_GUARD` exception types. //! For Windows, these are additional arguments (if any) as provided to //! `RaiseException()`. uint64_t ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; }; //! \brief Information about the exception that triggered a minidump file’s //! generation. struct __attribute__((packed, aligned(4))) MINIDUMP_EXCEPTION_STREAM { //! \brief The ID of the thread that caused the exception. //! //! \sa MINIDUMP_THREAD::ThreadId uint32_t ThreadId; uint32_t __alignment; //! \brief Information about the exception. MINIDUMP_EXCEPTION ExceptionRecord; //! \brief A pointer to a CPU-specific CONTEXT structure containing the //! thread’s context at the time the exception was caused. //! //! The interpretation of the context structure is dependent on the CPU //! architecture identified by MINIDUMP_SYSTEM_INFO::ProcessorArchitecture. //! For crashpad::kMinidumpCPUArchitectureX86, this will be //! crashpad::MinidumpContextX86. For crashpad::kMinidumpCPUArchitectureAMD64, //! this will be crashpad::MinidumpContextAMD64. MINIDUMP_LOCATION_DESCRIPTOR ThreadContext; }; //! \brief Information about a specific module loaded within the process at the //! time the snapshot was taken. //! //! A module may be the main executable, a shared library, or a loadable module. //! //! \sa MINIDUMP_MODULE_LIST struct __attribute__((packed, aligned(4))) MINIDUMP_MODULE { //! \brief The base address of the loaded module in the address space of the //! process that the minidump file contains a snapshot of. uint64_t BaseOfImage; //! \brief The size of the loaded module. uint32_t SizeOfImage; //! \brief The loaded module’s checksum, or `0` if unknown. //! //! On Windows, this field comes from the `CheckSum` field of the module’s //! `IMAGE_OPTIONAL_HEADER` structure, if present. It reflects the checksum at //! the time the module was linked. uint32_t CheckSum; //! \brief The module’s timestamp, in `time_t` units, seconds since the POSIX //! epoch, or `0` if unknown. //! //! On Windows, this field comes from the `TimeDateStamp` field of the //! module’s `IMAGE_FILE_HEADER` structure. It reflects the timestamp at the //! time the module was linked. uint32_t TimeDateStamp; //! \brief ::RVA of a MINIDUMP_STRING containing the module’s path or file //! name. RVA ModuleNameRva; //! \brief The module’s version information. VS_FIXEDFILEINFO VersionInfo; //! \brief A pointer to the module’s CodeView record, typically a link to its //! debugging information in crashpad::CodeViewRecordPDB70 format. //! //! The specific format of the CodeView record is indicated by its signature, //! the first 32-bit value in the structure. For links to debugging //! information in contemporary usage, this is normally a //! crashpad::CodeViewRecordPDB70 structure, but may be a //! crashpad::CodeViewRecordPDB20 structure instead. These structures identify //! a link to debugging data within a `.pdb` (Program Database) file. See Matching //! Debug Information, PDB Files. //! //! On Windows, it is also possible for the CodeView record to contain //! debugging information itself, as opposed to a link to a `.pdb` file. See //! Microsoft //! Symbol and Type Information, section 7.2, “Debug Information Format” //! for a list of debug information formats, and Undocumented Windows 2000 //! Secrets, Windows 2000 Debugging Support/Microsoft Symbol File //! Internals/CodeView Subsections for an in-depth description of the CodeView //! 4.1 format. Signatures seen in the wild include “NB09” (0x3930424e) for //! CodeView 4.1 and “NB11” (0x3131424e) for CodeView 5.0. This form of //! debugging information within the module, as opposed to a link to an //! external `.pdb` file, is chosen by building with `/Z7` in Visual Studio //! 6.0 (1998) and earlier. This embedded form of debugging information is now //! considered obsolete. //! //! On Windows, the CodeView record is taken from a module’s //! IMAGE_DEBUG_DIRECTORY entry whose Type field has the value //! IMAGE_DEBUG_TYPE_CODEVIEW (`2`), if any. Records in //! crashpad::CodeViewRecordPDB70 format are generated by Visual Studio .NET //! (2002) (version 7.0) and later. //! //! When the CodeView record is not present, the fields of this //! MINIDUMP_LOCATION_DESCRIPTOR will be `0`. MINIDUMP_LOCATION_DESCRIPTOR CvRecord; //! \brief A pointer to the module’s miscellaneous debugging record, a //! structure of type IMAGE_DEBUG_MISC. //! //! This field is Windows-specific, and has no meaning on other operating //! systems. It is largely obsolete on Windows, where it was used to link to //! debugging information stored in a `.dbg` file. `.dbg` files have been //! superseded by `.pdb` files. //! //! On Windows, the miscellaneous debugging record is taken from module’s //! IMAGE_DEBUG_DIRECTORY entry whose Type field has the value //! IMAGE_DEBUG_TYPE_MISC (`4`), if any. //! //! When the miscellaneous debugging record is not present, the fields of this //! MINIDUMP_LOCATION_DESCRIPTOR will be `0`. //! //! \sa #CvRecord MINIDUMP_LOCATION_DESCRIPTOR MiscRecord; uint64_t Reserved0; uint64_t Reserved1; }; //! \brief Information about all modules loaded within the process at the time //! the snapshot was taken. struct __attribute__((packed, aligned(4))) MINIDUMP_MODULE_LIST { //! \brief The number of modules present in the #Modules array. uint32_t NumberOfModules; //! \brief Structures identifying each module present in the minidump file. MINIDUMP_MODULE Modules[0]; }; //! \brief Information about memory regions within the process. //! //! Typically, a minidump file will not contain a snapshot of a process’ entire //! memory image. For minidump files identified as ::MiniDumpNormal in //! MINIDUMP_HEADER::Flags, memory regions are limited to those referenced by //! MINIDUMP_THREAD::Stack fields, and a small number of others possibly related //! to the exception that triggered the snapshot to be taken. struct __attribute__((packed, aligned(4))) MINIDUMP_MEMORY_LIST { //! \brief The number of memory regions present in the #MemoryRanges array. uint32_t NumberOfMemoryRanges; //! \brief Structures identifying each memory region present in the minidump //! file. MINIDUMP_MEMORY_DESCRIPTOR MemoryRanges[0]; }; //! \brief Contains the state of an individual system handle at the time the //! snapshot was taken. This structure is Windows-specific. //! //! \sa MINIDUMP_HANDLE_DESCRIPTOR_2 struct __attribute__((packed, aligned(4))) MINIDUMP_HANDLE_DESCRIPTOR { //! \brief The Windows `HANDLE` value. uint64_t Handle; //! \brief An RVA to a MINIDUMP_STRING structure that specifies the object //! type of the handle. This member can be zero. RVA TypeNameRva; //! \brief An RVA to a MINIDUMP_STRING structure that specifies the object //! name of the handle. This member can be zero. RVA ObjectNameRva; //! \brief The attributes for the handle, this corresponds to `OBJ_INHERIT`, //! `OBJ_CASE_INSENSITIVE`, etc. uint32_t Attributes; //! \brief The `ACCESS_MASK` for the handle. uint32_t GrantedAccess; //! \brief This is the number of open handles to the object that this handle //! refers to. uint32_t HandleCount; //! \brief This is the number kernel references to the object that this //! handle refers to. uint32_t PointerCount; }; //! \brief Contains the state of an individual system handle at the time the //! snapshot was taken. This structure is Windows-specific. //! //! \sa MINIDUMP_HANDLE_DESCRIPTOR struct __attribute__((packed, aligned(4))) MINIDUMP_HANDLE_DESCRIPTOR_2 : public MINIDUMP_HANDLE_DESCRIPTOR { //! \brief An RVA to a MINIDUMP_HANDLE_OBJECT_INFORMATION structure that //! specifies object-specific information. This member can be zero if //! there is no extra information. RVA ObjectInfoRva; //! \brief Must be zero. uint32_t Reserved0; }; //! \brief Represents the header for a handle data stream. //! //! A list of MINIDUMP_HANDLE_DESCRIPTOR or MINIDUMP_HANDLE_DESCRIPTOR_2 //! structures will immediately follow in the stream. struct __attribute((packed, aligned(4))) MINIDUMP_HANDLE_DATA_STREAM { //! \brief The size of the header information for the stream, in bytes. This //! value is `sizeof(MINIDUMP_HANDLE_DATA_STREAM)`. uint32_t SizeOfHeader; //! \brief The size of a descriptor in the stream, in bytes. This value is //! `sizeof(MINIDUMP_HANDLE_DESCRIPTOR)` or //! `sizeof(MINIDUMP_HANDLE_DESCRIPTOR_2)`. uint32_t SizeOfDescriptor; //! \brief The number of descriptors in the stream. uint32_t NumberOfDescriptors; //! \brief Must be zero. uint32_t Reserved; }; //! \brief Information about a specific module that was recorded as being //! unloaded at the time the snapshot was taken. //! //! An unloaded module may be a shared library or a loadable module. //! //! \sa MINIDUMP_UNLOADED_MODULE_LIST struct __attribute__((packed, aligned(4))) MINIDUMP_UNLOADED_MODULE { //! \brief The base address where the module was loaded in the address space //! of the process that the minidump file contains a snapshot of. uint64_t BaseOfImage; //! \brief The size of the unloaded module. uint32_t SizeOfImage; //! \brief The module’s checksum, or `0` if unknown. //! //! On Windows, this field comes from the `CheckSum` field of the module’s //! `IMAGE_OPTIONAL_HEADER` structure, if present. It reflects the checksum at //! the time the module was linked. uint32_t CheckSum; //! \brief The module’s timestamp, in `time_t` units, seconds since the POSIX //! epoch, or `0` if unknown. //! //! On Windows, this field comes from the `TimeDateStamp` field of the //! module’s `IMAGE_FILE_HEADER` structure. It reflects the timestamp at the //! time the module was linked. uint32_t TimeDateStamp; //! \brief ::RVA of a MINIDUMP_STRING containing the module’s path or file //! name. RVA ModuleNameRva; }; //! \brief Information about all modules recorded as unloaded when the snapshot //! was taken. //! //! A list of MINIDUMP_UNLOADED_MODULE structures will immediately follow in the //! stream. struct __attribute__((packed, aligned(4))) MINIDUMP_UNLOADED_MODULE_LIST { //! \brief The size of the header information for the stream, in bytes. This //! value is `sizeof(MINIDUMP_UNLOADED_MODULE_LIST)`. uint32_t SizeOfHeader; //! \brief The size of a descriptor in the stream, in bytes. This value is //! `sizeof(MINIDUMP_UNLOADED_MODULE)`. uint32_t SizeOfEntry; //! \brief The number of entries in the stream. uint32_t NumberOfEntries; }; //! \brief Information about XSAVE-managed state stored within CPU-specific //! context structures. struct __attribute__((packed, aligned(4))) XSTATE_CONFIG_FEATURE_MSC_INFO { //! \brief The size of this structure, in bytes. This value is //! `sizeof(XSTATE_CONFIG_FEATURE_MSC_INFO)`. uint32_t SizeOfInfo; //! \brief The size of a CPU-specific context structure carrying all XSAVE //! state components described by this structure. //! //! Equivalent to the value returned by `InitializeContext()` in \a //! ContextLength. uint32_t ContextSize; //! \brief The XSAVE state-component bitmap, XSAVE_BV. //! //! See Intel Software Developer’s Manual, Volume 1: Basic Architecture //! (253665-060), 13.4.2 “XSAVE Header”. uint64_t EnabledFeatures; //! \brief The location of each state component within a CPU-specific context //! structure. //! //! This array is indexed by bit position numbers used in #EnabledFeatures. XSTATE_FEATURE Features[MAXIMUM_XSTATE_FEATURES]; }; //! \anchor MINIDUMP_MISCx //! \name MINIDUMP_MISC* //! //! \brief Field validity flag values for MINIDUMP_MISC_INFO::Flags1. //! \{ //! \brief MINIDUMP_MISC_INFO::ProcessId is valid. #define MINIDUMP_MISC1_PROCESS_ID 0x00000001 //! \brief The time-related fields in MINIDUMP_MISC_INFO are valid. //! //! The following fields are valid: //! - MINIDUMP_MISC_INFO::ProcessCreateTime //! - MINIDUMP_MISC_INFO::ProcessUserTime //! - MINIDUMP_MISC_INFO::ProcessKernelTime #define MINIDUMP_MISC1_PROCESS_TIMES 0x00000002 //! \brief The CPU-related fields in MINIDUMP_MISC_INFO_2 are valid. //! //! The following fields are valid: //! - MINIDUMP_MISC_INFO_2::ProcessorMaxMhz //! - MINIDUMP_MISC_INFO_2::ProcessorCurrentMhz //! - MINIDUMP_MISC_INFO_2::ProcessorMhzLimit //! - MINIDUMP_MISC_INFO_2::ProcessorMaxIdleState //! - MINIDUMP_MISC_INFO_2::ProcessorCurrentIdleState //! //! \note This macro should likely have been named //! MINIDUMP_MISC2_PROCESSOR_POWER_INFO. #define MINIDUMP_MISC1_PROCESSOR_POWER_INFO 0x00000004 //! \brief MINIDUMP_MISC_INFO_3::ProcessIntegrityLevel is valid. #define MINIDUMP_MISC3_PROCESS_INTEGRITY 0x00000010 //! \brief MINIDUMP_MISC_INFO_3::ProcessExecuteFlags is valid. #define MINIDUMP_MISC3_PROCESS_EXECUTE_FLAGS 0x00000020 //! \brief The time zone-related fields in MINIDUMP_MISC_INFO_3 are valid. //! //! The following fields are valid: //! - MINIDUMP_MISC_INFO_3::TimeZoneId //! - MINIDUMP_MISC_INFO_3::TimeZone #define MINIDUMP_MISC3_TIMEZONE 0x00000040 //! \brief MINIDUMP_MISC_INFO_3::ProtectedProcess is valid. #define MINIDUMP_MISC3_PROTECTED_PROCESS 0x00000080 //! \brief The build string-related fields in MINIDUMP_MISC_INFO_4 are valid. //! //! The following fields are valid: //! - MINIDUMP_MISC_INFO_4::BuildString //! - MINIDUMP_MISC_INFO_4::DbgBldStr #define MINIDUMP_MISC4_BUILDSTRING 0x00000100 //! \brief MINIDUMP_MISC_INFO_5::ProcessCookie is valid. #define MINIDUMP_MISC5_PROCESS_COOKIE 0x00000200 //! \} //! \brief Information about the process that the minidump file contains a //! snapshot of, as well as the system that hosted that process. //! //! \sa \ref MINIDUMP_MISCx "MINIDUMP_MISC*" //! \sa MINIDUMP_MISC_INFO_2 //! \sa MINIDUMP_MISC_INFO_3 //! \sa MINIDUMP_MISC_INFO_4 //! \sa MINIDUMP_MISC_INFO_5 //! \sa MINIDUMP_MISC_INFO_N struct __attribute__((packed, aligned(4))) MINIDUMP_MISC_INFO { //! \brief The size of the structure. //! //! This field can be used to distinguish between different versions of this //! structure: MINIDUMP_MISC_INFO, MINIDUMP_MISC_INFO_2, MINIDUMP_MISC_INFO_3, //! and MINIDUMP_MISC_INFO_4. //! //! \sa Flags1 uint32_t SizeOfInfo; //! \brief A bit field of \ref MINIDUMP_MISCx "MINIDUMP_MISC*" values //! indicating which fields of this structure contain valid data. uint32_t Flags1; //! \brief The process ID of the process. uint32_t ProcessId; //! \brief The time that the process started, in `time_t` units, seconds since //! the POSIX epoch. uint32_t ProcessCreateTime; //! \brief The amount of user-mode CPU time used by the process, in seconds, //! at the time of the snapshot. uint32_t ProcessUserTime; //! \brief The amount of system-mode (kernel) CPU time used by the process, in //! seconds, at the time of the snapshot. uint32_t ProcessKernelTime; }; //! \brief Information about the process that the minidump file contains a //! snapshot of, as well as the system that hosted that process. //! //! This structure variant is used on Windows Vista (NT 6.0) and later. //! //! \sa \ref MINIDUMP_MISCx "MINIDUMP_MISC*" //! \sa MINIDUMP_MISC_INFO //! \sa MINIDUMP_MISC_INFO_3 //! \sa MINIDUMP_MISC_INFO_4 //! \sa MINIDUMP_MISC_INFO_5 //! \sa MINIDUMP_MISC_INFO_N struct __attribute__((packed, aligned(4))) MINIDUMP_MISC_INFO_2 : public MINIDUMP_MISC_INFO { //! \brief The maximum clock rate of the system’s CPU or CPUs, in MHz. uint32_t ProcessorMaxMhz; //! \brief The clock rate of the system’s CPU or CPUs, in MHz, at the time of //! the snapshot. uint32_t ProcessorCurrentMhz; //! \brief The maximum clock rate of the system’s CPU or CPUs, in MHz, reduced //! by any thermal limitations, at the time of the snapshot. uint32_t ProcessorMhzLimit; //! \brief The maximum idle state of the system’s CPU or CPUs. uint32_t ProcessorMaxIdleState; //! \brief The idle state of the system’s CPU or CPUs at the time of the //! snapshot. uint32_t ProcessorCurrentIdleState; }; //! \brief Information about the process that the minidump file contains a //! snapshot of, as well as the system that hosted that process. //! //! This structure variant is used on Windows 7 (NT 6.1) and later. //! //! \sa \ref MINIDUMP_MISCx "MINIDUMP_MISC*" //! \sa MINIDUMP_MISC_INFO //! \sa MINIDUMP_MISC_INFO_2 //! \sa MINIDUMP_MISC_INFO_4 //! \sa MINIDUMP_MISC_INFO_5 //! \sa MINIDUMP_MISC_INFO_N struct __attribute__((packed, aligned(4))) MINIDUMP_MISC_INFO_3 : public MINIDUMP_MISC_INFO_2 { //! \brief The process’ integrity level. //! //! Windows typically uses `SECURITY_MANDATORY_MEDIUM_RID` (0x2000) for //! processes belonging to normal authenticated users and //! `SECURITY_MANDATORY_HIGH_RID` (0x3000) for elevated processes. //! //! This field is Windows-specific, and has no meaning on other operating //! systems. uint32_t ProcessIntegrityLevel; //! \brief The process’ execute flags. //! //! On Windows, this appears to be returned by `NtQueryInformationProcess()` //! with an argument of `ProcessExecuteFlags` (34). //! //! This field is Windows-specific, and has no meaning on other operating //! systems. uint32_t ProcessExecuteFlags; //! \brief Whether the process is protected. //! //! This field is Windows-specific, and has no meaning on other operating //! systems. uint32_t ProtectedProcess; //! \brief Whether daylight saving time was being observed in the system’s //! location at the time of the snapshot. //! //! This field can contain the following values: //! - `0` if the location does not observe daylight saving time at all. The //! TIME_ZONE_INFORMATION::StandardName field of #TimeZoneId contains the //! time zone name. //! - `1` if the location observes daylight saving time, but standard time //! was in effect at the time of the snapshot. The //! TIME_ZONE_INFORMATION::StandardName field of #TimeZoneId contains the //! time zone name. //! - `2` if the location observes daylight saving time, and it was in effect //! at the time of the snapshot. The TIME_ZONE_INFORMATION::DaylightName //! field of #TimeZoneId contains the time zone name. //! //! \sa #TimeZone uint32_t TimeZoneId; //! \brief Information about the time zone at the system’s location. //! //! \sa #TimeZoneId TIME_ZONE_INFORMATION TimeZone; }; //! \brief Information about the process that the minidump file contains a //! snapshot of, as well as the system that hosted that process. //! //! This structure variant is used on Windows 8 (NT 6.2) and later. //! //! \sa \ref MINIDUMP_MISCx "MINIDUMP_MISC*" //! \sa MINIDUMP_MISC_INFO //! \sa MINIDUMP_MISC_INFO_2 //! \sa MINIDUMP_MISC_INFO_3 //! \sa MINIDUMP_MISC_INFO_5 //! \sa MINIDUMP_MISC_INFO_N struct __attribute__((packed, aligned(4))) MINIDUMP_MISC_INFO_4 : public MINIDUMP_MISC_INFO_3 { //! \brief The operating system’s “build string”, a string identifying a //! specific build of the operating system. //! //! This string is UTF-16-encoded and terminated by a UTF-16 `NUL` code unit. //! //! On Windows 8.1 (NT 6.3), this is “6.3.9600.17031 //! (winblue_gdr.140221-1952)”. char16_t BuildString[260]; //! \brief The minidump producer’s “build string”, a string identifying the //! module that produced a minidump file. //! //! This string is UTF-16-encoded and terminated by a UTF-16 `NUL` code unit. //! //! On Windows 8.1 (NT 6.3), this may be “dbghelp.i386,6.3.9600.16520” or //! “dbghelp.amd64,6.3.9600.16520” depending on CPU architecture. char16_t DbgBldStr[40]; }; //! \brief Information about the process that the minidump file contains a //! snapshot of, as well as the system that hosted that process. //! //! This structure variant is used on Windows 10 and later. //! //! \sa \ref MINIDUMP_MISCx "MINIDUMP_MISC*" //! \sa MINIDUMP_MISC_INFO //! \sa MINIDUMP_MISC_INFO_2 //! \sa MINIDUMP_MISC_INFO_3 //! \sa MINIDUMP_MISC_INFO_4 //! \sa MINIDUMP_MISC_INFO_N struct __attribute__((packed, aligned(4))) MINIDUMP_MISC_INFO_5 : public MINIDUMP_MISC_INFO_4 { //! \brief Information about XSAVE-managed state stored within CPU-specific //! context structures. //! //! This information can be used to locate state components within //! CPU-specific context structures. XSTATE_CONFIG_FEATURE_MSC_INFO XStateData; uint32_t ProcessCookie; }; //! \brief The latest known version of the MINIDUMP_MISC_INFO structure. typedef MINIDUMP_MISC_INFO_5 MINIDUMP_MISC_INFO_N; //! \brief Describes a region of memory. struct __attribute__((packed, aligned(4))) MINIDUMP_MEMORY_INFO { //! \brief The base address of the region of pages. uint64_t BaseAddress; //! \brief The base address of a range of pages in this region. The page is //! contained within this memory region. uint64_t AllocationBase; //! \brief The memory protection when the region was initially allocated. This //! member can be one of the memory protection options (such as //! \ref PAGE_x "PAGE_EXECUTE", \ref PAGE_x "PAGE_NOACCESS", etc.), along //! with \ref PAGE_x "PAGE_GUARD" or \ref PAGE_x "PAGE_NOCACHE", as //! needed. uint32_t AllocationProtect; uint32_t __alignment1; //! \brief The size of the region beginning at the base address in which all //! pages have identical attributes, in bytes. uint64_t RegionSize; //! \brief The state of the pages in the region. This can be one of //! \ref MEM_x "MEM_COMMIT", \ref MEM_x "MEM_FREE", or \ref MEM_x //! "MEM_RESERVE". uint32_t State; //! \brief The access protection of the pages in the region. This member is //! one of the values listed for the #AllocationProtect member. uint32_t Protect; //! \brief The type of pages in the region. This can be one of \ref MEM_x //! "MEM_IMAGE", \ref MEM_x "MEM_MAPPED", or \ref MEM_x "MEM_PRIVATE". uint32_t Type; uint32_t __alignment2; }; //! \brief Contains a list of memory regions. struct __attribute__((packed, aligned(4))) MINIDUMP_MEMORY_INFO_LIST { //! \brief The size of the header data for the stream, in bytes. This is //! generally sizeof(MINIDUMP_MEMORY_INFO_LIST). uint32_t SizeOfHeader; //! \brief The size of each entry following the header, in bytes. This is //! generally sizeof(MINIDUMP_MEMORY_INFO). uint32_t SizeOfEntry; //! \brief The number of entries in the stream. These are generally //! MINIDUMP_MEMORY_INFO structures. The entries follow the header. uint64_t NumberOfEntries; }; //! \brief Contains the name of the thread with the given thread ID. struct __attribute__((packed, aligned(4))) MINIDUMP_THREAD_NAME { //! \brief The identifier of the thread. uint32_t ThreadId; //! \brief RVA64 of a MINIDUMP_STRING containing the name of the thread. RVA64 RvaOfThreadName; }; //! \brief Variable-sized struct which contains a list of MINIDUMP_THREAD_NAME //! structs. struct __attribute__((packed, aligned(4))) MINIDUMP_THREAD_NAME_LIST { //! \brief The number of MINIDUMP_THREAD_NAME structs following this field. uint32_t NumberOfThreadNames; //! \brief A variably-sized array containing zero or more //! MINIDUMP_THREAD_NAME structs. The length of the array is indicated by //! the NumberOfThreadNames field in this struct. struct MINIDUMP_THREAD_NAME ThreadNames[0]; }; //! \brief Minidump file type values for MINIDUMP_HEADER::Flags. These bits //! describe the types of data carried within a minidump file. enum MINIDUMP_TYPE { //! \brief A minidump file without any additional data. //! //! This type of minidump file contains: //! - A MINIDUMP_SYSTEM_INFO stream. //! - A MINIDUMP_MISC_INFO, MINIDUMP_MISC_INFO_2, MINIDUMP_MISC_INFO_3, or //! MINIDUMP_MISC_INFO_4 stream, depending on which fields are present. //! - A MINIDUMP_THREAD_LIST stream. All threads are present, along with a //! snapshot of each thread’s stack memory sufficient to obtain backtraces. //! - If the minidump file was generated as a result of an exception, a //! MINIDUMP_EXCEPTION_STREAM describing the exception. //! - A MINIDUMP_MODULE_LIST stream. All loaded modules are present. //! - Typically, a MINIDUMP_MEMORY_LIST stream containing duplicate pointers //! to the stack memory regions also referenced by the MINIDUMP_THREAD_LIST //! stream. This type of minidump file also includes a //! MINIDUMP_MEMORY_DESCRIPTOR containing the 256 bytes centered around //! the exception address or the instruction pointer. MiniDumpNormal = 0x00000000, //! \brief A minidump with extended contexts. //! //! Contains Normal plus a MISC_INFO_5 structure describing the contexts. MiniDumpWithAvxXStateContext = 0x00200000, }; #endif // CRASHPAD_COMPAT_NON_WIN_DBGHELP_H_ ================================================ FILE: compat/non_win/minwinbase.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_NON_WIN_MINWINBASE_H_ #define CRASHPAD_COMPAT_NON_WIN_MINWINBASE_H_ #include //! \brief Represents a date and time. struct SYSTEMTIME { //! \brief The year, represented fully. //! //! The year 2014 would be represented in this field as `2014`. uint16_t wYear; //! \brief The month of the year, `1` for January and `12` for December. uint16_t wMonth; //! \brief The day of the week, `0` for Sunday and `6` for Saturday. uint16_t wDayOfWeek; //! \brief The day of the month, `1` through `31`. uint16_t wDay; //! \brief The hour of the day, `0` through `23`. uint16_t wHour; //! \brief The minute of the hour, `0` through `59`. uint16_t wMinute; //! \brief The second of the minute, `0` through `60`. uint16_t wSecond; //! \brief The millisecond of the second, `0` through `999`. uint16_t wMilliseconds; }; #endif // CRASHPAD_COMPAT_NON_WIN_MINWINBASE_H_ ================================================ FILE: compat/non_win/timezoneapi.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_NON_WIN_TIMEZONEAPI_H_ #define CRASHPAD_COMPAT_NON_WIN_TIMEZONEAPI_H_ #include #include "compat/non_win/minwinbase.h" //! \brief Information about a time zone and its daylight saving rules. struct TIME_ZONE_INFORMATION { //! \brief The number of minutes west of UTC. int32_t Bias; //! \brief The UTF-16-encoded name of the time zone when observing standard //! time. char16_t StandardName[32]; //! \brief The date and time to switch from daylight saving time to standard //! time. //! //! This can be a specific time, or with SYSTEMTIME::wYear set to `0`, it can //! reflect an annual recurring transition. In that case, SYSTEMTIME::wDay in //! the range `1` to `5` is interpreted as the given occurrence of //! SYSTEMTIME::wDayOfWeek within the month, `1` being the first occurrence //! and `5` being the last (even if there are fewer than 5). SYSTEMTIME StandardDate; //! \brief The bias relative to #Bias to be applied when observing standard //! time. int32_t StandardBias; //! \brief The UTF-16-encoded name of the time zone when observing daylight //! saving time. char16_t DaylightName[32]; //! \brief The date and time to switch from standard time to daylight saving //! time. //! //! This field is specified in the same manner as #StandardDate. SYSTEMTIME DaylightDate; //! \brief The bias relative to #Bias to be applied when observing daylight //! saving time. int32_t DaylightBias; }; #endif // CRASHPAD_COMPAT_NON_WIN_TIMEZONEAPI_H_ ================================================ FILE: compat/non_win/verrsrc.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_NON_WIN_VERRSRC_H_ #define CRASHPAD_COMPAT_NON_WIN_VERRSRC_H_ #include //! \file //! \brief The magic number for a VS_FIXEDFILEINFO structure, stored in //! VS_FIXEDFILEINFO::dwSignature. #define VS_FFI_SIGNATURE 0xfeef04bd //! \brief The version of a VS_FIXEDFILEINFO structure, stored in //! VS_FIXEDFILEINFO::dwStrucVersion. #define VS_FFI_STRUCVERSION 0x00010000 //! \anchor VS_FF_x //! \name VS_FF_* //! //! \brief File attribute values for VS_FIXEDFILEINFO::dwFileFlags and //! VS_FIXEDFILEINFO::dwFileFlagsMask. //! \{ #define VS_FF_DEBUG 0x00000001 #define VS_FF_PRERELEASE 0x00000002 #define VS_FF_PATCHED 0x00000004 #define VS_FF_PRIVATEBUILD 0x00000008 #define VS_FF_INFOINFERRED 0x00000010 #define VS_FF_SPECIALBUILD 0x00000020 //! \} //! \anchor VOS_x //! \name VOS_* //! //! \brief Operating system values for VS_FIXEDFILEINFO::dwFileOS. //! \{ #define VOS_UNKNOWN 0x00000000 #define VOS_DOS 0x00010000 #define VOS_OS216 0x00020000 #define VOS_OS232 0x00030000 #define VOS_NT 0x00040000 #define VOS_WINCE 0x00050000 #define VOS__BASE 0x00000000 #define VOS__WINDOWS16 0x00000001 #define VOS__PM16 0x00000002 #define VOS__PM32 0x00000003 #define VOS__WINDOWS32 0x00000004 #define VOS_DOS_WINDOWS16 0x00010001 #define VOS_DOS_WINDOWS32 0x00010004 #define VOS_OS216_PM16 0x00020002 #define VOS_OS232_PM32 0x00030003 #define VOS_NT_WINDOWS32 0x00040004 //! \} //! \anchor VFT_x //! \name VFT_* //! //! \brief File type values for VS_FIXEDFILEINFO::dwFileType. //! \{ #define VFT_UNKNOWN 0x00000000 #define VFT_APP 0x00000001 #define VFT_DLL 0x00000002 #define VFT_DRV 0x00000003 #define VFT_FONT 0x00000004 #define VFT_VXD 0x00000005 #define VFT_STATIC_LIB 0x00000007 //! \} //! \anchor VFT2_x //! \name VFT2_* //! //! \brief File subtype values for VS_FIXEDFILEINFO::dwFileSubtype. //! \{ #define VFT2_UNKNOWN 0x00000000 #define VFT2_DRV_PRINTER 0x00000001 #define VFT2_DRV_KEYBOARD 0x00000002 #define VFT2_DRV_LANGUAGE 0x00000003 #define VFT2_DRV_DISPLAY 0x00000004 #define VFT2_DRV_MOUSE 0x00000005 #define VFT2_DRV_NETWORK 0x00000006 #define VFT2_DRV_SYSTEM 0x00000007 #define VFT2_DRV_INSTALLABLE 0x00000008 #define VFT2_DRV_SOUND 0x00000009 #define VFT2_DRV_COMM 0x0000000A #define VFT2_DRV_INPUTMETHOD 0x0000000B #define VFT2_DRV_VERSIONED_PRINTER 0x0000000C #define VFT2_FONT_RASTER 0x00000001 #define VFT2_FONT_VECTOR 0x00000002 #define VFT2_FONT_TRUETYPE 0x00000003 //! \} //! \brief Version information for a file. //! //! On Windows, this information is derived from a file’s version information //! resource, and is obtained by calling `VerQueryValue()` with an `lpSubBlock` //! argument of `"\"` (a single backslash). struct VS_FIXEDFILEINFO { //! \brief The structure’s magic number, ::VS_FFI_SIGNATURE. uint32_t dwSignature; //! \brief The structure’s version, ::VS_FFI_STRUCVERSION. uint32_t dwStrucVersion; //! \brief The more-significant portion of the file’s version number. //! //! This field contains the first two components of a four-component version //! number. For a file whose version is 1.2.3.4, this field would be //! `0x00010002`. //! //! \sa dwFileVersionLS uint32_t dwFileVersionMS; //! \brief The less-significant portion of the file’s version number. //! //! This field contains the last two components of a four-component version //! number. For a file whose version is 1.2.3.4, this field would be //! `0x00030004`. //! //! \sa dwFileVersionMS uint32_t dwFileVersionLS; //! \brief The more-significant portion of the product’s version number. //! //! This field contains the first two components of a four-component version //! number. For a product whose version is 1.2.3.4, this field would be //! `0x00010002`. //! //! \sa dwProductVersionLS uint32_t dwProductVersionMS; //! \brief The less-significant portion of the product’s version number. //! //! This field contains the last two components of a four-component version //! number. For a product whose version is 1.2.3.4, this field would be //! `0x00030004`. //! //! \sa dwProductVersionMS uint32_t dwProductVersionLS; //! \brief A bitmask of \ref VS_FF_x "VS_FF_*" values indicating which bits in //! #dwFileFlags are valid. uint32_t dwFileFlagsMask; //! \brief A bitmask of \ref VS_FF_x "VS_FF_*" values identifying attributes //! of the file. Only bits present in #dwFileFlagsMask are valid. uint32_t dwFileFlags; //! \brief The file’s intended operating system, a value of \ref VOS_x //! "VOS_*". uint32_t dwFileOS; //! \brief The file’s type, a value of \ref VFT_x "VFT_*". uint32_t dwFileType; //! \brief The file’s subtype, a value of \ref VFT2_x "VFT2_*" corresponding //! to its #dwFileType, if the file type has subtypes. uint32_t dwFileSubtype; //! \brief The more-significant portion of the file’s creation date. //! //! The intended encoding of this field is unknown. This field is unused and //! always has the value `0`. uint32_t dwFileDateMS; //! \brief The less-significant portion of the file’s creation date. //! //! The intended encoding of this field is unknown. This field is unused and //! always has the value `0`. uint32_t dwFileDateLS; }; #endif // CRASHPAD_COMPAT_NON_WIN_VERRSRC_H_ ================================================ FILE: compat/non_win/windows.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // dbghelp.h on Windows requires inclusion of windows.h before it. To avoid // cluttering all inclusions of dbghelp.h with #ifdefs, always include windows.h // and have an empty one on non-Windows platforms. ================================================ FILE: compat/non_win/winnt.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_NON_WIN_WINNT_H_ #define CRASHPAD_COMPAT_NON_WIN_WINNT_H_ #include //! \file //! \anchor VER_SUITE_x //! \name VER_SUITE_* //! //! \brief Installable product values for MINIDUMP_SYSTEM_INFO::SuiteMask. //! \{ #define VER_SUITE_SMALLBUSINESS 0x0001 #define VER_SUITE_ENTERPRISE 0x0002 #define VER_SUITE_BACKOFFICE 0x0004 #define VER_SUITE_COMMUNICATIONS 0x0008 #define VER_SUITE_TERMINAL 0x0010 #define VER_SUITE_SMALLBUSINESS_RESTRICTED 0x0020 #define VER_SUITE_EMBEDDEDNT 0x0040 #define VER_SUITE_DATACENTER 0x0080 #define VER_SUITE_SINGLEUSERTS 0x0100 #define VER_SUITE_PERSONAL 0x0200 #define VER_SUITE_BLADE 0x0400 #define VER_SUITE_EMBEDDED_RESTRICTED 0x0800 #define VER_SUITE_SECURITY_APPLIANCE 0x1000 #define VER_SUITE_STORAGE_SERVER 0x2000 #define VER_SUITE_COMPUTE_SERVER 0x4000 #define VER_SUITE_WH_SERVER 0x8000 //! \} //! \brief The maximum number of exception parameters present in the //! MINIDUMP_EXCEPTION::ExceptionInformation array. #define EXCEPTION_MAXIMUM_PARAMETERS 15 //! \anchor PROCESSOR_ARCHITECTURE_x //! \name PROCESSOR_ARCHITECTURE_* //! //! \brief CPU type values for MINIDUMP_SYSTEM_INFO::ProcessorArchitecture. //! //! \sa crashpad::MinidumpCPUArchitecture //! \{ #define PROCESSOR_ARCHITECTURE_INTEL 0 #define PROCESSOR_ARCHITECTURE_MIPS 1 #define PROCESSOR_ARCHITECTURE_ALPHA 2 #define PROCESSOR_ARCHITECTURE_PPC 3 #define PROCESSOR_ARCHITECTURE_SHX 4 #define PROCESSOR_ARCHITECTURE_ARM 5 #define PROCESSOR_ARCHITECTURE_IA64 6 #define PROCESSOR_ARCHITECTURE_ALPHA64 7 #define PROCESSOR_ARCHITECTURE_MSIL 8 #define PROCESSOR_ARCHITECTURE_AMD64 9 #define PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 10 #define PROCESSOR_ARCHITECTURE_NEUTRAL 11 #define PROCESSOR_ARCHITECTURE_ARM64 12 #define PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 13 #define PROCESSOR_ARCHITECTURE_UNKNOWN 0xffff //! \} //! \anchor PF_x //! \name PF_* //! //! \brief CPU feature values for \ref CPU_INFORMATION::ProcessorFeatures //! "CPU_INFORMATION::OtherCpuInfo::ProcessorFeatures". //! //! \{ #define PF_FLOATING_POINT_PRECISION_ERRATA 0 #define PF_FLOATING_POINT_EMULATED 1 #define PF_COMPARE_EXCHANGE_DOUBLE 2 #define PF_MMX_INSTRUCTIONS_AVAILABLE 3 #define PF_PPC_MOVEMEM_64BIT_OK 4 #define PF_ALPHA_BYTE_INSTRUCTIONS 5 #define PF_XMMI_INSTRUCTIONS_AVAILABLE 6 #define PF_3DNOW_INSTRUCTIONS_AVAILABLE 7 #define PF_RDTSC_INSTRUCTION_AVAILABLE 8 #define PF_PAE_ENABLED 9 #define PF_XMMI64_INSTRUCTIONS_AVAILABLE 10 #define PF_SSE_DAZ_MODE_AVAILABLE 11 #define PF_NX_ENABLED 12 #define PF_SSE3_INSTRUCTIONS_AVAILABLE 13 #define PF_COMPARE_EXCHANGE128 14 #define PF_COMPARE64_EXCHANGE128 15 #define PF_CHANNELS_ENABLED 16 #define PF_XSAVE_ENABLED 17 #define PF_ARM_VFP_32_REGISTERS_AVAILABLE 18 #define PF_ARM_NEON_INSTRUCTIONS_AVAILABLE 19 #define PF_SECOND_LEVEL_ADDRESS_TRANSLATION 20 #define PF_VIRT_FIRMWARE_ENABLED 21 #define PF_RDWRFSGSBASE_AVAILABLE 22 #define PF_FASTFAIL_AVAILABLE 23 #define PF_ARM_DIVIDE_INSTRUCTION_AVAILABLE 24 #define PF_ARM_64BIT_LOADSTORE_ATOMIC 25 #define PF_ARM_EXTERNAL_CACHE_AVAILABLE 26 #define PF_ARM_FMAC_INSTRUCTIONS_AVAILABLE 27 #define PF_RDRAND_INSTRUCTION_AVAILABLE 28 #define PF_ARM_V8_INSTRUCTIONS_AVAILABLE 29 #define PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE 30 #define PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE 31 #define PF_RDTSCP_INSTRUCTION_AVAILABLE 32 //! \} //! \anchor PAGE_x //! \name PAGE_* //! //! \brief Memory protection constants for MINIDUMP_MEMORY_INFO::Protect and //! MINIDUMP_MEMORY_INFO::AllocationProtect. //! \{ #define PAGE_NOACCESS 0x1 #define PAGE_READONLY 0x2 #define PAGE_READWRITE 0x4 #define PAGE_WRITECOPY 0x8 #define PAGE_EXECUTE 0x10 #define PAGE_EXECUTE_READ 0x20 #define PAGE_EXECUTE_READWRITE 0x40 #define PAGE_EXECUTE_WRITECOPY 0x80 #define PAGE_GUARD 0x100 #define PAGE_NOCACHE 0x200 #define PAGE_WRITECOMBINE 0x400 //! \} //! \anchor MEM_x //! \name MEM_* //! //! \brief Memory state and type constants for MINIDUMP_MEMORY_INFO::State and //! MINIDUMP_MEMORY_INFO::Type. //! \{ #define MEM_COMMIT 0x1000 #define MEM_RESERVE 0x2000 #define MEM_DECOMMIT 0x4000 #define MEM_RELEASE 0x8000 #define MEM_FREE 0x10000 #define MEM_PRIVATE 0x20000 #define MEM_MAPPED 0x40000 #define MEM_RESET 0x80000 //! \} //! \brief The maximum number of distinct identifiable features that could //! possibly be carried in an XSAVE area. //! //! This corresponds to the number of bits in the XSAVE state-component bitmap, //! XSAVE_BV. See Intel Software Developer’s Manual, Volume 1: Basic //! Architecture (253665-060), 13.4.2 “XSAVE Header”. #define MAXIMUM_XSTATE_FEATURES (64) //! \anchor XSTATE_x //! \name XSTATE_* //! //! \brief Offsets and constants for extended state. //! \{ #define XSTATE_COMPACTION_ENABLE (63) #define XSTATE_COMPACTION_ENABLE_MASK (1ull << XSTATE_COMPACTION_ENABLE) #define XSTATE_CET_U (11) #define XSTATE_MASK_CET_U (1ull << XSTATE_CET_U) //! \} //! \brief The location of a single state component within an XSAVE area. struct XSTATE_FEATURE { //! \brief The location of a state component within a CPU-specific context //! structure. //! //! This is equivalent to the difference (`ptrdiff_t`) between the return //! value of `LocateXStateFeature()` and its \a Context argument. uint32_t Offset; //! \brief The size of a state component with a CPU-specific context //! structure. //! //! This is equivalent to the size returned by `LocateXStateFeature()` in \a //! Length. uint32_t Size; }; //! \anchor IMAGE_DEBUG_MISC_x //! \name IMAGE_DEBUG_MISC_* //! //! Data type values for IMAGE_DEBUG_MISC::DataType. //! \{ //! \brief A pointer to a `.dbg` file. //! //! IMAGE_DEBUG_MISC::Data will contain the path or file name of the `.dbg` file //! associated with the module. #define IMAGE_DEBUG_MISC_EXENAME 1 //! \} //! \brief Miscellaneous debugging record. //! //! This structure is referenced by MINIDUMP_MODULE::MiscRecord. It is obsolete, //! superseded by the CodeView record. struct IMAGE_DEBUG_MISC { //! \brief The type of data carried in the #Data field. //! //! This is a value of \ref IMAGE_DEBUG_MISC_x "IMAGE_DEBUG_MISC_*". uint32_t DataType; //! \brief The length of this structure in bytes, including the entire #Data //! field and its `NUL` terminator. //! //! \note The Windows documentation states that this field is rounded up to //! nearest nearest 4-byte multiple. uint32_t Length; //! \brief The encoding of the #Data field. //! //! If this field is `0`, #Data contains narrow or multibyte character data. //! If this field is `1`, #Data is UTF-16-encoded. //! //! On Windows, with this field set to `0`, #Data will be encoded in the code //! page of the system that linked the module. On other operating systems, //! UTF-8 may be used. uint8_t Unicode; uint8_t Reserved[3]; //! \brief The data carried within this structure. //! //! For string data, this field will be `NUL`-terminated. If #Unicode is `1`, //! this field is UTF-16-encoded, and will be terminated by a UTF-16 `NUL` //! code unit (two `NUL` bytes). uint8_t Data[1]; }; //! \anchor VER_NT_x //! \name VER_NT_* //! //! \brief Operating system type values for MINIDUMP_SYSTEM_INFO::ProductType. //! //! \sa crashpad::MinidumpOSType //! \{ #define VER_NT_WORKSTATION 1 #define VER_NT_DOMAIN_CONTROLLER 2 #define VER_NT_SERVER 3 //! \} //! \anchor VER_PLATFORM_x //! \name VER_PLATFORM_* //! //! \brief Operating system family values for MINIDUMP_SYSTEM_INFO::PlatformId. //! //! \sa crashpad::MinidumpOS //! \{ #define VER_PLATFORM_WIN32s 0 #define VER_PLATFORM_WIN32_WINDOWS 1 #define VER_PLATFORM_WIN32_NT 2 //! \} #endif // CRASHPAD_COMPAT_NON_WIN_WINNT_H_ ================================================ FILE: compat/win/getopt.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_WIN_GETOPT_H_ #define CRASHPAD_COMPAT_WIN_GETOPT_H_ #include "third_party/getopt/getopt.h" #endif // CRASHPAD_COMPAT_WIN_GETOPT_H_ ================================================ FILE: compat/win/strings.cc ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include extern "C" { int strcasecmp(const char* s1, const char* s2) { return _stricmp(s1, s2); } } // extern "C" ================================================ FILE: compat/win/strings.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_WIN_STRINGS_H_ #define CRASHPAD_COMPAT_WIN_STRINGS_H_ #ifdef __cplusplus extern "C" { #endif int strcasecmp(const char* s1, const char* s2); #ifdef __cplusplus } // extern "C" #endif #endif // CRASHPAD_COMPAT_WIN_STRINGS_H_ ================================================ FILE: compat/win/sys/time.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_WIN_SYS_TIME_H_ #define CRASHPAD_COMPAT_WIN_SYS_TIME_H_ #include #endif // CRASHPAD_COMPAT_WIN_SYS_TIME_H_ ================================================ FILE: compat/win/sys/types.h ================================================ // Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_WIN_SYS_TYPES_H_ #define CRASHPAD_COMPAT_WIN_SYS_TYPES_H_ // This is intended to be roughly equivalent to #include_next. #include <../ucrt/sys/types.h> #include #endif // CRASHPAD_COMPAT_WIN_SYS_TYPES_H_ ================================================ FILE: compat/win/time.cc ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include extern "C" { struct tm* gmtime_r(const time_t* timep, struct tm* result) { if (gmtime_s(result, timep) != 0) return nullptr; return result; } struct tm* localtime_r(const time_t* timep, struct tm* result) { if (localtime_s(result, timep) != 0) return nullptr; return result; } const char* strptime(const char* buf, const char* format, struct tm* tm) { // TODO(scottmg): strptime implementation. return nullptr; } time_t timegm(struct tm* tm) { return _mkgmtime(tm); } } // extern "C" ================================================ FILE: compat/win/time.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_WIN_TIME_H_ #define CRASHPAD_COMPAT_WIN_TIME_H_ // This is intended to be roughly equivalent to #include_next. #include <../ucrt/time.h> #ifdef __cplusplus extern "C" { #endif struct tm* gmtime_r(const time_t* timep, struct tm* result); struct tm* localtime_r(const time_t* timep, struct tm* result); const char* strptime(const char* buf, const char* format, struct tm* tm); time_t timegm(struct tm* tm); #ifdef __cplusplus } // extern "C" #endif #endif // CRASHPAD_COMPAT_WIN_TIME_H_ ================================================ FILE: compat/win/winbase.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_WIN_WINBASE_H_ #define CRASHPAD_COMPAT_WIN_WINBASE_H_ // include_next #include <../um/winbase.h> // 10.0.15063.0 SDK #ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE #define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE (0x2) #endif #endif // CRASHPAD_COMPAT_WIN_WINBASE_H_ ================================================ FILE: compat/win/winnt.h ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_WIN_WINNT_H_ #define CRASHPAD_COMPAT_WIN_WINNT_H_ // include_next #include <../um/winnt.h> // https://msdn.microsoft.com/library/aa373184.aspx: "Note that this structure // definition was accidentally omitted from WinNT.h." struct PROCESSOR_POWER_INFORMATION { ULONG Number; ULONG MaxMhz; ULONG CurrentMhz; ULONG MhzLimit; ULONG MaxIdleState; ULONG CurrentIdleState; }; // 10.0.10240.0 SDK #ifndef PROCESSOR_ARCHITECTURE_ARM64 #define PROCESSOR_ARCHITECTURE_ARM64 12 #endif #ifndef PF_ARM_V8_INSTRUCTIONS_AVAILABLE #define PF_ARM_V8_INSTRUCTIONS_AVAILABLE 29 #endif #ifndef PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE #define PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE 30 #endif #ifndef PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE #define PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE 31 #endif #ifndef PF_RDTSCP_INSTRUCTION_AVAILABLE #define PF_RDTSCP_INSTRUCTION_AVAILABLE 32 #endif // 10.0.14393.0 SDK #ifndef PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 #define PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 13 #endif #endif // CRASHPAD_COMPAT_WIN_WINNT_H_ ================================================ FILE: compat/win/winternl.h ================================================ // Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_COMPAT_WIN_WINTERNL_H_ #define CRASHPAD_COMPAT_WIN_WINTERNL_H_ // include_next #include <../um/winternl.h> // 10.0.16299.0 SDK typedef struct _CLIENT_ID CLIENT_ID; #endif // CRASHPAD_COMPAT_WIN_WINTERNL_H_ ================================================ FILE: doc/.gitignore ================================================ /generated ================================================ FILE: doc/appengine/README ================================================ This is the App Engine app that serves https://crashpad.chromium.org/. To work on this app, obtain the following packages: - Go, from https://golang.org/dl/. This is only necessary for local development and testing. The directory containing the “go” executable, such as /usr/local/go/bin, must appear in $PATH. It does not appear critical for the Go version used to match the Go runtime version used (for example, these instructions were tested with Go 1.14 locally but a Go 1.11 runtime when deployed), but if problems are encountered, it would be wise to use the same version for both local development and AppEngine deployment. - The Google Cloud SDK (gcloud CLI) from https://cloud.google.com/sdk/docs/install-sdk. This is necessary for both local development and for AppEngine deployment. Unpacking this package produces a google-cloud-sdk directory, whose bin child directory may be added to $PATH for convenience, although this is not strictly necessary. The commands in this README are expected to be run from the directory containing it. To test locally: % go get -d ./src/crashpad-home % python3 …/google-cloud-sdk/bin/dev_appserver.py src/crashpad-home dev_appserver.py must be invoked using Python 3, but internally will use Python 2, and a Python 2 interpreter must be available in the PATH as python2. Look for the “Starting module "default" running at: http://localhost:8080” line, which tells you the URL of the local running instance of the app. Test http://localhost:8080/ to ensure that it works. It would be good to test http://localhost:8080/doxygen as well, but it may fail with HTTP status 500 and the following error returned as the HTTP response body because memcache seems to not be available in the local dev_appserver environment: service bridge HTTP failed: Post "http://appengine.googleapis.internal:10001/rpc_http": dial tcp: lookup appengine.googleapis.internal: no such host The /doxygen URL can be tested in a verison of the app that’s been deployed before traffic has been migrated to it by visiting the staged deployed version from the App Engine console. To deploy: % version=$(git rev-parse --short=12 HEAD) % [[ -n "$(git status --porcelain)" ]] && version+=-dirty % …/google-cloud-sdk/bin/gcloud app deploy \ --project=crashpad-home --version="${version}" --no-promote \ "$(pwd)/src/crashpad-home" (Note: the $(pwd) is necessary for “gcloud app deploy” to recognize that the application is in GOPATH, putting it into “GOPATH mode”. This normally happens correctly on its own even with a relative path, but will fail for relative paths when $(pwd) is a symbolic link. Using an absolute path here will save you from this frustration, freeing you up to undoubtedly experience other frustrations.) Activate a newly-deployed version by visiting the App Engine console at https://console.cloud.google.com/appengine/versions?project=crashpad-home, selecting it, and choosing “Migrate Traffic”. It is also possible to delete old versions from this page when they are no longer needed. ================================================ FILE: doc/appengine/go.mod ================================================ module src/crashpad-home go 1.21.6 require google.golang.org/appengine/v2 v2.0.5 require ( github.com/golang/protobuf v1.5.2 // indirect google.golang.org/protobuf v1.30.0 // indirect ) ================================================ FILE: doc/appengine/go.sum ================================================ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine/v2 v2.0.5 h1:4C+F3Cd3L2nWEfSmFEZDPjQvDwL8T0YCeZBysZifP3k= google.golang.org/appengine/v2 v2.0.5/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= ================================================ FILE: doc/appengine/src/crashpad-home/app.yaml ================================================ # Copyright 2015 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. runtime: go121 app_engine_apis: true handlers: - url: /.* script: auto secure: always ================================================ FILE: doc/appengine/src/crashpad-home/main.go ================================================ // Copyright 2015 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "encoding/base64" "fmt" "io" "io/ioutil" "net/http" "net/url" "path" "strings" "time" "google.golang.org/appengine/v2" "google.golang.org/appengine/v2/memcache" "google.golang.org/appengine/v2/urlfetch" ) func main() { appengine.Main() } func init() { http.HandleFunc("/", handler) } func handler(w http.ResponseWriter, r *http.Request) { const ( baseURL = "https://chromium.googlesource.com/crashpad/crashpad/+/" mainBaseURL = baseURL + "main/" generatedDocBaseURL = baseURL + "doc/doc/generated/?format=TEXT" bugBaseURL = "https://bugs.chromium.org/p/crashpad/" ) redirectMap := map[string]string{ "/": mainBaseURL + "README.md", "/bug": bugBaseURL, "/bug/": bugBaseURL, "/bug/new": bugBaseURL + "issues/entry", "/doc/developing.html": mainBaseURL + "/doc/developing.md", "/doc/status.html": mainBaseURL + "/doc/status.md", "/index.html": mainBaseURL + "README.md", "/man": mainBaseURL + "doc/man.md", "/man/": mainBaseURL + "doc/man.md", "/man/catch_exception_tool.html": mainBaseURL + "tools/mac/catch_exception_tool.md", "/man/crashpad_database_util.html": mainBaseURL + "tools/crashpad_database_util.md", "/man/crashpad_handler.html": mainBaseURL + "handler/crashpad_handler.md", "/man/exception_port_tool.html": mainBaseURL + "tools/mac/exception_port_tool.md", "/man/generate_dump.html": mainBaseURL + "tools/generate_dump.md", "/man/index.html": mainBaseURL + "doc/man.md", "/man/on_demand_service_tool.html": mainBaseURL + "tools/mac/on_demand_service_tool.md", "/man/run_with_crashpad.html": mainBaseURL + "tools/run_with_crashpad.md", } ctx := appengine.NewContext(r) client := urlfetch.Client(ctx) destinationURL, exists := redirectMap[r.URL.Path] if exists { http.Redirect(w, r, destinationURL, http.StatusFound) return } if strings.HasPrefix(r.URL.Path, "/bug/") { http.Redirect(w, r, bugBaseURL+"issues/detail?id="+r.URL.Path[5:], http.StatusFound) return } // Don’t show dotfiles. if strings.HasPrefix(path.Base(r.URL.Path), ".") { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } u, err := url.Parse(generatedDocBaseURL) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } u.Path = path.Join(u.Path, r.URL.Path) urlStr := u.String() item, err := memcache.Get(ctx, urlStr) if err == memcache.ErrCacheMiss { resp, err := client.Get(urlStr) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { w.WriteHeader(resp.StatusCode) for k, v := range w.Header() { w.Header()[k] = v } io.Copy(w, resp.Body) return } // Redirect directories to their index pages (/doc/ -> /doc/index.html). if resp.Header.Get("X-Gitiles-Object-Type") == "tree" { http.Redirect(w, r, path.Join(r.URL.Path, "/index.html"), http.StatusFound) return } decoder := base64.NewDecoder(base64.StdEncoding, resp.Body) b, err := ioutil.ReadAll(decoder) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } item = &memcache.Item{ Key: urlStr, Value: b, Expiration: 1 * time.Hour, } if err := memcache.Set(ctx, item); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } else if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", contentType(path.Base(u.Path))) fmt.Fprintf(w, "%s", item.Value) } // contentType returns the appropriate content type header for file. func contentType(file string) string { contentTypes := map[string]string{ ".css": "text/css; charset=UTF-8", ".html": "text/html; charset=UTF-8", ".ico": "image/x-icon", ".js": "text/javascript; charset=UTF-8", ".png": "image/png", ".svg": "image/svg+xml", } for suffix, typ := range contentTypes { if strings.HasSuffix(file, suffix) { return typ } } return "text/plain; charset=UTF-8" } ================================================ FILE: doc/developing.md ================================================ # Developing Crashpad ## Status [Project status](status.md) information has moved to its own page. ## Introduction Crashpad is a [Chromium project](https://www.chromium.org/Home). Most of its development practices follow Chromium’s. In order to function on its own in other projects, Crashpad uses [mini_chromium](https://chromium.googlesource.com/chromium/mini_chromium/), a small, self-contained library that provides many of Chromium’s useful low-level base routines. [mini_chromium’s README](https://chromium.googlesource.com/chromium/mini_chromium/+/main/README.md) provides more detail. ## Prerequisites To develop Crashpad, the following tools are necessary, and must be present in the `$PATH` environment variable: * Appropriate development tools. * On macOS, install [Xcode](https://developer.apple.com/xcode/). The latest version is generally recommended. * On Windows, install [Visual Studio](https://www.visualstudio.com/) with C++ support and the Windows SDK. The latest version is generally recommended. Some tests also require the CDB debugger, installed with [Debugging Tools for Windows](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/). * On Linux, obtain appropriate tools for C++ development through any appropriate means including the system’s package manager. On Debian and Debian-based distributions, the `build-essential`, `zlib1g-dev`, and any one of the `libcurl4-*-dev` packages such as `libcurl4-openssl-dev` should suffice. * Chromium’s [depot_tools](https://www.chromium.org/developers/how-tos/depottools). * [Git](https://git-scm.com/). This is provided by Xcode on macOS, by depot_tools on Windows, and through any appropriate means including the system’s package manager on Linux. * [Python](https://www.python.org/). This is provided by the operating system on macOS, by depot_tools on Windows, and through any appropriate means including the system’s package manager on Linux. ## Getting the Source Code The main source code repository is a Git repository hosted at https://chromium.googlesource.com/crashpad/crashpad. Although it is possible to check out this repository directly with `git clone`, Crashpad’s dependencies are managed by [`gclient`](https://www.chromium.org/developers/how-tos/depottools#TOC-gclient) instead of Git submodules, so to work on Crashpad, it is best to use `fetch` to get the source code. `fetch` and `gclient` are part of the [depot_tools](https://www.chromium.org/developers/how-tos/depottools). There’s no need to install them separately. ### Initial Checkout ``` $ mkdir ~/crashpad $ cd ~/crashpad $ fetch crashpad ``` `fetch crashpad` performs the initial `git clone` and `gclient sync`, establishing a fully-functional local checkout. ### Subsequent Checkouts ``` $ cd ~/crashpad/crashpad $ git pull -r $ gclient sync ``` ## Building ### Windows, Mac, Linux, Fuchsia On Windows, Mac, Linux, and Fuchsia, Crashpad uses [GN](https://gn.googlesource.com/gn) to generate [Ninja](https://ninja-build.org/) build files. For example, ``` $ cd ~/crashpad/crashpad $ gn gen out/Default $ ninja -C out/Default ``` You can then use `gn args out/Default` or edit `out/Default/args.gn` to configure the build, for example things like `is_debug=true` or `target_cpu="x86"`. GN and Ninja are part of the [depot_tools](https://www.chromium.org/developers/how-tos/depottools). There’s no need to install them separately. #### Fuchsia In order to instruct gclient to download the Fuchsia SDK, you need to add the following to `~/crashpad/.gclient`: ``` target_os=["fuchsia"] ``` If you're using this tree to develop for multiple targets, you can also add other entries to the the list (e.g. `target_os=["fuchsia", "mac"]`). #### Optional Linux Configs To pull and use Crashpad's version of clang and sysroot, make the following changes. Add the following to `~/crashpad/.gclient`. ``` "custom_vars": { "pull_linux_clang": True }, ``` Add these args to `out/Default/args.gn`. ``` clang_path = "//third_party/linux/clang/linux-amd64" target_sysroot = "//third_party/linux/sysroot" ``` ### Android This build relies on cross-compilation. It’s possible to develop Crashpad for Android on any platform that the [Android NDK (Native Development Kit)](https://developer.android.com/ndk/) runs on. If it’s not already present on your system, [download the NDK package for your system](https://developer.android.com/ndk/downloads/) and expand it to a suitable location. These instructions assume that it’s been expanded to `~/android-ndk-r21b`. Note that Chrome uses Android API level 21 for both 64-bit platforms and 32-bit platforms. See Chrome’s [`build/config/android/config.gni`](https://chromium.googlesource.com/chromium/src/+/main/build/config/android/config.gni) which sets `android32_ndk_api_level` and `android64_ndk_api_level`. Set these gn args ``` target_os = "android" android_ndk_root = ~/android-ndk-r21b android_api_level = 21 ``` ## Testing Crashpad uses [Google Test](https://github.com/google/googletest/) as its unit-testing framework, and some tests use [Google Mock](https://github.com/google/googletest/tree/master/googlemock/) as well. Its tests are currently split up into several test executables, each dedicated to testing a different component. This may change in the future. After a successful build, the test executables will be found at `out/Debug/crashpad_*_test`. ``` $ cd ~/crashpad/crashpad $ out/Debug/crashpad_minidump_test $ out/Debug/crashpad_util_test ``` A script is provided to run all of Crashpad’s tests. It accepts a single argument, a path to the directory containing the test executables. ``` $ cd ~/crashpad/crashpad $ python build/run_tests.py out/Debug ``` To run a subset of the tests, use the `--gtest_filter` flag, e.g., to run all the tests for MinidumpStringWriter: ``` $ python build/run_tests.py out/Debug --gtest_filter MinidumpStringWriter\* ``` ### Windows On Windows, `end_to_end_test.py` requires the CDB debugger, installed with [Debugging Tools for Windows](https://docs.microsoft.com/windows-hardware/drivers/debugger/). This can be installed either as part of the [Windows Driver Kit](https://docs.microsoft.com/windows-hardware/drivers/download-the-wdk) or the [Windows SDK](https://developer.microsoft.com/windows/downloads/windows-10-sdk/). If the Windows SDK has already been installed (possibly with Visual Studio) but Debugging Tools for Windows is not present, it can be installed from Add or remove programs→Windows Software Development Kit. ### Android To test on Android, [ADB (Android Debug Bridge)](https://developer.android.com/studio/command-line/adb.html) from the [Android SDK](https://developer.android.com/sdk/) must be in the `PATH`. Note that it is sufficient to install just the command-line tools from the Android SDK. The entire Android Studio IDE is not necessary to obtain ADB. When asked to test an Android build directory, `run_tests.py` will detect a single connected Android device (including an emulator). If multiple devices are connected, one may be chosen explicitly with the `ANDROID_DEVICE` environment variable. `run_tests.py` will upload test executables and data to a temporary location on the detected or selected device, run them, and clean up after itself when done. ### Fuchsia To test on Fuchsia, you need a connected device running Fuchsia. Run: ``` $ gn gen out/fuchsia --args 'target_os="fuchsia" target_cpu="x64" is_debug=true' $ ninja -C out/fuchsia $ python build/run_tests.py out/fuchsia ``` If you have multiple devices running, you will need to specify which device you want using their hostname, for instance: ``` $ ZIRCON_NODENAME=scare-brook-skip-dried python build/run_tests.py out/fuchsia ``` ## Contributing Crashpad’s contribution process is very similar to [Chromium’s contribution process](https://chromium.googlesource.com/chromium/src/+/main/docs/contributing.md). ### Code Review A code review must be conducted for every change to Crashpad’s source code. Code review is conducted on [Chromium’s Gerrit](https://chromium-review.googlesource.com/) system, and all code reviews must be sent to an appropriate reviewer, with a Cc sent to [crashpad-dev](https://groups.google.com/a/chromium.org/group/crashpad-dev). The [`codereview.settings`](https://chromium.googlesource.com/crashpad/crashpad/+/main/codereview.settings) file specifies this environment to `git-cl`. `git-cl` is part of the [depot_tools](https://www.chromium.org/developers/how-tos/depottools). There’s no need to install it separately. ``` $ cd ~/crashpad/crashpad $ git checkout -b work_branch origin/main …do some work… $ git add … $ git commit $ git cl upload ``` Uploading a patch to Gerrit does not automatically request a review. You must select a reviewer on the Gerrit review page after running `git cl upload`. This action notifies your reviewer of the code review request. If you have lost track of the review page, `git cl issue` will remind you of its URL. Alternatively, you can request review when uploading to Gerrit by using `git cl upload --send-mail`. Git branches maintain their association with Gerrit reviews, so if you need to make changes based on review feedback, you can do so on the correct Git branch, committing your changes locally with `git commit`. You can then upload a new patch set with `git cl upload` and let your reviewer know you’ve addressed the feedback. The most recently uploaded patch set on a review may be tested on a [trybot](https://chromium.googlesource.com/chromium/src/+/main/docs/infra/trybot_usage.md) by running `git cl try` or by clicking the “CQ Dry Run” button in Gerrit. These set the “Commit-Queue: +1” label. This does not mean that the patch will be committed, but the trybot and commit queue share infrastructure and a Gerrit label. The patch will be tested on trybots in a variety of configurations. Status information will be available on Gerrit. Trybot access is available to Crashpad and Chromium committers. ### Landing Changes After code review is complete and “Code-Review: +1” has been received from all reviewers, the patch can be submitted to Crashpad’s [commit queue](https://chromium.googlesource.com/chromium/src/+/main/docs/infra/cq.md) by clicking the “Submit to CQ” button in Gerrit. This sets the “Commit-Queue: +2” label, which tests the patch on trybots before landing it. Commit queue access is available to Crashpad and Chromium committers. Although the commit queue is recommended, if needed, project members can bypass the commit queue and land patches without testing by using the “Submit” button in Gerrit or by committing via `git cl land`: ``` $ cd ~/crashpad/crashpad $ git checkout work_branch $ git cl land ``` ### External Contributions Copyright holders must complete the [Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) or [Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate) as appropriate before any submission can be accepted, and must be listed in the [`AUTHORS`](https://chromium.googlesource.com/crashpad/crashpad/+/main/AUTHORS) file. Contributors may be listed in the [`CONTRIBUTORS`](https://chromium.googlesource.com/crashpad/crashpad/+/main/CONTRIBUTORS) file. ## Buildbot The [Crashpad Buildbot](https://ci.chromium.org/p/crashpad/g/main/console) performs automated builds and tests of Crashpad. Before checking out or updating the Crashpad source code, and after checking in a new change, it is prudent to check the Buildbot to ensure that “the tree is green.” ================================================ FILE: doc/ios_overview_design.md ================================================ # iOS Crashpad Overview Design [TOC] ## iOS Limitations Crashpad on other platforms captures exceptions out-of-process. The iOS sandbox, however, restricts applications from delegating work to separate processes. This limitation means Crashpad on iOS must combine the work of the handler and the client into the same process as the main application. ## The Crashpad In-Process Handler In-process handling comes with a number of limitations and difficulties. It is not possible to catch the specific Mach exception `EXC_CRASH`, so certain groups of crashes cannot be captured. This includes some major ones, like out-of-memory crashes. This also introduces difficulties in capturing all the relevant crash data and writing the minidump, as the process itself is in an unsafe state. While handling an exception, the handler may not, for example: - Allocate memory. - Use libc, or most any library call. While handling an exception, the handler may only: - Use audited syscalls. - access memory via `vm_read`. In conjunction with Crashpad’s existing minidump writer and structural limitations of the minidump format, it is not possible to write a minidump immediately from the crash handler. Instead, an intermediate dump is written when a handler would normally write a minidump (such as during an exception or a forced dump without crashing). The intermediate dump file will be converted to a minidump on the next run (or when the application decides it's safe to do so). During Crashpad initialization, the handler gathers basic system information and opens a pending intermediate dump adjacent to the Crashpad database. ## The Crashpad IntermediateDump Format Due to the limitations of in-process handling, an intermediate dump file is written during exceptions. The data is streamed to a file, which will be used to generate a final minidump when appropriate. The file format is similar to binary JSON, supporting keyed properties, maps and arrays. - `Property` [key:int, length:int, value:intarray] - `StartMap` [key:int], followed by repeating Properties until `EndMap` - `StartArray` [key:int], followed by repeating Maps until `EndArray` - `EndMap`, `EndArray`, `EndDocument` Similar to JSON, maps can contain other maps, arrays and properties. ## The life of an iOS crash report Immediately upon calling StartCrashpadInProcessHandler, the iOS in-process handler is installed. This will open a temporary file within the database directory, in a subdirectory named `pending-serialized-ios-dump`. This file will be used to write an intermediate dump in the event of a crash. This must happen before installing the various types of crash handlers, as each depends on having a valid handler with an intermediate dump ready to be written to. After the in-process handler is initialized, the Mach exception, POSIX signal and Objective-C exception preprocessor handlers are installed. ### Intermediate Dump File Locking It is expected that multiple Crashpad clients may share the same database directory, and this directory may be inside an iOS app group directory. While it's possible for each Crashpad client to write to its own private directory, if a shared directory is used, it's possible for different applications to upload a crash report from any application in a shared group. This might be used, for example, by an application and its various app extensions, where each client may generate a crash report but only the main application uploads reports. Alternatively, a suite of applications may upload each other's crash reports. Otherwise, the only opportunity to upload a report would be when a specific app that crashed relaunches. To prevent multiple clients from processing a pending intermediate dump, files must be locked. However, POSIX locks on app group files will trigger app termination on app backgrounding, so a custom file locking protocol is used. Locked temporary files are named `@.locked`. The `.locked` extension is removed when the file is unlocked. The `bundle-id` is used to determine which Crashpad clients can process leftover locked files. ### Writing Crashes to Intermediate Dumps When an app encounters a crash (via a Mach exception, Objective-C exception, or a POSIX signal), an intermediate dump is written to the temporary locked file, the .locked extension is removed, and a new temporary locked file is opened. App terminations not handled by Crashpad will leave behind a temporary locked file, to be cleaned up on next launch. These files are still processed, because it is possible for the app to be terminated while writing an intermediate dump, and if enough data is written this may still be valuable. Note: Generally iOS apps are single-process, so it's safe for the client to consider any files matching its `bundle-id`, but there are edge-cases (such as if a share-to app extension is opened at the same time in two different apps) so old locked files won't be cleared until after 24 hours. Any locked file found after 60 days is unlocked regardless of `bundle-id`. ### Writing to Intermediate Dumps without a Crash Apps may also generate intermediate dumps without a crash, often used for debugging. Chromium makes heavy use of this for detecting main thread hangs, something that can appear as a crash for the user, but is uncatchable for crash handlers like Crashpad. When an app requests this (via DumpWithoutCrash, DumpWithoutCrashAndDeferProcessing), an intermediate dump is written to the temporary locked file, the .locked extension is removed, and a new temporary locked file is opened. Note: DumpWithoutCrashAndDeferProcessingAtPath writes an intermediate dump to the requested location, not the previously opened temporary file. This is useful because Chromium's main thread hang detection will throw away hang reports in certain circumstances (if the app recovers, if a different crash report is written, etc). ## The Crashpad In-Process Client Other Crashpad platforms handle exceptions and upload minidumps out-of-process. On iOS, everything must happen in-process. Once started, the client will automatically handle exceptions and capture the crashed process state in an intermediate dump file. Converting that intermediate dump file into a minidump is likely not safe to do from within a crashed process, and uploading a minidump is definitely unsafe to do at crash time. Applications are expected to process intermediate dumps into pending minidumps and begin processing pending minidumps, possibly for upload, at suitable times following the next application restart. Note: Applications are not required to call either of these methods. For example, application extensions may choose to generate dumps but leave processing and uploading to the main applications. Clients that share the same database directory between apps can take advantage of processing and uploading crash reports from different applications. ### `ProcessIntermediateDumps` For performance and stability reasons applications may choose the correct time to convert intermediate dumps, as well as append metadata to the pending intermediate dumps. This is expected to happen during application startup, when suitable. After converting, a minidump will be written to the Crashpad database, similar to how other platforms write a minidump on exception handling. If uploading is enabled, this minidump will also be immediately uploaded. New intermediate dumps generated by exceptions or by `CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING` will not be processed until the next call to `ProcessIntermediateDumps`. Conversely, `CRASHPAD_SIMULATE_CRASH` can be called when the client has no performance or stability concerns. In this case, intermediate dumps are automatically converted to minidumps and immediately eligible for uploading. Applications can include annotations here as well. Chromium uses this for its insta-crash logic, which detects if an app is crashing repeatedly on startup. ### `StartProcessingPendingReports` For similar reasons, applications may choose the correct time to begin uploading pending reports, such as when ideal network conditions exist. By default, clients start with uploading disabled. Applications should call this API when it is determined that it is appropriate to do so (such as on a few seconds after startup, or when network connectivity is appropriate). ## tvOS considerations tvOS, an operating system based on iOS, shares the constraints described above and uses the same architecture. Additionally, tvOS does not allow the use of the `mach_msg()` APIs so using Mach exceptions is impossible. Consequently, the Mach exception handler is _not_ installed once the in-process handler is initialized; the tvOS handler only uses the POSIX signals and Objective-C exception preprocessors. Furthermore, the POSIX signal handler is unable to handle all crashes that the Mach exception handler can (e.g. stack overflows, as `sigaltstack()` cannot be used either). In other words, the tvOS in-process handler can only handle a subset of all exceptions that the iOS in-process handler does. ================================================ FILE: doc/man.md ================================================ # Man Pages ## Section 1: User Commands * [crashpad_database_util](../tools/crashpad_database_util.md) * [crashpad_http_upload](../tools/crashpad_http_upload.md) * [generate_dump](../tools/generate_dump.md) ### macOS-Specific * [catch_exception_tool](../tools/mac/catch_exception_tool.md) * [exception_port_tool](../tools/mac/exception_port_tool.md) * [on_demand_service_tool](../tools/mac/on_demand_service_tool.md) * [run_with_crashpad](../tools/run_with_crashpad.md) ## Section 8: Dӕmons * [crashpad_handler](../handler/crashpad_handler.md) ================================================ FILE: doc/overview_design.md ================================================ # Crashpad Overview Design [TOC] ## Objective Crashpad is a library for capturing, storing and transmitting postmortem crash reports from a client to an upstream collection server. Crashpad aims to make it possible for clients to capture process state at the time of crash with the best possible fidelity and coverage, with the minimum of fuss. Crashpad also provides a facility for clients to capture dumps of process state on-demand for diagnostic purposes. Crashpad additionally provides minimal facilities for clients to adorn their crashes with application-specific metadata in the form of per-process key/value pairs. More sophisticated clients are able to adorn crash reports further through extensibility points that allow the embedder to augment the crash report with application-specific metadata. ## Background It’s an unfortunate truth that any large piece of software will contain bugs that will cause it to occasionally crash. Even in the absence of bugs, software incompatibilities can cause program instability. Fixing bugs and incompatibilities in client software that ships to millions of users around the world is a daunting task. User reports and manual reproduction of crashes can work, but even given a user report, often times the problem is not readily reproducible. This is for various reasons, such as e.g. system version or third-party software incompatibility, or the problem can happen due to a race of some sort. Users are also unlikely to report problems they encounter, and user reports are often of poor quality, as unfortunately most users don’t have experience with making good bug reports. Automatic crash telemetry has been the best solution to the problem so far, as this relieves the burden of manual reporting from users, while capturing the hardware and software state at the time of crash. TODO(siggi): examples of this? Crash telemetry involves capturing postmortem crash dumps and transmitting them to a backend collection server. On the server they can be stackwalked and symbolized, and evaluated and aggregated in various ways. Stackwalking and symbolizing the reports on an upstream server has several benefits over performing these tasks on the client. High-fidelity stackwalking requires access to bulky unwind data, and it may be desirable to not ship this to end users out of concern for the application size. The process of symbolization requires access to debugging symbols, which can be quite large, and the symbolization process can consume considerable other resources. Transmitting un-stackwalked and un-symbolized postmortem dumps to the collection server also allows deep analysis of individual dumps, which is often necessary to resolve the bug causing the crash. Transmitting reports to the collection server allows aggregating crashes by cause, which in turn allows assessing the importance of different crashes in terms of the occurrence rate and e.g. the potential security impact. A postmortem crash dump must contain the program state at the time of crash with sufficient fidelity to allow diagnosing and fixing the problem. As the full program state is usually too large to transmit to an upstream server, the postmortem dump captures a heuristic subset of the full state. The crashed program is in an indeterminate state and, in fact, has often crashed because of corrupt global state - such as heap. It’s therefore important to generate crash reports with as little execution in the crashed process as possible. Different operating systems vary in the facilities they provide for this. ## Overview Crashpad is a client-side library that focuses on capturing machine and program state in a postmortem crash report, and transmitting this report to a backend server - a “collection server”. The Crashpad library is embedded by the client application. Conceptually, Crashpad breaks down into the handler and the client. The handler runs in a separate process from the client or clients. It is responsible for snapshotting the crashing client process’ state on a crash, saving it to a crash dump, and transmitting the crash dump to an upstream server. Clients register with the handler to allow it to capture and upload their crashes. On iOS, there is no separate process for the handler. [This is a limitation of iOS.](ios_overview_design.md#ios-limitations) ### The Crashpad handler The Crashpad handler is instantiated in a process supplied by the embedding application. It provides means for clients to register themselves by some means of IPC, or where operating system support is available, by taking advantage of such support to cause crash notifications to be delivered to the handler. On crash, the handler snapshots the crashed client process’ state, writes it to a postmortem dump in a database, and may also transmit the dump to an upstream server if so configured. The Crashpad handler is able to handle cross-bitted requests and generate crash dumps across bitness, where e.g. the handler is a 64-bit process while the client is a 32-bit process or vice versa. In the case of Windows, this is limited by the OS such that a 32-bit handler can only generate crash dumps for 32-bit clients, but a 64-bit handler can acquire nearly all of the detail for a 32-bit process. ### The Crashpad client The Crashpad client provides two main facilities. 1. Registration with the Crashpad handler. 2. Metadata communication to the Crashpad handler on crash. A Crashpad embedder links the Crashpad client library into one or more executables, whether a loadable library or a program file. The client process then registers with the Crashpad handler through some mode of IPC or other operating system-specific support. On crash, metadata is communicated to the Crashpad handler via the CrashpadInfo structure. Each client executable module linking the Crashpad client library embeds a CrashpadInfo structure, which can be updated by the client with whatever state the client wishes to record with a crash. ![Overview image](overview.png) Here is an overview picture of the conceptual relationships between embedder (in light blue), client modules (darker blue), and Crashpad (in green). Note that multiple client modules can contain a CrashpadInfo structure, but only one registration is necessary. ## Detailed Design ### Requirements The purpose of Crashpad is to capture machine, OS and application state in sufficient detail and fidelity to allow developers to diagnose and, where possible, fix the issue causing the crash. Each distinct crash report is assigned a globally unique ID, in order to allow users to associate them with a user report, report in bug reports and so on. It’s critical to safeguard the user’s privacy by ensuring that no crash report is ever uploaded without user consent. Likewise it’s important to ensure that Crashpad never captures or uploads reports from non-client processes. ### Concepts * **Client ID**. A UUID tied to a single instance of a Crashpad database. When creating a crash report, the Crashpad handler includes the client ID stored in the database. This provides a means to determine how many individual end users are affected by a specific crash signature. * **Crash ID**. A UUID representing a single crash report. Uploaded crash reports also receive a “server ID.” The Crashpad database indexes both the locally-generated and server-generated IDs. * **Collection Server**. See [crash server documentation.]( https://goto.google.com/crash-server-overview) * **Client Process**. Any process that has registered with a Crashpad handler. * **Handler process**. A process hosting the Crashpad handler library. This may be a dedicated executable, or it may be hosted within a client executable with control passed to it based on special signaling under the client’s control, such as a command-line parameter. * **CrashpadInfo**. A structure used by client modules to provide information to the handler. * **Annotations**. Each CrashpadInfo structure points to a dictionary of {string, string} annotations that the client can use to communicate application state in the case of crash. * **Database**. The Crashpad database contains persistent client settings as well as crash dumps pending upload. TODO(siggi): moar concepts? ### Overview Picture Here is a rough overview picture of the various Crashpad constructs, their layering and intended use by clients. ![Layering image](layering.png) Dark blue boxes are interfaces, light blue boxes are implementation. Gray is the embedding client application. Note that wherever possible, implementation that necessarily has to be OS-specific, exposes OS-agnostic interfaces to the rest of Crashpad and the client. ### Registration The particulars of how a client registers with the handler varies across operating systems. #### macOS At registration time, the client designates a Mach port monitored by the Crashpad handler as the EXC_CRASH exception port for the client. The port may be acquired by launching a new handler process or by retrieving service already registered with the system. The registration is maintained by the kernel and is inherited by subprocesses at creation time by default, so only the topmost process of a process tree need register. Crashpad provides a facility for a process to disassociate (unregister) with an existing crash handler, which can be necessary when an older client spawns an updated version. #### iOS iOS registers both a signal handler for `SIGABRT` and a Mach exception handler with a subset of available exceptions. [This is a limitation of iOS.](ios_overview_design.md#ios-limitations) #### Windows There are two modes of registration on Windows. In both cases the handler is advised of the address of a set of structures in the client process’ address space. These structures include a pair of ExceptionInformation structs, one for generating a postmortem dump for a crashing process, and another one for generating a dump for a non- crashing process. ##### Normal registration In the normal registration mode, the client connects to a named pipe by a pre-arranged name. A registration request is written to the pipe. During registration, the handler creates a set of events, duplicates them to the registering client, then returns the handle values in the registration response. This is a blocking process. ##### Initial Handler Creation In order to avoid blocking client startup for the creation and initialization of the handler, a different mode of registration can be used for the handler creation. In this mode, the client creates a set of event handles and inherits them into the newly created handler process. The handler process is advised of the handle values and the location of the ExceptionInformation structures by way of command line arguments in this mode. #### Linux/Android On Linux, a registration is a connected socket pair between a client process and the Crashpad handler. This socket pair may be private or shared among many client processes. ##### Private Connections Private connections are the default registration mode when starting the handler process in response to a crash or on behalf of another client. This mode is required to use a ptrace broker, which is in turn required to trace Android isolated processes. ##### Shared Connections Shared connections are the default mode when using a long-lived handler. The same connected socket pair may be shared among any number of clients. The socket pair is created by the first process to start the handler at which point the client socket end may be shared with other clients by any convenient means (e.g. inheritance). ### Capturing Exceptions The details of how Crashpad captures the exceptions leading to crashes varies between operating systems. #### macOS On macOS, the operating system will notify the handler of client crashes via the Mach port set as the client process’ exception port. As exceptions are dispatched to the Mach port by the kernel, on macOS, exceptions can be handled entirely from the Crashpad handler without the need to run any code in the crash process at the time of the exception. #### iOS On iOS, the operating system will notify the handler of crashes via the Mach exception port or the signal handler. As exceptions are handled in-process, an intermediate dump file is generated rather than a minidump. See more information about the [iOS in-process handler.](ios_overview_design.md#ios-in-process-handler) #### Windows On Windows, the OS dispatches exceptions in the context of the crashing thread. To notify the handler of exceptions, the Crashpad client registers an UnhandledExceptionFilter (UEF) in the client process. When an exception trickles up to the UEF, it stores the exception information and the crashing thread’s ID in the ExceptionInformation structure registered with the handler. It then sets an event handle to signal the handler to go ahead and process the exception. ##### Caveats * If the crashing thread’s stack is smashed when an exception occurs, the exception cannot be dispatched. In this case the OS will summarily terminate the process, without the handler having an opportunity to generate a crash report. * If an exception is handled in the crashing thread, it will never propagate to the UEF, and thus a crash report won’t be generated. This happens a fair bit in Windows as system libraries will often dispatch callbacks under a structured exception handler. This occurs during Window message dispatching on some system configurations, as well as during e.g. DLL entry point notifications. * A growing number of conditions in the system and runtime exist where detected corruption or illegal calls result in summary termination of the process, in which case no crash report will be generated. ###### Out-Of-Process Exception Handling There exists a mechanism in Windows Error Reporting (WER) that allows a client process to register for handling client exceptions out of the crashing process. Unfortunately this mechanism is difficult to use, and doesn’t provide coverage for many of the caveats above. [Details here.](https://crashpad.chromium.org/bug/133) #### Linux/Android On Linux, exceptions are dispatched as signals to the crashing thread. Crashpad signal handlers will send a message over the socket to the Crashpad handler notifying it of the crash and the location of exception information to be read from the crashing process. When using a shared socket connection, communication is entirely one-way. The client sends its dump request to the handler and then waits until the handler responds with a SIGCONT or a timeout occurs. When using a private socket connection, the handler may respond over the socket to communicate with a ptrace broker process. The broker is forked from the crashing process, executes ptrace requests against the crashing process, and sends the information over the socket to the handler. ### The CrashpadInfo structure The CrashpadInfo structure is used to communicate information from the client to the handler. Each executable module in a client process can contain a CrashpadInfo structure. On a crash, the handler crawls all modules in the crashing process to locate all CrashpadInfo structures present. The CrashpadInfo structures are linked into a special, named section of the executable, where the handler can readily find them. The CrashpadInfo structure has a magic signature, and contains a size and a version field. The intent is to allow backwards compatibility from older client modules to newer handler. It may also be necessary to provide forwards compatibility from newer clients to older handler, though this hasn’t occurred yet. The CrashpadInfo structure contains such properties as the cap for how much memory to include in the crash dump, some tristate flags for controlling the handler’s behavior, a pointer to an annotation dictionary and so on. ### Snapshot Snapshot is a layer of interfaces that represent the machine and OS entities that Crashpad cares about. Different concrete implementations of snapshot can then be backed different ways, such as e.g. from the in-memory representation of a crashed process, or e.g. from the contents of a minidump. ### Crash Dump Creation To create a crash dump, a subset of the machine, OS and application state is grabbed from the crashed process into an in-memory snapshot structure in the handler process. Since the full application state is typically too large for capturing to disk and transmitting to an upstream server, the snapshot contains a heuristically selected subset of the full state. The precise details of what’s captured varies between operating systems, but generally includes the following * The set of modules (executable, shared libraries) that are loaded into the crashing process. * An enumeration of the threads running in the crashing process, including the register contents and the contents of stack memory of each thread. * A selection of the OS-related state of the process, such as e.g. the command line, environment and so on. * A selection of memory potentially referenced from registers and from stack. To capture a crash dump, the crashing process is first suspended, then a snapshot is created in the handler process. The snapshot includes the CrashpadInfo structures of the modules loaded into the process, and the contents of those is used to control the level of detail captured for the crash dump. Once the snapshot has been constructed, it is then written to a minidump file, which is added to the database. The process is un-suspended after the minidump file has been written. In the case of a crash (as opposed to a client request to produce a dump without crashing), it is then either killed by the operating system or the Crashpad handler. In general the snapshotting process has to be very intimate with the operating system it’s working with, so there will be a set of concrete implementation classes, many deriving from the snapshot interfaces, doing this for each operating system. ### Minidump The minidump implementation is responsible for writing a snapshot to a serialized on-disk file in the minidump format. The minidump implementation is OS-agnostic, as it works on an OS-agnostic Snapshot interface. TODO(siggi): Talk about two-phase writes and contents ordering here. ### Database The Crashpad database contains persistent client settings, including a unique crash client identifier and the upload-enabled bit. Note that the crash client identifier is assigned by Crashpad, and is distinct from any identifiers the client application uses to identify users, installs, machines or such - if any. The expectation is that the client application will manage the user’s upload consent, and inform Crashpad of changes in consent. The unique client identifier is set at the time of database creation. It is then recorded into every crash report collected by the handler and communicated to the upstream server. The database stores a configurable number of recorded crash dumps to a configurable maximum aggregate size. For each crash dump it stores annotations relating to whether the crash dumps have been uploaded. For successfully uploaded crash dumps it also stores their server-assigned ID. The database consists of a settings file, named "settings.dat" with binary contents (see crashpad::Settings::Data for the file format), as well as directory containing the crash dumps. Additionally each crash dump is adorned with properties relating to the state of the dump for upload and such. The details of how these properties are stored vary between platforms. #### macOS The macOS implementation simply stores database properties on the minidump files in filesystem extended attributes. #### iOS The iOS implementation also stores database properties of minidump files in filesystem extended attributes. Separate from the database, iOS also stores its intermediate dump files adjacent to the database. See more information about [iOS intermediate dumps.](ios_overview_design.md#the-crashpad-intermediatedump-format) #### Windows The Windows implementation stores database properties in a binary file named “metadata” at the top level of the database directory. ### Report Format Crash reports are recorded in the Windows minidump format with extensions to support Crashpad additions, such as e.g. Annotations. ### Upload to collection server #### Wire Format For the time being, Crashpad uses the Breakpad wire protocol, which is essentially a MIME multipart message communicated over HTTP(S). To support this, the annotations from all the CrashpadInfo structures found in the crashing process are merged to create the Breakpad “crash keys” as form data. The postmortem minidump is then attached as an “application/octet- stream” attachment with the name “upload_file_minidump”. The entirety of the request body, including the minidump, can be gzip-compressed to reduce transmission time and increase transmission reliability. Note that by convention there is a set of “crash keys” that are used to communicate the product, version, client ID and other relevant data about the client, to the server. Crashpad normally stores these values in the minidump file itself, but retrieves them from the minidump and supplies them as form data for compatibility with the Breakpad-style server. This is a temporary compatibility measure to allow the current Breakpad-based upstream server to handle Crashpad reports. In the fullness of time, the wire protocol is expected to change to remove this redundant transmission and processing of the Annotations. #### Transport The embedding client controls the URL of the collection server by the command line passed to the handler. The handler can upload crashes with HTTP or HTTPS, depending on client’s preference. It’s strongly suggested use HTTPS transport for crash uploads to protect the user’s privacy against man-in-the-middle snoopers. TODO(mmentovai): Certificate pinning. #### Throttling & Retry Strategy To protect both the collection server from DDoS as well as to protect the clients from unreasonable data transfer demands, the handler implements a client-side throttling strategy. At the moment, the strategy is very simplistic, it simply limits uploads to one upload per hour, and failed uploads are aborted. An experiment has been conducted to lift all throttling. Analysis on the aggregate data this produced shows that multiple crashes within a short timespan on the same client are nearly always due to the same cause. Therefore there is very little loss of signal due to the throttling, though the ability to reconstruct at least the full crash count is highly desirable. The lack of retry is expected to [change soon](https://crashpad.chromium.org/bug/23), as this creates blind spots for client crashes that exclusively occur on e.g. network down events, during suspend and resume and such. ### Extensibility #### Client Extensibility Clients are able to extend the generated crash reports in two ways, by manipulating their CrashpadInfo structure. The two extensibility points are: 1. Nominating a set of address ranges for inclusion in the crash report. 2. Adding user-defined minidump streams for inclusion in the crash report. In both cases the CrashpadInfo structure has to be updated before a crash occurs. ##### Embedder Extensibility Additionally, embedders of the handler can provide "user stream data source" instances to the handler's main function. Any time a minidump is written, these instances get called. Each data source may contribute a custom stream to the minidump, which can be computed from e.g. system or application state relevant to the crash. As a case in point, it can be handy to know whether the system was under memory or other resource duress at the time of crash. ### Dependencies Aside from system headers and APIs, when used outside of Chromium, Crashpad has a dependency on “mini_chromium”, which is a subset of the Chromium base library. This is to allow non-Chromium clients to use Crashpad, without taking a direct dependency on the Chromium base, while allowing Chromium projects to use Crashpad with minimum code duplication or hassle. When using Crashpad as part of Chromium, Chromium’s own copy of the base library is used instead of mini_chromium. The downside to this is that mini_chromium must be kept up to date with interface and implementation changes in Chromium base, for the subset of functionality used by Crashpad. ## Caveats TODO(anyone): You may need to describe what you did not do or why simpler approaches don't work. Mention other things to watch out for (if any). ## Security Considerations Crashpad may be used to capture the state of sandboxed processes and it writes minidumps to disk. It may therefore straddle security boundaries, so it’s important that Crashpad handle all data it reads out of the crashed process with extreme care. The Crashpad handler takes care to access client address spaces through specially-designed accessors that check pointer validity and enforce accesses within prescribed bounds. The flow of information into the Crashpad handler is exclusively one-way: Crashpad never communicates anything back to its clients, aside from providing single-bit indications of completion. ## Privacy Considerations Crashpad may capture arbitrary contents from crashed process’ memory, including user IDs and passwords, credit card information, URLs and whatever other content users have trusted the crashing program with. The client program must acquire and honor the user’s consent to upload crash reports, and appropriately manage the upload state in Crashpad’s database. Crashpad must also be careful not to upload crashes for arbitrary processes on the user’s system. To this end, Crashpad will never upload a process that hasn’t registered with the handler, but note that registrations are inherited by child processes on some operating systems. ================================================ FILE: doc/status.md ================================================ # Project Status ## Completed Crashpad has complete crash-reporting clients and some related tools for macOS, Windows, Fuchsia, and Linux (including Android and Chromium OS). Crashpad became the crash reporter client for [Chromium](https://www.chromium.org/Home) on macOS as of [March 2015](https://chromium.googlesource.com/chromium/src/\+/d413b2dcb54d523811d386f1ff4084f677a6d089), Windows as of [November 2015](https://chromium.googlesource.com/chromium/src/\+/cfa5b01bb1d06bf96967bd37e21a44752801948c), and Android as of [January 2019](https://chromium.googlesource.com/chromium/src/+/f890e4b5495ab693d2d37aec3c378239946154f7). ## In Progress Chromium is transitioning to Crashpad for [Chromium OS and Desktop Linux](https://crbug.com/942279). Work has begun on a Crashpad client for [iOS](https://crashpad.chromium.org/bug/31). ## Future There are also plans to implement a [crash report processor](https://crashpad.chromium.org/bug/29) as part of Crashpad. No timeline for completing this work has been set yet. ================================================ FILE: doc/support/compat.sh ================================================ # Copyright 2015 The Crashpad Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if [[ "${BASH_SOURCE[0]}" = "${0}" ]]; then echo "${0}: this file must be sourced, not run directly" >& 2 exit 1 fi # Some extensions of command-line tools behave differently on different systems. # $sed_ext should be a sed invocation that enables extended regular expressions. # $date_time_t should be a date invocation that causes it to print the date and # time corresponding to a time_t string that immediately follows it. case "$(uname -s)" in Darwin) sed_ext="sed -E" date_time_t="date -r" ;; Linux) sed_ext="sed -r" date_time_t="date -d@" ;; *) echo "${0}: unknown operating system" >& 2 exit 1 ;; esac ================================================ FILE: doc/support/crashpad.doxy ================================================ # Doxyfile 1.13.2 # This file describes the settings to be used by the documentation system # Doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). # # Note: # # Use Doxygen to compare the used configuration file with the template # configuration file: # doxygen -x [configFile] # Use Doxygen to compare the used configuration file with the template # configuration file without replacing the environment variables or CMake type # replacement variables: # doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the configuration # file that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = Crashpad # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewers a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # With the PROJECT_ICON tag one can specify an icon that is included in the tabs # when the HTML document is shown. Doxygen will copy the logo to the output # directory. PROJECT_ICON = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where Doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = out/doc/doxygen # If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 # sub-directories (in 2 levels) under the output directory of each output format # and will distribute the generated files over these directories. Enabling this # option can be useful when feeding Doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise cause # performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to # control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO # Controls the number of sub-directories that will be created when # CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every # level increment doubles the number of directories, resulting in 4096 # directories at level 8 which is the default and also the maximum value. The # sub-directories are organized in 2 levels, the first level always has a fixed # number of 16 directories. # Minimum value: 0, maximum value: 8, default value: 8. # This tag requires that the tag CREATE_SUBDIRS is set to YES. CREATE_SUBDIRS_LEVEL = 8 # If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by Doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, # Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English # (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, # Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with # English messages), Korean, Korean-en (Korean with English messages), Latvian, # Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, # Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, # Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which Doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where Doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = . \ compat/non_mac \ compat/non_win # If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but # less readable) file names. This can be useful if your file system doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the # first line (until the first dot, question mark or exclamation mark) of a # Javadoc-style comment as the brief description. If set to NO, the Javadoc- # style will behave just like regular Qt-style comments (thus requiring an # explicit @brief command for a brief description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be # interpreted by Doxygen. # The default value is: NO. JAVADOC_BANNER = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first # line (until the first dot, question mark or exclamation mark) of a Qt-style # comment as the brief description. If set to NO, the Qt-style will behave just # like regular Qt-style comments (thus requiring an explicit \brief command for # a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # By default Python docstrings are displayed as preformatted text and Doxygen's # special commands cannot be used. By setting PYTHON_DOCSTRING to NO the # Doxygen's special commands can be used and the contents of the docstring # documentation blocks is shown as Doxygen documentation. # The default value is: YES. PYTHON_DOCSTRING = YES # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". Note that you cannot put \n's in the value part of an alias # to insert newlines (in the resulting output). You can put ^^ in the value part # of an alias to insert a newline as if a physical newline was in the original # file. When you need a literal { or } or , in the value part of an alias you # have to escape them by means of a backslash (\), this can lead to conflicts # with the commands \{ and \} for these it is advised to use the version @{ and # @} or use a double escape (\\{ and \\}) ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice # sources only. Doxygen will then generate output that is more tailored for that # language. For instance, namespaces will be presented as modules, types will be # separated into more groups, etc. # The default value is: NO. OPTIMIZE_OUTPUT_SLICE = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, # VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make Doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by Doxygen. When specifying no_extension you should add # * to the FILE_PATTERNS. # # Note see also the list of default file extension mappings. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by Doxygen, so you can # mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 6. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 0 # The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to # generate identifiers for the Markdown headings. Note: Every identifier is # unique. # Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a # sequence number starting at 0 and GITHUB use the lower case version of title # with any whitespace replaced by '-' and punctuation characters removed. # The default value is: DOXYGEN. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. MARKDOWN_ID_STYLE = DOXYGEN # When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. Words listed in the # AUTOLINK_IGNORE_WORDS tag are excluded from automatic linking. # The default value is: YES. AUTOLINK_SUPPORT = YES # This tag specifies a list of words that, when matching the start of a word in # the documentation, will suppress auto links generation, if it is enabled via # AUTOLINK_SUPPORT. This list does not affect affect links explicitly created # using \# or the \link or commands. # This tag requires that the tag AUTOLINK_SUPPORT is set to YES. AUTOLINK_IGNORE_WORDS = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let Doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also makes the inheritance and # collaboration diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # https://www.riverbankcomputing.com/software) sources only. Doxygen will parse # them like normal C++ but will assume all classes use public instead of private # inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # Doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then Doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = YES # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small # Doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run Doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 # The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use # during processing. When set to 0 Doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, # which effectively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. NUM_PROC_THREADS = 1 # If the TIMESTAMP tag is set different from NO then each generated page will # contain the date or date and time when the page was generated. Setting this to # NO can help when comparing the output of multiple runs. # Possible values are: YES, NO, DATETIME and DATE. # The default value is: NO. TIMESTAMP = NO #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. # The default value is: NO. EXTRACT_PRIV_VIRTUAL = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If this flag is set to YES, the name of an unnamed parameter in a declaration # will be determined by the corresponding definition. By default unnamed # parameters remain unnamed in the output. # The default value is: YES. RESOLVE_UNNAMED_PARAMS = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # will also hide undocumented C++ concepts if enabled. This option has no effect # if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all # undocumented namespaces that are normally visible in the namespace hierarchy. # If set to NO, these namespaces will be included in the various overviews. This # option has no effect if EXTRACT_ALL is enabled. # The default value is: YES. HIDE_UNDOC_NAMESPACES = YES # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # With the correct setting of option CASE_SENSE_NAMES Doxygen will better be # able to match the capabilities of the underlying filesystem. In case the # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that # are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and macOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. # Possible values are: SYSTEM, NO and YES. # The default value is: SYSTEM. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_HEADERFILE tag is set to YES then the documentation for a class # will show which file needs to be included to use the class. # The default value is: YES. SHOW_HEADERFILE = YES # If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = YES # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = NO # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = NO # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = NO # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= NO # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # Doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by Doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by Doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents Doxygen's defaults, run Doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. See also section "Changing the # layout of pages" for information. # # Note that if you run Doxygen from a directory containing a file called # DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = # The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH # environment variable) so that external tools such as latex and gs can be # found. # Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the # path already specified by the PATH variable, and are added in the order # specified. # Note: This option is particularly useful for macOS version 14 (Sonoma) and # higher, when running Doxygen from Doxywizard, because in this case any user- # defined changes to the PATH are ignored. A typical example on macOS is to set # EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin # together with the standard path, the full search path used by doxygen when # launching external tools will then become # PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin EXTERNAL_TOOL_PATH = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by Doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = NO # If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for # potential errors in the documentation, such as documenting some parameters in # a documented function twice, or documenting parameters that don't exist or # using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete # function parameter documentation. If set to NO, Doxygen will accept that some # parameters have no documentation without warning. # The default value is: YES. WARN_IF_INCOMPLETE_DOC = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, Doxygen will only warn about wrong parameter # documentation, but not about the absence of documentation. If EXTRACT_ALL is # set to YES then this flag will automatically be disabled. See also # WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = NO # If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about # undocumented enumeration values. If set to NO, Doxygen will accept # undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: NO. WARN_IF_UNDOC_ENUM_VAL = NO # If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found # while parsing the user defined layout file, such as missing or wrong elements. # See also LAYOUT_FILE for details. If set to NO, problems with the layout file # will be suppressed. # The default value is: YES. WARN_LAYOUT_FILE = YES # If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the Doxygen process Doxygen will return with a non-zero status. # If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves # like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not # write the warning messages in between other messages but write them at the end # of a run, in case a WARN_LOGFILE is defined the warning messages will be # besides being in the defined file also be shown at the end of a run, unless # the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case # the behavior will remain as with the setting FAIL_ON_WARNINGS. # Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that Doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # In the $text part of the WARN_FORMAT command it is possible that a reference # to a more specific place is given. To make it easier to jump to this place # (outside of Doxygen) the user can define a custom "cut" / "paste" string. # Example: # WARN_LINE_FORMAT = "'vi $file +$line'" # See also: WARN_FORMAT # The default value is: at line $line of file $file. WARN_LINE_FORMAT = "at line $line of file $file" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). In case the file specified cannot be opened for writing the # warning and error messages are written to standard error. When as file - is # specified the warning and error messages are written to standard output # (stdout). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = # This tag can be used to specify the character encoding of the source files # that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. # See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # This tag can be used to specify the character encoding of the source files # that Doxygen parses. The INPUT_FILE_ENCODING tag can be used to specify # character encoding on a per file pattern basis. Doxygen will compare the file # name with each pattern and apply the encoding instead of the default # INPUT_ENCODING if there is a match. The character encodings are a list of the # form: pattern=encoding (like *.php=ISO-8859-1). # See also: INPUT_ENCODING for further information on supported encodings. INPUT_FILE_ENCODING = # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by Doxygen. # # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, # *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, # *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, # *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to # be provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ *.h \ *.m \ *.mm # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which Doxygen is # run. EXCLUDE = compat/android \ compat/ios \ compat/linux \ compat/mac \ compat/win \ third_party # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = out* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that Doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that Doxygen will use the data processed and written to standard output # for further processing, therefore nothing else, like debug statements or used # commands (so in case of a Windows batch file always use @echo OFF), should be # written to standard output. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the Doxygen output. USE_MDFILE_AS_MAINPAGE = # If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- # directories of the project's root, is used as the documentation for that sub- # directory, except when the README.md starts with a \dir, \page or \mainpage # command. If set to NO, the README.md file needs to start with an explicit \dir # command in order to be used as directory documentation. # The default value is: YES. IMPLICIT_DIR_DOCS = YES # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common # extension is to allow longer lines before the automatic comment starts. The # setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can # be processed before the automatic comment starts. # Minimum value: 7, maximum value: 10000, default value: 72. FORTRAN_COMMENT_AFTER = 72 #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # multi-line macros, enums or list initialized variables directly into the # documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of Doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by Doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) # that should be ignored while generating the index headers. The IGNORE_PREFIX # tag works for classes, function and member names. The entity will be placed in # the alphabetical list under the first letter of the entity name that remains # after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank Doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that Doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that Doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of Doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank Doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that Doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank Doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that Doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by Doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). # Note: Since the styling of scrollbars can currently not be overruled in # Webkit/Chromium, the styling will be left out of the default doxygen.css if # one or more extra stylesheets have been specified. So if scrollbar # customization is desired it has to be added explicitly. For an example see the # documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = doc/support/crashpad_doxygen.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE tag can be used to specify if the generated HTML output # should be rendered with a dark or light theme. # Possible values are: LIGHT always generates light mode output, DARK always # generates dark mode output, AUTO_LIGHT automatically sets the mode according # to the user preference, uses light mode if no preference is set (the default), # AUTO_DARK automatically sets the mode according to the user preference, uses # dark mode if no preference is set and TOGGLE allows a user to switch between # light and dark mode via a button. # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE = AUTO_LIGHT # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a color-wheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML # page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_MENUS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be # dynamically folded and expanded in the generated HTML source code. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_CODE_FOLDING = YES # If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in # the top right corner of code and text fragments that allows the user to copy # its content to the clipboard. Note this only works if supported by the browser # and the web page is served via a secure context (see: # https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: # protocol. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COPY_CLIPBOARD = YES # Doxygen stores a couple of settings persistently in the browser (via e.g. # cookies). By default these settings apply to all HTML pages generated by # Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store # the settings under a project specific key, such that the user preferences will # be stored separately. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_PROJECT_COOKIE = # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: # https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To # create a documentation set, Doxygen will generate a Makefile in the HTML # output directory. Running make will produce the docset in that directory and # running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag determines the URL of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDURL = # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # on Windows. In the beginning of 2021 Microsoft took the original page, with # a.o. the download links, offline (the HTML help workshop was already many # years in maintenance mode). You can download the HTML help workshop from the # web archives at Installation executable (see: # http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo # ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # Doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # The SITEMAP_URL tag is used to specify the full URL of the place where the # generated documentation will be placed on the server by the user during the # deployment of the documentation. The generated sitemap is called sitemap.xml # and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL # is specified no sitemap is generated. For information about the sitemap # protocol see https://www.sitemaps.org # This tag requires that the tag GENERATE_HTML is set to YES. SITEMAP_URL = # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location (absolute path # including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to # run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = YES # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine tune the look of the index (see "Fine-tuning the output"). As an # example, the default style sheet generated by Doxygen has an example that # shows how to put an image at the root of the tree instead of the PROJECT_NAME. # Since the tree basically has the same information as the tab index, you could # consider setting DISABLE_INDEX to YES when enabling this option. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = YES # When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the # FULL_SIDEBAR option determines if the side bar is limited to only the treeview # area (value NO) or if it should extend to the full height of the window (value # YES). Setting this to YES gives a layout similar to # https://docs.readthedocs.io with more room for contents, but less room for the # project logo, title, and description. If either GENERATE_TREEVIEW or # DISABLE_INDEX is set to NO, this option has no effect. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. FULL_SIDEBAR = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # Doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 0 # When the SHOW_ENUM_VALUES tag is set doxygen will show the specified # enumeration values besides the enumeration mnemonics. # The default value is: NO. SHOW_ENUM_VALUES = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email # addresses. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. OBFUSCATE_EMAILS = YES # If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. # Possible values are: png (the default) and svg (looks nicer but requires the # pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FORMULA_FORMAT = png # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # Doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # With MATHJAX_VERSION it is possible to specify the MathJax version to be used. # Note that the different versions of MathJax have different requirements with # regards to the different settings, so it is possible that also other MathJax # settings have to be changed when switching between the different MathJax # versions. # Possible values are: MathJax_2 and MathJax_3. # The default value is: MathJax_2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_VERSION = MathJax_2 # When MathJax is enabled you can set the default output format to be used for # the MathJax output. For more details about the output format see MathJax # version 2 (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 # (see: # http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best # compatibility. This is the name for Mathjax version 2, for MathJax version 3 # this will be translated into chtml), NativeMML (i.e. MathML. Only supported # for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This # is the name for Mathjax version 3, for MathJax version 2 this will be # translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. The default value is: # - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 # - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # for MathJax version 2 (see # https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # For example for MathJax version 3 (see # http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): # MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled Doxygen will generate a search box for # the HTML output. The underlying search engine uses JavaScript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the JavaScript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /