Skip to content

Commit 75a647e

Browse files
authored
feat: add the print_frame command (#4)
1 parent 2c8bca9 commit 75a647e

2 files changed

Lines changed: 106 additions & 1 deletion

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ This project enhances the LLDB debugging experience with custom Python commands,
2828
| **`alcheck`** | Highlights `UIViews` with Auto Layout issues by outlining them in red. | `[-c color] [-w width]` |
2929
| **`pdefaults`** | Dumps `NSUserDefaults` contents as a formatted key-value table. | `[-s suite] [-f filter] [-o]` |
3030
| **`hex_dump`** | Reads memory at a specific address and returns a formatted hex dump. | `<address> [-c count] [-o offset]` |
31+
| **`print_frame`** | Displays a detailed low-level architectural snapshot and resolved symbolic context of the current stack frame. | None |
3132

3233
### Useful Aliases & Regex Commands
3334

‎lldb/commands/DTInspection.py‎

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def commands():
1717
Returns a list of custom LLDB command instances defined in this module.
1818
The registration logic in lldbinit.py calls this function.
1919
"""
20-
return [DTHexDumpCommand()]
20+
return [DTHexDumpCommand(), DTPrintFrameCommand()]
2121

2222

2323
class DTHexDumpCommand(bc.BaseCommand):
@@ -168,3 +168,107 @@ def run(self, args, options):
168168
print(f"0x{current_addr:016x} {hex_part} {ascii_part}")
169169

170170
print("")
171+
172+
173+
class DTPrintFrameCommand(bc.BaseCommand):
174+
"""
175+
A custom LLDB command that performs a low-level architectural inspection
176+
of the current thread's topmost stack frame (Frame #0).
177+
178+
Usage: print_frame
179+
180+
This command extracts raw CPU execution context and translates it into high-value
181+
metadata for reverse-engineering. It automates symbolication, resolves memory bounds,
182+
and dynamically calculates ASLR slides to map live runtime execution directly to
183+
static analysis tools (like Hopper Disassembler or IDA Pro).
184+
"""
185+
186+
def name(self):
187+
"""Returns the command name as it will be used in the LLDB console."""
188+
return "print_frame"
189+
190+
def description(self):
191+
"""Returns a short description of the command's functionality."""
192+
return "Display architecture-level details of the current stack frame."
193+
194+
def options(self):
195+
"""This command does not require any additional arguments or options."""
196+
return []
197+
198+
def run(self, args, options):
199+
"""
200+
The main execution logic for the print_frame command.
201+
"""
202+
203+
target = lldb.debugger.GetSelectedTarget()
204+
process = target.GetProcess()
205+
206+
if not process or not process.IsValid():
207+
print("error: valid active process not found")
208+
return
209+
210+
thread = process.GetSelectedThread()
211+
212+
if not thread or not thread.IsValid():
213+
print("error: no active thread discovered")
214+
return
215+
216+
frame = thread.GetSelectedFrame() if thread else None
217+
218+
if not frame or not frame.IsValid():
219+
print("error: unable to resolve valid stack frame #0")
220+
return
221+
222+
pc_address = frame.GetPC()
223+
sp_address = frame.GetSP()
224+
frame_idx = frame.GetFrameID()
225+
226+
symbol_context = frame.GetSymbolContext(lldb.eSymbolContextEverything)
227+
228+
module = symbol_context.GetModule()
229+
module_name = (
230+
module.GetFileSpec().GetFilename()
231+
if module and module.IsValid()
232+
else "Unknown Module"
233+
)
234+
235+
function_name = "Unknown Function"
236+
237+
if symbol_context.GetFunction().IsValid():
238+
function_name = symbol_context.GetFunction().GetName()
239+
elif symbol_context.GetSymbol().IsValid():
240+
function_name = symbol_context.GetSymbol().GetName()
241+
242+
aslr_offset_str = "N/A"
243+
244+
if module and module.IsValid():
245+
header_load_addr = module.GetObjectFileHeaderAddress().GetLoadAddress(
246+
target
247+
)
248+
header_file_addr = module.GetObjectFileHeaderAddress().GetFileAddress()
249+
250+
if header_load_addr != lldb.LLDB_INVALID_ADDRESS:
251+
aslr_slide = header_load_addr - header_file_addr
252+
relative_offset = pc_address - header_load_addr
253+
aslr_offset_str = (
254+
f"0x{relative_offset:x} (Module ASLR Slide: 0x{aslr_slide:x})"
255+
)
256+
257+
print("\n" + "=" * 80)
258+
print(f"[FRAME #{frame_idx}] -> CURRENT CPU ARCHITECTURE CONTEXT")
259+
print("=" * 80)
260+
print(f"• Module: {module_name}")
261+
print(f"• Function: {function_name}")
262+
print(f"• Address PC: 0x{pc_address:016x}")
263+
print(f"• File Offset: {aslr_offset_str}")
264+
print(f"• Stack SP: 0x{sp_address:016x}")
265+
266+
line_entry = symbol_context.GetLineEntry()
267+
if line_entry and line_entry.IsValid():
268+
print(
269+
f"• Source File: {line_entry.GetFileSpec().GetFilename()}:{line_entry.GetLine()}"
270+
)
271+
272+
print("=" * 80 + "\n")
273+
274+
return

0 commit comments

Comments
 (0)