Skip to content

Instantly share code, notes, and snippets.

@dreness
Created April 3, 2014 00:41
Show Gist options
  • Save dreness/9946199 to your computer and use it in GitHub Desktop.
Save dreness/9946199 to your computer and use it in GitHub Desktop.
A possibly useful template for runtime patching with lldb.
#!/usr/bin/python
import lldb
def patchAVAssetWriterInput64(frame, bp_loc, dict):
'''
Replace the 'outputSettings' argument to AVAssetWriterInput, to specify
a different video output codec.
This function wants to be called from a breakpoint command.
Use my setbp() function to create the breakpoint and breakpoint command.
'''
# The call we want to patch looks like:
# +[AVAssetWriterInput assetWriterInputWithMediaType:outputSettings:]
# In x86_64, the second obj-c method arg is in the rcx register
# Get a SBValue handle on the rcx register
rcxreg = frame.regs[0].GetChildMemberWithName("rcx")
# Cache rcx type and thread ID for later...
rcxType = rcxreg.GetType()
origThreadID = frame.GetThread().GetIndexID()
print "Caching rcx type: %s on thread %d" % (rcxType.name, origThreadID)
# Our breakpoint gets hit twice; the same AVFoundation call is used for both audio and video.
# Look at the current options dict being passed to AVFoundation to decide whether to replace it or continue
# Get the obj-c value of the object referenced by the register - roughly equivalent to 'po'
rcx = rcxreg.GetObjectDescription()
if 'AVSampleRateKey' in rcx:
# go fish
frame.thread.process.Continue()
return
if 'avc1' not in rcx:
# No H.264 codec description to replace...
frame.thread.process.Continue()
return
print "replacing: %r" % (rcx)
# Now we have work to do. Start creating the replacement outputSettings
# Begin by composing an array of strings that will become an expression to create a dict
# Cast the new dict to the same type we got from the original dict
a = []
a.append('(%s *)[NSDictionary dictionaryWithObjectsAndKeys: ' % (rcxType,))
# We came all this way just to replace 3 characters... ap4h gets us ProRes4444, avc1 gets h.264
a.append('@"ap4h",@"AVVideoCodecKey",')
# We also need to specify frame height and width with NSNumbers
# We'll evaluate obj-c expresssions in the frame to create the objects
# All expression results are also automatically stored in lldb's numbered variables ($1, $2, ...)
width = frame.EvaluateExpression("(id) [NSNumber numberWithInteger:1760]")
height = frame.EvaluateExpression("(id) [NSNumber numberWithInteger:1100]")
# The 'name' property of the expression result object holds the LLDB variable name of the expression result
a.append('%s,@"AVVideoHeightKey",%s,@"AVVideoWidthKey",nil]' % (height.name,width.name))
# Cinch up the expression and fire it off
exp = ''.join(a)
print "built expression: %r" % (exp,)
newopts = frame.EvaluateExpression(exp)
optvar = newopts.name
optt = newopts.GetType()
print "stored new options dict in var %s with type %s" % (optvar,optt)
# LLDB as of LLVM-159 doesn't have python API for setting registers
# so we'll just compose an interpreter command to do it...
smash = "register write rcx `%s`" % (optvar)
print "Assembled smash command: %s" % (smash,)
# We'll use HandleCommand to interact with the interpreter,
# but to do that, we need a reference to the debugger.
# It takes long arms to reach the debugger from the frame
debugger = frame.thread.process.target.debugger
# PRO TIP: the debugger object we just got doesn't know which thread the current frame is on
# Switch to the thread that has the register we want to replace
# I must have smashed thread 1's rcx about a thousand times while writing this...
debugger.HandleCommand("thread select %d" % (origThreadID,))
# Run the register-smashing command.
debugger.HandleCommand(smash)
# as you were...
frame.thread.process.Continue()
def setbp():
'''Convenience function for setting the breakpoint + command'''
try:
lldb.target
except NameError:
print "No target, bailing..."
return
sig = "+[AVAssetWriterInput assetWriterInputWithMediaType:outputSettings:]"
# See if we already have one...
if (lldb.target.GetNumBreakpoints() > 0):
return
bp = lldb.target.BreakpointCreateByName(sig)
lldb.debugger.HandleCommand("breakpoint command add -F wowfix.patchAVAssetWriterInput64 python %s" % (bp.GetID()),)
print "breakpoint installed: %r" % (bp)
lldb.debugger.HandleCommand("break list")
if __name__ == '__main__':
lldb.debugger = lldb.SBDebugger.Create()
elif lldb.debugger:
print 'wowfix loaded'
setbp()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment