Chapters

Hide chapters

Advanced Apple Debugging & Reverse Engineering

Third Edition · iOS 12 · Swift 4.2 · Xcode 10

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Low Level

Section 3: 7 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

26. SB Examples, Improved Lookup
Written by Derek Selander

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 in the previous chapter, 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 with 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 will create a new skeleton 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 that 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 previous chapter, the foundation behind this lookup command is rather simple. The main “secret” is using SBTarget’s FindGlobalFunctions API. After that, all you need to do is format the output as you like.

(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/derekselander/Library/Developer/Xcode/DerivedData/Allocator-czsgsdzfgtmanrdjnydkbzdmhifw/Build/Products/Debug-iphonesimulator/Allocator.app/Allocator", arch = "x86_64"
CompileUnit: id = {0x00000000}, file = "/Users/derekselander/iOS/dbg/s4-custom-lldb-commands/22.  Ex 1, Improved Lookup/projects/final/Allocator/Allocator/DSObjectiveCObject.m", language = "objective-c"
   Function: id = {0x100000268}, name = "-[DSObjectiveCObject setLastName:]", range = [0x0000000100001c00-0x0000000100001c37)
   FuncType: id = {0x100000268}, decl = DSObjectiveCObject.h:33, compiler_type = "void (NSString *)"
     Symbol: id = {0x0000001e}, range = [0x0000000100001c00-0x0000000100001c40), name="-[DSObjectiveCObject setLastName:]"
(lldb) script print k[0].symbol.name
-[DSObjectiveCObject setLastName:]
# 3
contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)
# 4
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(\w+\:){2,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
1 hits in: GLKit
18 hits in: ContactsUI
3 hits in: DocumentManager
8 hits in: MapKit
49 hits in: UIKitCore
4 hits in: Allocator

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.

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