1*4d495c6eSApple OSS Distributions# XNU debugging 2*4d495c6eSApple OSS Distributions 3*4d495c6eSApple OSS DistributionsDebugging XNU through kernel core files or with a live device. 4*4d495c6eSApple OSS Distributions 5*4d495c6eSApple OSS Distributions## Overview 6*4d495c6eSApple OSS Distributions 7*4d495c6eSApple OSS DistributionsXNU’s debugging macros are compatible with Python 3.9+. Please be careful about pulling 8*4d495c6eSApple OSS Distributionsin the latest language features. Some users are living on older Xcodes and may not have the newest 9*4d495c6eSApple OSS DistributionsPython installed. 10*4d495c6eSApple OSS Distributions 11*4d495c6eSApple OSS Distributions## General coding tips 12*4d495c6eSApple OSS Distributions 13*4d495c6eSApple OSS Distributions### Imports 14*4d495c6eSApple OSS Distributions 15*4d495c6eSApple OSS DistributionsThe current implementation re-exports a lot of submodules through the XNU main module. This leads to some 16*4d495c6eSApple OSS Distributionssurprising behavior: 17*4d495c6eSApple OSS Distributions 18*4d495c6eSApple OSS Distributions* Name collisions at the top level may override methods with unexpected results. 19*4d495c6eSApple OSS Distributions* New imports may change the order of imports, leading to some surpising side effects. 20*4d495c6eSApple OSS Distributions 21*4d495c6eSApple OSS DistributionsPlease avoid `from xnu import *` where possible and always explicitly import only what is 22*4d495c6eSApple OSS Distributionsrequired from other modules. 23*4d495c6eSApple OSS Distributions 24*4d495c6eSApple OSS Distributions### Checking the type of an object 25*4d495c6eSApple OSS Distributions 26*4d495c6eSApple OSS DistributionsAvoid testing for a `type` explicitly like `type(obj) == type`. 27*4d495c6eSApple OSS DistributionsInstead, always use the inheritance-sensitive `isinstance(obj, type)`. 28*4d495c6eSApple OSS Distributions 29*4d495c6eSApple OSS Distributions### Dealing with binary data 30*4d495c6eSApple OSS Distributions 31*4d495c6eSApple OSS DistributionsIt’s recommended to use **bytearray**, **bytes**, and **memoryviews** instead of a string. 32*4d495c6eSApple OSS DistributionsSome LLDB APIs no longer accept a string in place of binary data in Python 3. 33*4d495c6eSApple OSS Distributions 34*4d495c6eSApple OSS Distributions### Accessing large amounts of binary data (or accessing small amounts frequently) 35*4d495c6eSApple OSS Distributions 36*4d495c6eSApple OSS DistributionsIn case you're planning on accessing large contiguous blocks of memory (e.g. reading a whole 10KB of memory), 37*4d495c6eSApple OSS Distributionsor you're accessing small semi-contiguous chunks (e.g. if you're parsing large structured data), then it might 38*4d495c6eSApple OSS Distributionsbe hugely beneficial performance-wise to make use of the `io.SBProcessRawIO` class. Furthermore, if you're in 39*4d495c6eSApple OSS Distributionsa hurry and just want to read one specific chunk once, then it might be easier to use `LazyTarget.GetProcess().ReadMemory()` 40*4d495c6eSApple OSS Distributionsdirectly. 41*4d495c6eSApple OSS Distributions 42*4d495c6eSApple OSS DistributionsIn other words, avoid the following: 43*4d495c6eSApple OSS Distributions 44*4d495c6eSApple OSS Distributions``` 45*4d495c6eSApple OSS Distributionsdata_ptr = kern.GetValueFromAddress(start_addr, 'uint8_t *') 46*4d495c6eSApple OSS Distributionswith open(filepath, 'wb') as f: 47*4d495c6eSApple OSS Distributions f.write(data_ptr[:4096]) 48*4d495c6eSApple OSS Distributions``` 49*4d495c6eSApple OSS Distributions 50*4d495c6eSApple OSS DistributionsAnd instead use: 51*4d495c6eSApple OSS Distributions 52*4d495c6eSApple OSS Distributions``` 53*4d495c6eSApple OSS Distributionsfrom core.io import SBProcessRawIO 54*4d495c6eSApple OSS Distributionsimport shutil 55*4d495c6eSApple OSS Distributions 56*4d495c6eSApple OSS Distributionsio_access = SBProcessRawIO(LazyTarget.GetProcess(), start_addr, 4096) 57*4d495c6eSApple OSS Distributionswith open(filepath, 'wb') as f: 58*4d495c6eSApple OSS Distributions shutil.copyfileobj(io_access, f) 59*4d495c6eSApple OSS Distributions``` 60*4d495c6eSApple OSS Distributions 61*4d495c6eSApple OSS DistributionsOr, if you're in a hurry: 62*4d495c6eSApple OSS Distributions 63*4d495c6eSApple OSS Distributions``` 64*4d495c6eSApple OSS Distributionserr = lldb.SBError() 65*4d495c6eSApple OSS Distributionsmy_data = LazyTarget.GetProcess().ReadMemory(start_addr, length, err) 66*4d495c6eSApple OSS Distributionsif err.Success(): 67*4d495c6eSApple OSS Distributions # Use my precious data 68*4d495c6eSApple OSS Distributions pass 69*4d495c6eSApple OSS Distributions``` 70*4d495c6eSApple OSS Distributions 71*4d495c6eSApple OSS DistributionsFor small semi-contiguous chunks, you can map the whole region and access random chunks from it like so: 72*4d495c6eSApple OSS Distributions 73*4d495c6eSApple OSS Distributions``` 74*4d495c6eSApple OSS Distributionsfrom core.io import SBProcessRawIO 75*4d495c6eSApple OSS Distributions 76*4d495c6eSApple OSS Distributionsio_access = SBProcessRawIO(LazyTarget.GetProcess(), start_addr, size) 77*4d495c6eSApple OSS Distributionsio_access.seek(my_struct_offset) 78*4d495c6eSApple OSS Distributionsmy_struct_contents = io_access.read(my_struct_size) 79*4d495c6eSApple OSS Distributions``` 80*4d495c6eSApple OSS Distributions 81*4d495c6eSApple OSS DistributionsNot only that, but you can also tack on a BufferedRandom class on top of the SBProcessRawIO instance, which 82*4d495c6eSApple OSS Distributionsprovides you with buffering (aka caching) in case your random small chunk accesses are repeated: 83*4d495c6eSApple OSS Distributions 84*4d495c6eSApple OSS Distributions``` 85*4d495c6eSApple OSS Distributionsfrom core.io import SBProcessRawIO 86*4d495c6eSApple OSS Distributionsfrom io import BufferedRandom 87*4d495c6eSApple OSS Distributions 88*4d495c6eSApple OSS Distributionsio_access = SBProcessRawIO(LazyTarget.GetProcess(), start_addr, size) 89*4d495c6eSApple OSS Distributionsbuffered_io = BufferedRandom(io_access) 90*4d495c6eSApple OSS Distributions# And then use buffered_io for your accesses 91*4d495c6eSApple OSS Distributions``` 92*4d495c6eSApple OSS Distributions 93*4d495c6eSApple OSS Distributions### Encoding data to strings and back 94*4d495c6eSApple OSS Distributions 95*4d495c6eSApple OSS DistributionsAll strings are now `unicode` and must be converted between binary data and strings explicitly. 96*4d495c6eSApple OSS DistributionsWhen no explicit encoding is selected then UTF-8 is the default. 97*4d495c6eSApple OSS Distributions 98*4d495c6eSApple OSS Distributions``` 99*4d495c6eSApple OSS Distributionsmystring = mybytes.decode() 100*4d495c6eSApple OSS Distributionsmybytes = mystring.encode() 101*4d495c6eSApple OSS Distributions``` 102*4d495c6eSApple OSS DistributionsIn most cases **utf-8** will work but be careful to be sure that the encoding matches your data. 103*4d495c6eSApple OSS Distributions 104*4d495c6eSApple OSS DistributionsThere are two options to consider when trying to get a string out of the raw data without knowing if 105*4d495c6eSApple OSS Distributionsthey are valid string or not: 106*4d495c6eSApple OSS Distributions 107*4d495c6eSApple OSS Distributions* **lossy conversion** - escapes all non-standard characters in form of ‘\xNNN’ 108*4d495c6eSApple OSS Distributions* **lossless conversion** - maps invalid characters to special unicode range so it can reconstruct 109*4d495c6eSApple OSS Distributionsthe string precisely 110*4d495c6eSApple OSS Distributions 111*4d495c6eSApple OSS DistributionsWhich to use depends on the transformation goals. The lossy conversion produces a printable string 112*4d495c6eSApple OSS Distributionswith strange characters in it. The lossless option is meant to be used when a string is only a transport 113*4d495c6eSApple OSS Distributionsmechanism and needs to be converted back to original values later. 114*4d495c6eSApple OSS Distributions 115*4d495c6eSApple OSS DistributionsSwitch the method by using `errors` handler during conversion: 116*4d495c6eSApple OSS Distributions 117*4d495c6eSApple OSS Distributions``` 118*4d495c6eSApple OSS Distributions# Lossy escapes invalid chars 119*4d495c6eSApple OSS Distributionsb.decode('utf-8', errors='`backslashreplace'`) 120*4d495c6eSApple OSS Distributions# Lossy removes invalid chars 121*4d495c6eSApple OSS Distributionsb.decode('utf-8', errors='ignore') 122*4d495c6eSApple OSS Distributions# Loss-less but may likely fail to print() 123*4d495c6eSApple OSS Distributionsb.decode('utf-8', errors='surrogateescape') 124*4d495c6eSApple OSS Distributions``` 125*4d495c6eSApple OSS Distributions 126*4d495c6eSApple OSS Distributions### Dealing with signed numbers 127*4d495c6eSApple OSS Distributions 128*4d495c6eSApple OSS DistributionsPython's int has unlimited precision. This may be surprising for kernel developers who expect 129*4d495c6eSApple OSS Distributionsthe behavior follows twos complement. 130*4d495c6eSApple OSS Distributions 131*4d495c6eSApple OSS DistributionsAlways use **unsigned()** or **signed()** regardless of what the actual underlying type is 132*4d495c6eSApple OSS Distributionsto ensure that macros use the correct semantics. 133*4d495c6eSApple OSS Distributions 134*4d495c6eSApple OSS Distributions## Testing changes 135*4d495c6eSApple OSS Distributions 136*4d495c6eSApple OSS DistributionsPlease check documentation here: <doc:macro_testing> 137*4d495c6eSApple OSS Distributions 138*4d495c6eSApple OSS Distributions### Coding style 139*4d495c6eSApple OSS Distributions 140*4d495c6eSApple OSS DistributionsUse a static analyzer like **pylint** or **flake8** to check the macro source code: 141*4d495c6eSApple OSS Distributions 142*4d495c6eSApple OSS Distributions``` 143*4d495c6eSApple OSS Distributions$ python3 -m pip install --user pylint flake8 144*4d495c6eSApple OSS Distributions 145*4d495c6eSApple OSS Distributions# Run the lint either by setting your path to point to one of the runtimes 146*4d495c6eSApple OSS Distributions# or through python 147*4d495c6eSApple OSS Distributions$ python3 -m pylint <src files/dirs> 148*4d495c6eSApple OSS Distributions$ python3 -m flake8 <src files/dirs> 149*4d495c6eSApple OSS Distributions``` 150*4d495c6eSApple OSS Distributions 151*4d495c6eSApple OSS Distributions### Correctness 152*4d495c6eSApple OSS Distributions 153*4d495c6eSApple OSS DistributionsEnsure the macro matches what LLDB returns from the REPL. For example, compare `showproc(xxx)` with `p/x *(proc_t)xxx`. 154*4d495c6eSApple OSS Distributions 155*4d495c6eSApple OSS Distributions``` 156*4d495c6eSApple OSS Distributions# 1. Run LLDB with debug options set 157*4d495c6eSApple OSS Distributions$ DEBUG_XNU_LLDBMACROS=1 xcrun -sdk <sdk> lldb -c core <dsympath>/mach_kernel 158*4d495c6eSApple OSS Distributions 159*4d495c6eSApple OSS Distributions# 2. Optionally load modified operating system plugin 160*4d495c6eSApple OSS Distributions(lldb) settings set target.process.python-os-plugin-path <srcpath>/tools/lldbmacros/core/operating_system.py 161*4d495c6eSApple OSS Distributions 162*4d495c6eSApple OSS Distributions# 3. Load modified scripts 163*4d495c6eSApple OSS Distributions(lldb) command script import <srcpath>/tools/lldbmacros/xnu.py 164*4d495c6eSApple OSS Distributions 165*4d495c6eSApple OSS Distributions# 4. Exercise macros 166*4d495c6eSApple OSS Distributions``` 167*4d495c6eSApple OSS Distributions 168*4d495c6eSApple OSS DistributionsDepending on the change, test other targets and architectures (for instance, both Astris and KDP). 169*4d495c6eSApple OSS Distributions 170*4d495c6eSApple OSS Distributions### Regression 171*4d495c6eSApple OSS Distributions 172*4d495c6eSApple OSS DistributionsThis is simpler than previous step because the goal is to ensure behavior has not changed. 173*4d495c6eSApple OSS DistributionsYou can speed up few things by using local symbols: 174*4d495c6eSApple OSS Distributions 175*4d495c6eSApple OSS Distributions``` 176*4d495c6eSApple OSS Distributions# 1. Get a coredump from a device and kernel UUID 177*4d495c6eSApple OSS Distributions# 2. Grab symbols with dsymForUUID 178*4d495c6eSApple OSS Distributions$ dsymForUUID --nocache --copyExecutable --copyDestination <dsym path> 179*4d495c6eSApple OSS Distributions 180*4d495c6eSApple OSS Distributions# 3. Run lldb with local symbols to avoid dsymForUUID NFS 181*4d495c6eSApple OSS Distributions 182*4d495c6eSApple OSS Distributions$ xcrun -sdk <sdk> lldb -c core <dsym_path>/<kernel image> 183*4d495c6eSApple OSS Distributions``` 184*4d495c6eSApple OSS Distributions 185*4d495c6eSApple OSS DistributionsThe actual steps are identical to previous testing. Run of a macro to different file with `-o <outfile>` 186*4d495c6eSApple OSS Distributionsoption. Then run `diff` on the outputs of the baseline and modified code: 187*4d495c6eSApple OSS Distributions 188*4d495c6eSApple OSS Distributions* No environment variables to get baseline 189*4d495c6eSApple OSS Distributions* Modified dSYM as described above 190*4d495c6eSApple OSS Distributions 191*4d495c6eSApple OSS DistributionsIt’s difficult to make this automated: 192*4d495c6eSApple OSS Distributions 193*4d495c6eSApple OSS Distributions* Some macros needs arguments which must be found in a core file. 194*4d495c6eSApple OSS Distributions* Some macros take a long time to run against a target (more than 30 minutes). Instead, a core dump 195*4d495c6eSApple OSS Distributions should be taken and then inspected afterwards, but this ties up a lab device for the duration of the 196*4d495c6eSApple OSS Distributions test. 197*4d495c6eSApple OSS Distributions* Even with coredumps, testing the macros takes too long in our automation system and triggers the 198*4d495c6eSApple OSS Distributions failsafe timeout. 199*4d495c6eSApple OSS Distributions 200*4d495c6eSApple OSS Distributions### Code coverage 201*4d495c6eSApple OSS Distributions 202*4d495c6eSApple OSS DistributionsUse code coverage to check which parts of macros have actually been tested. 203*4d495c6eSApple OSS DistributionsInstall **coverage** lib with: 204*4d495c6eSApple OSS Distributions 205*4d495c6eSApple OSS Distributions``` 206*4d495c6eSApple OSS Distributions$ python3 -m pip install --user coverage 207*4d495c6eSApple OSS Distributions``` 208*4d495c6eSApple OSS Distributions 209*4d495c6eSApple OSS DistributionsThen collect coverage:. 210*4d495c6eSApple OSS Distributions 211*4d495c6eSApple OSS Distributions``` 212*4d495c6eSApple OSS Distributions(lldb) xnudebug coverage /tmp/coverage.cov showallstacks 213*4d495c6eSApple OSS Distributions 214*4d495c6eSApple OSS Distributions... 215*4d495c6eSApple OSS Distributions 216*4d495c6eSApple OSS DistributionsCoverage info saved to: "/tmp/coverage.cov" 217*4d495c6eSApple OSS Distributions``` 218*4d495c6eSApple OSS Distributions 219*4d495c6eSApple OSS DistributionsYou can then run `coverage html --data-file=/tmp/coverage.cov` in your terminal 220*4d495c6eSApple OSS Distributionsto generate an HTML report. 221*4d495c6eSApple OSS Distributions 222*4d495c6eSApple OSS Distributions 223*4d495c6eSApple OSS DistributionsCombine coverage from multiple files: 224*4d495c6eSApple OSS Distributions 225*4d495c6eSApple OSS Distributions``` 226*4d495c6eSApple OSS Distributions# Point PATH to local python where coverage is installed. 227*4d495c6eSApple OSS Distributions$ export PATH="$HOME/Library/Python/3.8/bin:$PATH" 228*4d495c6eSApple OSS Distributions 229*4d495c6eSApple OSS Distributions# Use --keep to avoid deletion of input files after merge. 230*4d495c6eSApple OSS Distributions$ coverage combine --keep <list of .coverage files or dirs to scan> 231*4d495c6eSApple OSS Distributions 232*4d495c6eSApple OSS Distributions# Get HTML report or use other subcommands to inspect. 233*4d495c6eSApple OSS Distributions$ coverage html 234*4d495c6eSApple OSS Distributions``` 235*4d495c6eSApple OSS Distributions 236*4d495c6eSApple OSS DistributionsIt is possible to start coverage collection **before** importing the operating system library and 237*4d495c6eSApple OSS Distributionsloading macros to check code run during bootstrapping. 238*4d495c6eSApple OSS Distributions 239*4d495c6eSApple OSS DistributionsFor this, you'll need to run coverage manually: 240*4d495c6eSApple OSS Distributions# 1. Start LLDB 241*4d495c6eSApple OSS Distributions 242*4d495c6eSApple OSS Distributions# 2. Load and start code coverage recording. 243*4d495c6eSApple OSS Distributions(lldb) script import coverage 244*4d495c6eSApple OSS Distributions(lldb) script cov = coverage.Coverage(data_file=_filepath_) 245*4d495c6eSApple OSS Distributions(lldb) script cov.start() 246*4d495c6eSApple OSS Distributions 247*4d495c6eSApple OSS Distributions# 3. Load macros 248*4d495c6eSApple OSS Distributions 249*4d495c6eSApple OSS Distributions# 4. Collect the coverage. 250*4d495c6eSApple OSS Distributions(lldb) script cov.stop() 251*4d495c6eSApple OSS Distributions(lldb) script cov.save() 252*4d495c6eSApple OSS Distributions 253*4d495c6eSApple OSS Distributions### Performance testing 254*4d495c6eSApple OSS Distributions 255*4d495c6eSApple OSS DistributionsSome macros can run for a long time. Some code may be costly even if it looks simple because objects 256*4d495c6eSApple OSS Distributionsaren’t cached or too many temporary objects are created. Simple profiling is similar to collecting 257*4d495c6eSApple OSS Distributionscode coverage. 258*4d495c6eSApple OSS Distributions 259*4d495c6eSApple OSS DistributionsFirst setup your environment: 260*4d495c6eSApple OSS Distributions 261*4d495c6eSApple OSS Distributions``` 262*4d495c6eSApple OSS Distributions# Install gprof2dot 263*4d495c6eSApple OSS Distributions$ python3 -m pip install gprof2dot 264*4d495c6eSApple OSS Distributions# Install graphviz 265*4d495c6eSApple OSS Distributions$ brew install graphviz 266*4d495c6eSApple OSS Distributions``` 267*4d495c6eSApple OSS Distributions 268*4d495c6eSApple OSS DistributionsThen to profile commands, follow this sequence: 269*4d495c6eSApple OSS Distributions 270*4d495c6eSApple OSS Distributions``` 271*4d495c6eSApple OSS Distributions(lldb) xnudebug profile /tmp/macro.prof showcurrentstacks 272*4d495c6eSApple OSS Distributions[... command outputs ...] 273*4d495c6eSApple OSS Distributions 274*4d495c6eSApple OSS Distributions Ordered by: cumulative time 275*4d495c6eSApple OSS Distributions List reduced from 468 to 30 due to restriction <30> 276*4d495c6eSApple OSS Distributions 277*4d495c6eSApple OSS Distributions ncalls tottime percall cumtime percall filename:lineno(function) 278*4d495c6eSApple OSS Distributions [... profiling output ...] 279*4d495c6eSApple OSS Distributions 280*4d495c6eSApple OSS DistributionsProfile info saved to "/tmp/macro.prof" 281*4d495c6eSApple OSS Distributions``` 282*4d495c6eSApple OSS Distributions 283*4d495c6eSApple OSS DistributionsThen to visualize callgraphs in context, in a separate shell: 284*4d495c6eSApple OSS Distributions 285*4d495c6eSApple OSS Distributions``` 286*4d495c6eSApple OSS Distributions# Now convert the file to a colored SVG call graph 287*4d495c6eSApple OSS Distributions$ python3 -m gprof2dot -f pstats /tmp/macro.prof -o /tmp/call.dot 288*4d495c6eSApple OSS Distributions$ dot -O -T svg /tmp/call.dot 289*4d495c6eSApple OSS Distributions 290*4d495c6eSApple OSS Distributions# and view it in your favourite viewer 291*4d495c6eSApple OSS Distributions$ open /tmp/call.dot.svg 292*4d495c6eSApple OSS Distributions``` 293*4d495c6eSApple OSS Distributions 294*4d495c6eSApple OSS Distributions## Debugging your changes 295*4d495c6eSApple OSS Distributions 296*4d495c6eSApple OSS Distributions### Get detailed exception report 297*4d495c6eSApple OSS Distributions 298*4d495c6eSApple OSS DistributionsThe easiest way to debug an exception is to re-run your macro with the `--debug` option. 299*4d495c6eSApple OSS DistributionsThis turns on more detailed output for each stack frame that includes source lines 300*4d495c6eSApple OSS Distributionsand local variables. 301*4d495c6eSApple OSS Distributions 302*4d495c6eSApple OSS Distributions### File a radar 303*4d495c6eSApple OSS Distributions 304*4d495c6eSApple OSS DistributionsTo report an actionable radar, please use re-run your failing macro with `--radar`. 305*4d495c6eSApple OSS DistributionsThis will collect additional logs to an archive located in `/tmp`. 306*4d495c6eSApple OSS Distributions 307*4d495c6eSApple OSS DistributionsUse the link provided to create a new radar. 308*4d495c6eSApple OSS Distributions 309*4d495c6eSApple OSS Distributions### Debugging with pdb 310*4d495c6eSApple OSS Distributions 311*4d495c6eSApple OSS DistributionsYES, It is possible to use a debugger to debug your macro! 312*4d495c6eSApple OSS Distributions 313*4d495c6eSApple OSS DistributionsThe steps are similar to testing techniques described above (use scripting interactive mode). There is no point to 314*4d495c6eSApple OSS Distributionsdocument the debugger itself. Lets focus on how to use it on a real life example. The debugger used here is PDB which 315*4d495c6eSApple OSS Distributionsis part of Python installation so works out of the box. 316*4d495c6eSApple OSS Distributions 317*4d495c6eSApple OSS DistributionsProblem: Something wrong is going on with addkext macro. What now? 318*4d495c6eSApple OSS Distributions 319*4d495c6eSApple OSS Distributions (lldb) addkext -N com.apple.driver.AppleT8103PCIeC 320*4d495c6eSApple OSS Distributions Failed to read MachO for address 18446741875027613136 errormessage: seek to offset 2169512 is outside window [0, 1310] 321*4d495c6eSApple OSS Distributions Failed to read MachO for address 18446741875033537424 errormessage: seek to offset 8093880 is outside window [0, 1536] 322*4d495c6eSApple OSS Distributions Failed to read MachO for address 18446741875033568304 errormessage: seek to offset 8124208 is outside window [0, 1536] 323*4d495c6eSApple OSS Distributions ... 324*4d495c6eSApple OSS Distributions Fetching dSYM for 049b9a29-2efc-32c0-8a7f-5f29c12b870c 325*4d495c6eSApple OSS Distributions Adding dSYM (049b9a29-2efc-32c0-8a7f-5f29c12b870c) for /Library/Caches/com.apple.bni.symbols/bursar.apple.com/dsyms/StarE/AppleEmbeddedPCIE/AppleEmbeddedPCIE-502.100.35~3/049B9A29-2EFC-32C0-8A7F-5F29C12B870C/AppleT8103PCIeC 326*4d495c6eSApple OSS Distributions section '__TEXT' loaded at 0xfffffe001478c780 327*4d495c6eSApple OSS Distributions 328*4d495c6eSApple OSS DistributionsThere is no exception, lot of errors and no output. So what next? 329*4d495c6eSApple OSS DistributionsTry to narrow the problem down to an isolated piece of macro code: 330*4d495c6eSApple OSS Distributions 331*4d495c6eSApple OSS Distributions 1. Try to get values of globals through regular LLDB commands 332*4d495c6eSApple OSS Distributions 2. Use interactive mode and invoke functions with arguments directly. 333*4d495c6eSApple OSS Distributions 334*4d495c6eSApple OSS DistributionsAfter inspecting addkext macro code and calling few functions with arguments directly we can see that there is an 335*4d495c6eSApple OSS Distributionsexception in the end. It was just captured in try/catch block. So the simplified reproducer is: 336*4d495c6eSApple OSS Distributions 337*4d495c6eSApple OSS Distributions (lldb) script 338*4d495c6eSApple OSS Distributions >>> import lldb 339*4d495c6eSApple OSS Distributions >>> import xnu 340*4d495c6eSApple OSS Distributions >>> err = lldb.SBError() 341*4d495c6eSApple OSS Distributions >>> data = xnu.LazyTarget.GetProcess().ReadMemory(0xfffffe0014c0f3f0, 0x000000000001b5d0, err) 342*4d495c6eSApple OSS Distributions >>> m = macho.MemMacho(data, len(data)) 343*4d495c6eSApple OSS Distributions Traceback (most recent call last): 344*4d495c6eSApple OSS Distributions File "<console>", line 1, in <module> 345*4d495c6eSApple OSS Distributions File ".../lldbmacros/macho.py", line 91, in __init__ 346*4d495c6eSApple OSS Distributions self.load(fp) 347*4d495c6eSApple OSS Distributions File ".../site-packages/macholib/MachO.py", line 133, in load 348*4d495c6eSApple OSS Distributions self.load_header(fh, 0, size) 349*4d495c6eSApple OSS Distributions File ".../site-packages/macholib/MachO.py", line 168, in load_header 350*4d495c6eSApple OSS Distributions hdr = MachOHeader(self, fh, offset, size, magic, hdr, endian) 351*4d495c6eSApple OSS Distributions File ".../site-packages/macholib/MachO.py", line 209, in __init__ 352*4d495c6eSApple OSS Distributions self.load(fh) 353*4d495c6eSApple OSS Distributions File ".../lldbmacros/macho.py", line 23, in new_load 354*4d495c6eSApple OSS Distributions _old_MachOHeader_load(s, fh) 355*4d495c6eSApple OSS Distributions File ".../site-packages/macholib/MachO.py", line 287, in load 356*4d495c6eSApple OSS Distributions fh.seek(seg.offset) 357*4d495c6eSApple OSS Distributions File ".../site-packages/macholib/util.py", line 91, in seek 358*4d495c6eSApple OSS Distributions self._checkwindow(seekto, "seek") 359*4d495c6eSApple OSS Distributions File ".../site-packages/macholib/util.py", line 76, in _checkwindow 360*4d495c6eSApple OSS Distributions raise IOError( 361*4d495c6eSApple OSS Distributions OSError: seek to offset 9042440 is outside window [0, 112080] 362*4d495c6eSApple OSS Distributions 363*4d495c6eSApple OSS DistributionsClearly an external library is involved and execution flow jumps between dSYM and the library few times. 364*4d495c6eSApple OSS DistributionsLets try to look around with a debugger. 365*4d495c6eSApple OSS Distributions 366*4d495c6eSApple OSS Distributions (lldb) script 367*4d495c6eSApple OSS Distributions # Prepare data variable as described above. 368*4d495c6eSApple OSS Distributions 369*4d495c6eSApple OSS Distributions # Run last statement with debugger. 370*4d495c6eSApple OSS Distributions >>> import pdb 371*4d495c6eSApple OSS Distributions >>> pdb.run('m = macho.MemMacho(data, len(data))', globals(), locals()) 372*4d495c6eSApple OSS Distributions > <string>(1)<module>() 373*4d495c6eSApple OSS Distributions 374*4d495c6eSApple OSS Distributions # Show debugger's help 375*4d495c6eSApple OSS Distributions (Pdb) help 376*4d495c6eSApple OSS Distributions 377*4d495c6eSApple OSS DistributionsIt is not possible to break on exception. Python uses them a lot so it is better to put a breakpoint to source 378*4d495c6eSApple OSS Distributionscode. This puts breakpoint on the IOError exception mentioned above. 379*4d495c6eSApple OSS Distributions 380*4d495c6eSApple OSS Distributions (Pdb) break ~/Library/Python/3.8/lib/python/site-packages/macholib/util.py:76 381*4d495c6eSApple OSS Distributions Breakpoint 4 at ~/Library/Python/3.8/lib/python/site-packages/macholib/util.py:76 382*4d495c6eSApple OSS Distributions 383*4d495c6eSApple OSS DistributionsYou can now single step or continue the execution as usuall for a debugger. 384*4d495c6eSApple OSS Distributions 385*4d495c6eSApple OSS Distributions (Pdb) cont 386*4d495c6eSApple OSS Distributions > /Users/tjedlicka/Library/Python/3.8/lib/python/site-packages/macholib/util.py(76)_checkwindow() 387*4d495c6eSApple OSS Distributions -> raise IOError( 388*4d495c6eSApple OSS Distributions (Pdb) bt 389*4d495c6eSApple OSS Distributions /Volumes/.../Python3.framework/Versions/3.8/lib/python3.8/bdb.py(580)run() 390*4d495c6eSApple OSS Distributions -> exec(cmd, globals, locals) 391*4d495c6eSApple OSS Distributions <string>(1)<module>() 392*4d495c6eSApple OSS Distributions /Volumes/...dSYM/Contents/Resources/Python/lldbmacros/macho.py(91)__init__() 393*4d495c6eSApple OSS Distributions -> self.load(fp) 394*4d495c6eSApple OSS Distributions /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/MachO.py(133)load() 395*4d495c6eSApple OSS Distributions -> self.load_header(fh, 0, size) 396*4d495c6eSApple OSS Distributions /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/MachO.py(168)load_header() 397*4d495c6eSApple OSS Distributions -> hdr = MachOHeader(self, fh, offset, size, magic, hdr, endian) 398*4d495c6eSApple OSS Distributions /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/MachO.py(209)__init__() 399*4d495c6eSApple OSS Distributions -> self.load(fh) 400*4d495c6eSApple OSS Distributions /Volumes/...dSYM/Contents/Resources/Python/lldbmacros/macho.py(23)new_load() 401*4d495c6eSApple OSS Distributions -> _old_MachOHeader_load(s, fh) 402*4d495c6eSApple OSS Distributions /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/MachO.py(287)load() 403*4d495c6eSApple OSS Distributions -> fh.seek(seg.offset) 404*4d495c6eSApple OSS Distributions /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/util.py(91)seek() 405*4d495c6eSApple OSS Distributions -> self._checkwindow(seekto, "seek") 406*4d495c6eSApple OSS Distributions > /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/util.py(76)_checkwindow() 407*4d495c6eSApple OSS Distributions -> raise IOError( 408*4d495c6eSApple OSS Distributions 409*4d495c6eSApple OSS Distributions 410*4d495c6eSApple OSS DistributionsNow we can move a frame above and inspect stopped target: 411*4d495c6eSApple OSS Distributions 412*4d495c6eSApple OSS Distributions # Show current frame arguments 413*4d495c6eSApple OSS Distributions (Pdb) up 414*4d495c6eSApple OSS Distributions (Pdb) a 415*4d495c6eSApple OSS Distributions self = <fileview [0, 112080] <macho.MemFile object at 0x1075cafd0>> 416*4d495c6eSApple OSS Distributions offset = 9042440 417*4d495c6eSApple OSS Distributions whence = 0 418*4d495c6eSApple OSS Distributions 419*4d495c6eSApple OSS Distributions # globals, local or expressons 420*4d495c6eSApple OSS Distributions (Pdb) p type(seg.offset) 421*4d495c6eSApple OSS Distributions <class 'macholib.ptypes.p_uint32'> 422*4d495c6eSApple OSS Distributions (Pdb) p hex(seg.offset) 423*4d495c6eSApple OSS Distributions '0x89fa08' 424*4d495c6eSApple OSS Distributions 425*4d495c6eSApple OSS Distributions # Find attributes of a Python object. 426*4d495c6eSApple OSS Distributions (Pdb) p dir(section_cls) 427*4d495c6eSApple OSS Distributions ['__class__', '__cmp__', ... ,'reserved3', 'sectname', 'segname', 'size', 'to_fileobj', 'to_mmap', 'to_str'] 428*4d495c6eSApple OSS Distributions (Pdb) p section_cls.sectname 429*4d495c6eSApple OSS Distributions <property object at 0x1077bbef0> 430*4d495c6eSApple OSS Distributions 431*4d495c6eSApple OSS DistributionsUnfortunately everything looks correct but there is actually one ineteresting frame in the stack. The one which 432*4d495c6eSApple OSS Distributionsprovides the offset to the seek method. Lets see where we are in the source code. 433*4d495c6eSApple OSS Distributions 434*4d495c6eSApple OSS Distributions (Pdb) up 435*4d495c6eSApple OSS Distributions > /Users/tjedlicka/Library/Python/3.8/lib/python/site-packages/macholib/MachO.py(287)load() 436*4d495c6eSApple OSS Distributions -> fh.seek(seg.offset) 437*4d495c6eSApple OSS Distributions (Pdb) list 438*4d495c6eSApple OSS Distributions 282 not_zerofill = (seg.flags & S_ZEROFILL) != S_ZEROFILL 439*4d495c6eSApple OSS Distributions 283 if seg.offset > 0 and seg.size > 0 and not_zerofill: 440*4d495c6eSApple OSS Distributions 284 low_offset = min(low_offset, seg.offset) 441*4d495c6eSApple OSS Distributions 285 if not_zerofill: 442*4d495c6eSApple OSS Distributions 286 c = fh.tell() 443*4d495c6eSApple OSS Distributions 287 -> fh.seek(seg.offset) 444*4d495c6eSApple OSS Distributions 288 sd = fh.read(seg.size) 445*4d495c6eSApple OSS Distributions 289 seg.add_section_data(sd) 446*4d495c6eSApple OSS Distributions 290 fh.seek(c) 447*4d495c6eSApple OSS Distributions 291 segs.append(seg) 448*4d495c6eSApple OSS Distributions 292 # data is a list of segments 449*4d495c6eSApple OSS Distributions 450*4d495c6eSApple OSS DistributionsRunning debugger on working case and stepping through the load() method shows that this code is not present. 451*4d495c6eSApple OSS DistributionsThat means we are broken by a library update! Older versions of library do not load data for a section. 452