Like an ordinary debugger, Simics can run user binaries, allowing the user to set breakpoints, inspect state, single step, etc. Some difficult bugs are easier to find using various esoteric breakpoint types. In Simics you can set breakpoints on:
Simics is fully deterministic, allowing you to narrow down the location of difficult bugs. If your session has interactive input, you can record it using the recorder and replay when you need to reproduce the same execution. If Hindsight is available, you can of course freely go forward and backward in time until you found the location of the problem.
A memory breakpoint stops the simulation whenever a memory location in a specified address interval is accessed. The address interval can be of arbitrary length and the type of the memory access can be specified as any combination of read, write, and execute.
Physical memory breakpoints refer to addresses within a memory space, so the breakpoint itself is always connected to a specific memory space object in Simics. If this object is known by name (as phys_mem0 in the following example), the breakpoint can be set with the break command:
simics> phys_mem0.break address = 0x10000 length = 16 -w Breakpoint 1 set on address 0x10000, length 16 with access mode 'w'
Virtual memory breakpoints are handled by context objects:
simics> primary_context.break 0x1ff00 Breakpoint 1 set on address 0x1ff00 with access mode 'x'
Note that by default, all simulated processors in one Simics process share one context. If you want a virtual breakpoint to apply only to a subset of the processors, create a new context just for them:
simics> new-context foo simics> cpu1.set-context foo simics> cpu7.set-context foo simics> foo.break 0xffffffffbfc008b8
The break command can also be used without explicitly specifying an address space or context object. Instead you can prefix the address with p: for a physical address, or v: for a virtual address. The breakpoint will refer respectively to the memory space (physical address) or context (virtual address) connected to the current front-end processor (as specified with the pselect command). Note that, unless you have created a new context for the current processor, the breakpoint will apply to all processors.
As you can see in the following example, Simics interprets a breakpoint address as virtual unless p: is explicitly specified:
simics> break v:0x4711 Breakpoint 2 set on address 0x4711 with access mode 'x' simics> break p:0x4711 Breakpoint 3 set on address 0x4711 with access mode 'x' simics> break 0x4711 Breakpoint 4 set on address 0x4711 with access mode 'x' Note: overlaps with breakpoint 2
Execution breakpoints can be modified with filter rules to only trigger when instructions match certain syntactical criteria. This feature is mainly useful with breakpoints covering large areas of memory. The commands available are set-prefix (to match the start of an instruction), set-substr (to match a particular substring), and set-pattern (to match the bit pattern of the instruction). The commands work by modifying an existing breakpoint, so you first have to set an execution breakpoint and then modify it to match only particular expressions.
For example, to stop when an instruction with the name add is executed in a memory range from 0x10000 to 0x12000, use the following commands:
simics> break 0x10000 0x2000 -x Breakpoint 1 set on address 0x10000, length 8192 with access mode 'x' simics> set-prefix 1 "add"
Simics will stop when the first add instruction is encountered. For more information, see the Simics Reference Manual or use the help break command.
Unlike an ordinary debugger, Simics can handle temporal breakpoints, i.e., breakpoints in time. As the concept of time is based on steps and cycles, a temporal breakpoint refers to a specific step or a cycle count for a given processor object:
simics> cpu0.cycle-break 100 simics> cpu0.step-break 100
In the example above, the breakpoints are specified relative to the current time. It is also possible to set temporal breakpoints in absolute time (where 0 refers to the time when the original configuration was set up in Simics). When Hindsight is available, you can freely set time breakpoints in the past as well as in the future.
simics> cpu0.cycle-break-absolute 100 simics> cpu0.step-break-absolute 100
All the commands cycle-break, step-break, cycle-break-absolute, and step-break-absolute, can be given without prefixing them with the CPU. Note that the in this case the commands will plant a breakpoint for current front-end processor (and not all processors).
A control register breakpoint is triggered when a selected control register is accessed. The control register is specified either by name or number, and the access type can be any combination of read or write. For example:
simics> break-cr reg-name = asi
Note that the exact arguments to this command depend on the target architecture. A list of available control registers can be obtained by tab-completing the reg-name argument. See the documentation for break-cr in the Simics Reference Manual for more information..
An I/O breakpoint is always connected to a specific device object. The breakpoint is triggered when that device is accessed. The breakpoint is set using the break-io command, which take the device name as a parameter. For example, to break on accesses to the hme0 device, we would use the following syntax:
simics> break-io object-name = hme0
A list of devices can be obtained by tab-completing the object-name argument.
The graphics-console can be used to save and set graphical breakpoints. A graphical breakpoint is a rectangular area on the simulated display that triggers a hap (Gfx_Break_String) whenever the pixels inside the saved breakpoint rectangle exactly match those on the display.
The following commands can be used to save and set breakpoints for a graphics console:
The text console can set breakpoints on the occurrence of certain character sequences in the output sent to the screen.
For each simulated processor architecture, a special no-operation instruction has been chosen to be a magic instruction for the simulator. When the simulator executes such an instruction, it triggers a Core_Magic_Instruction hap and calls all the callbacks functions registered on this hap (see chapter 8 to get more information about haps).
If the architecture makes it possible, an immediate value is encoded in the magic instruction. When the hap is triggered, this value is passed as an argument to the hap handlers. This provides the user with a rudimentary way of passing information from the simulated system to the hap handler.
Magic instructions have to be compiled in the binary files that are executed on the target. The file magic-instruction.h in [simics]/src/include/simics/ defines a MAGIC(n) macro that can be used to place magic instructions in your program, where n is the immediate value to use.
A complete definition of magic instructions and the values the parameter n can take is provided in figure 3.
| ||||||||||||||||||||||||||||||
Figure 3. Magic instructions for different Simics Targets |
Here is a simple pseudo-code example:
#include "magic-instruction.h" int main(int argc, char **argv) { initialize(); MAGIC(1); tell the simulator to start the cache simulation do_something_important(); MAGIC(2); tell the simulator to stop the cache simulation clean_up(); }
This code needs to be coupled with a callback registered on the magic instruction hap to handle what happens when the simulator encounters a magic instruction with the arguments 1 or 2 (in this example, to start and stop the cache simulation).
Simics implements a special handling of magic instructions called magic breakpoints. A magic breakpoint occurs if magic breakpoints are enabled and if the parameter n of a magic instruction matches a special condition. When a magic breakpoint is triggered, the simulation stops and returns to prompt.
Magic breakpoints can be enabled and disabled with the commands magic-break-enable and magic-break-disable . The condition on n for a magic instruction to be recognized as a magic breakpoint is the following:
n == 0 || (n & 0x3f0000) == 0x40000
Note that the value 0 is included for architectures where no immediate can be specified. The file magic-instruction.h defines a macro called MAGIC_BREAKPOINT that places a magic instruction with a correct parameter value in your program.
On architectures that only offer a single magic instruction (x86 and Alpha), more information can be passed from the simulation to the magic instruction hap handler by putting data values in machine registers prior to triggering the magic instruction.
As a concrete example, on the x86, the hap argument can only be 0. This can be worked around by putting extra information into register eax before executing the MAGIC(0) magic instruction. The hap handler for magic instructions then needs to read the value from eax and do different things depending on its contents.
The following is an example program implementing this technique, using the gcc compiler's syntax for inserting inline assembler:
#include "stdio.h" #include "magic-instruction.h" #define MY_MAGIC(n) do { \ asm volatile ("movl %0, %%eax" : : "g" (n) : "eax"); \ MAGIC(0); \ } while (0) int main(void) { printf("Hello,\n"); MY_MAGIC(1); printf("World!\n"); MY_MAGIC(2); return 0; }
The hap handler for this would look something like the following:
@def call_back_1(cpu): pr("call back one triggered\n") @def call_back_2(cpu): pr("another one here\n") @def hap_callback(user_arg, cpu, arg): eax = cpu.eax # read value passed from program if eax == 1: # take appropriate action call_back_1(cpu) elif eax == 2: call_back_2(cpu) else: print "Unknown callback, eax is", eax SIM_break_simulation("snore") @SIM_hap_add_callback("Core_Magic_Instruction", hap_callback, None)
The same technique can be applied to other architectures, but you need to adapt the names of the registers involved.