Chapters

Hide chapters

Advanced Apple Debugging & Reverse Engineering

Fourth Edition · iOS 16, macOS 13.3 · Swift 5.8, Python 3 · Xcode 14

Section I: Beginning LLDB Commands

Section 1: 10 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

25. SB Examples, Improved Lookup
Written by Walter Tyree

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

For the rest of the chapters in this section, you’ll focus on Python scripts.

As alluded to earlier, the image lookup -rn command is on its way out. Time to make a prettier script to display content.

Here’s what you get right now with the image lookup -rn command:

When you finish this chapter, you’ll have a new script named lookup which queries in a much cleaner way.

In addition, you’ll add a couple of parameters to the lookup command to add some bells and whistles for your new searches.

Automating Script Creation

Included in the starter directory of this project are two Python scripts that will make your life easier when creating LLDB script content. They are as follows:

  • generate_new_script.py: This creates new skeletons script with whatever name you provide it and stick it into the same directory generate_new_script resides in.
  • lldbinit.py: This script will enumerate all scripts (files that end with .py) located within the same directory as itself and try to load them into LLDB. In addition, if there are any files with a txt extension, LLDB will try to load those files’ contents through command import.

Take both of these files found in the starter folder of this chapter and stick them into your ~/lldb/ directory.

Once the files are in their correct locations, jump over to your ~/.lldbinit file and add following line of code:

command script import ~/lldb/lldbinit.py

This will load the lldbinit.py file which will enumerate all .py files and .txt files found in the same directory and load them into LLDB. This means from here on out, simply adding a script file into the ~/lldb directory will load it automatically once LLDB starts.

Creating the Lookup Command

With your new tools properly set up, open up a Terminal window. Launch a new instance of LLDB:

lldb
(lldb) reload_script
(lldb) __generate_script lookup
Opening "/Users/derekselander/lldb/lookup.py"...
(lldb) lookup
Hello! The lookup command is working!

lldbinit Directory Structure Suggestions

The way I’ve structured my own lldbinit files might be insightful to some. This is not a required section, but more of a suggestion on how to organize all of your custom scripts and content for LLDB.

command script import /Users/derekselander/lldb_repo/lldb_commands/lldbinit.py
command script import /Users/derekselander/chisel/chisel/fblldb.py
settings set target.skip-prologue false
settings set target.x86-disassembly-flavor intel

Implementing the Lookup Command

As you saw briefly in the Chapter 7 “Image”, the foundation behind this lookup command is rather simple. But, with the default image lookup, specifying filters and reading the output are difficult. You’ll focus on enhancing how FindGlobalFunctions works.

(lldb) script help(lldb.SBTarget.FindGlobalFunctions)
FindGlobalFunctions(self, *args) unbound lldb.SBTarget method
    FindGlobalFunctions(self, str name, uint32_t max_matches, MatchType matchtype) -> SBSymbolContextList
# Uncomment if you are expecting at least one argument
# clean_command = shlex.split(args[0])[0]
result.AppendMessage('Hello! The lookup command is working!')
# 1
clean_command = shlex.split(args[0])[0]
# 2
target = debugger.GetSelectedTarget()

# 3
contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)
# 4
result.AppendMessage(str(contextlist))
(lldb) reload_script
lookup DSObjectiveCObject

(lldb) script k = lldb.target.FindGlobalFunctions('DSObjectiveCObject', 0, lldb.eMatchTypeRegex)
(lldb) gdocumentation SBSymbolContextList
(lldb) script dir(lldb.SBSymbolContextList)

(lldb) script k[0]
<lldb.SBSymbolContext; proxy of <Swig Object of type 'lldb::SBSymbolContext *' at 0x113a83780> >
(lldb) script print (k[0])
Module: file = "/Users/wtyree/Library/Developer/Xcode/DerivedData/Allocator-adwweurwxijqutezmvmbahiztsjd/Build/Products/Debug-iphonesimulator/Allocator.app/Allocator", arch = "arm64"

CompileUnit: id = {0x00000000}, file = "/Users/wtyree/Repos/dbg-materials/26-sb-examples-improved-lookup/projects/starter/Allocator/Allocator/DSObjectiveCObject.m", language = "<not loaded>"
  Function: id = {0x100000258}, name = "-[DSObjectiveCObject setLastName:]", range = [0x0000000100003b0c-0x0000000100003b40)
  FuncType: id = {0x100000258}, byte-size = 0, decl = DSObjectiveCObject.h:36, compiler_type = "void (NSString *)"
  Symbol: id = {0x00000112}, range = [0x0000000100003b0c-0x0000000100003b40), name="-[DSObjectiveCObject setLastName:]"
(lldb) script print (k[0].symbol.name)
-[DSObjectiveCObject setLastName:]
contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)

result.AppendMessage(str(contextlist))
contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)

output = ''
for context in contextlist:
    output += context.symbol.name + '\n\n'

result.AppendMessage(output)
(lldb) reload_script
(lldb) lookup DSObjectiveCObject
-[DSObjectiveCObject setLastName:]

-[DSObjectiveCObject .cxx_destruct]

-[DSObjectiveCObject setFirstName:]

-[DSObjectiveCObject eyeColor]

-[DSObjectiveCObject init]

-[DSObjectiveCObject lastName]

-[DSObjectiveCObject setEyeColor:]

-[DSObjectiveCObject firstName]
def generateModuleDictionary(contextlist):
    mdict = {}
    for context in contextlist:
        # 1
        key = context.module.file.fullpath
        # 2
        if not key in mdict:
            mdict[key] = []

        # 3
        mdict[key].append(context)
    return mdict
def generateOutput(mdict, options, target):
    # 1
    output = ''
    separator = '*' * 60 + '\n'
    # 2
    for key in mdict:
        # 3
        count = len(mdict[key])
        firstItem = mdict[key][0]
        # 4
        moduleName = firstItem.module.file.basename
        output += '{0}{1} hits in {2}\n{0}'.format(separator,
                                                   count,
                                                   moduleName)
        # 5
        for context in mdict[key]:
            query = ''
            query += context.symbol.name
            query += '\n\n'
            output += query
  return output
output = ''
for context in contextlist:
    output += context.symbol.name + '\n\n'
mdict = generateModuleDictionary(contextlist)
output = generateOutput(mdict, options, target)
(lldb) reload_script
(lldb) lookup DSObjectiveCObject
************************************************************
8 hits in Allocator
************************************************************
-[DSObjectiveCObject setLastName:]

-[DSObjectiveCObject .cxx_destruct]

-[DSObjectiveCObject setFirstName:]

-[DSObjectiveCObject eyeColor]

-[DSObjectiveCObject init]

-[DSObjectiveCObject lastName]

-[DSObjectiveCObject setEyeColor:]

-[DSObjectiveCObject firstName]
(lldb) lookup initWith([A-Za-z0-9_]+\:){2}\]

Adding Options to Lookup

You’ll keep the options nice and simple and implement only two options that don’t require any extra parameters.

def generateOptionParser():
    usage = "usage: %prog [options] code_to_query"
    parser = optparse.OptionParser(usage=usage, prog="lookup")

    parser.add_option("-l", "--load_address",
          action="store_true",
          default=False,
          dest="load_address",
          help="Show the load addresses for a particular hit")

    parser.add_option("-s", "--module_summary",
          action="store_true",
          default=False,
          dest="module_summary",
          help="Only show the amount of queries in the module")
    return parser
for context in mdict[key]:
    query = ''

    # 1
    if options.load_address:
        # 2
        start = context.symbol.addr.GetLoadAddress(target)
        end = context.symbol.end_addr.GetLoadAddress(target)
        # 3
        startHex = '0x' + format(start, '012x')
        endHex = '0x' + format(end, '012x')
        query += '[{}-{}]\n'.format(startHex, endHex)

    query += context.symbol.name
    query += '\n\n'
    output += query
(lldb) reload_script
(lldb) lookup -l DSObjectiveCObject
************************************************************
8 hits in Allocator
************************************************************
[0x0001099d2c00-0x0001099d2c40]
-[DSObjectiveCObject setLastName:]

[0x0001099d2c40-0x0001099d2cae]
-[DSObjectiveCObject .cxx_destruct]
(lldb) b 0x0001099d2c00
Breakpoint 3: where = Allocator`-[DSObjectiveCObject setLastName:] at DSObjectiveCObject.h:33, address = 0x00000001099d2c00
moduleName = firstItem.module.file.basename
if options.module_summary:
    output += '{} hits in {}\n'.format(count, moduleName)
    continue
(lldb) reload_script
(lldb) lookup -s viewWillAppear
2 hits in Allocator
1 hits in DocumentManager
54 hits in UIKitCore
6 hits in ShareSheet
5 hits in PrintKitUI
1 hits in GLKit
8 hits in MapKit

Key Points

  • Give some thought to organizing your .lldbinit and loading scripts now, while you’ve only got a few.
  • Experiment with commands in the lldb console before attempting to write scripts so you can experiment and iterate faster.
  • There’s often more than one API in LLDB’s Script Bridge module that will return the same information. Always be on the lookout for alternatives.
  • You can add options to your generateOptionParser that don’t yet do anything. Then add code for each option one-by-one so you can code and test each one separately.

Where to Go From Here?

There are many more options you could add to this lookup command. You could make a -S or -Swift_only query by going after SBSymbolContext’s SBFunction (through the function property) to access the GetLanguage() API. While you’re at it, you should also add a -m or --module option to filter content to a certain module. Also, don’t forget to add some general help text.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now