blob: 601b570149db030f9753733d48a48f7e4a46ad8b [file] [log] [blame]
// Copyright 2019 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_STACK_H_
#define SRC_DEVELOPER_DEBUG_ZXDB_CLIENT_STACK_H_
#include <optional>
#include <vector>
#include "lib/fit/function.h"
#include "src/developer/debug/ipc/protocol.h"
#include "src/developer/debug/zxdb/symbols/location.h"
#include "src/lib/fxl/macros.h"
#include "src/lib/fxl/memory/weak_ptr.h"
namespace zxdb {
class Err;
class Frame;
class FrameFingerprint;
// Represents the stack of a thread that's suspended or blocked in an exception. If a thread is
// running, blocked (not in an exception), or in any other state, the stack frames are not
// available.
//
// EMPTY, PARTIAL AND COMPLETE STACKS
// ----------------------------------
// When a thread is suspended or blocked in an exception, it will UNUSUALLY have its top frame
// available (the current IP and stack position) and the next (the calling frame) if possible.
//
// Sometimes a thread might have an empty stack (and hence no current location) from an exception.
// This is because exceptions are delivered from the kernel asynchronously, and by the time an
// exception is handled in the debug agent, the thread may have been killed. This will result in
// failred register reads for the thread and no stack or location. Code should never assume there
// are any stack frames.
//
// If the full backtrace is needed, SyncFrames() can be called which will compute the full backtrace
// and issue the callback when complete. This backtrace will be cached until the thread is resumed.
//
// INLINE FRAMES
// -------------
// The thread's current position can be in multiple inline frames at the same time (the first
// address of an inline function is both the first instruction of that function, and the virtual
// "call" of that function in the outer frame). This only applies to the topmost set of inline
// frames since anything below the first physical frame is unambiguous).
//
// To make stepping work as expected, code can adjust which of these ambiguous inline frames the
// stack reports is the top, and inline frames above that are hidden from the normal size() and
// operator[] functions.
class Stack {
public:
// Provides a way for this class to talk to the environment.
class Delegate {
public:
// Requests that the Stack be provided with a new set of frames. The implementation should
// asynchronously request the frame information, call Stack::SetFrames(), then issue the
// callback to indicate completion.
//
// The callback should be issued with an error if the object is destroyed during processing.
virtual void SyncFramesForStack(fit::callback<void(const Err&)> callback) = 0;
// Constructs a Frame implementation for the given IPC stack frame and location. The location
// must be an input since inline frame expansion requires stack frames be constructed with
// different symbols than just looking up the address in the symbols.
virtual std::unique_ptr<Frame> MakeFrameForStack(const debug_ipc::StackFrame& input,
Location location) = 0;
virtual Location GetSymbolizedLocationForStackFrame(const debug_ipc::StackFrame& input) = 0;
};
// The delegate must outlive this class.
explicit Stack(Delegate* delegate);
~Stack();
fxl::WeakPtr<Stack> GetWeakPtr();
// Returns whether the frames in this backtrace are all the frames or only the top 1-2 (see
// class-level comment above).
bool has_all_frames() const { return has_all_frames_; }
size_t size() const { return frames_.size() - hide_ambiguous_inline_frame_count_; }
bool empty() const { return frames_.empty(); }
// Access into the individual frames. The topmost stack frame is index 0. There may be hidden
// inline frames above index 0. Callers should always check the stack size() before accessing
// since the stack is never guaranteed to have elements in it (even during processing an
// exception -- see class comment above).
Frame* operator[](size_t index) {
return frames_[index + hide_ambiguous_inline_frame_count_].get();
}
const Frame* operator[](size_t index) const {
return frames_[index + hide_ambiguous_inline_frame_count_].get();
}
// Returns the index of the frame pointer in this stack if it is there.
std::optional<size_t> IndexForFrame(const Frame* frame) const;
// Returns the inline depth of the frame at the given index. If the frame is a physical frame,
// this will be 0.
size_t InlineDepthForIndex(size_t index) const;
// Computes the stack frame fingerprint for the stack frame at the given index. The index must be
// valid in the current set of frames in this stack object.
//
// See frame_fingerprint.h for a discussion on fingerprints.
FrameFingerprint GetFrameFingerprint(size_t frame_index) const;
// Sets the number of inline frames at the top of the stack to show. See the class-level comment
// above for more.
//
// Anything trimmed should have its current position at the beginning of a code range of an inline
// function for this trimming to make logical sense. The number of inline frames at the top of the
// stack can be modified.
//
// The "top inline frame count" is the number of inline frames above the topmost physical frame
// that exist in the stack. This does not change when the hide count is modified.
//
// From 0 to "top inline frame count" of inline frames can be hidden or unhidden. By default they
// are all visible (hide count = 0).
size_t GetAmbiguousInlineFrameCount() const;
size_t hide_ambiguous_inline_frame_count() const { return hide_ambiguous_inline_frame_count_; }
void SetHideAmbiguousInlineFrameCount(size_t hide_count);
// Queries the size and for frames at indices ignoring any hidden inline frames. With
// FrameAtIndexIndcludingHiddenInline(), the 0th index is always the innermost inline frame and is
// not affected by SetTopInlineFrameShowCount().
size_t SizeIncludingHiddenInline() const { return frames_.size(); }
Frame* FrameAtIndexIncludingHiddenInline(size_t index) { return frames_[index].get(); }
// Requests that all frame information be updated. This can be used to (asynchronously) populate
// the frames when a Stack has only partial frame information, and it can be used to force an
// update from the remote system in case anything changed.
//
// If the stack is destroyed before the frames can be synced, the callback will be issued with an
// error.
void SyncFrames(fit::callback<void(const Err&)> callback);
// Provides a new set of frames computed by a backtrace in the debug_agent. In normal operation
// this is called by the Thread.
//
// This can be called in two cases: (1) when a thread stops to provide a new stack, and (2) when
// updating a stack with more frames. If there are existing frames when SetFrames is called, it
// will assume state (2) if possible (the stack could have changed out from under us) and will
// attempt to preserve the ambiguous inline hide count, etc. consistent with updating an existing
// stack.
//
// If you don't want this behavior, call ClearFrames() first. ClearFrames() will be called whever
// a thread is resumed so fresh stops should get this behavior by default.
void SetFrames(debug_ipc::ThreadRecord::StackAmount amount,
const std::vector<debug_ipc::StackFrame>& frames);
// Sets the frames to a known set to provide synthetic stacks for tests.
void SetFramesForTest(std::vector<std::unique_ptr<Frame>> frames, bool has_all);
// Removes all frames. In normal operation this is called by the Thread when things happen that
// invalidate all frames such as resuming the thread.
//
// Callers should generally do this via the thread. Code in ThreadImpl should use
// ThreadImpl::ClearFrames instead which will send observer notifications.
//
// Returns true if anything was modified (false means there were no frames to clear).
bool ClearFrames();
private:
// Adds the given stack frame to the end of the current stack (going backwards in time). Inline
// frames will be expanded so this may append more than one frame.
void AppendFrame(const debug_ipc::StackFrame& record);
Delegate* delegate_;
std::vector<std::unique_ptr<Frame>> frames_;
bool has_all_frames_ = false;
// Number of frames to hide from size() and operator[] that are inline frames at the top of the
// stack that shouldn't be exposed right now.
size_t hide_ambiguous_inline_frame_count_ = 0;
fxl::WeakPtrFactory<Stack> weak_factory_;
FXL_DISALLOW_COPY_AND_ASSIGN(Stack);
};
} // namespace zxdb
#endif // SRC_DEVELOPER_DEBUG_ZXDB_CLIENT_STACK_H_