source: doc/theses/andrew_beach_MMath/unwinding.tex @ 6c6e36c

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

second pass over Andrew's Chapter 1

  • Property mode set to 100644
File size: 9.2 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's 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 be 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}
36\CFA uses two primary functions in $libunwind$ to create most of its
37exceptional control-flow: $_Unwind_RaiseException$ and $_Unwind_ForcedUnwind$.
38Their operation is divided into two phases: search and clean-up. The search
39phase -- phase 1 -- is used to scan the stack but not unwinding it. The
40clean-up phase -- phase 2 -- is used for unwinding.
42% Somewhere around here I need to talk about the control structures.
43% $_Unwind_Exception$ is used to carry the API's universal data. Some
44% of this is internal, other fields are used to communicate between different
45% exception handling mechanisms in different runtimes.
46% $_Unwind_Context$ is an opaque data structure that is used to pass
47% information to helper functions.
49The raise-exception function uses both phases. It starts by searching for a
50handler, and if found, performs a clean-up phase to unwind the stack to the
51handler. If a handler is not found, control returns allowing the
52exception-handling policy for unhandled exception to be executed.  During both
53phases, the raise-exception function searches down the stack, calling each
54function's \emph{personality function}.
56A personality function performs three tasks, although not all have to be
57present. The tasks performed are decided by the actions provided.
58% Something argument something bitmask.
60\item$_UA_SEARCH_PHASE$ is called during the clean-up phase and means search
61for handlers. If a hander is found, the personality function should return
62$_URC_HANDLER_FOUND$, otherwise it returns $_URC_CONTINUE_UNWIND$.
63{\color{red}What is the connection between finding the handler and the
64personality function?}
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 difference is that it
78passes in an extra action to the personality function $_UA_FORCE_UNWIND$, which
79means a handler cannot be installed. The most difference significant is the
80addition of the $stop$ function, which is passed in as an argument to forced
83The $stop$ function is similar to a personality function. It takes an extra
84argument: a $void$ pointer passed into force unwind. It may return
85$_URC_NO_REASON$ to continue unwinding or it can transfer control out of the
86unwind code using its own mechanism.
87% Is there a reason that NO_REASON is used instead of CONTINUE_UNWIND?
88The $stop$ function is called for each stack frame and at the end of the
89stack. In a stack frame, it is called before the personality routine with the
90same arguments (except for the extra $void$ pointer). At the end of the stack,
91the arguments are mostly the same, except the stack pointer stored in the
92context is set to null. Because of this change, both GCC and Clang add an extra
93action in this case $_UA_END_OF_STACK$.  The $stop$ function may not return at
94the end of the stack.
96{\color{red}This needs work as I do not understand all of it.}
99\section{\CFA Implementation}
101To use $libunwind$, \CFA provides several wrappers, its own storage,
102personality functions, and a $stop$ function.
104The wrappers perform three tasks: set-up, clean-up and controlling the
105unwinding. The set-up allocates a copy of the \CFA exception into a handler to
106control its lifetime, and stores it in the exception context.  Clean-up -- run
107when control exits a catch clause and returns to normal code -- frees the
108exception copy.
109% It however does not set up the unwind exception so we can't use any inter-
110% runtime/language features. Also the exception context is global.
112The control code in the middle {\color{red}(In the middle of what?)} is run
113every time a throw or re-throw is called. It uses raise exception to search for
114a handler and to run it, if one is found. Otherwise, it uses forced unwind to
115unwind the stack, running all destructors, before terminating the process.
117The $stop$ function is very simple. It checks the end of stack flag to see if
118it is finished unwinding. If so, it calls $exit$ to end the process, otherwise
119it tells the system {\color{red}(What system?)} to continue unwinding.
120% Yeah, this is going to have to change.
122The personality routine is more complex because it has to obtain information
123about the function by scanning the LSDA (Language Specific Data Area). This
124step allows a single personality function to be used for multiple functions and
125it accounts for multiple regions{\color{red}(What's a region?)} and possible
126handlers in a single function.
127% Not that we do that yet.
129However, generating the LSDA is difficult. It requires knowledge about the
130location of the instruction pointer and stack layout, which varies by
131optimization levels. So for frames where there are only destructors, GCC's
132attribute cleanup with the $-fexception$ flag is sufficient to handle unwinding.
134For functions with handlers (defined in the $try$ statement) the function is
135split into several functions. Everything outside the $try$ statement is the
136first function, which only has destructors to be run during unwinding. The
137catch clauses of the $try$ block are then converted into GCC inner functions,
138which are passed via function pointers while still having access to the outer
139function's scope. $catchResume$ and $finally$ clauses are handled separately
140and not discussed here.
142The $try$ clause {\color{red}You have $try$ statement, $try$ block, and $try$
143clause, which need clarification.)} is converted to a function directly. The
144$catch$ clauses are combined into two functions. The first is the match
145function, which is used during the search phase to find a handler. The second
146it the catch function, which is a large switch-case for the different
147handlers. These functions do not interact with unwinding except for running
148destructors and so can be handled by GCC.
150These three functions are passed into $try_terminate$, an internal function
151that represents the $try$ statement. This function uses the generated
152personality functions as well as assembly statements to create the LSDA.  In
153normal execution, this function only calls the $try$ block closure. However,
154using $libunwind$, its personality function now handles exception matching and
155catching. {\color{red}(I don't understand the last sentence.)}
157During the search phase, the personality function retrieves the match function
158from the stack using the saved stack pointer. The function is called, either
159returning 0 for no match or the index (a positive integer) of the handler for a
160match. If a handler is found, the personality function reports it after saving
161the index to the exception context.
163During the clean-up phase there is nothing for the personality function to
164clean-up in $try_terminate$. So if this is not the handler frame, unwinding
165continues. If this is the handler frame, control is transferred to the catch
166function, giving it the exception and the handler index.
168{\color{red}This needs work as I do not understand all of it.}
Note: See TracBrowser for help on using the repository browser.