Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions libdebug/data/breakpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,11 @@ class Breakpoint:
def enable(self: Breakpoint) -> None:
"""Enable the breakpoint."""
provide_internal_debugger(self).ensure_process_stopped()
self.enabled = True
self._changed = True

def disable(self: Breakpoint) -> None:
"""Disable the breakpoint."""
provide_internal_debugger(self).ensure_process_stopped()
self.enabled = False
self._changed = True

def hit_on(self: Breakpoint, thread_context: ThreadContext) -> bool:
Expand All @@ -68,7 +66,8 @@ def hit_on(self: Breakpoint, thread_context: ThreadContext) -> bool:

internal_debugger = provide_internal_debugger(self)
internal_debugger.ensure_process_stopped()
return internal_debugger.resume_context.event_hit_ref.get(thread_context.thread_id) == self
events = internal_debugger.resume_context.event_hit_ref.get(thread_context.thread_id, [])
return self in events

def __hash__(self: Breakpoint) -> int:
"""Hash the breakpoint by its address, so that it can be used in sets and maps correctly."""
Expand Down
87 changes: 87 additions & 0 deletions libdebug/data/breakpoint_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#
# This file is part of libdebug Python library (https://github.com/libdebug/libdebug).
# Copyright (c) 2024 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.data.breakpoint import Breakpoint

if TYPE_CHECKING:
from collections.abc import Callable

from libdebug.state.thread_context import ThreadContext


class BreakpointList(list):
"""A list of breakpoints installed at the same address in the target process.

Attributes:
address (int): The address of the breakpoints installed in the target process.
symbol (str): The symbol, if available, of the breakpoints installed in the target process.
hit_count (int): The sum of the hit counts of all the breakpoints inside the BreakpointList.
callback list[Callable[[ThreadContext, Breakpoint], None]]: The list of callbacks defined by the user to execute when the breakpoints are hit.
enabled (bool): Whether at least one of the breakpoints is enabled or not.
"""

address: int = 0
symbol: str = ""

def __init__(self: BreakpointList, breakpoints: list[Breakpoint], address: int, symbol: str) -> None:
"""Initializes the BreakpointList."""
self.address = address
self.symbol = symbol
super().__init__(breakpoints)

@property
def hit_count(self: BreakpointList) -> int:
"""Returns the sum of the hit counts of all the breakpoints inside the BreakpointList."""
return sum(bp.hit_count for bp in self)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would multiply the hit count for the number of superimposed breakpoints... Is this really what we want?


@property
def callback(self: BreakpointList) -> list[Callable[[ThreadContext, Breakpoint], None]]:
"""Returns the list of callbacks defined by the user to execute when the breakpoints are hit."""
return [bp.callback for bp in self]

@property
def enabled(self: BreakpointList) -> bool:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to set enabled on the list to enable all or disable all breakpoints at that address? If not, I would rename this property to avoid confusion (because of the symmetry with the property of the breakpoint object itself).

"""Returns whether at least one of the breakpoints is enabled or not."""
return any(bp.enabled for bp in self)

def filter(self: BreakpointList, **kwargs: dict[str, object]) -> BreakpointList:
"""Filters the breakpoints according to the specified Breakpoint attributes.

Args:
**kwargs: The arguments to filter the breakpoints. It can be any Breakpoint attribute.

Returns:
BreakpointList[Breakpoint]: The list of breakpoints installed in the specified thread and/or with the specified condition.
"""
if not kwargs:
return self

filtered_breakpoints = self
for key, value in kwargs.items():
if key not in Breakpoint.__annotations__:
raise ValueError(f"Invalid attribute: {key} is not a valid Breakpoint attribute")
if key == "thread_id":
filtered_breakpoints = [bp for bp in filtered_breakpoints if bp.thread_id in (value, -1)]
else:
filtered_breakpoints = [bp for bp in filtered_breakpoints if getattr(bp, key) == value]

return BreakpointList(filtered_breakpoints, self.address, self.symbol)

def __hash__(self) -> int:
"""Return the hash of the symbol list."""
return hash(id(self))

def __eq__(self, other: object) -> bool:
"""Check if the symbol list is equal to another object."""
return super().__eq__(other)

def __repr__(self: BreakpointList) -> str:
"""Returns the string representation of the BreakpointList without the default factory."""
return f"BreakpointList({super().__repr__()})"
4 changes: 2 additions & 2 deletions libdebug/data/symbol_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class SymbolList(list):
"""A list of symbols in the target process."""

def __init__(self: SymbolList, symbols: list[Symbol]) -> None:
"""Initializes the SymbolDict."""
"""Initializes the SymbolList."""
super().__init__(symbols)

def _search_by_address(self: SymbolList, address: int) -> list[Symbol]:
Expand Down Expand Up @@ -95,5 +95,5 @@ def __eq__(self, other: object) -> bool:
return super().__eq__(other)

def __repr__(self: SymbolList) -> str:
"""Returns the string representation of the SymbolDict without the default factory."""
"""Returns the string representation of the SymbolList without the default factory."""
return f"SymbolList({super().__repr__()})"
18 changes: 7 additions & 11 deletions libdebug/debugger/internal_debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@
from libdebug.utils.debugger_wrappers import (
background_alias,
change_state_function_process,
change_state_function_thread,
)
from libdebug.utils.debugging_utils import (
normalize_and_validate_address,
resolve_address_in_maps,
resolve_symbol_in_maps,
)
from libdebug.utils.elf_utils import get_all_symbols
Expand Down Expand Up @@ -578,23 +578,19 @@ def breakpoint(
def callback(_: ThreadContext, __: Breakpoint) -> None:
pass

if bp := self.breakpoints.get(address):
# TODO: we should allow multiple breakpoints at the same address (e.g., for different threads)
liblog.warning(f"Breakpoint at {position} already set. Overriding it.")

bp = Breakpoint(address, position, thread_id, 0, hardware, callback, condition.lower(), length)

if hardware:
validate_hardware_breakpoint(self.arch, bp)

link_to_internal_debugger(bp, self)

self.__polling_thread_command_queue.put((self.__threaded_breakpoint, (bp, thread_id)))
self.__polling_thread_command_queue.put((self.__threaded_breakpoint, (bp,)))

self._join_and_check_status()

# the breakpoint should have been set by interface
if address not in self.breakpoints:
# The breakpoint should have been set by interface
if self.breakpoints.get(address) is None or bp not in self.breakpoints[address]:
raise RuntimeError("Something went wrong while inserting the breakpoint.")

return bp
Expand Down Expand Up @@ -1612,11 +1608,11 @@ def __threaded_wait(self: InternalDebugger, thread: InternalThreadContext = None
else:
thread.running = False

def __threaded_breakpoint(self: InternalDebugger, bp: Breakpoint, thread_id: int) -> None:
def __threaded_breakpoint(self: InternalDebugger, bp: Breakpoint) -> None:
liblog.debugger(
f"Setting breakpoint at {bp.address:x}" + (f" for thread {thread_id}" if thread_id != -1 else "."),
f"Setting breakpoint at {bp.address:x}" + (f" for thread {bp.thread_id}" if bp.thread_id != -1 else "."),
)
self.debugging_interface.set_breakpoint(bp, thread_id)
self.debugging_interface.set_breakpoint(bp)

def __threaded_catch_signal(self: InternalDebugger, catcher: SignalCatcher) -> None:
liblog.debugger(
Expand Down
32 changes: 16 additions & 16 deletions libdebug/ptrace/native/libdebug_ptrace_binding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ void LibdebugPtraceInterface::forward_signals(const std::vector<std::pair<pid_t,
}
}

void LibdebugPtraceInterface::register_breakpoint(const unsigned long address)
void LibdebugPtraceInterface::install_breakpoint(const unsigned long address)
{
unsigned long instruction = ptrace(PTRACE_PEEKTEXT, process_id, (void *) address, NULL);

Expand All @@ -595,7 +595,7 @@ void LibdebugPtraceInterface::register_breakpoint(const unsigned long address)
software_breakpoints[address] = bp;
}

void LibdebugPtraceInterface::unregister_breakpoint(const unsigned long address)
void LibdebugPtraceInterface::uninstall_breakpoint(const unsigned long address)
{
if (software_breakpoints.find(address) == software_breakpoints.end()) {
throw std::runtime_error("Breakpoint not found");
Expand Down Expand Up @@ -626,7 +626,7 @@ void LibdebugPtraceInterface::disable_breakpoint(const unsigned long address)
ptrace(PTRACE_POKETEXT, process_id, (void *) address, (void *) software_breakpoints[address].instruction);
}

void LibdebugPtraceInterface::register_hw_breakpoint(const pid_t tid, unsigned long address, const int type, const int len)
void LibdebugPtraceInterface::install_hw_breakpoint(const pid_t tid, unsigned long address, const int type, const int len)
{
Thread &t = get_thread(tid);

Expand All @@ -648,7 +648,7 @@ void LibdebugPtraceInterface::register_hw_breakpoint(const pid_t tid, unsigned l
install_hardware_breakpoint(bp);
}

void LibdebugPtraceInterface::unregister_hw_breakpoint(const pid_t tid, const unsigned long address)
void LibdebugPtraceInterface::uninstall_hw_breakpoint(const pid_t tid, const unsigned long address)
{
if (threads.find(tid) == threads.end()) {
return;
Expand Down Expand Up @@ -927,13 +927,13 @@ NB_MODULE(libdebug_ptrace_binding, m)
" tid (int): The thread id to get the remaining hardware watchpoint count for.\n"
)
.def(
"register_hw_breakpoint",
&LibdebugPtraceInterface::register_hw_breakpoint,
"install_hw_breakpoint",
&LibdebugPtraceInterface::install_hw_breakpoint,
nb::arg("tid"),
nb::arg("address"),
nb::arg("type"),
nb::arg("len"),
"Registers a hardware breakpoint for a thread.\n"
"Install a hardware breakpoint for a thread.\n"
"\n"
"Args:\n"
" tid (int): The thread id to register the hardware breakpoint for.\n"
Expand All @@ -942,11 +942,11 @@ NB_MODULE(libdebug_ptrace_binding, m)
" len (int): The length of the hardware breakpoint."
)
.def(
"unregister_hw_breakpoint",
&LibdebugPtraceInterface::unregister_hw_breakpoint,
"uninstall_hw_breakpoint",
&LibdebugPtraceInterface::uninstall_hw_breakpoint,
nb::arg("tid"),
nb::arg("address"),
"Unregisters a hardware breakpoint for a thread.\n"
"Uninstall a hardware breakpoint for a thread.\n"
"\n"
"Args:\n"
" tid (int): The thread id to unregister the hardware breakpoint for.\n"
Expand All @@ -965,19 +965,19 @@ NB_MODULE(libdebug_ptrace_binding, m)
" int: The address of the hit hardware breakpoint."
)
.def(
"register_breakpoint",
&LibdebugPtraceInterface::register_breakpoint,
"install_breakpoint",
&LibdebugPtraceInterface::install_breakpoint,
nb::arg("address"),
"Registers a software breakpoint at a specific address.\n"
"Install a software breakpoint at a specific address.\n"
"\n"
"Args:\n"
" address (int): The address to set the software breakpoint at."
)
.def(
"unregister_breakpoint",
&LibdebugPtraceInterface::unregister_breakpoint,
"uninstall_breakpoint",
&LibdebugPtraceInterface::uninstall_breakpoint,
nb::arg("address"),
"Unregisters a software breakpoint at a specific address.\n"
"Uninstall a software breakpoint at a specific address.\n"
"\n"
"Args:\n"
" address (int): The address to remove the software breakpoint from."
Expand Down
8 changes: 4 additions & 4 deletions libdebug/ptrace/native/libdebug_ptrace_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@ class LibdebugPtraceInterface
void forward_signals(const std::vector<std::pair<pid_t, int>>);

// Debugger software breakpoint methods
void register_breakpoint(const unsigned long);
void unregister_breakpoint(const unsigned long);
void install_breakpoint(const unsigned long);
void uninstall_breakpoint(const unsigned long);
void enable_breakpoint(const unsigned long);
void disable_breakpoint(const unsigned long);

// Debugger hardware breakpoint methods
void register_hw_breakpoint(const pid_t, unsigned long address, const int type, const int len);
void unregister_hw_breakpoint(const pid_t, const unsigned long);
void install_hw_breakpoint(const pid_t, unsigned long address, const int type, const int len);
void uninstall_hw_breakpoint(const pid_t, const unsigned long);
unsigned long get_hit_hw_breakpoint(const pid_t);
int get_remaining_hw_breakpoint_count(const pid_t);
int get_remaining_hw_watchpoint_count(const pid_t);
Expand Down
Loading