| 1 | \chapter{Unwinding in \CFA}
|
|---|
| 2 |
|
|---|
| 3 | When a function returns, a \emph{single} stack frame is unwound, removing the
|
|---|
| 4 | function's parameters and local variables, and control continues in the
|
|---|
| 5 | function'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
|
|---|
| 7 | local variables for called functions from the exception raise-frame to the
|
|---|
| 8 | exception catch-frame.
|
|---|
| 9 |
|
|---|
| 10 | Unwinding multiple levels is simple for a programming languages without object
|
|---|
| 11 | destructors or block finalizers because a direct transfer is possible from the
|
|---|
| 12 | current stack frame to a prior stack frame, where control continues at a
|
|---|
| 13 | location within the prior caller's function. For example, C provides non-local
|
|---|
| 14 | transfer using $longjmp$, which stores a function's state including its
|
|---|
| 15 | frame pointer and program counter, and simply reloads this information to
|
|---|
| 16 | continue at this prior location on the stack.
|
|---|
| 17 |
|
|---|
| 18 | For programming languages with object destructors or block finalizers it is
|
|---|
| 19 | necessary to walk the stack frames from raise to catch, checking for code that
|
|---|
| 20 | must be executed as part of terminating each frame. Walking the stack has a
|
|---|
| 21 | higher cost, and necessary information must be available to detect
|
|---|
| 22 | destructors/finalizers and call them.
|
|---|
| 23 |
|
|---|
| 24 | A powerful package to provide stack-walking capabilities is $libunwind$,
|
|---|
| 25 | which is used in this work to provide exception handling in \CFA. The following
|
|---|
| 26 | explains how $libunwind$ works and how it is used.
|
|---|
| 27 |
|
|---|
| 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.
|
|---|
| 33 |
|
|---|
| 34 | \section{libunwind Usage}
|
|---|
| 35 |
|
|---|
| 36 | \CFA uses two primary functions in $libunwind$ to create most of its
|
|---|
| 37 | exceptional control-flow: $_Unwind_RaiseException$ and $_Unwind_ForcedUnwind$.
|
|---|
| 38 | Their operation is divided into two phases: search and clean-up. The search
|
|---|
| 39 | phase -- phase 1 -- is used to scan the stack but not unwinding it. The
|
|---|
| 40 | clean-up phase -- phase 2 -- is used for unwinding.
|
|---|
| 41 |
|
|---|
| 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.
|
|---|
| 48 |
|
|---|
| 49 | The raise-exception function uses both phases. It starts by searching for a
|
|---|
| 50 | handler, and if found, performs a clean-up phase to unwind the stack to the
|
|---|
| 51 | handler. If a handler is not found, control returns allowing the
|
|---|
| 52 | exception-handling policy for unhandled exception to be executed. During both
|
|---|
| 53 | phases, the raise-exception function searches down the stack, calling each
|
|---|
| 54 | function's \emph{personality function}.
|
|---|
| 55 |
|
|---|
| 56 | A personality function performs three tasks, although not all have to be
|
|---|
| 57 | present. The tasks performed are decided by the actions provided.
|
|---|
| 58 | % Something argument something bitmask.
|
|---|
| 59 | \begin{itemize}
|
|---|
| 60 | \item$_UA_SEARCH_PHASE$ is called during the clean-up phase and means search
|
|---|
| 61 | for 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
|
|---|
| 64 | personality function?}
|
|---|
| 65 | \item$_UA_CLEANUP_PHASE$ is passed in during the clean-up phase and means part
|
|---|
| 66 | or all of the stack frame is removed. The personality function should do
|
|---|
| 67 | whatever clean-up the language defines (such as running destructors/finalizers)
|
|---|
| 68 | and then generally returns $_URC_CONTINUE_UNWIND$.
|
|---|
| 69 | \item$_UA_HANDLER_FRAME$ means the personality function must install a
|
|---|
| 70 | handler. It is also passed in during the clean-up phase and is in addition to
|
|---|
| 71 | the clean-up action. $libunwind$ provides several helpers for the personality
|
|---|
| 72 | function here. Once it is done, the personality function must return
|
|---|
| 73 | $_URC_INSTALL_CONTEXT$.
|
|---|
| 74 | \end{itemize}
|
|---|
| 75 |
|
|---|
| 76 | Forced unwind only performs the clean-up phase. It is similar to the phase 2
|
|---|
| 77 | section of raise exception with a few changes. A simple difference is that it
|
|---|
| 78 | passes in an extra action to the personality function $_UA_FORCE_UNWIND$, which
|
|---|
| 79 | means a handler cannot be installed. The most difference significant is the
|
|---|
| 80 | addition of the $stop$ function, which is passed in as an argument to forced
|
|---|
| 81 | unwind.
|
|---|
| 82 |
|
|---|
| 83 | The $stop$ function is similar to a personality function. It takes an extra
|
|---|
| 84 | argument: 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
|
|---|
| 86 | unwind code using its own mechanism.
|
|---|
| 87 | % Is there a reason that NO_REASON is used instead of CONTINUE_UNWIND?
|
|---|
| 88 | The $stop$ function is called for each stack frame and at the end of the
|
|---|
| 89 | stack. In a stack frame, it is called before the personality routine with the
|
|---|
| 90 | same arguments (except for the extra $void$ pointer). At the end of the stack,
|
|---|
| 91 | the arguments are mostly the same, except the stack pointer stored in the
|
|---|
| 92 | context is set to null. Because of this change, both GCC and Clang add an extra
|
|---|
| 93 | action in this case $_UA_END_OF_STACK$. The $stop$ function may not return at
|
|---|
| 94 | the end of the stack.
|
|---|
| 95 |
|
|---|
| 96 | {\color{red}This needs work as I do not understand all of it.}
|
|---|
| 97 |
|
|---|
| 98 |
|
|---|
| 99 | \section{\CFA Implementation}
|
|---|
| 100 |
|
|---|
| 101 | To use $libunwind$, \CFA provides several wrappers, its own storage,
|
|---|
| 102 | personality functions, and a $stop$ function.
|
|---|
| 103 |
|
|---|
| 104 | The wrappers perform three tasks: set-up, clean-up and controlling the
|
|---|
| 105 | unwinding. The set-up allocates a copy of the \CFA exception into a handler to
|
|---|
| 106 | control its lifetime, and stores it in the exception context. Clean-up -- run
|
|---|
| 107 | when control exits a catch clause and returns to normal code -- frees the
|
|---|
| 108 | exception 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.
|
|---|
| 111 |
|
|---|
| 112 | The control code in the middle {\color{red}(In the middle of what?)} is run
|
|---|
| 113 | every time a throw or re-throw is called. It uses raise exception to search for
|
|---|
| 114 | a handler and to run it, if one is found. Otherwise, it uses forced unwind to
|
|---|
| 115 | unwind the stack, running all destructors, before terminating the process.
|
|---|
| 116 |
|
|---|
| 117 | The $stop$ function is very simple. It checks the end of stack flag to see if
|
|---|
| 118 | it is finished unwinding. If so, it calls $exit$ to end the process, otherwise
|
|---|
| 119 | it tells the system {\color{red}(What system?)} to continue unwinding.
|
|---|
| 120 | % Yeah, this is going to have to change.
|
|---|
| 121 |
|
|---|
| 122 | The personality routine is more complex because it has to obtain information
|
|---|
| 123 | about the function by scanning the LSDA (Language Specific Data Area). This
|
|---|
| 124 | step allows a single personality function to be used for multiple functions and
|
|---|
| 125 | it accounts for multiple regions{\color{red}(What's a region?)} and possible
|
|---|
| 126 | handlers in a single function.
|
|---|
| 127 | % Not that we do that yet.
|
|---|
| 128 |
|
|---|
| 129 | However, generating the LSDA is difficult. It requires knowledge about the
|
|---|
| 130 | location of the instruction pointer and stack layout, which varies by
|
|---|
| 131 | optimization levels. So for frames where there are only destructors, GCC's
|
|---|
| 132 | attribute cleanup with the $-fexception$ flag is sufficient to handle unwinding.
|
|---|
| 133 |
|
|---|
| 134 | For functions with handlers (defined in the $try$ statement) the function is
|
|---|
| 135 | split into several functions. Everything outside the $try$ statement is the
|
|---|
| 136 | first function, which only has destructors to be run during unwinding. The
|
|---|
| 137 | catch clauses of the $try$ block are then converted into GCC inner functions,
|
|---|
| 138 | which are passed via function pointers while still having access to the outer
|
|---|
| 139 | function's scope. $catchResume$ and $finally$ clauses are handled separately
|
|---|
| 140 | and not discussed here.
|
|---|
| 141 |
|
|---|
| 142 | The $try$ clause {\color{red}You have $try$ statement, $try$ block, and $try$
|
|---|
| 143 | clause, which need clarification.)} is converted to a function directly. The
|
|---|
| 144 | $catch$ clauses are combined into two functions. The first is the match
|
|---|
| 145 | function, which is used during the search phase to find a handler. The second
|
|---|
| 146 | it the catch function, which is a large switch-case for the different
|
|---|
| 147 | handlers. These functions do not interact with unwinding except for running
|
|---|
| 148 | destructors and so can be handled by GCC.
|
|---|
| 149 |
|
|---|
| 150 | These three functions are passed into $try_terminate$, an internal function
|
|---|
| 151 | that represents the $try$ statement. This function uses the generated
|
|---|
| 152 | personality functions as well as assembly statements to create the LSDA. In
|
|---|
| 153 | normal execution, this function only calls the $try$ block closure. However,
|
|---|
| 154 | using $libunwind$, its personality function now handles exception matching and
|
|---|
| 155 | catching. {\color{red}(I don't understand the last sentence.)}
|
|---|
| 156 |
|
|---|
| 157 | During the search phase, the personality function retrieves the match function
|
|---|
| 158 | from the stack using the saved stack pointer. The function is called, either
|
|---|
| 159 | returning 0 for no match or the index (a positive integer) of the handler for a
|
|---|
| 160 | match. If a handler is found, the personality function reports it after saving
|
|---|
| 161 | the index to the exception context.
|
|---|
| 162 |
|
|---|
| 163 | During the clean-up phase there is nothing for the personality function to
|
|---|
| 164 | clean-up in $try_terminate$. So if this is not the handler frame, unwinding
|
|---|
| 165 | continues. If this is the handler frame, control is transferred to the catch
|
|---|
| 166 | function, giving it the exception and the handler index.
|
|---|
| 167 |
|
|---|
| 168 | {\color{red}This needs work as I do not understand all of it.}
|
|---|