blob: 13c18cb8effa3fbdab3638cbe9398817d925219a [file] [log] [blame]
// 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