Instruction observers are what implement instruction
stepping, Code observers are what implement low level
instruction breakpoints.
Lets assume that we installed an
So we start at
If the Task made its step then we will get a TrappedEvent. Here things
get a little messy since ptrace/wait uses trap events for signaling
almost everything. Luckily the
When a Code observer is installed then the ptrace task states will all
call
All the logic of how to breakpoint step is contained in the
For stepping the
In the case of
When the
The
Deficiencies of the Instruction Parser. The framework is in
place and works for the few Instructions that are known to the
instruction parser, but there are all hand coded (see
InstructionObserver, the
Task stopped notified the observer which decided that the
Task needed to be blocked which turned the
TaskState into BlockedSignal. If you are
interested in what would happen while stepping and the
Task not being blocked already, start reading from (*)
and assume we are in the Running state and just want to
continue running the task after some event happened.
Task.unblock(InstructionObserver) which
will notify the BlockedSignal state class in
LinuxTaskState. The handleUnblock() method
will be triggered. Depending on which observers are actually
installed (and whether all blocking observer have been cleared now) a
Running TaskState is determined and
sendContinue(task, sig) is called on that
Running state (the Running state class is
also defined inside the LinuxTaskState).
sendContinue() is an instance method of the
Running TaskState class which will return
the appropriate Running subclass/instance depending on how the
Task continues. If an Instruction observer
(or the task is currently at a breakpoint, see below) then
LinuxTask.sendStepInstruction code is called and
sendContinue will return Stepping (which is
a subclass of Running) as the new state of the Task.
sendStepInstruction() will do some bookkeeping to
remember which signal number was requested and whether or not the Task
is currently at a signal return instruction. We need to do that here
so we know what we were doing when we get a callback from the kernel
after the step. Then the appropriate ptrace command is invoked so the
Task is doing the actual step. Some, but not all (signal number and
sigret in particular) this is contained as state in the
Stepping task state instance.
Isa should be able to tell
us if the current Task just did an instruction step. If
it did (and it isn't a breakpoint address in which case we need to do
some extra things) then we inform all Instruction
observers and if any of them tells us to Block then we move into the
BlockedSignal state, otherwise we keep in the Stepping
state and continue from (*).
setupSteppingBreakpoint() after a breakpoint hit has
been detected, which makes sure the PC is setup correctly (which can
be off by one on some architectures after a breakpoint) and then mark
the Task as being at that particular breakpoint (this is
still kept as Task field for now). We need to always do the adjustment
immediately in case the user decides to move to a Blocked
state instead of of continuing the Task. When
Running.sendContinue is later called it will depend on
this fact.
Breakpoint class that checks the properties of the
Instruction class object which is created by the
Isa through an instruction parser before the breakpoint
is inserted at a given location. An Instruction knows how
long the instruction is, which bytes it represents, whether it can be
single stepped out of line and how to set that up given the original
pc location and an alternative address, plus any fixups that are
needed to the Task afterwards (and it has a notion of whether or not
the Instruction can be simulated, but that isn't
currently used, see below). A Breakpoint ties an
Instruction to a particular address and Proc
(and Tasks can have zero or more
Breakpoints, they share the same Breakpoint
on the same address with other Tasks of a
Proc and when no Tasks of a
Proc has an Breakpoint at a particular
address anymore the Breakpoint is removed).
, the Breakpoint the
Running.sendContinue() method first calls
Breakpoint.prepareStep(), then signals ptrace to do a
single step of the Task, putting the Task in
Stepping state and then in handleTrapEvent()
calls Breakpoint.stepDone(). prepareStep()
queries the capabilities of the Instruction at the pc
address and depending on that either sets things up for doing a step
out of line, simulate the instruction (but none of the current
Instructions have been setup to do simulation yet, and
look at the comment in prepareStep() to see what is
needed to fully enable this option) or reset the current
Instruction (removing the breakpoint instruction
temporarily). Accordingly a Breakpoint can be in the state
NOT_STEPPING, OUT_OF_LINE_STEPPING,
SIMULATE_STEPPING or RESET_STEPPING.
RESET_STEPPING other Tasks might miss and
just past the Breakpoint during the brief period between the reset,
step and reinstall. Breakpoint prepareStep() just takes
the Instruction bytes and puts them at the current pc address, and
doneStep() reinstates the breakpoint instruction. The
right solution here would be to stop all other Tasks first, step and
then continue them all.
Instruction supports single step out of line
then the Breakpoint requests an address in the single
step out of line area of the Proc, instructs the
Instruction to install itself there for the current Task
calling Instruction.setupExecuteOutOfLine(). The default
action of setupExecuteOutOfLine() is to set the pc to the
given address and place a copy the instruction bytes there (although
this can be overridden if an Instruction wants to do
something more fancy). When the task signals the
Breakpoint that a step was taken by calling
,code>stepDone()Breakpoint calls
Instruction.fixupExecuteOutOfLine() with the original pc
and replacement address so any adjustments can be done to the
Task registers. The default action is to just set the pc
to the original pc plus the length of the Instruction
just stepped. But Instructions can override that if more is needed. As
an example the RET instruction doesn't do any fixup
(since the only action is setting the pc to the right location in the
first place) and the JMP instruction sets the pc to
original pc plus/minus the difference of the alternate address and the
pc after the single step. Afterward the Breakpoint
returns the used address to the Proc so it can be used by
other Tasks needing to do a single step out of line.
Proc maintains a single step out of line area pool of
addresses that point to locations that are at least as big is the
largest instruction of the instruction set. The Proc gets
this list from the Isa the first time an address is
requested through getOutOfLineAddress(). Currently this
is just the address of the main function entry point (see below). The
address is taken out of the pool and the Breakpoint is
responsible for putting it back through doneOutOfLine
(see above). If no address is currently available the call blocks till
one is available (this was way easier than inventing yet another
TaskState and getting the communication between
Proc and Task about this right, and
contention is very low and at the longest it takes for an address to
become available is one instruction single step).
IA32InstructionParser which just handles
NOP, INT3, RETQ and one
JMP variant, the X8664Instruction just
delegates to the IA32 for now). There don't seem to be libraries
available to easily plugin that would give us the fixup instructions
needed. The best available is the kprobes examples from the linux
kernel which have as drawback that they are coded to be intimately
tied to the kernel/C way of doing things and only seem handles
instructions found in kernel space (no robust instruction parsing,
just a instruction bits/lookup table).appreciated. So we need to sit
down with the various instruction manuals and just code it up by hand
if we cannot find an existing library. (Bonus points for finding
something that would not just give us ssol fixups but also simulation
of instructions when hooked to the registers and memory of a Task.)
This is
Tasks which means
breakpoints can and will be missed.