Add a MemoryPatcher class as an optimization of scattered patchMemory.

This class can cache the set of memory regions during its lifetime,
and make them writable only once. This avoids e.g. re-reading
/proc/*/maps once for every modified vtable in interpose code.
develop
Alexander Gavrilov 2012-10-27 21:58:40 +04:00
parent e353f5f03e
commit 92a3277777
4 changed files with 83 additions and 22 deletions

@ -1615,15 +1615,27 @@ void ClassNameCheck::getKnownClassNames(std::vector<std::string> &names)
names.push_back(*it); names.push_back(*it);
} }
bool Process::patchMemory(void *target, const void* src, size_t count) MemoryPatcher::MemoryPatcher(Process *p) : p(p)
{
if (!p)
p = Core::getInstance().p;
}
MemoryPatcher::~MemoryPatcher()
{
close();
}
bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write)
{ {
uint8_t *sptr = (uint8_t*)target; uint8_t *sptr = (uint8_t*)target;
uint8_t *eptr = sptr + count; uint8_t *eptr = sptr + count;
// Find the valid memory ranges // Find the valid memory ranges
std::vector<t_memrange> ranges; if (ranges.empty())
getMemRanges(ranges); p->getMemRanges(ranges);
// Find the ranges that this area spans
unsigned start = 0; unsigned start = 0;
while (start < ranges.size() && ranges[start].end <= sptr) while (start < ranges.size() && ranges[start].end <= sptr)
start++; start++;
@ -1645,24 +1657,49 @@ bool Process::patchMemory(void *target, const void* src, size_t count)
if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared) if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared)
return false; return false;
// Apply writable permissions & update if (!write)
bool ok = true; return true;
for (unsigned i = start; i < end && ok; i++) // Apply writable permissions & update
for (unsigned i = start; i < end; i++)
{ {
t_memrange perms = ranges[i]; auto &perms = ranges[i];
if (perms.write && perms.read)
continue;
save.push_back(perms);
perms.write = perms.read = true; perms.write = perms.read = true;
if (!setPermisions(perms, perms)) if (!p->setPermisions(perms, perms))
ok = false; return false;
} }
if (ok) return true;
memmove(target, src, count); }
bool MemoryPatcher::write(void *target, const void *src, size_t size)
{
if (!makeWritable(target, size))
return false;
memmove(target, src, size);
return true;
}
for (unsigned i = start; i < end && ok; i++) void MemoryPatcher::close()
setPermisions(ranges[i], ranges[i]); {
for (size_t i = 0; i < save.size(); i++)
p->setPermisions(save[i], save[i]);
save.clear();
ranges.clear();
};
bool Process::patchMemory(void *target, const void* src, size_t count)
{
MemoryPatcher patcher(this);
return ok; return patcher.write(target, src, count);
} }
/******************************************************************************* /*******************************************************************************

@ -166,12 +166,12 @@ void *virtual_identity::get_vmethod_ptr(int idx)
return vtable[idx]; return vtable[idx];
} }
bool virtual_identity::set_vmethod_ptr(int idx, void *ptr) bool virtual_identity::set_vmethod_ptr(MemoryPatcher &patcher, int idx, void *ptr)
{ {
assert(idx >= 0); assert(idx >= 0);
void **vtable = (void**)vtable_ptr; void **vtable = (void**)vtable_ptr;
if (!vtable) return NULL; if (!vtable) return NULL;
return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*)); return patcher.write(&vtable[idx], &ptr, sizeof(void*));
} }
/* /*
@ -344,7 +344,9 @@ void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
auto last = this; auto last = this;
while (last->prev) last = last->prev; while (last->prev) last = last->prev;
from->set_vmethod_ptr(vmethod_idx, last->saved_chain); MemoryPatcher patcher;
from->set_vmethod_ptr(patcher, vmethod_idx, last->saved_chain);
// Unlink the chains // Unlink the chains
child_hosts.erase(from); child_hosts.erase(from);
@ -379,13 +381,15 @@ bool VMethodInterposeLinkBase::apply(bool enable)
assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr)); assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr));
// Apply the new method ptr // Apply the new method ptr
MemoryPatcher patcher;
set_chain(old_ptr); set_chain(old_ptr);
if (next_link) if (next_link)
{ {
next_link->set_chain(interpose_method); next_link->set_chain(interpose_method);
} }
else if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) else if (!host->set_vmethod_ptr(patcher, vmethod_idx, interpose_method))
{ {
set_chain(NULL); set_chain(NULL);
return false; return false;
@ -459,7 +463,7 @@ bool VMethodInterposeLinkBase::apply(bool enable)
{ {
auto nhost = *it; auto nhost = *it;
assert(nhost->interpose_list[vmethod_idx] == old_link); assert(nhost->interpose_list[vmethod_idx] == old_link);
nhost->set_vmethod_ptr(vmethod_idx, interpose_method); nhost->set_vmethod_ptr(patcher, vmethod_idx, interpose_method);
nhost->interpose_list[vmethod_idx] = this; nhost->interpose_list[vmethod_idx] = this;
} }
@ -496,9 +500,11 @@ void VMethodInterposeLinkBase::remove()
} }
else else
{ {
MemoryPatcher patcher;
// Remove from the list in the identity and vtable // Remove from the list in the identity and vtable
host->interpose_list[vmethod_idx] = prev; host->interpose_list[vmethod_idx] = prev;
host->set_vmethod_ptr(vmethod_idx, saved_chain); host->set_vmethod_ptr(patcher, vmethod_idx, saved_chain);
for (auto it = child_next.begin(); it != child_next.end(); ++it) for (auto it = child_next.begin(); it != child_next.end(); ++it)
{ {
@ -515,7 +521,7 @@ void VMethodInterposeLinkBase::remove()
auto nhost = *it; auto nhost = *it;
assert(nhost->interpose_list[vmethod_idx] == this); assert(nhost->interpose_list[vmethod_idx] == this);
nhost->interpose_list[vmethod_idx] = prev; nhost->interpose_list[vmethod_idx] = prev;
nhost->set_vmethod_ptr(vmethod_idx, saved_chain); nhost->set_vmethod_ptr(patcher, vmethod_idx, saved_chain);
if (prev) if (prev)
prev->child_hosts.insert(nhost); prev->child_hosts.insert(nhost);
} }

@ -294,6 +294,7 @@ namespace DFHack
#endif #endif
class DFHACK_EXPORT VMethodInterposeLinkBase; class DFHACK_EXPORT VMethodInterposeLinkBase;
class MemoryPatcher;
class DFHACK_EXPORT virtual_identity : public struct_identity { class DFHACK_EXPORT virtual_identity : public struct_identity {
static std::map<void*, virtual_identity*> known; static std::map<void*, virtual_identity*> known;
@ -313,7 +314,7 @@ namespace DFHack
bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); } bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); }
void *get_vmethod_ptr(int index); void *get_vmethod_ptr(int index);
bool set_vmethod_ptr(int index, void *ptr); bool set_vmethod_ptr(MemoryPatcher &patcher, int index, void *ptr);
public: public:
virtual_identity(size_t size, TAllocateFn alloc, virtual_identity(size_t size, TAllocateFn alloc,

@ -315,5 +315,22 @@ namespace DFHack
// Get list of names given to ClassNameCheck constructors. // Get list of names given to ClassNameCheck constructors.
static void getKnownClassNames(std::vector<std::string> &names); static void getKnownClassNames(std::vector<std::string> &names);
}; };
class DFHACK_EXPORT MemoryPatcher
{
Process *p;
std::vector<t_memrange> ranges, save;
public:
MemoryPatcher(Process *p = NULL);
~MemoryPatcher();
bool verifyAccess(void *target, size_t size, bool write = false);
bool makeWritable(void *target, size_t size) {
return verifyAccess(target, size, true);
}
bool write(void *target, const void *src, size_t size);
void close();
};
} }
#endif #endif