except.c
来自「GCC编译器源代码」· C语言 代码 · 共 2,017 行 · 第 1/5 页
C
2,017 行
/* Implements exception handling. Copyright (C) 1989, 92-96, 1997 Free Software Foundation, Inc. Contributed by Mike Stump <mrs@cygnus.com>.This file is part of GNU CC.GNU CC is free software; you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe Free Software Foundation; either version 2, or (at your option)any later version.GNU CC is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with GNU CC; see the file COPYING. If not, write tothe Free Software Foundation, 59 Temple Place - Suite 330,Boston, MA 02111-1307, USA. *//* An exception is an event that can be signaled from within a function. This event can then be "caught" or "trapped" by the callers of this function. This potentially allows program flow to be transferred to any arbitrary code associated with a function call several levels up the stack. The intended use for this mechanism is for signaling "exceptional events" in an out-of-band fashion, hence its name. The C++ language (and many other OO-styled or functional languages) practically requires such a mechanism, as otherwise it becomes very difficult or even impossible to signal failure conditions in complex situations. The traditional C++ example is when an error occurs in the process of constructing an object; without such a mechanism, it is impossible to signal that the error occurs without adding global state variables and error checks around every object construction. The act of causing this event to occur is referred to as "throwing an exception". (Alternate terms include "raising an exception" or "signaling an exception".) The term "throw" is used because control is returned to the callers of the function that is signaling the exception, and thus there is the concept of "throwing" the exception up the call stack. There are two major codegen options for exception handling. The flag -fsjlj-exceptions can be used to select the setjmp/longjmp approach, which is the default. -fno-sjlj-exceptions can be used to get the PC range table approach. While this is a compile time flag, an entire application must be compiled with the same codegen option. The first is a PC range table approach, the second is a setjmp/longjmp based scheme. We will first discuss the PC range table approach, after that, we will discuss the setjmp/longjmp based approach. It is appropriate to speak of the "context of a throw". This context refers to the address where the exception is thrown from, and is used to determine which exception region will handle the exception. Regions of code within a function can be marked such that if it contains the context of a throw, control will be passed to a designated "exception handler". These areas are known as "exception regions". Exception regions cannot overlap, but they can be nested to any arbitrary depth. Also, exception regions cannot cross function boundaries. Exception handlers can either be specified by the user (which we will call a "user-defined handler") or generated by the compiler (which we will designate as a "cleanup"). Cleanups are used to perform tasks such as destruction of objects allocated on the stack. In the current implementation, cleanups are handled by allocating an exception region for the area that the cleanup is designated for, and the handler for the region performs the cleanup and then rethrows the exception to the outer exception region. From the standpoint of the current implementation, there is little distinction made between a cleanup and a user-defined handler, and the phrase "exception handler" can be used to refer to either one equally well. (The section "Future Directions" below discusses how this will change). Each object file that is compiled with exception handling contains a static array of exception handlers named __EXCEPTION_TABLE__. Each entry contains the starting and ending addresses of the exception region, and the address of the handler designated for that region. If the target does not use the DWARF 2 frame unwind information, at program startup each object file invokes a function named __register_exceptions with the address of its local __EXCEPTION_TABLE__. __register_exceptions is defined in libgcc2.c, and is responsible for recording all of the exception regions into one list (which is kept in a static variable named exception_table_list). On targets that support crtstuff.c, the unwind information is stored in a section named .eh_frame and the information for the entire shared object or program is registered with a call to __register_frame_info. On other targets, the information for each translation unit is registered from the file generated by collect2. __register_frame_info is defined in frame.c, and is responsible for recording all of the unwind regions into one list (which is kept in a static variable named unwind_table_list). The function __throw is actually responsible for doing the throw. On machines that have unwind info support, __throw is generated by code in libgcc2.c, otherwise __throw is generated on a per-object-file basis for each source file compiled with -fexceptions by the the C++ frontend. Before __throw is invoked, the current context of the throw needs to be placed in the global variable __eh_pc. __throw attempts to find the appropriate exception handler for the PC value stored in __eh_pc by calling __find_first_exception_table_match (which is defined in libgcc2.c). If __find_first_exception_table_match finds a relevant handler, __throw transfers control directly to it. If a handler for the context being thrown from can't be found, __throw walks (see Walking the stack below) the stack up the dynamic call chain to continue searching for an appropriate exception handler based upon the caller of the function it last sought a exception handler for. It stops then either an exception handler is found, or when the top of the call chain is reached. If no handler is found, an external library function named __terminate is called. If a handler is found, then we restart our search for a handler at the end of the call chain, and repeat the search process, but instead of just walking up the call chain, we unwind the call chain as we walk up it. Internal implementation details: To associate a user-defined handler with a block of statements, the function expand_start_try_stmts is used to mark the start of the block of statements with which the handler is to be associated (which is known as a "try block"). All statements that appear afterwards will be associated with the try block. A call to expand_start_all_catch marks the end of the try block, and also marks the start of the "catch block" (the user-defined handler) associated with the try block. This user-defined handler will be invoked for *every* exception thrown with the context of the try block. It is up to the handler to decide whether or not it wishes to handle any given exception, as there is currently no mechanism in this implementation for doing this. (There are plans for conditionally processing an exception based on its "type", which will provide a language-independent mechanism). If the handler chooses not to process the exception (perhaps by looking at an "exception type" or some other additional data supplied with the exception), it can fall through to the end of the handler. expand_end_all_catch and expand_leftover_cleanups add additional code to the end of each handler to take care of rethrowing to the outer exception handler. The handler also has the option to continue with "normal flow of code", or in other words to resume executing at the statement immediately after the end of the exception region. The variable caught_return_label_stack contains a stack of labels, and jumping to the topmost entry's label via expand_goto will resume normal flow to the statement immediately after the end of the exception region. If the handler falls through to the end, the exception will be rethrown to the outer exception region. The instructions for the catch block are kept as a separate sequence, and will be emitted at the end of the function along with the handlers specified via expand_eh_region_end. The end of the catch block is marked with expand_end_all_catch. Any data associated with the exception must currently be handled by some external mechanism maintained in the frontend. For example, the C++ exception mechanism passes an arbitrary value along with the exception, and this is handled in the C++ frontend by using a global variable to hold the value. (This will be changing in the future.) The mechanism in C++ for handling data associated with the exception is clearly not thread-safe. For a thread-based environment, another mechanism must be used (possibly using a per-thread allocation mechanism if the size of the area that needs to be allocated isn't known at compile time.) Internally-generated exception regions (cleanups) are marked by calling expand_eh_region_start to mark the start of the region, and expand_eh_region_end (handler) is used to both designate the end of the region and to associate a specified handler/cleanup with the region. The rtl code in HANDLER will be invoked whenever an exception occurs in the region between the calls to expand_eh_region_start and expand_eh_region_end. After HANDLER is executed, additional code is emitted to handle rethrowing the exception to the outer exception handler. The code for HANDLER will be emitted at the end of the function. TARGET_EXPRs can also be used to designate exception regions. A TARGET_EXPR gives an unwind-protect style interface commonly used in functional languages such as LISP. The associated expression is evaluated, and whether or not it (or any of the functions that it calls) throws an exception, the protect expression is always invoked. This implementation takes care of the details of associating an exception table entry with the expression and generating the necessary code (it actually emits the protect expression twice, once for normal flow and once for the exception case). As for the other handlers, the code for the exception case will be emitted at the end of the function. Cleanups can also be specified by using add_partial_entry (handler) and end_protect_partials. add_partial_entry creates the start of a new exception region; HANDLER will be invoked if an exception is thrown with the context of the region between the calls to add_partial_entry and end_protect_partials. end_protect_partials is used to mark the end of these regions. add_partial_entry can be called as many times as needed before calling end_protect_partials. However, end_protect_partials should only be invoked once for each group of calls to add_partial_entry as the entries are queued and all of the outstanding entries are processed simultaneously when end_protect_partials is invoked. Similarly to the other handlers, the code for HANDLER will be emitted at the end of the function. The generated RTL for an exception region includes NOTE_INSN_EH_REGION_BEG and NOTE_INSN_EH_REGION_END notes that mark the start and end of the exception region. A unique label is also generated at the start of the exception region, which is available by looking at the ehstack variable. The topmost entry corresponds to the current region. In the current implementation, an exception can only be thrown from a function call (since the mechanism used to actually throw an exception involves calling __throw). If an exception region is created but no function calls occur within that region, the region can be safely optimized away (along with its exception handlers) since no exceptions can ever be caught in that region. This optimization is performed unless -fasynchronous-exceptions is given. If the user wishes to throw from a signal handler, or other asynchronous place, -fasynchronous-exceptions should be used when compiling for maximally correct code, at the cost of additional exception regions. Using -fasynchronous-exceptions only produces code that is reasonably safe in such situations, but a correct program cannot rely upon this working. It can be used in failsafe code, where trying to continue on, and proceeding with potentially incorrect results is better than halting the program. Walking the stack: The stack is walked by starting with a pointer to the current frame, and finding the pointer to the callers frame. The unwind info tells __throw how to find it. Unwinding the stack: When we use the term unwinding the stack, we mean undoing the effects of the function prologue in a controlled fashion so that we still have the flow of control. Otherwise, we could just return (jump to the normal end of function epilogue). This is done in __throw in libgcc2.c when we know that a handler exists in a frame higher up the call stack than its immediate caller. To unwind, we find the unwind data associated with the frame, if any. If we don't find any, we call the library routine __terminate. If we do find it, we use the information to copy the saved register values from that frame into the register save area in the frame for __throw, return into a stub which updates the stack pointer, and jump to the handler. The normal function epilogue for __throw handles restoring the saved values into registers. When unwinding, we use this method if we know it will work (if DWARF2_UNWIND_INFO is defined). Otherwise, we know that an inline unwinder will have been emitted for any function that __unwind_function cannot unwind. The inline unwinder appears as a normal exception handler for the entire function, for any function that we know cannot be unwound by __unwind_function. We inform the compiler of whether a function can be unwound with __unwind_function by having DOESNT_NEED_UNWINDER evaluate to true when the unwinder isn't needed. __unwind_function is used as an action of last resort. If no other method can be used for unwinding, __unwind_function is used. If it cannot unwind, it should call __terminate. By default, if the target-specific backend doesn't supply a definition for __unwind_function and doesn't support DWARF2_UNWIND_INFO, inlined unwinders will be used instead. The main tradeoff here is in text space utilization. Obviously, if inline unwinders have to be generated repeatedly, this uses much more space than if a single routine is used. However, it is simply not possible on some platforms to write a generalized routine for doing stack unwinding without having some form of additional data associated with each function. The current implementation can encode this data in the form of additional machine instructions or as static data in tabular form. The later is called the unwind data. The backend macro DOESNT_NEED_UNWINDER is used to conditionalize whether or not per-function unwinders are needed. If DOESNT_NEED_UNWINDER is defined and has a non-zero value, a per-function unwinder is not emitted for the current function. If the static unwind data is supported, then a per-function unwinder is not emitted. On some platforms it is possible that neither __unwind_function nor inlined unwinders are available. For these platforms it is not possible to throw through a function call, and abort will be invoked instead of performing the throw. The reason the unwind data may be needed is that on some platforms the order and types of data stored on the stack can vary depending on the type of function, its arguments and returned values, and the compilation options used (optimization versus non-optimization, -fomit-frame-pointer, processor variations, etc). Unfortunately, this also means that throwing through functions that aren't compiled with exception handling support will still not be possible on some platforms. This problem is currently being investigated, but no solutions have been found that do not imply some unacceptable performance penalties. Future directions: Currently __throw makes no differentiation between cleanups and user-defined exception regions. While this makes the implementation simple, it also implies that it is impossible to determine if a user-defined exception handler exists for a given exception without completely unwinding the stack in the process. This is undesirable from the standpoint of debugging, as ideally it would be possible to trap unhandled exceptions in the debugger before the process of unwinding has even started. This problem can be solved by marking user-defined handlers in a special way (probably by adding additional bits to exception_table_list). A two-pass scheme could then be used by __throw to iterate through the table. The first pass would search for a relevant user-defined handler for the current context of the throw, and if one is found, the second pass would then invoke all needed cleanups before jumping to the user-defined handler. Many languages (including C++ and Ada) make execution of a user-defined handler conditional on the "type" of the exception thrown. (The type of the exception is actually the type of the data that is thrown with the exception.) It will thus be necessary for __throw to be able to determine if a given user-defined exception handler will actually be executed, given the type of exception. One scheme is to add additional information to exception_table_list as to the types of exceptions accepted by each handler. __throw can do the type comparisons and then determine if the handler is actually going to be executed. There is currently no significant level of debugging support available, other than to place a breakpoint on __throw. While this is sufficient in most cases, it would be helpful to be able to know where a given exception was going to be thrown to before it is actually thrown, and to be able to choose between stopping before every exception region (including cleanups), or just user-defined exception regions. This should be possible to do in the two-pass scheme by adding additional labels to __throw for appropriate breakpoints, and additional debugger commands could be added to query various state variables to determine what actions are to be performed next. Another major problem that is being worked on is the issue with stack unwinding on various platforms. Currently the only platforms that have support for the generation of a generic unwinder are the SPARC and MIPS. All other ports require per-function unwinders, which produce large amounts of code bloat. For setjmp/longjmp based exception handling, some of the details are as above, but there are some additional details. This section discusses the details. We don't use NOTE_INSN_EH_REGION_{BEG,END} pairs. We don't optimize EH regions yet. We don't have to worry about machine specific issues with unwinding the stack, as we rely upon longjmp for all the machine specific details. There is no variable context of a throw, just the one implied by the dynamic handler stack pointed to by the dynamic handler chain. There is no exception table, and no calls to __register_exceptions. __sjthrow is used instead of __throw, and it works by using the dynamic handler chain, and longjmp. -fasynchronous-exceptions has no effect, as the elimination of trivial exception regions is not yet performed. A frontend can set protect_cleanup_actions_with_terminate when all the cleanup actions should be protected with an EH region that calls terminate when an unhandled exception is throw. C++ does this, Ada does not. */#include "config.h"#include "defaults.h"#include <stdio.h>#include "rtl.h"#include "tree.h"#include "flags.h"#include "except.h"#include "function.h"#include "insn-flags.h"#include "expr.h"#include "insn-codes.h"#include "regs.h"#include "hard-reg-set.h"#include "insn-config.h"
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?