Repository: Gallopsled/pwntools

Issue:
How to set breakpoint commands from GDB Python API?
I'm able to set breakpoints using the experimental support for the Python GDB API, but I'm not able to set any commands that should be executed when that breakpoint is hit. According to the [docs](https://sourceware.org/gdb/onlinedocs/gdb/Breakpoints-In-Python.html), `Breakpoints.commands` is writable and can be set to the commands you want to run when the breakpoint is hit. I'm trying something like this:
```
b = p.gdb.Breakpoint('main')
b.commands = 'echo commands are working'
p.gdb.continue_nowait()
```

The breakpoint gets hit, but no commands are executed. Am I doing something wrong here, or are we not exposing this functionality for some reason?

I'm using version 4.8.0 on Ubuntu 22.04.


Discussion:
This should generally work, as this is simple RPyC remote object access.  However, if you have some time to investigate this further, that would be awesome.

Resolving patch:
diff --git a/pwnlib/gdb.py b/pwnlib/gdb.py
--- a/pwnlib/gdb.py
+++ b/pwnlib/gdb.py
@@ -652,7 +652,10 @@ def __init__(self, conn, *args, **kwargs):
         """
         # Creates a real breakpoint and connects it with this mirror
         self.conn = conn
-        self.server_breakpoint = conn.root.set_breakpoint(
+        self.server_breakpoint = self._server_set_breakpoint(*args, **kwargs)
+
+    def _server_set_breakpoint(self, *args, **kwargs):
+        return self.conn.root.set_breakpoint(
             self, hasattr(self, 'stop'), *args, **kwargs)
 
     def __getattr__(self, item):
@@ -670,25 +673,43 @@ def __getattr__(self, item):
             raise AttributeError()
         return getattr(self.server_breakpoint, item)
 
+    def __setattr__(self, name, value):
+        """Set attributes of the real breakpoint."""
+        if name in (
+            'enabled',
+            'silent',
+            'thread',
+            'task',
+            'ignore_count',
+            'hit_count'
+            'condition',
+            'commands',
+        ):
+            return setattr(self.server_breakpoint, name, value)
+        return super().__setattr__(name, value)
+
     def exposed_stop(self):
         # Handle stop() call from the server.
         return self.stop()
 
-class FinishBreakpoint:
+class FinishBreakpoint(Breakpoint):
     """Mirror of ``gdb.FinishBreakpoint`` class.
 
     See https://sourceware.org/gdb/onlinedocs/gdb/Finish-Breakpoints-in-Python.html
     for more information.
     """
 
-    def __init__(self, conn, *args, **kwargs):
+    def __init__(self, *args, **kwargs):
         """Do not create instances of this class directly.
 
         Use ``pwnlib.gdb.Gdb.FinishBreakpoint`` instead.
         """
-        # Creates a real finish breakpoint and connects it with this mirror
-        self.conn = conn
-        self.server_breakpoint = conn.root.set_finish_breakpoint(
+        # See https://github.com/pylint-dev/pylint/issues/4228
+        # pylint: disable=useless-super-delegation
+        super().__init__(*args, **kwargs)
+
+    def _server_set_breakpoint(self, *args, **kwargs):
+        return self.conn.root.set_finish_breakpoint(
             self, hasattr(self, 'stop'), hasattr(self, 'out_of_scope'),
             *args, **kwargs)
 
@@ -708,10 +729,6 @@ def __getattr__(self, item):
             raise AttributeError()
         return getattr(self.server_breakpoint, item)
 
-    def exposed_stop(self):
-        # Handle stop() call from the server.
-        return self.stop()
-
     def exposed_out_of_scope(self):
         # Handle out_of_scope() call from the server.
         return self.out_of_scope()
diff --git a/pwnlib/gdb_api_bridge.py b/pwnlib/gdb_api_bridge.py
--- a/pwnlib/gdb_api_bridge.py
+++ b/pwnlib/gdb_api_bridge.py
@@ -110,5 +110,6 @@ def exposed_quit(self):
     socket_path=socket_path,
     protocol_config={
         'allow_all_attrs': True,
+        'allow_setattr': True,
     },
 ).start)


Test patch: