mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-11 17:20:56 +00:00
Add an new android hooking method using JDWP to inject & PLT hooks
* This is based on the GAPID method of injection, using the same steps to connect with JDWP and debug the application, breakpointing on key init-time steps to patch vulkan layer search paths and load libraries from our installed apk. * We diverge by then using PLT hooks instead of trampolines to hook the EGL/GL calls - and so we also hook dlopen. * This should work on any debuggable application - not currently checked but does not replace/break any of the previous injection, which can still work.
This commit is contained in:
@@ -95,6 +95,10 @@ set(sources
|
||||
android/android_utils.cpp
|
||||
android/android.h
|
||||
android/android_utils.h
|
||||
android/jdwp.h
|
||||
android/jdwp.cpp
|
||||
android/jdwp_util.cpp
|
||||
android/jdwp_connection.cpp
|
||||
core/plugins.cpp
|
||||
core/plugins.h
|
||||
core/resource_manager.cpp
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
namespace Android
|
||||
{
|
||||
void adbForwardPorts(int index, const std::string &deviceID)
|
||||
void adbForwardPorts(int index, const std::string &deviceID, uint16_t jdwpPort, int pid)
|
||||
{
|
||||
const char *forwardCommand = "forward tcp:%i localabstract:renderdoc_%i";
|
||||
int offs = RenderDoc_AndroidPortOffset * (index + 1);
|
||||
@@ -40,7 +40,88 @@ void adbForwardPorts(int index, const std::string &deviceID)
|
||||
RenderDoc_RemoteServerPort));
|
||||
adbExecCommand(deviceID, StringFormat::Fmt(forwardCommand, RenderDoc_FirstTargetControlPort + offs,
|
||||
RenderDoc_FirstTargetControlPort));
|
||||
|
||||
if(jdwpPort && pid)
|
||||
adbExecCommand(deviceID, StringFormat::Fmt("forward tcp:%hu jdwp:%i", jdwpPort, pid));
|
||||
}
|
||||
|
||||
uint16_t GetJdwpPort()
|
||||
{
|
||||
// we loop over a number of ports to try and avoid previous failed attempts from leaving sockets
|
||||
// open and messing with subsequent attempts
|
||||
const uint16_t portBase = RenderDoc_FirstTargetControlPort + RenderDoc_AndroidPortOffset * 2;
|
||||
|
||||
static uint16_t portIndex = 0;
|
||||
|
||||
portIndex++;
|
||||
portIndex %= RenderDoc_AndroidPortOffset;
|
||||
|
||||
return portBase + portIndex;
|
||||
}
|
||||
|
||||
std::string GetDefaultActivityForPackage(const std::string &deviceID, const std::string &packageName)
|
||||
{
|
||||
Process::ProcessResult activity =
|
||||
adbExecCommand(deviceID, StringFormat::Fmt("shell cmd package resolve-activity"
|
||||
" -c android.intent.category.LAUNCHER %s",
|
||||
packageName.c_str()));
|
||||
|
||||
if(activity.strStdout.empty())
|
||||
{
|
||||
RDCERR("Failed to resolve default activity of APK. STDERR: %s", activity.strStderror.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<std::string> lines;
|
||||
split(activity.strStdout, lines, '\n');
|
||||
|
||||
for(std::string &line : lines)
|
||||
{
|
||||
line = trim(line);
|
||||
|
||||
if(!strncmp(line.c_str(), "name=", 5))
|
||||
{
|
||||
return line.substr(5);
|
||||
}
|
||||
}
|
||||
|
||||
RDCERR("Didn't find default activity in adb output");
|
||||
return "";
|
||||
}
|
||||
|
||||
int GetCurrentPid(const std::string &deviceID, const std::string &packageName)
|
||||
{
|
||||
// try 5 times, 200ms apart to find the pid
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
Process::ProcessResult pidOutput =
|
||||
adbExecCommand(deviceID, StringFormat::Fmt("shell ps | grep %s", packageName.c_str()));
|
||||
|
||||
std::string output = trim(pidOutput.strStdout);
|
||||
size_t space = output.find_first_of("\t ");
|
||||
|
||||
if(output.empty() || output.find(packageName) == std::string::npos || space == std::string::npos)
|
||||
{
|
||||
Threading::Sleep(200);
|
||||
continue;
|
||||
}
|
||||
|
||||
char *pid = &output[space];
|
||||
while(*pid == ' ' || *pid == '\t')
|
||||
pid++;
|
||||
|
||||
char *end = pid;
|
||||
while(*end >= '0' && *end <= '9')
|
||||
end++;
|
||||
|
||||
*end = 0;
|
||||
|
||||
return atoi(pid);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t StartAndroidPackageForCapture(const char *host, const char *package)
|
||||
{
|
||||
int index = 0;
|
||||
@@ -49,11 +130,31 @@ uint32_t StartAndroidPackageForCapture(const char *host, const char *package)
|
||||
|
||||
string packageName = basename(string(package)); // Remove leading '/' if any
|
||||
|
||||
// adb shell cmd package resolve-activity -c android.intent.category.LAUNCHER com.jake.cube1
|
||||
string activityName = GetDefaultActivityForPackage(deviceID, packageName);
|
||||
|
||||
uint16_t jdwpPort = GetJdwpPort();
|
||||
|
||||
// remove any previous jdwp port forward on this port
|
||||
adbExecCommand(deviceID, StringFormat::Fmt("forward --remove tcp:%i", jdwpPort));
|
||||
// force stop the package if it was running before
|
||||
adbExecCommand(deviceID, "shell am force-stop " + packageName);
|
||||
adbForwardPorts(index, deviceID);
|
||||
// enable the vulkan layer (will only be used by vulkan programs)
|
||||
adbExecCommand(deviceID, "shell setprop debug.vulkan.layers VK_LAYER_RENDERDOC_Capture");
|
||||
adbExecCommand(deviceID,
|
||||
"shell monkey -p " + packageName + " -c android.intent.category.LAUNCHER 1");
|
||||
// start the activity in this package with debugging enabled and force-stop after starting
|
||||
adbExecCommand(deviceID, StringFormat::Fmt("shell am start -S -D %s/%s", packageName.c_str(),
|
||||
activityName.c_str()));
|
||||
|
||||
// adb shell ps | grep $PACKAGE | awk '{print $2}')
|
||||
int pid = GetCurrentPid(deviceID, packageName);
|
||||
|
||||
adbForwardPorts(index, deviceID, jdwpPort, pid);
|
||||
|
||||
// sleep a little to let the ports initialise
|
||||
Threading::Sleep(500);
|
||||
|
||||
// use a JDWP connection to inject our libraries
|
||||
InjectWithJDWP(deviceID, jdwpPort);
|
||||
|
||||
uint32_t ret = RenderDoc_FirstTargetControlPort + RenderDoc_AndroidPortOffset * (index + 1);
|
||||
uint32_t elapsed = 0,
|
||||
@@ -73,6 +174,10 @@ uint32_t StartAndroidPackageForCapture(const char *host, const char *package)
|
||||
elapsed += 1000;
|
||||
}
|
||||
|
||||
// we might open the connection early, when the library is first injected, before the vulkan
|
||||
// loader completes .
|
||||
Threading::Sleep(1000);
|
||||
|
||||
// Let the app pickup the setprop before we turn it back off for replaying.
|
||||
adbExecCommand(deviceID, "shell setprop debug.vulkan.layers :");
|
||||
|
||||
@@ -317,7 +422,7 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdc
|
||||
ret += StringFormat::Fmt("adb:%d:%s", idx, tokens[0].c_str());
|
||||
|
||||
// Forward the ports so we can see if a remoteserver/captured app is already running.
|
||||
adbForwardPorts(idx, tokens[0]);
|
||||
adbForwardPorts(idx, tokens[0], 0, 0);
|
||||
|
||||
idx++;
|
||||
}
|
||||
@@ -344,7 +449,7 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_StartAndroidRemoteServer(co
|
||||
}
|
||||
|
||||
adbExecCommand(deviceID, "shell am force-stop org.renderdoc.renderdoccmd");
|
||||
adbForwardPorts(index, deviceID);
|
||||
adbForwardPorts(index, deviceID, 0, 0);
|
||||
adbExecCommand(deviceID, "shell setprop debug.vulkan.layers :");
|
||||
adbExecCommand(
|
||||
deviceID,
|
||||
|
||||
@@ -35,4 +35,5 @@ uint32_t StartAndroidPackageForCapture(const char *host, const char *package);
|
||||
void ExtractDeviceIDAndIndex(const std::string &hostname, int &index, std::string &deviceID);
|
||||
Process::ProcessResult adbExecCommand(const std::string &deviceID, const std::string &args,
|
||||
const string &workDir = ".");
|
||||
bool InjectWithJDWP(const std::string &deviceID, uint16_t jdwpport);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,406 @@
|
||||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Baldur Karlsson
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
#include "jdwp.h"
|
||||
#include <functional>
|
||||
#include "core/core.h"
|
||||
#include "strings/string_utils.h"
|
||||
#include "android.h"
|
||||
|
||||
namespace JDWP
|
||||
{
|
||||
void InjectVulkanLayerSearchPath(Connection &conn, threadID thread, int32_t slotIdx,
|
||||
const std::string &libPath)
|
||||
{
|
||||
referenceTypeID stringClass = conn.GetType("Ljava/lang/String;");
|
||||
methodID stringConcat = conn.GetMethod(stringClass, "concat");
|
||||
|
||||
if(conn.IsErrored())
|
||||
return;
|
||||
|
||||
if(!stringClass || !stringConcat)
|
||||
{
|
||||
RDCERR("Couldn't find java.lang.String (%llu) or java.lang.String.concat() (%llu)", stringClass,
|
||||
stringConcat);
|
||||
return;
|
||||
}
|
||||
|
||||
// get the callstack frames
|
||||
std::vector<StackFrame> stack = conn.GetCallStack(thread);
|
||||
|
||||
if(stack.empty())
|
||||
{
|
||||
RDCERR("Couldn't get callstack!");
|
||||
return;
|
||||
}
|
||||
|
||||
// get the local in the top (current) frame
|
||||
value librarySearchPath = conn.GetLocalValue(thread, stack[0].id, slotIdx, Tag::Object);
|
||||
|
||||
if(librarySearchPath.tag != Tag::String || librarySearchPath.String == 0)
|
||||
{
|
||||
RDCERR("Couldn't get 'String librarySearchPath' local parameter!");
|
||||
return;
|
||||
}
|
||||
|
||||
RDCDEBUG("librarySearchPath is %s", conn.GetString(librarySearchPath.String).c_str());
|
||||
|
||||
value appendSearch = conn.NewString(thread, ":" + libPath);
|
||||
|
||||
// temp = librarySearchPath.concat(appendSearch);
|
||||
value temp = conn.InvokeInstance(thread, stringClass, stringConcat, librarySearchPath.String,
|
||||
{appendSearch});
|
||||
|
||||
if(temp.tag != Tag::String || temp.String == 0)
|
||||
{
|
||||
RDCERR("Failed to concat search path!");
|
||||
return;
|
||||
}
|
||||
|
||||
RDCDEBUG("librarySearchPath is now %s", conn.GetString(temp.String).c_str());
|
||||
|
||||
// we will have resume the thread above to call concat, invalidating our frames.
|
||||
// Re-fetch the callstack
|
||||
stack = conn.GetCallStack(thread);
|
||||
|
||||
if(stack.empty())
|
||||
{
|
||||
RDCERR("Couldn't get callstack!");
|
||||
return;
|
||||
}
|
||||
|
||||
// replace the search path with our modified one
|
||||
// librarySearchPath = temp;
|
||||
conn.SetLocalValue(thread, stack[0].id, slotIdx, temp);
|
||||
}
|
||||
|
||||
bool InjectLibraries(Network::Socket *sock, std::string libPath)
|
||||
{
|
||||
Connection conn(sock);
|
||||
|
||||
// check that the handshake completed successfully
|
||||
if(conn.IsErrored())
|
||||
return false;
|
||||
|
||||
// immediately re-suspend, as connecting will have woken it up
|
||||
conn.Suspend();
|
||||
|
||||
conn.SetupIDSizes();
|
||||
|
||||
if(conn.IsErrored())
|
||||
return false;
|
||||
|
||||
// default to arm as a safe bet
|
||||
std::string abi = "armeabi-v7a";
|
||||
|
||||
// determine the CPU ABI from android.os.Build.CPU_ABI
|
||||
referenceTypeID buildClass = conn.GetType("Landroid/os/Build;");
|
||||
if(buildClass)
|
||||
{
|
||||
fieldID CPU_ABI = conn.GetField(buildClass, "CPU_ABI");
|
||||
|
||||
if(CPU_ABI)
|
||||
{
|
||||
value val = conn.GetFieldValue(buildClass, CPU_ABI);
|
||||
|
||||
if(val.tag == Tag::String)
|
||||
abi = conn.GetString(val.String);
|
||||
else
|
||||
RDCERR("CPU_ABI value was type %u, not string!", (uint32_t)val.tag);
|
||||
}
|
||||
else
|
||||
{
|
||||
RDCERR("Couldn't find CPU_ABI field in android.os.Build");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RDCERR("Couldn't find android.os.Build");
|
||||
}
|
||||
|
||||
// use the ABI to determine where to find our library
|
||||
if(abi == "armeabi-v7a")
|
||||
{
|
||||
libPath += "arm";
|
||||
}
|
||||
else if(abi == "arm64-v8a")
|
||||
{
|
||||
libPath += "arm64";
|
||||
}
|
||||
else
|
||||
{
|
||||
RDCERR("Unhandled ABI '%s'", abi.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if(conn.IsErrored())
|
||||
return false;
|
||||
|
||||
// try to find the vulkan loader class and patch the search path when getClassLoader is called.
|
||||
// This is an optional step as some devices may not support vulkan and may not have this class, so
|
||||
// in that case we just skip it.
|
||||
referenceTypeID vulkanLoaderClass = conn.GetType("Landroid/app/ApplicationLoaders;");
|
||||
|
||||
if(vulkanLoaderClass)
|
||||
{
|
||||
// See:
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/f9419f0f8524da4980726e06130a80e0fb226763/core/java/android/app/ApplicationLoaders.java
|
||||
// for the public getClassLoader.
|
||||
|
||||
// look for both signatures in this order, note that the final "String classLoaderName" was
|
||||
// added recently
|
||||
|
||||
// ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
|
||||
// String librarySearchPath, String libraryPermittedPath,
|
||||
// ClassLoader parent);
|
||||
methodID vulkanLoaderMethod = conn.GetMethod(
|
||||
vulkanLoaderClass, "getClassLoader",
|
||||
"(Ljava/lang/String;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)"
|
||||
"Ljava/lang/ClassLoader;");
|
||||
|
||||
// ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
|
||||
// String librarySearchPath, String libraryPermittedPath,
|
||||
// ClassLoader parent, String classLoaderName);
|
||||
if(vulkanLoaderMethod == 0)
|
||||
vulkanLoaderMethod = conn.GetMethod(
|
||||
vulkanLoaderClass, "getClassLoader",
|
||||
"(Ljava/lang/String;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;"
|
||||
"Ljava/lang/String;)Ljava/lang/ClassLoader;");
|
||||
|
||||
if(vulkanLoaderMethod)
|
||||
{
|
||||
int32_t slotIdx =
|
||||
conn.GetLocalVariable(vulkanLoaderClass, vulkanLoaderMethod, "librarySearchPath");
|
||||
|
||||
// as a default, use the 4th slot as it's the 4th argument argument (0 is this), if symbols
|
||||
// weren't available we can't identify the variable by name
|
||||
if(slotIdx == -1)
|
||||
slotIdx = 4;
|
||||
|
||||
// wait for the method to get hit - WaitForEvent will resume, watch events, and return
|
||||
// (re-suspended) when the first event occurs that matches the filter function
|
||||
Event evData =
|
||||
conn.WaitForEvent(EventKind::MethodEntry, {{ModifierKind::ClassOnly, vulkanLoaderClass}},
|
||||
[vulkanLoaderMethod](const Event &evData) {
|
||||
return evData.MethodEntry.location.methodID == vulkanLoaderMethod;
|
||||
});
|
||||
|
||||
// if we successfully hit the event, try to inject
|
||||
if(evData.eventKind == EventKind::MethodEntry)
|
||||
InjectVulkanLayerSearchPath(conn, evData.MethodEntry.thread, slotIdx, libPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we expect if we can get the class, we should find the method.
|
||||
RDCERR("Couldn't find getClassLoader method in android.app.ApplicationLoaders");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// warning only - it's not a problem if we're capturing GLES
|
||||
RDCWARN("Couldn't find class android.app.ApplicationLoaders. Vulkan won't be hooked.");
|
||||
}
|
||||
|
||||
// we get here whether we processed vulkan or not. Now we need to wait for the application to hit
|
||||
// onCreate() and load our library
|
||||
|
||||
referenceTypeID androidApp = conn.GetType("Landroid/app/Application;");
|
||||
|
||||
if(androidApp == 0)
|
||||
{
|
||||
RDCERR("Couldn't find android.app.Application");
|
||||
return false;
|
||||
}
|
||||
|
||||
methodID appConstruct = conn.GetMethod(androidApp, "<init>", "()V");
|
||||
|
||||
if(appConstruct == 0)
|
||||
{
|
||||
RDCERR("Couldn't find android.app.Application constructor");
|
||||
return false;
|
||||
}
|
||||
|
||||
threadID thread;
|
||||
|
||||
// wait until we hit the constructor of android.app.Application
|
||||
{
|
||||
Event evData = conn.WaitForEvent(EventKind::MethodEntry, {{ModifierKind::ClassOnly, androidApp}},
|
||||
[appConstruct](const Event &evData) {
|
||||
return evData.MethodEntry.location.methodID == appConstruct;
|
||||
});
|
||||
|
||||
if(evData.eventKind == EventKind::MethodEntry)
|
||||
thread = evData.MethodEntry.thread;
|
||||
}
|
||||
|
||||
if(thread == 0)
|
||||
{
|
||||
RDCERR("Didn't hit android.app.Application constructor");
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the callstack frames
|
||||
std::vector<StackFrame> stack = conn.GetCallStack(thread);
|
||||
|
||||
if(stack.empty())
|
||||
{
|
||||
RDCERR("Couldn't get callstack!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// get this on the top frame
|
||||
objectID thisPtr = conn.GetThis(thread, stack[0].id);
|
||||
|
||||
if(thisPtr == 0)
|
||||
{
|
||||
RDCERR("Couldn't find this");
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the type for the this object
|
||||
referenceTypeID thisType = conn.GetType(thisPtr);
|
||||
|
||||
if(thisType == 0)
|
||||
{
|
||||
RDCERR("Couldn't find this's class");
|
||||
return false;
|
||||
}
|
||||
|
||||
// call getClass, this will give us the information for the most derived class
|
||||
methodID getClass = conn.GetMethod(thisType, "getClass", "()Ljava/lang/Class;");
|
||||
|
||||
if(getClass == 0)
|
||||
{
|
||||
RDCERR("Couldn't find this.getClass()");
|
||||
return false;
|
||||
}
|
||||
|
||||
value thisClass = conn.InvokeInstance(thread, thisType, getClass, thisPtr, {});
|
||||
|
||||
if(thisClass.tag != Tag::ClassObject || thisClass.Object == 0)
|
||||
{
|
||||
RDCERR("Failed to call this.getClass()!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// look up onCreate in the most derived class - since we can't guarantee that the base
|
||||
// application.app.onCreate() will get called.
|
||||
methodID onCreate = conn.GetMethod(thisClass.Object, "onCreate", "()V");
|
||||
|
||||
if(onCreate == 0)
|
||||
{
|
||||
RDCERR("Couldn't find this.getClass().onCreate()");
|
||||
return false;
|
||||
}
|
||||
|
||||
// wait until we hit the derived onCreate
|
||||
{
|
||||
thread = 0;
|
||||
|
||||
Event evData = conn.WaitForEvent(
|
||||
EventKind::MethodEntry, {{ModifierKind::ClassOnly, thisClass.Object}},
|
||||
[onCreate](const Event &evData) { return evData.MethodEntry.location.methodID == onCreate; });
|
||||
|
||||
if(evData.eventKind == EventKind::MethodEntry)
|
||||
thread = evData.MethodEntry.thread;
|
||||
}
|
||||
|
||||
if(thread == 0)
|
||||
{
|
||||
RDCERR("Didn't hit android.app.Application.onCreate()");
|
||||
return false;
|
||||
}
|
||||
|
||||
// find java.lang.Runtime
|
||||
referenceTypeID runtime = conn.GetType("Ljava/lang/Runtime;");
|
||||
|
||||
if(runtime == 0)
|
||||
{
|
||||
RDCERR("Couldn't find java.lang.Runtime");
|
||||
return false;
|
||||
}
|
||||
|
||||
// find both the static Runtime.getRuntime() as well as the instance Runtime.load()
|
||||
methodID getRuntime = conn.GetMethod(runtime, "getRuntime", "()Ljava/lang/Runtime;");
|
||||
methodID load = conn.GetMethod(runtime, "load", "(Ljava/lang/String;)V");
|
||||
|
||||
if(getRuntime == 0 || load == 0)
|
||||
{
|
||||
RDCERR("Couldn't find java.lang.Runtime.getRuntime() %llu or java.lang.Runtime.load() %llu",
|
||||
getRuntime, load);
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the Runtime object via java.lang.Runtime.getRuntime()
|
||||
value runtimeObject = conn.InvokeStatic(thread, runtime, getRuntime, {});
|
||||
|
||||
if(runtimeObject.tag != Tag::Object || runtimeObject.Object == 0)
|
||||
{
|
||||
RDCERR("Failed to call getClass!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// call Runtime.load() on our library. This will load the library and from then on it's
|
||||
// responsible for injecting its hooks into GLES on its own. See android_hook.cpp for more
|
||||
// information on the implementation
|
||||
value ret =
|
||||
conn.InvokeInstance(thread, runtime, load, runtimeObject.Object,
|
||||
{conn.NewString(thread, libPath + "/libVkLayer_GLES_RenderDoc.so")});
|
||||
|
||||
if(ret.tag != Tag::Void)
|
||||
{
|
||||
RDCERR("Failed to call load()!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}; // namespace JDWP
|
||||
|
||||
namespace Android
|
||||
{
|
||||
bool InjectWithJDWP(const std::string &deviceID, uint16_t jdwpport)
|
||||
{
|
||||
Network::Socket *sock = Network::CreateClientSocket("localhost", jdwpport, 500);
|
||||
|
||||
if(sock)
|
||||
{
|
||||
std::string apk = adbExecCommand(deviceID, "shell pm path org.renderdoc.renderdoccmd").strStdout;
|
||||
apk = trim(apk);
|
||||
apk.resize(apk.size() - 8);
|
||||
apk.erase(0, 8);
|
||||
|
||||
bool ret = JDWP::InjectLibraries(sock, apk + "lib/");
|
||||
delete sock;
|
||||
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
RDCERR("Couldn't make JDWP connection");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}; // namespace Android
|
||||
@@ -0,0 +1,531 @@
|
||||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Baldur Karlsson
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "serialise/streamio.h"
|
||||
|
||||
namespace JDWP
|
||||
{
|
||||
// enums and structs defined by the JDWP spec. These are defined in:
|
||||
// https://docs.oracle.com/javase/7/docs/platform/jpda/jdwp/jdwp-protocol.html#JDWP_Tag
|
||||
|
||||
enum class CommandSet : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
VirtualMachine = 1,
|
||||
ReferenceType = 2,
|
||||
ClassType = 3,
|
||||
ArrayType = 4,
|
||||
InterfaceType = 5,
|
||||
Method = 6,
|
||||
Field = 8,
|
||||
ObjectReference = 9,
|
||||
StringReference = 10,
|
||||
ThreadReference = 11,
|
||||
ThreadGroupReference = 12,
|
||||
ArrayReference = 13,
|
||||
ClassLoaderReference = 14,
|
||||
EventRequest = 15,
|
||||
StackFrame = 16,
|
||||
ClassObjectReference = 17,
|
||||
Event = 64,
|
||||
};
|
||||
|
||||
enum class TypeTag : byte
|
||||
{
|
||||
Class = 1,
|
||||
Interface = 2,
|
||||
Arrary = 3,
|
||||
};
|
||||
|
||||
enum class Tag : byte
|
||||
{
|
||||
Unknown = '0',
|
||||
Array = '[',
|
||||
Byte = 'B',
|
||||
Char = 'C',
|
||||
Object = 'L',
|
||||
Float = 'F',
|
||||
Double = 'D',
|
||||
Int = 'I',
|
||||
Long = 'J',
|
||||
Short = 'S',
|
||||
Void = 'V',
|
||||
Boolean = 'Z',
|
||||
String = 's',
|
||||
Thread = 't',
|
||||
ThreadGroup = 'g',
|
||||
ClassLoader = 'l',
|
||||
ClassObject = 'c',
|
||||
};
|
||||
|
||||
enum class EventKind : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
SingleStep = 1,
|
||||
Breakpoint = 2,
|
||||
FramePop = 3,
|
||||
Exception = 4,
|
||||
UserDefined = 5,
|
||||
ThreadStart = 6,
|
||||
ThreadDeath = 7,
|
||||
ThreadEnd = 7,
|
||||
ClassPrepare = 8,
|
||||
ClassUnload = 9,
|
||||
ClassLoad = 10,
|
||||
FieldAccess = 20,
|
||||
FieldModification = 21,
|
||||
ExceptionCatch = 30,
|
||||
MethodEntry = 40,
|
||||
MethodExit = 41,
|
||||
MethodExitWithReturnValue = 42,
|
||||
MonitorContendedEnter = 43,
|
||||
MonitorContendedEntered = 44,
|
||||
MonitorWait = 45,
|
||||
MonitorWaited = 46,
|
||||
VMStart = 90,
|
||||
VMInit = 90,
|
||||
VMDeath = 99,
|
||||
VMDisconnected = 100,
|
||||
};
|
||||
|
||||
enum class ModifierKind : byte
|
||||
{
|
||||
Count = 1,
|
||||
Conditional = 2,
|
||||
ThreadOnly = 3,
|
||||
ClassOnly = 4,
|
||||
ClassMatch = 5,
|
||||
ClassExclude = 6,
|
||||
LocationOnly = 7,
|
||||
ExceptionOnly = 8,
|
||||
FieldOnly = 9,
|
||||
Step = 10,
|
||||
InstanceOnly = 11,
|
||||
SourceNameMatch = 12,
|
||||
};
|
||||
|
||||
enum class SuspendPolicy : byte
|
||||
{
|
||||
None = 0,
|
||||
EventThread = 1,
|
||||
All = 2,
|
||||
};
|
||||
|
||||
enum class InvokeOptions : int32_t
|
||||
{
|
||||
SingleThreaded = 0x1,
|
||||
NonVirtual = 0x2,
|
||||
};
|
||||
|
||||
enum class ClassStatus : int32_t
|
||||
{
|
||||
Verified = 0x1,
|
||||
Prepared = 0x2,
|
||||
Initialized = 0x4,
|
||||
Error = 0x8,
|
||||
};
|
||||
|
||||
enum class Error : uint16_t
|
||||
{
|
||||
None = 0,
|
||||
InvalidThread = 10,
|
||||
InvalidThreadGroup = 11,
|
||||
InvalidPriority = 12,
|
||||
ThreadNotSuspended = 13,
|
||||
ThreadSuspended = 14,
|
||||
ThreadNotAlive = 15,
|
||||
InvalidObject = 20,
|
||||
InvalidClass = 21,
|
||||
ClassNotPrepared = 22,
|
||||
InvalidMethodid = 23,
|
||||
InvalidLocation = 24,
|
||||
InvalidFieldid = 25,
|
||||
InvalidFrameid = 30,
|
||||
NoMoreFrames = 31,
|
||||
OpaqueFrame = 32,
|
||||
NotCurrentFrame = 33,
|
||||
TypeMismatch = 34,
|
||||
InvalidSlot = 35,
|
||||
Duplicate = 40,
|
||||
NotFound = 41,
|
||||
InvalidMonitor = 50,
|
||||
NotMonitorOwner = 51,
|
||||
Interrupt = 52,
|
||||
InvalidClassFormat = 60,
|
||||
CircularClassDefinition = 61,
|
||||
FailsVerification = 62,
|
||||
AddMethodNotImplemented = 63,
|
||||
SchemaChangeNotImplemented = 64,
|
||||
InvalidTypestate = 65,
|
||||
HierarchyChangeNotImplemented = 66,
|
||||
DeleteMethodNotImplemented = 67,
|
||||
UnsupportedVersion = 68,
|
||||
NamesDontMatch = 69,
|
||||
ClassModifiersChangeNotImplemented = 70,
|
||||
MethodModifiersChangeNotImplemented = 71,
|
||||
NotImplemented = 99,
|
||||
NullPointer = 100,
|
||||
AbsentInformation = 101,
|
||||
InvalidEventType = 102,
|
||||
IllegalArgument = 103,
|
||||
OutOfMemory = 110,
|
||||
AccessDenied = 111,
|
||||
VmDead = 112,
|
||||
Internal = 113,
|
||||
UnattachedThread = 115,
|
||||
InvalidTag = 500,
|
||||
AlreadyInvoking = 502,
|
||||
InvalidIndex = 503,
|
||||
InvalidLength = 504,
|
||||
InvalidString = 506,
|
||||
InvalidClassLoader = 507,
|
||||
InvalidArray = 508,
|
||||
TransportLoad = 509,
|
||||
TransportInit = 510,
|
||||
NativeMethod = 511,
|
||||
InvalidCount = 512,
|
||||
};
|
||||
|
||||
struct CommandData;
|
||||
|
||||
// we abstract the objectID size away, and always treat it as a uint64 (if it's actually 4 bytes, we
|
||||
// just only read/write the lower 4 bytes).
|
||||
struct objectID
|
||||
{
|
||||
public:
|
||||
objectID() = default;
|
||||
objectID(uint64_t v) { data.u64 = v; }
|
||||
operator uint64_t() const { return size == 4 ? data.u32 : data.u64; }
|
||||
static int32_t getSize() { return size; }
|
||||
static void setSize(int32_t s)
|
||||
{
|
||||
RDCASSERT(s == 4 || s == 8);
|
||||
size = s;
|
||||
}
|
||||
|
||||
void EndianSwap()
|
||||
{
|
||||
if(size == 4)
|
||||
data.u32 = ::EndianSwap(data.u32);
|
||||
else
|
||||
data.u64 = ::EndianSwap(data.u64);
|
||||
}
|
||||
|
||||
private:
|
||||
union
|
||||
{
|
||||
uint32_t u32;
|
||||
uint64_t u64;
|
||||
} data;
|
||||
static int32_t size;
|
||||
};
|
||||
|
||||
// all object types are the same and we don't care too much about type safety, so we just typedef
|
||||
// them.
|
||||
typedef objectID threadID;
|
||||
typedef objectID threadGroupID;
|
||||
typedef objectID stringID;
|
||||
typedef objectID classLoaderID;
|
||||
typedef objectID classObjectID;
|
||||
typedef objectID arrayID;
|
||||
typedef objectID referenceTypeID;
|
||||
typedef objectID classID;
|
||||
typedef objectID interfaceID;
|
||||
typedef objectID arrayTypeID;
|
||||
typedef objectID methodID;
|
||||
typedef objectID fieldID;
|
||||
typedef objectID frameID;
|
||||
|
||||
struct taggedObjectID
|
||||
{
|
||||
Tag tag;
|
||||
objectID id;
|
||||
};
|
||||
|
||||
struct value
|
||||
{
|
||||
Tag tag;
|
||||
|
||||
union
|
||||
{
|
||||
arrayID Array;
|
||||
byte Byte;
|
||||
char Char;
|
||||
objectID Object;
|
||||
float Float;
|
||||
double Double;
|
||||
int32_t Int;
|
||||
int64_t Long;
|
||||
int16_t Short;
|
||||
bool Bool;
|
||||
objectID String;
|
||||
threadID Thread;
|
||||
threadGroupID ThreadGroup;
|
||||
classLoaderID ClassLoader;
|
||||
classObjectID ClassObject;
|
||||
};
|
||||
};
|
||||
|
||||
struct Location
|
||||
{
|
||||
TypeTag tag;
|
||||
objectID classID;
|
||||
objectID methodID;
|
||||
uint64_t index;
|
||||
};
|
||||
|
||||
struct Method
|
||||
{
|
||||
methodID id;
|
||||
std::string name;
|
||||
std::string signature;
|
||||
int32_t modBits;
|
||||
};
|
||||
|
||||
struct Field
|
||||
{
|
||||
fieldID id;
|
||||
std::string name;
|
||||
std::string signature;
|
||||
int32_t modBits;
|
||||
};
|
||||
|
||||
struct VariableSlot
|
||||
{
|
||||
uint64_t codeIndex;
|
||||
std::string name;
|
||||
std::string signature;
|
||||
uint32_t length;
|
||||
int32_t slot;
|
||||
};
|
||||
|
||||
struct StackFrame
|
||||
{
|
||||
frameID id;
|
||||
Location location;
|
||||
};
|
||||
|
||||
struct EventFilter
|
||||
{
|
||||
ModifierKind modKind;
|
||||
referenceTypeID ClassOnly;
|
||||
};
|
||||
|
||||
struct Event
|
||||
{
|
||||
EventKind eventKind;
|
||||
int32_t requestID;
|
||||
|
||||
struct
|
||||
{
|
||||
threadID thread;
|
||||
Location location;
|
||||
} MethodEntry;
|
||||
|
||||
struct
|
||||
{
|
||||
threadID thread;
|
||||
TypeTag refTypeTag;
|
||||
referenceTypeID typeID;
|
||||
std::string signature;
|
||||
union
|
||||
{
|
||||
// get around strict aliasing
|
||||
ClassStatus status;
|
||||
int32_t statusInt;
|
||||
};
|
||||
} ClassPrepare;
|
||||
};
|
||||
|
||||
struct Command
|
||||
{
|
||||
Command(CommandSet s = CommandSet::Unknown, byte c = 0) : commandset(s), command(c) {}
|
||||
uint32_t Send(StreamWriter &writer);
|
||||
void Recv(StreamReader &reader);
|
||||
|
||||
// only these need to be publicly writeable
|
||||
CommandSet commandset;
|
||||
byte command;
|
||||
// get the ID assigned/received
|
||||
uint32_t GetID() { return id; }
|
||||
// get the error received (only for replies)
|
||||
Error GetError() { return error; }
|
||||
// read-write access to the data (encodes and endian swaps)
|
||||
CommandData GetData();
|
||||
|
||||
private:
|
||||
static uint32_t idalloc;
|
||||
|
||||
// automatically calculated
|
||||
uint32_t length = 0;
|
||||
uint32_t id = 0;
|
||||
Error error = Error::None;
|
||||
std::vector<byte> data;
|
||||
};
|
||||
|
||||
// a helper class for reading/writing the payload of a packet, with endian swapping.
|
||||
struct CommandData
|
||||
{
|
||||
CommandData(std::vector<byte> &dataStore) : data(dataStore) {}
|
||||
template <typename T>
|
||||
CommandData &Read(T &val)
|
||||
{
|
||||
RDCCOMPILE_ASSERT(sizeof(bool) == 1, "bool must be 1 byte for default template");
|
||||
ReadBytes(&val, sizeof(T));
|
||||
val = EndianSwap(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
CommandData &Write(const T &val)
|
||||
{
|
||||
T tmp = EndianSwap(val);
|
||||
WriteBytes(&tmp, sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// called when we've finished reading, to ensure we consumed all the data
|
||||
void Done() { RDCASSERT(offs == data.size(), offs, data.size()); }
|
||||
private:
|
||||
std::vector<byte> &data;
|
||||
size_t offs = 0;
|
||||
|
||||
void ReadBytes(void *bytes, size_t length);
|
||||
void WriteBytes(const void *bytes, size_t length);
|
||||
};
|
||||
|
||||
// overloads for the basic types we want to read and write
|
||||
template <>
|
||||
CommandData &CommandData::Read(std::string &str);
|
||||
template <>
|
||||
CommandData &CommandData::Write(const std::string &str);
|
||||
template <>
|
||||
CommandData &CommandData::Read(objectID &id);
|
||||
template <>
|
||||
CommandData &CommandData::Write(const objectID &id);
|
||||
template <>
|
||||
CommandData &CommandData::Read(taggedObjectID &id);
|
||||
template <>
|
||||
CommandData &CommandData::Write(const taggedObjectID &id);
|
||||
template <>
|
||||
CommandData &CommandData::Read(value &val);
|
||||
template <>
|
||||
CommandData &CommandData::Write(const value &val);
|
||||
template <>
|
||||
CommandData &CommandData::Read(Location &loc);
|
||||
template <>
|
||||
CommandData &CommandData::Write(const Location &loc);
|
||||
|
||||
typedef std::function<bool(const Event &evData)> EventFilterFunction;
|
||||
|
||||
// A JDWP connection, with high-level helper functions that implement the protocol underneath
|
||||
class Connection
|
||||
{
|
||||
public:
|
||||
Connection(Network::Socket *sock);
|
||||
~Connection();
|
||||
|
||||
bool IsErrored() { return error || writer.IsErrored() || reader.IsErrored(); }
|
||||
// suspend or resume the whole VM's execution.
|
||||
void Suspend();
|
||||
void Resume();
|
||||
|
||||
// this must be called first before any of the commands that use IDs. It's separated out because
|
||||
// depending on the circumstance it might be necessary to suspend the VM first before sending this
|
||||
// command.
|
||||
void SetupIDSizes();
|
||||
|
||||
// get the type handle for a given JNI signature
|
||||
referenceTypeID GetType(const std::string &signature);
|
||||
|
||||
// get the type handle for an object
|
||||
referenceTypeID GetType(objectID obj);
|
||||
|
||||
// get a field handle. If signature is empty, it's ignored for matching
|
||||
fieldID GetField(referenceTypeID type, const std::string &name, const std::string &signature = "");
|
||||
|
||||
// get the value of a static field
|
||||
value GetFieldValue(referenceTypeID type, fieldID field);
|
||||
|
||||
// get a method handle. If signature is empty, it's ignored for matching
|
||||
methodID GetMethod(referenceTypeID type, const std::string &name,
|
||||
const std::string &signature = "");
|
||||
|
||||
// get a local variable slot. If signature is empty, it's ignored for matching. Returns -1 if not
|
||||
// found
|
||||
int32_t GetLocalVariable(referenceTypeID type, methodID method, const std::string &name,
|
||||
const std::string &signature = "");
|
||||
|
||||
// get a thread's stack frames
|
||||
std::vector<StackFrame> GetCallStack(threadID thread);
|
||||
|
||||
// get the this pointer for a given stack frame
|
||||
objectID GetThis(threadID thread, frameID frame);
|
||||
|
||||
// get the value of a string object
|
||||
std::string GetString(objectID str);
|
||||
|
||||
// resume the VM and wait for an event to happen, filtered by some built-in filters or a callback.
|
||||
// Returns the matching event and leaves the VM suspended, or an empty event if there was a
|
||||
// problem
|
||||
Event WaitForEvent(EventKind kind, const std::vector<EventFilter> &eventFilters,
|
||||
EventFilterFunction filterCallback);
|
||||
|
||||
// create a new string reference on the given thread
|
||||
value NewString(threadID thread, const std::string &str);
|
||||
|
||||
// get/set local variables
|
||||
value GetLocalValue(threadID thread, frameID frame, int32_t slot, Tag tag);
|
||||
void SetLocalValue(threadID thread, frameID frame, int32_t slot, value val);
|
||||
|
||||
// invoke a static method
|
||||
value InvokeStatic(threadID thread, classID clazz, methodID method,
|
||||
const std::vector<value> &arguments,
|
||||
InvokeOptions options = InvokeOptions::SingleThreaded)
|
||||
{
|
||||
// instance detects if object is empty, and invokes as static
|
||||
return InvokeInstance(thread, clazz, method, 0, arguments, options);
|
||||
}
|
||||
// invoke an instance method
|
||||
value InvokeInstance(threadID thread, classID clazz, methodID method, objectID object,
|
||||
const std::vector<value> &arguments,
|
||||
InvokeOptions options = InvokeOptions::SingleThreaded);
|
||||
|
||||
private:
|
||||
StreamWriter writer;
|
||||
StreamReader reader;
|
||||
bool error = false;
|
||||
|
||||
int suspendRefCount = 0;
|
||||
|
||||
bool SendReceive(Command &cmd);
|
||||
void ReadEvent(CommandData &data, Event &ev);
|
||||
|
||||
classID GetSuper(classID clazz);
|
||||
std::string GetSignature(referenceTypeID typeID);
|
||||
std::vector<Method> GetMethods(referenceTypeID searchClass);
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,586 @@
|
||||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Baldur Karlsson
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
#include "jdwp.h"
|
||||
|
||||
namespace JDWP
|
||||
{
|
||||
// short helper functions to read/write vectors - always preceeded by an int length
|
||||
template <typename inner>
|
||||
void ReadVector(CommandData &data, std::vector<inner> &vec,
|
||||
std::function<void(CommandData &data, inner &i)> process)
|
||||
{
|
||||
int32_t count = 0;
|
||||
data.Read(count);
|
||||
|
||||
vec.resize((size_t)count);
|
||||
for(int32_t i = 0; i < count; i++)
|
||||
process(data, vec[i]);
|
||||
}
|
||||
|
||||
template <typename inner>
|
||||
void WriteVector(CommandData &data, const std::vector<inner> &vec,
|
||||
std::function<void(CommandData &data, const inner &i)> process)
|
||||
{
|
||||
int32_t count = (int32_t)vec.size();
|
||||
data.Write(count);
|
||||
|
||||
for(int32_t i = 0; i < count; i++)
|
||||
process(data, vec[i]);
|
||||
}
|
||||
|
||||
Connection::Connection(Network::Socket *sock)
|
||||
: writer(sock, Ownership::Nothing), reader(sock, Ownership::Nothing)
|
||||
{
|
||||
// the first thing we do is write the handshake bytes and expect them immediately echo'd back.
|
||||
const int handshakeLength = 14;
|
||||
const char handshake[handshakeLength + 1] = "JDWP-Handshake";
|
||||
|
||||
writer.Write(handshake, handshakeLength);
|
||||
writer.Flush();
|
||||
|
||||
char response[15] = {};
|
||||
reader.Read(response, handshakeLength);
|
||||
|
||||
if(memcmp(handshake, response, 14))
|
||||
{
|
||||
RDCERR("handshake failed - received >%s< - expected >%s<", response, handshake);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
Connection::~Connection()
|
||||
{
|
||||
// bail immediately if we're in error state
|
||||
if(IsErrored())
|
||||
return;
|
||||
|
||||
// otherwise, undo any suspends we might have done that are still outstanding, in case a logic
|
||||
// error made us bail while we had the VM suspended. We copy the refcount since Resume()
|
||||
// decrements it.
|
||||
int ref = suspendRefCount;
|
||||
for(int i = 0; i < ref; i++)
|
||||
Resume();
|
||||
}
|
||||
|
||||
bool Connection::SendReceive(Command &cmd)
|
||||
{
|
||||
// send the command, and recieve the reply back into the same object.
|
||||
// save the auto-generated ID for this command so we can compare it to the reply - we expect a
|
||||
// synchronous reply, no other commands in the way.
|
||||
uint32_t id = cmd.Send(writer);
|
||||
cmd.Recv(reader);
|
||||
|
||||
if(id != cmd.GetID())
|
||||
{
|
||||
RDCERR("Didn't get matching reply packet for %d/%d", cmd.commandset, cmd.command);
|
||||
error = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Connection::SetupIDSizes()
|
||||
{
|
||||
Command cmd(CommandSet::VirtualMachine, 7);
|
||||
if(!SendReceive(cmd))
|
||||
return;
|
||||
|
||||
int32_t fieldIDSize = 0;
|
||||
int32_t methodIDSize = 0;
|
||||
int32_t objectIDSize = 0;
|
||||
int32_t referenceTypeIDSize = 0;
|
||||
int32_t frameIDSize = 0;
|
||||
|
||||
cmd.GetData()
|
||||
.Read(fieldIDSize)
|
||||
.Read(methodIDSize)
|
||||
.Read(objectIDSize)
|
||||
.Read(referenceTypeIDSize)
|
||||
.Read(frameIDSize)
|
||||
.Done();
|
||||
|
||||
// we only support sizes that are all the same
|
||||
if(fieldIDSize != methodIDSize || fieldIDSize != objectIDSize ||
|
||||
fieldIDSize != referenceTypeIDSize || fieldIDSize != frameIDSize)
|
||||
{
|
||||
RDCLOG("Field ID sizes (%d %d %d %d %d) are not the same. Unsupported!", fieldIDSize,
|
||||
methodIDSize, objectIDSize, referenceTypeIDSize, frameIDSize);
|
||||
error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// we also only support 4 or 8 bytes
|
||||
if(fieldIDSize != 4 && fieldIDSize != 8)
|
||||
{
|
||||
RDCLOG("Field ID size %d is unsupported!", fieldIDSize);
|
||||
error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
objectID::setSize(fieldIDSize);
|
||||
}
|
||||
|
||||
void Connection::Suspend()
|
||||
{
|
||||
suspendRefCount++;
|
||||
|
||||
Command cmd(CommandSet::VirtualMachine, 8);
|
||||
SendReceive(cmd);
|
||||
}
|
||||
|
||||
void Connection::Resume()
|
||||
{
|
||||
if(suspendRefCount > 0)
|
||||
suspendRefCount--;
|
||||
else
|
||||
RDCERR("Resuming while we are believed to be running! suspend refcount problem");
|
||||
|
||||
Command cmd(CommandSet::VirtualMachine, 9);
|
||||
SendReceive(cmd);
|
||||
}
|
||||
|
||||
referenceTypeID Connection::GetType(const std::string &signature)
|
||||
{
|
||||
Command cmd(CommandSet::VirtualMachine, 2);
|
||||
cmd.GetData().Write(signature);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
int32_t numTypes = 0;
|
||||
byte typetag;
|
||||
referenceTypeID ret;
|
||||
int32_t status; // unused
|
||||
|
||||
cmd.GetData().Read(numTypes).Read(typetag).Read(ret).Read(status).Done();
|
||||
|
||||
if(numTypes == 0)
|
||||
return {};
|
||||
else if(numTypes > 1)
|
||||
RDCWARN("Multiple types found for '%s'", signature.c_str());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
referenceTypeID Connection::GetType(objectID obj)
|
||||
{
|
||||
Command cmd(CommandSet::ObjectReference, 1);
|
||||
cmd.GetData().Write(obj);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
byte tag = 0;
|
||||
referenceTypeID ret = {};
|
||||
|
||||
cmd.GetData().Read(tag).Read(ret).Done();
|
||||
return ret;
|
||||
}
|
||||
|
||||
methodID Connection::GetMethod(referenceTypeID type, const std::string &name,
|
||||
const std::string &signature)
|
||||
{
|
||||
referenceTypeID searchClass = type;
|
||||
|
||||
while(searchClass != 0)
|
||||
{
|
||||
/*
|
||||
RDCDEBUG("Searching for %s (%s) in '%s'", name.c_str(), signature.c_str(),
|
||||
GetSignature(searchClass).c_str());
|
||||
*/
|
||||
|
||||
std::vector<Method> methods = GetMethods(searchClass);
|
||||
|
||||
for(const Method &m : methods)
|
||||
{
|
||||
if(m.name == name && (signature == "" || signature == m.signature))
|
||||
{
|
||||
// RDCDEBUG("Found %s (%s)!", m.name.c_str(), m.signature.c_str());
|
||||
return m.id;
|
||||
}
|
||||
}
|
||||
|
||||
searchClass = GetSuper(searchClass);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int32_t Connection::GetLocalVariable(referenceTypeID type, methodID method, const std::string &name,
|
||||
const std::string &signature)
|
||||
{
|
||||
Command cmd(CommandSet::Method, 2);
|
||||
cmd.GetData().Write(type).Write(method);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return -1;
|
||||
|
||||
int32_t argumentCount = 0;
|
||||
std::vector<VariableSlot> slots;
|
||||
|
||||
CommandData data = cmd.GetData();
|
||||
data.Read(argumentCount); // unused for now
|
||||
ReadVector<VariableSlot>(data, slots, [](CommandData &data, VariableSlot &s) {
|
||||
data.Read(s.codeIndex).Read(s.name).Read(s.signature).Read(s.length).Read(s.slot);
|
||||
});
|
||||
data.Done();
|
||||
|
||||
for(const VariableSlot &s : slots)
|
||||
if(s.name == name && (signature == "" || signature == s.signature))
|
||||
return s.slot;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
fieldID Connection::GetField(referenceTypeID type, const std::string &name,
|
||||
const std::string &signature)
|
||||
{
|
||||
Command cmd(CommandSet::ReferenceType, 4);
|
||||
cmd.GetData().Write(type);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
std::vector<Field> fields;
|
||||
CommandData data = cmd.GetData();
|
||||
ReadVector<Field>(data, fields, [](CommandData &data, Field &f) {
|
||||
data.Read(f.id).Read(f.name).Read(f.signature).Read(f.modBits);
|
||||
});
|
||||
data.Done();
|
||||
|
||||
for(const Field &f : fields)
|
||||
if(f.name == name && (signature == "" || signature == f.signature))
|
||||
return f.id;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
value Connection::GetFieldValue(referenceTypeID type, fieldID field)
|
||||
{
|
||||
Command cmd(CommandSet::ReferenceType, 6);
|
||||
cmd.GetData().Write(type).Write<int32_t>(1).Write(field);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
int32_t numVals = 0;
|
||||
value ret;
|
||||
|
||||
cmd.GetData().Read(numVals).Read(ret).Done();
|
||||
|
||||
if(numVals != 1)
|
||||
RDCWARN("Unexpected number of values found in GetValue: %d", numVals);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<StackFrame> Connection::GetCallStack(threadID thread)
|
||||
{
|
||||
Command cmd(CommandSet::ThreadReference, 6);
|
||||
// always fetch full stack
|
||||
cmd.GetData().Write(thread).Write<int32_t>(0).Write<int32_t>(-1);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
std::vector<StackFrame> ret;
|
||||
CommandData data = cmd.GetData();
|
||||
ReadVector<StackFrame>(
|
||||
data, ret, [](CommandData &data, StackFrame &f) { data.Read(f.id).Read(f.location); });
|
||||
data.Done();
|
||||
|
||||
// simplify error handling, if the stack came back as nonsense then clear it
|
||||
if(!ret.empty() && ret[0].id == 0)
|
||||
ret.clear();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
objectID Connection::GetThis(threadID thread, frameID frame)
|
||||
{
|
||||
Command cmd(CommandSet::StackFrame, 3);
|
||||
cmd.GetData().Write(thread).Write(frame);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
taggedObjectID ret = {};
|
||||
cmd.GetData().Read(ret).Done();
|
||||
return ret.id;
|
||||
}
|
||||
|
||||
void Connection::ReadEvent(CommandData &data, Event &ev)
|
||||
{
|
||||
data.Read((byte &)ev.eventKind).Read(ev.requestID);
|
||||
|
||||
switch(ev.eventKind)
|
||||
{
|
||||
case EventKind::ClassPrepare:
|
||||
{
|
||||
data.Read(ev.ClassPrepare.thread)
|
||||
.Read((byte &)ev.ClassPrepare.refTypeTag)
|
||||
.Read(ev.ClassPrepare.typeID)
|
||||
.Read(ev.ClassPrepare.signature)
|
||||
.Read(ev.ClassPrepare.statusInt);
|
||||
break;
|
||||
}
|
||||
case EventKind::MethodEntry:
|
||||
{
|
||||
data.Read(ev.MethodEntry.thread).Read(ev.MethodEntry.location);
|
||||
break;
|
||||
}
|
||||
default: RDCERR("Unexpected event! Add handling");
|
||||
}
|
||||
}
|
||||
|
||||
Event Connection::WaitForEvent(EventKind kind, const std::vector<EventFilter> &eventFilters,
|
||||
EventFilterFunction filterCallback)
|
||||
{
|
||||
int32_t eventRequestID = 0;
|
||||
|
||||
{
|
||||
Command cmd(CommandSet::EventRequest, 1);
|
||||
CommandData data = cmd.GetData();
|
||||
|
||||
// always suspend all threads
|
||||
data.Write((byte)kind).Write((byte)SuspendPolicy::All);
|
||||
|
||||
WriteVector<EventFilter>(data, eventFilters, [](CommandData &data, const EventFilter &f) {
|
||||
data.Write((byte)f.modKind);
|
||||
if(f.modKind == ModifierKind::ClassOnly)
|
||||
data.Write(f.ClassOnly);
|
||||
else
|
||||
RDCERR("Unsupported event filter %d", f.modKind);
|
||||
});
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
cmd.GetData().Read(eventRequestID).Done();
|
||||
}
|
||||
|
||||
if(eventRequestID == 0)
|
||||
{
|
||||
RDCERR("Failed to set event");
|
||||
error = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
// now resume execution
|
||||
Resume();
|
||||
|
||||
// wait for method entry on the method we care about
|
||||
while(!reader.IsErrored())
|
||||
{
|
||||
SuspendPolicy suspendPolicy;
|
||||
std::vector<Event> events;
|
||||
|
||||
Command msg;
|
||||
msg.Recv(reader);
|
||||
|
||||
if(msg.commandset != CommandSet::Event || msg.command != 100)
|
||||
{
|
||||
RDCERR("Expected event packet, but got %d/%d", msg.commandset, msg.command);
|
||||
error = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
CommandData data = msg.GetData();
|
||||
|
||||
data.Read((byte &)suspendPolicy);
|
||||
ReadVector<Event>(data, events, [this](CommandData &data, Event &ev) { ReadEvent(data, ev); });
|
||||
data.Done();
|
||||
|
||||
// event arrived, we're now suspended
|
||||
if(suspendPolicy != SuspendPolicy::None)
|
||||
suspendRefCount++;
|
||||
|
||||
for(const Event &event : events)
|
||||
{
|
||||
if(event.eventKind == kind && event.requestID == eventRequestID)
|
||||
{
|
||||
// check callback filter
|
||||
if(filterCallback(event))
|
||||
{
|
||||
// stop listening to this event, and leave VM suspended
|
||||
Command cmd(CommandSet::EventRequest, 2);
|
||||
cmd.GetData().Write((byte)EventKind::MethodEntry).Write(eventRequestID);
|
||||
SendReceive(cmd);
|
||||
|
||||
// return the matching event
|
||||
return event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resume to get the next event
|
||||
Resume();
|
||||
}
|
||||
|
||||
// something went wrong, we stopped receiving events before the found we wanted.
|
||||
return {};
|
||||
}
|
||||
|
||||
value Connection::NewString(threadID thread, const std::string &str)
|
||||
{
|
||||
Command cmd(CommandSet::VirtualMachine, 11);
|
||||
cmd.GetData().Write(str);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
stringID ret;
|
||||
cmd.GetData().Read(ret).Done();
|
||||
return {Tag::String, ret};
|
||||
}
|
||||
|
||||
value Connection::GetLocalValue(threadID thread, frameID frame, int32_t slot, Tag tag)
|
||||
{
|
||||
Command cmd(CommandSet::StackFrame, 1);
|
||||
// request one value
|
||||
cmd.GetData().Write(thread).Write(frame).Write<int32_t>(1).Write(slot).Write((byte)tag);
|
||||
|
||||
SendReceive(cmd);
|
||||
|
||||
int32_t numVals = 0;
|
||||
value ret;
|
||||
|
||||
cmd.GetData().Read(numVals).Read(ret).Done();
|
||||
|
||||
if(numVals != 1)
|
||||
RDCWARN("Unexpected number of values found in GetValue: %d", numVals);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Connection::SetLocalValue(threadID thread, frameID frame, int32_t slot, value val)
|
||||
{
|
||||
Command cmd(CommandSet::StackFrame, 2);
|
||||
// set one value
|
||||
cmd.GetData().Write(thread).Write(frame).Write<int32_t>(1).Write(slot).Write(val);
|
||||
|
||||
SendReceive(cmd);
|
||||
}
|
||||
|
||||
value Connection::InvokeInstance(threadID thread, classID clazz, methodID method, objectID object,
|
||||
const std::vector<value> &arguments, InvokeOptions options)
|
||||
{
|
||||
Command cmd;
|
||||
CommandData data = cmd.GetData();
|
||||
|
||||
// static or instance invoke
|
||||
if(object == 0)
|
||||
{
|
||||
cmd.commandset = CommandSet::ClassType;
|
||||
cmd.command = 3;
|
||||
|
||||
data.Write(clazz).Write(thread).Write(method);
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.commandset = CommandSet::ObjectReference;
|
||||
cmd.command = 6;
|
||||
|
||||
data.Write(object).Write(thread).Write(clazz).Write(method);
|
||||
}
|
||||
|
||||
WriteVector<value>(data, arguments, [](CommandData &data, const value &v) { data.Write(v); });
|
||||
|
||||
data.Write((int32_t)options);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
value returnValue;
|
||||
taggedObjectID exception;
|
||||
|
||||
cmd.GetData().Read(returnValue).Read(exception).Done();
|
||||
|
||||
if(exception.id != 0)
|
||||
{
|
||||
RDCERR("Exception encountered while invoking method");
|
||||
return {};
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
std::string Connection::GetString(objectID str)
|
||||
{
|
||||
Command cmd(CommandSet::StringReference, 1);
|
||||
cmd.GetData().Write(str);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return "";
|
||||
|
||||
std::string ret;
|
||||
cmd.GetData().Read(ret).Done();
|
||||
return ret;
|
||||
}
|
||||
|
||||
classID Connection::GetSuper(classID clazz)
|
||||
{
|
||||
Command cmd(CommandSet::ClassType, 1);
|
||||
cmd.GetData().Write(clazz);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
classID ret;
|
||||
cmd.GetData().Read(ret).Done();
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Connection::GetSignature(referenceTypeID typeID)
|
||||
{
|
||||
Command cmd(CommandSet::ReferenceType, 1);
|
||||
cmd.GetData().Write(typeID);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
std::string ret;
|
||||
cmd.GetData().Read(ret).Done();
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Method> Connection::GetMethods(referenceTypeID searchClass)
|
||||
{
|
||||
Command cmd(CommandSet::ReferenceType, 5);
|
||||
cmd.GetData().Write(searchClass);
|
||||
|
||||
if(!SendReceive(cmd))
|
||||
return {};
|
||||
|
||||
std::vector<Method> ret;
|
||||
CommandData data = cmd.GetData();
|
||||
ReadVector<Method>(data, ret, [](CommandData &data, Method &m) {
|
||||
data.Read(m.id).Read(m.name).Read(m.signature).Read(m.modBits);
|
||||
});
|
||||
data.Done();
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,238 @@
|
||||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Baldur Karlsson
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
#include "jdwp.h"
|
||||
|
||||
// The handshake and packet spec is defined in:
|
||||
// https://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp-spec.html
|
||||
// This gives the overall structure of each packet, plus the format of the 'basic' types like
|
||||
// objectID, value, location, etc.
|
||||
|
||||
namespace JDWP
|
||||
{
|
||||
uint32_t Command::idalloc = 42;
|
||||
int32_t objectID::size = 8;
|
||||
|
||||
uint32_t Command::Send(StreamWriter &writer)
|
||||
{
|
||||
id = idalloc++;
|
||||
length = 11 + (uint32_t)data.size();
|
||||
|
||||
uint32_t tmp = EndianSwap(length);
|
||||
writer.Write(tmp);
|
||||
tmp = EndianSwap(id);
|
||||
writer.Write(tmp);
|
||||
|
||||
// no need to endian swap these
|
||||
byte flags = 0;
|
||||
writer.Write(flags);
|
||||
writer.Write(commandset);
|
||||
writer.Write(command);
|
||||
|
||||
// already endian swapped
|
||||
writer.Write(data.data(), data.size());
|
||||
|
||||
writer.Flush();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void Command::Recv(StreamReader &reader)
|
||||
{
|
||||
reader.Read(length);
|
||||
length = EndianSwap(length);
|
||||
reader.Read(id);
|
||||
id = EndianSwap(id);
|
||||
|
||||
byte flags = 0;
|
||||
reader.Read(flags);
|
||||
if(flags == 0x80)
|
||||
{
|
||||
commandset = CommandSet::Unknown;
|
||||
command = 0;
|
||||
uint16_t errInt = 0;
|
||||
reader.Read(errInt);
|
||||
error = (Error)EndianSwap(errInt);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Read(commandset);
|
||||
reader.Read(command);
|
||||
error = Error::None;
|
||||
}
|
||||
|
||||
data.clear();
|
||||
data.resize(length - 11);
|
||||
reader.Read(&data[0], data.size());
|
||||
}
|
||||
|
||||
CommandData Command::GetData()
|
||||
{
|
||||
return CommandData(data);
|
||||
}
|
||||
|
||||
void CommandData::ReadBytes(void *bytes, size_t length)
|
||||
{
|
||||
// read the data if there's enough, otherwise set to 0s.
|
||||
if(offs + length <= data.size())
|
||||
memcpy(bytes, data.data() + offs, length);
|
||||
else
|
||||
memset(bytes, 0, length);
|
||||
|
||||
// always increment the offset so we can see how much we over-read in Done().
|
||||
offs += length;
|
||||
}
|
||||
|
||||
void CommandData::WriteBytes(const void *bytes, size_t length)
|
||||
{
|
||||
const byte *start = (const byte *)bytes;
|
||||
data.insert(data.end(), start, start + length);
|
||||
}
|
||||
|
||||
template <>
|
||||
CommandData &CommandData::Read(std::string &str)
|
||||
{
|
||||
uint32_t length = 0;
|
||||
Read(length);
|
||||
str.resize(length);
|
||||
ReadBytes(&str[0], length);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <>
|
||||
CommandData &CommandData::Write(const std::string &str)
|
||||
{
|
||||
uint32_t length = (uint32_t)str.size();
|
||||
Write(length);
|
||||
WriteBytes(str.c_str(), str.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <>
|
||||
CommandData &CommandData::Read(objectID &id)
|
||||
{
|
||||
ReadBytes(&id, objectID::getSize());
|
||||
id.EndianSwap();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <>
|
||||
CommandData &CommandData::Write(const objectID &id)
|
||||
{
|
||||
objectID tmp = id;
|
||||
tmp.EndianSwap();
|
||||
WriteBytes(&tmp, objectID::getSize());
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <>
|
||||
CommandData &CommandData::Read(taggedObjectID &id)
|
||||
{
|
||||
Read((byte &)id.tag);
|
||||
Read(id.id);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <>
|
||||
CommandData &CommandData::Write(const taggedObjectID &id)
|
||||
{
|
||||
Write((const byte &)id.tag);
|
||||
Write(id.id);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <>
|
||||
CommandData &CommandData::Read(value &val)
|
||||
{
|
||||
Read((byte &)val.tag);
|
||||
switch(val.tag)
|
||||
{
|
||||
case Tag::Unknown: break;
|
||||
case Tag::Array: Read(val.Array); break;
|
||||
case Tag::Byte: Read(val.Byte); break;
|
||||
case Tag::Char: Read(val.Char); break;
|
||||
case Tag::Object: Read(val.Object); break;
|
||||
case Tag::Float: Read(val.Float); break;
|
||||
case Tag::Double: Read(val.Double); break;
|
||||
case Tag::Int: Read(val.Int); break;
|
||||
case Tag::Long: Read(val.Long); break;
|
||||
case Tag::Short: Read(val.Short); break;
|
||||
case Tag::Void: break;
|
||||
case Tag::Boolean: Read(val.Bool); break;
|
||||
case Tag::String: Read(val.String); break;
|
||||
case Tag::Thread: Read(val.Thread); break;
|
||||
case Tag::ThreadGroup: Read(val.ThreadGroup); break;
|
||||
case Tag::ClassLoader: Read(val.ClassLoader); break;
|
||||
case Tag::ClassObject: Read(val.ClassObject); break;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <>
|
||||
CommandData &CommandData::Write(const value &val)
|
||||
{
|
||||
Write((const byte &)val.tag);
|
||||
switch(val.tag)
|
||||
{
|
||||
case Tag::Unknown: break;
|
||||
case Tag::Array: Write(val.Array); break;
|
||||
case Tag::Byte: Write(val.Byte); break;
|
||||
case Tag::Char: Write(val.Char); break;
|
||||
case Tag::Object: Write(val.Object); break;
|
||||
case Tag::Float: Write(val.Float); break;
|
||||
case Tag::Double: Write(val.Double); break;
|
||||
case Tag::Int: Write(val.Int); break;
|
||||
case Tag::Long: Write(val.Long); break;
|
||||
case Tag::Short: Write(val.Short); break;
|
||||
case Tag::Void: break;
|
||||
case Tag::Boolean: Write(val.Bool); break;
|
||||
case Tag::String: Write(val.String); break;
|
||||
case Tag::Thread: Write(val.Thread); break;
|
||||
case Tag::ThreadGroup: Write(val.ThreadGroup); break;
|
||||
case Tag::ClassLoader: Write(val.ClassLoader); break;
|
||||
case Tag::ClassObject: Write(val.ClassObject); break;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <>
|
||||
CommandData &CommandData::Read(Location &loc)
|
||||
{
|
||||
Read((byte &)loc.tag);
|
||||
Read(loc.classID);
|
||||
Read(loc.methodID);
|
||||
Read(loc.index);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <>
|
||||
CommandData &CommandData::Write(const Location &loc)
|
||||
{
|
||||
Write((const byte &)loc.tag);
|
||||
Write(loc.classID);
|
||||
Write(loc.methodID);
|
||||
Write(loc.index);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
@@ -61,7 +61,19 @@ public:
|
||||
return false;
|
||||
|
||||
if(libName)
|
||||
{
|
||||
// register our hooked functions for PLT hooking on android
|
||||
PosixHookFunctions();
|
||||
|
||||
// load the libEGL.so library and when loaded call libHooked which initialises GLES capture
|
||||
PosixHookLibrary("libEGL.so", &libHooked);
|
||||
PosixHookLibrary("libEGL.so.1", NULL);
|
||||
PosixHookLibrary("libGL.so.1", NULL);
|
||||
PosixHookLibrary("libGLESv1_CM.so", NULL);
|
||||
PosixHookLibrary("libGLESv2.so", NULL);
|
||||
PosixHookLibrary("libGLESv2.so.2", NULL);
|
||||
PosixHookLibrary("libGLESv3.so", NULL);
|
||||
}
|
||||
|
||||
bool success = SetupHooks();
|
||||
|
||||
@@ -216,13 +228,6 @@ public:
|
||||
bool PopulateHooks();
|
||||
} eglhooks;
|
||||
|
||||
void EGLHook::libHooked(void *realLib)
|
||||
{
|
||||
libGLdlsymHandle = realLib;
|
||||
eglhooks.CreateHooks(NULL);
|
||||
eglhooks.GetDriver()->SetDriverType(RDCDriver::OpenGLES);
|
||||
}
|
||||
|
||||
// everything below here needs to have C linkage
|
||||
extern "C" {
|
||||
|
||||
@@ -243,6 +248,8 @@ __attribute__((visibility("default"))) EGLContext eglCreateContext(EGLDisplay di
|
||||
EGLContext shareContext,
|
||||
EGLint const *attribList)
|
||||
{
|
||||
PosixHookReapply();
|
||||
|
||||
EGLint defaultAttribList[] = {0};
|
||||
|
||||
const EGLint *attribs = attribList ? attribList : defaultAttribList;
|
||||
@@ -392,7 +399,11 @@ __attribute__((visibility("default"))) __eglMustCastToProperFunctionPointerType
|
||||
if(eglhooks.real.GetProcAddress == NULL)
|
||||
eglhooks.SetupExportedFunctions();
|
||||
|
||||
__eglMustCastToProperFunctionPointerType realFunc = eglhooks.real.GetProcAddress(func);
|
||||
__eglMustCastToProperFunctionPointerType realFunc = NULL;
|
||||
{
|
||||
PosixScopedSuppressHooking suppress;
|
||||
realFunc = eglhooks.real.GetProcAddress(func);
|
||||
}
|
||||
|
||||
if(!strcmp(func, "eglCreateContext"))
|
||||
return (__eglMustCastToProperFunctionPointerType)&eglCreateContext;
|
||||
@@ -416,13 +427,31 @@ __attribute__((visibility("default"))) __eglMustCastToProperFunctionPointerType
|
||||
|
||||
}; // extern "C"
|
||||
|
||||
void EGLHook::libHooked(void *realLib)
|
||||
{
|
||||
libGLdlsymHandle = realLib;
|
||||
eglhooks.CreateHooks(NULL);
|
||||
eglhooks.GetDriver()->SetDriverType(RDCDriver::OpenGLES);
|
||||
|
||||
PosixHookFunction("eglCreateContext", (void *)&eglCreateContext);
|
||||
PosixHookFunction("eglGetDisplay", (void *)&eglGetDisplay);
|
||||
PosixHookFunction("eglDestroyContext", (void *)&eglDestroyContext);
|
||||
PosixHookFunction("eglMakeCurrent", (void *)&eglMakeCurrent);
|
||||
PosixHookFunction("eglSwapBuffers", (void *)&eglSwapBuffers);
|
||||
PosixHookFunction("eglGetProcAddress", (void *)&eglGetProcAddress);
|
||||
}
|
||||
|
||||
bool EGLHook::PopulateHooks()
|
||||
{
|
||||
SetupHooks();
|
||||
|
||||
return SharedPopulateHooks(
|
||||
false, // dlsym can return GL symbols during a GLES context
|
||||
[](const char *funcName) { return (void *)eglGetProcAddress(funcName); });
|
||||
// dlsym can return GL symbols during a GLES context
|
||||
return SharedPopulateHooks(false, [](const char *funcName) {
|
||||
// on some android devices we need to hook dlsym, but eglGetProcAddress might call dlsym so we
|
||||
// need to ensure we return the 'real' pointers
|
||||
PosixScopedSuppressHooking suppress;
|
||||
return (void *)eglGetProcAddress(funcName);
|
||||
});
|
||||
}
|
||||
|
||||
const GLHookSet &GetRealGLFunctionsEGL()
|
||||
|
||||
@@ -1181,6 +1181,7 @@ bool SharedPopulateHooks(bool dlsymFirst, void *(*lookupFunc)(const char *))
|
||||
#define HookInit(function) \
|
||||
if(GL.function == NULL) \
|
||||
{ \
|
||||
PosixScopedSuppressHooking suppress; \
|
||||
if(dlsymFirst) \
|
||||
GL.function = (CONCAT(function, _hooktype))dlsym(libGLdlsymHandle, STRINGIZE(function)); \
|
||||
lookupFunc((const char *)STRINGIZE(function)); \
|
||||
@@ -1204,3 +1205,19 @@ bool SharedPopulateHooks(bool dlsymFirst, void *(*lookupFunc)(const char *))
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PosixHookFunctions()
|
||||
{
|
||||
#undef HookInit
|
||||
#define HookInit(function) \
|
||||
PosixHookFunction(STRINGIZE(function), (void *)&CONCAT(function, _renderdoc_hooked))
|
||||
#undef HookExtension
|
||||
#define HookExtension(funcPtrType, function) \
|
||||
PosixHookFunction(STRINGIZE(function), (void *)&CONCAT(function, _renderdoc_hooked))
|
||||
#undef HookExtensionAlias
|
||||
#define HookExtensionAlias(funcPtrType, function, alias) \
|
||||
PosixHookFunction(STRINGIZE(alias), (void *)&CONCAT(function, _renderdoc_hooked))
|
||||
|
||||
DLLExportHooks();
|
||||
HookCheckGLExtensions();
|
||||
}
|
||||
@@ -32,6 +32,7 @@ void CloneDisplay(Display *dpy);
|
||||
|
||||
void *SharedLookupFuncPtr(const char *func, void *realFunc);
|
||||
bool SharedPopulateHooks(bool dlsymFirst, void *(*lookupFunc)(const char *));
|
||||
void PosixHookFunctions();
|
||||
|
||||
extern GLHookSet GL;
|
||||
extern WrappedOpenGL *m_GLDriver;
|
||||
|
||||
@@ -68,7 +68,7 @@ private:
|
||||
#include <dlfcn.h>
|
||||
|
||||
#define HOOKS_BEGIN() PosixHookInit()
|
||||
#define HOOKS_END()
|
||||
#define HOOKS_END() PosixHookApply()
|
||||
#define HOOKS_REMOVE()
|
||||
#define HOOKS_IDENTIFY(identifier) PosixHookDetect(identifier)
|
||||
|
||||
|
||||
@@ -22,10 +22,55 @@
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
#include "3rdparty/plthook/plthook.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threading.h"
|
||||
#include "os/posix/posix_hook.h"
|
||||
|
||||
#include <android/dlext.h>
|
||||
#include <dlfcn.h>
|
||||
#include <jni.h>
|
||||
#include <link.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
// uncomment the following to print (very verbose) debugging prints for the android PLT hooking
|
||||
//#define HOOK_DEBUG_PRINT(...) RDCLOG(__VA_ARGS__)
|
||||
|
||||
#if !defined(HOOK_DEBUG_PRINT)
|
||||
#define HOOK_DEBUG_PRINT(...) \
|
||||
do \
|
||||
{ \
|
||||
} while(0)
|
||||
#endif
|
||||
|
||||
// from plthook_elf.c
|
||||
#if defined __x86_64__ || defined __x86_64
|
||||
#define R_JUMP_SLOT R_X86_64_JUMP_SLOT
|
||||
#define Elf_Rel ElfW(Rela)
|
||||
#define ELF_R_TYPE ELF64_R_TYPE
|
||||
#define ELF_R_SYM ELF64_R_SYM
|
||||
#elif defined __i386__ || defined __i386
|
||||
#define R_JUMP_SLOT R_386_JMP_SLOT
|
||||
#define Elf_Rel ElfW(Rel)
|
||||
#define ELF_R_TYPE ELF32_R_TYPE
|
||||
#define ELF_R_SYM ELF32_R_SYM
|
||||
#elif defined __arm__ || defined __arm
|
||||
#define R_JUMP_SLOT R_ARM_JUMP_SLOT
|
||||
#define Elf_Rel ElfW(Rel)
|
||||
#define ELF_R_TYPE ELF32_R_TYPE
|
||||
#define ELF_R_SYM ELF32_R_SYM
|
||||
#elif defined __aarch64__ || defined __aarch64 /* ARM64 */
|
||||
#define R_JUMP_SLOT R_AARCH64_JUMP_SLOT
|
||||
#define Elf_Rel ElfW(Rela)
|
||||
#define ELF_R_TYPE ELF64_R_TYPE
|
||||
#define ELF_R_SYM ELF64_R_SYM
|
||||
#else
|
||||
#error unsupported OS
|
||||
#endif
|
||||
|
||||
void PosixHookInit()
|
||||
{
|
||||
@@ -36,7 +81,448 @@ bool PosixHookDetect(const char *identifier)
|
||||
return dlsym(RTLD_DEFAULT, identifier) != NULL;
|
||||
}
|
||||
|
||||
class HookingInfo
|
||||
{
|
||||
public:
|
||||
void AddFunctionHook(const std::string &name, void *hook)
|
||||
{
|
||||
SCOPED_LOCK(lock);
|
||||
funchooks[name] = hook;
|
||||
}
|
||||
|
||||
void AddLibHook(const std::string &name)
|
||||
{
|
||||
SCOPED_LOCK(lock);
|
||||
libhooks.insert(name);
|
||||
}
|
||||
|
||||
void *GetFunctionHook(const std::string &name)
|
||||
{
|
||||
SCOPED_LOCK(lock);
|
||||
return funchooks[name];
|
||||
}
|
||||
|
||||
bool IsLibHook(const std::string &path)
|
||||
{
|
||||
SCOPED_LOCK(lock);
|
||||
for(const std::string &filename : libhooks)
|
||||
{
|
||||
if(path.find(filename) != std::string::npos)
|
||||
{
|
||||
HOOK_DEBUG_PRINT("Intercepting and returning ourselves for %s (matches %s)", path.c_str(),
|
||||
filename.c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsLibHook(void *handle)
|
||||
{
|
||||
SCOPED_LOCK(lock);
|
||||
for(const std::string &lib : libhooks)
|
||||
{
|
||||
void *libHandle = dlopen(lib.c_str(), RTLD_NOLOAD);
|
||||
HOOK_DEBUG_PRINT("%s is %p", lib.c_str(), libHandle);
|
||||
if(libHandle == handle)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AlreadyHooked(void *handle)
|
||||
{
|
||||
SCOPED_LOCK(lock);
|
||||
bool ret = hooked_handle_already.find(handle) != hooked_handle_already.end();
|
||||
hooked_handle_already.insert(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AlreadyHooked(const std::string &soname)
|
||||
{
|
||||
SCOPED_LOCK(lock);
|
||||
if(hooked_soname_already.find(soname) != hooked_soname_already.end())
|
||||
return true;
|
||||
|
||||
// above will be absolute path, allow substring matches
|
||||
for(const std::string &fn : hooked_soname_already)
|
||||
if(soname.find(fn) != std::string::npos)
|
||||
return true;
|
||||
|
||||
hooked_soname_already.insert(soname);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::set<std::string> hooked_soname_already;
|
||||
std::set<void *> hooked_handle_already;
|
||||
|
||||
std::map<std::string, void *> funchooks;
|
||||
std::set<std::string> libhooks;
|
||||
|
||||
Threading::CriticalSection lock;
|
||||
};
|
||||
|
||||
HookingInfo &GetHookInfo()
|
||||
{
|
||||
static HookingInfo hookinfo;
|
||||
return hookinfo;
|
||||
}
|
||||
|
||||
void PosixHookFunction(const char *name, void *hook)
|
||||
{
|
||||
GetHookInfo().AddFunctionHook(name, hook);
|
||||
}
|
||||
|
||||
void PosixHookLibrary(const char *name, dlopenCallback cb)
|
||||
{
|
||||
cb(dlopen(name, RTLD_NOW));
|
||||
GetHookInfo().AddLibHook(name);
|
||||
|
||||
if(cb)
|
||||
cb(dlopen(name, RTLD_NOW));
|
||||
}
|
||||
|
||||
static int dl_iterate_callback(struct dl_phdr_info *info, size_t size, void *data)
|
||||
{
|
||||
if(info->dlpi_name == NULL)
|
||||
{
|
||||
HOOK_DEBUG_PRINT("Skipping NULL entry!");
|
||||
return 0;
|
||||
}
|
||||
std::string soname = info->dlpi_name;
|
||||
|
||||
if(GetHookInfo().AlreadyHooked(soname))
|
||||
return 0;
|
||||
|
||||
HOOK_DEBUG_PRINT("Hooking %s", soname.c_str());
|
||||
|
||||
for(int ph = 0; ph < info->dlpi_phnum; ph++)
|
||||
{
|
||||
if(info->dlpi_phdr[ph].p_type != PT_DYNAMIC)
|
||||
continue;
|
||||
|
||||
ElfW(Dyn) *dynamic = (ElfW(Dyn) *)(info->dlpi_addr + info->dlpi_phdr[ph].p_vaddr);
|
||||
|
||||
ElfW(Sym) *dynsym = NULL;
|
||||
const char *strtab = NULL;
|
||||
size_t strtabcount = 0;
|
||||
Elf_Rel *pltbase = NULL;
|
||||
ElfW(Sword) pltcount = 0;
|
||||
|
||||
while(dynamic->d_tag != DT_NULL)
|
||||
{
|
||||
if(dynamic->d_tag == DT_SYMTAB)
|
||||
dynsym = (ElfW(Sym) *)(info->dlpi_addr + dynamic->d_un.d_ptr);
|
||||
else if(dynamic->d_tag == DT_STRTAB)
|
||||
strtab = (const char *)(info->dlpi_addr + dynamic->d_un.d_ptr);
|
||||
else if(dynamic->d_tag == DT_STRSZ)
|
||||
strtabcount = dynamic->d_un.d_val;
|
||||
else if(dynamic->d_tag == DT_JMPREL)
|
||||
pltbase = (Elf_Rel *)(info->dlpi_addr + dynamic->d_un.d_ptr);
|
||||
else if(dynamic->d_tag == DT_PLTRELSZ)
|
||||
pltcount = dynamic->d_un.d_val / sizeof(Elf_Rel);
|
||||
|
||||
/*
|
||||
if(dynamic->d_tag == DT_NEEDED)
|
||||
HOOK_DEBUG_PRINT("NEEDED [%i, %s]", dynamic->d_un.d_val, strtab + dynamic->d_un.d_val);
|
||||
*/
|
||||
|
||||
dynamic++;
|
||||
}
|
||||
|
||||
if(!dynsym || !strtab || !pltbase || pltcount == 0 || strtabcount == 0)
|
||||
{
|
||||
RDCWARN("Missing required section to hook %s", info->dlpi_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
void **relro_base = NULL;
|
||||
void **relro_end = NULL;
|
||||
bool relro_failed = false;
|
||||
|
||||
FILE *f = FileIO::fopen(info->dlpi_name, "r");
|
||||
|
||||
// read the file on disk to get the .relro section
|
||||
if(f)
|
||||
{
|
||||
ElfW(Ehdr) ehdr;
|
||||
size_t read = FileIO::fread(&ehdr, sizeof(ehdr), 1, f);
|
||||
|
||||
if(read == 1 && ehdr.e_ident[0] == ELFMAG0 && ehdr.e_ident[1] == 'E' &&
|
||||
ehdr.e_ident[2] == 'L' && ehdr.e_ident[3] == 'F')
|
||||
{
|
||||
FileIO::fseek64(f, ehdr.e_phoff, SEEK_SET);
|
||||
for(ElfW(Half) idx = 0; idx < ehdr.e_phnum; idx++)
|
||||
{
|
||||
ElfW(Phdr) phdr;
|
||||
read = FileIO::fread(&phdr, sizeof(phdr), 1, f);
|
||||
if(read != 1)
|
||||
{
|
||||
RDCWARN("Failed reading section");
|
||||
break;
|
||||
}
|
||||
|
||||
if(phdr.p_type == PT_GNU_RELRO)
|
||||
{
|
||||
relro_base = (void **)(info->dlpi_addr + phdr.p_vaddr);
|
||||
relro_end = (void **)(info->dlpi_addr + phdr.p_vaddr + phdr.p_memsz);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RDCWARN("Didn't get valid ELF header");
|
||||
}
|
||||
|
||||
FileIO::fclose(f);
|
||||
}
|
||||
else
|
||||
{
|
||||
RDCWARN("Couldn't open '%s' to look for relro!", info->dlpi_name);
|
||||
relro_failed = true;
|
||||
}
|
||||
|
||||
if(relro_base)
|
||||
HOOK_DEBUG_PRINT("Got relro %p -> %p", relro_base, relro_end);
|
||||
HOOK_DEBUG_PRINT("Got %i PLT entries", pltcount);
|
||||
|
||||
int pagesize = sysconf(_SC_PAGE_SIZE);
|
||||
|
||||
for(ElfW(Sword) i = 0; i < pltcount; i++)
|
||||
{
|
||||
Elf_Rel *plt = pltbase + i;
|
||||
if(ELF_R_TYPE(plt->r_info) != R_JUMP_SLOT)
|
||||
{
|
||||
HOOK_DEBUG_PRINT("[%i]: Mismatched type %i vs %i", i, ELF_R_TYPE(plt->r_info), R_JUMP_SLOT);
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t idx = ELF_R_SYM(plt->r_info);
|
||||
size_t name = dynsym[idx].st_name;
|
||||
if(name + 1 > strtabcount)
|
||||
{
|
||||
HOOK_DEBUG_PRINT("[%i] name out of boundstoo big section header string table index: %zu", i,
|
||||
name);
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *importname = strtab + name;
|
||||
void **import = (void **)(info->dlpi_addr + plt->r_offset);
|
||||
|
||||
HOOK_DEBUG_PRINT("[%i] %s at %p (ptr to %p)", i, importname, import, *import);
|
||||
|
||||
void *repl = GetHookInfo().GetFunctionHook(importname);
|
||||
if(repl)
|
||||
{
|
||||
HOOK_DEBUG_PRINT("replacing %s!", importname);
|
||||
|
||||
uintptr_t pagebase = 0;
|
||||
|
||||
if(relro_failed || (relro_base <= import && import <= relro_end))
|
||||
{
|
||||
if(relro_failed)
|
||||
HOOK_DEBUG_PRINT("Couldn't get relro sections - mapping read/write");
|
||||
else
|
||||
HOOK_DEBUG_PRINT("In relro range - %p <= %p <= %p", relro_base, import, relro_end);
|
||||
pagebase = uintptr_t(import) & ~(pagesize - 1);
|
||||
|
||||
int ret = mprotect((void *)pagebase, pagesize, PROT_READ | PROT_WRITE);
|
||||
if(ret != 0)
|
||||
{
|
||||
RDCERR("Couldn't read/write the page: %d %d", ret, errno);
|
||||
return 0;
|
||||
}
|
||||
|
||||
HOOK_DEBUG_PRINT("Marked page read/write");
|
||||
}
|
||||
else
|
||||
{
|
||||
HOOK_DEBUG_PRINT("Not in relro! - %p vs %p vs %p", relro_base, import, relro_end);
|
||||
}
|
||||
|
||||
*import = repl;
|
||||
|
||||
if(pagebase)
|
||||
{
|
||||
if(relro_failed)
|
||||
{
|
||||
HOOK_DEBUG_PRINT(
|
||||
"Couldn't find relro sections - being conservative and leaving read-write");
|
||||
}
|
||||
else
|
||||
{
|
||||
HOOK_DEBUG_PRINT("Moving back to read-only");
|
||||
mprotect((void *)pagebase, pagesize, PROT_READ);
|
||||
}
|
||||
}
|
||||
|
||||
HOOK_DEBUG_PRINT("[%i*] %s at %p (ptr to %p)", i, importname, import, *import);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) void *hooked_dlopen(const char *filename, int flag);
|
||||
extern "C" __attribute__((visibility("default"))) void *hooked_dlsym(void *handle,
|
||||
const char *symbol);
|
||||
extern "C" __attribute__((visibility("default"))) void *hooked_android_dlopen_ext(
|
||||
const char *__filename, int __flags, const android_dlextinfo *__info);
|
||||
|
||||
// android has a special dlopen that passes the caller address in.
|
||||
typedef void *(*pfn__loader_dlopen)(const char *filename, int flags, const void *caller_addr);
|
||||
|
||||
pfn__loader_dlopen loader_dlopen = NULL;
|
||||
uint64_t suppressTLS = 0;
|
||||
|
||||
void PosixHookApply()
|
||||
{
|
||||
RDCLOG("Applying hooks");
|
||||
|
||||
suppressTLS = Threading::AllocateTLSSlot();
|
||||
|
||||
// blacklist hooking certain system libraries or ourselves
|
||||
GetHookInfo().AlreadyHooked("libVkLayer_GLES_RenderDoc.so");
|
||||
GetHookInfo().AlreadyHooked("libc.so");
|
||||
GetHookInfo().AlreadyHooked("libvndksupport.so");
|
||||
|
||||
GetHookInfo().AddLibHook("libVkLayer_GLES_RenderDoc.so");
|
||||
|
||||
loader_dlopen = (pfn__loader_dlopen)dlsym(RTLD_NEXT, "__loader_dlopen");
|
||||
|
||||
if(loader_dlopen)
|
||||
{
|
||||
PosixHookFunction("dlopen", (void *)&hooked_dlopen);
|
||||
}
|
||||
else
|
||||
{
|
||||
RDCWARN("Couldn't find __loader_dlopen, falling back to slow path for dlopen hooking");
|
||||
PosixHookFunction("dlsym", (void *)&hooked_dlsym);
|
||||
}
|
||||
|
||||
PosixHookFunction("android_dlopen_ext", (void *)&hooked_android_dlopen_ext);
|
||||
|
||||
dl_iterate_phdr(dl_iterate_callback, NULL);
|
||||
}
|
||||
|
||||
void PosixHookReapply()
|
||||
{
|
||||
dl_iterate_phdr(dl_iterate_callback, NULL);
|
||||
}
|
||||
|
||||
void *intercept_dlopen(const char *filename, int flag)
|
||||
{
|
||||
if(GetHookInfo().IsLibHook(filename))
|
||||
return dlopen("libVkLayer_GLES_RenderDoc.so", flag);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void process_dlopen(const char *filename, int flag)
|
||||
{
|
||||
if(!GetHookInfo().AlreadyHooked(filename))
|
||||
{
|
||||
dl_iterate_phdr(dl_iterate_callback, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
HOOK_DEBUG_PRINT("Ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) void *hooked_dlopen(const char *filename, int flag)
|
||||
{
|
||||
// get caller address immediately.
|
||||
const void *caller_addr = __builtin_return_address(0);
|
||||
|
||||
HOOK_DEBUG_PRINT("hooked_dlopen for %s | %d", filename, flag);
|
||||
void *ret = intercept_dlopen(filename, flag);
|
||||
|
||||
// if we intercepted, return immediately
|
||||
if(ret)
|
||||
return ret;
|
||||
|
||||
ret = loader_dlopen(filename, flag, caller_addr);
|
||||
HOOK_DEBUG_PRINT("Got %p", ret);
|
||||
|
||||
if(filename && ret)
|
||||
process_dlopen(filename, flag);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) void *hooked_android_dlopen_ext(
|
||||
const char *__filename, int __flags, const android_dlextinfo *__info)
|
||||
{
|
||||
HOOK_DEBUG_PRINT("hooked_android_dlopen_ext for %s | %d", __filename, __flags);
|
||||
|
||||
void *ret = intercept_dlopen(__filename, __flags);
|
||||
|
||||
// if we intercepted, return immediately
|
||||
if(ret)
|
||||
return ret;
|
||||
|
||||
ret = android_dlopen_ext(__filename, __flags, __info);
|
||||
HOOK_DEBUG_PRINT("Got %p", ret);
|
||||
|
||||
if(__filename && ret)
|
||||
process_dlopen(__filename, __flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PosixScopedSuppressHooking::PosixScopedSuppressHooking()
|
||||
{
|
||||
if(suppressTLS == 0)
|
||||
return;
|
||||
|
||||
uintptr_t old = (uintptr_t)Threading::GetTLSValue(suppressTLS);
|
||||
Threading::SetTLSValue(suppressTLS, (void *)(old + 1));
|
||||
}
|
||||
|
||||
PosixScopedSuppressHooking::~PosixScopedSuppressHooking()
|
||||
{
|
||||
if(suppressTLS == 0)
|
||||
return;
|
||||
|
||||
uintptr_t old = (uintptr_t)Threading::GetTLSValue(suppressTLS);
|
||||
Threading::SetTLSValue(suppressTLS, (void *)(old - 1));
|
||||
}
|
||||
|
||||
bool hooks_suppressed()
|
||||
{
|
||||
return (uintptr_t)Threading::GetTLSValue(suppressTLS) > 0;
|
||||
}
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) void *hooked_dlsym(void *handle, const char *symbol)
|
||||
{
|
||||
if(handle == NULL || symbol == NULL || hooks_suppressed())
|
||||
return dlsym(handle, symbol);
|
||||
|
||||
void *repl = GetHookInfo().GetFunctionHook(symbol);
|
||||
|
||||
if(repl == NULL)
|
||||
return dlsym(handle, symbol);
|
||||
|
||||
if(!GetHookInfo().AlreadyHooked(handle))
|
||||
{
|
||||
dl_iterate_phdr(dl_iterate_callback, NULL);
|
||||
}
|
||||
|
||||
HOOK_DEBUG_PRINT("Got dlsym for %s which we want in %p...", symbol, handle);
|
||||
|
||||
if(GetHookInfo().IsLibHook(handle))
|
||||
{
|
||||
HOOK_DEBUG_PRINT("identified dlsym(%s) we want to interpose! returning %p", symbol, repl);
|
||||
return repl;
|
||||
}
|
||||
|
||||
void *ret = dlsym(handle, symbol);
|
||||
Dl_info info = {};
|
||||
dladdr(ret, &info);
|
||||
HOOK_DEBUG_PRINT("real ret is %p in %s", ret, info.dli_fname);
|
||||
return ret;
|
||||
}
|
||||
@@ -39,3 +39,24 @@ bool PosixHookDetect(const char *identifier)
|
||||
void PosixHookLibrary(const char *name, dlopenCallback cb)
|
||||
{
|
||||
}
|
||||
|
||||
// android only hooking functions, not used on apple
|
||||
PosixScopedSuppressHooking::PosixScopedSuppressHooking()
|
||||
{
|
||||
}
|
||||
|
||||
PosixScopedSuppressHooking::~PosixScopedSuppressHooking()
|
||||
{
|
||||
}
|
||||
|
||||
void PosixHookApply()
|
||||
{
|
||||
}
|
||||
|
||||
void PosixHookReapply()
|
||||
{
|
||||
}
|
||||
|
||||
void PosixHookFunction(char const *, void *)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ static std::map<std::string, dlopenCallback> libraryHooks;
|
||||
|
||||
void PosixHookLibrary(const char *name, dlopenCallback cb)
|
||||
{
|
||||
if(cb == NULL)
|
||||
return;
|
||||
|
||||
SCOPED_LOCK(libLock);
|
||||
libraryHooks[name] = cb;
|
||||
}
|
||||
@@ -116,3 +119,24 @@ void plthook_lib(void *handle)
|
||||
plthook_replace(plthook, "dlopen", (void *)dlopen, NULL);
|
||||
plthook_close(plthook);
|
||||
}
|
||||
|
||||
// android only hooking functions, not used on linux
|
||||
PosixScopedSuppressHooking::PosixScopedSuppressHooking()
|
||||
{
|
||||
}
|
||||
|
||||
PosixScopedSuppressHooking::~PosixScopedSuppressHooking()
|
||||
{
|
||||
}
|
||||
|
||||
void PosixHookApply()
|
||||
{
|
||||
}
|
||||
|
||||
void PosixHookReapply()
|
||||
{
|
||||
}
|
||||
|
||||
void PosixHookFunction(char const *, void *)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -32,4 +32,18 @@ void PosixHookInit();
|
||||
// to the callback and librenderdoc.so will be returned to user code
|
||||
void PosixHookLibrary(const char *name, dlopenCallback cb);
|
||||
|
||||
void PosixHookFunction(const char *name, void *hook);
|
||||
|
||||
void PosixHookApply();
|
||||
|
||||
// this is needed on android, when we are PLT hooking to ensure hooks are applied as soon as
|
||||
// possible.
|
||||
void PosixHookReapply();
|
||||
|
||||
struct PosixScopedSuppressHooking
|
||||
{
|
||||
PosixScopedSuppressHooking();
|
||||
~PosixScopedSuppressHooking();
|
||||
};
|
||||
|
||||
bool PosixHookDetect(const char *identifier);
|
||||
@@ -134,6 +134,7 @@
|
||||
<ClInclude Include="3rdparty\zstd\zstd.h" />
|
||||
<ClInclude Include="android\android.h" />
|
||||
<ClInclude Include="android\android_utils.h" />
|
||||
<ClInclude Include="android\jdwp.h" />
|
||||
<ClInclude Include="api\app\renderdoc_app.h" />
|
||||
<ClInclude Include="api\replay\basic_types.h" />
|
||||
<ClInclude Include="api\replay\capture_options.h" />
|
||||
@@ -310,6 +311,9 @@
|
||||
<ClCompile Include="android\android_patch.cpp" />
|
||||
<ClCompile Include="android\android_tools.cpp" />
|
||||
<ClCompile Include="android\android_utils.cpp" />
|
||||
<ClCompile Include="android\jdwp.cpp" />
|
||||
<ClCompile Include="android\jdwp_connection.cpp" />
|
||||
<ClCompile Include="android\jdwp_util.cpp" />
|
||||
<ClCompile Include="common\common.cpp" />
|
||||
<ClCompile Include="common\dds_readwrite.cpp" />
|
||||
<ClCompile Include="core\core.cpp" />
|
||||
|
||||
@@ -381,6 +381,9 @@
|
||||
<ClInclude Include="android\android_utils.h">
|
||||
<Filter>Android</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="android\jdwp.h">
|
||||
<Filter>Android</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="maths\camera.cpp">
|
||||
@@ -665,6 +668,15 @@
|
||||
<ClCompile Include="android\android_patch.cpp">
|
||||
<Filter>Android</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="android\jdwp.cpp">
|
||||
<Filter>Android</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="android\jdwp_connection.cpp">
|
||||
<Filter>Android</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="android\jdwp_util.cpp">
|
||||
<Filter>Android</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="os\win32\comexport.def">
|
||||
|
||||
Reference in New Issue
Block a user