- So welcome everyone, my
name is James McNellis. I'm an engineer at Microsoft, where I work on our time travel debugging toolkit. I'm not actually not going to be talking at all about that today, so if you're interested in
more information about that, I'd highly recommend you check out our talk from last year's CppCon, where we announced and released the first preview of those tools. Today, though, I'll be
talking about C++ Exceptions. Specifically, how C++
Exceptions work on Windows in the Visual C++ implementation. I will not be discussing
how C++ Exceptions work on other platforms, whether you should use exceptions in C++, how
best to use exceptions, or how to write exceptions save C++. Those are all very interesting subjects. We've had talks on many of
those at previous CppCons. I'd love to see talks on others
of those at future CppCons, but they're out of scope for today's talk. We're going to focus solely on how C++ Exceptions work on Windows. So C++ Exceptions, this
being CppCon I'm sure that most of us are at
least somewhat familiar with C++ Exceptions. We can write throw expressions
that throw exceptions, and we can write try catch
blocks to handle them. But, today, C++ Exceptions will be at the end of our journey. So we're going to start
at the bottom of the stack and work our way towards them. If you've done much
programming on Windows, you may be familiar with
structured exceptions as well. And you may have written these try except blocks to handle them. We will meet these on our way, and we will meet these language
extensions on our journey, but this is also not
where our journey begins. We're going to start all the
way at the very beginning. What happens when something
goes wrong, like really wrong? So I'm gonna tell you a story. Once upon a time, many years ago, I was working on a program
on my trusty 32-bit x86 PC. Those details are actually important. In this program I had a
global constant named, conveniently enough, ConstantZero,
and it's value was zero. But I had a problem. At the beginning of my program, the value of this global variable was
zero, and that was fine. But later in my program, I really needed this global variable to
have a different value. So I did what any programmer would do, and I just tried to change it. Unfortunately, the C++ compiler was in one of it's grumpy moods and it told me "no, you cannot assign to
a variable that is const." My teammates told me I should just remove the const keyword, but as a professional C++ progammer,
I had a better idea. I added a const_cast, and to that the compiler gave me a big thumbs up. So I copied the program onto my trusty floppy disk, and ran it. Unfortunately, I was very
surprised by the result. So it printed the first message, but where did my second message go? So then I checked to see
if something went wrong and I discovered that my program returned this big, scary negative number. I'm much more used to my
programs returning zero. So, what happened? When I declared this
global constant variable in my program, what I was actually doing is telling the compiler to tell the linker to tell the operating
system to tell the CPU, "don't let this thing get modified." So the CPU is executing the
instructions in my program, and it gets to this part where the program tried to write to that global variable. And the CPU remembers how we told it not to let anyone do that. So it metaphorically throws up it's hands and asks, "what are you doing?" Though the CPU doesn't
actually speak English so, what it really does, is it signals a general protection exception, or fault. This is not a C++ Exception, this is a hardware exception actually
signaled from the CPU. This causes control to
transfer from your program back to the operating system
via the interrupt table, and the operating system
now is responsible for fixing the problem. So the OS is going to do two things here. The first thing the OS is going to do, is it's going to record the
current state of the CPU so that it knows the state the program was in when the exception occurred. So the state of the CPU is
stored in a context structure, and consists of the values
of all the CPU registers. Since CPU registers are
architecture-specific, the definitely of this context structure is different on different architectures. We're discussing 32-bit x86 here, so this is the 32-bit x86 context. You can see, for example, that it has the integer registers
like Eax and Ebx. And it has the Control Registers like the instruction pointer Eip, and the stack pointer Esp. For our purposes, the actual
contents don't matter so much. The important thing is that the OS records the state of the CPU. The second thing the OS is going to do, is record information
about what went wrong by creating an exception record. So, whereas the context
is architecture-specific, the exception record contains an architecture-neutral
description of the failure. So let's look at the exception record that the OS will create for this general protection
exception that the CPU signaled. The first and most important member of the exception record
is the exception code, which indicates the type of the problem. For our error, the OS uses
the special status code status_access_violation, which is the architecture-neutral equivalent of the x86-specific general
protection exception. Next, the exception record can have flags and a nested exception. No flags are needed here, and we don't have a nested exception, so these are just left as their default values. The exception record contains the address at which the exception occurred, so this is generally the address of the instruction that
caused the exception. In this case, it's the
address of what would be a move instruction that tried
to modify the global variable. Finally, the exception record can carry exception-specific payloads, so up to 15 parameters can be included. For an access violation,
only two parameters are used. The first parameter contains a flag indicating the kind of access violation. So, here it was a write fault, but it also could have been
a read or an execute fault. The second parameter contains the address of the access that faulted. So, whereas the exception
address member contains the address of the instruction
that caused the fault, this address parameter
contains the address of the memory location that the
instruction tried to access. These parameter values can be found in the documentation for exception record if you go look on MSDN. So the OS has now saved the CPU context at the point where the problem occurred, and it's put together an exception record containing a generalized
description of the problem. Now what? So it has a few options. If it wanted to be overly dramatic, it could shut down the machine or give us one of those
famous blue craft screens. If the error happened
inside of the kernel, this might actually be a
reasonable thing to do. But this error happened
inside of our program, not the kernel, so killing whole machine would be overly drastic. A more reasonable option would be for the OS just to kill our program. We did something wrong, it wouldn't be entirely unreasonable
for the OS to punish us. But, no, the OS really wants things to work out between us, so it's going to give us an opportunity
to make things right. The OS is going to tell our
program about the failure, and it's going to ask us
if we'd like to fix things, or if we'd have some way that
we'd like to handle the error. How is it going to do this? It's going to raise what's called a structured exception
inside of our program. So structured exception handling, or SEH, is the generalized error
propagation mechanism used on Windows for this
kind of asynchronous error. It's part of the Windows
ABI, so the binary contract between your program and
the operating system. It is language-neutral,
though our focus today will be on C and C++, it's also usable in other languages and
environments on Windows. So our program needs some way
to tell the OS whether and how we would like to handle
this structured exception. In a Windows program, every
thread has an associated data structure called thread
environment block, or TEB. Most of the contents
of this data structure are internal implementation
details of the operating system, so your program should not touch them. But the first few members of the TEB are documented for our
program to use, and these form what is called the thread
information block, or TIB. The first member of this data structure is the exception list, and
it is rather poorly named. It would be better named if it were called the exception handler list. This exception list member
points to a linked list of exception handler
functions for the thread. So each handler is specified using an exception registration record, which is allocated on
the stack of the thread. Here, for example, we have an
exception registration record that points to the FooHandler function. Then the next handler
registration in the list is for the BarHandler function. Then we have one for the BazHandler. And then the end of the list is indicated by a pointer with all bits set. So when the OS wants to
notify us of an exception, it will get the exception
list for the thread and then iterate over the handlers,
asking each handler if it would like to handle the exception. Here the OS would first
call the FooHandler and then, if it does not
handle the exception, it would move on to the
BarHandler and so on until it reaches the end of the list. If we look back at our program, it does not register
any exception handlers. So when the OS tries to notify us of the exception that occurred,
it will not find a handler. The default behavior is for the OS just to terminate the program. And we can actually find a little hint about what went wrong if we look back at the output of our program. So let's take a closer
look at that return code, the big ugly negative number
that our program returned. If we convert that to a 32-bit hex value, we'll find that it's actually this value, which it turns out is the value
of status access violation. So our program crashed due to an unhandled structured exception, and the OS reported this by using the exception code as the
program's return value. So let's write an exception
handler for our program. So, here we have our program again. And I've just added a little white space to make room for the
code we're going to add. The first thing we need
to do is get the TEB, so that we can access the
handler list for our thread. There's a helper function
in the Windows STK called NtCurrentTeb,
so we'll just call that and cast it to right type that we need. The next thing that we need to do is create our exception
registration record, which will initialize with the address of the MyExceptionHandler function, which we'll define on the next slide. Then we have to push our
exception registration record onto the front of the handler list, so that it will be the first
handler that the OS will call. Now our exception handler is registered, so we can proceed with
the rest of our function. Before we return, however, we also need to pop our handler
registration from the list. So that's it. Next, let's define the
MyExceptionHandler function. An exception handler
takes four parameters. Only two of these are
important at the moment. So we can see that this function receives an exception record and a context record. These are, as you might guess, pointers to the exception
record and context that the OS created for
the exception earlier. We'll start with a very simple
exception handler function. For our first handler
we'll just print a message with the exception address
and exception code, so that we know the error
happened and what the error was, and we know that the OS
actually called our handler. We'll then return ExceptionContinueSearch, which tells the OS that
this handler does not want to handle the exception, and it should continue searching for a handler. So if we re-run our
program, then we'll observe that it still prints the first message. But now it also prints this
message from our handler, telling us that an exception occurred. So we know that the OS called our handler, and we can verify that the
exception code is indeed the value that we expect
for our access violation. We can also see that our program did not print out our second message with the new value of the global variable. And, if we look at the return code, we can see that the program still crashed. This is expected, because our handler returned ExceptionContinueSearch, which told the OS we did not
want to handle the exception. The OS continued it's
search but, since no other handlers were registered,
it failed to find a handler and so it terminated the program. Let's see if we can actually fix the problem inside of our handler. So I'm saying fix in quotes,
this is not something you should actually do in real code, but it makes for a fun demo. So we'll define our
handler function as before. And first we'll check
to see if the exception is actually the exception we're expecting. So if the exception is something other than an access violation, or if the failure was something
other than a write failure, then we'll opt not to
handle the exception. Next we'll print a message
like we did before, just so that we can verify
that our handler was called. We'll then look in the exception record to get the address to
which we failed to write. And then since we failed to write, and we'd really like to be
able to change that value, we'll call this VirtualProtect function. So this is a Windows API that changes the protection of a region of memory, so we'll call it the change the protection of the page containing our
variable to make it writable. Again, please don't ever
do this sort of thing. Finally, we'll return
Exception_Continue_Execution. So before, when we returned
ExceptionContinueSearch, we told the OS that we do not
want to handle the exception, so it should continue
searching for a handler. ExceptionContinueExecution tells the OS, "we've fixed the problem, please go and resume execution where
the failure occurred." So that, what the OS will
do, is it will attempt to restart execution of our program, starting with the instruction
that caused the fault. So now if we re-run our program we can see that we still
get our first message, and we still get the
message from our handler, but now we can also see that
we got our second message and it indicates that we successfully changed the value of the global variable. If we print out the return
code of our program, we can also see that the
program returned zero, indicating that it successfully exited. So to recap what we've seen
so far, structured exception handling is the Windows
operating system facility. So it's part of Windows, not
the Visual C++ runtime library or anything else on top of Windows, that makes it possible for the OS to report errors to our program, and to give us an opportunity
to try to resolve them. Our program can register
our list of handlers, which the OS will call in sequence, to see if each would like
to handle the exception. And each handler can choose to handle, or not to handle, the
exception as it sees fit. What we've seen so far has
been quite tedious, though. We've had to write code to manually register and unregister our handlers. We've had to write a substantial amount of code within each handler. It would be most unfortunate if we had to do all of
this work ourselves. Oh yeah, our program didn't crash. Wouldn't it be nice if we could get the compiler to help
us to do all of this? So the Visual C++ compiler, good news, provides a pair of compiler
extensions that make structured exception handling much simpler in both C and C++ code. The first is the __try/__except statement, so note that that's double underscore try and double underscore except. So these are extensions, not
the standard C++ try statement. And this statement
consists of a try block, which is a region of code whose execution is guarded by an exception filter, which can be an arbitrary C++ expression. So the filter acts kind of
like an exception handler, and we'll see how they're
related in a little bit. If an exception happens
inside of the Guarded Region, then the exception handler will evaluate the filter expression to
see what it should do. So, for example, the exception filter can evaluate to Exception_Continue_Search, which means that we choose not
to handle the exception here. In this particular case,
since the exception filter unconditionally evaluates to
Exception_Continue_Search, this try accept statement
is effectively a no op because we never actually
tried to handle the exception. Alternatively, if the exception filter evaluates to Exception_Continue_Execution, this will cause execution to continue at the point where the exception occurred. Both of these values, the all-capitals EXCEPTION_CONTINUE_SEARCH and
EXCEPTION_CONTINUE_EXECUTION, are similar to the constants that we used with our exception handler
in the previous section. These are just the
values for the Visual C++ support library, whereas the other ones were the raw values from the underlying Windows structured exception
handling implementation. So the exception filter can be
an arbitrary C++ expression, and it's evaluated in the context
of the enclosing function. So, for example, it can
refer to local variables. Here, we have the value of the filters determined by looking at the value of a function parameter
named ShouldContinue. In order for the exception filter to be useful for real
world exception handling, it really needs to be able to query information about the
exception being processed so that it can take action for some exceptions but not for others. And the compiler provides a pair of intrinsics to support this. So the first intrinsic is
GetExceptionInformation, which returns a pointer to an
exception pointer structure, and this structure just provides access to the exception record and the context for the exception that is being processed. The second intrinsic is GetExceptionCode, which returns the exception code. So this is provided for convenience. The exception code can also be obtained from the exception record
in the exception pointers. These intrinsics are
only valid for use inside of the filter expression
where we're trying to determine if we should
handle the exception. If you try and use the
somewhere else in your code the compiler will give you an error. So, a typical filter in real world code will actually just call a function. It'll pass any exception
pointers to that function, and then that function will return what the filter should do. So we now have enough information, or enough compiler support, that we can rewrite our original example using it. Here, for example, is our program using a try except block instead of manually registering a handler. And we'll also need to rewrite our exception handler function for use in the filter expression. So here is out exception
handler, which I've rewritten. And there aren't many differences, I've just highlighted them here. Basically we've renamed the function to MyExceptionFilter
because we're now using it as a filter instead of a handler. We've changed the function to just take an exception pointer structure instead of having the whole signature
of an exception handler. We now access the exception record through the exception
pointers that we get. And then we return the
Visual C++ constants, the ones that are shouty and in all-caps, instead of the underlying OS constants. There's one more feature to discuss here. If we look closely, or
actually we don't have to look too closely at all, we can see that the except is
actually a block statement. So it has braces and you can put arbitrary code inside of it. What is that? So we've seen two filter values so far. If the filter evaluates to
EXCEPTION_CONTINUE_EXECUTION, then execution will
resume at the instruction where the exception occurred. If the filter evaluates to
EXCEPTION_CONTINUE_SEARCH, that means we don't want to handle the exception, and the search continues. Finally, the filter can also evaluate to EXCEPTION_EXECUTE_HANDLER. If it evaluates to the
value, then the stack is unwound to this point and execution resumes inside of the except statement. For example, here we attempt
to modify constant zero inside of a try block,
our filter expression evaluates to EXCEPTION_EXECUTE_HANDLER, and when the exception occurs we will handle the exception and execution will resume inside of the except block. This case is more interesting
if, instead of the exception occurring directly
inside of the try block, it occurs inside of some nested frame. So here, inside of the
try block we call Foo, which then calls Bar,
which then calls Baz, and the exception occurs inside of Baz. In this case, the only exception handler that's been registered is the one for our top-level function, so
it will handle the exception and execution will resume
inside of the except block after the stack has been unwound. So the nested callframes of
Foo, Bar, and Baz are unwound. In this case, unwinding
just means that we reset the stack pointer and
reclaimed the stack space that these functions were using. So unwinding can have
some interesting results. For example, let's consider
a slightly different program. So here we have a similar
try except statement, and in the try we call
ModifyConstantZero under lock. This function acquires a lock,
then calls ModifyConstantZero and will release the lock
when that function returns. ModifyConstantZero, however, will attempt the modification and it will fail. So we handled the exception here, causing the stack to be unwound, and causing execution to resume
inside of our except clause. The problem here is that
when the stack is unwound we never actually release
the lock that we acquired because this LeaveCriticalSection
call gets skipped. It's after the ModifyConstantZero
that did not return. So, the second structured
exception handling language extension is the __try/__finally block. And with a __try/__finally
block we can customize the actions that take place during unwind. So the body of the
finally block is executed both when control leaves
the try block normally, so if ModifyConstantZero were to return, then in that case we would
execute the finally block, or when the stack is unwound
through the try block. So here, for example, we move the call to LeaveCriticalSection
into the finally block, so that we will release the
lock even if an exception occurs inside of ModifyConstantZero. So we've now seen how we can use these two Visual C++ language
extensions, __try/__except and __try/__finally, to make structured exception handling easier. But how do these work under the hood? How do these language extensions map to the raw structured
exception handling code that we wrote at the
beginning of this talk? So let's look and see. Before I begin, though, I do want to note that much of the code that follows is based on actual code
in the runtime library. But I've simplified and
cleaned up much of it to help us focus on the essentials and not get bogged down in details that are not important for this overview. For example, some of this
is written in assembly, and I've turned it into the equivalent C code just to make it
easier for us to understand. And for now we'll keep looking at how things work only on x86. We'll look at how things work on other architectures toward the end of the talk. So the naive approach
the compiler could take, would be to register a handler on entry into each try block, and
unregister the handler when control leaves the try block. But this is not so
great, because a function can have more than one try block. Here, for example, we have a function with multiple sequential try blocks. And here we have a function
with nested try blocks. But for discussion
purposes, let's consider a function that has a mixture of both. So, what is the compiler going
to do with this function? The ideal things is, is
we don't wanna have to do all that work to register a
handler and then unregister it, and then register another
handler and then unregister it as we execute through the function. So instead what the
compiler is going to do, is it's going to divide this function up into states or scope
levels, where each state indicates which try
blocks we are inside of. So we start with the
base state, or State -1. And this state contains the
entire body of the function. In this state we are not
inside of any try block so, if an exception occurs in this state, we don't take any action
for this function. Next, we define State 0 for
the first outer try block. So execution of this code is guarded by the associated except so,
if an exception occurs here, the filter for that except
will need to be called to see if the exception should be handled. Note that State 0 includes only try block, and not the associated except block, because the except block does
not guard execution of itself. So if an exception occurs
inside of the except block, like, the except block can't handle that exception on it's own. Next, we define State 1
for the inner try block, and then we'll define State 2 for the last try block in the function. So now that the compiler
has divided the function up into states, it will build a
ScopeTable to represent them. So a ScopeTable is an array of entries, each entry represents a state. It specifies the
enclosing state, or level, the address of the filter
function to be called, and the address of the handler. So for our function here, we need three states in our ScopeTable. We don't need an entry for -1, because no action needs to be
taken when we are in State -1. So first we'll set the
enclosing level for each state. The enclosing level of States 0 and 2 is the base state, or State -1. And the enclosing level
for State 1 is State 0. And we can just see that by seeing what the block is that
each state is inside of. Second, we'll set the filter pointer. So for try except
blocks, this is a pointer to the address of the exception filter, so that expression inside
of the except statement. For try finally blocks,
so like the first state, this is set to null. Finally we'll set the handler pointer, which is the address of
either the except block or the finally block, depending on what kind of statement this is. So this table makes it so that, instead of having to register a separate exception handler for each try block, we can register one handler
for the entire function and then just track the state that we are in within the function. If an exception occurs
and the handler is called, it can look up what it needs to do based on the current state. Moving on, then. So we've already seen the
EXCEPTION_REGISTRATION_RECORD, which is used to register
an exception handler. The compiler uses a type
C_EXCEPTION_REGISTRATION_RECORD, to hold the exception registration record and then some associated metadata that it's going to use at runtime. So the first thing to note is that it does contain a
registration exception record, and it's going to need
that in order to register the handler for the function. The stack pointer member will
be used to store the current stack pointer value any time we change it within this function so that,
if we handle an exception, we know where to reset
the stack pointer to. The exception member
will contain a pointer to the exception pointers
for the exception, so that we can access it from the filter. The ScopeTable member
will contain a pointer to the ScopeTable for the
function which we just built, and then the try level
contains the current state. So we wanna use just one
handler for everything, and have it dynamically act
using the state we just defined. But, how do we do that? So we can take one more look at the exception handler declaration. We've already introduced
two of the parameters, the ExceptionRecord and the ContextRecord. And now we'll look at the
third, the EstablisherFrame. So the EstablisherFrame actually is past the pointer to the exception
registration record for which the handler is being called. So we can think of this
function as actually being declared, something like this. So if we store our auxiliary state, like the current state
number in the pointer to the ScopeTable, alongside
our exception registration record, then we can just
use pointer arithmetic to access our auxiliary
state inside of the handler. We can just add or subtract
the appropriate number of bytes from the EstablisherFrame
pointer that we're given. Let's look at the code that the compiler has to generate now to
make all of this work. So first on entry into the function the compiler needs to initialize a C_EXCEPTION
REGISTRATION_RECORD, and push the handler onto the front of the list. So we'll start by declaring
the registration record, which we'll name RN for registration node. This is the term used internally
within the runtime code. Next we'll leave the first two members, the StackPointer and Exception as TBD because we don't have values for those. We'll update those later. We'll initialize the
handler registrations, there are currently four versions of the C structured exception handler. I'll be showing Version three, Internals. Version four adds a bunch
of security features, which are super important,
but they're just sort of extra information that is not essential for our stuff here today. Then we'll initialize the ScopeTable to point to the ScopeTable
for our function and we'll initialize the try level to -1, because we'll be in the
base state to begin with. And then we'll just link
our handler into the list. So then, before any
return from the function, the compiler will have
to unregister the handler by popping it from the list just like we did in our manual code. Then, inside of the
function, the compiler needs to add logic to update the try level as we transition from
one state to the next. So here, for example, I
actually just copied this from the debugger in Visual Studio. We can see the code that
the compiler generated. So when we enter the first try, it updates the TryLevel to 0. When we enter the second try,
it updates the TryLevel to 1. And then when it exits that second try and re-enters the first try, it updates the TryLevel back to 0. So that's all the code that the compiler has to generate for each function. Then the last piece of the puzzle is what does except_handler3 do? So except_handler3 is an
exception handler function, so it has the same parameters as our previous handlers
that we've looked at. So first it'll take the
EstablisherFrame, and it'll do that appropriate pointer
arithmetic that we mentioned to get the pointer to the
full registration node. Next, it'll update the
exception pointers members so that any exception
filters can access it. We'll then loop through the
currently active states. So we start in the current state. Each iteration through the loop we move to the enclosing state, as it's declared inside of the ScopeTable. And then we end when we reach State -1. If the filter pointer for a state is null then that means it's a finally
block, not an except block. So we skip it for now and we
move on to the next state. We're only interested in
except blocks right now, because we're trying to find someone to handle the exception. Otherwise we call the
filter, and then we decide what to do based on the
value of the returns. If it returns EXCEPTION_CONTINUE_SEARCH, then it does not want
to handle the exception and we move on to the next state. If it returns
EXCEPTION_CONTINUE_EXECUTION, then it's fixed things
up, and we immediately return to tell the operating system that we want to continue execution. We don't have to do anymore work here. If it returns EXCEPTION_EXECUTE_HANDLER, well, this is where
things get complicated. We need to unwind the stack, and transfer control to the except block. And we'll look at the
mechanics of this next. But finally, if no one
in this function wants to handle the exception, that's the point at which we return
EXCEPTION_CONTINUE_SEARCH to tell the operating system that we do not want to handle the exception and to let it continue
trying to find a handler. So, unwinding is the process of cleaning up nested stack frames. At a minimum, this includes
reclaiming the stack space used by nested functions for
structured exception handling. Unwinding also involves calling any finally blocks in nested frames. Unwinding is split into two parts. So there's global unwinding, which unwinds across all of the nested frames. And then local unwinding, which unwinds within a single frame. Global unwinding is performed
by the Windows API function RtlUnwind, which is
part of the Windows STK. It receives a few parameters. The parameters of interest to us here are the exception records. This is the, again, the record for the exception that was thrown. And then the TargetFrame. So the TargetFrame is the pointer to the EXCEPTION_REGISTRATION_RECORD, where it should stop unwinding. So how is this function
going to unwind the stack? It's going to basically just call the registered exception
handlers again, in order, to let them know that
they're being unwound. So the first thing it's going to do is inside of the exception record, it's going to set this
flag, EXCEPTION_UNWINDING. This flag can then be used within the exception handler functions to know whether the
handler is being called to handle an exception, or because the stack is being unwound. Next, it'll get the TIB so that it can get the handler list from it. It'll then iterate over the exception list until the target frame is reached. For each handler it will
call the handler function, and then it will pop the
handler from the list. So, as this moves along, it
removes frames from this list. Within each handler we then need to handle this unwind operation appropriately, by doing a local unwind. So inside of the C
structured exception handling implementation, the local unwind function is used to perform a local unwind. It takes those parameters a pointer to the registration node for
the function being unwound, and the state number at
which unwinding should stop. So inside of this function we iterate over the states until we reach
the desired target state. So we get the ScopeTable
entry for each state. If the filter for the entry is null, then it's a finally block. So we call the handler, which is the body of the finally block. If the filter is non-null,
then this is an except block, and so we take no action during the unwind because we're only trying
to do cleanup here. Finally, after we have
called the finally block, we update the TryLevel
to the EnclosingLevel. So the last thing we need to do is patch things up inside
of _except_handler3 so that it can handle
this extra unwinding case. So what we need to do
is indent the actions that we took during the
search for a handler, and then add a check so that we only do that if we're not unwinding. If we are unwinding,
then we call local unwind to unwind back to the base
state for the function, which is State -1. Now we can come back to what
we have to do when the filter expression evaluates to
EXCEPTION_EXECUTE_HANDLER. First we have to call RtlUnwind, unwind any nested stack frames, and we stop at this frame so that the
OS won't unwind this frame. Then we call local
unwind so that we unwind all of the nested states underneath us. So if, for example, if
there's a try finally inside of our try except, we need to execute that finally block. Then we transfer control
into the except block by calling the handler. So this resumes execution
inside of that except block, and that will not return. Okay, so that was a lot of information, and this is actually probably something that's a little easier
to show with a picture, so let's walk through
an end-to-end example. So here we have a stack, and someone calls our function named F. So the first thing we're going to do is build a registration
node for F on the stack. So this is the first
function that we have, so there's no next handler
to set the pointer to. We set the pointer to the handler in the runtime library,
so except_handler3. We set the ScopeTable to the appropriate ScopeTable for F, and then we initialize the TryLevel to the
base state, which is -1. We then allocate a call frame for F to hold any local
variables that are in here. I've just removed all those from this so that we can focus on
the important pieces. And then we store that stack pointer inside of our registration
node for future use. Then we enter the body of the function. We enter the Outer__try and we update the TryLevel to State 0. And then we enter the
Inner__try and we enter State 1. And then we call G. So inside of G the
first thing we do again, is we allocate a registration node. It's much the same as for
F, except the ScopeTable is set to the ScopeTable for G. And then the next pointer it
points to the registration of F, because there was a prior
registration on the stack. We then allocate a call frame for G, and we store the current
stack pointer, again, inside of the registration node. We then enter the
Outer__try block in G and we transition to State 0. Then enter the Inner__try
block and transition to State 1, and then we call H. So here is H. The first thing we do,
again, is we allocate a registration node for the function. The next pointer points to
the registration node for G. And we allocate a frame for H, and we store the stack
pointer as we did before. We then enter the try block and update the try level to level 0. Then we execute this statement which attempts to de-reference a null pointer. Oh no we should not do
that, definitely not. But this is where the CPU will signal a general protection exception, causing the OS to raise
a structured exception for the access violation. The OS dispatches the
exception, so it's going to construct that context
and the exception record and pass it back into our program. It's going to inject
the exception dispatcher onto our thread. The dispatcher starts by
getting the most recent handler registration from the TEB, so this is the registration node for H. It then calls the handler for H. The handler looks at the current TryLevel, it observes that this
is for a finally block. The enclosing state is the base state, so there are no except
statements in this function. And so the handler returns
ExceptionContinueSearch. The dispatcher then follows
the link to the next handler. It gets the registration node for G. It then calls the handler for G. The handler looks at the current TryLevel and it sees that it is
for an except block, so it calls the filter which then returns EXCEPTION_CONTINUE_SEARCH, indicating it does not want
to handle the exception. The handler then advances to
the enclosing scope in State 0. This is a finally block,
so no action is required. The next enclosing
scope is the base state, where we don't take any action. So the handler, again, returns
ExceptionContinueSearch. The dispatcher then follows
the link to the next handler to get the registration node for F. The dispatcher then
calls the handler for F. The handler looks at the current state, sees that it's an except
block, calls the filter, and the filter returns
EXCEPTION_EXECUTE_HANDLER indicating that here's where we want to handle the exception
and unwind the stack. So, for this case, the
handler is not going to return control back to the dispatcher. Instead, the handler
needs to unwind the stack using those three steps
that we had before. So first we call RtlUnwind
to do the global unwind, and unwind any nested frames. So we call RtlUnwind, it starts just like the dispatcher did, by getting the first exception handler
registration from the TEB. So this is the registration node for H. Inside of our handler we
look at the current state, which indicates that we
are in a finally block. It indicates that the state
is for a finally block. It runs the finally
block, which we'll return, and then we update the state. We've now reached the base state again so our handler can return. RtlUnwind will now pop the
handler H from the exception list and this frame has actually been unwound, so it's not actually in the list anymore. RtlUnwind will then follow
the link to the next handler, which is the handler for G. RtlUnwind calls the handler for G. Inside of that handler
we need to unwind back to the base state, so we start by looking at the current state, which
is for an except block. We're only interested in finally blocks during the unwind, so we update the state to the enclosing state, which is State 0. State 0 is a finally block, so the handler calls the finally block which
will execute and then return. The handler will then update the TryLevel to the enclosing state,
which is the base state, and the handler will return. Now, just as before,
RtlUnwind will pop G's handler from the list of handlers,
unregistering it. So G is now fully
unwound, and it will then follow the link to the next handler. It sees that the next
handler is the handler for F, and that's where we told it to stop, so RtlUnwind now returns
back to our handler. So, back inside the handler for F, we've now completed the global unwind. So we've successfully unwound
all of the nested frames. We now have to call local unwind to unwind any nested states in our frame. So inside of local unwind, we observe that the current state is 1, which is the same as our target state. So that is, it's the same as the state of the except block that we're processing. So this means that we have no work to do, but if there'd been a nested finally block we would have run it. So local unwind now returns
and, back in the handler, we've now completed the local unwind. We can now transfer control
to the __except block. So the __except block
will do several things. First it will update the
TryLevel to the enclosing scope, because we're no longer
inside of the Inner__try, so execution is no longer
guarded by that except filter. Next it will update the stack pointer to the value that it
had before G was called. This reclaims all the stack space that was used by nested frames. And, last but not least, it then begins execution again inside
of the except block. So that is, end-to-end, how structured exception handling and
stack unwinding works. There's one more essential thing to discuss before we proceed. All the exceptions that we've seen so far have been generated by the OS. What if we want to
create our own exception? Well, we can. There's a function
RaiseException which, well, it raises an exception. So it takes a few parameters, which should look fairly familiar. Earlier we saw the
exception record structure, and the parameters of RaiseException are just used to construct
an exception record, which is then dispatched as an exception. So for example, we can define
our own exception code, STATUS_COFFEE_SHORTAGE, which is something that the conference could have used this morning during the break,
with an appropriate value. And then call RaiseException to raise an exception with this code. Then we can write a program
that handles this exception and, in our filter, we
can check to see if it's the coffee shortage exception
and handle it appropriately. Okay. (audience murmuring) So I expect some of you may be wondering, I'm now 40 minutes and over 150 slides into my talk on C++ exceptions, and we have yet to meet a C++ exception. Why have I spent so much time
on structured exceptions? And the answer is, C++ exceptions are implemented as structured exceptions. So it's much easier and simpler
to look at this base case of how structured
exception handling works, and then look just at the C++ specific pieces that sit on top of that. So when we have a throw
expression in our code, the compiler actually transforms that into a call to a runtime library function that calls RaiseException. Try catch blocks are implemented
using an exception handler, similar to how __try/__except
is implemented. Local variable destruction is done in a very similar way to
how try finally blocks are handled during an unwind. And then we have the
_except_handler3 function used by structured exception
handling compiler support. And there's a similar CxxFrameHandler used for C++ exception handling. So I spent most of this talk, again, talking about structured
exception handling because it's simpler, and now we can focus on the C++ details that
sit on top of that. So, first things first. What happens when you
throw a C++ exception? Let's say, for example,
that I want to throw an exception of type MyBeautifulException. So there's a runtime library function called CxxThrowException,
which is responsible for throwing C++ exceptions. Fancy that, it's actually well-named. It's takes a pointer to
the object to the thrown, and then a pointer to some metadata describing the object's type. For when we have a throw
expression in our code, the compiler first constructs the object to be thrown locally on the stack. Now this is somewhat
incorrect because it actually constructs a copy of the object that was passed to the throw expression. And then it calls
CxxThrowException with that, and with the appropriate
metadata based on it's type. The throw info consists of a few things. So it has a member that contains some flags, called attributes, here. It has a pointer to the Destructor for the thrown type, so that the object can be destroyed at the right time. And then it has a
CatchableTypeArray which, as it's name indicates, is an array of catchable type objects
where each catchable type object describes one
of the types via which the thrown object can be caught. So this includes some flags, a pointer to a type info object
representing the type. So these are the same
structures used by RTTI. If you don't have RTTI
enabled in your build, but you do use exception handling the compiler will still
generate RTTI data, but only for the few types
needed for exception handling. It also contains displacement information, which is used for multiple
inheritance cases. So that if you catch
a derived class object via one of it's bases, we know
how to find the base class. And then it's got a copy function, so it knows how to make a
copy of the exception object. So let's consider an example. Here we have a base exception
type that stores some data. It's ThrowInfo has a
null destructor pointer because this type is
trivially destructible. It's CatchableType array
contains a single element, which describes the base exception type. We can define the derived exception type, which derives from base
exception and carries a string as it's payload. This also has it's own throw info, in this case the unwind
member function points to the DerivedException destructor, because it's destructor needs
to destroy this to the string. And then it has two
associated CatchableTypes. So first it has a CatchableType
for the DerivedException type itself, since DerivedException is not trivially copyable. It also has a pointer to
the copy constructor there. And then it also has as a
CatchableType the CatchableType for the BaseException because, again, you can catch an exception
of a derived type by a catch for one of it's base classes. So let's implement CxxThrowException, this one's really easy to do. So first it's going to
construct an EXCEPTION_RECORD, and it'll set the exception code to the Visual C++ ExceptionCode. Here's a little piece of trivia, it's this value which what you get if you do a logical or of the
multi-character literal msc with a few extra bits at
the top of the number. Next, it'll set the exception flag. So we haven't seen many of the flags here but we set one flag,
EXCEPTION_NONCONTINUABLE, and this prevents structured
exception handlers from attempting to continue
execution at the site where the exception was raised. So if a structured exception
handler tried to handle this, it would not be able to return
Exception Continue Execution, the OS would prevent that. C++ exceptions are not
resumable in that way. It will then initialize
the exception parameters, so we use three parameters inside of here. Parameter 0 is set to a magic number that, it's kind of used to know what
version of exception this is. Parameter 1 is then
just set to the pointer of the exception object. And Parameter 2 is set to the throw info described in the ExceptionObject. And then it just calls RaiseException, with all of the data
that it just filled in. Note that, because C++
exceptions are raised as structured exceptions,
you can actually use a try except block to
handle C++ exceptions. So here, for example,
we throw a C++ exception inside of a structured
exception handling try block. And then in our exception
filter we check for the Visual C++ exception
code, and we handle the exception if it matches. This is not usually something
that we do, but it's possible. Now we're gonna look at
the other side of things. How are exceptions caught,
and how is the stack unwound? So with the compiler
structured exception handling support we had this
C_EXCEPTION_REGISTRATION_RECORD which contains some auxiliary state. There is something similar
for the C++ exception handling support called the EHRegistrationNode. Like the structured
exception handling type we can see that we still have a member to store the stack pointer, and we still have the
handler registration. We also have a state which is
used to store the state index, which is the moral equivalent of the structured exception
handling TryLevel. The EHRegistrationNode
does not have storage for the exception pointers
like we had in the C version. This is because that information is not accessible from any user code. It's used entirely within
the C++ handler support. Additionally, it does not have
a pointer to a ScopeTable. So for the ScopeTable, the
compiler uses a little trick. We still use a single
common exception handler for all functions that use
C++ exception handling. But the compiler actually
generates custom thunks, one for each function, that basically pass the function metadata to the
handler as a hidden parameter. So for example, if we
have a function named F, then the compiler will generate a little two-instruction thunk
that just puts the pointer to the appropriate
metadata, which I've called FuncInfoForF, into a
register and then it'll transfer control to the
common handler code. So then, in the handler we
can just refer to that data. So this approach, it's a
little cheaper to enter a function that uses
C++ exception handling because we don't have to copy this pointer onto the stack every time
we enter the function. We only have to copy it
if we actually need to when an exception has
actually been thrown. So, just like with structured
exception handling, the compiler will split our
function into a set of states. With structured exception
handling the compiler generated states for
each structured exception handling TryBlock in the function. With C++ exception handling
the compiler generates states for each C++ TryBlock, and
also for each local variable destructor that might need
to be called during unwind. And then here we can see
it also generates the code to update the state as we
execute through the function. For structured exception
handling we had a ScopeTable. For C++ exception
handling we have something similar called a FuncInfo,
and it contains two things. The first is an UnwindMap,
which maps all those state indexes to the sets of destructors that needs to be called during an unwind. And the second is the
TryBlockMap, which contains information about all of the
TryBlocks in the function. So we're going to focus
on the TryBlock map. For each TryBlock, it
contains information to map which set of states are
covered by the TryBlock because you can have
multiple local variables declared inside of each of those blocks. And then it contains
a list of catch blocks associated with the try. There's effectively
one handler type object for each catch statement. It then, each contains
the type info describing the handler type, and then a pointer to the handler function. So if we look at the
exception handler function, so for structured exception
handling we showed except_handler3, for C++
we use __CxxFrameHandler. The first thing that
this function does is it ignores non-C++ exceptions,
at least in the usual mode. Second, it looks at the
FuncInfo and the current state, and it uses that to compute
the range of try blocks whose catch blocks should be considered. Third, for each try block,
from the innermost to the outermost, it enumerates
the associated catch blocks. Then for each catch block,
it checks to see if the type is a match for the type
of the thrown object. So each handler has a
std::type_info describing the type of object it can catch. And the throw info has an
array of catchable types, each of which describes,
contains a type info describing a type by which
the exception can be caught. So basically we just have
to compare all of those, and then pick whichever catch block we find that matches first. If a catch block matches, then it initializes a catch object. So if the catch block
catches by reference, this this will just be
reference to the thrown object. Otherwise we have to make a
copy of the thrown object, or a copy of some part of it,
if it might be a base class. It then performs a global unwind to unwind all of the nested frames. This will destroy local variables that have been constructed
in those nested functions. It'll then perform a local unwind to unwind any local frames. So, for example, this will
destroy any local variables that were declared and
that had been constructed inside of the try block. And then it calls the catch block. There's two ways that
a catch block can exit. So first, control can reach
the end of the catch block and then, after any local
variables that were declared in the catch block are destroyed, it will return control
back to the handler. In this case the thrown
object is then destroyed, and the stack pointer is
reset, and execution resumes with the first statement
that follows the catch block. Alternatively, the catch block
can rethrow the exception via a throw with no operand. In that case, the original
exception is re-raised, but from the context of the catch block. Finally, if no catch block matches, ExceptionContinueSearch
is returned to let the OS continue the search for a handler. I'll note a few interesting tidbits. First, we use a different handler for structured exception
handling, except_handler3. Then for C++ exception
handling, where we use CxxFrameHandler. So this means that you can't
mix and match C++ try blocks with SEH try blocks in a single function. They have to be in separate functions. In the structured exception handler we reset the stack pointer before we enter the except block and,
basically after we enter the except block, we never
return back to the handler. In the C++ exception handler,
we execute the catch block, and then let it return
control back to the handler, where we then reset the stack pointer and then resume execution after that. This is because the C++
exception object is allocated on the stack inside of the
function where it was thrown, so we can't reclaim that stack space until after control
leaves the catch block. There's a few other interesting
things worth noting. First, what happens there's
no matching catch block? In this case, it's required
that std::terminate should be called. So during program startup,
there's a function in the runtime library that will call this Windows API SetUnhandledExceptionFilter to register an exception filter that the OS will call if it fails to find a
handler the normal way. And then inside of that
helper we just check if it's a C++ exception, then
it'll call std::terminate. C++ used to support dynamic
exception specifications, which would let you restrict
the set of exceptions that a function could throw. Visual C++ did not implement this feature, but this feature was
completely removed from the language in C++ 17. So the way I see it, Visual
C++ was just a couple decades ahead of everyone else here. (audience laughing) - There's noexcept. So a function can be declared
as noexcept, in which case no C++ exception are allowed
to unwind through it. And the implementation
here is actually pretty straightforward, at least at runtime. And that is, inside of
CxxFrameHandler, if it fails to find a catch block
to handle an exception, then just before it returns
ExceptionContinueSearch indicating that it does not
wanna handle the exception, it checks to see if the
function is noexcept via a flag that's stored in the FuncInfo. If the function is noexcept,
then it calls terminate immediately without unwinding the stack. So the implementations
are given the option of either unwinding the stack
to the point of the noexcept or not unwinding, and Visual
C++ makes the choice not to. Finally, there's an
interesting issue of how C++ exceptions and non-C++
structured exceptions should interact with each other. This is configurable at
compile time via the EH switch. I'm not going to go into details here. The documentation is
actually pretty thorough about how these things work. So if you're interested in
that, I would refer you to MSDN. Alright, what about other architectures? So, so far everything I've discussed is how things work on 32-bit x86. The x86 implementation
is, well, not great, because it imposes non-negligible overhead even in the case where
no exceptions are thrown. So, even if no exceptions are thrown, the compiler still has to
put code into each function to initialize and register
the exception handler at the beginning of the function, unregister when control
exits the function, and then it also has to update the state as objects are constructed and destroyed, and as try blocks are entered and left. Is that a lot of overhead? Well that really depends
on the application, and what each function does. But, ideally, C++ exceptions should impose no runtime overhead until an
exception is actually thrown. So you shouldn't have
to pay for the feature if doesn't get used, you
know, the usual C++ thing. The x86 implementation is also problematic for another reason, and
that is that the handler registration records that we've seen, they contain function pointers and they're allocated on the stack. So they're ripe for attack if there's a buffer overrun on the stack. The implementation was
improved in 2005 with a new feature called SAFESEH, and that mitigates some of those vulnerabilities. That's why I didn't show the Version 4. The exception handler basically just adds all that security support that wasn't particularly relevant to what
we're talking about here. I do want to note that the implementation, it's not that the implementation was bad at the time when it was designed. Structured exception
handling actually predates the addition of exceptions to C++. So when this feature
was first implemented, it was kind of rare to have functions that actually handled exceptions. The overhead basically
just wasn't a big deal. Additionally, the security
issues weren't really known at that time in the late
1980s, very early 1990s. Unfortunately, we can't change how things work on 32-bit x86. Again, the exception handling mechanics are part of the fundamental ABI. But as Windows is imported
to other architectures, we have an opportunity
to make ABI improvements. So on x64 ARM, and then now 64-bit ARM, we use a different
implementation which resolves both the performance issue and most of those potential security issues. So all of these architectures use very similar implementations. There's a few differences,
but the fundamentals are the same. So, for today, I'll
show what's done on x64. We start with an observation, and that is that each
function that registers an exception handler always
registers the same handler. This is probably not
true in 100% of cases, but it's true in practically all cases. And in the few cases where it wasn't true we can transform the code
to make it true, right? Every one of those C++
functions is registering CxxFrameHandler as it's handler function. That means that, instead of maintaining a dynamic handler
registrations at runtime, we can store a static table of handler registrations in the binary. So the OS will need some
way to find this table and to facilitate that, we
store in the special part of our xcr.dll called the .pdata section. The OS will also need some way to know what functions are currently on the stack, so that it knows what entries
in the table to look up. On x86, walking the stack is hard. Due to the long legacy of 32-bit x86, the ABI is very loosely defined. There are numerous calling conventions, and then each function
generally has free reign to do as it pleases,
as long as it cleans up before it returns control
back to it's caller. But if we're building all
of this from the ground up for a new architecture we can
define the ABI very precisely, and in such a way that
stack walking is easy. So on x64 we define the .pdata
section that's containing an array of these
Runtime_Function objects. Each runtime function specifies the begin and end address of the function, and then a pointer to the
metadata describing the function. This array is sorted
so the OS can basically just do a binary search, using whatever the current instruction pointer is to find the right function entry. The metadata then includes information that the OS needs to walk the stack. It includes the address of the
exception handler function, and then it includes
language-specific metadata. So the C++ compiler uses
the language-specific metadata to store either the ScopeTable for structured exception handling, or the FuncInfo for
C++ exception handling. So, from this metadata the OS can find out what functions are on the stack, and it can find the
handler for each function. The C runtime library can also
use the instruction pointer to determine the current state
that each function is in. Everything else works
more or less the same. The algorithms are the same. There's one file the C
runtime library that actually implements CxxFrameHandler
and the bulk of the logic. You know, there's a few
architecture-dependent pieces, but they're not part of the core algorithm of how we search for exceptions. The one other thing worth noting here, is that we don't store pointers in any of the data structures in
this new implementation. So x64 is a 64-bit
architecture, so pointers are 64 bits in size. However, a single binary can't be larger than four gigabytes. I'm sure the practical
limit might be much lower, but four gigabytes is the absolute maximum permitted by the file format. So it would be quite wasteful
for us to store pointers, as our data would be twice as large. So instead, what we do is
we actually store pointers as 32-bit RVAs, which are just offsets from the beginning of the DLL or EXE. If you wanna learn more
about that, I'll refer you to my talk from last year's
CppCon, where I discussed all the details of how
DLLs work on Windows. Finally, just a little fun
thing to end the talk with. At the beginning of the talk, I showed how structured exceptions are resumable. So, when you handle an exception, you can tell the OS
that you want execution to resume at the point where
the exception occurred. C++ exceptions are not
resumable, but what if they were? So as part of my research for this talk I was reviewing some
old specifications for the Visual C++ exception
handling implementation. And a former manager, when
he left the Visual C++ team, he dumped this pile of
documents on my desk. And in it there was this specification of how exception handling
worked, and a bunch of papers. So I came across this
paper, numbered here, that Microsoft submitted
to the C++ committee back, I believe, in 1990 or
1991, where we proposed support for optionally
resumable exceptions in C++. So here was one example
of the proposed syntax showing how you could
specify the throw site. Or at the throw site, whether an exception should be resumable or not. Then you could explicitly
catch resumable exceptions, and then control whether
the exception was resumable if you rethrew it. And then you could catch
non-resumable exceptions, and then attempting to resume
a non-resumable exception would result in a compilation error. Anyway, obviously this
wasn't adopted into C++, but I just found it interesting history and wanted to share it. In the Visual C++ EH implementation today, there's actually still
reserved flags for this. You know, if we ever decide to add this to C++ in the future. Though, to the best of my knowledge, there are no plans to
resume work on this feature. (laughing) - Okay, and with that, that's the end. If you're interested in exploring more, I will note that we shipped most of the runtime library sources
with the Visual C++ STK, so if you have Visual
Studio installed, you can actually go and take a
look at the source code. It's not buildable, but
you can debug through it if you're using the debug
libraries which is pretty great. And with that, I'm
actually done 50 seconds ahead of schedule, so
are there any questions? (audience applauding) (Audience member asking question) - Yeah, so the question is how do vectored exception handlers fit in with this? I did not talk about
vectored exception handlers because I only wanted to
cover the parts of structured exception handling that
fit in with C++ exceptions. So I'd be happy to chat about
that after the talk, yeah. (audience member asking question) - Destructors that throw. Yeah I didn't talk about
that but, basically, inside of the frame handler,
when it's doing the unwind operation, when it's actually
calling the destructors, it basically just has
a try except around it or maybe a try catch, even. But it has a try except
and then, if an exception is thrown out of that
while it's unwinding, then it calls to terminate in that case. So basically it just guards the execution of those handlers, or
of those destructors. Yes? (Audience member asking question) - How does it work with fibers? That is a good question, and I don't immediately know the answer to that. But I suspect that, since multiple fibers would share the same TEB,
depending on which thread is currently scheduled,
they would also share the same exception list. But I don't actually know that, yeah. Yes? (Audience member asking question) - Yeah, do I have numbers on
x86 performance implications even if you don't catch anything. I do not, no. Yes? (Audience member asking question) - So the question was,
yeah, the question was how do things happen in the
case of a stack overrun. So, in that case, you do get a stack overflow structured exception. When I gave the dry run of this talk on Friday, I was asked that question. I don't actually know how
that's handled internally, and I did not look it up because I was not expecting to
get the question here. So that will teach me to not listen to the feedback from my colleagues. (audience laughing) - But, that's something I do wanna look up because I don't actually know
what happens in that case. - [Man In Audience] Can I do follow up with you on something else? - Sure yeah, absolutely, after the talk. - [Man In Audience] Thank you. - Yes? (Audience member asking question) - So on x86 it's not zero cost, obviously, because you've got all that
work that you have to do. With the new implementation
used on x64, bascially, the two pieces of information
that you need to track as you're running, is you have to track what is the ScopeTable
for the current function, and then what is the current state that you're in within that. And it turns out that both of those can be derived from the
instruction pointer. So basically, instead of
updating some state manually if an exception is raised,
we then go and we look up what the state is using
the instruction pointer. So we just look at where
we are in the function, and then that'll tell us what the state is and what information to use. Yes? (Audience member asking question) - Yes. No, so the question was
when you throw an object we have to make a copy, so
that's actually required by the C++ standard,
as whatever expression you're passing we end up
making a copy of that object. You know, that copy
could throw an exception. So, for example, a vic
could throw a bad_alloc or something like that. In that case, the original exception has not yet been raised. So in that case, it's that new exception is the one that's going to get thrown. And then you may handle that somewhere, but then the original exception
is not going to get raised. Yes? (Audience member asking question) - Can you get a back trace from, (audience member continuing question) - You can, there's actually
a Windows API that can help you get a stack trace,
and I can refer you that. And it will use, for
example on x64, it will use the information stored in the binary that the OS uses to do the stack walk. Yes? (Audience member asking question) - Yes, you could catch C++ exceptions in a language that is not C++. Now there's some danger
there because, for example, if you catch and handle
the exception in a language that's not C++, the C++ exception object may not get destroyed correctly because we rely on the handler actually doing that destruction work. Now, in my example that I showed where we caught the C++ exception using a structured exception
handling try accept, in that case the runtime library actually will do the destruction correctly, but it may not do unwind correctly, yep. Yes? (Audience member asking question) - If you have two separate, So the question is, with
respect to optimizations, how does that work with
the state table, but (Audience member continuing question) - No, no. But if you have two separate
functions that are similar? (Audience member continuing question) - But the compiler in
that case still knows when the destructors
need to be called, right? No matter what optimizations
the compiler does to the code, it still has to be able to call the destructors in the right place, right? So the compiler does have to track that. And with that I think
we'll end, but I'm happy to take more questions afterwards and my colleagues from
Microsoft can heckle me and so excellent, yeah,
thank you for coming. (audience applauding)