Source code for libdebug.state.thread_context
#
# This file is part of libdebug Python library (https://github.com/libdebug/libdebug).
# Copyright (c) 2024 Roberto Alessandro Bertolini, Gabriele Digregorio. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for details.
#
from __future__ import annotations
from typing import TYPE_CHECKING
from libdebug.architectures.stack_unwinding_provider import stack_unwinding_provider
from libdebug.debugger.internal_debugger_instance_manager import (
provide_internal_debugger,
)
from libdebug.liblog import liblog
from libdebug.utils.debugging_utils import resolve_address_in_maps
from libdebug.utils.signal_utils import resolve_signal_name, resolve_signal_number
if TYPE_CHECKING:
from libdebug.data.memory_view import MemoryView
from libdebug.data.register_holder import RegisterHolder
from libdebug.data.registers import Registers
from libdebug.debugger.internal_debugger import InternalDebugger
[docs]
class ThreadContext:
"""This object represents a thread in the context of the target process. It holds information about the thread's state, registers and stack."""
instruction_pointer: int
"""The thread's instruction pointer."""
syscall_arg0: int
"""The thread's syscall argument 0."""
syscall_arg1: int
"""The thread's syscall argument 1."""
syscall_arg2: int
"""The thread's syscall argument 2."""
syscall_arg3: int
"""The thread's syscall argument 3."""
syscall_arg4: int
"""The thread's syscall argument 4."""
syscall_arg5: int
"""The thread's syscall argument 5."""
syscall_number: int
"""The thread's syscall number."""
syscall_return: int
"""The thread's syscall return value."""
regs: Registers
"""The thread's registers."""
_internal_debugger: InternalDebugger | None = None
"""The debugging context this thread belongs to."""
_dead: bool = False
"""Whether the thread is dead."""
_exit_code: int | None = None
"""The thread's exit code."""
_exit_signal: int | None = None
"""The thread's exit signal."""
_signal_number: int = 0
"""The signal to forward to the thread."""
_thread_id: int
"""The thread's ID."""
def __init__(self: ThreadContext, thread_id: int, registers: RegisterHolder) -> None:
"""Initializes the Thread Context."""
self._internal_debugger = provide_internal_debugger(self)
self._thread_id = thread_id
regs_class = registers.provide_regs_class()
self.regs = regs_class()
registers.apply_on_regs(self.regs, regs_class)
registers.apply_on_thread(self, ThreadContext)
[docs]
def set_as_dead(self: ThreadContext) -> None:
"""Set the thread as dead."""
self._dead = True
@property
def dead(self: ThreadContext) -> bool:
"""Whether the thread is dead."""
return self._dead
@property
def memory(self: ThreadContext) -> MemoryView:
"""The memory view of the debugged process."""
return self._internal_debugger.memory
@property
def process_id(self: ThreadContext) -> int:
"""The process ID of the thread."""
return self._internal_debugger.process_id
@property
def pid(self: ThreadContext) -> int:
"""The process ID of the thread."""
return self._internal_debugger.process_id
@property
def thread_id(self: ThreadContext) -> int:
"""The thread ID."""
return self._thread_id
@property
def tid(self: ThreadContext) -> int:
"""The thread ID."""
return self._thread_id
@property
def running(self: ThreadContext) -> bool:
"""Whether the process is running."""
return self._internal_debugger.running
@property
def exit_code(self: ThreadContext) -> int | None:
"""The thread's exit code."""
self._internal_debugger._ensure_process_stopped()
if not self.dead:
liblog.warning("Thread is not dead. No exit code available.")
elif self._exit_code is None and self._exit_signal is not None:
liblog.warning(
"Thread exited with signal %s. No exit code available.",
resolve_signal_name(self._exit_signal),
)
return self._exit_code
@property
def exit_signal(self: ThreadContext) -> str | None:
"""The thread's exit signal."""
self._internal_debugger._ensure_process_stopped()
if not self.dead:
liblog.warning("Thread is not dead. No exit signal available.")
return None
elif self._exit_signal is None and self._exit_code is not None:
liblog.warning("Thread exited with code %d. No exit signal available.", self._exit_code)
return None
return resolve_signal_name(self._exit_signal)
@property
def signal(self: ThreadContext) -> str | None:
"""The signal will be forwarded to the thread."""
self._internal_debugger._ensure_process_stopped()
return None if self._signal_number == 0 else resolve_signal_name(self._signal_number)
@signal.setter
def signal(self: ThreadContext, signal: str | int) -> None:
"""Set the signal to forward to the thread."""
self._internal_debugger._ensure_process_stopped()
if self._signal_number != 0:
liblog.debugger(
f"Overwriting signal {resolve_signal_name(self._signal_number)} with {resolve_signal_name(signal) if isinstance(signal, int) else signal}."
)
if isinstance(signal, str):
signal = resolve_signal_number(signal)
self._signal_number = signal
self._internal_debugger.resume_context.threads_with_signals_to_forward.append(self.thread_id)
[docs]
def backtrace(self: ThreadContext) -> list:
"""Returns the current backtrace of the thread."""
internal_debugger = self._internal_debugger
internal_debugger._ensure_process_stopped()
stack_unwinder = stack_unwinding_provider()
backtrace = stack_unwinder.unwind(self)
maps = internal_debugger.debugging_interface.maps()
return [resolve_address_in_maps(x, maps) for x in backtrace]
[docs]
def current_return_address(self: ThreadContext) -> int:
"""Returns the return address of the current function."""
self._internal_debugger._ensure_process_stopped()
stack_unwinder = stack_unwinding_provider()
return stack_unwinder.get_return_address(self)
[docs]
def step(self: ThreadContext) -> None:
"""Executes a single instruction of the process."""
self._internal_debugger.step(self)
[docs]
def step_until(
self: ThreadContext,
position: int | str,
max_steps: int = -1,
file: str = "default",
) -> None:
"""Executes instructions of the process until the specified location is reached.
Args:
position (int | bytes): The location to reach.
max_steps (int, optional): The maximum number of steps to execute. Defaults to -1.
file (str, optional): The user-defined backing file to resolve the address in. Defaults to "default"
(libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t.
the "binary" map file).
"""
self._internal_debugger.step_until(self, position, max_steps, file)
[docs]
def finish(self: ThreadContext, heuristic: str = "backtrace") -> None:
"""Continues execution until the current function returns or the process stops.
The command requires a heuristic to determine the end of the function. The available heuristics are:
- `backtrace`: The debugger will place a breakpoint on the saved return address found on the stack and continue execution on all threads.
- `step-mode`: The debugger will step on the specified thread until the current function returns. This will be slower.
Args:
heuristic (str, optional): The heuristic to use. Defaults to "backtrace".
"""
self._internal_debugger.finish(self, heuristic=heuristic)
[docs]
def si(self: ThreadContext) -> None:
"""Alias for the `step` method.
Executes a single instruction of the process.
"""
self._internal_debugger.step(self)
[docs]
def su(
self: ThreadContext,
position: int | str,
max_steps: int = -1,
) -> None:
"""Alias for the `step_until` method.
Executes instructions of the process until the specified location is reached.
Args:
position (int | bytes): The location to reach.
max_steps (int, optional): The maximum number of steps to execute. Defaults to -1.
"""
self._internal_debugger.step_until(self, position, max_steps)
[docs]
def fin(self: ThreadContext, heuristic: str = "backtrace") -> None:
"""Alias for the `finish` method. Continues execution until the current function returns or the process stops.
The command requires a heuristic to determine the end of the function. The available heuristics are:
- `backtrace`: The debugger will place a breakpoint on the saved return address found on the stack and continue execution on all threads.
- `step-mode`: The debugger will step on the specified thread until the current function returns. This will be slower.
Args:
heuristic (str, optional): The heuristic to use. Defaults to "backtrace".
"""
self._internal_debugger.finish(self, heuristic)