Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I automate this sequence of lldb commands?

Tags:

python

lldb

vmat

In order to work around a bug in Apple's lldb (rdar://13702081) I very frequently need to type two commands in sequence, like this:

(lldb) p todo.matA
(vMAT_Array *) $2 = 0x000000010400b5a0
(lldb) po $2.dump
$3 = 0x0000000100503ce0 <vMAT_Int8Array: 0x10400b5a0; size: [9 1]> = 
1
1
1
1
1
1
1
1
1

Is it possible to write a new lldb command using the Python library (or something) that could combine those steps for me? Ideally to something like:

(lldb) pmat todo
$3 = 0x0000000100503ce0 <vMAT_Int8Array: 0x10400b5a0; size: [9 1]> = 
1
1
1
1
1
1
1
1
1

Solution

Thanks to Jason Molenda here is output from a working lldb command script:

(lldb) pmat Z
$0 = 0x0000000100112920 <vMAT_DoubleArray: 0x101880c20; size: [9 3]> = 
       7        9 0.848715
       3        5 0.993378
       0        1  1.11738
       4       12   1.2013
      11       13  1.20193
       6       10  1.29206
      14       15  1.53283
       8       16  1.53602
       2       17  1.68116

I did have to tweak the script provided in the answer below very slightly, using Jason's suggestions for working around the lldb bug with overly-complex expressions. Here is my final script:

# import this into lldb with a command like
# command script import pmat.py
import lldb
import shlex
import optparse

def pmat(debugger, command, result, dict):
  # Use the Shell Lexer to properly parse up command options just like a
  # shell would
  command_args = shlex.split(command)
  parser = create_pmat_options()
  try:
    (options, args) = parser.parse_args(command_args)
  except:
   return

  target = debugger.GetSelectedTarget()
  if target:
    process = target.GetProcess()
    if process:
      frame = process.GetSelectedThread().GetSelectedFrame()
      if frame:
        var = frame.FindVariable(args[0])
        if var:
          array = var.GetChildMemberWithName("matA")
          if array:
            id = array.GetValueAsUnsigned (lldb.LLDB_INVALID_ADDRESS)
            if id != lldb.LLDB_INVALID_ADDRESS:
              debugger.HandleCommand ('po [0x%x dump]' % id)

def create_pmat_options():
  usage = "usage: %prog"
  description='''Print a dump of a vMAT_Array instance.'''
  parser = optparse.OptionParser(description=description, prog='pmat',usage=usage)
  return parser

#
# code that runs when this script is imported into LLDB
#
def __lldb_init_module (debugger, dict):
  # This initializer is being run from LLDB in the embedded command interpreter
  # Make the options so we can generate the help text for the new LLDB
  # command line command prior to registering it with LLDB below

  # add pmat
  parser = create_pmat_options()
  pmat.__doc__ = parser.format_help()
  # Add any commands contained in this module to LLDB
  debugger.HandleCommand('command script add -f %s.pmat pmat' % __name__)
like image 763
Kaelin Colclasure Avatar asked Mar 24 '23 05:03

Kaelin Colclasure


1 Answers

You can do this either with a regex command or by creating your own python command and loading it in to lldb. In this specific instance the regex command won't help you because you'll hit the same crasher you're hitting. But just for fun, I'll show both solutions.

First, python. This python code gets the currently selected frame on the currently selected thread. It looks for a variable whose name is provided on the command argument. It finds a child of that variable called matA and it runs GetObjectDescription() on that SBValue object.

# import this into lldb with a command like
# command script import pmat.py
import lldb
import shlex
import optparse

def pmat(debugger, command, result, dict):
  # Use the Shell Lexer to properly parse up command options just like a
  # shell would
  command_args = shlex.split(command)
  parser = create_pmat_options()
  try:
    (options, args) = parser.parse_args(command_args)
  except:
   return

  target = debugger.GetSelectedTarget()
  if target:
    process = target.GetProcess()
    if process:
      frame = process.GetSelectedThread().GetSelectedFrame()
      if frame:
        var = frame.FindVariable(args[0])
        if var:
          child = var.GetChildMemberWithName("matA")
          if child:
            print child.GetObjectDescription()

def create_pmat_options():
  usage = "usage: %prog"
  description='''Call po on the child called "matA"'''
  parser = optparse.OptionParser(description=description, prog='pmat',usage=usage)
  return parser

#
# code that runs when this script is imported into LLDB
#
def __lldb_init_module (debugger, dict):
  # This initializer is being run from LLDB in the embedded command interpreter
  # Make the options so we can generate the help text for the new LLDB
  # command line command prior to registering it with LLDB below

  # add pmat
  parser = create_pmat_options()
  pmat.__doc__ = parser.format_help()
  # Add any commands contained in this module to LLDB
  debugger.HandleCommand('command script add -f %s.pmat pmat' % __name__)

In use,

(lldb) br s -p break
Breakpoint 2: where = a.out`main + 31 at a.m:8, address = 0x0000000100000eaf

(lldb) r
Process 18223 launched: '/private/tmp/a.out' (x86_64)
Process 18223 stopped
* thread #1: tid = 0x1f03, 0x0000000100000eaf a.out`main + 31 at a.m:8, stop reason = breakpoint 2.1
    #0: 0x0000000100000eaf a.out`main + 31 at a.m:8
   5        @autoreleasepool {
   6            struct var myobj;
   7            myobj.matA = @"hello there";
-> 8            printf ("%s\n", [(id)myobj.matA UTF8String]); // break here
   9        }
   10   }
(lldb) p myobj
(var) $0 = {
  (void *) matA = 0x0000000100001070
}
(lldb) comm scri imp ~/lldb/pmat.py
(lldb) pmat myobj
hello there
(lldb) 

You can put the command script import line in your ~/.lldbinit file if you want to use this.

It's easy to use the Python APIs once you have a general idea of how the debugger is structured. I knew that I would find the variable based on the frame, so I looked at the help for the SBFrame object with

(lldb) script help (lldb.SBFrame)

The method FindVariable returns an SBValue so then I looked at the lldb.SBValue help page, etc. There's a lot of boilerplate in my example python above - you're really looking at 4 lines of python that do all the work.

If this is still triggering the code path that is crashing your lldb process, you can do the last little bit of the script in two parts - get the address of the object and run po on that raw address. e.g.

          child = var.GetChildMemberWithName("matA")
          if child:
            id = child.GetValueAsUnsigned (lldb.LLDB_INVALID_ADDRESS)
            if id != lldb.LLDB_INVALID_ADDRESS:
              debugger.HandleCommand ('po 0x%x' % id)

Second, using a command regex:

(lldb) br s -p break
Breakpoint 1: where = a.out`main + 31 at a.m:8, address = 0x0000000100000eaf
(lldb) r
Process 18277 launched: '/private/tmp/a.out' (x86_64)
Process 18277 stopped
* thread #1: tid = 0x1f03, 0x0000000100000eaf a.out`main + 31 at a.m:8, stop reason = breakpoint 1.1
    #0: 0x0000000100000eaf a.out`main + 31 at a.m:8
   5        @autoreleasepool {
   6            struct var myobj;
   7            myobj.matA = @"hello there";
-> 8            printf ("%s\n", [(id)myobj.matA UTF8String]); // break here
   9        }
   10   }
(lldb) command regex pmat 's/(.*)/po %1.matA/'
(lldb) pmat myobj
$0 = 0x0000000100001070 hello there
(lldb) 

You can't use the simpler command alias in this instance - you have to use a regex alias - because you're calling a command which takes raw input. Specifically, po is really an alias to expression and you need to use regex command aliases to substitute values into those.

like image 79
Jason Molenda Avatar answered Apr 19 '23 16:04

Jason Molenda