blob: d6c9290a4d2cafe294c1d4d917fa4b890d799460 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_DEVELOPER_DEBUG_ZXDB_CLIENT_SESSION_H_
#define SRC_DEVELOPER_DEBUG_ZXDB_CLIENT_SESSION_H_
#include <filesystem>
#include <map>
#include <memory>
#include <set>
#include <vector>
#include "lib/fit/function.h"
#include "src/developer/debug/ipc/protocol.h"
#include "src/developer/debug/shared/platform.h"
#include "src/developer/debug/zxdb/client/analytics_reporter.h"
#include "src/developer/debug/zxdb/client/arch_info.h"
#include "src/developer/debug/zxdb/client/component_observer.h"
#include "src/developer/debug/zxdb/client/session_observer.h"
#include "src/developer/debug/zxdb/client/system.h"
#include "src/developer/debug/zxdb/common/err.h"
#include "src/developer/debug/zxdb/expr/abi.h"
#include "src/lib/fxl/memory/ref_ptr.h"
#include "src/lib/fxl/memory/weak_ptr.h"
namespace debug {
class BufferedFD;
class StreamBuffer;
} // namespace debug
namespace zxdb {
class ArchInfo;
class BreakpointObserver;
class DownloadObserver;
class FilterObserver;
class ProcessImpl;
class ProcessObserver;
class RemoteAPI;
class RemoteAPIImpl;
class RemoteAPITest;
class TargetObserver;
class ThreadImpl;
class ThreadObserver;
enum class SessionConnectionType : uint32_t {
kNetwork = 1,
kUnix = 2,
};
struct SessionConnectionInfo {
SessionConnectionType type = SessionConnectionType::kNetwork;
// If the connection type is `Network` then `host` is the IP address or URL.
// If the connection type is `Unix` then `host` is the file path to the socket.
std::string host;
// If the connection type is `Network` then `port` is the port address.
// If the connection type is `Unix` then `port` is unused.
uint16_t port = 0;
};
// The session object manages the connection with the remote debug agent.
class Session : public SettingStoreObserver {
public:
// Creates a session with no connection. All sending will fail until the callback associated with
// a Connect() call is issued.
Session();
// Creates a session using a custom RemoteAPI implementation. Use for tests to mock out sending
// IPC messages.
Session(std::unique_ptr<RemoteAPI> remote_api, debug::Arch arch, debug::Platform platform,
uint64_t page_size);
// Creates with a previously-allocated local connection. The pointer must outlive this class. In
// this mode, the stream can not be disconnected.
explicit Session(debug::StreamBuffer* stream);
virtual ~Session();
fxl::WeakPtr<Session> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
// The RempteAPI for sending messages to the debug_agent.
RemoteAPI* remote_api() { return remote_api_.get(); }
uint32_t ipc_version() const { return ipc_version_; }
void AddObserver(SessionObserver* observer) { observers_.AddObserver(observer); }
void RemoveObserver(SessionObserver* observer) { observers_.RemoveObserver(observer); }
void AddBreakpointObserver(BreakpointObserver* observer) {
breakpoint_observers_.AddObserver(observer);
}
void RemoveBreakpointObserver(BreakpointObserver* observer) {
breakpoint_observers_.RemoveObserver(observer);
}
void AddDownloadObserver(DownloadObserver* observer) {
download_observers_.AddObserver(observer);
}
void RemoveDownloadObserver(DownloadObserver* observer) {
download_observers_.RemoveObserver(observer);
}
void AddProcessObserver(ProcessObserver* observer) { process_observers_.AddObserver(observer); }
void RemoveProcessObserver(ProcessObserver* observer) {
process_observers_.RemoveObserver(observer);
}
// Returns information about whether this session is connected to a minidump instead of a live
// system.
bool is_minidump() const { return is_minidump_; }
// Notification about the stream.
void OnStreamReadable();
void OnStreamError();
// Returns true if there is currently a connection.
bool IsConnected() const { return stream_ != nullptr; }
// Returns whether a connection is pending.
bool HasPendingConnection() const { return pending_connection_.get(); }
// Returns the last connection error.
Err last_connection_error() const { return last_connection_error_; }
// Information about the current connection.
const std::string minidump_path() const { return minidump_path_; }
const std::string connected_host() const { return connected_info_.host; }
uint16_t connected_port() const { return connected_info_.port; }
// Call with an empty host and 0 port to reconnect to the last attempted connection destination.
// If there is no previous destination, this will be issue an error.
void Connect(const SessionConnectionInfo& info, fit::callback<void(const Err&)> cb);
// Synchronously disconnects from the remote system. Calling when there is no connection will
// return an error.
//
// This can also be called when a connection is pending (Connect() has been called but the
// callback has not been issued yet) which will cancel the pending connection. The Connect()
// callback will still be issued but will indicate failure.
Err Disconnect();
// Open a minidump instead of connecting to a running system. The callback will be issued with an
// error if the file cannot be opened or if there is already a connection.
void OpenMinidump(const std::string& path, fit::callback<void(const Err&)> callback);
// Frees all connection-related data. A helper for different modes of cleanup. Returns true if
// there was a connection to clear.
bool ClearConnectionData();
// Access to the singleton corresponding to the debugged system.
System& system() { return system_; }
// Platform/architecture of the attached system. Will be "kUnknown" when not connected.
debug::Arch arch() const { return arch_; }
debug::Platform platform() const { return platform_; }
// Architecture information of the attached system.
const ArchInfo& arch_info() const { return *arch_info_; }
// Observer list getters.
fxl::ObserverList<TargetObserver>& target_observers() { return target_observers_; }
fxl::ObserverList<ProcessObserver>& process_observers() { return process_observers_; }
fxl::ObserverList<ThreadObserver>& thread_observers() { return thread_observers_; }
fxl::ObserverList<BreakpointObserver>& breakpoint_observers() { return breakpoint_observers_; }
fxl::ObserverList<DownloadObserver>& download_observers() { return download_observers_; }
fxl::ObserverList<ComponentObserver>& component_observers() { return component_observers_; }
// Dispatches these particular notification types from the agent. These are public since tests
// will commonly want to synthesize these events.
//
// Note on DispatchNotifyException: Test code can skip setting the metadata by clearing the
// set_metadata flag. This allows them to set up the thread's state manually before issuing an
// exception. Production code should always set the set_metadata flag to populate the thread's
// state from the data in the exception.
void DispatchNotifyThreadStarting(const debug_ipc::NotifyThreadStarting& notify);
void DispatchNotifyThreadExiting(const debug_ipc::NotifyThreadExiting& notify);
void DispatchNotifyException(const debug_ipc::NotifyException& notify, bool set_metadata = true);
void DispatchNotifyModules(const debug_ipc::NotifyModules& notify);
void DispatchNotifyProcessStarting(const debug_ipc::NotifyProcessStarting& notify);
void DispatchNotifyProcessExiting(const debug_ipc::NotifyProcessExiting& notify);
void DispatchNotifyIO(const debug_ipc::NotifyIO& notify);
void DispatchNotifyLog(const debug_ipc::NotifyLog& notify);
void DispatchNotifyComponentDiscovered(const debug_ipc::NotifyComponentDiscovered& notify);
void DispatchNotifyComponentStarting(const debug_ipc::NotifyComponentStarting& notify);
void DispatchNotifyComponentExiting(const debug_ipc::NotifyComponentExiting& notify);
void DispatchNotifyTestExited(const debug_ipc::NotifyTestExited& notify);
void DispatchNotifyFilterCreated(const debug_ipc::NotifyFilterCreated& notify);
// Settings that control how the exception is handled in the Session before passing the
// information along to the Thread object to decide what to actually do. This is primarily useful
// for determining what information to tell the thread from the exception notification itself
// as opposed to us requesting more complete information.
//
// In particular, the exception won't give us the complete stack, which is useful in the contexts
// of tests where we want to jump to a specific frame that won't be in the notification's stack.
struct HandleExceptionSettings {
// Typically, we want to use the metadata from the exception to populate the thread's metadata.
// Set this to false to not set metadata from the exception (note: the thread's information will
// be outdated and must be synchronized separately). This default should be kept in sync with
// the default value for the |set_metadata| argument to |DispatchNotifyException| above.
bool set_metadata = true;
// Use this option to set all the metadata from the exception, except the stack frames. This can
// be used to synchronize the stack explicitly but use the rest of the metadata from the
// exception to keep the thread information in sync.
bool skip_metadata_frames = false;
// Do not assume that we have symbols loaded for this process. Only dispatch the exception
// notification.
bool notify_only = false;
};
void HandleException(ThreadImpl* thread, const debug_ipc::NotifyException& notify,
HandleExceptionSettings settings);
// SettingStoreObserver
void OnSettingChanged(const SettingStore&, const std::string& setting_name) override;
// For test purposes, so that the Session appears to be connected.
void set_stream(debug::StreamBuffer* stream) { stream_ = stream; }
AnalyticsReporter& analytics() { return analytics_reporter_; }
protected:
fxl::ObserverList<SessionObserver> observers_;
private:
class PendingConnection;
friend PendingConnection;
friend RemoteAPIImpl;
friend RemoteAPITest;
// Nonspecific callback type. Implemented by SessionDispatchCallback (with the type-specific
// parameter pre-bound). The uint32_t is the transaction ID. If the error is set, the data will be
// invalid and the callback should be issued with the error instead of trying to deserialize.
using Callback = fit::callback<void(const Err&, std::vector<char>)>;
// Set the arch_ and arch_info_ fields.
Err SetArch(debug::Arch arch, debug::Platform platform, uint64_t page_size);
// Checks whether it's safe to begin establishing a connection. If not, the callback is invoked
// with details. The opening_dump argument indicates whether we are trying to open a dump file
// rather than connect to a debug agent.
bool ConnectCanProceed(fit::callback<void(const Err&)>& callback, bool opening_dump);
// Dispatches unsolicited notifications sent from the agent.
void DispatchNotification(const debug_ipc::MsgHeader& header, std::vector<char> data);
// Returns the thread object from the given koids, or null.
ThreadImpl* ThreadImplFromKoid(const debug_ipc::ProcessThreadId& id);
// Resolve a pending connection that has been established. An error could be returned if e.g. the
// protocol is not supported.
Err ResolvePendingConnection(fxl::RefPtr<PendingConnection> pending,
const debug_ipc::HelloReply& reply,
std::unique_ptr<debug::BufferedFD> buffer);
// Sends a hello request to a locally connected debug_agent where the IPC protocol version is
// known. Handles saving the relevant information on the reply, and then calls the callback.
void SendLocalHello(fit::callback<void(const Err&)> cb);
// Saves any information from the "hello" reply.
Err HandleHelloReply(const debug_ipc::HelloReply& reply);
// Syncs the status of the debug agent (like which processes it's already connected to and what
// processes are in limbo). Called after a connection is initiated.
void SyncAgentStatus();
void ListenForSystemSettings();
void AttachToLimboProcessAndNotify(uint64_t koid, const std::string& process_name);
// Commit |core_data| to the filesystem at |path|. If no file name is given, "<name>_<koid>.core"
// is appended to the given path. If no path was given, defaults to "/tmp/<name>_<koid>.core".
// Returns true on successful completion of write operation, false on failure.
Err WriteCoreDataToFile(const std::filesystem::path& path,
const std::vector<uint8_t>& minidump_data);
// Whether we have opened a core dump. Makes much of the connection-related stuff obsolete.
bool is_minidump_ = false;
// Whether to automatically attach to processes found in Process Limbo upon a
// successful connection.
bool auto_attach_limbo_ = false;
// Cache of koids that have been automatically attached from limbo during this session. If a koid
// that has been cached crashes again, it will not be automatically attached to.
//
// This behavior could be seen when a user detaches from a process in limbo (rather than
// explicitly killing it) and it immediately crashes again and ends back up in limbo and would
// otherwise attach automatically again.
std::set<uint64_t> koid_seen_in_limbo_;
// Observers.
fxl::ObserverList<TargetObserver> target_observers_;
fxl::ObserverList<ProcessObserver> process_observers_;
fxl::ObserverList<ThreadObserver> thread_observers_;
fxl::ObserverList<BreakpointObserver> breakpoint_observers_;
fxl::ObserverList<DownloadObserver> download_observers_;
fxl::ObserverList<ComponentObserver> component_observers_;
// Non-owning pointer to the connected stream. If this is non-null and connection_storage_ is
// null, the connection is persistent (made via the constructor) and can't be disconnected.
//
// This could be null when the connection_storage_ isn't when we're waiting for the initial
// connection.
debug::StreamBuffer* stream_ = nullptr;
std::unique_ptr<RemoteAPI> remote_api_;
uint32_t ipc_version_ = 0;
// When using non-persistent connections (no connection passed in via the constructor), this will
// hold the underlying OS connection that is used to back stream_ as well as the
//
// Code should use stream_ for sending and receiving.
std::unique_ptr<debug::BufferedFD> connection_storage_;
// Stores what the session is currently connected to.
std::string minidump_path_;
SessionConnectionInfo connected_info_;
// When a connection has been requested but is being connected on the background thread, this will
// hold the pointer.
fxl::RefPtr<PendingConnection> pending_connection_;
std::map<uint32_t, Callback> pending_;
uint32_t next_transaction_id_ = 1; // Reserve 0 for notifications.
AnalyticsReporter analytics_reporter_;
System system_;
debug::Arch arch_ = debug::Arch::kUnknown;
debug::Platform platform_ = debug::Platform::kUnknown;
std::unique_ptr<ArchInfo> arch_info_; // Guaranteed non-null.
// The last connection that was made by the session. Will have an empty host and a 0 port
// if there has never been a connection.
SessionConnectionInfo last_connection_;
Err last_connection_error_;
fxl::WeakPtrFactory<Session> weak_factory_;
};
} // namespace zxdb
#endif // SRC_DEVELOPER_DEBUG_ZXDB_CLIENT_SESSION_H_