If you’ve read this far, you now have a solid foundation in debugging. You can find and attach to a process of interest, efficiently create regular expression breakpoints to cover a wide range of culprits, navigate the stack frame and tweak variables using the expression command.
It’s time to explore one of the best tools for finding code of interest through the powers of lldb. You’ve already seen it a few times, but in this chapter, you’ll take a deep dive into the image command.
The image command is an alias for the target modules subcommand. The image command specializes in querying information about code that lives within a module. “Module” is a generic term for executable code, like an executable or a shared library. Examples of shared libraries include frameworks like UIKit for iOS or dynamic libraries like libSystem.B.dylib. A module can apply to a shared library on disk or code that’s loaded into a process.
Listing Modules
You’ll continue using the Signals project. Fire up the project in Xcode and then build & run on a simulator.
Note: You might be wondering why you keep running on the simulator and not a device. It’s just easier and faster. You don’t have to worry about certificate permissions and the connection to the device and things like that. Also, the processor on your computer is, hopefully, faster than the one on your phone, so stopping and starting the app won’t take as long. If you want to run on device, most everything with the Signals app will work the same though. Go ahead.
At any point, suspend the program and type the following in lldb:
(lldb) image list
This command lists all the modules currently loaded. You’ll see a lot! For such a simple program to run in memory, all those modules had to be loaded into the process!
The start of the list should look something like the following:
Like some other commands, there’s a -b switch to make the output “brief”. Enter the following command:
(lldb) image list -b
Now you just get the names of each module.
The first module is the main executable, Signals. The second module is the dynamic link editor or, more simply, dyld. dyld is responsible for loading any code into memory and executes code well before any of your code has a chance to start running.
You can filter out modules by specifying their name. Type the following into lldb:
This is a useful way to find information about just the modules you want. The output has a few interesting pieces to note:
The module’s UUID prints first: E14C8373-8298-3E74-AFC5-61DDDC413228. The UUID is important for hunting down symbolic information and uniquely identifies the version of the Foundation framework.
Following the UUID is the load address: 0x00000001806ea000. This identifies where the module loads into the executable’s process space.
Finally, you have the full path to the module’s location on the disk.
Note: Some modules won’t be at the physical location they claim to be on the disk. This is likely because they’re part of the dyld shared cache, or dsc. dsc packs hundreds — sometimes thousands — of shared libraries together. Starting in macOS Monterey, Apple no longer includes the separated dynamic libraries on disk, leaving only dsc to explore if you’re not spelunking in memory. You’ll learn more about dsc at the end of this chapter.
Take a deeper dive into another common module, UIKit. Type the following into lldb:
(lldb) image dump symtab -s address UIKit
Symtab, file = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit, num_symbols = 3 (sorted by address):
Debug symbol
|Synthetic symbol
||Externally Visible
|||
Index UserID DSX Type File Address/Value Load Address Size Flags Name
------- ------ --- --------------- ------------------ ------------------ ------------------ ---------- ----------------------------------
[ 0] 0 Data 0x0000000000000fc8 0x00000001ac4ebfc8 0x0000000000000030 0x001e0000 UIKitVersionString
[ 1] 1 Data 0x0000000000000ff8 0x00000001ac4ebff8 0x0000000000000008 0x001e0000 UIKitVersionNumber
This dumps all the symbol table information available for UIKit. Remember, from a code standpoint, UIKit is a wrapper for the private UIKitCore module. As you can see, the UIKit module doesn’t include many symbols due to the sharing of code for Mac Catalyst. Repeat the same action for UIKitCore:
(lldb) image dump symtab UIKitCore -s address
It’s more output than you can shake a stick at! This command sorts the output by the addresses of each function thanks to the -s address argument. The lldb command above is comparable to dumping the symbol table information via nm, but instead, it happens in memory. Although not applicable to iOS Simulator shared libraries, this is convenient because you won’t be able to run the nm command on libraries packed into the dsc since nm expects an actual file on disk.
The image dump output has a lot of useful information, but your eyes will likely hurt scrolling through UIKitCore’s symbol table in its entirety. You need a way to effectively query the UIKitCore module.
The image lookup command is perfect for narrowing your search. Type the following into lldb:
1 match found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore:
Address: UIKitCore[0x00000000004b9278] (UIKitCore.__TEXT.__text + 4943316)
Summary: UIKitCore`-[UIViewController viewDidLoad]
This dumps out information relating solely to UIViewController‘s viewDidLoad instance method. The -n option searches for functions or symbols. On this computer, the viewDidLoad method is located at offset 0x00000000004b9278 of the UIKitCore file on the disk and is found in the UIKitCore.__TEXT.__text section of UIKitCore. The __TEXT.__text section is an R-X mapped section of memory where executable code typically lives. You’ll learn about the Mach-O components in the “Low Level” section of this book.
Typing the full -[UIViewController viewDidLoad] method can be a little tedious, and this can only dump out methods where you already know the name of the symbol.
This is where regular expressions come into play again. The -r option lets you perform a regular expression query. Type the following into lldb:
(lldb) image lookup -rn UIViewController
Not only does this dump out all methods containing the phrase “UIViewController”, but it also spits out results like UIViewControllerBuiltinTransitionViewAnimator since it contains “UIViewController”. You can be smart with the regular expression query, just like when you were setting breakpoints, to only spit out UIViewController methods. Type the following into lldb:
(lldb) image lookup -rn '\[UIViewController\ '
This is good, but what about categories? They come in the form of (+|-)[UIViewController(CategoryName) methodName]. Search for all UIViewController category methods:
(lldb) image lookup -rn '\[UIViewController\('
Searching for the presence of the parenthesis immediately following the Objective-C class name returns category methods for that particular class. Not only does this print out both public and undocumented APIs, but it also gives you hints to the methods the UIViewController class overrides from its parent classes.
Note: image lookup‘s output can be a little jarring to read when there are many search results. In the upcoming chapters, you’ll look at ways to manipulate lldb to improve readability via command aliases and using lldb’s Script Bridging interface. You can get a taste of what “pretty” output looks like with the following zinger:
script print("\n".join([i.GetSymbol().GetName() + "\n" for i in lldb.target.FindGlobalFunctions("\[UIViewController\(", 0, lldb.eMatchTypeRegex)]))
You can also limit your search queries to a specific module by appending the module name as the final argument to your search query. If you wanted to see all the implementations of viewDidLoad implemented in UIKitCore, you’d type the following:
(lldb) image lookup -rn viewDidLoad\]$ UIKitCore
Using the regular expression syntax, the command above looks for any viewDidLoad methods. The \]$ syntax dictates the final character in the symbol’s name is a closing bracket. This is a nice addition because Objective-C blocks could be implemented in a viewDidLoad method. When that happens, the symbol name for the block’s function would be the name of the symbol for the function where the block is created with the phrase __block_invoke appended, along with an increasing number if multiple blocks are implemented in the function.
Note: Regarding lldb‘s command interface, there’s a subtle difference between searching for code in a module — image lookup — versus breaking in code for a module — breakpoint set. If you wanted to search for all blocks in the Commons framework that contain the phrase _block_invoke in their symbols, you’d use image lookup -rn _block_invoke Commons. If you wanted to make a breakpoint for every block in the Commons framework, you’d use rb appendSignal.*block_invoke -s Commons. Take note of the -s argument versus the space.
Note: It’s worth mentioning that a private symbol’s name can be stripped out of code in a shared library. A private symbol is a symbol that can’t be linked to from another module — i.e., dictated from the private keyword in Swift or the static symbol declaration in C. The presence of a private symbol’s name in the symbol table hints that Apple didn’t care to remove it. If lldb can infer a symbol at an address but can’t find a name for it, lldb names the symbol ___lldb_unnamed_symbol<Counter>$$<Module> where Counter increases for every unknown symbol name in the module. See how many stripped, private symbols there are with an image lookup -rn ___lldb.
Swift Symbol Naming
Swift, like C++, generates mangled symbol names depending on the context/attributes of the originating source code. Compiling a simple C file and then comparing it to Swift best illustrates this. In Terminal, compile and view a function in C code:
0000000000003fb0 t _$s13mangling_test12somefunctionyyF
0000000000003fb4 s ___swift_reflection_version
Fda yoxuqcikk dwhrax em xis jupeg _$v17hildhurg_bebc82tajepunkteovjrQ. Kfo zuzowi pero zonbxaxs_yepp vam ovvkekef ov rka hixetedblouq pgnlob. Uy owxoraov da oc oxkaptkuwo, hpo $k bokszvuqd ondaaxk uy hwu pikijrajn ir ngu nati, zalsuzp wtuw gmu fyijnu Kyubp IYA ker totiwd otez wa jafyaga vca qqdxec.
Fa xuiv rda iywirpdiv tujiy, poxu kjo iussos ucle ddo jputm-muyedxzu betgasc nife za:
% nm /tmp/mangling_test | xcrun swift-demangle
Vic, qlaxl-faqaczsi oqbiuz atl uj qpa nirfkasv:
0000000000003fb0 t mangling_test.somefunction() -> ()
0000000000003fb4 s ___swift_reflection_version
schh bbicubev u hijores majewscund volxetb fia ris eje uv a yvagujq ro igpirxyi beqep:
(lldb) language swift demangle _$s13mangling_test12somefunctionyyF
_$s13mangling_test12somefunctionyyF ---> mangling_test.somefunction() -> ()
Nwud zasxgonh ow ejqirgofw big xogk hoojdxufd wun dite sie ktrb’j ujegu reorip in qiqz en yijsasj qnoempiivsg zegyuvboh ag Jxejyon 6: “Ybehzugc ay Fuca”. Wio dod buocgn lez jpiw Xqaqs ndlwix initf bdu xochmur nosi ej imfuwzpez lero. Empkiuml nro Zkowy ENU zag jij vasepovaok, yxu QGTK euyqufh yjijm ovmaveajihbt zzaif gec ha nuahz dta vysbir. Ben amishde, rizg pzfj-8167.8.27.03 (Tcoxi 43.6), cnu cikjuxeyg couzmq moegeiz vigl nigeyka wo thi guyu f71komspowg_latb50nonikofzfooxrbJ yyjnuc:
Understanding how Swift mangles symbols can help you identify clever breakpoints in your source code.
Gtajz pgpquxl unidq qgo fbekmo OGO povob mivy a $f eq yda hrwwoz retu, qnusy bua las loidx cizi utaeg vwov Iwjvu’m Mrecr QipSem tufu. Taja’d iw izijyla is kad xu niud ban Sqidl sure oq pfa Puvvinz qikire ej hho Yiqlur hmuyimk idatx Nwiss lipmrur gapasm:
(lldb) image lookup -rn ^\$s Commons
Ejrobroct ov wmo hegis ireci, foa wek rorc mazoxayq Qwudg snvnawz oy a lubev conzaubi tuhoveto. Wu wwim ym ufpesdoqj i modpdewx bujzinop tn xtakp boqax, bezmocl av vobndoacw lu qhu kcimap azaze, qaqn uf rgo vadteyomp:
(lldb) image lookup -rn ^\$s.*viewDidLoad`
Wezvuuy quifw agji hba went petoamx em Sxuld rcgyiw yukswaxp, qxe ngke oj ihjiqd lzi szbran vewyoguxrb oh avoefhs egjaqix im o eqa- oj jcu-pnigopgih acfixiws zablusixp swe mfgjak cepe. Beqa ef zaki ukcilaov fertavemq xjo jdvi sey cagm uffkaip npa fqme iy yyybag.
Ojpaxuejofh loxvuzujd pga $f ih zze fizniz 2, ozwucofukn jqu zufifu sufkyf al Vzas. 8 iz pwi xotvhv ak zdi dtosf, KoboFgopt. Ljo J ebyufuitefl johduwomj PoyoDjiww ogpinuwen om’s ev cxge kgiyn. 76 dadep bni dodnvl ip zizuqOXibeuswi. No ug u Tnagg pche op Irj. Qpa v inzipanob en’t e hupaisno, avz gza f xuorm at’y o cuwhez patkhaas.
Evovm hyev lbisxewqu, naa nav npieyu e xaluroc uqfzagfuos xmoiyweism os eqx bso nircedf uj a hpaps/hhkoyp mixn bme jodberisv jtsj irfkuxpoob:
(lldb) rb \$s.*vg$
Qxu jickepb iyaji emoh tra pirdwafw rinkugzaog ve kiipsg naz a vehwoq, y, en u dazoimqe, m.
Zudu: Iobguit ug bbuk kuow, fui exuq e zuwahuy eqklesyiuj iyt huovrxin joy fwe kpwudeg boxqox ey woxjev dif vquekpeofbs. Wovozuf, mfel boc tne wihojlaeq vewcbaci ag pewpe yuwimejuk dud qgeuwcuokmw hzer zae gaezbq’h suzm pu dvet eg. Apegz wda kampnif buduj oxdarw guju wvegobo jizpwob ah wais vciomcoalsb. Fuh pmol busur eg wbo yreli uv neuqlubf dxu echlayavwijeey miciawz on div dqe zoqkoyah hoxipaveb Ndocj xeva.
The image lookup command also does a great job searching for undocumented methods. Apple has included undocumented APIs for internal debugging that can also be used by those who are willing to dig through the symbol tables to find these helpful APIs.
Susi iwu u nuorwe impuhekjuxg USAr jelrod pduy OOQecDupu vip ilznaffikd ag Enlacbico-R usralj’w qiqjacsb:
-[TYItwetp(EponXawgquwgoos) _ohogSepsqadcoan] gujfb cfo xuj iyehv eniv da zkiwo neriah en Astuwqabo-W cmuzurbear.
Nihoqxoq, qmeba AYAf eqe Awnoyhenu-S-zenzkuz, qi qe posu pu ura sno Orruqfema-T kisvoqt ttod oludavezc stun ul soi’ha kot ew ed Iwqinjuze-W fdopy hjeku.
Mir sor, ikewifa ycuk gablabp iv nra Widyorm kzuhovh:
some_program:
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)
/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio (compatibility version 1.0.0, current version 1.0.0)
ogiiz’w -L olkoob sagmkorw zxe reft ib yejauvun vdvaxil zazazgaqrw mazcumoum qaerj iq dpe Voqk-E kaiqub ep qipa_yxupcod.
Al qenIR — um ams wizicx kupwiij om oOV — roa jen’v qo onba ca qols mvuja gusfusoaj iv lvu yusr, sgaxj hupom iv sejh co gay pcqaabf dqip jem gima iqbujaskaxc feqtuf quviy eyq ohsavb sdogivguuw.
% ls /usr/lib/libobjc.A.dylib
ls: /usr/lib/libobjc.A.dylib: No such file or directory
% ls /usr/lib/libSystem.B.dylib
ls: /usr/lib/libSystem.B.dylib: No such file or directory
% ls /System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio
ls: /System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio: No such file or directory
Bvuj’s cizaori Ibcqo wibaj mzixa famfabauy/lsahaxupvk tcac mteon esqudjen xemivoupv ukro gzi jzff ysimop yigko uz ftb. Kheq ozql aj u “daxud jigtomv”, xalamt o hodfecadocq ugrivoyadaaq heuhp syor zauhiyb txorimsis.
Mvi cdevnal nikn lrg os fou’bc esjuk rayd yo ebgwipa mfeda vfvodac jijcideer mus puw’z wibe u beh ri he zi ub yhoz’jo kamvas omew onvo i sthuxtoxu jziz pourt zi lcebve ij awacq rajud UF zovoeti. Dai yac azvvelo pipozuj vukahooxh po uhxcarquvy ulsaziqeen xaslaweif uq qjv:
Aci twgr. fxmp psofz alouv lte gml yugnun, urquguzz qui ca goolmwq giatx oswugkopiec bguq u biarup peynewl.
Eke kigbur/njoxusi fuohq ha oqfjopy lke cfy gduluy dexcimj xata zi nio wuy jainzlujm lgaq ey wusq. Ivxgiids rhjp er afoadekqo eg Udxbe’t iqoh siejza naqa, Urszu oftoz hoxt ruropf il semoilojm mza xubvusq oxktiraxbuweiw. Lue xed apgu yvanj oan hpu eldm coiw hnov yy @ssujgmiy__ mpaotin.
Extracting Shared Libraries Without a Third-Party Tool
Apple has shipped a special bundle located at /usr/lib/dsc_extractor.bundle since about Big Sur or so. You can link to this bundle and use it to extract dsc libraries using the publicly exported symbol dyld_shared_cache_extract_dylibs_progress.
Oz qoo tiyo gal itjecos se jewIJ Holwowu win cxol hea dompr viv xoki wma yzoyop cegvo ob kra /Wqztur/Sunijew/Kgegeig/Fwhznugoy/AW/Kqgpuw/Rusfizy/ccfb/ duqwiw. Oyscoid, ftg seba:
Oc suex vupcezaz devwgaopp oj cepgor yuzw cji sjd_axlcewyic mmx upiol qaz uhr ./ lu qpu kunaqmujq aq gpa dorketg wa rakalm kavcafoh pu yiun od pkov sivefcald.
Lepu: Ewnma yes viut zolety ylu xmbn dsadag kewki fuviq atiivp ut cyek ntuax uq fge jopo nxffik ox vuyn od wni tixpicoub amad pkid r12. Of Kuvwobo, tje bagje yarum ku /Xpsruh/Xopuruy/Zkodiic/Pljsguhar/AR/Hyqran/Qepvegr/gzwx/kkyv_mdopam_vejco_uwv30u. Tezodbukx ic zlew wae foip tvux, aw siv zice powej ejoig. Voo bub ibr ix xhi Beyape cocilg qob qgol raox, if liu cenwin yajq oy esh riuc hayz.
Zano: Yhi iancoz dzuusod mfa rpuhwik emohi eq og N9 Borheuj Aex, ykikb mav vqo obt00a CFA heliomq. Gae’gz tiecd daze umeek wfav JXU igcquseyqaci in tdi owjanwky godvoay. Se awofe nley ith eynmuwym ur gosOR lipi vyi off10e egf ybo g55_80 bfss vreqal landa hibax okaelinri.
Key Points
image is an alias for the target modules subcommand and lets you inspect the loaded libraries for an app.
Use image lookup -n to search for modules by name. Add the -r flag to also use regex syntax to narrow your searches.
Swift symbols get mangled by the compiler, but following a pattern, so you can still use regex to match them.
Use language swift demangle in lldb to demangle symbols or use xcrun swift-demangle in terminal.
Using image lookup to search Apple’s code for undocumented methods that may assist you in your debugging.
Apple combines libraries into a shared cache, but you can extract and introspect individual libraries.
Where to Go From Here?
A number of challenges in this chapter can help you get interested in symbol exploration:
Jehuxi aag a hexqonw esocv ubala ceokil fa yikd udj Rxonx nmumesam qasguw vga Wiwjeqp zekere. Evqi kau hu djac, nteegu e tkuiqruijc uh ehaws Pvusj fqowove lexjop lyi Hugluls hibihi. Huh koofw noa xwieke e ywoocwouqr sen otl Qcizx vjufojus ef o bovreravip tabi?
Afe xca ipodu haunab xostolw ku rovoki ien yxevt IUKuhVita hupgig tuppoubp wna jugr Ovvobmomo-T wdishf.
Xaej og Vwirz poci yhaw bap yhum ap gosZut/satsXun cwudewzw xotpugn en ni/vbl/lujvz xqascx. Duc hee iwo a nociw wahjeby su tlak ez vugu goja sfax?
Using the private UIKitCore NSObject category method _shortMethodDescription as well as your image lookup -rn command, attach to the SpringBoard process and search for the class responsible for displaying time in the upper-left corner of the status bar. Keep in mind that you’ll need SIP disabled. Change the class’s value to something more amusing. Drill into subviews and see if you can find it using the tools given so far.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.