Created
April 3, 2014 00:41
-
-
Save dreness/9946199 to your computer and use it in GitHub Desktop.
A possibly useful template for runtime patching with lldb.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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