Certifying Low-Level Programs with Hardware Interrupts and Preemptive Threads
Total Page:16
File Type:pdf, Size:1020Kb
Certifying Low-Level Programs with Hardware Interrupts and Preemptive Threads Xinyu Feng†‡ Zhong Shao† Yuan Dong†§ Yu Guo ∗ †Yale University, New Haven, CT 06520-8285, U.S.A. ‡Toyota Technological Institute at Chicago, Chicago, IL 60637, U.S.A. §Tsinghua University, Beijing 100084, China ∗University of Science and Technology of China, Hefei, Anhui 230026, China [email protected] [email protected] [email protected] [email protected] Abstract Hardware interrupts are widely used in the world’s critical soft- . ware systems to support preemptive threads, device drivers, operat- ing system kernels, and hypervisors. Handling interrupts properly High-Level Concurrent Programs is an essential component of low-level system programming. Un- Low-Level Code with Interrupts & Concurrency 1 irq0 1 irq1 fortunately, interrupts are also extremely hard to reason about: they 0 irq2 locks I/O primitives dramatically alter the program control flow and complicate the in- cond var. cli/ 1 irq3 . sti . 1 irq4 variants in low-level concurrent code (e.g., implementation of syn- scheduler device drivers 0 irq5 chronization primitives). Existing formal verification techniques— ctxt switching interrupt handlers 1 irq6 including Hoare logic, typed assembly language, concurrent sepa- 0 irq7 ration logic, and the assume-guarantee method—have consistently ignored the issues of interrupts; this severely limits the applicability Figure 1. “High-Level” vs. “Low-Level” System Programs and power of today’s program verification systems. In this paper we present a novel Hoare-logic-like framework 1. Introduction for certifying low-level system programs involving both hard- Low-level system programs (e.g., thread implementations, device ware interrupts and preemptive threads. We show that enabling drivers, OS kernels, and hypervisors) form the backbone of almost and disabling interrupts can be formalized precisely using simple every safety-critical software system in the world. It is thus highly ownership-transfer semantics, and the same technique also extends desirable to formally certify the correctness of these programs. to the concurrent setting. By carefully reasoning about the inter- Indeed, there have been several new projects launched recently— action among interrupt handlers, context switching, and synchro- including Verisoft/XT (Gargano et al. 2005; Paul et al. 2007), nization libraries, we are able to—for the first time—successfully L4.verified (Tuch et al. 2005), and Singularity (Hunt and Larus certify a preemptive thread implementation and a large number of 2004)—all aiming to build certified OS kernels and/or hypervisors. common synchronization primitives. Our work provides a foun- With formal specifications and provably safe components, certified dation for reasoning about interrupt-based kernel programs and system software can provide a trustworthy computing platform makes an important advance toward building fully certified operat- and enable anticipatory statements about system configurations and ing system kernels and hypervisors. behaviors (Hunt and Larus 2004). Categories and Subject Descriptors F.3.1 [Logics and Meanings Unfortunately, system programs—especially those involving of Programs]: Specifying and Verifying and Reasoning about Pro- both interrupts and concurrency—are extremely hard to reason grams; D.2.4 [Software Engineering]: Software/Program Verifi- about. In Fig. 1, we divide programs in a typical preemptible cation — Correctness proofs, formal methods, reliability; D.3.1 uniprocessor OS kernel into two layers. At the “higher” abstrac- [Programming Languages]: Formal Definitions and Theory — Se- tion level, we have threads that follow the standard concurrent mantics; D.4.5 [Operating Systems]: Reliability — Verification programming model (Hoare 1972): interrupts are invisible, but the execution of a thread can be preempted by other threads; synchro- General Terms Languages, Reliability, Security, Verification nization operations are treated as primitives. Below this layer (see the shaded box), we have more sub- Keywords Hardware Interrupts, Preemptive Threads, Certified tle “lower-level” code involving both interrupts and concurrency. System Software, Concurrency, Separation Logic The implementation of many synchronization primitives and in- put/output operations requires explicit manipulation of interrupts; they behave concurrently in a preemptive way (if interrupt is en- abled) or a non-preemptive way (if interrupt is disabled). When execution of a thread is interrupted, control is transferred to an in- Permission to make digital or hard copies of all or part of this work for personal or terrupt handler, which may call the thread scheduler and switch the classroom use is granted without fee provided that copies are not made or distributed control to another thread. Some of the code in the shaded box (e.g., for profit or commercial advantage and that copies bear this notice and the full citation on the first page. To copy otherwise, to republish, to post on servers or to redistribute the scheduler and context switching routine) may behave sequen- to lists, requires prior specific permission and/or a fee. tially since they are always executed with interrupt disabled. PLDI’08, June 7–13, 2008, Tucson, Arizona, USA. Existing program verification techniques (including Hoare Copyright c 2008 ACM 978-1-59593-860-2/08/06. $5.00 logic (Hoare 1969), typed assembly language (Morrisett et al. Locks Condition Variables yield void acq_m(Lock *l); void wait_m(Lock *l, CV *cv); void signal_m(CV *cv); timer_handler void rel_m(Lock *l); void yield() cli/ void acq_h(Lock *l); Sec. 5.1 void wait_h(Lock *l, CV *cv); Sec. 5.2 sti Sec. 6 & [TR] void rel_h(Lock *l); & [TR] void signal_h(Lock *l, CV *cv); & [TR] void acq_spin(Lock *l); void wait_bh(Lock *l, CV *cv); Sec. 3.2 & [TR] Level C void rel_spin(Lock *l); void signal_bh(Lock *l, CV *cv); switch block unblock void scheduler() void blk(queue * q) int unblk(queue * q) Level S [TR] node* deQueue(queue * q) void enQueue(queue * q, node *n) ctxt switching code Figure 3. Structure of Our Certified Preemptive Thread Implementation Thread AHandler 0 Thread B interrupt handlers, context switching, and synchronization libraries. 0 q . ) . Handler 1 Our paper makes the following new contributions: r (1 . i . sti q1 • . ir As far as we know, our work presents the first program logic switch . (see Sec. 4) that can successfully certify the correctness of low- . cli . (4 . ) level programs involving both interrupts and concurrency. Our iret ( cli ( switch 3) 2) iret . idea of using ownership-transfer semantics to model interrupts . ( sti 5) switch is both novel and general (since it also works in the concurrent . sti setting). Our logic supports modular verification: threads and . handlers can be certified in the same way as we certify sequen- tial code without worrying about possible interleaving. Sound- Figure 2. Interaction between Threads and Interrupts ness of our logic is formally proved in the Coq proof assistant. • Following separation logic’s local-reasoning idea, our program 1998), concurrent separation logic (O’Hearn 2004; Brookes 2004), logic also enforces partitions of resources between different and its assume-guarantee variant (Feng et al. 2007a; Vafeiadis and threads and between threads and interrupt handlers. These log- Parkinson 2007)) can probably handle those high-level concurrent ical partitions at different program points essentially give an programs, but they have consistently ignored the issues of inter- abstract formalization of the semantics of interrupts and the in- rupts thus cannot be used to certify concurrent code in the shaded teraction between handlers and threads. box. Having both explicit interrupts and threads creates the follow- • Our AIM machine (see Sec. 3) unifies both the preemptive and ing new challenges: non-preemptive threading models, and to our best knowledge, • Asymmetric preemption relations. Non-handler code may be is the first to successfully formalize concurrency with explicit preempted by an interrupt handler (and low-priority handlers interrupt handlers. In AIM, operations that manipulate thread can be preempted by higher-priority ones), but not vice versa. queues are treated as primitives; These operations, together Interrupt handlers cannot be simply treated as threads (Regehr with the scheduler and context-switching code (the low half of and Cooprider 2007). Fig. 3), are strictly sequential thus can be certified in a simpler • Subtle intertwining between interrupts and threads. In Fig. 2, logic. Certified code at different levels is linked together using thread A is interrupted by the interrupt request irq0. In the an OCAP-style framework (Feng et al. 2007b). handler, the control is switched to thread B. From thread A’s • Synchronization operations can be implemented as subroutines point of view, the behavior of the handler 0 is complex: should in AIM. To demonstrate the power of our framework, we have the handler be responsible for the behavior of thread B? certified, for the first time, various implementations of locks • Asymmetric synchronizations. Synchronization between han- and condition variables (see Sec. 5). Our specifications pinpoint dler and non-handler code is achieved simply by enabling and precisely the differences between different implementations. disabling interrupts (via sti and cli instructions in x86). Unlike locks, interrupts can be disabled by one thread and enabled by 2. Informal Development another. In Fig. 2, thread A disables interrupts and then