The invention relates to software debugging. More specifically, the invention relates to debugging multi-threaded applications running on multiprocessor systems.
A traditional model of computer operation involves a single programmable processor executing a sequence of instructions contained in a memory to perform various operations on data. (In the taxonomy proposed by Michael Flynn in his 1972 paper, “Some Computer Organizations and Their Effectiveness,” IEEE Trans. Comput., Vol. C-21, pp. 94, this is a “single-instruction, single-data” or “SISD” model.) Debugging a SISD program is straightforward: one can set a breakpoint to stop the program if execution reaches a particular location, or if the program performs a particular operation, and examine the state of the program at that time.
However, many contemporary computer systems have multiple processors that can execute a corresponding number of instruction streams concurrently, operating on data that may be shared (“multiple-instruction, multiple-data” or “MIMD” systems). Concurrently-executing instruction sequences operating in a shared memory arena are commonly called “threads.” Debugging threaded programs may be more difficult because although a breakpoint can stop any particular thread at a selected instruction, other threads may continue to execute. If the other threads alter shared memory state, an engineer examining the memory contents may find inconsistent or confusing data. Even thread-aware debuggers that provide “global suspension” to stop related threads when one thread reaches a breakpoint cannot guarantee that the related threads will stop before altering shared memory.
Techniques to ensure memory state consistency when debugging threaded programs may be of value in this field.
Embodiments of the invention are illustrated by way of example and not by way of limitation in the figures of the accompanying drawings in which like references indicate similar elements. It should be noted that references to “an” or “one” embodiment in this disclosure are not necessarily to the same embodiment, and such references mean “at least one.”
Embodiments of the invention use software and/or hardware techniques to ensure state consistency when debugging multi-threaded applications. When one thread reaches a breakpoint, it produces a signal that can be sensed by other threads. Other threads respond to the signal by stopping before they alter, or even before they access, shared memory.
Threads 1, 3 and 4 may access or modify shared memory while they are executing. References and modifications that occur before breakpoint 110 (e.g. memory write 105 and memory read 108) do not change memory in a way that may be unexpected, but memory writes that occur after breakpoint 110 (time 115) (e.g. memory writes 125, 140 and 165) may change memory so that an engineer cannot determine what was the program's state at time 115. Memory reads 130 and 135, as well as “other operations,” (unlabeled), do not alter memory contents, and so may be permitted.
Note that the length of time between the breakpoint at 115 and all threads stopping at 180 may be quite short—perhaps on the order of tenths or hundredths of milliseconds—but modern processors can execute millions of instructions in that short time, so there is ample opportunity for confusing memory changes to occur.
An embodiment of the invention can improve on the situation shown in
As before, thread 2102 encounters breakpoint 110 at time 115. Processing in the thread context or in the debugger sets breakpoint flag 200 in the shared memory. When threads 1101, 3103 and 4104 execute the next instrumented memory read or write within their respective instruction sequences, the instrumentation code detects the set breakpoint flag and stops the thread before the read or write is performed. These are shown as “sympathetic breakpoints” 225, 230 and 240.
Sibling threads may execute for some time after breakpoint 110 is encountered at time 115 (in this example, thread 1101 performs “other operation” 210) but because memory writes and, optionally, memory reads are instrumented, memory state 120 will not be changed after time 115 and the debugging work may be simplified.
A breakpoint is a generic term for a facility that can interrupt or pause the execution of a thread if certain conditions arise. The simplest sort of breakpoint causes the thread to stop if execution reaches a particular instruction. Other types of breakpoints can cause the program to stop if an instruction reads a particular memory location, or if an instruction attempts to alter the contents of a memory location. Some debugging environments provide additional controls, so that a breakpoint is only triggered on the second (or other subsequent) occurrence of an event, only if a particular value is stored in a memory location, or only upon the occurrence of another combination of conditions.
After the program is launched, it may spawn several threads to perform various tasks (330). These “sibling” threads 335 execute concurrently with each other and with the first thread, and share at least a portion of their memory spaces.
When one of the threads pauses at a breakpoint (340), a breakpoint handler sets a global breakpoint flag (345). Since multiple breakpoints may have been set in the various threads, writes to the global breakpoint flag should be protected by synchronization code. The debugging environment may wait for some or all sibling threads to stop before permitting the user to examine the execution environment (350).
In the meanwhile, another thread may begin a memory access (355). If the access is instrumented, the instrumentation code will examine the breakpoint flag (360), and if it is set (365), this thread will pause as well (375). If the flag is clear, the memory access proceeds as usual (370).
Once some or all of the sibling threads have stopped, the debugger permits the user to examine the program state (380). In some cases, the user may also be permitted to modify the program state (for example, to test the program's response to a specific set of conditions). Finally, the debugger un-pauses the threads and execution resumes (385).
By operating a program within a debugging environment as described above, one can ensure that a multi-threaded program's global state is consistent when a breakpoint is recognized and thus avoid some confusion and wasted effort during software development and debugging. However, instrumenting a program may increase its size or alter its execution patterns, thereby changing its behavior. In some embodiments, hardware facilities of a system may be employed instead of instrumentation to achieve the same consistent shared memory state during debugging.
A processor may include an interrupt logic unit 440 to manage various interrupt sources, including thread breakpoint logic 445, which may provide an interrupt if bus snooping logic detects a breakpoint signal from another processor that is executing a thread related to the thread currently executing in processor 400. (Some embodiments may omit this comparison logic and simply interrupt the processor if any other processor signals that it has reached a breakpoint).
Processors in a system according to an embodiment of the invention may be physically separate units, or may be sub-components of multi-processor packages. Some processors may include multiple “execution cores,” or instruction execution units and associated logic and state information, which may share other support and/or control logic. Systems containing processors, CPUs, and/or execution cores that can concurrently execute instructions from a plurality of instruction sequences may benefit from embodiments of the invention. This sort of concurrent execution should be distinguished from pseudo-concurrency that may be simulated on a uniprocessor system through techniques such as time-slicing.
A system including the hardware support structures shown in
A multi-threaded program is loaded into memory (510) and the processors begin executing its threads (520). If there are more threads than processors, some of the threads may be time-sliced, but at least some instructions of some threads are executed concurrently on different processors or execution cores.
When one thread reaches a breakpoint (530), the system pauses its execution (540) and signals that a thread breakpoint has occurred (550). The signal may be a hardware signal, breakpoint bus cycle, or similar mechanism, as discussed above. The bus snooping logic on other processors in the system detects the breakpoint signal (560) and pauses the execution of thread instructions on those processors (580). In some embodiments, thread breakpoint logic may compare an identity of the thread that reached a breakpoint to the identity of a thread executing on the processor, and only pause instruction execution if the threads are related (570). Debugging system logic permits the user to examine and/or modify the thread state (590), then execution may be resumed (599).
An embodiment of the invention may be a machine-readable medium having stored thereon instructions which cause a processor to perform operations as described above. For example, an embodiment of the invention may be implemented in a compiler to translate source code into executable machine instructions; the embodiment could instrument global (or all) memory writes (or all accesses). Another embodiment in the form of a machine-readable medium may be a debugging environment to instrument pre-compiled code before executing it.
A machine-readable medium may include any mechanism for storing or transmitting information in a form readable by a machine (e.g., a computer), including but not limited to Compact Disc Read-Only Memory (CD-ROMs), Read-Only Memory (ROMs), Random Access Memory (RAM), Erasable Programmable Read-Only Memory (EPROM), and a transmission over the Internet.
The applications of the present invention have been described largely by reference to specific examples and in terms of particular allocations of functionality to certain hardware and/or software components. However, those of skill in the art will recognize that consistent thread global state for simpler debugging can also be achieved by software and hardware that distribute the functions of embodiments of this invention differently than herein described. Such variations and implementations are understood to be captured according to the following claims.