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);
}
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 *eptr = sptr + count;
// Find the valid memory ranges
std::vector<t_memrange> ranges;
getMemRanges(ranges);
if (ranges.empty())
p->getMemRanges(ranges);
// Find the ranges that this area spans
unsigned start = 0;
while (start < ranges.size() && ranges[start].end <= sptr)
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)
return false;
// Apply writable permissions & update
bool ok = true;
if (!write)
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;
if (!setPermisions(perms, perms))
ok = false;
if (!p->setPermisions(perms, perms))
return false;
}
if (ok)
memmove(target, src, count);
return true;
}
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++)
setPermisions(ranges[i], ranges[i]);
void MemoryPatcher::close()
{
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];
}
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);
void **vtable = (void**)vtable_ptr;
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;
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
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));
// Apply the new method ptr
MemoryPatcher patcher;
set_chain(old_ptr);
if (next_link)
{
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);
return false;
@ -459,7 +463,7 @@ bool VMethodInterposeLinkBase::apply(bool enable)
{
auto nhost = *it;
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;
}
@ -496,9 +500,11 @@ void VMethodInterposeLinkBase::remove()
}
else
{
MemoryPatcher patcher;
// Remove from the list in the identity and vtable
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)
{
@ -515,7 +521,7 @@ void VMethodInterposeLinkBase::remove()
auto nhost = *it;
assert(nhost->interpose_list[vmethod_idx] == this);
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)
prev->child_hosts.insert(nhost);
}

@ -294,6 +294,7 @@ namespace DFHack
#endif
class DFHACK_EXPORT VMethodInterposeLinkBase;
class MemoryPatcher;
class DFHACK_EXPORT virtual_identity : public struct_identity {
static std::map<void*, virtual_identity*> known;
@ -313,7 +314,7 @@ 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);
bool set_vmethod_ptr(MemoryPatcher &patcher, int index, void *ptr);
public:
virtual_identity(size_t size, TAllocateFn alloc,

@ -315,5 +315,22 @@ namespace DFHack
// Get list of names given to ClassNameCheck constructors.
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