source: doc/theses/andrew_beach_MMath/unwinding.tex @ 21f914e

Last change on this file since 21f914e was 21f914e, checked in by Peter A. Buhr <pabuhr@…>, 4 years ago

first pass over Chapter 1

  • Property mode set to 100644
File size: 8.9 KB
1\chapter{Unwinding in \CFA}
3When a function returns, a \emph{single} stack frame is unwound, removing the
4function's parameters and local variables, and control continues in the
5function caller using the caller's stack frame.  When an exception is raised,
6\emph{multiple} stack frames are unwound, removing the function parameters and
7local variables for called functions from the exception raise-frame to the
8exception catch-frame.
10Unwinding multiple levels is simple for a programming languages without object
11destructors or block finalizers because a direct transfer is possible from the
12current stack frame to a prior stack frame, where control continues at a
13location within the prior caller's function. For example, C provides non-local
14transfer using $longjmp$, which stores a function's state including its
15frame pointer and program counter, and simply reloads this information to
16continue at this prior location on the stack.
18For programming languages with object destructors or block finalizers it is
19necessary to walk the stack frames from raise to catch, checking for code that
20must be executed as part of terminating each frame. Walking the stack has a
21higher cost, and necessary information must available to detect
22destructors/finalizers and call them.
24A powerful package to provide stack-walking capabilities is $libunwind$,
25which is used in this work to provide exception handling in \CFA. The following
26explains how $libunwind$ works and how it is used.
28% Stack unwinding is the process of removing things from the stack from outside
29% the functions there. In languages that don't provide a way to guaranty that
30% code will run when the program leaves a scope or finishes a function, this
31% can be relatively trivial. C does this with $longjmp$ by setting the
32% stack pointer and a few other registers.
34\section{libunwind Usage}
36There are two primary functions that \CFA uses in $libunwind$ to create most of
37the exceptional control-flow. These functions are
38$_Unwind_RaiseException$ and $_Unwind_ForcedUnwind$.
40Their operation is divided into two phases: search and clean-up. The search
41phase -- phase 1 -- is used to scan the stack but not unwinding it. The
42clean-up phase -- phase 2 -- is used to during unwinding.
44% Somewhere around here I need to talk about the control structures.
45% $_Unwind_Exception$ is used to carry the API's universal data. Some
46% of this is internal, other fields are used to communicate between different
47% exception handling mechanisms in different runtimes.
48% $_Unwind_Context$ is an opaque data structure that is used to pass
49% information to helper functions.
51The raise-exception function uses both phases. It starts by searching for the
52handler, and if found, performs a clean-up phase to unwind the stack to the
53hander. If a handler is not found, control returns allowing the
54exception-handling policy for unhandled exception to be executed.  During both
55phases, the raise exception function searches down the stack, calling each
56function's \emph{personality function}.
58A personality function performs three tasks, although not all of them have to
59be present. The tasks performed are decided by the actions provided.
60% Something argument something bitmask.
62\item$_UA_SEARCH_PHASE$ is called during the clean-up phase and means search
63for handlers. If the hander is found, the personality function should return
64$_URC_HANDLER_FOUND$, otherwise it returns $_URC_CONTINUE_UNWIND$.
65\item$_UA_CLEANUP_PHASE$ is passed in during the clean-up phase and means part
66or all of the stack frame is removed. The personality function should do
67whatever clean-up the language defines (such as running destructors/finalizers)
68and then generally returns $_URC_CONTINUE_UNWIND$.
69\item$_UA_HANDLER_FRAME$ means the personality function must install a
70handler. It is also passed in during the clean-up phase and is in addition to
71the clean-up action. Libunwind provides several helpers for the personality
72function here. Once it is done, the personality function must return
76Forced unwind only performs the clean-up phase. It is similar to the phase 2
77section of raise exception with a few changes. A simple one is that it passes
78in an extra action to the personality function $_UA_FORCE_UNWIND$, which means
79a handler cannot be installed. The most significant is the addition of the $stop$
80function, which is passed in as an argument to the forced unwind.
82The stop function is a lot like a personality function. It takes an extra
83argument, a void pointer passed into force unwind. It may return
84$_URC_NO_REASON$ to continue unwinding or it can transfer control out of the
85unwind code using its own mechanism.
86% Is there a reason that NO_REASON is used instead of CONTINUE_UNWIND?
87The stop function is called once on every stack frame and once at the end of
88the stack. In a stack frame, it is called before the personality routine with
89the same arguments (except for the extra void pointer). At the end of the
90stack, the arguments are mostly the same, except the stack pointer stored in
91the context is set to null. Because of the significance of that change, both
92GCC and Clang add an extra action in this case $_UA_END_OF_STACK$.  The stop
93function may not return at the end of the stack.
95\section{\CFA Implementation}
97To use $libunwind$, \CFA provides several wrappers, its own storage, the
98personality functions, and a stop function.
100The wrappers perform three tasks: set-up, clean-up and controlling the
101unwinding. The set-up allocates a copy of the \CFA exception into a handler
102so it can control its lifetime, and stores it in the exception context.
103Clean-up -- run when control exits a catch clause and return to normal code --
104frees the exception copy.
105% It however does not set up the unwind exception so we can't use any inter-
106% runtime/language features. Also the exception context is global.
108The control code in the middle is run every time a throw or re-throw is
109called. It uses raise exception to search for a handler and to run it if one
110is found. Otherwise it uses forced unwind to unwind the stack, running all
111destructors, before terminating the process.
113The stop function is very simple, it checks the end of stack flag to see if it
114is finished unwinding. If so, it calls exit to end the process, otherwise it
115tells the system to continue unwinding.
116% Yeah, this is going to have to change.
118The personality routine is much more complicated.  First, because it has to get
119some information about the function by scanning the LSDA (Language Specific
120Data Area). This allows a single personality function to be used for multiple
121functions and it accounts for multiple regions and possible handlers in a
122single function.
123% Not that we do that yet.
125However generating the LSDA is very difficult. It requires a lot of knowledge
126about the location of the instruction pointer and stack layout that can be
127broken by optimization. So instead for frames where there are only destructors
128we use GCC's attribute cleanup and the -fexception flag to handle unwinding
129without adding our own functionality.
131For functions that do have handlers (defined in try statements) the function
132is split into several functions. Everything outside the try statement is the
133first function, which has then has only destructors to be run during
134unwinding. The clauses of the try block are then converted into GCC inner
135functions which can be passed around with function pointers while still having
136access to the outer function's scope. $catchResume$ and $finally$
137clauses are handled separately and will not be discussed here.
139The $try$ clause is converted to a function directly. The $catch$
140clauses are combined and then create two functions. The first is the match
141function which is used during the search phase to see if any of the handler
142here. The second it the catch function, which is a large switch-case block
143with the different handlers. All of these function do not interact with
144unwinding except for running destructors and so can be handled by GCC.
146These three functions are passed into $try_terminate$, an internal
147function that represents the try statement. This is the only function with
148our personality function as well as assembly statements that create the LSDA.
149In normal execution all the function does is call the try block closure and
150return once that has finished executing. However using $libunwind$ its
151personality function now handles exception matching and catching.
153During the search phase the personality function retrieves the match function
154from the stack using the saved stack pointer. The function is called, either
155returning 0 for no match or the index (a positive integer) of the handler
156that will handle it. The if a handler was found the personality function
157reports it after saving the index to the exception context.
159During the clean-up phase there is nothing for the personality function to
160clean-up in $try_terminate$. So if this is not the handler frame
161unwinding continues. If this is the handler frame than control is transferred
162to the catch function, giving it the exception and the handler index.
Note: See TracBrowser for help on using the repository browser.