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

23. Script Bridging With Options & Arguments
Written by Walter Tyree

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

When you’re creating a custom debugging command, you’ll often want to slightly tweak functionality based upon options or arguments supplied to your command. A custom LLDB command that can do a job only one way is a boring one-trick pony.

In this chapter, you’ll explore how to pass optional parameters (a.k.a. options) as well as arguments (parameters which are expected) to your custom command to alter functionality or logic in your custom LLDB scripts.

You’ll continue working with the bar, “break-after-regex”, command you created in the previous chapter. In this chapter, you’ll finish up the bar command by adding logic to handle options in your script.

By the end of this chapter, the bar command will have logic to handle the following optional parameters:

  • Non-regular expression search: Using the -n or --non_regex option will result in the bar command using a non-regular expression breakpoint search instead. This option will not take any additional parameters.
  • Filter by module: Using the -m or --module option will only search for breakpoints in that particular module. This option will expect an additional parameter which specifies the name of the module.

This will be a dense but fun chapter. Make sure you’ve got a good supply of caffeine!

Setting Up

If you’ve gone through the previous chapter and your bar command is working, then you can continue using that script and ignore this part. Otherwise, head on over to the starter folder in this chapter’s resources, and copy the BreakAfterRegex.py file into your ~/lldb folder. Make sure your ~/.lldbinit file has the following line which you should have from the previous chapter:

command script import ~/lldb/BreakAfterRegex.py

If you’ve any doubts if this command loaded successfully into LLDB, simply fire up a new LLDB instance in Terminal:

lldb

Then check for the help docstring of the bar command:

(lldb) help bar

If you get an error, it’s not successfully loaded. However, even if it is loaded, you don’t get some very useful help. You’ll fix that now. Remember earlier in the book, you added -h and -H flag to add some help string. Another way to add help text is using a docstring. Add this in BreakAfterRegex.py right after the line that reads def breakAfterRegex(debugger, command, result, internal_dict): but before the first line of code:

  '''Creates a regular expression breakpoint and adds it.
  Once the breakpoint is hit, control will step out of the current
  function and print the return value. Useful for stopping on
  getter/accessor/initialization methods
  '''

Now, save your work and restart lldb and run help bar again. You should see your nice, more robust help text now.

The RWDevCon Project

For this chapter, you’ll use an app called RWDevcon.

The optparse Python Module

The lovely thing about LLDB Python scripts is you have all the power of Python — and its modules — at your disposal.

some_command woot -b 34 -a "hello world"

Adding Options Without Params

With the knowledge you need to educate your parser with what arguments are expected, it’s time to add your first option which will alter the functionality of the bar command to apply the SBBreakpoint without using a regular expression, but instead use a normal expression.

some_command -f
some_command -f true
import optparse
import shlex
import shlex
command = '"hello world" "2nd parameter" 34'
shlex.split(command)
['hello world', '2nd parameter', '34']
def generateOptionParser():
  '''Gets the return register as a string for lldb
    based upon the hardware
  '''
  usage = "usage: %prog [options] breakpoint_query\n" +\
          "Use 'bar -h' for option desc"
  # 1
  parser = optparse.OptionParser(usage=usage, prog='bar')
  # 2
  parser.add_option("-n", "--non_regex",
                    # 3
                    action="store_true",
                    # 4
                    default=False,
                    # 5
                    dest="non_regex",
                    # 6
                    help="Use a non-regex breakpoint instead")
  # 7
  return parser
target = debugger.GetSelectedTarget()
breakpoint = target.BreakpointCreateByRegex(command)
# 1
command = command.replace('\\', '\\\\')
# 2
command_args = shlex.split(command, posix=False)

# 3
parser = generateOptionParser()

# 4
try:
  # 5
  (options, args) = parser.parse_args(command_args)
except:
  result.SetError(parser.usage)
  return

target = debugger.GetSelectedTarget()

# 6
clean_command = shlex.split(args[0])[0]

# 7
if options.non_regex:
  breakpoint = target.BreakpointCreateByName(
                      clean_command)
else:
  breakpoint = target.BreakpointCreateByRegex(
                      clean_command)

# The rest remains unchanged

Testing Out Your First Option

Enough code. Time to test this script out.

br dis 1
bar -n "-[NSUserDefaults(NSUserDefaults) objectForKey:]"

Adding Options With Params

You’ve learned how to add an option that expects no arguments. You’ll now add another option that expects a parameter. This next option will be the --module option to specify which module you want to constrain your regular expression query to.

# 1
parser.add_option("-m", "--module",
                  # 2
                  action="store",
                  # 3
                  default=None,
                  # 4
                  dest="module",
                  help="Filter a breakpoint by only searching within a specified Module")
if options.non_regex:
  breakpoint = target.BreakpointCreateByName(clean_command)
else:
  breakpoint = target.BreakpointCreateByRegex(clean_command)
if options.non_regex:
  breakpoint = target.BreakpointCreateByName(clean_command, options.module)
else:
  breakpoint = target.BreakpointCreateByRegex(clean_command, options.module)
(lldb) script help (lldb.SBTarget.BreakpointCreateByRegex)

BreakpointCreateByRegex(SBTarget self, str symbol_name_regex, str module_name=None) -> SBBreakpoint
bar @objc.*.init -m RWDevCon

(lldb) methods Session
(lldb) expression -lobjc -O -- [Session _shortMethodDescription]

Key Points

  • Placing some help at the beginning of a Python def within ''' quotes will be treated as the function’s documentation. This documentation is shown when you run help <my_function>.
  • The optparse module is deprecated in Python, but is still widely used with the lldb modules. Watch for an official switch to argparse, but it hasn’t happened quite yet.
  • The shlex lexer offers .split and .join commands you can use to manipulate arguments passed to your function, treating them in a way you’d expect as a shell user. For example when passing strings with spaces in them you can wrap in quotes.
  • The .add_option command of a parser allows you to supply default values and help text for that option.
  • Placing a symbolic breakpoint in getenv or main is a common technique to set up lldb commands and breakpoints every time your code runs. You could also use it to create an .lldbinit type of file for a specific project.

Where to Go From Here?

That was pretty intense, but you’ve learned how to incorporate options into your own Python scripts.

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 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.

Unlock now