1111import argparse
1212import enum
1313import json
14+ import logging
1415import os
16+ import sys
1517import time
1618
17- import lldb
18-
1919
2020class StateType (enum .Enum ):
2121 Invalid = 0
@@ -26,6 +26,10 @@ class StateType(enum.Enum):
2626 Stopped = 5
2727 Running = 6
2828 Stepping = 7
29+ Crashed = 8
30+ Detached = 9
31+ Exited = 10
32+ Suspended = 11
2933
3034
3135class StopReason (enum .Enum ):
@@ -59,43 +63,85 @@ def main():
5963 args = parser .parse_args ()
6064
6165 if os .path .exists (args .output ):
62- print (f"Removing output file { args .output } ..." )
66+ logging . info (f"Removing output file { args .output } ..." )
6367 os .remove (args .output )
6468
65- print ("Creating debugger..." )
69+ mapping = extract_mapping (args .exe )
70+ with open (args .output , "w" ) as f :
71+ json .dump (mapping , f , indent = 2 )
72+
73+
74+ def extract_mapping (exe : str ) -> dict [int , str ]:
75+ # Add the installed LLVM Python path to the Python path and error if the Python version does not match:
76+ # i.e.: /opt/homebrew/opt/llvm/libexec/python3.13/site-packages
77+ LLVM_PYTHON_ROOT = "/opt/homebrew/opt/llvm/libexec"
78+ if not os .path .exists (LLVM_PYTHON_ROOT ):
79+ raise ImportError (
80+ f"{ LLVM_PYTHON_ROOT } does not exist. Please install LLVM/LLDB first."
81+ )
82+
83+ existing_versions = [
84+ x for x in os .listdir (LLVM_PYTHON_ROOT ) if x .startswith ("python" )
85+ ]
86+
87+ THIS_PYTHON_LLVM_PATH = f"{ LLVM_PYTHON_ROOT } /python{ sys .version_info .major } .{ sys .version_info .minor } /site-packages"
88+ if not os .path .exists (THIS_PYTHON_LLVM_PATH ):
89+ raise ImportError (
90+ "Your system has LLVM/LLDB installed, but it is not the same version as the Python interpreter "
91+ f"you are using; found: { ', ' .join (existing_versions )} , but the current Python version is "
92+ f"{ sys .version_info .major } .{ sys .version_info .minor } . Please install the same "
93+ "version of LLVM/LLDB as your Python interpreter."
94+ )
95+
96+ sys .path .append (THIS_PYTHON_LLVM_PATH )
97+
98+ import lldb
99+
100+ logging .info ("Creating debugger..." )
66101 debugger = lldb .SBDebugger .Create ()
67102 debugger .SetAsync (False )
68- print (f"Creating target of { args . exe } ..." )
69- target = debugger .CreateTargetWithFileAndArch (args . exe , None )
70- print ("Setting breakpoint for _sendFinishLaunchingNotification..." )
103+ logging . info (f"Creating target of { exe } ..." )
104+ target = debugger .CreateTargetWithFileAndArch (exe , None )
105+ logging . info ("Setting breakpoint for _sendFinishLaunchingNotification..." )
71106 target .BreakpointCreateByName ("_sendFinishLaunchingNotification" )
72107
73- print ("Setting breakpoint for _handleAEOpenEvent:..." )
108+ logging . info ("Setting breakpoint for _handleAEOpenEvent:..." )
74109 target .BreakpointCreateByName ("_handleAEOpenEvent:" )
75110
76- print ("Setting breakpoint for [CKContainer containerWithIdentifier:]..." )
111+ logging . info ("Setting breakpoint for [CKContainer containerWithIdentifier:]..." )
77112 # let's break in the CloudKit code and early exit the function before it can raise an exception:
78113 target .BreakpointCreateByName ("[CKContainer containerWithIdentifier:]" )
79114
80- print ("Setting breakpoint for ___lldb_unnamed_symbol[0-9]+..." )
115+ logging . info ("Setting breakpoint for ___lldb_unnamed_symbol[0-9]+..." )
81116 # In later Keynote versions, 'containerWithIdentifier' isn't called directly, but we can break on similar methods:
82117 # Note: this __lldb_unnamed_symbol hack was determined by painstaking experimentation. It will break again for sure.
83118 target .BreakpointCreateByRegex ("___lldb_unnamed_symbol[0-9]+" , "CloudKit" )
84119
85- print ("Launching process..." )
120+ logging . info ("Launching process..." )
86121 process = target .LaunchSimple (None , None , os .getcwd ())
87122
88123 if not process :
89- raise ValueError ("Failed to launch process: " + args . exe )
124+ raise ValueError (f "Failed to launch process: { exe } " )
90125 try :
91- print ("Waiting for process to stop on a breakpoint..." )
92- while process .GetState () != lldb .eStateStopped :
93- print (f"Current state: { StateType (process .GetState ())} " )
126+ logging .info ("Waiting for process to stop on a breakpoint..." )
127+ while (
128+ process .GetState () != lldb .eStateStopped
129+ and process .GetState () != lldb .eStateExited
130+ ):
131+ logging .info (f"Current state: { StateType (process .GetState ())} " )
94132 time .sleep (0.1 )
95133
134+ if process .GetState () == lldb .eStateExited :
135+ raise ValueError (
136+ "Process exited before stopping on a breakpoint. "
137+ "Ensure the process is properly code signed."
138+ )
139+
96140 while process .GetState () == lldb .eStateStopped :
97141 thread = process .GetThreadAtIndex (0 )
98- print (f"Thread: { thread } stopped at: { StopReason (thread .GetStopReason ())} " )
142+ logging .info (
143+ f"Thread: { thread } stopped at: { StopReason (thread .GetStopReason ())} "
144+ )
99145 match thread .GetStopReason ():
100146 case lldb .eStopReasonBreakpoint :
101147 if any (
@@ -113,7 +159,7 @@ def main():
113159 else :
114160 break
115161 case lldb .eStopReasonException :
116- print (repr (thread ) + "\n " )
162+ logging . info (repr (thread ) + "\n " )
117163 raise NotImplementedError (
118164 f"LLDB caught exception, { __file__ } needs to be updated to handle."
119165 )
@@ -134,11 +180,8 @@ def main():
134180 if x .strip ()
135181 ]
136182 mapping = [(int (a ), b .split (" " )[- 1 ]) for a , b in split if "null" not in b ]
137- print (f"Extracted mapping with { len (mapping ):,} elements." )
138- results = json .dumps (dict (sorted (mapping )), indent = 2 )
139- with open (args .output , "w" ) as f :
140- f .write (results )
141- print (f"Wrote { len (results ):,} bytes of mapping to { args .output } ." )
183+ logging .info (f"Extracted mapping with { len (mapping ):,} elements." )
184+ return dict (sorted (mapping ))
142185 finally :
143186 process .Kill ()
144187
0 commit comments