Repository: pwndbg/pwndbg

Issue:
Add `telescope --frame`
It'd be nice to have a `telescope` command that prints out the stack frame, from `rsp` to `rbp`. Obviously this only works if the program actually uses a frame pointer.

It would be equivalent to this:
```
pwndbg> p ($rbp-$rsp)/8 + 1
$5 = 9
pwndbg> telescope $rsp $$
00:0000│ rsp 0x7ffe5f4951a0 ◂— 0x300000001
01:0008│     0x7ffe5f4951a8 —▸ 0x7ffe5f495220 ◂— 0x170c94ca0
02:0010│     0x7ffe5f4951b0 —▸ 0x7f6870c96168 —▸ 0x563e09600000 ◂— jg     0x563e09600047
03:0018│     0x7ffe5f4951b8 ◂— 0xf0
04:0020│ rsi 0x7ffe5f4951c0 ◂— 0xb1ed2074ada5ce5
05:0028│     0x7ffe5f4951c8 ◂— 0xda37756c736484c1
06:0030│     0x7ffe5f4951d0 —▸ 0x7ffe5f4951fe ◂— 0x563e096013e00000
07:0038│     0x7ffe5f4951d8 ◂— 0x56657596c3d91600
08:0040│ rbp 0x7ffe5f4951e0 —▸ 0x7ffe5f495200 —▸ 0x563e096013e0 ◂— push   r15
```


Discussion:
Shall this be in `telescope`? We also have a `stack` command
We discussed this on discord, it's probably best to either alias `stack` to `telescope`, or just remove `stack` (after displaying a deprecation warning for a few months).
FWIW the `stack` command:
```
user@user:~/pwndbg$ rg 'def stack' -A5
pwndbg/commands/telescope.py
216:def stack(count, offset):
217-    ptrsize = pwndbg.gdblib.typeinfo.ptrsize
218-    telescope.repeat = stack.repeat
219-    telescope(address=pwndbg.gdblib.regs.sp + offset * ptrsize, count=count)
```

So it takes `<count> <offset>` and calls `telescope $rsp+(8*offset) count` (where 8 is arch.ptrsize, so differs on x86).

I'm fine with deprecating `stack` and removing it in the future.

Resolving patch:
diff --git a/pwndbg/commands/telescope.py b/pwndbg/commands/telescope.py
--- a/pwndbg/commands/telescope.py
+++ b/pwndbg/commands/telescope.py
@@ -58,6 +58,15 @@
     help="Show <count> previous addresses instead of next ones",
 )
 
+parser.add_argument(
+    "-f",
+    "--frame",
+    dest="frame",
+    action="store_true",
+    default=False,
+    help="Show the stack frame, from rsp to rbp",
+)
+
 parser.add_argument(
     "address", nargs="?", default="$sp", type=int, help="The address to telescope at."
 )
@@ -69,7 +78,7 @@
 
 @pwndbg.commands.ArgparsedCommand(parser, category=CommandCategory.MEMORY)
 @pwndbg.commands.OnlyWhenRunning
-def telescope(address=None, count=telescope_lines, to_string=False, reverse=False):
+def telescope(address=None, count=telescope_lines, to_string=False, reverse=False, frame=False):
     """
     Recursively dereferences pointers starting at the specified address
     ($sp by default)
@@ -96,6 +105,24 @@ def telescope(address=None, count=telescope_lines, to_string=False, reverse=Fals
     if reverse:
         address -= (count - 1) * ptrsize
 
+    # Allow invocation of telescope -f (--frame) to dump frame addresses
+    if frame:
+        sp = pwndbg.gdblib.regs.sp
+        bp = pwndbg.gdblib.regs[pwndbg.gdblib.regs.frame]
+        if sp > bp:
+            print("Cannot display stack frame because base pointer is below stack pointer")
+            return
+
+        for page in pwndbg.gdblib.vmmap.get():
+            if sp in page and bp not in page:
+                print(
+                    "Cannot display stack frame because base pointer is not on the same page with stack pointer"
+                )
+                return
+
+        address = sp
+        count = int((bp - sp) / ptrsize) + 1
+
     # Allow invocation of "telescope a b" to dump all bytes from A to B
     if int(address) <= int(count):
         # adjust count if it is an address. use ceil division as count is number of


Test patch:
diff --git a/tests/gdb-tests/tests/test_command_telescope.py b/tests/gdb-tests/tests/test_command_telescope.py
--- a/tests/gdb-tests/tests/test_command_telescope.py
+++ b/tests/gdb-tests/tests/test_command_telescope.py
@@ -102,3 +102,62 @@ def test_command_telescope_reverse_skipped_records_shows_input_address(start_bin
     result_lines = result_str.strip("\n").split("\n")
 
     assert expected_value in result_lines[-1]
+
+
+def test_command_telescope_frame(start_binary):
+    """
+    Tests telescope --frame
+    """
+    start_binary(TELESCOPE_BINARY)
+
+    gdb.execute("break break_here")
+    gdb.execute("run")
+
+    rsp = hex(pwndbg.gdblib.regs.sp)
+    rbp = hex(pwndbg.gdblib.regs[pwndbg.gdblib.regs.frame])
+
+    result_str = gdb.execute("telescope --frame", to_string=True)
+    result_lines = result_str.strip().split("\n")
+
+    assert rsp in result_lines[0]
+    assert rbp in result_lines[-1]
+
+
+def test_command_telescope_frame_bp_below_sp(start_binary):
+    """
+    Tests telescope --frame when base pointer is below stack pointer
+    """
+    start_binary(TELESCOPE_BINARY)
+
+    gdb.execute("break break_here")
+    gdb.execute("run")
+    gdb.execute("memoize")  # turn off cache
+
+    pwndbg.gdblib.regs.sp = pwndbg.gdblib.regs[pwndbg.gdblib.regs.frame] + 1
+
+    result_str = gdb.execute("telescope --frame", to_string=True)
+
+    assert "Cannot display stack frame because base pointer is below stack pointer" in result_str
+
+
+def test_command_telescope_frame_bp_sp_different_vmmaps(start_binary):
+    """
+    Tests telescope --frame when base pointer and stack pointer are on different vmmap pages
+    """
+    start_binary(TELESCOPE_BINARY)
+
+    gdb.execute("break break_here")
+    gdb.execute("run")
+    gdb.execute("memoize")  # turn off cache
+
+    pages = pwndbg.gdblib.vmmap.get()
+
+    pwndbg.gdblib.regs.sp = pages[0].start
+    pwndbg.gdblib.regs.bp = pages[1].start
+
+    result_str = gdb.execute("telescope --frame", to_string=True)
+
+    assert (
+        "Cannot display stack frame because base pointer is not on the same page with stack pointer"
+        in result_str
+    )