Skip to content

Instantly share code, notes, and snippets.

@jesuslg123
Last active November 14, 2019 13:59
Show Gist options
  • Save jesuslg123/4ef6890f671924aff49f189ff04899ba to your computer and use it in GitHub Desktop.
Save jesuslg123/4ef6890f671924aff49f189ff04899ba to your computer and use it in GitHub Desktop.
Reveal 13
#!/usr/bin/python
#Already fixde for xcode 11
import lldb
import sys
import os
import shlex
import argparse
# Global constants
# This path assumes that the script is stored in Contents/SharedSupport/Scripts inside Reveal bundle.
revealLocalSharedSupportPath = "/Applications/Reveal.app/Contents/SharedSupport/"
# Entry point
def __lldb_init_module(debugger, internal_dict):
# Make the options parser so we can generate the help text for the LLDB command prior to registering it below
HandleRevealCommand.__doc__ = CreateRevealCommandOptionsParser().format_help()
# Install the debugger command
debugger.HandleCommand("command script add -f {0}.HandleRevealCommand reveal".format(__name__))
# Command options parsing
def CreateRevealCommandOptionsParser():
subcommands = {}
subcommands["load"] = { "func" : HandleRevealLoadCommand, "help" : "Load the Reveal Server library." }
subcommands["start"] = { "func" : HandleRevealStartCommand, "help" : "Start Reveal Server, if it's loaded." }
subcommands["stop"] = { "func" : HandleRevealStopCommand, "help" : "Stop Reveal Server." }
subcommands["status"] = { "func" : HandleRevealStatusCommand, "help" : "Print the current status of Reveal Server." }
description = "Commands for loading and controlling Reveal Server in the current debugging session."
parser = argparse.ArgumentParser(prog="reveal", description=description)
subparsers = parser.add_subparsers(title="Available actions", metavar="action")
subparsersMap = {}
for key, info in subcommands.items():
subparser = subparsers.add_parser(key, help=info["help"])
subparser.set_defaults(func=info["func"])
subparsersMap[key] = subparser
subparsersMap["load"].add_argument("-a", "--autostart", action="store_true", help="Automatically start Reveal Server after loading.")
return parser
# Command handlers
def HandleRevealCommand(debugger, command, exe_ctx, result, internal_dict):
# Parse the command
parser = CreateRevealCommandOptionsParser()
commandArgs = shlex.split(command)
if len(commandArgs) == 0:
parser.print_help()
return
try:
args = parser.parse_args(commandArgs)
except:
return
# Bail out if running an incompatible target
target = exe_ctx.target
if TargetIsCompatible(target) == False:
result.SetError("target {0} is not supported by Reveal Server.".format(target.triple))
return
# Check that process is already in the right state
process = exe_ctx.process
if lldb.SBDebugger.StateIsStoppedState(process.state) == False:
result.SetError("process must be paused to execute Reveal Server commands.")
return
if ProcessMainThreadContainsFrameWithName(process, "UIApplicationMain") == False:
if ProcessMainThreadContainsFrameWithName(process, "NSExtensionMain") == False:
result.SetError("process is not yet ready to execute Reveal Server commands.")
else:
result.SetError("process is running an unsupported application extension.")
return
# Create a loader object with main thread's current frame and execute the command with it
mainThreadFrame = process.GetThreadAtIndex(0).GetSelectedFrame()
loader = RevealLoader(mainThreadFrame)
args.func(loader, result, args)
def HandleRevealLoadCommand(loader, result, args):
# If target is running locally (i.e. in the Simulator or UIKit Host), load local Reveal Server
if TargetIsLocal(loader.process.target):
binaryPath = loader.localRevealServerBinaryPath()
loader.injectServer(binaryPath, result)
else:
# If target is running on device, check if Reveal Server framework is included in the bundle and try loading it if it exists
remoteServerBinaryPath = loader.remoteRevealServerBinaryPath()
if loader.remoteFileExists(remoteServerBinaryPath):
loader.injectServer(remoteServerBinaryPath, result)
else:
# Otherwise, can't load Reveal
result.SetError("failed to load Reveal Server because it was not found in the application bundle. For information about debugging apps with Reveal on device, please refer to the Integration Guide.")
return
# If injection succeeded and autostart option is specified, start the Server
if result.Succeeded() and args.autostart == True:
loader.startServer()
def HandleRevealStartCommand(loader, result, args):
if loader.isRevealServerLoaded():
loader.startServer()
else:
result.SetError("Reveal Server is not loaded. Use `reveal load` command first.")
def HandleRevealStopCommand(loader, result, args):
if loader.isRevealServerLoaded():
loader.stopServer()
else:
result.AppendWarning("Reveal Server is not loaded, there's nothing to stop.")
def HandleRevealStatusCommand(loader, result, args):
if loader.isRevealServerLoaded():
version = loader.getRevealServerProtocolVersion()
if loader.isRevealServerRunning():
result.AppendMessage("Reveal Server (protocol version {0}) is loaded and running.".format(version))
else:
result.AppendMessage("Reveal Server (protocol version {0}) is loaded, but not running.".format(version))
else:
result.AppendMessage("Reveal Server is not loaded.")
# Reveal Loader
class RevealLoader(object):
def __init__(self, frame):
self.frame = frame
self.process = frame.thread.process
def localRevealServerBinaryPath(self):
if "apple-tvos" in self.process.target.triple:
librariesDirectory = "tvOS-Libraries"
else:
# iOS or watchOS
librariesDirectory = "iOS-Libraries"
return os.path.join(revealLocalSharedSupportPath, librariesDirectory, "RevealServer.framework/RevealServer")
def remoteRevealServerBinaryPath(self):
expression = "(id)[[objc_getClass(\"NSBundle\") mainBundle] pathForResource:@\"RevealServer\" ofType:@\"framework\"]"
revealServerFrameworkPath = self.frame.EvaluateExpression(expression, GetCommonExpressionOptions()).GetObjectDescription()
if revealServerFrameworkPath is not None and revealServerFrameworkPath != "nil":
return os.path.join(revealServerFrameworkPath, "RevealServer")
else:
return ""
def remoteFileExists(self, remoteFilePath):
expression = "(BOOL)[[objc_getClass(\"NSFileManager\") defaultManager] fileExistsAtPath:@\"{0}\"] != NO".format(remoteFilePath)
return self.frame.EvaluateExpression(expression, GetCommonExpressionOptions()).value == "true"
def isRevealServerLoaded(self):
expression = "(void*)dlsym((void*)-2, \"OBJC_CLASS_$_IBARevealLoader\")"
value = self.frame.EvaluateExpression(expression, GetCommonExpressionOptions()).value
pointerValue = int(value, 16)
return pointerValue != 0
def getRevealServerProtocolVersion(self):
expression = "(int)[objc_getClass(\"IBARevealProtocol\") protocolVersion]"
return self.frame.EvaluateExpression(expression, GetCommonExpressionOptions()).signed
def injectServer(self, path, result):
# Check that Reveal Server is already loaded first
if self.isRevealServerLoaded():
result.AppendWarning("not loading Reveal Server, it's already loaded.")
return
# Load Reveal Server image from the specified path
print("Loading Reveal Server from {0}...".format(path))
error = lldb.SBError()
self.process.LoadImage(lldb.SBFileSpec(path), error)
if error.fail:
result.SetError(error)
else:
result.AppendMessage("Reveal Server was loaded successfully.")
def isRevealServerRunning(self):
expression = "(BOOL)[[[objc_getClass(\"IBARevealServer\") sharedServer] httpServer] isRunning] != NO"
return self.frame.EvaluateExpression(expression, GetCommonExpressionOptions()).value == "true"
def startServer(self):
self.frame.EvaluateExpression("(void)[objc_getClass(\"IBARevealLoader\") startServer]", GetCommonExpressionOptions())
def stopServer(self):
self.frame.EvaluateExpression("(void)[objc_getClass(\"IBARevealLoader\") stopServer]", GetCommonExpressionOptions())
# Target info utilities
def TargetIsCompatible(target):
architecture = target.triple
return "apple-ios" in architecture or "apple-tvos" in architecture or "apple-watchos" in architecture
def TargetIsLocal(target):
platformName = target.platform.GetName()
return platformName.endswith("simulator") or platformName.endswith("host")
# Process info utilities
def ProcessMainThreadContainsFrameWithName(process, frameName):
mainThreadFrames = process.GetThreadAtIndex(0).frames
for frame in mainThreadFrames:
if frame.name == frameName:
return True
return False
# Debugger utilities
def GetCommonExpressionOptions():
options = lldb.SBExpressionOptions()
options.SetLanguage(lldb.eLanguageTypeObjC)
options.SetSuppressPersistentResult(True)
return options
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment