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:
baldurk
2018-01-26 21:19:13 +00:00
parent ec71b05173
commit 047bff7261
17 changed files with 2498 additions and 19 deletions
+4
View File
@@ -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
+111 -6
View File
@@ -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,
+1
View File
@@ -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);
};
+406
View File
@@ -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
+531
View File
@@ -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);
};
};
+586
View File
@@ -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;
}
};
+238
View File
@@ -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;
}
};
+40 -11
View File
@@ -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;
+1 -1
View File
@@ -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)
+487 -1
View File
@@ -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;
}
+21
View File
@@ -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 *)
{
}
+24
View File
@@ -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 *)
{
}
+14
View File
@@ -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);
+4
View File
@@ -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" />
+12
View File
@@ -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">