Age | Commit message (Collapse) | Author | Files | Lines |
|
Previously, there was a bug on PTRACE_EVENT_FORK in which the forked
child process inherits all installed breakpoints due to their interrupt
instructions being resident in memory at the time of the fork, but the
debugger initializes the new process model with an empty list of
breakpoints.
There are some differing opinions on what the correct behavior ought to
be here, but at a minimum these two realities must be brought into sync
to prevent data corruption or any process crash of the fork child.
For the time being, manually "uninstall" the residual breakpoint
interrupts from a newly forked child that we attach to, leaving it with
no breakpoints of any kind. Process model initialization in the
debugger is left as-is.
Signed-off-by: Matt Hunter <m@lfurio.us>
|
|
On exec, the program image and address space completely changes, so any
previously established breakpoints going forward are meaningless. Even
if we are re-execing the same actual program, ASLR may invalidate the
addresses of BPs, let alone if the program is modified/recompiled.
The act of performing the exec already "uninstalls" any breakpoints by
reloading the memory space, adapt to this by simply freeing our list of
managed breakpoints from the debugger, without any additional
processing.
Signed-off-by: Matt Hunter <m@lfurio.us>
|
|
Signed-off-by: Matt Hunter <m@lfurio.us>
|
|
Add initial 32-bit ARM support and additionally build out internal
breakpoint design to allow the use of single-step oriented breakpoints.
* arm32:
Always prune step breakpoints when uninstalling from memory
Update detect_breakpoint() to better handle single stepping
Add architecture-specific single step support
Add 32-bit ARM architecture params
|
|
On completion of an initial single step, we can and should discard these
breakpoints, even though the thread may not be "stopping" and go on to
continue in free-run. They have served their purpose at this point and
we would like to avoid any other thread encountering them.
Also, whenever uninstall_breakpoints() is called, it is because we are
cycling a process's threads and are about to run resume_threads() which
currently does the work of re-computing a thread's step breakpoints if a
previous single step was interrupted, whether or not the step
breakpoints for that instant have already been figured. So this also
addresses a bug where one thread, with repeatedly interrupted single
steps, would accumulate more and more redundant breakpoint entries until
it was finally able to proceed and eventually stop.
Signed-off-by: Matt Hunter <m@lfurio.us>
|
|
A stopped thread should sometimes restart, even if a breakpoint
interrupted a single step. detect_breakpoint() and its interaction with
wait_thread() is updated such that any restart during a single step
scenario properly "requeues" the thread's run intent, by preserving the
doing/donext flags.
Furthermore, detect_breakpoint() ditches its call to get_breakpoint()
and considers any and all breakpoints impacting the current thread PC,
since they may each suggest different restarting requirements. In
effect, the thread will only remain stopped if at least one relevant
breakpoint allows it to do so.
This reverts commit 5589a9e3afd5 ("Ignore breakpoints during
singlestep").
Signed-off-by: Matt Hunter <m@lfurio.us>
|
|
ARM 32-bit is the first platform added to misplays which lacks
underlying hardware support for single step traps - so the kernel does
not implement PTRACE_SINGLESTEP in this case.
We will work around this in a similar way as gdb does and how the kernel
used to do it until 2011. arm_singlestep() implements logic which
disassembles the program's current instruction and analyzes it to
determine all possible next locations - eg: the next instruction in
memory, or the jump target of a branch instruction, etc. This logic is
dynamically dispatched by the debugger core if an ARM build is running
in 32-bit mode.
arm_singlestep() uses breakpoints to stop execution at it's computed
next locations. However, misplays is currently very careful about
controling the use of breakpoints in order to avoid issues with thread
single steps - so a new flag (called "step") is added to breakpoints to
enable the debugger to selectively install this subset of breakpoints
for each thread's single step action, and more or less keep treating
thread free-run as normal. install_breakpoints() is updated to take a
"step" parameter to control which set of breakpoints is installed at any
given time.
resume_threads() is updated to perform this new single step dynamic
dispatch, and manage the installation of step breakpoints.
add_breakpoint() is also given a "step" parameter. This initializes the
flag for the new breakpoint, but crucially is used to sort the new
breakpoint into the process breakpoint list. Since step breakpoints
will always be installed first, prioritize them in the list so that
uninstall_breakpoints() doesn't corrupt memory when it runs the list
backward to remove them.
Signed-off-by: Matt Hunter <m@lfurio.us>
|
|
Signed-off-by: Matt Hunter <m@lfurio.us>
|
|
Signed-off-by: Matt Hunter <m@lfurio.us>
|
|
If dbg_attach fails, it calls this function to clean up before the
process in added to the main process list, so list_remove() is invalid.
Signed-off-by: Matt Hunter <m@lfurio.us>
|
|
Previously, the test UI logic would constantly regenerate and print the
screen's contents every iteration of the main processing loop to naively
ensure that changes to the underlying system were reflected immediately.
Sometimes, this would result in a screen flicker due to the rapid
clearing and printing.
This makes the UI a bit smarter about when refreshes occur, by
monitoring activity results from the debugger and console modules in
addition to user input. This should improve the screen flicker
performance.
This also addresses an issue selecting text on the screen, which was
previously not possible due to the rapid refreshing as well.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Abstract architecture details into architecture.h and add x86 constants.
This is slightly complicated by the fact that 64-bit hosts can run
32-bit code, so we do still need to resolve some values dynamically.
The architecture_info() function is intented to address this, and
performs parameter lookups based on the current state of the guest
process.
Resolving values on a per-process-state basis is important due to the
process model under Linux. If we fork to debug a 32-bit program, the
forked process will be native 64-bit until the execve system call. And
of course, the process is then free to exec anything it likes later on
as well.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
I don't think this is strictly necessary, just seemed like a good idea.
However, this function doesn't seem to be available on my test ARM
system, so it is removed for compatibility.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Add list functions which can efficiently cut down on some bolierplate.
The rest of the code will eventually be cleaned to make use of them.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
This bug would occur when the debugger keeps rapidly hitting a
breakpoint and restarting execution (and the breakpoint is temporary /
one-time). For example, stepping over a long recursive call.
It would be possible for the user to manually interrupt execution at the
precise moment that the debugee was returning from a single step over
the breakpoint. In this scenario, the thread would correctly remain
stopped as requested, however the temporary breakpoint would incorrectly
remain in the breakpoint list. The cause of this issue is that
uninstall_breakpoints only considered removing temporaries if they were
currently installed for use.
This makes sense, as we have used the b->installed flag as a sort of
check to see if we have YET installed the breakpoint (rather than "is it
STILL installed?") to ensure that we don't remove it too soon.
The more accurate logic here is to acctually check whether the
breakpoint has EVER been installed and only then consider it for removal
if it is temporary. This fixes the case in question without breaking
behavior when performing an initial run single step.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Display the value of active breakpoint threads, target stack frames, and
enable status.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
This branch adds initial support for multithreaded targets, as well as
forking and exec syscalls. All of the bugs from initial testing are
fixed so far.
* threads: (25 commits)
Don't spin waiting to interrupt zombie process
Allow termination and exec events to propagate immediately
Implement support for PTRACE_EVENT_FORK and ui
Handle PTRACE_EVENT_EXIT to capture a final state snapshot
Detect out-of-band thread exec state changes to prevent deadlock
Enable user creation of thread-specific breakpoints
Add orig_rax to register display
Tweak SCHEDULER_DELAY for use with installing breakpoints
Ignore breakpoints during singlestep
Fix bug with cleaning temporary breakpoints
Independent thread control refactor
Multithread version 3
Add strict_strtoul
Prevent lingering traps after detach
Handle PTRACE_EVENT_EXEC
Workaround SIGSTOP on child process startup
setpgid is redundant with setsid and causes an error
Display name of pending signal
dbg_realcont for testing purposes
Display installed status of breakpoints
...
|
|
Targets can get into this state, for example, when receiving a killing
signal.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Also, it is now possible for interrupt_all_threads to fail to stop
threads in uninterruptable sleep (eg: the main thread during execve).
This may happen in more general cases as well, but it is now common
enough in that case to worry about.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Due to new independent thread control, it is now possible and likely
that breakpoints will be installed before singlesteps are waited upon to
be completed.
also clean detect_breakpoint with get_breakpoint.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
dont remove them before initial use
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
There can sometimes be a pending SIGTRAP when we resume a thread. This
is usually due to interrupting a hung single-step. If this trap is hit
after the debugger leaves, the original process will crash. This is a
quick workaround to attempt to consume such traps ourselves before
detaching.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
The debugger design prefers to use PTRACE_SEIZE instead of
PTRACE_ATTACH, due to the simpler thread control semantics that are
available.
However, to utilize the same featureset for forked processes, we can no
longer use PTRACE_TRACEME to guarantee that the child becomes a tracee
before it execs into the target program.
Manually raising SIGSTOP to act as a synchronization point is
problematic for a couple reasons:
- We need to detect whether the special SIGSTOP was or was not yet
encountered by the time our debugger module attaches and
interrupts the thread. This complicates the dance of input
controls to ensure we are at the exec (and nowhere else) when the
real user takes over the controls.
- The injection of an extra signal circumvents the benefits we hope
to leverage by using the PTRACE_SEIZE semantics. We can no longer
assume that all incoming signals are genuine.
For the time being, sleep in the newly forked child for the scheduler
delay period. This is not bullet-proof, but tends to allow the debugger
module enough time to actually seize the thread before anything
interesting happens. At this point a single dbg_cont() will cause the
child to arrive and stop at the user's exec.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
We need to perform these changes with a thread ID that is known to be in
ptrace stop. This is a requirement of the API even though the memory
change is seen by all threads of the guest process.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Use `th->state == NULL` as an indicator that each thread's state is
capturable, discard use of the `all` parameter.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
This is vaguely competent at tracing single-threaded programs. Vi-like
keybinds defined in misplays.c.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
* Bring-your-own-node (generic / zero allocations)
* Doubly-linked and circular, forward and backward traversable
* Random insert/removal in constant time
* All operations are no-fail
* [Some type safety concessions though]
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Abort on allocation failure. This is mostly done as a formality, as
Linux tends to over-commit memory anyway. In the event of most
failures, we won't have a reasonable recovery either.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|