| // 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 <mach/mach.h> |
| #include <stdint.h> |
| |
| #include <atomic> |
| #include <functional> |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #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 "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/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<void()>; |
| |
| //! \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<std::string, std::string>& 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. |
| void ProcessIntermediateDumps( |
| const std::map<std::string, std::string>& annotations); |
| |
| //! \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. |
| void ProcessIntermediateDump( |
| const base::FilePath& path, |
| const std::map<std::string, std::string>& annotations = {}); |
| |
| //! \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 Mach handling. Intended to be used by |
| //! tests to trigger a reentrant exception. |
| void SetMachExceptionCallbackForTesting(void (*callback)()) { |
| mach_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<std::string, std::string>& 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. |
| void SaveSnapshot(ProcessSnapshotIOSIntermediateDump& process_snapshot); |
| |
| //! \brief Process a maximum of 20 pending intermediate dumps. Dumps named |
| //! with our bundle id get first priority to prevent spamming. |
| std::vector<base::FilePath> 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<IOSIntermediateDumpWriter> 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 after aquiring the cached_writer_. |
| void (*mach_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<std::string, std::string> annotations_; |
| base::FilePath base_dir_; |
| std::string cached_writer_path_; |
| std::string cached_writer_unlocked_path_; |
| std::unique_ptr<IOSIntermediateDumpWriter> cached_writer_; |
| std::atomic<uint64_t> exception_thread_id_ = 0; |
| std::unique_ptr<CrashReportUploadThread> upload_thread_; |
| std::unique_ptr<PruneIntermediateDumpsAndCrashReportsThread> prune_thread_; |
| std::unique_ptr<CrashReportDatabase> database_; |
| std::string bundle_identifier_and_seperator_; |
| IOSSystemDataCollector system_data_; |
| InitializationStateDcheck initialized_; |
| }; |
| |
| } // namespace internal |
| } // namespace crashpad |