summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Gavrilov2012-08-17 22:40:53 +0400
committerAlexander Gavrilov2012-08-17 22:40:53 +0400
commit236ffd578b66805fa45d65df79e099d00156bfff (patch)
treecdbf17b2c1fb3c3cf5135531861ecd0c90061abe
parentbcc41c081a43a68042c4757a6569cb690098d9b8 (diff)
downloaddfhack-236ffd578b66805fa45d65df79e099d00156bfff.tar.gz
dfhack-236ffd578b66805fa45d65df79e099d00156bfff.tar.bz2
dfhack-236ffd578b66805fa45d65df79e099d00156bfff.tar.xz
Add experimental support for interposing vmethods of known classes.
The hairiest bit is the abuse of compiler-specific pointer-to-member internals in order to provide more or less transparent API.
-rw-r--r--CMakeLists.txt3
-rw-r--r--library/CMakeLists.txt2
-rw-r--r--library/DataDefs.cpp8
-rw-r--r--library/VTableInterpose.cpp238
-rw-r--r--library/include/DataDefs.h11
-rw-r--r--library/include/DataFuncs.h15
-rw-r--r--library/include/VTableInterpose.h158
-rw-r--r--plugins/devel/CMakeLists.txt1
-rw-r--r--plugins/devel/vshook.cpp61
9 files changed, 491 insertions, 6 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f77a6c3b..dfb13cd5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -111,6 +111,9 @@ IF(UNIX)
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -Wall -Wno-unused-variable")
SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -m32 -march=i686 -mtune=generic -std=c++0x")
SET(CMAKE_C_FLAGS "-fvisibility=hidden -m32 -march=i686 -mtune=generic")
+ELSEIF(MSVC)
+ # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm")
ENDIF()
# use shared libraries for protobuf
diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt
index bbf22611..cd3d52c8 100644
--- a/library/CMakeLists.txt
+++ b/library/CMakeLists.txt
@@ -27,6 +27,7 @@ include/Core.h
include/ColorText.h
include/DataDefs.h
include/DataIdentity.h
+include/VTableInterpose.h
include/LuaWrapper.h
include/LuaTools.h
include/Error.h
@@ -53,6 +54,7 @@ SET(MAIN_SOURCES
Core.cpp
ColorText.cpp
DataDefs.cpp
+VTableInterpose.cpp
LuaWrapper.cpp
LuaTypes.cpp
LuaTools.cpp
diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp
index 7f0bacc9..d6604cdb 100644
--- a/library/DataDefs.cpp
+++ b/library/DataDefs.cpp
@@ -35,6 +35,7 @@ distribution.
// must be last due to MS stupidity
#include "DataDefs.h"
#include "DataIdentity.h"
+#include "VTableInterpose.h"
#include "MiscUtils.h"
@@ -214,6 +215,13 @@ virtual_identity::virtual_identity(size_t size, TAllocateFn alloc,
{
}
+virtual_identity::~virtual_identity()
+{
+ // Remove interpose entries, so that they don't try accessing this object later
+ for (int i = interpose_list.size()-1; i >= 0; i--)
+ interpose_list[i]->remove();
+}
+
/* Vtable name to identity lookup. */
static std::map<std::string, virtual_identity*> name_lookup;
diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp
new file mode 100644
index 00000000..44707062
--- /dev/null
+++ b/library/VTableInterpose.cpp
@@ -0,0 +1,238 @@
+/*
+https://github.com/peterix/dfhack
+Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+#include "Internal.h"
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "MemAccess.h"
+#include "Core.h"
+#include "VersionInfo.h"
+#include "VTableInterpose.h"
+
+#include "MiscUtils.h"
+
+using namespace DFHack;
+
+/*
+ * Code for accessing method pointers directly. Very compiler-specific.
+ */
+
+#if defined(_MSC_VER)
+
+struct MSVC_MPTR {
+ void *method;
+ intptr_t this_shift;
+};
+
+bool DFHack::is_vmethod_pointer_(void *pptr)
+{
+ auto pobj = (MSVC_MPTR*)pptr;
+ if (!pobj->method) return false;
+
+ // MSVC implements pointers to vmethods via thunks.
+ // This expects that they all follow a very specific pattern.
+ auto pval = (unsigned*)pobj->method;
+ switch (pval[0]) {
+ case 0x60FF018BU: // mov eax, [ecx]; jmp [eax+0x??]
+ case 0xA0FF018BU: // mov eax, [ecx]; jmp [eax+0x????????]
+ return true;
+ default:
+ return false;
+ }
+}
+
+int DFHack::vmethod_pointer_to_idx_(void *pptr)
+{
+ auto pobj = (MSVC_MPTR*)pptr;
+ if (!pobj->method || pobj->this_shift != 0) return -1;
+
+ auto pval = (unsigned*)pobj->method;
+ switch (pval[0]) {
+ case 0x60FF018BU: // mov eax, [ecx]; jmp [eax+0x??]
+ return ((int8_t)pval[1])/sizeof(void*);
+ case 0xA0FF018BU: // mov eax, [ecx]; jmp [eax+0x????????]
+ return ((int32_t)pval[1])/sizeof(void*);
+ default:
+ return -1;
+ }
+}
+
+void* DFHack::method_pointer_to_addr_(void *pptr)
+{
+ if (is_vmethod_pointer_(pptr)) return NULL;
+ auto pobj = (MSVC_MPTR*)pptr;
+ return pobj->method;
+}
+
+void DFHack::addr_to_method_pointer_(void *pptr, void *addr)
+{
+ auto pobj = (MSVC_MPTR*)pptr;
+ pobj->method = addr;
+ pobj->this_shift = 0;
+}
+
+#elif defined(__GXX_ABI_VERSION)
+
+struct GCC_MPTR {
+ intptr_t method;
+ intptr_t this_shift;
+};
+
+bool DFHack::is_vmethod_pointer_(void *pptr)
+{
+ auto pobj = (GCC_MPTR*)pptr;
+ return (pobj->method & 1) != 0;
+}
+
+int DFHack::vmethod_pointer_to_idx_(void *pptr)
+{
+ auto pobj = (GCC_MPTR*)pptr;
+ if ((pobj->method & 1) == 0 || pobj->this_shift != 0)
+ return -1;
+ return (pobj->method-1)/sizeof(void*);
+}
+
+void* DFHack::method_pointer_to_addr_(void *pptr)
+{
+ auto pobj = (GCC_MPTR*)pptr;
+ if ((pobj->method & 1) != 0 || pobj->this_shift != 0)
+ return NULL;
+ return (void*)pobj->method;
+}
+
+void DFHack::addr_to_method_pointer_(void *pptr, void *addr)
+{
+ auto pobj = (GCC_MPTR*)pptr;
+ pobj->method = (intptr_t)addr;
+ pobj->this_shift = 0;
+}
+
+#else
+#error Unknown compiler type
+#endif
+
+void *virtual_identity::get_vmethod_ptr(int idx)
+{
+ assert(idx >= 0);
+ void **vtable = (void**)vtable_ptr;
+ if (!vtable) return NULL;
+ return vtable[idx];
+}
+
+bool virtual_identity::set_vmethod_ptr(int idx, void *ptr)
+{
+ assert(idx >= 0);
+ void **vtable = (void**)vtable_ptr;
+ if (!vtable) return NULL;
+ return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*));
+}
+
+void VMethodInterposeLinkBase::set_chain(void *chain)
+{
+ saved_chain = chain;
+ addr_to_method_pointer_(chain_mptr, chain);
+}
+
+VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr)
+ : host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), chain_mptr(chain_mptr),
+ saved_chain(NULL), next(NULL), prev(NULL)
+{
+}
+
+VMethodInterposeLinkBase::~VMethodInterposeLinkBase()
+{
+ if (is_applied())
+ remove();
+}
+
+bool VMethodInterposeLinkBase::apply()
+{
+ if (is_applied())
+ return true;
+ if (!host->vtable_ptr)
+ return false;
+
+ // Retrieve the current vtable entry
+ void *old_ptr = host->get_vmethod_ptr(vmethod_idx);
+ assert(old_ptr != NULL);
+
+ // Check if there are other interpose entries for the same slot
+ VMethodInterposeLinkBase *old_link = NULL;
+
+ for (int i = host->interpose_list.size()-1; i >= 0; i--)
+ {
+ if (host->interpose_list[i]->vmethod_idx != vmethod_idx)
+ continue;
+
+ old_link = host->interpose_list[i];
+ assert(old_link->next == NULL && old_ptr == old_link->interpose_method);
+ break;
+ }
+
+ // Apply the new method ptr
+ if (!host->set_vmethod_ptr(vmethod_idx, interpose_method))
+ return false;
+
+ set_chain(old_ptr);
+
+ // Link into the chain if any
+ if (old_link)
+ {
+ old_link->next = this;
+ prev = old_link;
+ }
+
+ return true;
+}
+
+void VMethodInterposeLinkBase::remove()
+{
+ if (!is_applied())
+ return;
+
+ // Remove from the list in the identity
+ for (int i = host->interpose_list.size()-1; i >= 0; i--)
+ if (host->interpose_list[i] == this)
+ vector_erase_at(host->interpose_list, i);
+
+ // Remove from the chain
+ if (prev)
+ prev->next = next;
+
+ if (next)
+ {
+ next->set_chain(saved_chain);
+ next->prev = prev;
+ }
+ else
+ {
+ host->set_vmethod_ptr(vmethod_idx, saved_chain);
+ }
+
+ prev = next = NULL;
+ set_chain(NULL);
+}
diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h
index 1d485156..7903530d 100644
--- a/library/include/DataDefs.h
+++ b/library/include/DataDefs.h
@@ -292,6 +292,8 @@ namespace DFHack
typedef virtual_class *virtual_ptr;
#endif
+ class DFHACK_EXPORT VMethodInterposeLinkBase;
+
class DFHACK_EXPORT virtual_identity : public struct_identity {
static std::map<void*, virtual_identity*> known;
@@ -299,6 +301,9 @@ namespace DFHack
void *vtable_ptr;
+ friend class VMethodInterposeLinkBase;
+ std::vector<VMethodInterposeLinkBase*> interpose_list;
+
protected:
virtual void doInit(Core *core);
@@ -306,10 +311,14 @@ namespace DFHack
bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); }
+ void *get_vmethod_ptr(int index);
+ bool set_vmethod_ptr(int index, void *ptr);
+
public:
virtual_identity(size_t size, TAllocateFn alloc,
const char *dfhack_name, const char *original_name,
virtual_identity *parent, const struct_field_info *fields);
+ ~virtual_identity();
virtual identity_type type() { return IDTYPE_CLASS; }
@@ -337,6 +346,8 @@ namespace DFHack
: (this == get(instance_ptr));
}
+ template<class P> static P get_vmethod_ptr(P selector);
+
public:
bool can_instantiate() { return can_allocate(); }
virtual_ptr instantiate() { return can_instantiate() ? (virtual_ptr)do_allocate() : NULL; }
diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h
index 637a532f..52039566 100644
--- a/library/include/DataFuncs.h
+++ b/library/include/DataFuncs.h
@@ -75,28 +75,31 @@ namespace df {
cur_lua_ostream_argument name(state);
#define INSTANTIATE_RETURN_TYPE(FArgs) \
- template<FW_TARGSC class RT> struct return_type<RT (*) FArgs> { typedef RT type; }; \
- template<FW_TARGSC class RT, class CT> struct return_type<RT (CT::*) FArgs> { typedef RT type; };
+ template<FW_TARGSC class RT> struct return_type<RT (*) FArgs> { \
+ typedef RT type; \
+ static const bool is_method = false; \
+ }; \
+ template<FW_TARGSC class RT, class CT> struct return_type<RT (CT::*) FArgs> { \
+ typedef RT type; \
+ typedef CT class_type; \
+ static const bool is_method = true; \
+ };
#define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \
template<FW_TARGS> struct function_wrapper<void (*) FArgs, true> { \
- static const bool is_method = false; \
static const int num_args = Count; \
static void execute(lua_State *state, int base, void (*cb) FArgs) { Loads; INVOKE_VOID(cb Args); } \
}; \
template<FW_TARGSC class RT> struct function_wrapper<RT (*) FArgs, false> { \
- static const bool is_method = false; \
static const int num_args = Count; \
static void execute(lua_State *state, int base, RT (*cb) FArgs) { Loads; INVOKE_RV(cb Args); } \
}; \
template<FW_TARGSC class CT> struct function_wrapper<void (CT::*) FArgs, true> { \
- static const bool is_method = true; \
static const int num_args = Count+1; \
static void execute(lua_State *state, int base, void (CT::*cb) FArgs) { \
LOAD_CLASS() Loads; INVOKE_VOID((self->*cb) Args); } \
}; \
template<FW_TARGSC class RT, class CT> struct function_wrapper<RT (CT::*) FArgs, false> { \
- static const bool is_method = true; \
static const int num_args = Count+1; \
static void execute(lua_State *state, int base, RT (CT::*cb) FArgs) { \
LOAD_CLASS(); Loads; INVOKE_RV((self->*cb) Args); } \
diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h
new file mode 100644
index 00000000..59a5c283
--- /dev/null
+++ b/library/include/VTableInterpose.h
@@ -0,0 +1,158 @@
+/*
+https://github.com/peterix/dfhack
+Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+#pragma once
+
+#include "DataFuncs.h"
+
+namespace DFHack
+{
+ template<bool> struct StaticAssert;
+ template<> struct StaticAssert<true> {};
+
+#define STATIC_ASSERT(condition) { StaticAssert<(condition)>(); }
+
+ /* Wrapping around compiler-specific representation of pointers to methods. */
+
+#if defined(_MSC_VER)
+#define METHOD_POINTER_SIZE (sizeof(void*)*2)
+#elif defined(__GXX_ABI_VERSION)
+#define METHOD_POINTER_SIZE (sizeof(void*)*2)
+#else
+#error Unknown compiler type
+#endif
+
+#define ASSERT_METHOD_POINTER(type) \
+ STATIC_ASSERT(df::return_type<type>::is_method && sizeof(type)==METHOD_POINTER_SIZE);
+
+ DFHACK_EXPORT bool is_vmethod_pointer_(void*);
+ DFHACK_EXPORT int vmethod_pointer_to_idx_(void*);
+ DFHACK_EXPORT void* method_pointer_to_addr_(void*);
+ DFHACK_EXPORT void addr_to_method_pointer_(void*,void*);
+
+ template<class T> bool is_vmethod_pointer(T ptr) {
+ ASSERT_METHOD_POINTER(T);
+ return is_vmethod_pointer_(&ptr);
+ }
+ template<class T> int vmethod_pointer_to_idx(T ptr) {
+ ASSERT_METHOD_POINTER(T);
+ return vmethod_pointer_to_idx_(&ptr);
+ }
+ template<class T> void *method_pointer_to_addr(T ptr) {
+ ASSERT_METHOD_POINTER(T);
+ return method_pointer_to_addr_(&ptr);
+ }
+ template<class T> T addr_to_method_pointer(void *addr) {
+ ASSERT_METHOD_POINTER(T);
+ T rv;
+ addr_to_method_pointer_(&rv, addr);
+ return rv;
+ }
+
+ /* Access to vmethod pointers from the vtable. */
+
+ template<class P>
+ P virtual_identity::get_vmethod_ptr(P selector)
+ {
+ typedef typename df::return_type<P>::class_type host_class;
+ virtual_identity &identity = host_class::_identity;
+ int idx = vmethod_pointer_to_idx(selector);
+ return addr_to_method_pointer<P>(identity.get_vmethod_ptr(idx));
+ }
+
+ /* VMethod interpose API
+
+ Usage:
+
+ struct my_hack : public someclass {
+ typedef someclass interpose_base;
+
+ DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) {
+ ...
+ INTERPOSE_NEXT(foo)(arg) // call the original
+ ...
+ }
+ };
+
+ IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
+
+ void init() {
+ my_hack::interpose_foo.apply()
+ }
+ */
+
+#define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \
+ typedef rtype (interpose_base::*interpose_ptr_##name)args; \
+ static DFHack::VMethodInterposeLink<interpose_base,interpose_ptr_##name> interpose_##name; \
+ rtype interpose_fn_##name args
+
+#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) \
+ DFHack::VMethodInterposeLink<class::interpose_base,class::interpose_ptr_##name> \
+ class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name);
+
+#define INTERPOSE_NEXT(name) (this->*interpose_##name.chain)
+
+ class DFHACK_EXPORT VMethodInterposeLinkBase {
+ /*
+ These link objects try to:
+ 1) Allow multiple hooks into the same vmethod
+ 2) Auto-remove hooks when a plugin is unloaded.
+ */
+
+ virtual_identity *host; // Class with the vtable
+ int vmethod_idx;
+ void *interpose_method; // Pointer to the code of the interposing method
+ void *chain_mptr; // Pointer to the chain field below
+
+ void *saved_chain; // Previous pointer to the code
+ VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method
+
+ void set_chain(void *chain);
+ public:
+ VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr);
+ ~VMethodInterposeLinkBase();
+
+ bool is_applied() { return saved_chain != NULL; }
+ bool apply();
+ void remove();
+ };
+
+ template<class Base, class Ptr>
+ class VMethodInterposeLink : public VMethodInterposeLinkBase {
+ public:
+ Ptr chain;
+
+ operator Ptr () { return chain; }
+
+ template<class Ptr2>
+ VMethodInterposeLink(Ptr target, Ptr2 src)
+ : VMethodInterposeLinkBase(
+ &Base::_identity,
+ vmethod_pointer_to_idx(target),
+ method_pointer_to_addr(src),
+ &chain
+ )
+ { src = target; /* check compatibility */ }
+ };
+}
diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt
index 5d1d585a..8274accf 100644
--- a/plugins/devel/CMakeLists.txt
+++ b/plugins/devel/CMakeLists.txt
@@ -17,3 +17,4 @@ DFHACK_PLUGIN(stockcheck stockcheck.cpp)
DFHACK_PLUGIN(stripcaged stripcaged.cpp)
DFHACK_PLUGIN(rprobe rprobe.cpp)
DFHACK_PLUGIN(nestboxes nestboxes.cpp)
+DFHACK_PLUGIN(vshook vshook.cpp)
diff --git a/plugins/devel/vshook.cpp b/plugins/devel/vshook.cpp
new file mode 100644
index 00000000..28f98362
--- /dev/null
+++ b/plugins/devel/vshook.cpp
@@ -0,0 +1,61 @@
+#include "Core.h"
+#include <Console.h>
+#include <Export.h>
+#include <PluginManager.h>
+#include <modules/Gui.h>
+#include <vector>
+#include <cstdio>
+#include <stack>
+#include <string>
+#include <cmath>
+
+#include <VTableInterpose.h>
+#include "df/graphic.h"
+#include "df/viewscreen_titlest.h"
+
+using std::vector;
+using std::string;
+using std::stack;
+using namespace DFHack;
+
+using df::global::gps;
+
+DFHACK_PLUGIN("vshook");
+
+struct title_hook : df::viewscreen_titlest {
+ typedef df::viewscreen_titlest interpose_base;
+
+ DEFINE_VMETHOD_INTERPOSE(void, render, ())
+ {
+ INTERPOSE_NEXT(render)();
+
+ if (gps) {
+ uint8_t *buf = gps->screen;
+ int32_t *stp = gps->screentexpos;
+
+ for (const char *p = "DFHack " DFHACK_VERSION; *p; p++) {
+ *buf++ = *p; *buf++ = 7; *buf++ = 0; *buf++ = 1;
+ *stp++ = 0;
+ }
+ }
+ }
+};
+
+IMPLEMENT_VMETHOD_INTERPOSE(title_hook, render);
+
+DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
+{
+ if (gps)
+ {
+ if (!title_hook::interpose_render.apply())
+ out.printerr("Could not interpose viewscreen_titlest::render\n");
+ }
+
+ return CR_OK;
+}
+
+DFhackCExport command_result plugin_shutdown ( color_ostream &out )
+{
+ title_hook::interpose_render.remove();
+ return CR_OK;
+}