This disclosure relates to memory management and memory allocation of data structures and functions.
The run-time organization of memory for a computer program often divides a system's memory into regions to store data used by the program. For example, a portion of the memory stores the executable software or code and another portion stores the variables, arguments, data, etc. used by the executable software or code. Often, local or automatic variables are stored in a stack memory structure and global variables are stored at fixed locations in a so-called global memory, and a heap memory structure can be used to store variables and other data. In programs written in C or C++ or Objective C or other C-like procedural languages, including Java, a run-time stack holds the local variables for the currently executing function or functions; each execution of a function may be referred to as an activation. The run-time stack holds the local variables for the currently executing activation as well as the activation or function, which called the currently executing function. The currently executing function F1 has its data (e.g., local variables within the scope of function F1) at the top of the stack and just below the top of the stack is the data for the function F2 which called F1, and so on. Further information about stack usage can be found at pages 230-240 in the book Mac OS X Internals—A Systems Approach by Amit Singh (Addison-Wesley, 2007, Pearson Education, Inc.); these pages are incorporated herein by reference. A function's stack frame is “lost” when the function completes/exits and returns control to its caller. Hence, the local variables in the scope of the function are not retained valid in the stack after the function returns control to the caller of the function. A programmer can decide to avoid use of the stack by defining variables as global variables or by using a call, such as malloc, to allocate space for data in the heap memory structure; in this case, the stack is avoided (but can still be used for functions which use local variables that do not need to persist outside of their respective scope).
Programmers often desire to use a function or data structure known as a closure. A closure is a function that is evaluated in an environment containing zero or more bound variables. When called, the function can access these variables. In some languages, a closure may occur when a function is defined within another function, and the inner function refers to local variables of the outer function. At run-time, when the outer function executes, a closure is formed and consists of the inner function's code and references to any variables of the outer function required by the closure. Memory allocation in the prior art causes the closure and all bound local variables to be initially stored in the heap memory structure (and in this case the closure is always on the heap), although certain compilers attempt to determine if a closure will never need to be stored on the heap, in which case they are allocated, at run-time, on the stack (so in this case the closure is always on the stack). In other cases, a compiler can create run-time code which will automatically migrate a closure from an initial position in the stack to the heap in response to an escape from the closure's lexical scope. In the prior art, recovery of the heap-based storage is done through a garbage collector, which is uncommon for C or C based languages.
Furthermore, there is a common need and use of compiler generated thunks to recover or initialize extra data before calling specific functions. This allows the functions to appear to take fewer arguments than are necessary yet be supplied with the correct and complete amount of data. In GNU C Compiler (GCC), an inner function is written (e.g., a pre-compare function with reference to existing stack address) to recover the extra data. The inner function is passed in as a thunk. The problem with this approach is that the processor has to execute instruction on the stack to make it possible. This creates grave security risk because malicious programs can take over. Currently, some processors disallow executable code on the stack so the above GCC technique does not work with these processors.
Another conventional technique is to allocate a page, making it writable. Code is written on this page, including reference to the stack (which contains private data). Then the operating system is asked to mark this page not writable, but only executable. Sometimes, the execution cache of the processor may need to be flushed to make this approach work. This approach is expensive and has poor performance because a page has to be specifically allocated.
In one embodiment of the invention, a method for executing software written in a language, which uses a stack memory structure to store local or automatic variables, includes writing a data structure of a block or closure to the stack memory structure and then executing a block copy process, caused or invoked by a programmer in the creation of the software, to copy the block to a heap memory structure which is configured to store global variables. The block includes a function pointer that references a function which uses data in the block. In at least certain implementations of this method, the block is always initialized to be stored in the stack and a programmer is required to explicitly copy the closure to the heap; this explicit copy may be caused by writing a “block copy” call in the software, and this call at run time will invoke a block copy process or subroutine. Variables in the lexical scope of the closure or block are copied to the heap such that they still work after the lexical scope of the closure's creation is destroyed by returning from the function which created the closure. As a further optimization, certain variables are imported or appended to the closure in the heap as constants. The use of the heap's space may be managed by recovering space through explicit program instructions (e.g. a “block-dispose” call or instruction written by the programmer to match the “block-copy” call or instruction for a block) or through garbage collection techniques or through a combination of both.
Another aspect of this description relates to debugging when blocks or closures are present in the software being debugged. Source level debugging is a process of examining a program and providing data, where possible, on command, of where the instruction counter(s) are with respect to the original source as well as, potentially, the current data values of variables within that source program. A block, in one embodiment, includes a data block with a specialized function pointer that references a function that knows and uses the layout of that data block for computation according to the syntax of the block. A block can appear to a debugger as an opaque data structure that can be invoked like a function. The debugger may not be able to display the data in the opaque data structure, and it is desirable to provide a way for the debugger to display this data. In at least certain embodiments, one way for the debugger to display this data is to associate the specialized layout of the block with the specialized function that is referenced by the block. The data layout definitions are keyed to the specialized function referenced by the block. The debugger finds the specialized function within the otherwise opaque data structure of the block and can then use conventional debugger lookup functions to find and display the specialized layout information for that block.
Another aspect of this description relates to defenses against viruses and other malicious code. In order to prevent viruses and other malicious code from harming a computation it is necessary to not allow writeable data to be used as executable instructions. There is a common need and use of compiler generated thunks to recover or initialize extra data before calling specific functions. A thunk as used herein broadly refers to a piece of code to perform some delayed computation. This allows the functions to appear to take fewer arguments than are necessary yet be supplied with the correct and complete amount of data. The prior art often requires that each such thunk be allocated on its own page of memory such that it can start with writeable permission, be written upon with instructions to recover specific data, and then having the page marked as not writeable and executable, and depending on the architecture of the processor, the processor's instruction cache is flushed; these operations in the prior art are computationally expensive. Since each thunk requires a page of memory, these thunks need to be tracked and deallocated in many implementations. Uses of such thunks include “inner functions” in GCC and certain implementations of closures. At least certain embodiments of the invention provide an efficient way to use thunks by allocating paired chunks of memory and on one chunk of the pair write a series of small thunks that dereference a data area counterpart on the other chunk in the pair. Once written, the memory is protected as execute only and each thunk-data pair in the series is separately allocated and restored. Each thunk-data pair is provided as part of a thunk allocation which also takes the extra data that needs to be recovered. The extra data is stored in the appropriate location in the writeable chunk of memory. The instructions that are written vary according to the needs of the compiler or client and can be as simple as pushing the extra data as an extra stack argument.
The present invention is illustrated by way of example and not limitation in the figures of the accompanying drawings in which like references indicate similar elements.
Various embodiments and aspects of the inventions will be described with reference to details discussed below, and the accompanying drawings will illustrate the various embodiments. The following description and drawings are illustrative of the invention and are not to be construed as limiting the invention. Numerous specific details are described to provide a thorough understanding of various embodiments of the present invention. However, in certain instances, well-known or conventional details are not described in order to provide a concise discussion of embodiments of the present inventions.
Reference in the specification to one embodiment or an embodiment means that a particular feature, structure or characteristic described in connection with the embodiment is included in at least one embodiment of the invention. The appearance of the phrase “in one embodiment” in various places in the specification do not necessarily refer to the same embodiment.
Examples of methods for writing, compiling, and executing software which includes one or more block structures are provided in this description in the context of a language which uses a stack memory structure to store local or automatic variables. Examples of such languages include the language C, C++, and Objective C and other C-like procedural languages, including Java, etc. The block includes a function pointer that references a function which uses data in the block. In at least certain embodiments, the block is always initialized to be stored in the stack and the programmer is required to explicitly copy the block to the program's heap, and this explicit copy operation may be caused by writing an indication, such as a call or a directive or an instruction, to cause a block copying operation to be performed at run-time. A heap memory structure may be implemented in a variety of different ways including a tree structure, as is known in the art.
The operations shown in
In operation 201 of
In operation 203, the compiler processes any indication, entered by the programmer, to cause a block copy operation at run-time, which will copy the block from a respective stack to a heap and to cause an updating of stack based pointers held within the byref variable structures to point to the location on the heap. In one embodiment, the compiler may process a call to a block copy subroutine in a shared run-time library which performs the block copy of the block from the stack to the program's heap. It will be understood that, in a typical embodiment, only the first indication within the program will cause the copy of the block from the stack to the heap and further indications do not cause additional copies of the block to be created on the heap. In operation 205, the compiler processes a block release indication to cause, at run-time, removal of the block from the heap. In one embodiment, the block release indication was entered by the programmer in writing the software according to a rule which requires that for every block copy indication for a particular block, there should be a block release indication. In alternative embodiments, the compiler may display an error message should such block release indication not be present. The block release indication should be placed in an appropriate location within the software such that the block is released after the completion of execution of the block.
In operation 207, the compiler creates software that references shared byref variables indirectly through a pointer held within a structure holding the variables. In operation 211, the compiler creates software (e.g. a call to a run-time routine) to remove a copy of the shared byref variable data structure from the heap at every lexical escape of a shared variable, when the run-time system is not using a garbage collector. The code generator in operation 211 may, in one embodiment, be a call to a “byref_block_release” function that actually recovers the storage in the heap if after decrementing a reference count it finds the count at zero. Operation 213 performs an optional method which can be used to copy variables as constants for use within a block. In another embodiment, these variables may not be treated as constants (“const”), but although they may be changed the effects of the change would not be shared. It will be understood that this is an optimization procedure in which certain variables are imported into the block as constants. This can be performed at the programmer's discretion by having the variable named by value which causes the software created in operation 213 to append the constant to the data block structure on the heap during run-time (as shown in operation 307).
The method shown in
As noted above, an optional operation may be performed to append a constant, which replaces a variable, by appending the constant to represent the variable to the block's data structure on the heap.
Operation 309 and 311 are performed at run-time in order to release memory from the heap for both the block data structure and the shared variable data structure. The execution of the block dispose instructions, which may be called by the block release indication specified as part of operation 205, cause the removal of the block from the heap. The programmer will typically place this call at the appropriate point in the program when the block is no longer needed or else rely on garbage collection if present to recover the heap memory. Operation 311 may be automatically performed to remove a copy of the shared variable data structure; this operation may be caused to occur by a compiler inserting calls to a routine at every place where the code which uses the shared variable data structure exits or where the variable goes out of scope.
One embodiment of the run-time embodiment of the invention may manage memory recovery by requiring the programmer to include calls to the block dispose subroutine. Hence, memory recovery is self-managed rather than being managed by an automatic garbage collection system. However, in other embodiments, garbage collection routines may be utilized, in which case the garbage collection systems manages the block release instructions.
Another aspect of the present invention relates to debugging when blocks or closures are present in software being debugged. A block in one embodiment includes a data block with a specialized function pointer that references a function that knows and uses the layout of that data block for computation according to the syntax of the block. A block can appear to a debugger as an opaque data structure that can be invoked like a function. Hence, the debugger may not be able to display the data in the opaque data structure, and it may be desirable to provide a way for the debugger to display this data. In at least certain embodiments, one way for the debugger to display this data is to associate the specialized layout of the block with the specialized function that is referenced by the block. One association is shown in
Following is an example of a formal specification of blocks according to one implementation.
A new type is introduced to C and by extension Objective-C, C++, and Objective-C++. The type is a pair consisting of the result value type and a list of parameter types very similar to a function type.
The string “int (char, float)” describes the type of a Block that has a result value of type int and two parameters, the first of type char and the second of type float.
A Block type is declared using function pointer style notation but substituting for *. The following are all valid Block types:
(void)
(int, char)
Variadic ‘ . . . ’ arguments are supported. A Block that takes no arguments must specify void in the argument list. An empty parameter list does not represent, as K&R provide, any argument list.
There is one operation on Block types, invoking them with a type checked set of parameters and simultaneously extracting a result value.
Blocks are invoked with a list of expression parameters of types corresponding to the declaration.
Objective-C extends the definition of a Block type to be that also of id. A variable or expression of Block type may be messaged or used as a parameter wherever an id may be. The converse is also true.
All Blocks are constructed to be Objective-C objects regardless of whether the Objective-C run-time is operational in the program or not.
A Block is implemented as a structure that starts with the following fields.
Compiler generated code for invoking a Block can extract the invoke function pointer and call it passing the Block data structure and all additional parameters.
Note that if the Block returns a value, such as a structure, that is passed via a hidden argument in the ABI (Application Binary Interface), the normal ABI conventions are followed. Thus, a raw Block pointer, just like a raw function pointer, cannot be correctly invoked without knowing its return value type.
A Block literal is created by the new use of thetoken as a unary operator. The form is, generally, the
symbol followed by the parenthesized list of expression parameters, and a code body. The return type is inferred from the type of the return statement expression. The code body is that of a compound statement. The list must appear before the first statement (if any) and is itself enclosed by | tokens. Examples:
( void ) {
Local automatic (stack) variables referenced within the compound statement of a Block are imported and captured by the block. Global variables and references to global functions are treated normally. A variable declared with the_block storage specifier is moved by the compiler into a special on-stack structure that can, if needed, be copied to a heap based memory location while still being shared by both the function that defines it and all Blocks that reference it. Local variables not marked with_block are imported as const copies of their values at the time the Block expression is formed.
The compound statement body of a Block establishes a new lexical scope such that new local variables may be defined. Other variable references are to the definition point in the closest enclosing lexical scope. A local variable defined in a Block may be referenced as either a const import or by_reference in a subsequent Block.
Objective-C extends the definition to allow the use of the names of instance variables when a Block expression is formed in an appropriate instance method (Class methods have no access to instance variables). If instance variables are referenced then a const import of the self variable is made and all accesses within the Block are via the imported const version of self. There is no similar provision for C++ because it is not likely to be desirable to form a const zero-argument constructor copy of this.
In the example above the values x and y are implicitly imported into the compound statement as const variables. The basic structure is augmented as
This structure is allocated on the stack and the fields x and y are initialized to the values of x and y variables at the point of declaration. C scalars, structures, references, and Blocks are simply assigned as simple initialization assignment.
Blocks support persistence by way of the run-time helper function_Block_copy( ). The compiler provides assistance in copying for variables of types Objective-C objects, Blocks, by_reference variables, and C++ stack objects. There are two additional helper routines synthesized for use by_Block_copy, if needed, and if present, Blocks_flags is marked with BLOCK_HAS_COPY_DISPOSE. The first is the copy helper which takes the new and the existing Block data structure. Objective-C object pointers are sent the -retain message unless the -fobjc-gc-only flag is set, and they are assigned using the objc_assignStrongCast( ) operation if -fobjc-gc or -fobjc-gc-only flag is set. Block variables are copied using the specific objc run-time helper function_Block_copy_assign. C++ objects are copied using the default copy constructor.
Similarly the compiler provides a destruction helper function that is passed the Block data structure. Objective-C objects are sent the -release message unless the -fobjc-gc-only flag is set. Block variables are passed to a support routine_Block_destroy, and C++ objects have their appropriate destructors synthesized.
By_ref parameters (those marked with_block) are limited to automatic variables of an enclosing scope. Conceptually, every local variable that is imported as a by_reference variable in any block in that scope, including that of the function/method, is actually allocated on the stack as a member of its own unique structure. This structure will be copied to a heap if one of the Blocks that references this variable is copied using Block_copy( ). To support continued access to this variable as it is copied, the structure contains a forwarding pointer that is initially set to the start of this structure, and all accesses to that variable are made indirectly through the forwarding pointer.
After the point of last use and before escape from the enclosing scope a call to a run-time dispose function Block_destroy_byref( ) is made upon the structure.
The layout of the shared storage is
The copy and destroy helper routines synthesized for a Block must also, for each such shared structure containing a shared by_reference variable, emit a copy helper function call_Block_byref_assign_copy to preserve (if necessary) the shared data structure by copying it to the heap. And in the destroy helper it must call_Block_byref_release( ) to help recover the heap reference.
Similar to the case of constant imported variables, if variables of types Objective-C objects, Blocks, or C++ stack variables are named in by_reference sections, they need support help from the compiler for when they are copied to the heap. The support help is identical to that of const imports, except that the destinations are not typed or treated as const variables. The flags word of the Block_byref structure should be marked with BLOCK_HAS_COPY_DISPOSE if such helper routines are present.
C++ stack objects continue to require destructors despite their enclosure in the stack based structure.
The idea here is that all stack based Blocks share a stack based byref data structure. The byref data structure initially holds non-retained objects and Blocks. Upon copy, the run-time arranges to mark the copy as a copy and to properly retain its components.
A byref storage structure is conceptually required for each variable shared in any reachable Block.
Note: All variables used as by_reference variables in the same set of Blocks may share the same shared storage structure.
The compound statement of a Block is treated much like a function body with respect to control flow in that gotos, breaks, and continues do not escape the Block. Exceptions are treated “normally” in that when thrown they pop stack frames until a catch clause is found.
The scope of local variables is that of a function—each activation frame contains a new copy of variables declared within the local scope of the Block. Such variable declarations should be allowed anywhere rather than only with C99 or gnu specific flags, including for statements.
There are no “Block” lifetime scoped variables that persist across multiple invocations. This would likely require a way to specify finalization logic for when the Block was recovered.
Another aspect of some embodiments of the current invention relates to defenses against viruses and other malicious code. Some embodiments of computing systems include dynamic compilers. Generally speaking, a dynamic compiler in a computing system allows embedding of data addresses directly in dynamically generated code (as opposed to a static compiler), and compiles the code into binary code executable by a processor in the computing system. It should be noted that for small instruction sequences such as are needed by thunks only a few well known instructions need be generated and this knowledge may be embedded by the compiler into the program directly and thus not require that an entire dynamic compiler be embedded within the program. An address of a function of interest in the binary code allows recovery of data address. In some embodiments, this function of interest may be viewed as an address of code instruction with a list of arguments. The address of the function of interest is put into a stack during execution. A function pointer is synthesized to recover additional argument(s) using a thunk, which is substantially similar to a mini function within the function of interest. As discussed above, a thunk as used herein broadly refers to a piece of code to perform some delayed computation. In some embodiments, the thunk provides a new way to bind data to a particular function, which may be referred to as per function runtime data. This approach is applicable to pre-compiled instructions in various types of computing languages, such as C, object-oriented programming languages (e.g., C++, Objective-C), assembly languages, etc.
To further illustrate the above concept, one example is used in the following discussion. In this example, an array of strings in a specific format (e.g., first name in the first six (6) characters, last name in the next six (6) characters, etc.) can be input to a facility (e.g., a sort routine named Qsort), which calls a compare function to compare the strings in order to sort them. A compare function is built to look up extra data that indicates by which piece of data the strings are supposed to be sorted (e.g., by first name), and/or the row and column by which the strings are to be sorted. According to one conventional approach, the extra data, which is writable data, is used as executable instruction. But such usage may pose a severe security risk to the computing system because viruses or malicious code may be introduced via the writable data.
In some embodiments, the above security problem is solved using thunk synthesis, which provides a way to bind data to a particular function. To create reusable resource for thunk synthesis, the operating system first sets up a portion of the memory in the computing system.
Note that the operating system performs the above operations only once to create this resource (i.e., the two chunks of memory). These two chunks of memory are reusable. In other words, there is essentially no additional setup cost incurred when a second function is executed because the resource needed has already been set up during execution of the first function.
At block 714, processing logic determines if two chunks of memory have been set up yet. If not, processing logic calls a coordinator function in a library to set up the two chunks of memory at block 718. In some embodiments, the coordinator function in the library is used to get a function slot and to return the function slot. For example, the coordinator function may cause the operating system to allocate two chunks of memory for thunk synthesis. Then the coordinator function may cause the operating system to set the two chunks of memory up by marking them as writable or non-writable, executable, etc., as described above with reference to
Otherwise, if the two chunks of memory have already been set up, then processing logic looks up a first pointer in a first location in the first chunk of memory associated with the function at block 720. Then processing logic uses the first pointer to look up a second location in the second chunk of memory at block 724. Processing logic further looks up a second pointer at the second location in the second chunk of memory at block 726. Finally, processing logic uses the second pointer to load an extra argument of the function or to jump to a closure function at block 728.
Applying the above technique to the previous example, a program including the function Qsort, may be processed as follows. As previously discussed, Qsort calls a programmer supplied function, compare, to sort an array of strings. The programmer will supply an inner function sortByName that uses local information about the column positions of the first and last names. Because the compiler knows that there are secret additional parameters (namely the column position information), the compiler writes the sortByName function in a way that accesses a secret extra parameter, say, held in a scratch register. It then writes code that will allocate one of the many thunks established by the coordinator. One embodiment of two chunks of memory 810 and 820 for thunk synthesis is illustrated in
During execution, Qsort calls the thunk code which loads the previously stored address of the column position variables 822 into the known scratch register and then jumps to the address 824 also stored in the second chunk of memory 820, which is the code for the sortByName function. It does the comparison using the extra information and returns directly to Qsort. This operation is done for every pair of elements that the Qsort function determines is necessary to sort the array.
After Qsort returns execution passes back to the recovery code that the compiler generated and the thunk is returned to the coordinator for some other use. Note that the thunks are small and that many are established in the two chunks of memory 810 and 820, where the thunks are handed out and recovered.
If the two chunks of memory 810 and 820 run out of space, then the processor may call the coordinator function again to extend the two chunks of memory 810 and 820 by allocating more memory to the two chunks of memory 810 and 820.
When execution of the function compare has been completed, get_function may be called to return (or to free up) the thunk in the two chunks of memory 810 and 820. Alternatively, a garbage collection system or other memory recovery mechanism may be used instead of get_function to return the thunk.
As shown in
It will be apparent from this description that aspects of the present invention may be embodied, at least in part, in software. That is, the techniques may be carried out in a computer system or other data processing system in response to its processor, such as a microprocessor, executing sequences of instructions contained in a machine-readable storage medium such as a memory (e.g. memory 49 and/or memory 50). In various embodiments, hardwired circuitry may be used in combination with software instructions to implement the present invention. Thus, the techniques are not limited to any specific combination of hardware circuitry and software nor to any particular source for the instructions executed by the data processing system. In addition, throughout this description, various functions and operations are described as being performed by or caused by software code to simplify description. However, those skilled in the art will recognize what is meant by such expressions is that the functions result from execution of the code by a processor, such as the processing system 47.
In the foregoing specification, the invention has been described with reference to specific exemplary embodiments thereof. It will be evident that various modifications may be made thereto without departing from the broader spirit and scope of the invention as set forth in the following claims. The specification and drawings are, accordingly, to be regarded in an illustrative sense rather than a restrictive sense.
This application claims the benefit of U.S. Provisional Patent Application No. 61/059,724, filed on Jun. 6, 2008, which is herein incorporated by reference.
Number | Date | Country | |
---|---|---|---|
61059724 | Jun 2008 | US |