// 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 #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 "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; //! \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. void ProcessIntermediateDumps( const std::map& 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& 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& 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 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 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 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