Interactive Property Checking at Runtime with a - Check-exec
Transcription
Interactive Property Checking at Runtime with a - Check-exec
Master of Science in Informatics at Grenoble Master Mathématiques Informatique - spécialité Informatique option PDES Interactive Property Checking at Runtime with a Debugger Raphaël Jakse 23 June 2016 Research project performed at LIG / INRIA Under the supervision of: Yliès Falcone (LIG), Jean-François Méhaut (LIG), Kevin Pouget (LIG) Defended before a jury composed of: Noel de Palma Martin Heusse Thomas Ropars Arnaud Legrand Olivier Gruber External expert: Albert Cohen June 2016 Abstract Monitoring is the study of a system at runtime, looking for input and output events in order to discover, check or enforce behavioral properties. Debugging is the study of a system at runtime in order to discover and understand its bugs and fix them, inspecting interactively its internal state. In this work, we combine monitoring and debugging. We define an efficient and convenient way to check behavioral properties automatically on a program using a debugger. We aim at helping bug discovery while keeping the classical debugging techniques and interactiveness, which allow understanding and fixing bugs. Résumé Le monitoring est l’étude d’un système pendant son exécution, en surveillant les évènements qui y entrent et qui en sortent, afin de découvrir, vérifier ou pour faire respecter des propriétés sur son comportement. Le débogage est l’étude d’un système pendant son exécution afin de trouver et comprendre ses dysfonctionnements dans le but de les corriger, en inspectant son état interne, de manière interactive. Dans ce papier, nous combinons le monitoring et le débogage. Nous définissons un moyen efficace et pratique de vérifier automatiquement des propriétés sur le comportement d’un programme à l’aide d’un débogueur. Notre but est d’aider à détecter des anomalies dans son code, en conservant les techniques et le caractère interactif du débogage classique permettant de comprendre et corriger les bugs. Contents Abstract i Résumé i 1 Introduction 1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Overview of our Approach . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Organization of this Report . . . . . . . . . . . . . . . . . . . . . . . . 1 1 2 4 2 Background 2.1 Existing Methods for Finding Bugs . . . . . . . . . . . . . . . . . . . . 2.2 Contribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 7 3 Joint Execution of the Debugger, the Monitor and the Program 3.1 Configurations of the System . . . . . . . . . . . . . . . . . 3.2 Description of Events . . . . . . . . . . . . . . . . . . . . . 3.3 Property Model . . . . . . . . . . . . . . . . . . . . . . . . 3.4 Evolution of Configurations . . . . . . . . . . . . . . . . . . 3.5 Semantic rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 9 12 13 15 18 . . . . . 27 27 28 30 32 33 5 Related Work 5.1 Monitoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 37 4 Implementation and Evaluation 4.1 Check-exec: a GDB extension 4.2 Syntax of Properties . . . . . . 4.3 Using Check-exec . . . . . . . 4.4 Architecture of Check-exec . . 4.5 Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 6 Conclusion and Future Work 6.1 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Future Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 39 40 A check-exec Command List 43 B Check-exec’s Monitor Interface 45 C Executing an Example of a Program 49 D Proof (sketch): the execution of a program is not affected 53 Bibliography 57 iv 1 Introduction 1.1 Motivation When developing a software, fixing bugs as early as possible is important. This can prove to be difficult. A way to do this consists in observing a bad behavior and start a debugging session to find the cause. A debugging session generally consists in repeating the following steps: execute the program in a debugger, set breakpoints before the expected cause of the bug, find the point in the execution where it starts being erratic and inspect the internal state (callstack, values of variables) to determine the cause of the problem. This process can be tedious and prone to a lot of trials and errors. Besides, a bug does not systematically lead to a crash, it can remain undetected during a whole development cycle. Therefore, we need methods and tools to ease bug detection. A difference between the specification of the program and an actual execution is an anomaly which has to be fixed. This report presents a method to ease failure detection by automating some tedious aspects of a debugging session by making some checks systematic. More specifically, our idea is to check properties linked to the specification of the program or the API (Application Programming Interface) provided by the libraries it uses. Our approach is inspired by monitoring. In this domain, the studied system is viewed as a black box. What is observed is the sequence of events it produces and receives. Considering the execution from this point of view can ease the expression of properties on the expected runtime behavior of the program. When a property is violated, the developer can be warned and is able to look at the internal state of the program using the debugger to understand what happened. Our approach is meant to be as little intrusive as possible in term of workflow for the developer. It integrates well to the developer’s toolbox, which may ease its adoption. It also takes advantage of the infrastructure already provided by the debugger. Part of this work will presented at the national conference COMPAS 2016 [22]. A new paper will be submitted to an international conference later. 1.2 Overview of our Approach Here we summarize the main ideas and the general principles of our approach. 1.2.1 Execution Model of the Program When debugging, the execution is seen as a sequence of program states that the developer inspects step by step using a debugger in order to understand the origin and the cause of a misbehavior. The state of the program can be modified at runtime. This lets the developer try hypotheses on the bug without having to recompile and rerun the whole program, which would be time consuming. The bug is also not guaranteed to show up in the new execution. When monitoring, the execution is abstracted into a sequence of events of program state updates. The main goal of monitoring is to detect misbehaviors of a system considered as a black box: the set of its internal behaviors is not accessible and its internal state cannot be altered. Information on the internal state can be retrieved by instrumenting the execution of the program. Analyzing the execution trace can be done postmortem. This abstraction of the execution can ease the expression of properties on the program’s behavior. 1.2.2 Methodology Our approach combines ideas from debugging and monitoring as follows. The developer describes properties to verify. These properties are checked during the execution of the program following the trace in real time. Decisions can be taken according to the current state of the evaluation of the property. Our approach can be used for instance as follows: as soon as a property is violated, the execution is suspended to let the developer inspect and debug the program in the usual way. We implemented this approach by extending the debugger, making it capable of running monitors to check properties during the execution and by making this evaluation capable of altering the execution. A monitor is a device (a piece of hardware or software) that evaluates a property against an execution trace, giving a verdict upon the reception of each event of the trace. The verdict corresponding to the last event of the execution trace is called the final verdict [15]. In Chapter 4, we give details on the implementation of our proof-of-concept extension for GDB. 1.2.3 Model of Property and Instrumentation Properties can be expressed on the whole set of events that can be retrieved from the debugger. Events can be parameterized, that is, values can be linked to events. For 2 Slice on q Init N:=0, Max:=0 If N < Max : queue_push(q) / N := N+1 queue_new(q, size) Max := size – 1 queue_ready If N ≥ Max : queue_push(q) If N ≤ 0 : queue_pop(q) sink If N > 0 : queue_pop(q) / N := N-1 Figure 1.1: Property on a queue overflow in a producer-consumer program described in Subsection 4.5.1. instance, a function call is an event parameterized with values of arguments passed during this call, as well as values that are accessible at this time (global variables for example). Events can be monitored by setting breakpoints. Thus, when the evaluation of a property requires monitoring calls to a particular function, a breakpoint is set on this function. When it is reached during the execution, the relevant monitor updates its state and can let the execution resume or let the developer interact with the debugger if the property has been violated. When an event does not affect the evaluation of any property anymore, the breakpoint becomes useless, it is then unset: the instrumentation is dynamic. We describe properties in a model based on finite-state machines. It is composed of states, transitions and a memory and it recognizes sequences of events. Transitions have guards that are expressed in function of event parameters and the memory. The memory can be updated when taking a transition. The execution trace can be sliced, so that a property is checked against parts of the trace concerning specific instances of objects (e.g. opened files) instead of the global state of the program [6]. In this case, a monitor does not correspond to a single finite state machine, but to a set of finite state machines, one for each particular instance of an object. An example of property is given in Figure 1.1. We define our model of property more precisely in Chapter 3. 1.2.4 Scenarios A scenario describes what should be done given the evaluation of the property. An example of a scenario is: when the verdict given by a monitor becomes false, that is, when the evaluation of a property enters a non-accepting state, the execution is suspended. Scenarios allow binding actions to events generated by the monitor. Examples of these events are “the state X has been entered”, “the state X has been left”, “an accepting state has been entered”, “a non-accepting state has been left”. These actions can affect the state of the debugger or the program. Scenarios are defined more precisely in Chapter 3. 1.3 Organization of this Report In Chapter 2, we present the state of the art and the building blocks behind our approach. In Chapter 3, we gather these building blocks and describe our approach more precisely. In Chapter 4, we present our proof-of-concept implementation of this approach, checkexec, and we use it to evaluate our approach. In Chapter 6, we conclude by presenting our future works. 4 2 Background 2.1 Existing Methods for Finding Bugs There are several families of techniques for finding bugs. Three of them are static analysis, testing and runtime verification. In this chapter, we briefly present several techniques from these three families. 2.1.1 Testing Manual testing. Beta testing is a widespread way of testing. Most obvious bugs can easily be spotted this way during the development of the software. Modifications to the code are manually tested, possibly by a team responsible for testing the software [21]. Bugs are also spotted by final users of the software, which, depending on the development model, the kind of software and the severity of the bug, is more or less undesirable. Automatic testing. Automatic testing through unit tests can be used to ensure that the already-fixed bugs do not show again, to limit regressions and to check whether the code is correct for a restricted set of inputs [20]. Some research efforts have been carried out on the automatic generation of unit tests. For instance, [7] defines a way to generate test oracles from formal specifications of the expected behavior of a Java method or class. Interactive debugging. In interactive debugging, the program is seen as a white box. At any time, its internal state can be inspected using the debugger, including the call stack and values of variables. The execution can be affected: variables can be edited, functions can be called, the execution can be restored from a previous state. This lets the developer test hypotheses without having to modify the code, recompile and execute the program. However, the execution is seen at a low level (assembly ode, often mapped to the source code) and does not really address bug discovery: usually, when debugging, one already knows there is a bug and uses tools to try to understand it. The main debuggers are GDB, LLDB and the Visual Studio debugger. GDB is a crossplatform free debugger from the Free Software Foundation. LLDB the cross-platform free debugger of the LLVM project, started by the University of Illinois, now backed by various firms like Apple and Google. The Visual Studio debugger is Microsoft’s debugger. Reverse debugging [14, 17, 34] is another debugging technique. When the developer is looking for a bug, instead of running the program in a debugger, (s)he runs it normally and records its execution. If the bug is encountered, the execution can be replayed in a debugger, and reversing the execution is also possible. It also allows replaying and debugging the record deterministically. This guarantees that the behavior of the program remains unchanged and the bug happens again. UndoDB and rr are GDB-based tools allowing record and replay and reverse debugging with a small overhead. 2.1.2 Static analysis and abstract interpretation In static analysis and abstract interpretation [11], the source code of the software is analyzed without being run. The goal is to find programming errors and chunks of code that can cause maintenance difficulties. Maintenance difficulties raise the risk of introducing bugs during subsequent modifications. Properties can also be proven over the software’s behavior. Unfortunately, theses approaches have limitations: they can be really slow, limited to certain classes of bugs or properties, produce false positives and false negatives. While static analysis and abstract interpretation can provide certain guarantees by proving properties over the program, proving correction of a software by static analysis is undecidable in general [26]. Model checking. Model checking is an automatic verification technique for finitestate reactive systems. Model checking consists in checking that a model of the system verifies temporal properties [8]. 2.1.3 Runtime verification and runtime analysis Valgrind. Valgrind [33] is a tool to detect illegal memory accesses, cache-related performance and memory consumption problems and can interface with GDB. Valgrind instruments programs by dynamically adding instrumentation to the machine code of the inspected program at runtime and by running it on a simulated CPU. Monitoring. In monitoring [12, 18, 27, 35], the program is seen as a black box. The execution is rather seen as a sequence of input and output events. This trace can be analyzed offline (i.e. after the termination of the program) as well as online (i.e. during the execution) and constitutes a convenient abstraction one which it is possible 6 to express runtime properties. JavaMOP [23] is a tool that allows runtime property checking on Java programs. In monitoring, there are different ways of instrumenting, like aspects [25, 24] and code injection. In monitoring, the execution of the program can also be affected, but only from the outside: this can be done by modifying the sequence of input events. The sequence of output events can also be edited to make it comply with some properties [16]. This, however, does not allow to understand an incorrect behavior and does not actually fix the program. 2.2 Contribution Theses approaches to finding bugs, as well as other approaches not covered in this introduction, have their own sets of drawbacks and benefits. This makes them suitable for discovering different sets of bugs in different situations: some classes of bugs are more easily avoided by analyzing the software statically, others are more easily and rapidly spotted by testing the software directly. The relevance of each technique also depends on the point at which is life cycle of the development of the application. There are, however, bugs that are not discovered or understood easily or efficiently by aforementioned methods: as said above, static analysis cannot find every bug in all softwares and some bugs are not apparent in any obvious way. When a malfunction shows during a test, its cause is not always easily understandable as the programming error at its root could be caused earlier during the execution of the software (e.g. a pointer that should contain a valid address instead of a null or a random address). Our approach combines and takes its roots from debugging and monitoring. Both domains aim at program correctness in two different ways. They are complementary: monitoring helps bug detection while debugging helps understanding and fixing bugs when one already knows that there is a bug. Our idea is to get the best of both worlds to ease bug discovery and understanding by leveraging the possibility to check runtime properties using a debugger. Our general long-term objective is that during development of a software, systematically checking properties while running the software helps finding, understanding and fixing bugs and regressions. 3 Joint Execution of the Debugger, the Monitor and the Program Our approach relies on the joint execution of different components: the program, the debugger, the monitor and the scenario. The execution of these components is concurrent. In this chapter, we describe the system composed of these components as a Labeled Transition System (LTS). We first present its configurations and their components individually in (Section 3.1). Events play the role of symbols of the LTS. We define the notion of events in Section 3.2. In Section 3.3, we describe the model used to describe properties, based on an extension of finite-state machines. We then give semantics rules describing the joint execution (Section 3.4). Semantics rules provide a rigorous definition of the composition of the entities and facilitate the reasoning on their properties. 3.1 Configurations of the System The composition of the whole system is represented in Figure 3.1. Its set of configurations is Γ = (Mem p × Address) × (Qd × Breakpoints × Checkpoints) × (Qm × Memm × Mems ). Its initial configuration is γ0 = ((m p,0 , pc0 ), (I, ε, ε), (init, mm,0 , ms,0 ). The Program The Debugger The Monitor Memp Address Qd Breakpoints Checkpoints Qm Memp Mems mp pc qd B C qm mm ms Debugger's state Debugger's memory breakpoints Debugger's memory checkpoints Monitor's state Monitor's memory environment Scenario's memory environment Program's memory Figure 3.1: The elements of the configuration of the whole system Passive State: Program executes without interruption CTRL+C Program terminates Program is suspended Other commands continue run Interactive State: Waiting for users’ Input. Program execution Is suspended Figure 3.2: The states of the debugger In this section, we describe and explain each component of this configuration individually. The Debugger. A configuration of the debugger is a tuple (qd , B, C ). qd is the current state of the debugger, that is either I (interactive mode, in which the user can issue commands in the debugger) or P (passive mode, in which the program runs without interruption). See Figure 3.2 for a representation of the two possible states of the debugger. B is the sequence of breakpoints. A breakpoint is a tuple (addr, instr, isUserBP) where addr is the address of the breakpoint in the memory of the program, instr is the instruction to restore when the breakpoint is removed and isUserBP a boolean equal to true if the breakpoint was set by the user, false if it was set by the monitor. C ∈ (Mem p × Address × Qm × Memm )∗ is the sequence of saved checkpoints. A checkpoint is a snapshot of the whole internal state of the program during its execution that can be restored (i.e, the checkpoint can be used to drop the current internal state of the program and make the execution continue from the snapshot). A checkpoint is a tuple (m p , pc, qm , mm ) where m p is the memory of the program at the moment of the creation of the checkpoint, pc is the current program counter, qm is the state of the monitor and mm the memory of the monitor at the time of the checkpoint. See Example 1 for an illustration of a checkpoint. Example 1 Let P be the program given by the following source code: a := 0 b := 1 a := a + b A checkpoint when the third line is about to be executed is a tuple (m p , pc, qm , mm ) where m p = [a 7→ 0, b 7→ 1], describing the internal state of the program P , pc = <the address of the third line>, the address of the instruction to be executed, qm and mm are the state of the monitor and its memory when the checkpoint is set. 10 The Program. A configuration of the program is a tuple (m p , pc) ∈ Mem p ×Address. m p is the memory environment, that maps addresses to machine words and pc is the current program counter (an address in the program’s memory), that is, the address of the next instruction in the memory. The program is defined by an initial memory environment, a symbol table Sym that maps symbol names to addresses, a start address that points to the first instruction to run in the memory and a function runInstr representing the instruction set of the program. The function runInstr takes the program memory and an address (the current address) and returns a new program memory and a new address. The exact definition of function runInstr depends on the instruction set of the program. It is here to abstract away the programming language we use. The Monitor. A configuration of the monitor is a tuple (qm , mm ) where mm is the current memory of the monitor and qm to the current state of the property. The monitor is defined by an initial memory environment mm,0 , its set of states Qm , its initial state qm,0 , Σm its set of input events, ∆m : Qm × Memm × Σm → Qm × Memm its transition function. We denote by enabled(q) the set of uninstantiated events to which a state is “sensible”, that is, ∀q ∈ Q, e ∈ enabled(Q) ⇐⇒ (q, e) ∈ D(∆m )1 . See Example 2 for an illustration of enabled. Example 2 For the property of Figure 1.1: • enabled(Init) = {e f ,be f ore (queue_new)}; • enabled(queue_ready) = {e f ,be f ore (queue_push), e f ,be f ore (queue_pop)}; • enabled(sink) = 0. / where e f ,be f ore ( f ) is the formal event corresponding to a call to function f . It is a before event. The Scenario. The scenario is defined by an initial memory environment ms,0 and a list S of actions triggered by monitor updates. The configuration of the scenario is ms , its memory. S is a set of tuples, each containing the information on when to trigger an action and the action itself. More precisely, S ∈ (When × Point × P(Qm ) × A )∗ where When = {before, after} and Point = {entering, leaving} The scenario element (ba, lt, Qs , a): • is triggered before the effective update if ba = before, and after if ba = after. • if lt = leaving and if an element of Qs is the current state of one slice of the monitor the current state of the slice changes, the action is run. 1 D( f ) is the domain of definition of f . • if lt = entering and if the current state of a slice of the monitor changes and the new current state of the slice belongs to Qs , the action is run. a is a list of instructions that can be used to control the debugger and the monitor. An instruction consists in setting a user’s breakpoint, watchpoint, checkpoint, restart from a checkpoint, update and consult the memory of the scenario ms . It can also consist in a condition or a loop, allowing the expression of complex behaviors. For instance, it lets express conditions like: If this is the second time the state x is accessed, run action a. Otherwise, run action b instead. In this section, we presented the configurations of the system and we described each components of these configuration. In the next section, we give the description of events, that are the symbols making the system transition from one configuration to another. 3.2 Description of Events An event is an object that has a name, a list of parameters (that can be empty) and possibly a list of instances of these parameters. An event is generated during the execution of the program whenever something happens. A function call, for instance, generates an event. The name of this event is the name of the function, its type is Call, its parameters are the parameters of the function and their instances are the arguments passed during the call. Our approach is based on the ability to capture events from the program execution with the debugger. We therefore need a precise description of events that is suitable in this context. Let e be an event. We denote params(e) its formal parameter list, name(e) its name, type(e) its type, values(e) : Name → Value the function that maps parameters to their values if e is an instantiated event. isBefore(e) equals true if e is a before event, that is, the event happened before the instruction that triggered it. isBefore(e) equals false otherwise (i.e. if e is an after event). The following are valid event types: • Call: an event generated by a function call. A before event is triggered before the first instruction of the function and after the jump to the function body. An after event fires after the last instruction of the function and before the jump to the caller. • ValueWrite: an event generated by an assignment. A before event fires before the assignment instruction. A after event fires after the assignment instruction. • ValueRead: an event generated by a variable read. A before event fires before the read instruction. An after event is triggered after the instruction that reads the variable. 12 • UpdateExpr: an event generated whenever an expression’s value is changed. A before event is fired before the update (in which case Ret<e> denotes the value of the expression before the modification). An after event is fired after (in which case Ret<e> denotes the new value of the expression). Let e be a Call event and N the number of parameters of the function. • arg i, 0 < i < N is a valid parameter name. Its value is the value of the corresponding variable at the time the event fires. The value is not necessarily the value passed during the call for an after event of type Call but the current value of this variable. • v is a valid parameter name, if v is the name of a variable (including a parameter) defined when the event fires. • *p, is a valid parameter name if p is a valid parameter name and p refers to a pointer in the program, ∗p is the value pointed by p. • &p, is a valid parameter name if p is a valid parameter name, &p is the address of p in the program. • Ret<e> is a valid parameter name if e is an after event. Its value is the value returned by the call. Formal events are used when writing properties. Arguments are not known at this time. During the execution, instantiated events are then matched to formal events. An instantiated event matches a formal event if all its attributes, except values are identical to the attributes of the formal event Definition 1 An instantiated event ei matches a formal event e f if name(ei ) = name(e f ) and type(ei ) = type(e f ) and isBefore(ei ) = isBefore(e f ) and params(ei ) = params(e f ) and Example 3 “Before push(q, 5)” is an instantiated event matching the uninstantiated event “Before a call to function (type) push (name) that takes a queue and an element in parameters (list of parameters)”. 3.3 Property Model Our property model is inspired by finite-state machines, that we extend with the ability to memorize data using an environment. For the sake of simplicity, we do not take trace slicing in account in this chapter, though we implemented it and take i A property is a tuple (Q, Σ, init, env0 , ∆, v) where Q is a set of state, Σ is the set of formal events involved in this property, env0 ∈ Env is the initial environment (Env = Names → Values, where Names is the set of variable names and Values is the set of values that can be stored in a variable), ∆ : Q × Σ × (Env × Env → B) × (Env × Env → Env) × Q is the transition function and v ∈ [Q → V ] is the function that maps states to verdicts. A transition is a tuple (qs , e f , g, upd, qd ) where qs is the start state, e f is the formal event, g is the guard, upd is the “updater” and qd is the destination state. The guard g : Env × Env → B takes the environment built from the instantiated event’s parameters, the environment of the monitor and returns a boolean. If it returns true, the transition is taken. If it returns false, the transition will not be taken. The updater upd : Env × Env → Env returns an environment from the environment built from the instantiated event’s parameters the environment of the monitor. This function is used to update the environment of the property. When an instantiated event ei is triggered, a transition (qs , e f , g, upd, qd ) is taken if the current state qm of the property is qs , ei matches e f and g(ei , m p ) = true, where mm is the current memory of the property. If so, the memory and the state of the property are updated: m0m = upd(ei , m p ), where m0m denotes the new memory of the property and qd becomes the new current state. See Example 4. Example 4 The property illustrated in Figure 1.1 (if we ignore trace slicing, which is equivalent to assuming that there is only one instance of a queue) is a tuple (Q, Σ, init, env0 , ∆, v) where: • Q = {Init, queue_ready, sink} • Σ = {e f ,be f ore (queue_new), e f ,be f ore (queue_push), e f ,be f ore (queue_pop)}, • init = Init • env0 = [N 7→ 0, Max 7→ 0] • v = [Init 7→ true, queue_ready = true, sink = false] • The transition function: ∆={ (Init, e f ,be f ore (new), [any 7→ true], ([size], env) 7→ env[max := size − 1], ready), (ready, e f ,be f ore (push), [[N, Max] 7→ N < Max], (any, env) 7→ env[N+ = 1], ready), (ready, e f ,be f ore (pop), [[N, Max] 7→ N > 0], (any, env) 7→ env[N− = 1], ready), (ready, e f ,be f ore (push), [[N, Max] 7→ N >= Max], (any, env) 7→ env, sink), (ready, e f ,be f ore (pop), [[N, Max] 7→ N <= 0], (any, env) 7→ env, sink), } 14 The first tuple makes the property transition from Init to ready when (queue_)new is called. The guard always returns true so the transition is taken unconditionally. The updater stores the maximum number of elements in the queue in the environment of the monitor. This maximum is computed from the size parameter of the event new. The two next tuples make the monitor stay on the ready state when it is correct to (respectively) add or remove elements from the queue. In each case, the updater updates the number of elements in the queue in the environment of the monitor. The two last tuple detect that an element is (respectively) added or removed though the queue is (respectively) full or empty, and makes the property transition from ready to sink. 3.4 Evolution of Configurations In this section, we describe how the system evolves, that is, we give rules to build the sequence of configurations of the system. First, we define several function used in these rules in Section 3.4.1. Then, we give the semantic rules in Section 3.4. Limitation. For the sake of simplicity, we limit the set of events the system can handle to events that can be retrieved using breakpoints. In particular, watchpoints, that can be used to track memory accesses, are not considered. 3.4.1 Functions Used in the Rules setBP, setInstrBp Breakpoints are used to monitor function calls. Therefore, functions that set and unset breakpoints are needed. We define the following functions: setInstrBp (sets a breakpoint in the memory of the program), setBP (sets a breakpoint in the memory of the program and keeps track of it in the memory of the debugger), unsetBP (find a breakpoint by its address and remove it from the memories of the program and the debugger). The function setInstrBp replaces the word at the given address in the memory of the program by a breakpoint instruction (denoted B) in the memory of the program at the given address. When this instruction is encountered during the execution of the program, the execution is suspended and the debugger takes control over it. Formally, setInstrBp(m p , addr) = m0p with m0p such that ( B if addr = a ∀addr, m0p [addr] = m p [addr] otherwise Writing a breakpoint instruction is not sufficient. We also need to keep the word we replace in memory, so when the execution is resumed from this breakpoint, the instruction corresponding to this word can be executed. When the breakpoint is removed, the instruction will replace back the special breakpoint instruction. Several breakpoints can be set at the same address. For instance, the monitor and the user might want to set a breakpoint on the same function. We need to keep track of all of them in order in the structures of the debugger. We therefore use a list to save them. We define the function setBP that sets a breakpoint and keeps track of it. We also indicate if the breakpoint was set by the human user or by the monitor, so that breakpoints set by the user do not call the monitor and breakpoint set by the monitor are not seen by the user. setBP(m p , B, addr, isUserBP) = (B 0 , m0p ) with m0p = setInstrBp(m p , addr) and B 0 = (a, m p [addr], isUserBP) · B. unsetBP We also define unsetBP, which role is to unset a breakpoint and stop keeping track of it. unsetBP(m p , B, a, isUserBP) = (m0p , B 0 , b) such that b is the first breakpoint ( in B matching (addr, _, isUserBP) instr s.t. (_, instr, _) = b ∀addr, m0p [addr] = m p [a] otherwise B 0 = B from which b ∈ B is removed Remark. The functions setInstrBp, setBP and unsetBP match the behavior of a real debugger using software breakpoints. A software breakpoint is implemented using a trap instruction. When several breakpoints are set at a single address, the debugger sets one trap instruction at this address in the program’s memory and keeps track of all the breakpoints in its internal structures. When the current state of the property changes, breakpoints corresponding to events monitored by the old state must be unset, and breakpoints corresponding to events monitored by the new state must be set. setBPs, unsetBPs In Algorithm 1, we define the function setBPs used to set breakpoints needed for the new state. It returns a new program’s memory and a new list of breakpoints from the old memory of the program, the new state and the symbol table. The function unsetBPs is described in a similar way, but unsets breakpoints needed for the old state. Functions NEEDS B REAKPOINT and EVT T OA DDRESS are used by the functions setBPs and unsetBPs. 16 Algorithm 1 setBPs, unsetBPs 1: function NEEDS B REAKPOINT(e) 2: return type(e) = FunctionCall 3: end function 4: function EVT T OA DDRESS(e, Sym) 5: if type(e) = FunctionCall then 6: return Sym[name(e)] 7: else 8: ERROR 9: end if 10: end function 11: function UNSET BP S(m p , B, qm , Sym) 12: m0p ← m p 13: for all e ∈ enabled(qm ) do 14: if needsBreakpoint(e) then 15: (m0p , B 0 ) ← UNSET BP(m0p , B 0 , EVT T OA DDRESS(e, Sym), false) 16: end if 17: end for 18: return (m0p , B 0 ) 19: end function 20: function SET BP S(m p , B, qm , Sym) 21: m0p ← m p 22: for all e ∈ enabled(qm ) do 23: if needsBreakpoint(e) then 24: (m0p , B 0 ) ← SET BP(m0p , B 0 , EVT T OA DDRESS(e, Sym), false) 25: end if 26: end for 27: return (m0p , B 0 ) 28: end function removeAllBPs A checkpoint is snapshot of the program memory and the current program counter. It must not contain any instruction B. Setting a checkpoint (in rule 3.5.7 in the next section) thus requires that all breakpoints are removed from the memory before saving the memory of the program. We define the function removeAllBPs, which iterates over the list of breakpoints of the debugger and replaces each instruction B by the original instruction. • removeAllBPs(m p , ε) = m p and • ∀B 6= ε, removeAllBPs(m p , B) = removeAllBPs(m0p , B 0 ) where ( instr if a = addr B = (addr, instr, b) · B 0 & m0p [a] = m p [a] otherwise restoreAllBPs Conversely, when a checkpoint is restored (Rule 3.5.8), as the memory doesn’t contain any breakpoints, current breakpoints must be restored in the memory of the program. We therefore define the function restoreAllBPs which iterates over the list of breakpoints and sets the instruction B at the relevant addresses. restoreAllBPs(m p , B) = m0p with ( BREAK if ∃(addr, instr, b) ∈ B : addr = a ∀a, m0p [a] = m p [a] otherwise 3.5 Semantic rules In this section, we give the semantic rules specifying the precise behavior if the system. First step of the execution. When starting the execution of the system, the monitor is initialized. That is, breakpoints corresponding to the initial state of the property are set. m0p , B 0 = setBPs(m p , B, qm , Sym) load monitor ((m p , pc), (I, B, C ), (qm , mm , ms )) −−−−−−−→ ((m0p , pc), (I, B 0 , C ), (qm , mm , ms )) (3.5.1) When the command load monitor is issued in the debugger in interactive (I) mode, the current memory of the program m p and the list of breakpoints of the debugger B are updated. They become respectively m0p and B 0 where (m0p , B 0 ) = setBPs(m p , B, qm , Sym): 18 They contain the breakpoints needed for the current state of the monitor, which is the initial state. The execution of the program is interrupted. If the user interrupts the execution of the program (see Figure 3.2), the debugger transitions from passive (P) to interactive (I). The rest of the configuration does not change. InterruptPassive (P, (P, B, C ), M) −−−−−−−−→ (P, (I, B, C ), M) (3.5.2) The execution of the program is resumed. This rule is the inverse of the previous one. If the user continues the execution of the program (e.g. by issuing the command continue, see Figure 3.2), the system transitions from I to P. Again, the rest of the configuration does not change. cmd continue (P, (I, B, C ), M) −−−−−−−→ (P, (P, B, C ), M) (3.5.3) Normal execution. If the system is in passive mode and the instruction about to be executed is not a special instruction from the debugger (e.g. it is not a breakpoint), the program executes normally as if there were no debugger and no monitor. The program counter and the program memory are updated by the function runInstr, which runs the instruction to be executed. The instruction stop ends the execution. (m0p , pc0 ) = runInstr(m p , pc) ((m p , pc), (P, B, C ), M) → ((m0p , pc0 ), (P, B, C ), M) m p [pc] ∈ / DBGInst m p [pc] 6= stop (3.5.4) Given the current memory of the program m p and the current program counter pc, if the debugger is in passive mode and the current instruction is not a debugger instruction nor the instruction stop, the current instruction is run, resulting in a new program memory m0p and a new program counter pc0 . the memory of the program and its program counter are updated in the configuration of the system. Nothing else in the configuration is affected. The user sets a breakpoint. When the debugger is in its interactive mode (I), the user can set a breakpoint. They are two main ways of setting a breakpoint: giving the address in the program’s memory where the breakpoint should be set, and giving a symbol name (typically a function name), which is transformed into an address in the program’s memory using the symbol table. We first give the semantic rule for setting a breakpoint at an address. (m0p , B 0 ) = setBP(m p , B, a, true) break a ((m p , pc), (I, B, C ), M) −−−−→ ((m0p , pc), (I, B 0 , C ), M) [a ∈ Address] (3.5.5) If the user issues a command to set a breakpoint at address a, the function setBP updates the current memory of the program m p and the list of breakpoints B of the debugger. The resulting memory m0p and breakpoint list B 0 are stored in the configuration of the system. The rule for setting a breakpoint on a symbol name is identical, except we need to look up the symbol in the symbol table. (m0p , B 0 ) = setBP(m p , Sym(name), B, true) break name ((m p , pc), (I, B, C ), M) −−−−−−−→ ((m0p , pc), (I, B 0 , C ), M) (3.5.6) If the user issues a command to set a breakpoint at symbol name, the function, the configuration is updated in the same way as the previous rule. The difference is that the address, not given by the user, is deduced from the given symbol name using the symbol table Sym, which is part of the definition of the program (like the function runInstr). The user sets a checkpoint. When the debugger is in interactive mode (I), the user can set a checkpoint. When the user sets a checkpoint, several things are saved: the program’s memory, the program’s current address, the property’s state, the property’s environment. Breakpoint instructions in the memory of the program must be removed before saving the program’s memory. n = min N, (CN = ⊥ Ck k 6= n ∀k, Ck0 = (removeAllBPs(m p , B), mm , pc) k = n checkpoint (P, (I, B, C ), M) −−−−−−−→ (P, (I, B, C 0 ), M) (3.5.7) When the user sets a checkpoint, the smallest identifier n that is not used for a checkpoint is used. The new checkpoint list, C 0 , is the same as the current checkpoint list C , except for index n. The checkpoint is stored in Cn0 . In this checkpoint, the current memory of the program, the current state of the monitor, its memory and the current program counter are stored. Breakpoints are removed from the memory of the program before storage in the checkpoint. The user restarts a checkpoint. When the debugger is in interactive mode (I), the user can restore a checkpoint. The program’s memory, the current address, the 20 property’s state and the property’s memory are restored. Breakpoints are also reset in the new memory of the program. (m p,tmp , pc0 , q0m , m0m ) = Checkn m0p = restoreAllBPs(m p , B) restart n ((m p , pc), (I, B, C ), (qm , mm , ms )) −−−−−−→ ((m0p , pc0 ), (I, B 0 , C ), (q0m , m0m , ms )) (3.5.8) When the user restarts a checkpoint, the current memory of the program, the current program counter, the current state of the monitor and its current memory are replaced by the respective components of the checkpoint. The current breakpoints are set in the newly restored memory of the program, as the program memory stored in the checkpoint does not contain any breakpoints. This behavior matches the behavior of GDB and LLDB. The program’s execution encounters a debugger’s instruction. When the program’s execution encounters a special instruction, the debugger has to check if the instruction corresponds to a user’s breakpoint or to a monitor’s breakpoint. In the first case, the system transitions to the I state. In the second case, the event is applied. Remark. In real systems, the trap corresponding to the special instruction is caught by the operating system. The operating system suspends the execution of the program and informs the debugger that a trap happened. This behavior is close to what is described here. Here, we do not model the operating system so traps are not described. Handling a breakpoint. In Algorithm 2, we define HANDLE BP. If the breakpoints was set by the user, the state of the system is returned as is, except for the state of the debugger, which becomes interactive. If the breakpoint belongs to the monitor, breakpoints from the current state are removed, the breakpoint is interpreted as an event, the event is applied as a before event using the function APPLY E VENT defined in Algorithm 4, the original instruction is run, the event is applied as an after event using the function APPLY E VENT and the breakpoints for the new current state are set. Applying an event. The function applyEvent takes an event and updates the state of the system. It first use the transition function to get the new state and the new memory of the monitor. It then applies the scenario using the function λSC (applyScenario) defined in Algorithm 4. Applying the scenario. The scenario can set and unset breakpoints, save and restore checkpoints and update its own memory. As a consequence, it has the power to update the whole system. The scenario is applied only if the current state has been updated (Line 2 of Algorithm 4). Then, for each entry of the scenario, if the event corresponds to the entry, the action that is attached to it is run with the function runAction (Line 7). The function runAction is not defined precisely here and depends on the language chosen to implement scenarios. Algorithm 2 HANDLE BP 1: function HANDLE BP((m p , pc), (P, B, C ), (qm , mm , ms ), (Btmp , (addr, instr, isUserBP))) 2: if isUserBP then 3: qd ← I 4: pc0 ← pc 5: m0p ← m p 6: q0d ← qd 7: B0 ← B 8: C0 ← C 9: (q0m , m0m , m0s ) ← (qm , mm , ms ) 10: else 11: (m p,tmp , B 00 ) ← unsetBPs(m p , B, qm , Sym) 12: l ← bpToEvts(m p,tmp , pc, mm , Sym) 13: for all e ∈ l s.t. isBefore(e) do 14: ((mdp , pcd ), (B d , C d ), (qdm , mdm , mds )) ← APPLY E VENT( 15: (m00p , pc0 ), (B 00 , C ), (q0m , m0m ), S, e 16: ) 17: end for 18: (m00p , pc0 ) ← runInstr(m p,tmp , pcd ) 19: for all e ∈ l s.t. not isBefore(e) do f 20: ((m p , pc f ), (B f , C f ), (q0m , m0m , m0s )) ← APPLY E VENT( 21: (mdp , pc0 ), (B d , C ), (qdm , mdm ), S, e 22: ) 23: end for f 24: (m0p , B 0 ) ← setBPs(m p , B f , q0m , Sym) 25: end if 26: return (qd , pc0 , m0p , q0d , B 0 , C 0 , (q0m , m0m , m0s )) 27: end function 22 (_, Btmp , (addr, instr, isUserBP)) = unsetBP(B, pc, _) ((m0p , pc0 ), (qd , B 0 , C 0 ), M 0 ) = HANDLE BP( (m p , pc), (P, B, C ), M, (Btmp , (addr, instr, isUserBP)) ) ((m p , pc), (P, B, C ), M) → ((m0p , pc0 ), (qd , B 0 , C 0 ), M 0 ) m p [pc] = B (3.5.9) The function bpToEvts generates a list of events from a breakpoint. The type, the name, the parameters and the values of each event from the list is determined by the current program counter, the symbol table and the memory of the program and is given by the instruction set of the program. If the program counter is at the first instruction of a function, a before event of type FunctionCall is generated. If the program counter is at the last instruction of a function, an after event of type FunctionCall is generated. The function bpToEvts specifies for each generated event whether the event is a before event or an after event. Algorithm 3 getValueInPrgm 1: function GET VALUE I N P RGM (p, m p , pc, Sym) 2: if p matches (arg i), i ∈ N then 3: getParamN(i, pc) 4: else if p matches (∗v) then 5: m p [getValueInPrgm(v, pc)] 6: else if p matches (&arg i), i ∈ N then 7: getAddrParamN(i, pc) 8: else if p matches (n), n ∈ DSym then 9: m p [Sym(n)] 10: else 11: ERROR 12: end if 13: end function The execution of the program is done step by step. If the system is in interactive mode and the user issues the command step, the system executes the instruction at the current address. If the instruction is a breakpoint, the breakpoint is handled if it is a monitor breakpoint and the original instruction is executed. If a user breakpoint is found, it is ignored, but we take care of keeping it in the debugger’s internal structures Algorithm 4 APPLY E VENT and APPLY S CENARIO 1: function APPLY S CENARIO(S, (m p , pc), (qd , B, C ), (qm , mm , ms ), (q0m , m0m ), evtBefore) 2: if qm = q0m ∨ S = ε then 3: return (m p , pc), (qd , B, C ), (qm, mm, ms ) 4: end if 5: (ba, lt, Qs , a) ← head(S) 6: if ((ba = before) = evtBe f ore) ∧ ((lt = leaving ∧ qm ∈ Qs ) ∨ (lt = entering ∧ q0m ∈ Qs )) then 7: (m0p , pc0 ), (q0d , B 0 , C 0 ), (q0m , m0m , m0s ) ← runAction((m p , pc), (qd , B, C ), a) 8: else 9: (m0p , pc0 ), (q0d , B 0 , C 0 ), (q0m , m0m , m0s ) ← (m p , pc), (qd , B, C ), (qm , mm , ms ) 10: end if 11: return APPLY S CENARIO(tail(S), (m0p , pc0 ), (q0d , B 0 , C 0 ), 12: (qm , mm , ms ), (q0m , m0m ), eventIsBefore 13: ) 14: end function 15: function APPLY E VENT((m p , pc), (qd , B, C ), (qm , mm ), e) 16: (q0m , m0m ) ← ∆m ((qm , mm ), e) 17: return ( 18: (q0m , m0m ), 19: APPLY S CENARIO (S, (m p , pc), (qd , B, C ), (qm , mm , ms ), (q0m , m0m ), isBe f ore(e)) 20: ) 21: end function while updating the monitor and its breakpoints. In Algorithm 5, we define the function HANDLE S TEP BP used in the following rule. ((m0p , pc0 ), (I, B f , C f ), (q0m , m0m , m0s )) = HANDLE S TEP BP( (m p , pc), (I, Btmp , C ), (qm , mm , ms )) cmd continue m p [pc]=B ((m p , pc), (I, B, C ), (qm , mm , ms )) −−−−−−−→ ((m0p , pc0 ), (I, B f , C f ), (q0m , m0m , m0s )) (3.5.10) In Appendix C, we illustrate some of these semantic rules by running a toy program with property checking. In Appendix D, we giving the sketch of a proof of Proposition to illustrate these semantic rules in another way 1. Proposition 1 The execution of a program is not affected by the property evaluation (the monitor). 24 Algorithm 5 HANDLE S TEP BP function HANDLE S TEP BP((m p , pc), (I, Btmp , C ), (qm , mm , ms )) (, Btmp , (addr, instr, isUserBP)) ← unsetBP(B, pc) if isUserBP then cmd continue m p [pc]=B ((m p , pc), (I, Btmp , C ), (qm , mm , ms )) −−−−−−−→ ( f (m0p , pc0 ), (I, Btmp , C f ), (q0m , m0m , m0s ) ) f B f ← (Btmp , (addr, instr, isUserBP)) · Btmp else ((m0p , pc0 ), (I, B f , C f ), (q0m , m0m , m0s )) ← HANDLE BP( (m p , pc), (I, Btmp , C ), (qm , mm , ms ), (Btmp , (addr, instr, isUserBP)) ) end if end function 4 Implementation and Evaluation In order to evaluate our approach in terms of usefulness, efficiency, performance), we developed a proof-of-concept of our approach. In this chapter, we present our implementation. In Section 4.1, we give an brief overview of check-exec. In Section 4.2, we describe the syntax used in check-exec to write properties. We then explain how to use check-exec in Section 4.3. We give a brief description of the organization of its source code in Section 4.4. We conclude the chapter by an evaluation of our approach using check-exec in Section 4.5. 4.1 Check-exec: a GDB extension GDB Process Check-exec Monitored Process (inside Python) rea set b ts kp oin Monitor 1 event Monitor n event Breakpoint reached Breakpoint reached Program execution Figure 4.1: Architecture of check-exec Our proof of concept is named check-exec1 and extends GDB using its Python interface. Check-exec provides a graphical and animated view of the property being checked. The view is optional but can ease understanding the current state of the property and, as a consequence, the program. Check-exec also lets the developer control the monitors and access their internal state (property instances, current states, environments). 1 check-exec can be downloaded at http://check-exec.forge.imag.fr/. In this work, we focused on programs written in C and C++. However, managing other programming languages supported by GDB is possible. check-exec is written in Python and is executed by GDB when it handles breakpoints in the monitored process that were set by check-exec. When a breakpoint is reached, the state of the property is updated and the execution is resumed. It can run one or several monitors, each evaluating instances of one property independently. Each monitor sets and deletes breakpoints according to the events that are relevant to its current state. A diagram of the execution of a program with the tool is presented in Figure 4.1. 4.2 Syntax of Properties Check-exec provides a syntax for writing properties in the automaton model presented in Section 3.3, with a slight modification to make properties more concise and their evaluation more efficient by reducing repetitions. Transitions can be written with two destination states: a success state, used when the guard returns SUCCESS, and a failure state, used when the guard returns FAILURE. The guard can also return NOT RELEVANT, which means that the transition will not be taken. An informal grammar is given in Figure 4.3. First, the optional keyword slice on gives the list of parameters on which the execution trace should be sliced for the evaluation of the property. slice on queue Then, an optional initialization block initializes the monitor’s memory using a Python code block: initialization { N = 0 } Then, comes the list of states containing the mandatory state init. Each state has a name, an optional annotation indicating whether the state is accepting or not, an optional action name attached to the state and its transitions. Each transition comprises the monitored event, the event’s parameters used in the guard, the guard (optional), the success block and the failure block (optional). Success and failure blocks comprise an optional block of Python code, an optional action name, and the name of a destination state. The guard is a block of Python code which should not have any side effect and which should return True if the guards succeeds (SUCCESS), False if the guard fails (FAILURE) and None if the transition should not be taken (NOT RELEVANT). An example of a property written for check-exec, checking whether an overflow happens in a multi-threaded producer-consumer program, can be seen in Figure 4.2. 28 s l i c e on q u e u e initialization { N = 0 max = 0 } state i n i t accepting { transition { success { e v e n t queue_new ( queue , s i z e : i n t ) max = s i z e − 1 } queue_ready } } s t a t e queue_ready accepting { transition { e v e n t q u e u e _ p u s h ( queue , p r o d _ i d ) { r e t u r n N < max } success { N = N + 1 p r i n t ( " nb elem : "+ s t r (N ) ) ; } queue_ready failure { p r i n t ("% d made %d o v e r f l o w ! " % ( prod_id , queue ) ) } sink } transition { e v e n t q u e u e _ p o p ( queue , p r o d _ i d ) { return N > 0 } success { N = N − 1 p r i n t ( " nb elem : "+ s t r (N ) ) ; } queue_ready f a i l u r e sink } } s t a t e s i n k non−a c c e p t i n g s i n k _ r e a c h e d ( ) Figure 4.2: Description in the format of check-exec of the property represented in Figure 1.1 s l i c e on param , + ? initialization { Python code } ? s t a t e state_name non− ? a c c e p t i n g ? action_name ( ) ? { t r a n s i t i o n { b e f o r e | a f t e r ? e v e n t event_name ( param , * ) { Python code returning T r u e F a l s e or None } ? success { Python code } ? action_name ( ) ? state_name ? failure { Python code } ? action_name ( ) ? state_name ? } * } + Figure 4.3: Informal grammar for the automaton-based property description language in check-exec 4.3 Using Check-exec A typical usage session begins by launching gdb and loading check-exec. Here, we suppose that GDB was configured to load check-exec automatically at the beginning of the session: $ gdb ./my-application Then, the user loads a property. If actions, which are written as Python and referred to in the property by their name, are needed, they can be loaded at the same time. If several properties must be loaded, the following line can be repeated for each property. (gdb) check-exec load-property correct-behavior.prop actions.py A scenario can also be loaded: (gdb) check-exec load-scenario default-scenario.sc 30 Figure 4.4: During the execution of the property given in Figure 4.2, the following graphs can be seen respectively before property’s initialization, on property’s initialization, while the property is verified and when the property becomes falsified. Light red, red, brown and gray respectively correspond to non accepting state, a current non accepting state, a transition taken during the last state change and a state which was current before the last state change. Graphs are automatically drawn using Graphviz and colors animated during the execution. Then, the user starts the program execution. It is also possible to display the graph of the property with the show-graph subcommand (see Figure 4.4). (gdb) check-exec show-graph (gdb) check-exec run-with-program ... [check-exec] Initialization: [check-exec] N: 0 [check-exec]Current state: init [check-exec] N: 0 queue.c: push! [check-exec] Current state: init ... queue.c: push! [check-exec] GUARD: nb push: 63 [check-exec] Overflow detected! [check-exec] Current state: sink monitorinterface.py De fine sanI nt e r f ac et o mani pul at emoni t or s gdbcommands.py Sour c e df r om GDB graphdisplay.py graphdisplayer.py Di s pl a yani mat e dpr ope r t i e s monitor.py De fine st heMoni t orc l as s property.py De fine st hePr ope r t y c l as s breakpoint.py Handl e sbr e akpoi nt s , i nt e r f ac e st oGDB scenario.py De fine st heSc e nar i o c l as s property_parser.py scenario_parser.py Par s e spr ope r t i e s Par s e ss c e nar i os Figure 4.5: Organization of check-exec’s code [check-exec] N: 63 [check-exec: Execution stopped.] (gdb) Check-exec provides more fine-tuned commands to handle cases when properties and actions need to be loaded separately, or when properties and the program need to be run at different times. A list of commands is given in Appendix A 4.4 Architecture of Check-exec The organization of the code is pictured in Figure 4.5. The central part of check-exec is the Monitor class, defined in monitor.py. A Monitor object is instanced for each property checked at runtime. Files property.py and (resp.) scenario.py define the classes Property and (resp.) Scenario. When the developer’s properties and scenarios are read from files, the result is stored in instances of these classes. Theses instances are used to build monitors. Trace slicing and checkpointing the property are handled in the class Property. When a checkpoint is set, the property must be checkpointed at the same time as the program. In order to make a checkpoint, the whole state of the property needs to be 32 copied. Because of trace slicing, this can be costly: each slice has its own current state and its environment. In order to allow scenarios to set checkpoints and drop them with a limited overhead, we implemented checkpointing of the property using copy-on-write: the state of a slice is copied in the checkpoint only if (and just before) it is updated. Checkpointing the program is implemented in GDB using a fork, which is also copyon-write. check-exec and its monitors are controlled with the GDB command line interface using commands defined in gdbcommands.py. gdbcommands.py defines the interface between the GDB user and check-exec. Theses commands expose a part of the interface defined in the monitorinterface.py. This interface is meant to remain a stable interface to access monitors. It does not give access to the internal structures that are exposed by the Monitor class and that are not relevant for the end user. This interface is given in Appendix B. breakpoint.py defines the interface between the monitors and GDB. It defines methods to handle breakpoints in a way that is not specific to a particular debugger. graphdisplay.py defines the graphical view of running monitors. If the view is enabled, check-exec shows the property as a graph using Graphviz. As the current state of the monitor changes, the graphical view is updated: the current state is shown in green if it is accepting, in red if it is not accepting. Taken transitions are represented in brown. 4.5 Evaluation We evaluated our approach using check-exec in five scenarios in order to measure its usefulness, its efficiency and its limitations in terms of performance2 . 4.5.1 Multi-Threaded Producer-Consumers In this section, our goal is to check whether our approach is realistic in terms of usability. We evaluate the approach on this use-case: a developer works on a multi-threaded application in which a queue is filled and emptied by different threads and a segmentation fault happens in several cases. We wrote a program3 following this use-case, deliberately introducing a synchronization error, as well as a property (see Figure 1.1) on the number of additions in a queue in order to detect an overflow. The size of the queue is a parameter of the event queue_new. The function queue_push adds an element in the queue. A call to this function is awaited by the transition defined at line 15 2A video and the source codes needed for reproducing these benchmarks are available at http: //check-exec.forge.imag.fr/eval/. 3 The source code of this program can be found at http://check-exec.forge.imag.fr/eval/ prod_cons. of Figure 4.2. The program is run with check-exec. The execution stops in the state sink (defined line 39 of Figure 4.2). In the debugger, we have access to the precise line in the source code from which the function is called, as well as the complete call stack. Under certain conditions (that we artificially triggered), a mutex is not locked. After fixing this, the program behaves properly. 4.5.2 Micro-benchmark slowdown factor Overhead of property-checking at runtime with check-exec Time between two events (milliseconds) Figure 4.6: Instrumentation overhead with check-exec. The curve check-exec-arg corresponds to the evaluation of a property which retrieves an argument from calls to the monitored function. When writing a property, the developer must be aware of the overhead of the instrumentation in function of the temporal gap between events it handles. In this second experiment, we evaluate this overhead in function of the frequency of the events. We wrote a C program calling a NOP function in a loop. In order to measure the minimal gap between two monitored events for which the overhead is acceptable, we simulate the gap between these events by a loop of a configurable duration. Results of this benchmark using a Core i7-3770 @ 3.40 GHz, under Ubuntu 14.04 and Linux 3.13.0, are presented in Figure 4.6. With 0.5 ms between two events, we measured a slowdown factor of 2. Under 0.5 ms, the overhead can be significant. From 3 ms, the slowdown is under 20 % and from 10 ms, the slowdown is under 5 %. We noticed that the overhead is dominated by the process of handling breakpoints when they are encountered during the execution. The absolute overhead by monitored event, in the manner of the overhead of an argument retrieval, is constant. We measured the mean cost of encountering a breakpoint during the execution. We obtained 95 µs on the same machine and around 300 µs on a slower machine (i3-4030U CPU @ 1.90 GHz). 34 4.5.3 Multimedia Players and Video Games We evaluate our approach in a more realistic scenario, on widespread multimedia applications: the VLC and MPlayer video players and the SuperTux 2D platform video game. A property makes the monitor set a breakpoint on the function that draws each frame to the screen for these applications, respectively ThreadDisplayPicture, update_video and DrawingContext::do_drawing. For SuperTux, the function is called around 60 times per second. For the video players, it is called 24 times per second. In each case, the number of frames per second (FPS) is not affected and the CPU usage remains moderated: we get a overhead of less than 10 % by the GDB process. These results correspond to our measurements in Section 4.5.2: there is a gap of 16 ms between two function calls which is executed 60 times per second. Thus, our approach does not lead to a significant overhead for multimedia applications when the events occur at the same frequency as frames. 4.5.4 Downloading a File We measure the overhead of the instrumentation in a I/O intensive context. We download a 862 MiB file with wget from a local HTTP server using the SimpleHTTPServer class of the Python standard library. We monitor every call to the function read. The download stream is redirected to /dev/null to avoid write accesses. Without checkexec, it takes 6.0 seconds (133 MiB/s). With check-exec, it takes 27 seconds (31.4 MiB/s), corresponding to a slowdown factor of 4.5. During this download, wget does 110294 calls to read, corresponding to a mean gap of 0.05 ms between two events. These results are similar to those obtained and described in Section 4.5.2. 4.5.5 Opening and Closing Files, Iterators We evaluate the overhead with widespread applications as perceived by the user. We ensure that all open files are closed with the Dolphin file manager, the NetSurf Web browser, the Kate text editor and the Gimp image editor. Despite some slowdowns, caused by frequent disk accesses, they remain usable. Likewise, we check that no iterator over hash tables of the GLib library (GHashTableIter) that is invalidated is used. Simplest applications like the Gnome calculator remain usable but strong slowdowns are observed during the evaluation of this property, event for mere mouse movements. In Chapter 6, we present possible ways to mitigate these limitations. 4.5.6 Dynamic Instrumentation on a Stack We measure the effects of the dynamic instrumentation on the performance. A program adds and removes, alternatively, the first 100 natural integers in a stack and we check that the integer 42 is taken out of the stack after being added. A first version of this property takes advantage of the dynamic instrumentation. In this version, the call to the function which removes an element is watched only when the monitor know that 42 is in the stack. A second version of the property makes the monitor watch every event unconditionally. In this example, the execution is 2.2 times faster with the first version. 36 5 Related Work To our knowledge, combining debugging and monitoring has not been done before in the literature in the way we do in this work. In this chapter, we present existing works that are related or close to our contribution in each domain. Our contribution has also been inspired by some of these works. 5.1 Monitoring Similar work has already been done in Java (e.g. the Jassda framework [3] which use CSP-like specifications, LARVA [10, 9] and JavaMOP) and other programming languages which main implementation is a virtual machine. Events, in these works, are captured using introspection and debugging capabilities of these virtual machines. JavaMOP [5] is the reference implementation by the authors of the trace slicing method used in the paper. As our implementation, it can be used to check (parameterized) properties at runtime. It applies to Java program and makes use of the monitoring features of the Java Virtual Machine, contrary to our model, which is based on a debugger and applies to a set of languages which is not restricted to the set of languages based on the JVM. Ducassé and Jahier’s work [13] is also based on event tracing. In their proposal, the debugged (C) program is run in another process than the monitor, which is connected to a tracer producing execution traces of the monitored program. However, their work is focused on trace analysis: debugging is not interactive and program’s execution remains unaffected. RiTHM [31] is an implementation of a time-triggered monitor, that is, a monitor which ensures predictable and evenly distributed monitoring overhead at run-time by handling monitoring at predictable moments in the execution. Monitoring is done by adding instrumentation generated from properties that must be evaluated to the code of the C program to monitor. RV-Monitor [29] is a efficient tool aimed at finding bugs and bad software practice in Java programs. It has been used to check simultaneously more than 150 properties written by reading the Java API documentation of common packages. 5.2 Debugging A debugger has been written to type check program written in C [28]. Their approach is roughly to tag memory cells with types and break on inconsistency (e.g., when a double is stored in a cell pointed by a int* pointer). Valgrind is a framework to instrument binaries and check them for defects. It provides a way to detect memory related defects by a dynamic binary recompilation and instrumentation process of the software’s machine code and by running it on a simulated CPU [33]. It provides a more comprehensive detection of memory related defects than our approach [32], however our approach can be used to detect certain memory leaks more efficiently by writing a rule which checks that each manually allocated memory block is freed. SLAM is a project aimed at checking good API usage. This project is restricted to system softwares, mainly drivers, on Windows. Unlike our work, it is based on static analyzing. [1, 2] Our work brings improvements and new possibilities over existing approaches. Basing our development on a debugger like GDB allows potential support for multiple programming languages and avoid the necessity of learning a completely new tool and the need to change existing workflow. The visual representation of the property during the execution can ease program debugging or monitoring. Interactive debugging is also possible: our tool can check properties during the program’s execution and let the user inspect the program’s internal state with the debugger when a property breaks. 38 6 Conclusion and Future Work 6.1 Conclusion This report presents an approach combining monitoring and debugging as two complementary approaches to program correctness. In monitoring, the program receives and outputs events. Properties on these events are verified or enforced. Detecting a bug is possible: if a property on the correctness of the program breaks at runtime, a bug is present in the program. However, a limitation of monitoring is that it does not provide a way to understand bugs. In debugging, the program has an internal state that can be studied and modified. Interactive debugging is a way to understand a bug and find its cause. However, debugging has no support for bug discovery: a programmer uses a debugger at a time when the bug is already known. Our approach aims at taking the best of both techniques by seeing the program as a system that can be monitored to find bugs and, at the same time, as a system that can be debugged interactively to understand the bugs that were found. When a bug is found using monitoring, the debugger can be used in a traditional way to understand it. In this report, we described this approach in details, we provided a theoretical framework that eases the reasoning about the notion of join execution of the monitor, the debugger and the program. We also presented check-exec, a proof-of-concept implementation of this approach and used this implementation to evaluate our approach. Our experiments showed that though the property checker can slow down the execution of the program considerably when events are temporally close to each other, performance are acceptable beyond a reasonable threshold (Section 4.5.2). We found that this approach is applicable in realistic use-cases with software like video games and video players 4.5.3. Our current implementation shows limitations in terms of performance under other use-cases. In the next section, we present ideas to mitigate this issue. The results presented in this report open several interesting perspectives described in the next section. 6.2 Future Work In this section, we present some perspectives opened by this work. We would like to leverage possibilities offered by watchpoints, diversify supported event types, explore other ways of instrumenting the execution, explore possibilities opened by checkpoints and work on the validation of our approach. Watchpoints. We implemented basic support for watchpoints in check-exec. The next step is to experiment with them and find out how we could take advantage of them. This would, for instance, let us monitor read and write accesses to a C array and look for illegal modifications while iterating over it using an iterator. However, software watchpoints are slow, according to [19] and our preliminary experiments. We need to find a way to monitor a large number of addresses efficiently. Event Types. Our main event type is the function call. A way to make our approach more powerful is to find and include other kinds of events in our model. System calls are an example of event type we have not taken in account yet for technical reasons. They might be of interest for checking properties on drivers or programs dealing with hardware. Instrumentation. Handling breakpoints is costly [4]. Other ways of instrumenting could provide better efficiency and other kinds of events. Instrumentation could be done using code injection [31, 30]. Injecting a part of the property checker in the monitored program could improve performance by avoiding round trips between the debugger and the program: communications between the debugger and the program would be limited to the bare minimum (for example, when the scenario requires the execution to be suspended to let the user interact with the debugger), while keeping the current flexibility of the approach. Checkpoints. We implemented basic support for checkpoints, allowing the developer to restore a previous state of the execution. We also implemented scenarios. The next step is to take advantage of both checkpoints and scenarios. A possibility is to define scenarios that detect the right moments to set checkpoints and to possibly restore them. One could, for instance, write a scenario that sets checkpoints whenever the evaluation of the property enters a state that is next to a non-accepting state. More generally, the possibility offered by scenarios should be explored more in depth. Record and replay. Record and replay is a powerful technique for finding bugs. Once a buggy execution is recorded, the bug can be studied and observed again by running the recording. The recording can be inspected inside a debugger like a regular execution. It might be interesting to combine our approach with reverse debugging. 40 Validation of the Approach. For the moment, our approach has been evaluated on small, simple examples. Next step is to validate it in a more concrete situation, by finding a real bug in a widespread application with it and show that it indeed eases both discovery and understanding of the bug. An obstacle to this step is that one needs to be familiar with the code of the application that is involved in order to write properties that are of interest to developers. We consider using a database referencing bugs and try to find one of them with our approach. Another idea that is yet to be explored is verifying good practice rules and good API usage at runtime. We think that API designers and library writers could leverage our approach by providing properties with their APIs and their libraries. This would provide a means to check that their APIs are used correctly and make their usage safer. This would also be a means to document these APIs and these libraries. A check-exec Command List check-exec activate check-exec checkpoint check-exec check-exec check-exec check-exec check-exec checkpoint-restart cmd-group-begin cmd-group-end delete exec check-exec get-current check-exec load-functions check-exec load-property check-exec load-scenario check-exec new check-exec run check-exec run-with-program check-exec set-current check-exec show-graph Activates all the commands monitor related commands Sets a checkpoint for the program and each managed monitor Restores a checkpoint Begins a group of commands Ends a group of command Deletes a monitor Executes an action in the current monitor. Can be used to call methods of the current monitor’s interface defined in Appendix B Prints the name of the current monitor Loads a user defined functions file Loads a property file and possibly a function file in the given monitor Loads a property file and possibly a function file in the given monitor Creates a monitor that will also become the current monitor Runs the monitor Running the monitor and the program at the same time Sets the current monitor Shows the graph of the monitor in a window and animates it at runtime B Check-exec’s Monitor Interface The following is the documentation of the MonitorInterface class. Its methods can be used progammatically, some of them can be used from GDB’s shell using the check-exec exec command. For instance, check-exec exec get_current_states prints the current states of the current monitor. debugger_shell(self) Raises an exception, making check-exec interrupt the execution so the user can interract with the debugger’s shell. get_current_states(self) Returns the set of current states of the property. WARNING: Follow the same precautions as for the get_env_dict() method. get_env_dict(self, s) Returns the Python dictionary representing the environment of the property. WARNING: It is unspecified whether a modification to this dictionary will be reflected in the environment, and whether a modification to the environment will be reflected in the dictionary. Call get_env_dict() each time you need to get the most up-to-date environment. If needed, use copy() to be sure to keep a static version of the environment correponding to the moment when this method is called, and the set_env_dict() and set_env_value() methods to modify the environment. get_env_keys(self, s, as_iterator=True) Returns the keys of the property’s environment, as an iterator or a list, whether as_iterator is True or False, repectively. get_env_value(self, s, key) Returns the value of the given variable in the property’s environment. Raises if the key is not present in the environment. get_states(self) Returns the set of the states of the property. WARNING: Follow the same precautions as for the get_env_dict() method. print_monitor_state(self) Prints the monitor’s current state. register_event(self, event_type, callback) Register a callback for this event type. Possible events: - state_changed(new_states) new_states is the set of the new current states - transition_taken(transition, point) transition is the object representing the transition point is either "success" or "failure" - event_applied The event type is given by its name and the parameters passed to the callback is what is given in parenthesis. set_current_state(self, state) Sets the current state of (the root slice of) the property. set_env_dict(self, s, new_env) Sets the environment of the property. WARNING: Please don’t modify the given dictionary after passing it to this method. This causes undefined behavior. Use copy() if needed when passing the dictionary to set_env_dict(). new_env is not guaranteed to be a reference to the actual new dictionary set_env_value(self, s, key, value) 46 Sets the value of the given variable in the property’s environment. set_globals(self, g) Sets the dictionary in which functions will be found, if needed, when e.g. calling (un)register_event. % set_quiet(self, b=’True’) Sets the monitor quiet or not. set_transition_debug_function(self, fun_name=None) Specifies a user’s function to call whenever a monitored action not taken in account in the current states of the property is called. no argument means the default: no user function is called when it happens. step_by_step(self, b=’True’) Set step by step monitor stop_execution(self) Raises an exception making the execution of the monitor stop the program’s and the monitor’s execution. unregister_event(self, event_type, callback) Unregister a callback for this event type. See also register_event. Some commands are not accessible from check-exec exec: get_env_dict, set_globals, get_env_keys, stop_execution, set_current_states, debugger_shell, set_env_dict. C Executing an Example of a Program We run the program in Figure C.1 while checking the property in Figure C.2. We don’t use any scenario. The initial state of the system is ((m p,0 , pc0 = 5), (I, ε, ε), (Init, [], ε). We first load the monitor (Rule 3.5.1). A breakpoint is set on function f because the there is a transition from the initial state which can be taken if f is called. ((m p,0 , pc0 = 5), (I, ε, ε), (Init, [], ε) load monitor −−−−−−−→ ((m p,1 , pc0 = 5), (I, [(2, nop, false)], ε), (Init, [], ε) In the current memory of the program m p,1 , the nop instruction of the program has 1 2 3 4 5 6 7 8 function f ( ) : nop function g ( ) : nop a := 1 ( t h i s i s the f i r s t f () g() stop i n s t r u c t i o n of t h e program ) Figure C.1: A toy program Init before call to f has_f before call to g has_g Figure C.2: A toy property that do not use any memory. been replaced by the instruction B. The breakpoint has been saved in the memory of the debugger. The users makes the program run by itself by issuing the command continue. Rule 3.5.3 applies. The debugger enters passive mode. ((m p,1 , pc0 = 5), (I, [(2, nop, false)], ε), (Init, [], ε) cmd continue −−−−−−−→ ((m p,1 , pc0 = 5), (P, [(2, nop, false)], ε), (Init, [], ε) Then, normal execution happens (Rule 3.5.4). The instruction a := 1 is run. ((m p,1 , pc0 = 5), (P, [(2, nop, false)], ε), (Init, [], ε) normal execution −−−−−−−−−→ ((m p,2 , pc1 = 6), (P, [(2, nop, false)], ε), (Init, [], ε) In m p,2 , the variable a takes the value 1. The current program counter is now at the next line. Normal execution happens again (Rule 3.5.4). The execution enters function f . ((m p,2 , pc1 = 6), (P, [(2, nop, false)], ε), (Init, [], ε) normal execution −−−−−−−−−→ ((m p,2 , pc2 = 2), (P, [(2, nop, false)], ε), (Init, [], ε) The program counter is now at line 2. The original instruction nop has been replaced by a breakpoint. Rule 3.5.9 applies. First, the breakpoint is unset by the function unsetBP. We have: Btmp , (addr, instr, isUserBP) = (ε, nop, false) Then, the function HANDLE BP is called. As the breakpoint was set by the monitor, it is not a user breakpoint, so the “else” path of the function is taken. All breakpoints relative to the current state of the monitor are unset from the current memory of the program. In our case, this refers to the only breakpoint that has been set. The bpToEvts function generates a before event e which type is FunctionCall, which name is f and which parameters and values are an empty list. The before version of the event e is applied, setting the current state of the monitor to has_f. A breakpoint on line 4 is set to monitor calls to function f. The breakpoint on line 2 does not exist anymore. The instruction nop is run and makes the program leave the function. ((m p,2 , pc2 = 2), (P, [(2, nop, false)], ε), (Init, [], ε) monitor’s breakpoint −−−−−−−−−−−→ ((m p,3 , pc3 = 7), (P, [(4, nop, false)], ε), (has_f, [], ε) The current program counter is now at line 7. Normal execution happens again (Rule 3.5.4). The execution enters function g. 50 ((m p,3 , pc3 = 7), (P, [(4, nop, false)], ε), (has_f, [], ε) normal execution −−−−−−−−−→ ((m p,4 , pc4 = 4), (P, [(4, nop, false)], ε), (has_f, [], ε) The program counter is now at line 4. The original instruction nop has been replaced by a breakpoint. Rule 3.5.9 applies. The breakpoint on line 4 is unset. The current state of the monitor becomes has_g. No breakpoints are set, because enabled(has_g) = 0. / ((m p,4 , pc4 = 4), (P, [(4, nop, false)], ε), (has_f, [], ε) monitor’s breakpoint −−−−−−−−−−−→ ((m p,5 , pc5 = 8), (P, ε, ε), (has_g, [], ε) The instruction at pc5 (Line 8) is stop, the execution stops and the configuration is final (no rules apply). D Proof (sketch): the execution of a program is not affected In this appendix, we give the sketch of a proof of Proposition 1 (the execution of a program is not affected by the property evaluation). We have the program (m p,0 , pc0 ). We prove that for any execution that terminates, (m p, f , pc f ) = (m p, f 0 , pc f 0 ) where (m p, f , pc f ) is the final state of the program after execution and (m p, f , pc f ) is the final state of the program after execution with a monitor, for any (qm,0 , mm,0 ), that is, for any monitor. We assume that the program never accesses (read or write) its own instructions. We assume that the users only loads the monitor and launches the program but does not otherwise interact with the debugger. We consider an empty scenario (S = ε). We assume that there exists an instruction stop that is the unique way to end the execution. Without the monitor. Without monitor, we consider the debugger starts in passive mode. No monitor is loaded, so rule 3.5.1 does not apply. The execution of the program will not be interrupted so rule 3.5.2 does not apply. The debugger will never be in interactive mode, so rules 3.5.5, 3.5.6, 3.5.3, 3.5.7, 3.5.8 and 3.5.10 do not apply. The debugger will not set a debugger’s instruction in the program so rule 3.5.9 does not apply. If m p,0 [pc0 ] = stop, no rules apply. In this case, (m p,0 , pc0 ) = (m p, f , pc f ). Otherwise, Rule 3.5.4 applies until (m p, f , pc f ) such that m p, f [pc f ] = stop is reached, which happens because the execution terminates. (m p,1 ,pc1 )=runInstr(m p,0 ,pc0 ) ((m p,0 , pc0 ), (P, ε, ε), M) −−−−−−−−−−−−−−−−−→ ((m p,1 , pc1 ), (P, ε, ε), M) If m p,1 [pc1 ] = stop then (m p,1 , pc1 ) = (m p, f , pc f ). Else: runInstr runInstr ((m p,1 , pc1 ), (P, ε, ε), M) −−−−→ . . . −−−−→ ((m p, f , pc f ), (P, ε, ε), M) With the monitor. We first load the monitor (with M0 = (init, mm,0 , ms,0 )): load monitor (Rule 3.5.1) ((m p,0 , pc0 ), (I, ε, ε), M0 ) −−−−−−−−−−−−−−−→ ((m0p,0 , pc0 ), (P, B, ε), M0 ) By definition of the function setBPs, m0p0 [a] is equal to m p0 [a] for any address a, except for addresses stored in B. for these addresses, m0p0 [a] = B and instructions stored in B are instructions that are at these addresses in m p,0 . We have m0p,0 = setBPs(m p,0 , ε, init, Sym). We also have m p,0 = unsetBPs(m0p,0 , B, init, Sym). We continue the execution, that is, the debugger is set in passive mode. continue ((m0p,0 , pc0 ), (I, B, ε), M0 ) −−−−−→ ((m0p,0 , pc0 ), (P, B, ε), M0 ) No monitor is loaded anymore, so rule 3.5.1 does not apply anymore. The execution of the program will not be interrupted so rule 3.5.2 does not apply. The debugger will never be in interactive mode again, so rules 3.5.5, 3.5.6, 3.5.3, 3.5.7, 3.5.8 and 3.5.10 do not apply. Rule 3.5.9 will apply when a breakpoint is reached. Otherwise, Rule 3.5.4 (normal execution) applies. The system is now in the state ((m0p,k , pck ), (P, B, ε), (qk , mm,k , ms,k )). We prove that m p,k = unsetBPs(m p p, k0 ) and ∀a ∈ Address, m p,k [a] 6= B. For k = 0 (initial case), we already have m p,0 = unsetBPs(m0p,0 , B, init, Sym) As m0 is the initial memory of the program, no B is present in the memory. For k 6= 0, we suppose that m p,k−1 = unsetBPs(m0p,k−1 , B, qk−1 , Sym) ∧ ∀a ∈ Address, m p,k−1 [a] 6= B (D.0.1) We want to prove that m p,k = unsetBPs(m0p,k , B, qk , Sym) ∧ ∀a ∈ Address, m p,k [a] 6= B Case: m0p,k−1 [pck − 1] = B. In this case, Rule 3.5.9 applies: the breakpoint is handled. The function POP BP gets the instruction that was replaced by the instruction B. This instruction is not an instruction B. This would be the case if more than one breakpoints were set at the same address. This is impossible because of D.0.1 (unsetBPs removes breakpoints needed to monitor events of enabled(qk−1 ) and there are no breakpoints in the resulting program’s memory). The function APPLY E VENT removes all the breakpoints from the program, updates the monitor, runs the instruction returned by the function POP BP and set breakpoints for the new current state. Therefore, m p,k = unsetBPs(m0p,k , B, qk , Sym) does not contain any breakpoint 54 where m0p,k is the result of the application of Rule 3.5.9. As we consider an empty scenario, The system is not affected in any other way. The effect on the program’s execution is therefore transparent. When m0p,k−1 [pck ] = B, the original program’s instruction is run with the same data as an execution without the monitor. Case: m0p,k−1 [pck ] 6= B. In this case, Rule 3.5.4 applies: the instruction is executed normally. ((m0p,k−1 , pck−1 ), (P, B, ε), (qk−1 , mm,k−1 , ms,k−1 )) → ((m0p,k , pck ), (P, B, ε), (qk , mm,k , ms,k )) According to Rule 3.5.4, qk = qk−1 . Assuming that the program does not modify its own instructions (so the instruction that have just been executed did not modify the code of the program), m0p,k contains the same set of addresses where there is an instruction B as m0p,k−1 . Therefore, m p,k−1 = unsetBPs(m0p,k−1 , B, qk−1 , Sym) does not contain any breakpoint implies m p,k = unsetBPs(m0p,k , B, qk , Sym) does not contain any breakpoint Supposing that the program does not modify or read its own instructions, we have (m p,k , pck ) = runInstr(m p,k−1 , pck−1 ) That is, the execution is similar regardless of the presence of breakpoints in the program’s memory. In both cases, original instructions get executed in order without effect from the monitor. The whole execution with a monitor is thus equivalent to the execution without a monitor. Bibliography [1] Thomas Ball, Ella Bounimova, Byron Cook, Vladimir Levin, Jakob Lichtenberg, Con McGarvey, Bohus Ondrusek, Sriram K. Rajamani, and Abdullah Ustuner. Thorough static analysis of device drivers. In Proceedings of the 1st ACM SIGOPS/EuroSys European Conference on Computer Systems 2006, EuroSys ’06, pages 73–85, New York, NY, USA, 2006. ACM. [2] Thomas Ball and Sriram K. Rajamani. The slam project: Debugging system software via static analysis. In Proceedings of the 29th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, POPL ’02, pages 1–3, New York, NY, USA, 2002. ACM. [3] Mark Brörkens and Michael Möller. Dynamic event generation for runtime checking using the JDI. Electr. Notes Theor. Comput. Sci., 70(4):21–35, 2002. [4] Martial Chabot, Kévin Mazet, and Laurence Pierre. Automatic and configurable instrumentation of C programs with temporal assertion checkers. In 13. ACM/IEEE International Conference on Formal Methods and Models for Codesign, MEMOCODE 2015, Austin, TX, USA, September 21-23, 2015, pages 208– 217, 2015. [5] Feng Chen and Grigore Rosu. Mop: an efficient and generic runtime verification framework. In Proceedings of the 22nd Annual ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications, OOPSLA 2007, October 21-25, 2007, Montreal, Quebec, Canada, pages 569–588, 2007. [6] Feng Chen and Grigore Rosu. Parametric trace slicing and monitoring. In Tools and Algorithms for the Construction and Analysis of Systems, 15th International Conference, TACAS 2009, Held as Part of the Joint European Conferences on Theory and Practice of Software, ETAPS 2009, York, UK, March 22-29, 2009. Proceedings, pages 246–261, 2009. [7] Yoonsik Cheon and Gary T. Leavens. A simple and practical approach to unit testing: The JML and junit way. In ECOOP 2002 - Object-Oriented Programming, 16th European Conference, Malaga, Spain, June 10-14, 2002, Proceedings, pages 231–255, 2002. [8] Edmund M. Clarke, Orna Grumberg, and Doron A. Peled. Model checking. MIT Press, 2001. [9] Christian Colombo, Adrian Francalanza, and Rudolph Gatt. Elarva: A monitoring tool for erlang. In Sarfraz Khurshid and Koushik Sen, editors, Runtime Verification - Second International Conference, RV 2011, San Francisco, CA, USA, September 27-30, 2011, Revised Selected Papers, volume 7186 of Lecture Notes in Computer Science, pages 370–374. Springer, 2011. [10] Christian Colombo, Gordon J. Pace, and Gerardo Schneider. LARVA — safer monitoring of real-time Java programs (tool paper). In Seventh IEEE International Conference on Software Engineering and Formal Methods, SEFM 2009, Hanoi, Vietnam, 23-27 November 2009, pages 33–37, 2009. [11] Patrick Cousot and Radhia Cousot. Abstract interpretation: A unified lattice model for static analysis of programs by construction or approximation of fixpoints. In Conference Record of the Fourth ACM Symposium on Principles of Programming Languages, Los Angeles, California, USA, January 1977, pages 238–252, 1977. [12] Philip Daian, Yliès Falcone, Patrick O’Neil Meredith, Traian-Florin Serbanuta, Shin’ichi Shiriashi, Akihito Iwai, and Grigore Rosu. Rv-android: Efficient parametric android runtime verification, a brief tutorial. In Ezio Bartocci and Rupak Majumdar, editors, Runtime Verification - 6th International Conference, RV 2015 Vienna, Austria, September 22-25, 2015. Proceedings, volume 9333 of Lecture Notes in Computer Science, pages 342–357. Springer, 2015. [13] Mireille Ducassé and Erwan Jahier. Efficient automated trace analysis: Examples with morphine. Electr. Notes Theor. Comput. Sci., 55(2):118–133, 2001. [14] Jakob Engblom. A review of reverse debugging. In System, Software, SoC and Silicon Debug Conference (S4D), 2012, pages 1–6. IEEE, 2012. [15] Yliès Falcone. Étude et mise en œuvre de techniques de validation à l’exécution. PhD thesis, 2009. [16] Yliès Falcone. You should better enforce than verify. In Howard Barringer, Yliès Falcone, Bernd Finkbeiner, Klaus Havelund, Insup Lee, Gordon J. Pace, Grigore Rosu, Oleg Sokolsky, and Nikolai Tillmann, editors, Runtime Verification First International Conference, RV 2010, St. Julians, Malta, November 1-4, 2010. 58 Proceedings, volume 6418 of Lecture Notes in Computer Science, pages 89–105. Springer, 2010. [17] Kiril Georgiev and Vania Marangozova-Martin. Mpsoc zoom debugging: A deterministic record-partial replay approach. In 12th IEEE International Conference on Embedded and Ubiquitous Computing, EUC 2014, Milano, Italy, August 26-28, 2014, pages 73–80, 2014. [18] Klaus Havelund and Allen Goldberg. Verify your runs. In Bertrand Meyer and Jim Woodcock, editors, Verified Software: Theories, Tools, Experiments, First IFIP TC 2/WG 2.3 Conference, VSTTE 2005, Zurich, Switzerland, October 10-13, 2005, Revised Selected Papers and Discussions, volume 4171 of Lecture Notes in Computer Science, pages 374–383. Springer, 2005. [19] Laurie J. Hendren, editor. Compiler Construction, 17th International Conference, CC 2008, Held as Part of the Joint European Conferences on Theory and Practice of Software, ETAPS 2008, Budapest, Hungary, March 29 - April 6, 2008. Proceedings, volume 4959 of Lecture Notes in Computer Science. Springer, 2008. [20] Juha Itkonen, Mika V Mäntylä, and Casper Lassenius. Defect detection efficiency: Test case based vs. exploratory testing. In First International Symposium on Empirical Software Engineering and Measurement, pages 61–70. IEEE, 2007. [21] Juha Itkonen, Mika V Mantyla, and Casper Lassenius. How do testers do it? an exploratory study on manual testing practices. In Proceedings of the 3rd International Symposium on Empirical Software Engineering and Measurement, pages 494–497. IEEE Computer Society, 2009. [22] Raphaël Jakse, Jean-François Méhaut, and Kevin Pouget. Vérification interactive de propriétés à l’exécution d’un programme avec un débogueur. In Acte de la conférence COMPAS, 2016. [23] Dongyun Jin, Patrick O’Neil Meredith, Choonghwan Lee, and Grigore Rosu. Javamop: Efficient parametric runtime monitoring framework. In Martin Glinz, Gail C. Murphy, and Mauro Pezzè, editors, 34th International Conference on Software Engineering, ICSE 2012, June 2-9, 2012, Zurich, Switzerland, pages 1427–1430. IEEE Computer Society, 2012. [24] Shmuel Katz. Transactions on aspect-oriented software development i. chapter Aspect Categories and Classes of Temporal Properties, pages 106–134. SpringerVerlag, Berlin, Heidelberg, 2006. [25] Gregor Kiczales. AspectJ(tm): Aspect-oriented programming in Java. In Mehmet Aksit, Mira Mezini, and Rainer Unland, editors, Objects, Components, Architectures, Services, and Applications for a Networked World, International Conference NetObjectDays, NODe 2002, Erfurt, Germany, October 7-10, 2002, Revised Papers, volume 2591 of Lecture Notes in Computer Science. Springer, 2002. [26] William Landi. Undecidability of static analysis. ACM Letters on Programming Languages and Systems (LOPLAS), 1(4):323–337, 1992. [27] Martin Leucker and Christian Schallhart. A brief account of runtime verification. J. Log. Algebr. Program., 78(5):293–303, 2009. [28] Alexey Loginov, Suan Hsi Yong, Susan Horwitz, and Thomas W. Reps. Debugging via run-time type checking. In Fundamental Approaches to Software Engineering, 4th International Conference, FASE 2001 Held as Part of the Joint European Conferences on Theory and Practice of Software, ETAPS 2001 Genova, Italy, April 2-6, 2001, Proceedings, pages 217–232, 2001. [29] Qingzhou Luo, Yi Zhang, Choonghwan Lee, Dongyun Jin, Patrick O’Neil Meredith, Traian-Florin Serbanuta, and Grigore Rosu. Rv-monitor: Efficient parametric runtime verification with simultaneous properties. In Borzoo Bonakdarpour and Scott A. Smolka, editors, Runtime Verification - 5th International Conference, RV 2014, Toronto, ON, Canada, September 22-25, 2014. Proceedings, volume 8734 of Lecture Notes in Computer Science, pages 285–300. Springer, 2014. [30] Reed Milewicz, Rajeshwar Vanka, James Tuck, Daniel Quinlan, and Peter Pirkelbauer. Lightweight runtime checking of C programs with RTC. Computer Languages, Systems & Structures, 45:191–203, 2016. [31] Samaneh Navabpour, Yogi Joshi, Chun Wah Wallace Wu, Shay Berkovich, Ramy Medhat, Borzoo Bonakdarpour, and Sebastian Fischmeister. Rithm: a tool for enabling time-triggered runtime verification for C programs. In Bertrand Meyer, Luciano Baresi, and Mira Mezini, editors, Joint Meeting of the European Software Engineering Conference and the ACM SIGSOFT Symposium on the Foundations of Software Engineering, ESEC/FSE’13, Saint Petersburg, Russian Federation, August 18-26, 2013, pages 603–606. ACM, 2013. [32] Nicholas Nethercote and Julian Seward. How to shadow every byte of memory used by a program. In Chandra Krintz, Steven Hand, and David Tarditi, editors, Proceedings of the 3rd International Conference on Virtual Execution Environments, VEE 2007, San Diego, California, USA, June 13-15, 2007, pages 65–74. ACM, 2007. 60 [33] Nicholas Nethercote and Julian Seward. Valgrind: a framework for heavyweight dynamic binary instrumentation. In Proceedings of the ACM SIGPLAN 2007 Conference on Programming Language Design and Implementation, pages 89–100, 2007. [34] Jean-François Roos, Luc Courtrai, and Jean-François Méhaut. Execution replay of parallel programs. In 1993 Euromicro Workshop on Parallel and Distributed Processing, PDP 1993, Gran Canaria, Spain, 27-29 January 1993, pages 429– 434, 1993. [35] Oleg Sokolsky, Klaus Havelund, and Insup Lee. Introduction to the special section on runtime verification. International Journal on Software Tools for Technology Transfer, 14(3):243–247, 2012.