Files
TheRock/rocm-systems/projects/rocprofiler-systems/external/dyninst/stackwalk/doc/4-Callbacks.tex
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

369 lines
16 KiB
TeX

\section{Callback Interface Default Implementations}
StackwalkerAPI provides one or more default implementations of each of the
callback classes described in Section 3.5. These implementations are used by a
default configuration of StackwalkerAPI.
\subsection{Debugger Interface}
\label{subsec:debugger}
This section describes how to use StackwalkerAPI for collecting 3rd party stack
walks. In 3rd party mode StackwalkerAPI uses the OS's debugger interface to
connect to another process and walk its call stacks. As part of being a debugger
StackwalkerAPI receives and needs to handle debug events. When a debugger event
occurs, StackwalkerAPI must get control of the host process in order to receive
the debugger event and continue the target process.
To illustrate the complexities with running in 3rd party mode, consider the
follow code snippet that uses StackwalkerAPI to collect a stack walk every five
seconds.
\begin{lstlisting}
Walker *walker = Walker::newWalker(pid);
std::vector<Frame> swalk;
for (;;) {
walker->walkStack(swalk);
sleep(5);
}
\end{lstlisting}
StackwalkerAPI is running in 3rd party mode, since it attached to the target
process, \code{pid}. As the target process runs it may be generating debug events such
a thread creation and destruction, library loads and unloads, signals,
forking/execing, etc. When one of these debugger events is generated the OS will
pause the target process and send a notice to the host process. The target
process will remain paused until the host process handles the debug event and
resumes the target process.
In the above example the host process is spending almost all of its time in the
sleep call. If a debugger event happens during the sleep, then StackwalkerAPI
will not be able to get control of the host process and handle the event for up
to five seconds. This will cause long pauses in the target process and lead to a
potentially very large slowdown.
To work around this problem StackwalkerAPI provides a notification file
descriptor. This file descriptor represents a connection between the
StackwalkerAPI library and user code. StackwalkerAPI will write a single byte to
this file descriptor when a debug event occurs, thus notifying the user code
that it needs to let StackwalkerAPI receive and handle debug events. The user
code can use system calls such as select to watch for events on the notification
file descriptor.
The following example illustrates how to properly use StackwalkerAPI to collect
a stack walk from another process at a five second interval. Details on the
\code{ProcDebug} class, \code{getNotificationFD} method, and
\code{handleDebugEvent} method can be
found in Section~\ref{subsubsec:procdebug}. See the UNIX man pages for more information on the
\code{select} system call. Note that this example does not include all of the proper
error handling and includes that should be present when using \code{select}.
\begin{lstlisting}
Walker *walker = Walker::newWalker(pid);
ProcDebug *debugger = (ProcDebug *) walker->getProcessState();
std::vector<Frame> swalk;
for (;;) {
walker->walkStack(swalk);
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
int max = 1;
fd_set readfds, writefds, exceptfds;
FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds);
FD_SET(ProcDebug::getNotificationFD(), &readfds);
for (;;) {
int result = select(max, &readfds, &writefds, &exceptfds, &timeout);
if (FD_ISSET(ProcDebug::getNotificationFD(), readfds)) {
//Debug event
ProcDebug::handleDebugEvent();
}
if (result == 0) {
//Timeout
break;
}
}
}
\end{lstlisting}
\subsubsection{Class ProcDebug}
\label{subsubsec:procdebug}
\definedin{procstate.h}
Access to StackwalkerAPI's debugger is through the \code{ProcDebug} class, which
inherits from the \code{ProcessState} interface. The easiest way to get at a
\code{ProcDebug} object is to cast the return value of
\code{Walker::getProcessState} into a \code{ProcDebug}. C++'s
\code{dynamic\_cast} operation can be used to test if a \code{Walker} uses the
\code{ProcDebug} interface:
\begin{lstlisting}
ProcDebug *debugger;
debugger = dynamic_cast<ProcDebug*>(walker->getProcessState());
if (debugger != NULL) {
//3rd party
...
} else {
//1st party
...
}
\end{lstlisting}
In addition to the handling of debug events, described in
Section~\ref{subsec:debugger}, the \code{ProcDebug} class provides a process
control interface; users can pause and resume process or threads, detach from a
process, and test for events such as process death. As an implementation of the
\code{ProcessState} class, \code{ProcDebug} also provides all of the
functionality described in Section~\ref{subsec:processstate}.
\begin{apient}
virtual bool pause(Dyninst::THR_ID tid = NULL_THR_ID)
\end{apient}
\apidesc{
This method pauses a process or thread. The paused object will not resume
execution until \code{ProcDebug::resume} is called. If the \code{tid} parameter is not
\code{NULL\_THR\_ID} then StackwalkerAPI will pause the thread specified by
\code{tid}. If
\code{tid} is \code{NULL\_THR\_ID} then StackwalkerAPI will pause every thread in the
process.
When StackwalkerAPI collects a call stack from a running thread it first
pauses the thread, collects the stack walk, and then resumes the thread.
When collecting a call stack from a paused thread StackwalkerAPI will
collect the stack walk and leave the thread paused. This method is thus
useful for pausing threads before stack walks if the user needs to keep the
returned stack walk synchronized with the current state of the thread.
This method returns \code{true} if successful and \code{false} on error.
}
\begin{apient}
virtual bool resume(Dyninst::THR_ID tid = NULL_THR_ID)
\end{apient}
\apidesc{
This method resumes execution on a paused process or thread. This method
only resumes threads that were paused by the \code{ProcDebug::pause} call, using it
on other threads is an error. If the \code{tid} parameter is not
\code{NULL\_THR\_ID} then
StackwalkerAPI will resume the thread specified by \code{tid}. If \code{tid} is
\code{NULL\_THR\_ID} then StackwalkerAPI will resume all paused threads in the
process.
This method returns \code{true} if successful and \code{false} on error.
}
\begin{apient}
virtual bool detach(bool leave_stopped = false)
\end{apient}
\apidesc{
This method detaches StackwalkerAPI from the target process. StackwalkerAPI
will no longer receive debug events on this target process and will no
longer be able to collect call stacks from it. This method invalidates the
associated \code{Walker} and \code{ProcState} objects, they should be cleaned using C++'s
\code{delete} operator after making this call. It is an error to attempt to do
operations on these objects after a detach, and undefined behavior may
result.
If the \code{leave\_stopped} parameter is \code{true} StackwalkerAPI will detach from the
process but leave it in a paused state so that it does resume progress. This
is useful for attaching another debugger back to the process for further
analysis. The \code{leave\_stopped} parameter is not supported on the Linux platform
and its value will have no affect on the detach call.
This method returns \code{true} if successful and \code{false} on error.
}
\begin{apient}
virtual bool isTerminated()
\end{apient}
\apidesc{
This method returns \code{true} if the associated target process has
terminated and \code{false} otherwise. A target process may terminate itself by
calling exit, returning from main, or receiving an unhandled signal.
Attempting to collect stack walks or perform other operations on a
terminated process is illegal an will lead to undefined behavior.
A process termination will also be signaled through the notification FD.
Users should check processes for the isTerminated state after returning from
handleDebugEvent.
}
\begin{apient}
static int getNotificationFD()
\end{apient}
\apidesc{
This method returns StackwalkerAPI's notification FD. The notification FD is
a file descriptor that StackwalkerAPI will write a byte to whenever a debug
event occurs that need. If the user code sees a byte on this file descriptor
it should call \code{handleDebugEvent} to let StackwalkerAPI handle the debug
event. Example code using \code{getNotificationFD} can be found in
Section~\ref{subsec:debugger}.
StackwalkerAPI will only create one notification FD, even if it is attached to multiple 3rd party target processes.
}
\begin{apient}
static bool handleDebugEvent(bool block = false)
\end{apient}
\apidesc{
When this method is called StackwalkerAPI will receive and handle all
pending debug events from each 3rd party target process to which it is
attached. After handling debug events each target process will be continued
(unless it was explicitly stopped by the ProcDebug::pause method) and any
bytes on the notification FD will be cleared. It is generally expected that
users will call this method when a event is sent to the notification FD,
although it can be legally called at any time.
If the \code{block} parameter is \code{true}, then \code{handleDebugEvents}
will block until it has handled at least one debug event. If the block
parameter is \code{false}, then handleDebugEvents will handle any currently pending
debug events or immediately return if none are available.
StackwalkerAPI may receive process exit events for target processes while
handling debug events. The user should check for any exited processes by
calling \code{ProcDebug::isTerminated} after handling debug events.
This method returns \code{true} if successful and \code{false} on error.
}
\subsection{FrameSteppers}\label{sec:framesteppers}
\definedin{framestepper.h}
StackwalkerAPI ships with numerous default implementations of the
\code{FrameStepper}
class. Each of these \code{FrameStepper} implementations allow StackwalkerAPI to walk a
type of call frames. Section~\ref{subsec:defaults} describes which
\code{FrameStepper} implementations
are available on which platforms. This sections gives a brief description of
what each \code{FrameStepper} implementation does. Each of the following classes
implements the \code{FrameStepper} interface described in
Section~\ref{subsec:framestepper}, so we do not
repeat the API description for the classes here.
Several of the \code{FrameStepper}s use helper classes (see
\code{FrameFuncStepper} as an
example). Users can further customize the behavior of a \code{FrameStepper} by
providing their own implementation of these helper classes.
\subsubsection{Class FrameFuncStepper}
This class implements stack walking through a call frame that is setup with the
architectures standard stack frame. For example, on x86 this \code{FrameStepper} will
be used to walk through stack frames that are setup with a \code{push \%ebp/mov
\%esp,\%ebp} prologue.
\paragraph{Class FrameFuncHelper}
\code{FrameFuncStepper} uses a helper class, \code{FrameFuncHelper}, to get information on
what kind of stack frame it's walking through. The \code{FrameFuncHelper} will
generally use techniques such as binary analysis to determine what type of stack
frame the \code{FrameFuncStepper} is walking through. Users can have StackwalkerAPI use
their own binary analysis mechanisms by providing an implementation of this
\code{FrameFuncHelper}.
There are two important types used by \code{FrameFuncHelper} and one important function:
\begin{apient}
typedef enum {
unknown_t=0,
no_frame,
standard_frame,
savefp_only_frame,
} frame_type;
\end{apient}
\apidesc{
The \code{frame\_type} describes what kind of stack frame a function uses. If it
does not set up a stack frame then \code{frame\_type} should be
\code{no\_frame}. If it sets
up a standard frame then \code{frame\_type} should be \code{standard\_frame}. The
\code{savefp\_only\_frame} value currently only has meaning on the x86 family of
systems, and means that a function saves the old frame pointer, but does not
setup a new frame pointer (it has a \code{push \%ebp} instruction, but no \code{mov
\%esp,\%ebp}). If the \code{FrameFuncHelper} cannot determine the
\code{frame\_type}, then it should be assigned the value \code{unknown\_t}.
}
\begin{apient}
typedef enum {
unknown_s=0,
unset_frame,
halfset_frame,
set_frame
} frame_state;
\end{apient}
\apidesc{
The \code{frame\_state} type determines the current state of function with a stack
frame at some point of execution. For example, a function may set up a
standard stack frame and have a \code{frame\_type} of \code{standard\_frame}, but execution
may be at the first instruction in the function and the frame is not yet
setup, in which case the \code{frame\_state} will be \code{unset\_frame}.
If the function sets up a standard stack frame and the execution point is
someplace where the frame is completely setup, then the \code{frame\_state} should be
\code{set\_frame}. If the function sets up a standard frame and the execution point is
at a point where the frame does not yet exist or has been torn down, then
\code{frame\_state} should be \code{unset\_frame}. The \code{halfset\_frame}
value of \code{frame\_state} is
currently only meaningful on the x86 family of architecture, and should if the
function has saved the old frame pointer, but not yet set up a new frame
pointer.
}
\begin{apient}
typedef std::pair<frame_type, frame_state> alloc_frame_t;
virtual alloc_frame_t allocatesFrame(Address addr) = 0;
\end{apient}
\apidesc{
The \code{allocatesFrame} function of \code{FrameFuncHelper} returns a
\code{alloc\_frame\_t} that
describes the frame\_type of the function at \code{addr} and the
\code{frame\_state} of the
function when execution reached \code{addr}.
If \code{addr} is invalid or an error occurs, allocatedFrame should return
\code{alloc\_frame\_t(unknown\_t, unknown\_s)}.
}
\subsubsection{Class SigHandlerStepper}
The \code{SigHandlerStepper} is used to walk through UNIX signal handlers as found on
the call stack. On some systems a signal handler generates a special kind of
stack frame that cannot be walked through using normal stack walking techniques.
\subsubsection{Class DebugStepper}
This class uses debug information found in a binary to walk through a stack
frame. It depends on SymtabAPI to read debug information from a binary, then
uses that debug information to walk through a call frame.
Most binaries must be built with debug information (\code{-g} with \code{gcc}) in order to
include debug information that this \code{FrameStepper} uses. Some languages, such as
C++, automatically include stackwalking debug information for use by exceptions.
The \code{DebugStepper} class will also make use of this kind of exception
information if it is available.
\subsubsection{Class AnalysisStepper}
This class uses dataflow analysis to determine possible stack sizes at
all locations in a function as well as the location of the frame
pointer. It is able to handle optimized code with omitted frame
pointers and overlapping code sequences.
\subsubsection{Class StepperWanderer}
This class uses a heuristic approach to find possible return addresses
in the stack frame. If a return address is found that matches a valid
caller of the current function, we conclude it is the actual return
address and construct a matching stack frame. Since this approach is
heuristic it can make mistakes leading to incorrect stack
information. It has primarily been replaced by the
\code{AnalysisStepper} described above.
\subsubsection{Class BottomOfStackStepper}
The \code{BottomOfStackStepper} doesn't actually walk through any type of call frame.
Instead it attempts to detect whether the bottom of the call stack has been
reached. If so, \code{BottomOfStackStepper} will report \code{gcf\_stackbottom} from its
\code{getCallerFrame} method. Otherwise it will report \code{gcf\_not\_me}.
\code{BottomOfStackStepper}
runs with a higher priority than any other \code{FrameStepper} class.