End-to-End Verification of Stack-Space Bounds for C Programs
- Mae Harrington
- 2 years ago
- Views:
Transcription
1 End-to-End Verification of Stack-Space Bounds for C Programs Quentin Carbonneaux Jan Hoffmann Tahina Ramananandro Zhong Shao Yale University tquentin.carbonneaux, jan.hoffmann, tahina.ramananandro, * Artifact * AEC PLDI * Consistent * Complete * Well Documented * Easy to Reuse * Evaluated * Abstractspace system code. The examples include the verified operating-system kernel CertiKOS, parts of the MiBench embedded benchmark suite, and programs from the CompCert benchmarks. The derived bounds are close to the measured stack-space usage of executions of the compiled programs on a Linux x86 system. Categories and Subject Descriptors D.2.4 [Software Engineering]: Software/Program Verification; D.3.3 [Programming Languages]: Processors Compilers Keywords Formal Verification, Compiler Construction, Program Logics, Stack-Space Bounds, Quantitative Verification PLDI 14, June 9-11, 2014, Edinburgh, United Kingdom. Copyright is held by the owner/author(s). Publication rights licensed to ACM. ACM /14/06... $ Introduction It has been shown that formal verification can greatly improve software quality [25, 33, 35]. Consequently, formal verification is extensively studied in ongoing research and there exist sophisticated tools that can verify important program properties automatically. However, the most interesting program properties are undecidable and user interaction is therefore inevitable in formal verification. If a software system is (partly or entirely) developed in a highlevel language then the question arises on which language level the verification should be carried out. Formal verification at the source level has the advantage that a developer can interact with the verification tools using the code she has developed. This is beneficial because the compiled code can substantially differ from the source code and low-level code is harder to understand. Moreover, even fully automatic tools profit from the control-flow information and the structure that is available at higher abstraction layers. The disadvantage of verification at the source level is that tools such as compilers have to be part of the trusted computing base and that the verified properties are not directly guaranteed for the code that is executed on the system. Formally verified compilers [11, 24] such as the CompCert C Compiler [27] guarantee that certain program properties of the source programs are preserved during compilation. As a result, CompCert enables source-level verification of the preserved properties of the compiled code without increasing the size of the trusted computing base. 1 In fact, this has been one of the main motivations for the development of CompCert [27]. However, important quantitative properties such as memory and time consumption are not modeled nor preserved by CompCert and other verified compilers [11, 24]. Such quantitative properties are nevertheless crucial in the verification of safety-critical embedded systems. For example, the DO-178C standard, which is used by in the avionics industry and by regulatory authorities, requires verification activities to show that a program in executable form complies with its requirements on stack usage and worst-case execution time (WCET) [28]. Quantitative program requirements such as stack usage and WCET are usually directly checked at the machine or assemblycode level since only at this level is all necessary information available [34]. For stack-space bounds there exist commercial abstract interpretation based tools such as Absint s StackAnalyzer [14] that operate directly on machine code. While such tools can derive many simple bounds automatically, they rely on user annotations in the machine code to obtain bounds for more involved programs. The produced bounds are usually not parametric in the input, and the analysis is not modular and only applies to specific hardware platforms. Additionally, the used analysis tools rely on the correctness of the user annotations and are not formally verified. In this article, we present the first framework for deriving formally verified end-to-end stack-space bounds for C programs. Stack bounds are particularly interesting because stack overflow 1 If we assume that all verification is carried out using the same trusted base.
2 is one of the toughest (and unfortunately common) problems in embedded systems [13]. Moreover, stack-memory is the only dynamically allocated memory in many embedded systems and the stack usage depends on the implementation of the compiler. While we focus exclusively on stack bounds in this article, our framework is developed with other quantitative resources in mind. Many of the developed techniques can be applied to derive bounds for resources such as heap memory or clock cycles. However, for clock-cycle bounds there is a lot of additional work to be done that is beyond the scope of this article (e.g., developing a formal model for hardware caches and instruction pipelines). The main innovation of our framework is that it enables the formal verification of stack bounds for compiled x86 assembly code at the C level. To gain the benefits of source-level verification without the entailed disadvantages, we have to deal with three main challenges. 1. We have to model the stack consumption of programs at the C level and we have to formally prove that our model is consistent with the stack consumption of the compiled code. 2. We have to design and implement a C-level verification mechanism that allows users to derive parametric stack-usage bounds in an interactive and flexible way. 3. We have to minimize user interaction during the verification to enable the verification of large systems. To meet Challenge 1, we use event traces and verified compilation. Our starting point is the CompCert C Compiler. It relies on event traces to prove that a compiled program is a refinement of the source program. We extend event traces with events for function calls and returns and define a weight for event traces. The weight describes the stack-space consumption of one program execution as a function of a cost metric that assigns a cost to individual call and return events. The idea is that a user or an (semi) automatic analysis tool derives bounds on the weights of event traces that depend on the stack-frame sizes of the program functions. During compilation the compiler produces a specific cost metric that guarantees that the weight of an event trace under this metric is an upper bound on the stack-space usage of the compiled assembly program which produces this trace. As a result, we derive a verified upper bound if we instantiate the derived memory bound with the cost metric produced by the compiler. We implemented the extended event traces for full CompCert C and all intermediate languages down to x86 assembly in Coq. We extended CompCert s soundness theorem to take into account the weights of traces. In addition to CompCert s refinement theorem for the original event traces, we prove that compiled programs produce extended event traces whose weights are less than or equal to the weights of the traces at the source level. This means that we allow reordering or deletion of call and return events as long as the weight of the trace is reduced or unchanged. To relate the weight of traces to the execution on a system with finite stack space, we modified the CompCert x86 assembly semantics into a more realistic x86 assembly that features a finite stack, and reimplemented the assembly generation pass of CompCert to our new x86 assembly semantics. To meet Challenge 2, we have developed and implemented a novel quantitative Hoare logic for CompCert Clight in Coq. To account for memory consumption, the assertions of the logic generalize the usual boolean-valued assertions of Hoare logic. Instead of the classic true, our quantitative assertions return a natural number that indicates the amount of memory that is needed to execute the program. The boolean false is represented by 8 and indicates that there are no guarantees provided for the future execution. We proved the soundness of our quantitative Hoare logic with respect to Clight and CompCert s continuation-based small-step semantics. The soundness theorem states that Hoare triples that are derived with our inference rules describe sound bounds on the weights of traces. The logic can be used for interactive stack-bound development or as a backend for verified static analysis tools. For clarity, we do not prove the safety of programs and simply assume that this is done using a different tool such as Appel s separation logic for Clight [3]. It would be possible to integrate our logic into a separation logic for safety proofs. This would however diminish the deployability of the quantitative logic as a backend for static stack-bound analysis tools since they would be required to also prove memory safety. To meet Challenge 3, we implemented an automatic stack analyzer for C programs. To verify the soundness of the stack analyzer each successful run generates a derivation in the quantitative Hoare logic. Not only does this simplify the verification, but it also allows interoperability with stack bounds that have been interactively developed in the logic or derived by some other static analysis. Conceptually, our stack analyzer is rather simple but we have proved that it derives bounds for all programs without recursion and function pointers. This is already sufficient for many programs that are used in embedded systems. Using our automatic analysis we have created a verified C compiler that translates a program without function pointers and recursive calls to x86 assembly and automatically derives a stack bound for each function in the program including mainpq. We have successfully used our framework to verify end-to-end memory bounds for micro benchmarks and system software. Our main example is the CertiKOS [15] operating system kernel that is currently under development at Yale. Our automatic analyzer finds stack bounds for all functions in the simplified development version of CertiKOS that is currently verified. Other examples are taken from Leroy s CompCert benchmarks and the MiBench embedded benchmark suite [17]. To evaluate the quality of the verified stackspace bounds, we experimentally compared the automatically and manually verified bounds with the actual stack-space consumption during the execution of the compiled C programs. Our experiments indicate that both the manually and automatically derived bounds over-approximate the stack usage by exactly four bytes. More details can be found in Section 6. In summary, we make the following contributions. We introduce a methodology that uses cost metrics to link event traces to resource consumption. This approach enables us to link source-level code to the resource consumption of compiled target-level code. We develop a novel quantitative Hoare logic to reason about the resource consumption of programs at the source level. We have formally verified the soundness of the logic with respect to CompCert Clight in Coq. We introduce Quantitative CompCert, a modified version of the verified CompCert C Compiler, in which parametric stack bounds are preserved during compilation. Furthermore, Quantitative CompCert creates a cost metric so that the instantiation of the bounds with the metric forms an upper bound on the memory consumption of the compiled code. We have implemented and verified an automatic stack analyzer. We have evaluated the practicability of our framework with experiments using micro benchmarks and system code. The complete Coq development and the implemented tools are well documented and publically available on the authors websites. The PLDI Artifact Evaluation Committee reproduced samples of our experiments and tested the implemented tools on additional programs. The reviewers unanimously stated that our implementation exceeded their expectations. A companion technical report [9] contains additional explanation, lemmas, and examples.
3 2. An Illustrative Example In this section, we sketch the verification of stack-space bounds for an example program in our framework. Figure 1 shows a C program with two integer parameters: ALEN and SEED. This program will fill an array of size ALEN with an increasing sequence of pseudo random integers and search through it. The random numbers are created by a linear congruential generator initialized by the SEED parameter. The search procedure used is a binary search implemented in the recursive function search. Our goal is to derive stack bounds for the compiled x86 assembly code of the program that are verified with respect to our accurate x86 model in Coq. The first step is to create an abstract syntax tree of the code in Coq. This can be done automatically, for instance by using CompCert s parsing mechanism. The second step is to use our quantitative Hoare logic to prove bounds on the function calls that are performed when executing main. To relate function calls and returns at different abstraction levels during compilation we use call and return events. For instance, an execution of main could produce the following trace. callpmainq, callpinitq, callprandomq, retprandomq, retpinitq, callpsearchq, callpsearchq, retpsearchq, retpsearchq, retpmainq From such a trace and a metric M that maps each function name in the program to its stack-frame size, we can obtain the stack usage of the execution that produced the trace. For the previous example trace, we can for instance derive the following stack usage. Mpmainq ` maxtmpinitq ` Mprandomq, 2 Mpsearchqu In classical Hoare logic, assertions map program states to Booleans. In our quantitative Hoare logic assertions map program states to non-negative numbers. Intuitively, the meaning of a quantitative Hoare triple tp u S tqu is the following. For every program state σ, P pσq is an upper bound on the stack consumption of the statement S started in state σ. Furthermore, Q describes the stack space that has become available after the execution, as a function of the final program state. This is similar to type systems and program logics for amortized resource analysis [5, 21]. We implemented a function in Coq that automatically computes a derivation in the quantitative logic for a program without recursive functions. Using this automatic stack analyzer, we derive for instance the following triple for the function call initpq. tmpinitq ` Mprandomqu initpq tmpinitq ` Mprandomqu For functions making use of recursion such as search, we derive a quantitative triple interactively using Coq. For search we derive tlpend begqu searchpelem, beg, endq tlpend begqu where Lp q Mpsearchq p2`log 2 p qq. Since the mathematical log 2 function is undefined on non-positive values, we take as convention that log 2 p q `8 when ă 0 and log 2 p0q 0. This trick allows us to simulate a logical precondition stating that beg must be lower or equal to end before calling search. For main we combine the previous results and derive the bound tmpmainq ` Nu mainpq tmpmainq ` Nu where N maxpmpinitq ` Mprandomq, LpALENqq. To be able to derive this bound on the main function we have to require that 0 ă ALEN ď , in the Coq development this is stated as a section hypothesis which will later be instantiated when ALEN is chosen by the user before compiling. The third and final step in the derivation of the stack bounds is to compile the program with Quantitative CompCert, our modified CompCert C Compiler. The compiler produces x86 assembly code and a concrete metric M 0. It follows from CompCert s correctness theorem that the compiled code is a semantic refinetypedef unsigned int u32; u32 a[alen]; u32 seed = SEED; u32 search(u32 elem, u32 beg, u32 end) { u32 mid = beg + (end-beg) / 2; if (end-beg <= 1) return beg; if (a[mid] > elem) end = mid; else beg = mid; return search(elem, beg, end); } u32 random() { seed = (seed * ) ; return seed; } void init() { u32 i, rnd, prev = 0; for (i=0; i<alen; i++) { rnd = random(); a[i] = prev + rnd % 17; prev = a[i]; } } int main() { u32 idx, elem; init(); elem = random() % (17 * ALEN); idx = search(elem, 0, ALEN); return a[idx] == elem; } Figure 1. An illustrative example for static stack-bound computation. Constant stack bounds for the non-recursive functions are derived automatically. The logarithmic bound for the function search is derived with a hand-crafted proof in our quantitative Hoare logic. ment of our source program. In addition, we have formally verified that the metric M 0 correctly relates the abstractly defined stack consumption using the event traces to the actual stack consumption in our abstract x86 machine. Moreover, we have verified that applying M 0 to the preconditions in the triples of the quantitative Hoare logic results in sound stack bounds on the x86 machine. The final bounds that we obtain for our examples are for instance 32 bytes for initpq and 112 ` 40 log 2 palenq bytes for mainpq. 3. Quantitative CompCert: Verified Stack-Aware Compilation In this section, we introduce our new technique for verifying quantitative compiler correctness and its implementation in Quantitative CompCert. We focus on stack-space usage but believe that similar techniques can be used to bound the time and heap-space requirements of programs. Our development is highly influenced by the design of CompCert [27], a verified compiler for the C language. CompCert C accepts most of the ISO-C-90 language and produces machine code for the IA32 architecture (among others). CompCert uses 11 intermediate languages and 20 passes to compile a C AST to x86 assembly. The soundness proof of CompCert is based on trace-based operational semantics for the source, target, and intermediate languages. These semantics generate traces of events during the execution of programs. Events include input/output and external function calls. The soundness theorem of CompCert states that every event trace that can be generated by the compiled program can also be generated by the source program provided that the source program does not go wrong. In other words, the compiled program is a refinement of the source program with respect to the observable events.
4 Safety Proof Source Program s Verified Quantitative Hoare Logic Weight Bound :(E! Z)! N 8 M. W M (s) apple (M) C(s) v s Verified Compiler stack(c(s)) apple W Ms (C(s)) C(s) v Q s Safety Proof Target Program C(s) Event Metric M s : E! Z Stack-Usage Bound stack(c(s)) apple (M s ) Figure 2. Overview of our quantitative verification framework. We write W M psq suptw M pbq B P s u for the weight of the program s under the metric M. We write stackpsq for the smallest number n so that s runs without stack overflow if executed with a stack of size n. 3.1 Quantitative Compiler Correctness In the following, we show how to extend trace-based compilercorrectness proofs to also cover stack-space consumption. In short, our technique works as follows. 1. We generate events for all semantic actions that are relevant for stack-space usage, that is, function calls and returns. 2. We define a weight function for event traces that describes the stack-space consumption of program executions that produce that trace. The weight of an event trace is parameterized by a resource metric that describes the cost of each event. 3. We formally verify that for all resource metrics and for all event traces produced by a target program, the source program either goes wrong or produces an equivalent (see the following definition) event trace with a greater or equal weight. 4. During compilation, we produce a cost metric that accurately describes the memory consumption of target programs: If an execution of a target program produces an event trace of weight n under the produced metric then this execution can be performed on a system with stack size n. We now formalize and elaborate on these points. Event Traces In CompCert, the observable events are external function calls (e.g., I/O events) that are represented by function identifiers together with a list of input values and an output value as given by the following grammar. To track stack usage, we add memory events for internal function calls and returns. In contrast to I/O events, memory events do not have to be preserved during compilation. Event values I/O events Memory events v :: intpnq floatpqq ν :: fp v ÞÑ vq µ :: callpxq retpxq Event traces are defined in a similar way to CompCert. We distinguish finite (inductive) traces t and possibly infinite (coinductive) traces T. A program behavior is either a converging computation convpt, nq producing a finite event trace t and a return code n, a diverging computation divpt q producing a finite or infinite trace T, or a computation failptq that goes wrong and produces the finite trace t. Finite event traces Coinductive event traces Behaviors t :: ɛ ν t µ t T :: ɛ ν T µ T B :: convpt, nq divpt q failptq We write E for the set of memory and I/O events, B for the set of behaviors, and T for the set of traces. Weights of Behaviors For a behavior B, we define the set of finite prefix traces prefspbq of B as follows. prefspconvpt, nqq tt 1 t t 1 t 2u prefspdivpt qq tt T t T 1 u prefspfailptqq tt 1 t t 1 t 2u The weight W M pbq P N Y t8u of a behavior B describes the number of bytes that are needed in an execution that produces B. It is parameterized by a resource metric M : E Ñ Z that maps events to integers (bytes). The purpose of the metric in our work is to relate memory events to the sizes of the stack frames of functions in the target code. To this end, we only use stack metrics, that is, metrics M such that for all functions f and for all external functions g 0 ď Mpcallpfqq Mpretpfqq and Mpgp v ÞÑ vqq 0. In the Coq implementation of our compiler, we can also deal with nonzero stack consumption for external functions as long as the stack consumption of each call is bounded by a constant. Before we define the weight, we first inductively define the valuation V M ptq of a finite trace t. V M pɛq 0 and V M pα tq V M ptq ` Mpαq We now define the weight W M pt q of a potentially infinite trace T and the weight W M pbq of a behavior B under the metric M as follows: W M pt q suptv M ptq T t T 1 u W M pbq suptv M ptq t P prefspbqu Quantitative Refinement For our description of quantitative refinements we leave the definition of programs abstract. A program s P P is simply an object that is associated, through a function : P Ñ B, with a set of behaviors s P B. An execution of a program can produce different traces, either due to non-determinism in the semantics or due to user inputs recorded in the event traces. For a behavior B we define the pruned behavior as the behavior B that results from deleting all memory events (callpxq or retpxq) from B. The formal definition can be found in the TR [9]. In CompCert, compiler correctness is formalized through the notion of refinement. A (target) program s 1 is a refinement of a (source) program s, written s 1 ă s, if for every behavior B 1 P s 1 there is B P s such that B B 1 or failptq P s for some trace t. 2 Note that memory events are not taken into account in CompCert s classic definition of refinement. 2 In fact, it is enough to prove that B 1 B (bisimilarity of infinite traces), because s is closed by bisimilarity.
5 Quantitative Hoare Logic Automatic Stack Analyzer x86 ASM Stack Merging CompCert 1.13 w C Source w Q Clight w Cminor w Linear Mach Mach2sz Q Q w Q w Q w Q w x86 ASMsz Figure 3. Our modified stack-aware CompCert C compiler. We replace CompCert s x86 assembly with the more realistic x86 assembly semantics ASM sz with finite stack. Pseudo assembly instructions such as Pallocframe and Pfreeframe are not needed anymore. To also relate the memory events in the behaviors of two programs, we define a novel quantitative refinement. A (target) program s 1 is a quantitative refinement of a (source) program s, written s 1 ă Q s if the following holds. For every behavior B 1 P s 1 there exists B P s such that B B 1 and W M pb 1 q ď W M pbq for all stack metrics M, or failptq P s for some trace t. In Quantitative CompCert, our modified CompCert compiler, we prove for each compiler pass C that Cpsq ă Q s for every program s. Verifying Stack-Space Usage Figure 2 summarizes how we verify the stack-space usage of a program in our framework. First, we prove a bound β : pe Ñ Zq Ñ N on the weights of the event traces that a program can produce. This bound is parameterized by an event metric M : E Ñ Z. Second, our verified compiler thanks to quantitative refinement ensures that the computed bound also holds for the weights of the traces of the compiled program. Third, we have to relate the computed bound to the actual stack usage of the compiled code. Therefore, our compiler computes not only a target program Cpsq but also a metric M s such that Cpsq can be safely executed with a stack-memory size of suptw Ms pbq B P Cpsq u bytes. As a result, the initially derived bound for the source code can be instantiated with the metric M s to obtain the wanted stack-space bound M spβq for the target program. In this overview picture, we assume that the semantics of the target and source languages are both formulated with an unbounded stack. The final step of the soundness proof (not illustrated in Figure 2) is to relate the trace-based semantics of the target language to a realistic assembly semantics in which the program is executed with a fixed stack size. To this end, we prove that an execution of Cpsq with bounded stack space suptw Ms pbq B P Cpsq u is a refinement of the execution of Cpsq in the semantics with unbounded stack (see explanation in Section 3.2). 3.2 Verification and Implementation We implemented the verification framework that we outlined in Section 3.1 for the CompCert C compiler using the proof assistant Coq. The verification consists of about 5000 lines of Coq code that we integrated into CompCert 1.13 (which originally consists of about lines of Coq code) to obtain a modified version that we call Quantitative CompCert. CompCert 1.13 is decomposed into 20 passes between 11 intermediate languages (see [9] for an overview). We describe our modified Quantitative CompCert in this section. The problem: stack consumption in CompCert For each intermediate language of CompCert (beyond C subsets), each function call allocates a memory region called the stack frame to store its addressable local variables, and later the spilling locations and the function arguments to handle the calling conventions. This stack frame is freed upon function return. However, even though each stack frame is finite, there may well be an unbounded number of such allocations, even for nested function calls. Indeed, in CompCert, allocating a stack frame always succeeds, thus CompCert does not model stack overflow. Our solution: Quantitative CompCert In Quantitative CompCert, we overcome this issue by modifying the semantics of the target assembly language. We preallocate a finite memory region for the whole stack, into which all stack frames shall be merged together during the execution instead of being individually allocated. By contrast, we still want the source and intermediate languages to allocate an individual stack frame per function call. First, we want to change CompCert only if necessary so as to still support all features of CompCert C. Second, it would not be very meaningful to introduce a finite stack at a high language level since it is unclear how to model stack sizes. The only major change we bring to those languages is to introduce our call and return events into the trace. As shown in Figure 3, this leads us to split CompCert into two parts. In the first part, we compile CompCert C down to the CompCert Mach low-level language (which comes just before assembly generation) by adapting the proofs of existing passes to quantitative refinement. In the second part, we perform two passes to merge all stack frames together. The key point of our work is that this second part will require the Mach traces to not stack overflow, which justifies the use of quantitative refinement for the first part. Quantitative Refinement In the first part of the compiler, from CompCert C down to Mach, we add call and return events to the semantics of each language, at the level of each function call and return (as described in Section 3.1). This change is uniform in all languages between CompCert C to Mach: indeed, in each small-step operational semantics, the rules responsible for internal function call and return all have the same shape. Then, thanks to these changes, we support all of CompCert 1.13 passes except two optional optimizations (see Section 3.3), and, with no significant changes to the proofs, we prove that they exactly preserve traces with function call events. Generation of Target Cost Metric The semantics of CompCert C allocates a separate memory region for each addressable local variable. In Mach, all those variables as well as the spilling locations, the function arguments, and the return address are stored in a stack frame. Actually, the stack frame of a Mach function call is completely laid out, so that no additional memory is necessary when generating the CompCert x86 assembly code. This means that, at the level of Mach, we already know the stack size necessary for a function call (thanks to the fact that the original CompCert does not support some C features, see 3.3): for a given function, this size is constant and does not depend on the arguments nor the input. So, we can use the sizes of Mach stack frames as cost metric for functions to accurately estimate stack bounds at the source level. Generation of Assembly Code Recall that CompCert x86 assembly language is not realistic enough as it does not prevent a program from allocating an infinite number of stack frames. Our goal, as one of our main applications of our quantitative refinement, is to make the CompCert x86 assembly language more realistic by having it model a contiguous finite stack that is preallocated at the beginning of the program. The semantics of our new CompCert x86 assembly is parameterized by the size sz ` 4 3 of the whole stack (provided, in most cases, by the host operating system). We call this new x86 semantics ASM sz. We design it in such a way that an execution goes 3 sz is the stack size actually consumed by the program starting from main, but we have to account for the return address of the caller of main
6 wrong if the program tries to access more than sz bytes of stack. In other words, stack overflow becomes possible in ASM sz. Because the notion of function call is no longer relevant (there is no control stack ), we lose the ability to extend this semantics with call and return events. So, rather than quantitative refinement, we are actually interested in whether a CompCert C source program can run on ASM sz without going wrong because of stack overflow. The correctness of our Quantitative CompCert compiler is formalized by the following theorem. Theorem 1. Let sz ` 4 P 4, 2 32 be the size of the whole target stack. Consider a CompCert C source program S and assume the following: 1. S does not go wrong in the ordinary setting of unbounded stack space, that is, Et, failptq P S. 2. Quantitative CompCert produces a Mach intermediate target code I, with the sizes of stack frames 4 SF and the subsequent cost metric Mpfq SFpfq ` The stack bounds of S inferred at the source level are lower than sz under the Match cost metric P S, W M pbq ď sz. 4. From I, our compiler produces a target assembly code T. Then, when run in ASM sz, T refines S in the sense of 1 P T sz, DB P S, B 1 B. In particular, T cannot go wrong and thus does not stack overflow. It is important to first prove that S cannot go wrong in unbounded stack space. Indeed, the correctness of our assembly generation depends on the fact that the weights of Mach traces are lower than sz. If S were to have a wrong behavior failptq then I might actually have a behavior t B whose weight could well exceed sz even though W M pfailptqq does not. As each pass is proved independently of the others, it is not possible to track the behaviors of I that could potentially come from wrong behaviors of S. In the original CompCert x86 assembly language, the notion of stack frame is still kept, so that this language has two pseudoinstructions Pallocframe and Pfreeframe responsible of allocating and freeing the corresponding memory block, even though those pseudo-instructions are then turned into real x86 assembly instructions performing pointer arithmetics with the ESP stack pointer register. This latter transformation cannot be proved correct in CompCert because pointer arithmetics cannot cross block boundaries in the CompCert memory model. Therefore this transformation is done in an unverified pretty-printing stage, after CompCert has generated the x86 assembly code of the source program. Our new assembly semantics overcomes this limitation. Now, instead of allocating different memory blocks, we preallocate one single block of size sz ` 4 at the beginning of the program to hold the whole stack, and our assembly generation pass ensures that the value of ESP always points within this block. Therefore the pseudo-instructions are no longer necessary, and the pointer arithmetics needed at function entry and exit can be performed within our formalized ASM sz assembly language. As an interesting side effect, accessing function arguments is now simpler in our assembly language. Indeed, in the x86 calling convention, a function has to look for its arguments in the stack frame of its caller. To this purpose, the original CompCert keeps a back pointer to link each stack frame to its parent. Thanks to our changes, function arguments can now be provably accessed through pointer arithmetics with no indirection, so that this back link is no longer necessary. Because in the original CompCert, stack frames are independent memory blocks, it was necessary for the callee to have a pointer to the caller stack frame, called the back link, in its 4 In CompCert Mach, the syntax of a program p includes a finite map SF such that, for any function f defined in p, the operational semantics of Mach allocates a stack frame of SFpfq bytes whenever f is entered. own stack frame. The callee could then access its arguments by one indirection through this back link. In our new ASM sz assembly language, stack frames are no longer independent, so that the callee can access its arguments directly by pointer arithmetics within the whole stack block. 3.3 Limitations Neither the original CompCert nor Quantitative CompCert do support variable stack-frame size: C features such as variablelength arrays or dynamic stack allocation (alloca special library functions) are not supported. Thus, the size of the stack frame of a Mach function can be computed statically, and can be used to define the cost metric of the program. Moreover, the subsequently produced assembly code does not need to use push or pop, so any change to the stack pointer is done only through pointer arithmetics. Quantitative CompCert currently does not support the following optional two optimization passes (that are present in the original CompCert): tail-call recognition and function inlining. We describe how to deal with these two optimizations in the companion TR [9] and the implementation is underway. 4. Quantitative Hoare Logic for CompCert Clight In this section, we describe the novel quantitative program logic for CompCert Clight. The logic has been formalized and proved sound using Coq. At some points, we simplify the presented logic in comparison to the implemented version to discuss general ideas instead of technical details. Some particularities of the logic can be better understood with respect to Clight and the continuation-based small-step semantics for Clight programs that is used in CompCert. 4.1 CompCert Clight CompCert Clight is the most abstract intermediate language used by CompCert. Mainly, it is a subset of C in which loops can only be exited with a break statement and expressions are free of side effects. Using Clight instead of C simplifies the definition of our quantitative program logic and is also in line with the design of CompCert and the verification of CertiKOS. Syntax We use Clight expressions in the logic. Our statements syntax is a subset of Clight s to focus on the main ideas of our program logic. For simplicity, loops are infinite unless they are terminated using a break statement. We do not consider function pointers, goto statements, continue statements, and switch statements (see Section 4.4). S, S 1, S 2 :: skip x E x fpe q S 1; S 2 loop S if peq then S 1 else S 2 break return E Like in C, a program consists of a list of global variable declarations, a list of function declarations, and the identifier of the main statement, which is the entry point of the program. 4.2 Operational Semantics CompCert Clight s semantics is based on small-step transitions and continuations. Expressions which do not have side effects are evaluated in a big-step fashion. We use a simplified version of Clight s semantics that is sufficient for our subset. It is easy to relate evaluations in our simplified version to evaluations in the original semantics and we have implemented a verified compiler from our simple Clight to Clight with CompCert s original semantics. Values and Memory Model A value is either an integer n or a memory address l. Val :: int n adr l In the Coq development we use CompCert s memory model. However, the main ideas of the logic can be described with a simple
7 Γ $ tq s u skip tqu (Q:SKIP) Γ $ tq b u break tqu (Q:BREAK) Γ $ tλσ. Q r E σ qu return E tqu (Q:RETURN) Γ $ ti s u S tiu Γ $ ti s u loop S tpi b, K, I r qu (Q:LOOP) Γpfq pp f, Q f q P λpθ, H q.p f p E pθ,h q, H q Q λpθ, H q.q f p x pθ,h q, H q (Q:CALL) Γ $ tp ` Mpfqu x fpeq tpq ` Mpfq, K, Kqu Γ $ tp u S 1 tpr, Q b, Q r qu Γ $ tru S 2 tqu (Q:SEQ) Γ $ tp u S 1; S 2 tqu c ě 0 tp u S tqu tp ` cu S tq ` cu (Q:FRAME) P ě P 1 tp 1 u S tq 1 u Q 1 ě Q tp u S tqu (Q:CONSEQ) Figure 4. Selected rules of the quantitative program logic. memory model in which locations are mapped to values and labels. H : Mem Loc Ñ Val Y t u The label is used to indicate that a location has been freed and can no longer be used. Evaluating Expressions Expressions are evaluated with respect to a memory H : Mem and two environments θ : VID Ñ Val and : VID Ñ Loc. The local environment θ maps local variables to values and the global environment maps global variables to locations. We assume that always domp q X dompθq H. The semantics E pθ,h q v of an expression E under a global environment, a local environment θ, and a memory H is defined by induction on the structure of E. Continuations The small-step transition relation for statements is based on continuations. Continuations handle the local control flow within a function as well as the logical call stack. K :: Kstop Kseq S K Kloop S K Kcall x f θ K A continuation K is either the empty continuation Kstop, a sequence Kseq S K, a loop Kloop S K, or a stack frame Kcall x f θ K. Evaluating Statements Statements are evaluated under a program state pθ, H q P State pvid Ñ Valq ˆ Mem and a global environment pσ, q : FID Ñ prvids ˆ Sq ˆ pvid Ñ Locq that maps internal functions to their definitions a list of argument names and the function body and global variables to values. The small-step evaluation rules are given in the companion TR [9]. They define a transition pσ, q $ ps, K, σq Ñ tµ ν ɛu ps 1, K 1, σ 1 q where µ is a memory event, ν is an I/O event, ɛ denotes no event, S, S 1 are statements, K, K 1 are continuations, and σ, σ 1 P State. From the small-step transition relation we derive the following many-step relation in which t is a finite trace. We write pσ, q $ ps 1, K 1, σ 1q Ñ n t ps n`1, K n`1, σ n`1q if t a 1,..., a n and there exists ps i, K i, σ iq such that for all i pσ, q $ ps i, K i, σ iq Ñ ai ps i`1, K i`1, σ i`1q. For a statement S and a continuation K, we define the weight under the global environment pσ, q, the program state σ, and the metric M as pσ, q $ W pσ,mq ps, Kq suptv M ptq DS 1, K 1, σ 1, t, n. pσ, q $ ps, K, σq Ñ n t ps 1, K 1, σ 1 qu. 4.3 Quantitative Hoare Logic In the following we describe a simplified version of the quantitative Hoare logic that we use in Coq to prove bounds on the weights of the traces of Clight programs. For a given statement S and a continuation K, our goal is to derive a bound pσ, q $ P pσ, Mq P N such that pσ, q $ P pσ, Mq ě pσ, q $ W pσ,mq ps, Kq for all program states σ and resource metrics M. In the remainder of this section we assume a fixed global environment pσ, q. We generalize classic Hoare logic to express not only classical boolean-valued assertions but also assertions that talk about the future stack-space usage. Instead of the usual assertions P : State Ñ bool of Hoare logic we use assertions P : State Ñ N Y t8u. This can be understood as a refinement of boolean assertions where false is interpreted by 8 and true is refined by N. We write Assn for State Ñ N Y t8u, and K p ÞÑ 8q. In the actual implementation, assertions have the type State Ñ N Ñ Prop. For a given σ P State, such an assertion can be seen as a set B Ď N of valid bounds. We do this only to use Coq s support for propositional reasoning. The presentation here is easier to read. The continuation-based semantics of Clight requires that we distinguish pre- and postconditions in the logic to account for different possible ways to exit a block of code. This approach is standard in Hoare logics and followed for instance in Appel s separation logic for Clight [3]. Our postconditions Q pq s, Q b, Q r q : Assn ˆ Assn ˆ pval Ñ Assnq provide one assertion Q s for the case in which the block is exited by fall through, one assertion Q b if the block is exited by a break, and a function Q r from values to assertions in case the block is exited by a return. The function Q r takes the return value as argument. Since we have to deal with (possibly recursive) functions, we also need a function context Γ:FIDÑppVal ˆ MemqÑN Y t8uqˆppval ˆ MemqÑNYt8uq that maps function names to their specifications, that is, pre- and postconditions. The precondition depends on the value that is passed to the function by the caller and the memory. The postcondition depends on the return value and the memory. We assume that a function has only one argument in this article. In the Coq implementation, an arbitrary number of function arguments is allowed. In summary, a quantitative Hoare triple has the form Γ $ tp u S tqu where Γ is a function context, P : Assn is a precondition, Q : Assn ˆ Assn ˆ pval Ñ Assnq is a postcondition, and S is a statement. Intuitively, an assertion can be seen as a potential function that maps a program state to a non-negative potential. The potential of the precondition P must be sufficient to cover the cost of the execution of the statement S and the potential Q after the execution of S (as in amortized resource analysis [19]). Rules Figure 4 shows selected rules of the quantitative logic. We lift the operations ` and ě pointwise to assertions P, Q : Assn. A constant c P N Y t8u is sometimes used as the constant assertion ÞÑ c. We fix an event metric M and a global environment pσ, q. In the Q:SKIP rule, we do not have to account for any stack consumption. As a result, the precondition can be any (potential)
8 Γpfq pλpv, H q. 0, λpv, H q. 0q Γpgq pλpv, H q. 0, λpv, H q. 0q (Q:CALL) (Q:CALL) Γ $ tpm f u fpq tpm f, K, Kqu Γ $ tpm f ` X f u fpq tpm f ` X f, K, Kqu (Q:FRAME) Γ $ tpm gu gpq tpm g, K, Kqu Γ $ tpm g ` X gu gpq tpm g ` X g, K, Kqu (Q:FRAME) (EQ) (EQ) Γ $ tmaxpm f, m gqu fpq tqu Γ $ tmaxpm f, m gqu gpq tqu (Q:SEQ) Γ $ tmaxpm f, m gqu fpq; gpq tqu where Mpcallpfqq m f Mpcallpgqq m g Q pmaxpm f, m gq, K, Kq X θ maxpm f, m gq m θ for θ P tf, gu Figure 5. An example derivation of a stack-space bound in the quantitative logic. function. After the execution, the skip part of the postcondition must be valid on the same (unchanged) program state. So we have to make sure that we do not end up with more potential and simply use the precondition as the skip part of the postcondition. The break and return parts of the postcondition are not reachable and can therefore be arbitrary. The rules Q:BREAK and Q:RETURN are similar. In the Q:SEQ rule we have to account for early exits in statements. For instance, if S 1 contains a break statement then S 2 will never be executed so we must ensure in the break part of S 1 s postcondition that the break part of S 1; S 2 holds. For the same reason, the return part of S 1 s postcondition is special. The Q:LOOP rule uses the same principles as the Q:SEQ rule to tweak the final postcondition. In the case of Q:LOOP, we simply ensure that the break part of the inner statement becomes the skip part of the overall statement. We use K as the break part of the loop S statement since its operational semantics prevent it from terminating differently than with a skip or a return. The Q:CALL rule accounts for the actual stack-space usage of programs. It enforces that enough stack space is available to call the function f by adding Mpfq to the pre- and postcondition. The preand postconditions are taken from the function context Γ. There are two weakening rules in the quantitative Hoare logic. The framing rule Q:FRAME weakens a statement by stating that if S needs P bytes to run and leaves Q bytes free at its end, then it can very well run with P ` c bytes and return Q ` c bytes. It is very handy to prove tight bounds using the max function as demonstrated in Figure 5. The consequence Q:CONSEQ rule is directly imported from classical Hoare logics except that instead of using the logical implication ñ we use the quantitative ě. Auxiliary State The main difference between the implemented logic and the logic described here is that the latter does not have an auxiliary state. Auxiliary state is a classic extension of Hoare logic (see for example [30]). The auxiliary state is used to share information between the pre- and postcondition of a triple. In a logic without auxiliary state (or similar techniques) it is not possible to relate program states before and after a statement. For example, you cannot specify that the function int twice () t i = i+i;u doubles the value of the variable i. With an auxiliary variable Z it is possible specify this fact in Hoare logic using the triple ti Zu twicepq ti 2 Zu. One technical challenge with this auxiliary state is that some triples, for example ti Zu c ti Zu and ti Z 1u c ti Z 1u need to be proved equivalent by the logic to handle recursive calls. This problem is usually solved by introducing a more complex consequence rule, which our implemented system features. The typical use case is when we apply the rule Q:CALL to a recursive call. In this case, the Hoare triple for the function call is proved by an assumption from the derivation context with a slightly different auxiliary state. In the example derivation in Figure 6 this different state is Z 1. Adapting the derivation hypothesis to prove the recursive call is enabled in our logic by an extended consequence rule that we proved sound in the quantitative setting. Stack Framing Another minor difference is in the function application rule where we only present the rule for function calls with a tz log 2 ph σ l σq ñ M b Zu bsearch(x,l,h) { if (h-l <= 1) return l; tpzą0 ^ Z log 2 ph σ l σqq ñ M b Zu m = (h+l)/2; tpzą0 ^ Z log 2 ph σ l σq ^ m σ hσ`lσ q ñ M 2 b Zu if (a[m]>x) h=m else l=m; trz 1 log 2 ph σ l σq ñ M b pz 1qs ` M b u return bsearch(x,l,h); trm b pz 1qs ` M b u } tm b Zu Figure 6. Derivation with auxiliary state for the bsearch function. single argument and without framing of stack assertions. The latter is necessary to carry over information on the local environment from the precondition to the postcondition of the function call. Soundness The soundness of our quantitative logic can be simply expressed by the following theorem. Theorem 2. For a fixed global environment pσ, q, a derivation in our quantitative logic for a statement S implies a bound on the weight of S, that is, $ tp u S tqu M. P pσ, Mq ě W pσ,mq ps, Kstopq. Naturally, we have to prove a stronger statement that takes postconditions and continuations into account to justify the soundness of the rules of the logic. This is not unlike as in program logics for low-level code [22] and Hoare-style logics for CompCert Clight [3]. Furthermore, we have to assume that we have a non-empty function context Γ; and finally, we have to step-index the correctness statement in order to prove its soundness by induction. The details can be found in the TR [9] and in the Coq development. Of course we prove in Coq that the intuitive validity, as formulated in Theorem 2 is a consequence of our stronger formulation of validity. Example Figure 5 contains an example derivation for the statement fpq; gpq in our logic. We assume that we have already verified that the function bodies of f and g do not allocate stack space, that is, Γpgq Γpfq pλpv, H q. 0, λpv, H q. 0q. Our goal is to derive a quantitative Hoare triple that expresses that maxpm f, m gq, the maximum of the stack frame sizes of f and g, is a bound on the stack usage; and that after the execution maxpm f, m gq stack space is available. Since the effect of break and return statements cannot leak outside of a function body, the corresponding postconditions can be arbitrary and we simply use K. To derive our goal, we first have to apply the rule Q:SEQ for sequential composition. In the derivation of the function call fpq, we first reorder the precondition to get it in a form in which we can apply the rule Q:FRAME to eliminate the max operator. We then have a triple that is amenable to an application of the rule Q:CALL that uses the specification of the body of f in Γ.
9 4.4 Limitations In our program logic described in this section, we do not consider function pointers, goto statements, continue statements, and switch statements, even though our Quantitative CompCert compiler still supports all of these. It would be possible to add these features to our logic by building on the ideas of advanced program logics like XCAP [29]. 5. Automatic Stack Analyzer In larger C programs a manual, interactive verification with a program logic is too tedious and time-consuming to be practical. Therefore we have developed an automatic stack analysis tool that operates at the Clight level to enable the analysis of real system code. We view this automatic tool mainly as a proof of concept that demonstrates the value of the logic for formal verification of static analysis tools. In the future, we will extend our automatic analyzer with advanced techniques like amortized resource analysis [5, 21]. This is however beyond the scope of this article. The basic idea of our automatic stack analyzer is to compute a call graph from the Clight code and to derive a stack bound for each function in topological order. In Coq, the derivation of a function bound is implemented by a recursive function auto bound on the abstract syntax tree (AST) of a Clight program. The function auto bound does not only compute a stack bound but also a derivation in our quantitative program logic. This verifies the correctness of the generated bound and enables the composition of stack bounds that have been derived interactively or with other static analysis tools. In addition to the AST, auto bound takes a context of known function bounds together with their derivations in the logic as an argument. Given our verified quantitative logic, the implementation of auto bound is straightforward. For trivial commands like assignments or skip, auto bound simply generates the bound 0 and a derivation like t0u skip tp0, 0, 0qu. For a sequential composition S 1; S 2 we inductively apply auto bound to S 1 and S 2, and derive the bounds tb iu S i tpb s i, B b i, B r i qu for i 1, 2. We then return the precondition maxtb 1, B 2u and the postcondition pmaxtb s 1, B s 2u, maxtb b 1, B b 2u, maxtb s 1, B s 2uq for the command S 1; S 2. The derivation of this bound is similar to the example derivation that is sketched in Figure 5. The computation of the bound for the conditional works similar. For loops we can use the bound derived for the loop body to obtain a bound for the loop. In the derivation we just apply the rule Q:LOOP. Function calls are handled with the context of known function bounds (recursion is not allowed here) and the rule Q:CALL. We envision, that the quantitative logic can be a useful backend to verify more sophisticated static analyses. For our simple, automatic stack analyzer the logic was already very convenient and enabled us to verify the analyzer almost without additional effort. We have combined our automatic stack analyzer with our Quantitative CompCert compiler. The result is a verified C compiler that translates a program without function pointers and recursive calls to x86 assembly and automatically derives a stack bound for each function in the program including mainpq. The soundness theorem we have proved states the following. If a given program is memorysafe and the verified compiler successfully produces an assembly program A then A refines the source program and runs safely on an x86 machine with the stack size that has been computed by the automatic stack analysis for mainpq (see Point 3 of Theorem 1). 6. Experimental Evaluation To validate the practicality of our framework for stack-bound verification, we have performed an experimental evaluation with more than 3000 lines of C code from different sources including File Name / Function Name Verified Line Count Stack Bound mibench{net{dijkstra.c enqueue 40 bytes (174 LOC) dequeue 40 bytes dijkstra 88 bytes mibench{auto{bitcount.c bitcount 16 bytes (110 LOC) bitstring 32 bytes mibench{sec{blowfish.c BF encrypt 40 bytes (233 LOC) BF options 8 bytes BF ecb encrypt 80 bytes mibench{sec{pgp{md5.c MD5Init 16 bytes (335 LOC) MD5Update 168 bytes MD5Final 168 bytes MD5Transform 128 bytes mibench{tele{fft.c IsPowerOfTwo 16 bytes (195 LOC) NumberOfBitsNeeded 24 bytes ReverseBits 24 bytes fft float 160 bytes certikos{vmm.c palloc 48 bytes (608 LOC) pfree 40 bytes mem init 72 bytes pmap init 176 bytes pt free 80 bytes pt init 152 bytes pt init kern 136 bytes pt insert 80 bytes pt read 56 bytes pt resv 120 bytes certikos{proc.c enqueue 48 bytes (819 LOC) dequeue 48 bytes kctxt new 72 bytes sched init 232 bytes tdqueue init 208 bytes thread init 192 bytes thread spawn 96 bytes compcert{mandelbrot.c main 56 bytes (92 LOC) compcert{nbody.c advance 80 bytes (174 LOC) energy 56 bytes offset momentum 24 bytes setup bodies 16 bytes main 112 bytes Table 1. Automatically verified stack bounds for C functions. hand written code, programs from the CompCert test suite, programs from the MiBench [17] embedded software benchmark suite, and modules from the simplified development version of the CertiKOS operating system kernel which is currently being verified. Tables 1 and 2 show a representative compilation of the experiments. Table 1 contains bounds that were automatically derived with the stack analyzer. Table 2 contains 8 bounds that were interactively derived using the quantitative logic with occasional support of the automation. The size of the analyzed example files varies from 8 lines of code (fib.c) to 819 lines of code (proc.c). In general, the automatic stack-bound analysis runs very efficiently and needs less than a second for every example file on (one core of) a Linux workstation with 32G of RAM and a x86 processor with 16 cores at 3.10Ghz. In Table 1, the first column shows the file name of the examples together with the number of lines, the second column contains the name of selected functions from that file, and the third column contains the verified bound. The interactively-derived bounds in Table 2 are presented as symbolic expressions parametric in the functions
10 Function Name recidpq bsearchpx, lo, hiq fibpnq qsortpa, lo, hiq filter pospa, sz, lo, hiq sumpa, lo, hiq fact sqpnq filter findpa, sz, lo, hiq Verified Stack Bound 8a bytes 40p1 ` log 2 phi loqq bytes 24n bytes 48phi loq bytes 48phi loq bytes 32phi loq bytes 40 ` 24n 2 bytes 128 ` 48phi loq ` 40 log 2 pblq bytes Table 2. Manually verified stack bounds for C functions. arguments. These symbolic expressions are slight simplifications of the real pre- and postconditions of the functions that we proved in Coq. The actual Hoare triples proved in Coq carry a logical meaning which does, for instance, require that the qsort function be called on a valid sub array. The file sizes of the manual verified examples range from 8 to 52 lines of code. Our main application of the automatic stack-analyzer is the CertiKOS operating system kernel [15]. Currently, the stack in CertiKOS is preallocated and proving the absence of stack-overflow is essential in the verification of the reliability of the system. Since CertiKOS does not use recursion, we can use the automatic analysis to derive precise stack bounds. Using our Quantitative CompCert compiler, we were, for instance, able to compile and compute bounds for the virtual memory management module (certikos{vmm.c) and the process management module (certikos{proc.c). Because of the large number of functions in CertiKOS, only a sample of the analyzed functions is displayed in Table 1. Testing the quantitative Hoare logic and the compiler on CompCert test suite was a natural choice since our compiler builds on CompCert s architecture. This also allowed us to make sure that we did not introduce any regression with respect to the original CompCert compiler. To stress the expressivity of the logic we focused on test programs with recursive functions. The functions fib and qsort in Table 2 are for instance from the CompCert test suite. Files with automatically derived bounds for non-recursive functions from the CompCert test suite include mandelbrot.c which computes an approximation of the Mandelbrot set and nbody.c which computes an n-body simulation of a part of our solar system. We also made sure that our technique can handle safety critical embedded software. The MiBench [17] benchmark that we used for this purpose is free, publicly available, and representative for embedded software. The use of recursion in MiBench programs is relatively rare, which makes them a great target for our automatic stack analyzer. The analyzed examples we present in Table 1 include for instance Dijkstra s single-source shortest-path algorithm (dijkstra.c), and the cryptographic algorithms Blowfish (blowfish.c) and MD5 (md5.c). Finally, Table 2 contains some recursive functions that demonstrate the expressivity of our quantitative logic. The function bsearch is, for example, a recursive binary search with logarithmic recursion depth. The function fib computes the Fibonacci sequence using an exponential algorithm and the function qsort implements a recursive version of the quicksort algorithm. The verification of the function fact sq shows the modularity of the logic: We first verify a linear bound for the factorial function and then use this bound to verify fact sqpnq, which contains the call factpn 2 q. The function filter pos takes an array and computes a new array that contains all positive elements of the input array. Similarly, filter find uses the binary search bsearch to filter out all elements of an input array that are contained in another array of size BL. The modularity of the logic enables us to reuse the logarithmic bound that we already derived for bsearch in the proof. The verification of some functions is still measured stack consumption 40(1 + log_2(x)) measured stack consumption x^ Figure 7. Experimental evaluation of the accuracy of hand-derived stack bounds. The plots compare the derived bounds (blue lines) for the functions bsearch (at the top) and fact sq (at the bottom) with the measured stack usage of the execution of the respective function for different inputs (red crosses). The x-axis shows either the value of an integer argument (fact sq) or the length of an input array (bsearch). The y-axis shows the stack usage in bytes. underway. The bounds for the functions recid, bsearch, fib, and qsort are already completely verified. Our experiments show that the automatic stack analyzer works effectively for our main application, the CertiKOS OS kernel. The reason is that we designed the quantitative logic to include exactly the subset of Clight that is needed for CertiKOS. It turned out that this subset is also sufficient for many examples in the CompCert test suite and the MiBench embedded software benchmarks. If a program is not interactively analyzable in our logic then this is due to unsupported language constructs such as switch statements and function pointers. Many of these language features could easily be supported by relatively small additions to the logic. An exception to this are function pointers which would require more work, following for example XCAP [29]. We have evaluated the accuracy of the verified bounds by comparing them with the actual stack-space consumption of the compiled C programs. Our experiments show that our framework is expressive enough to derive very tight bounds for recursive and non-recursive programs. All manually and automatically derived bounds over-approximate the actual stack-space consumption by exactly 4 bytes. Figure 7 shows the results of two representative experiments with hand-derived bounds for recursive programs. The bound derived in the logic is plotted together with the actual stack consumption of C programs measured on different inputs. Measuring the stack consumption of C programs on modern computers is not as trivial as we originally thought. The measurement is complicated by some security mechanisms and unrestricted manipulation of the stack pointer by the compiler. To this end, we
11 designed a small C program that uses the linux system call ptrace. Using the this system call our tool forks the monitored process as a child then executes it step by step while keeping track of its stack consumption. The reason for the 4-byte looseness of the bounds is that stack frames always reserve four bytes for a potential function call: The return address needs to be pushed by a call instruction in the callee. Obviously, the last function in the function call chain does not call any other function. So these four bytes remain unused. 7. Related Work We now discuss research that is related to our contributions in verified compilation, program logics, and automatic resource analysis. Verified Compilation Soundness proofs of compilers have been extensively studied and we focus on formally verified proofs here. Klein and Nipkow [24] developed a verified compiler from an object-oriented, Java-like language to JVM byte code. Chlipala [11] describes a verified compiler from the simply-typed lambda calculus to an idealized assembly language. In contrast to our work, the aforementioned works do not model nor preserve quantitative properties such as stack usage. Our verified Quantitative CompCert compiler is an extension of the CompCert C Compiler [26, 27]. Despite being formally verified, important quantitative properties such as memory and time usage of programs compiled with CompCert have still to be verified at the assembly level [6]. Admittedly, there exists a clever annotation mechanism [6] in CompCert that allows to transport assertions on program states from the source level to the target machine code. However, these assertions can only contain statements about memory states but not bounds on the number of loop iterations and or recursion depth of functions. The novelty of our Quantitative CompCert extension to CompCert is that it enables us to reason about quantitative properties of event traces during compilation. Another novelty is that we model the assembly level semantics more realistically by using a finite stack. In particular, we do not have to use pseudo instructions anymore. This is similar to CompCertTSO [32]. However, we use event traces to get guarantees on the size of the stack that is needed to ensure refinement. On the other hand, it is always possible that the compiled code runs out of stack space in CompCertTSO. In the context of the Hume language [18], Jost et al. [23] developed a quantitative semantics for a functional language and related it to memory and time consumption of the compiled code for the Renesas M32C/85U embedded micro-controller architecture. In contrast to our work, the relation of the compiled code with functional code is not formally proved. Program Logics In the development of our quantitative Hoare logic we have drawn inspiration from mechanically verified Hoare logics. Nipkow s [30] description of his implementations of Hoare logics in Isabelle/HOL has been helpful to understand the interaction of auxiliary variables with the consequence rule. The consequence rule we use in our Coq implementation is a quantitative version of a consequence rule that has been attributed to Martin Hofmann by Nipkow [30]. Appel s separation logic for CompCert Clight [3] has been a blueprint for the structure of the quantitative logic. Since we do not deal with memory safety, our logic is much simpler and it would be possible to integrate it with Appel s logic. The continuation passing style that we use in the quantitative logic is not only used by Appel [3] but also in Hoare logics for low-level code [22, 29]. There exist quantitative logics that are integrated into separation logic [5, 20] and they are closely related to our quantitative logic. However, the purpose of these logics is slightly different since they focus on the verification of bounds that depend on the shape of heap data structures. Moreover, they are only defined for idealized languages and do not provide any guarantees for compiled code. Also closely related to our logic is a VDM-style logic for reasoning about resource usage of JVM byte code by Aspinall et al. [4]. Their logic is more general and applies to different quantitative resources while we focus on stack usage. However, it is unclear how realistic the presented resource metrics are. On the other hand, our logic applies to system code written in C, is verified with respect to CompCert Clight, and derives bounds for x86 assembly. Resource Analysis There exists a large body of research on statically deriving stack bounds on low-level code [8, 10, 31] as well as commercial tools such as the Bound-T Time and Stack Analyser and Absint s StackAnalyzer [14]. We are however not aware of any formally verified techniques. For high-level languages there exists a large number of systems for statically inferring or checking quantitative requirements such as stack usage [1, 12, 19, 23]. However, they are not formally verified and do not apply to system code that is written in C. For C programs, there exist methods to automatically derive loop bounds [16, 36] but the proposed methods are not verified and it is unclear if they can be used for computing stack bounds. We are only aware of two verified quantitative analysis systems. Albert et al. [2] rely on the KeY tool to automatically verify previously inferred loop invariants, size relations, and ranking functions for Java Card programs. However, they do not have a formal cost semantics and do not verify actual stack bounds. Blazy et al. [7] have verified a loop bound analysis for CompCert s RTL intermediate language. It is however unclear how the presented technique can be used to verify stack bounds or to formally translate bounds to a lower-level during compilation. 8. Conclusion Embedded software has always been a target of verified compilers. As a result, aiding verification of quantitative properties remains a major goal for verified compilation. In one of the earliest articles [26] on CompCert, Leroy stated: [...] it is hopeless to prove a stack memory bound on the source program and expect this resource certification to carry out to compiled code: stack consumption, like execution time, is a program property that is not preserved by compilation. Ironically, Leroy s groundbreaking work on CompCert has been the main inspiration in our development of a framework that enables exactly such a resource certification of stack-consumption bounds for compiled x86 assembly code at the C level. We have developed Quantitative CompCert, a realistic, verified C compiler which shows how verified compilation enables the verification of quantitative properties of compiled programs at the source level. We have implemented and formally verified a novel quantitative Hoare logic for CompCert Clight which is an ideal backend for static analysis tools. This is demonstrated through the implementation of a verified, automatic stack-analysis tool that computes derivations in the quantitative logic. Finally, we have shown through experiments that our framework can be applied to derive precise stack bounds for typical system code. Our work opens the door for the verification of powerful static analysis tools for quantitative properties that operate on the C level rather than on the machine code. There are multiple future research directions that we plan to explore on the basis of the present development. For one thing, we want to use our quantitative Hoare logic to verify more powerful analysis tools that can automatically derive stack-space bounds for recursive functions. For another thing, we plan to generalize the developed concepts to apply our technique to other resources such as heap-memory and clock-cycle consumption.
12 Acknowledgments We thank Lennart Beringer, Francesco Logozzo, the anonymous reviewers of PLDI 14, and the PLDI 14 Artifact Evaluation Committee for helpful comments and suggestions that improved this article and the implemented tools. This research is based on work supported in part by NSF grants and , and DARPA grants FA and FA Any opinions, findings, and conclusions contained in this document are those of the authors and do not reflect the views of these agencies. References [1] E. Albert, P. Arenas, S. Genaim, M. Gómez-Zamalloa, and G. Puebla. Cost Analysis of Concurrent OO Programs. In Prog. Langs. and Systems - 9th Asian Symposium (APLAS 11), pages , [2] E. Albert, R. Bubel, S. Genaim, R. Hähnle, and G. Román-Díez. Verified Resource Guarantees for Heap Manipulating Programs. In Fundamental Approaches to Soft. Eng. - 15th Int. Conf. (FASE 12), pages , [3] A. W. Appel et al. Program Logics for Certified Compilers. Cambridge University Press, [4] D. Aspinall, L. Beringer, M. Hofmann, H.-W. Loidl, and A. Momigliano. A Program Logic for Resources. Theor. Comput. Sci., 389(3): , [5] R. Atkey. Amortised Resource Analysis with Separation Logic. In 19th Euro. Symp. on Prog. (ESOP 10), pages , [6] R. Bedin França, S. Blazy, D. Favre-Felix, X. Leroy, M. Pantel, and J. Souyris. Formally Verified Optimizing Compilation in ACG-based Flight Control Software. In Embedded Real Time Software and Systems (ERTS 2012), [7] S. Blazy, A. Maroneze, and D. Pichardie. Formal Verification of Loop Bound Estimation for WCET Analysis. In Verified Software: Theories, Tools, Experiments - 5th Int. Conf. (VSTTE 13), To appear. [8] D. Brylow, N. Damgaard, and J. Palsberg. Static Checking of Interrupt- Driven Software. In 23rd Int. Conf. on Soft. Engineering (ICSE 01), pages 47 56, [9] Q. Carbonneaux, J. Hoffmann, T. Ramananandro, and Z. Shao. Endto-End Verification of Stack-Space Bounds for C Programs. Technical Report YALEU/DCS/TR-1487, Yale University, March [10] W.-N. Chin, H. H. Nguyen, C. Popeea, and S. Qin. Analysing Memory Resource Bounds for Low-Level Programs. In 7th Int Symp. on Memory Management (ISMM 08), pages , [11] A. Chlipala. A Certified Type-Preserving Compiler from Lambda Calculus to Assembly Language. In 28th Conf. on Prog. Lang. Design and Impl. (PLDI 07), pages 54 65, [12] K. Crary and S. Weirich. Resource Bound Certification. In 27th ACM Symp. on Principles of Prog. Langs. (POPL 00), pages , [13] Express Logic, Inc. Helping you avoid stack overflow crashes! White Paper, URL Analysis_White_paper.1_.pdf. [14] C. Ferdinand, R. Heckmann, and B. Franzen. Static Memory and Timing Analysis of Embedded Systems Code. In 3rd Europ. Symp. on Verification and Validation of Software Systems (VVSS 07), [15] L. Gu, A. Vaynberg, B. Ford, Z. Shao, and D. Costanzo. CertiKOS: A Certified Kernel for Secure Cloud Computing. In Asia Pacific Workshop on Systems (APSys 11), [16] S. Gulwani, K. K. Mehra, and T. M. Chilimbi. SPEED: Precise and Efficient Static Estimation of Program Computational Complexity. In 36th ACM Symp. on Principles of Prog. Langs. (POPL 09), pages , [17] M. R. Guthaus, J. S. Ringenberg, D. Ernst, T. M. Austin, T. Mudge, and R. B. Brown. MiBench: A Free, Commercially Representative Embedded Benchmark Suite. In IEEE International Workshop on Workload Characterization (WWC 01), pages 3 14, [18] K. Hammond and G. Michaelson. Hume: A Domain-Specific Language for Real-Time Embedded Systems. In Generative Progr. and Component Eng., 2nd Int. Conf. (GPCE 03), pages 37 56, [19] J. Hoffmann, K. Aehlig, and M. Hofmann. Multivariate Amortized Resource Analysis. ACM Trans. Program. Lang. Syst., [20] J. Hoffmann, M. Marmar, and Z. Shao. Quantitative Reasoning for Proving Lock-Freedom. In 28th ACM/IEEE Symposium on Logic in Computer Science (LICS 13), [21] M. Hofmann and S. Jost. Static Prediction of Heap Space Usage for First-Order Functional Programs. In 30th ACM Symp. on Principles of Prog. Langs. (POPL 03), pages , [22] J. B. Jensen, N. Benton, and A. Kennedy. High-Level Separation Logic for Low-Level Code. In 40th ACM Symp. on Principles of Prog. Langs. (POPL 13), pages , [23] S. Jost, H.-W. Loidl, K. Hammond, N. Scaife, and M. Hofmann. Carbon Credits for Resource-Bounded Computations using Amortised Analysis. In 16th Symp. on Form. Meth. (FM 09), pages , [24] G. Klein and T. Nipkow. A Machine-Checked Model for a Java-Like Language, Virtual Machine, and Compiler. ACM Trans. Program. Lang. Syst., 28(4): , [25] G. Klein, J. Andronick, K. Elphinstone, G. Heiser, D. Cock, P. Derrin, D. Elkaduwe, K. Engelhardt, R. Kolanski, M. Norrish, T. Sewell, H. Tuch, and S. Winwood. sel4: Formal Verification of an Operating- System Kernel. Commun. ACM, 53(6): , [26] X. Leroy. Formal Certification of a Compiler Back-End, or: Programming a Compiler with a Proof Assistant. In 33rd Symposium on Principles of Prog. Langs. (POPL 06), pages 42 54, [27] X. Leroy. Formal Verification of a Realistic Compiler. Communications of the ACM, 52(7): , [28] Y. Moy, E. Ledinot, H. Delseny, V. Wiels, and B. Monate. Testing or Formal Verification: DO-178C Alternatives and Industrial Experience. IEEE Software, 30(3):50 57, ISSN [29] Z. Ni and Z. Shao. Certified Assembly Programming with Embedded Code Pointers. In 33th ACM Symp. on Principles of Prog. Langs. (POPL 06), pages , [30] T. Nipkow. Hoare Logics in Isabelle/HOL. In Proof and System- Reliability, volume 62 of NATO Science Series, pages Springer, [31] J. Regehr, A. Reid, and K. Webb. Eliminating Stack Overflow by Abstract Interpretation. ACM Trans. Embed. Comput. Syst., 4(4): , [32] J. Sevcík, V. Vafeiadis, F. Z. Nardelli, S. Jagannathan, and P. Sewell. CompCertTSO: A Verified Compiler for Relaxed-Memory Concurrency. J. ACM, 60(3), [33] Z. Shao. Certified software. Commun. ACM, 53(12):56 66, [34] R. Wilhelm et al. The Worst-Case Execution-Time Problem Overview of Methods and Survey of Tools. ACM Trans. Embedded Comput. Syst., 7(3), [35] X. Yang, Y. Chen, E. Eide, and J. Regehr. Finding and Understanding Bugs in C Compilers. In 32nd Conf. on Prog. Lang. Design and Impl. (PLDI 11), pages , [36] F. Zuleger, M. Sinn, S. Gulwani, and H. Veith. Bound Analysis of Imperative Programs with the Size-change Abstraction. In 18th Int. Static Analysis Symposium (SAS 11), 2011.
An Overview of a Compiler
An Overview of a Compiler Department of Computer Science and Automation Indian Institute of Science Bangalore 560 012 NPTEL Course on Principles of Compiler Design Outline of the Lecture About the course
Computing basics. Ruurd Kuiper
Computing basics Ruurd Kuiper October 29, 2009 Overview (cf Schaum Chapter 1) Basic computing science is about using computers to do things for us. These things amount to processing data. The way a computer
Regression Verification: Status Report
Regression Verification: Status Report Presentation by Dennis Felsing within the Projektgruppe Formale Methoden der Softwareentwicklung 2013-12-11 1/22 Introduction How to prevent regressions in software
Optimization Techniques in C. Team Emertxe
Optimization Techniques in C Team Emertxe Optimization Techniques Basic Concepts Programming Algorithm and Techniques Optimization Techniques Basic Concepts What is Optimization Methods Space and Time Analysis (POPA Sec , )
Software Analysis (POPA Sec. 2.2.2, 2.5-2.5.2) Klaus Ostermann Based on slides by Jurriaan Hage Overview Correctness proof Live Variables Analysis The While language with procedures Live Variables
Rigorous Software Development CSCI-GA 3033-009
Rigorous Software Development CSCI-GA 3033-009 Instructor: Thomas Wies Spring 2013 Lecture 11 Semantics of Programming Languages Denotational Semantics Meaning of a program is defined as the mathematical
Eastern Washington University Department of Computer Science. Questionnaire for Prospective Masters in Computer Science Students
Eastern Washington University Department of Computer Science Questionnaire for Prospective Masters in Computer Science Students I. Personal Information Name: Last First M.I. Mailing Address: Permanent
Factoring & Primality
Factoring & Primality Lecturer: Dimitris Papadopoulos In this lecture we will discuss the problem of integer factorization and primality testing, two problems that have been the focus of a great amount
Module 2 Stacks and Queues: Abstract Data Types
Module 2 Stacks and Queues: Abstract Data Types A stack is one of the most important and useful non-primitive linear data structure in computer science. It is an ordered collection of items into which
Discrete Mathematics, Chapter 5: Induction and Recursion
Discrete Mathematics, Chapter 5: Induction and Recursion Richard Mayr University of Edinburgh, UK Richard Mayr (University of Edinburgh, UK) Discrete Mathematics. Chapter 5 1 / 20 Outline 1 Well-founded and Recursion
Induction and Recursion Winter 2010 Mathematical Induction Mathematical Induction Principle (of Mathematical Induction) Suppose you want to prove that a statement about an integer n is true for every
05 Case Study: C Programming Language
CS 2SC3 and SE 2S03 Fall 2009 05 Case Study: C Programming Language William M. Farmer Department of Computing and Software McMaster University 18 November 2009 The C Programming Language Developed by Dennis
Glossary of Object Oriented Terms
Appendix E Glossary of Object Oriented Terms abstract class: A class primarily intended to define an instance, but can not be instantiated without additional methods. abstract data type: An abstraction
The Classes P and NP
The Classes P and NP We now shift gears slightly and restrict our attention to the examination of two families of problems which are very important to computer scientists. These families constitute the Class: OS and Computer Architecture. Last Class: OS and Computer Architecture
Last Class: OS and Computer Architecture System bus Network card CPU, memory, I/O devices, network card, system bus Lecture 3, page 1 Last Class: OS and Computer Architecture OS Service Protection Interrupts
Formal verification of a C-like memory model and its uses for verifying program transformations
Journal of Automated Reasoning manuscript No. (will be inserted by the editor) Formal verification of a C-like memory model and its uses for verifying program transformations Xavier Leroy Sandrine Blazy
Virtual Time and Timeout in Client-Server Networks
Virtual Time and Timeout in Client-Server Networks Jayadev Misra July 13, 2011 Contents 1 Introduction 2 1.1 Background.............................. 2 1.1.1 Causal Model of Virtual Time...............
A Formally-Verified C Static Analyzer
A Formally-Verified C Static Analyzer Jacques-Henri Jourdan Inria Paris-Rocquencourt jacques-henri.jourdan@inria.fr Vincent Laporte IRISA and U. Rennes 1 vincent.laporte@irisa.fr Sandrine Blazy IRISA and
Data Structure. Lecture 3
Data Structure Lecture 3 Data Structure Formally define Data structure as: DS describes not only set of objects but the ways they are related, the set of operations which may be applied to the elements
Moving from CS 61A Scheme to CS 61B Java
Moving from CS 61A Scheme to CS 61B Java Introduction Java is an object-oriented language. This document describes some of the differences between object-oriented programming in Scheme (which we hope you
Mathematical Induction
Mathematical Induction Victor Adamchik Fall of 2005 Lecture 1 (out of three) Plan 1. The Principle of Mathematical Induction 2. Induction Examples The Principle of Mathematical Induction Suppose we have
X86-64 Architecture Guide
X86-64 Architecture Guide For the code-generation project, we shall expose you to a simplified version of the x86-64 platform. Example Consider the following Decaf program: class Program { int foo
3 Extending the Refinement Calculus
Building BSP Programs Using the Refinement Calculus D.B. Skillicorn? Department of Computing and Information Science Queen s University, Kingston, Canada skill@qucis.queensu.ca Abstract. We extend the
Abstract Interpretation-based Static Analysis Tools:
Abstract Interpretation-based Static Analysis Tools: Proving the Absence of Runtime Errors and Safe Upper Bounds on the Worst-Case Execution Time and Safe Upper Bounds on the Stack Usage Christian Ferdinand
Chapter 13. Disk Storage, Basic File Structures, and Hashing
Chapter 13 Disk Storage, Basic File Structures, and Hashing Chapter Outline Disk Storage Devices Files of Records Operations on Files Unordered Files Ordered Files Hashed Files Dynamic and Extendible Hashing,
Notes from Week 1: Algorithms for sequential prediction
CS 683 Learning, Games, and Electronic Markets Spring 2007 Notes from Week 1: Algorithms for sequential prediction Instructor: Robert Kleinberg 22-26 Jan 2007 1 Introduction In this course we will be looking
Semantic Analysis: Types and Type Checking
Semantic Analysis Semantic Analysis: Types and Type Checking CS 471 October 10, 2007 Source code Lexical Analysis tokens Syntactic Analysis AST Semantic Analysis AST Intermediate Code Gen lexical errors
2) Write in detail the issues in the design of code generator.
COMPUTER SCIENCE AND ENGINEERING VI SEM CSE Principles of Compiler Design Unit-IV Question and answers UNIT IV CODE GENERATION 9 Issues in the design of code generator The target machine Runtime Storage
Module 10. Coding and Testing. Version 2 CSE IIT, Kharagpur
Module 10 Coding and Testing Lesson 23 Code Review Specific Instructional Objectives At the end of this lesson the student would be able to: Identify the necessity of coding standards. Differentiate between
CS 103X: Discrete Structures Homework Assignment 3 Solutions
CS 103X: Discrete Structures Homework Assignment 3 s Exercise 1 (20 points). On well-ordering and induction: (a) Prove the induction principle from the well-ordering principle. (b) Prove the well-ordering
Summary. Pre requisition. Content Details: 1. Basics in C++
Summary C++ Language is one of the approaches to provide object-oriented functionality with C like syntax. C++ adds greater typing strength, scoping and other tools useful in object-oriented programming
Reading 13 : Finite State Automata and Regular Expressions
CS/Math 24: Introduction to Discrete Mathematics Fall 25 Reading 3 : Finite State Automata and Regular Expressions Instructors: Beck Hasti, Gautam Prakriya In this reading we study a mathematical model
3. Recurrence Recursive Definitions. To construct a recursively defined function:
3. RECURRENCE 10 3. Recurrence 3.1. Recursive Definitions. To construct a recursively defined function: 1. Initial Condition(s) (or basis): Prescribe initial value(s) of the function.. Recursion: Use a
Lumousoft Visual Programming Language and its IDE
Lumousoft Visual Programming Language and its IDE Xianliang Lu Lumousoft Inc. Waterloo Ontario Canada Abstract - This paper presents a new high-level graphical programming language and its IDE (Integration
1 if 1 x 0 1 if 0 x 1
Chapter 3 Continuity In this chapter we begin by defining the fundamental notion of continuity for real valued functions of a single real variable. When trying to decide whether a given function is or
Automated Program Behavior Analysis
Automated Program Behavior Analysis Stacy Prowell sprowell@cs.utk.edu March 2005 SQRL / SEI Motivation: Semantics Development: Most engineering designs are subjected to extensive analysis; software is................
7.1 Our Current Model
Chapter 7 The Stack In this chapter we examine what is arguably the most important abstract data type in computer science, the stack. We will see that the stack ADT and its implementation are very simple.
Certification Authorities Software Team (CAST) Position Paper CAST-13
Certification Authorities Software Team (CAST) Position Paper CAST-13 Automatic Code Generation Tools Development Assurance Completed June 2002 NOTE: This position paper has been coordinated among the
Integers: applications, base conversions.
CS 441 Discrete Mathematics for CS Lecture 14 Integers: applications, base conversions. Milos Hauskrecht milos@cs.pitt.edu 5329 Sennott Square Modular arithmetic in CS Modular arithmetic and congruencies
Lecture Notes on Linear Search
Lecture Notes on Linear Search 15-122: Principles of Imperative Computation Frank Pfenning Lecture 5 January 29, 2013 1 Introduction One of the fundamental and recurring problems in computer science
Formal Verification and Linear-time Model Checking
Formal Verification and Linear-time Model Checking Paul Jackson University of Edinburgh Automated Reasoning 21st and 24th October 2013 Why Automated Reasoning? Intellectually stimulating and challenging
Integrating Formal Models into the Programming Languages Course
Integrating Formal Models into the Programming Languages Course Allen B. Tucker Robert E. Noonan Computer Science Department Computer Science Department Bowdoin College College of William and Mary Brunswick,
Mathematical Induction
Mathematical Induction (Handout March 8, 01) The Principle of Mathematical Induction provides a means to prove infinitely many statements all at once The principle is logical rather than strictly mathematical,
15-150 Lecture 11: Tail Recursion; Continuations
15-150 Lecture 11: Tail Recursion; Continuations Lecture by Dan Licata February 21, 2011 In this lecture we will discuss space usage: analyzing the memory it takes your program to run tail calls and tail
Chapter 2 Limits Functions and Sequences sequence sequence Example
Chapter Limits In the net few chapters we shall investigate several concepts from calculus, all of which are based on the notion of a limit. In the normal sequence of mathematics courses that students
Mathematical Induction
Mathematical Induction Victor Adamchik Fall of 2005 Lecture 2 (out of three) Plan 1. Strong Induction 2. Faulty Inductions 3. Induction and the Least Element Principal Strong Induction Fibonacci Numbers
Chapter II. Controlling Cars on a Bridge
Chapter II. Controlling Cars on a Bridge 1 Introduction The intent of this chapter is to introduce a complete example of a small system development. During this development, you will be made aware
Sequential Data Structures
Sequential Data Structures In this lecture we introduce the basic data structures for storing sequences of objects. These data structures are based on arrays and linked lists, which you met in first year
Automata and Languages
Automata and Languages Computational Models: An idealized mathematical model of a computer. A computational model may be accurate in some ways but not in others. We shall be defining increasingly powerful
Constructing Contracts: Making Discrete Mathematics Relevant to Beginning Programmers
Constructing Contracts: Making Discrete Mathematics Relevant to Beginning Programmers TIMOTHY S. GEGG-HARRISON Winona State University Although computer scientists understand the importance of discrete
Coverability for Parallel Programs
2015 Coverability for Parallel Programs Lenka Turoňová* Abstract We improve existing method for the automatic verification of systems with parallel running processes. The technique
Functional programming languages
Functional programming languages Part II: abstract machines Xavier Leroy INRIA Rocquencourt MPRI 2-4-2, 2007 X. Leroy (INRIA) Functional programming languages MPRI 2-4-2, 2007 1 / 73 Execution models for
A simple criterion on degree sequences of graphs
Discrete Applied Mathematics 156 (2008) 3513 3517 Contents lists available at ScienceDirect Discrete Applied Mathematics journal homepage: Note A simple criterion on degree
Introducing Formal Methods. Software Engineering and Formal Methods
Introducing Formal Methods Formal Methods for Software Specification and Analysis: An Overview 1 Software Engineering and Formal Methods Every Software engineering methodology is based on a recommended
Compositional Real-Time Scheduling Framework with Periodic Model
Compositional Real-Time Scheduling Framework with Periodic Model INSIK SHIN and INSUP LEE University of Pennsylvania It is desirable to develop large complex systems using components based on systematic
Instruction Set Architecture (ISA)
Instruction Set Architecture (ISA) * Instruction set architecture of a machine fills the semantic gap between the user and the machine. * ISA serves as the starting point for the design of a new machine
Compiling Scheme to a Micro 2 computer
Massachusetts Institute of Technology Course Notes 7 6.844, Spring 03: Computability Theory of and with Scheme April 22 Prof. Albert Meyer revised April 22, 2003, 1115 minutes Compiling Scheme to a Micro,
AP Computer Science AB Syllabus 1
AP Computer Science AB Syllabus 1 Course Resources Java Software Solutions for AP Computer Science, J. Lewis, W. Loftus, and C. Cocking, First Edition, 2004, Prentice Hall. Video: Sorting Out Sorting,