Repository: pwndbg/pwndbg

Issue:
nextproginstr doesn't stop when reaching the program instructions
The `nextproginstr` command should stop when it reaches an instruction in the binary, but it seems to reach those instructions and continue executing instead of breaking.


Discussion:

Resolving patch:
diff --git a/pwndbg/commands/next.py b/pwndbg/commands/next.py
--- a/pwndbg/commands/next.py
+++ b/pwndbg/commands/next.py
@@ -67,9 +67,7 @@ def stepret():
 )
 @pwndbg.commands.OnlyWhenRunning
 def nextproginstr():
-    """Breaks at the next instruction that belongs to the running program"""
-    if pwndbg.gdblib.next.break_on_program_code():
-        pwndbg.commands.context.context()
+    pwndbg.gdblib.next.break_on_program_code()
 
 
 parser = argparse.ArgumentParser(
diff --git a/pwndbg/gdblib/next.py b/pwndbg/gdblib/next.py
--- a/pwndbg/gdblib/next.py
+++ b/pwndbg/gdblib/next.py
@@ -129,20 +129,24 @@ def break_on_program_code():
     Breaks on next instruction that belongs to process' objfile code.
     :return: True for success, False when process ended or when pc is at the code.
     """
-    mp = pwndbg.proc.mem_page
-    start = mp.start
-    end = mp.end
+    exe = pwndbg.proc.exe
+    binary_exec_page_ranges = [
+        (p.start, p.end) for p in pwndbg.vmmap.get() if p.objfile == exe and p.execute
+    ]
 
-    if start <= pwndbg.gdblib.regs.pc < end:
-        print(message.error("The pc is already at the binary objfile code. Not stepping."))
-        return False
+    pc = pwndbg.gdblib.regs.pc
+    for start, end in binary_exec_page_ranges:
+        if start <= pc < end:
+            print(message.error("The pc is already at the binary objfile code. Not stepping."))
+            return False
 
     while pwndbg.proc.alive:
         gdb.execute("si", from_tty=False, to_string=False)
 
-        addr = pwndbg.gdblib.regs.pc
-        if start <= addr < end:
-            return True
+        pc = pwndbg.gdblib.regs.pc
+        for start, end in binary_exec_page_ranges:
+            if start <= pc < end:
+                return True
 
     return False
 
diff --git a/pwndbg/proc.py b/pwndbg/proc.py
--- a/pwndbg/proc.py
+++ b/pwndbg/proc.py
@@ -69,10 +69,6 @@ def exe(self):
         """
         return gdb.current_progspace().filename
 
-    @property
-    def mem_page(self):
-        return next(p for p in pwndbg.vmmap.get() if p.objfile == self.exe)
-
     def OnlyWhenRunning(self, func):
         @functools.wraps(func)
         def wrapper(*a, **kw):


Test patch:
diff --git a/tests/test_commands_next.py b/tests/test_commands_next.py
new file mode 100644
--- /dev/null
+++ b/tests/test_commands_next.py
@@ -0,0 +1,40 @@
+import gdb
+
+import pwndbg.gdblib.regs
+import tests
+
+REFERENCE_BINARY = tests.binaries.get("reference-binary.out")
+
+
+def test_command_nextproginstr_binary_not_running():
+    out = gdb.execute("nextproginstr", to_string=True)
+    assert out == "nextproginstr: The program is not being run.\n"
+
+
+def test_command_nextproginstr(start_binary):
+    start_binary(REFERENCE_BINARY)
+
+    gdb.execute("break main")
+    gdb.execute("continue")
+
+    out = gdb.execute("nextproginstr", to_string=True)
+    assert out == "The pc is already at the binary objfile code. Not stepping.\n"
+
+    # Sanity check
+    exec_bin_pages = [p for p in pwndbg.vmmap.get() if p.objfile == pwndbg.proc.exe and p.execute]
+    assert any(pwndbg.gdblib.regs.pc in p for p in exec_bin_pages)
+    main_page = pwndbg.vmmap.find(pwndbg.gdblib.regs.pc)
+
+    gdb.execute("break puts")
+    gdb.execute("continue")
+
+    # Sanity check that we are in libc
+    assert "libc" in pwndbg.vmmap.find(pwndbg.gdblib.regs.rip).objfile
+
+    # Execute nextproginstr and see if we came back to the same vmmap page
+    gdb.execute("nextproginstr")
+    assert pwndbg.gdblib.regs.pc in main_page
+
+    # Ensure that nextproginstr won't jump now
+    out = gdb.execute("nextproginstr", to_string=True)
+    assert out == "The pc is already at the binary objfile code. Not stepping.\n"