massive refactors of process, processmanager is now processenumerator, better split between windows and linux code, finalized suspend/resume

develop
Petr Mrázek 2009-11-17 03:19:13 +00:00
parent 5be36e0f42
commit 6975661733
19 changed files with 988 additions and 662 deletions

@ -6,15 +6,12 @@ DFDataModel.h
DFHackAPI.h
DFMemAccess.h
DFMemInfo.h
DFProcessManager.h
DFProcessEnumerator.h
DFProcess.h
DFTileTypes.h
DFTypes.h
DFVector.h
integers.h
stdint_win.h
LinuxMemAccess-memfiles.h
LinuxMemAccess-ptrace.h
WindowsMemAccess.h
md5/md5.h
md5/md5wrapper.h
@ -26,8 +23,7 @@ tinyxml/tinyxml.h
SET(PROJECT_SRCS
DFDataModel.cpp
DFMemInfo.cpp
DFProcess.cpp
DFProcessManager.cpp
DFProcessEnumerator.cpp
DFHackAPI.cpp
DFTileTypes.cpp
md5/md5.cpp
@ -38,6 +34,32 @@ tinyxml/tinyxmlerror.cpp
tinyxml/tinyxmlparser.cpp
)
SET(PROJECT_HDRS_LINUX
DFProcess-linux.h
MemAccess-linux.h
)
SET(PROJECT_HDRS_WINDOWS
MemAccess-windows.h
stdint_win.h
)
SET(PROJECT_SRCS_LINUX
DFProcess-linux.cpp
)
SET(PROJECT_SRCS_WINDOWS
DFProcess-windows.cpp
)
IF(UNIX)
LIST(APPEND PROJECT_HDRS ${PROJECT_HDRS_LINUX})
LIST(APPEND PROJECT_SRCS ${PROJECT_SRCS_LINUX})
ELSE(UNIX)
LIST(APPEND PROJECT_HDRS ${PROJECT_HDRS_WINDOWS})
LIST(APPEND PROJECT_SRCS ${PROJECT_SRCS_WINDOWS})
ENDIF(UNIX)
SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE )
LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS})

@ -72,13 +72,30 @@ using namespace std;
#include <tlhelp32.h>
#endif
#ifdef LINUX_BUILD
typedef pid_t ProcessHandle;
#else
typedef HANDLE ProcessHandle;
#endif
namespace DFHack
{
class Process;
/*
* Currently attached process and its handle
*/
extern Process * g_pProcess; ///< current process. non-NULL when picked
extern ProcessHandle g_ProcessHandle; ///< cache of handle to current process. used for speed reasons
extern int g_ProcessMemFile; ///< opened /proc/PID/mem, valid when attached
}
#ifndef BUILD_DFHACK_LIB
# define BUILD_DFHACK_LIB
#endif
#include "DFTypes.h"
#include "DFDataModel.h"
#include "DFProcessManager.h"
#include "DFProcess.h"
#include "DFProcessEnumerator.h"
#include "DFMemAccess.h"
#include "DFVector.h"
#include "DFMemInfo.h"

@ -74,7 +74,7 @@ class API::Private
uint32_t dwarf_lang_table_offset;
ProcessManager* pm;
ProcessEnumerator* pm;
Process* p;
DataModel* dm;
memory_info* offset_descriptor;
@ -1078,7 +1078,7 @@ bool API::Attach()
// detach all processes, destroy manager
if(d->pm == NULL)
{
d->pm = new ProcessManager(d->xml); // FIXME: handle bad XML better
d->pm = new ProcessEnumerator(d->xml); // FIXME: handle bad XML better
}
// find a process (ProcessManager can find multiple when used properly)

@ -26,9 +26,9 @@ distribution.
#define PROCESSUTIL_H_INCLUDED
#ifdef LINUX_BUILD
#include "LinuxMemAccess-memfiles.h"
#include "MemAccess-linux.h"
#else
#include "WindowsMemAccess.h"
#include "MemAccess-windows.h"
#endif
#endif // PROCESSUTIL_H_INCLUDED

@ -0,0 +1,362 @@
/*
www.sourceforge.net/projects/dfhack
Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf
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 "DFCommonInternal.h"
using namespace DFHack;
class Process::Private
{
public:
Private()
{
my_datamodel = NULL;
my_descriptor = NULL;
my_handle = NULL;
my_pid = 0;
attached = false;
suspended = false;
};
~Private(){};
DataModel* my_datamodel;
memory_info * my_descriptor;
ProcessHandle my_handle;
uint32_t my_pid;
string memFile;
bool attached;
bool suspended;
bool identified;
bool validate(char * exe_file, uint32_t pid, char * mem_file, vector <memory_info> & known_versions);
};
Process::Process(uint32_t pid, vector <memory_info> & known_versions)
: d(new Private())
{
char dir_name [256];
char exe_link_name [256];
char mem_name [256];
char cwd_name [256];
char cmdline_name [256];
char target_name[1024];
int target_result;
d->identified = false;
sprintf(dir_name,"/proc/%d/", pid);
sprintf(exe_link_name,"/proc/%d/exe", pid);
sprintf(mem_name,"/proc/%d/mem", pid);
sprintf(cwd_name,"/proc/%d/cwd", pid);
sprintf(cmdline_name,"/proc/%d/cmdline", pid);
// resolve /proc/PID/exe link
target_result = readlink(exe_link_name, target_name, sizeof(target_name)-1);
if (target_result == -1)
{
return;
}
// make sure we have a null terminated string...
target_name[target_result] = 0;
// is this the regular linux DF?
if (strstr(target_name, "dwarfort.exe") != NULL)
{
// create linux process, add it to the vector
d->identified = d->validate(target_name,pid,mem_name,known_versions );
return;
}
// FIXME: this fails when the wine process isn't started from the 'current working directory'. strip path data from cmdline
// is this windows version of Df running in wine?
if(strstr(target_name, "wine-preloader")!= NULL)
{
// get working directory
target_result = readlink(cwd_name, target_name, sizeof(target_name)-1);
target_name[target_result] = 0;
// got path to executable, do the same for its name
ifstream ifs ( cmdline_name , ifstream::in );
string cmdline;
getline(ifs,cmdline);
if (cmdline.find("dwarfort-w.exe") != string::npos || cmdline.find("dwarfort.exe") != string::npos || cmdline.find("Dwarf Fortress.exe") != string::npos)
{
char exe_link[1024];
// put executable name and path together
sprintf("%s/%s",target_name,cmdline.c_str());
// create wine process, add it to the vector
d->identified = d->validate(exe_link,pid,mem_name,known_versions);
}
}
}
bool Process::isSuspended()
{
return d->suspended;
}
bool Process::isAttached()
{
return d->attached;
}
bool Process::isIdentified()
{
return d->identified;
}
bool Process::Private::validate(char * exe_file,uint32_t pid, char * memFile, vector <memory_info> & known_versions)
{
md5wrapper md5;
// get hash of the running DF process
string hash = md5.getHashFromFile(exe_file);
vector<memory_info>::iterator it;
// iterate over the list of memory locations
for ( it=known_versions.begin() ; it < known_versions.end(); it++ )
{
if(hash == (*it).getString("md5")) // are the md5 hashes the same?
{
memory_info * m = &*it;
Process * ret;
//cout <<"Found process " << PH << ". It's DF version " << m->getVersion() << "." << endl;
// df can run under wine on Linux
if(memory_info::OS_WINDOWS == (*it).getOS())
{
my_datamodel =new DMWindows40d();
my_descriptor = m;
my_handle = my_pid = pid;
}
else if (memory_info::OS_LINUX == (*it).getOS())
{
my_datamodel =new DMLinux40d();
my_descriptor = m;
my_handle = my_pid = pid;
}
else
{
// some error happened, continue with next process
continue;
}
// tell Process about the /proc/PID/mem file
this->memFile = memFile;
identified = true;
return true;
}
}
return false;
}
Process::~Process()
{
if(d->attached)
{
detach();
}
// destroy data model. this is assigned by processmanager
delete d->my_datamodel;
}
DataModel *Process::getDataModel()
{
return d->my_datamodel;
}
memory_info * Process::getDescriptor()
{
return d->my_descriptor;
}
/*void Process::setMemFile(const string & memf)
{
assert(!attached);
memFile = memf;
}*/
bool Process::getThreadIDs(vector<uint32_t> & threads )
{
return false;
}
void Process::getMemRanges( vector<t_memrange> & ranges )
{
char buffer[1024];
char name[1024];
char permissions[5]; // r/-, w/-, x/-, p/s, 0
sprintf(buffer, "/proc/%lu/maps", d->my_pid);
FILE *mapFile = ::fopen(buffer, "r");
uint64_t begin, end, offset, device1, device2, node;
while (fgets(buffer, 1024, mapFile))
{
t_memrange temp;
temp.name[0] = 0;
sscanf(buffer, "%llx-%llx %s %llx %2llu:%2llu %llu %s",
&temp.start,
&temp.end,
(char*)&permissions,
&offset, &device1, &device2, &node,
(char*)&temp.name);
temp.read = permissions[0] == 'r';
temp.write = permissions[1] == 'w';
temp.execute = permissions[2] == 'x';
ranges.push_back(temp);
}
}
bool Process::suspend()
{
int status;
if(!d->attached)
return false;
if(d->suspended)
return true;
if (kill(d->my_handle, SIGSTOP) == -1)
{
// no, we got an error
perror("kill SIGSTOP error");
return false;
}
while(true)
{
// we wait on the pid
pid_t w = waitpid(d->my_handle, &status, 0);
if (w == -1)
{
// child died
perror("DF exited during suspend call");
return false;
}
// stopped -> let's continue
if (WIFSTOPPED(status))
{
break;
}
}
d->suspended = true;
}
bool Process::resume()
{
int status;
if(!d->attached)
return false;
if(!d->suspended)
return true;
if (ptrace(PTRACE_CONT, d->my_handle, NULL, NULL) == -1)
{
// no, we got an error
perror("ptrace resume error");
return false;
}
d->suspended = false;
}
bool Process::attach()
{
int status;
if(g_pProcess != NULL)
{
return false;
}
// can we attach?
if (ptrace(PTRACE_ATTACH , d->my_handle, NULL, NULL) == -1)
{
// no, we got an error
perror("ptrace attach error");
cerr << "attach failed on pid " << d->my_handle << endl;
return false;
}
while(true)
{
// we wait on the pid
pid_t w = waitpid(d->my_handle, &status, 0);
if (w == -1)
{
// child died
perror("wait inside attach()");
return false;
}
// stopped -> let's continue
if (WIFSTOPPED(status))
{
break;
}
}
d->suspended = true;
int proc_pid_mem = open(d->memFile.c_str(),O_RDONLY);
if(proc_pid_mem == -1)
{
ptrace(PTRACE_DETACH, d->my_handle, NULL, NULL);
cerr << "couldn't open /proc/" << d->my_handle << "/mem" << endl;
perror("open(memFile.c_str(),O_RDONLY)");
return false;
}
else
{
d->attached = true;
g_pProcess = this;
g_ProcessHandle = d->my_handle;
g_ProcessMemFile = proc_pid_mem;
return true; // we are attached
}
}
bool Process::detach()
{
if(!d->attached) return false;
if(!d->suspended) suspend();
int result = 0;
// close /proc/PID/mem
result = close(g_ProcessMemFile);
if(result == -1)
{
cerr << "couldn't close /proc/"<< d->my_handle <<"/mem" << endl;
perror("mem file close");
return false;
}
else
{
// detach
g_ProcessMemFile = -1;
result = ptrace(PTRACE_DETACH, d->my_handle, NULL, NULL);
if(result == -1)
{
cerr << "couldn't detach from process pid" << d->my_handle << endl;
perror("ptrace detach");
return false;
}
else
{
d->attached = false;
g_pProcess = NULL;
g_ProcessHandle = 0;
return true;
}
}
}

@ -0,0 +1,32 @@
/*
www.sourceforge.net/projects/dfhack
Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf
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.
*/
/*
DO NOT USE DIRECTLY, use DFProcess.h instead.
*/
#ifndef PROCESS_LIN_H_INCLUDED
#define PROCESS_LIN_H_INCLUDED
#endif

@ -0,0 +1,276 @@
/*
www.sourceforge.net/projects/dfhack
Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf
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 "DFCommonInternal.h"
using namespace DFHack;
class Process::Private
{
public:
Private()
{
my_datamodel = NULL;
my_descriptor = NULL;
my_handle = NULL;
my_main_thread = NULL;
my_pid = 0;
attached = false;
suspended = false;
};
~Private(){};
DataModel* my_datamodel;
memory_info * my_descriptor;
ProcessHandle my_handle;
HANDLE my_main_thread;
uint32_t my_pid;
string memFile;
bool attached;
bool suspended;
bool identified;
};
Process::Process(uint32_t pid, vector <memory_info> & known_versions)
: d(new Private())
{
HMODULE hmod = NULL;
DWORD junk;
HANDLE hProcess;
bool found = false;
IMAGE_NT_HEADERS32 pe_header;
IMAGE_SECTION_HEADER sections[16];
d->identified = false;
// open process
hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pid );
if (NULL == hProcess)
return;
// try getting the first module of the process
if(EnumProcessModules(hProcess, &hmod, 1 * sizeof(HMODULE), &junk) == 0)
{
CloseHandle(hProcess);
}
// got base ;)
uint32_t base = (uint32_t)hmod;
// read from this process
g_ProcessHandle = hProcess;
uint32_t pe_offset = MreadDWord(base+0x3C);
Mread(base + pe_offset , sizeof(pe_header), (uint8_t *)&pe_header);
Mread(base + pe_offset+ sizeof(pe_header), sizeof(sections) , (uint8_t *)&sections );
// see if there's a version entry that matches this process
vector<memory_info>::iterator it;
for ( it=known_versions.begin() ; it < known_versions.end(); it++ )
{
// filter by OS
if(memory_info::OS_WINDOWS != (*it).getOS())
continue;
// filter by timestamp
uint32_t pe_timestamp = (*it).getHexValue("pe_timestamp");
if (pe_timestamp != pe_header.FileHeader.TimeDateStamp)
continue;
// all went well
{
printf("Match found! Using version %s.\n", (*it).getVersion().c_str());
d->identified = true;
// give the process a data model and memory layout fixed for the base of first module
memory_info *m = new memory_info(*it);
m->RebaseAll(base);
// keep track of created memory_info object so we can destroy it later
d->my_descriptor = m;
// process is responsible for destroying its data model
d->my_datamodel = new DMWindows40d();
d->my_pid = pid;
d->my_handle = hProcess;
d->identified = true;
// TODO: detect errors in thread enumeration
vector<uint32_t> threads;
getThreadIDs( threads );
d->my_main_thread = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD) threads[0]);
found = true;
break; // break the iterator loop
}
}
// close handle of processes that aren't DF
if(!found)
{
CloseHandle(hProcess);
}
}
/*
*/
Process::~Process()
{
if(d->attached)
{
detach();
}
// destroy data model. this is assigned by processmanager
delete d->my_datamodel;
delete d->my_descriptor;
if(d->my_handle != NULL)
{
CloseHandle(d->my_handle);
}
if(d->my_main_thread != NULL)
{
CloseHandle(d->my_main_thread);
}
}
DataModel *Process::getDataModel()
{
return d->my_datamodel;
}
memory_info * Process::getDescriptor()
{
return d->my_descriptor;
}
bool Process::isSuspended()
{
return d->suspended;
}
bool Process::isAttached()
{
return d->attached;
}
bool Process::isIdentified()
{
return d->identified;
}
bool Process::suspend()
{
if(!d->attached)
return false;
if(d->suspended)
{
return true;
}
SuspendThread(d->my_main_thread);
d->suspended = true;
return true;
}
bool Process::resume()
{
if(!d->attached)
return false;
if(!d->suspended)
{
return true;
}
ResumeThread(d->my_main_thread);
d->suspended = false;
return true;
}
bool Process::attach()
{
if(d->attached)
{
return false;
}
d->attached = true;
g_pProcess = this;
g_ProcessHandle = d->my_handle;
suspend();
return true;
}
bool Process::detach()
{
if(!d->attached)
{
return false;
}
resume();
d->attached = false;
g_pProcess = NULL;
g_ProcessHandle = 0;
return true;
}
bool Process::getThreadIDs(vector<uint32_t> & threads )
{
HANDLE AllThreads = INVALID_HANDLE_VALUE;
THREADENTRY32 te32;
AllThreads = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 );
if( AllThreads == INVALID_HANDLE_VALUE )
{
return false;
}
te32.dwSize = sizeof(THREADENTRY32 );
if( !Thread32First( AllThreads, &te32 ) )
{
CloseHandle( AllThreads );
return false;
}
do
{
if( te32.th32OwnerProcessID == d->my_pid )
{
threads.push_back(te32.th32ThreadID);
}
} while( Thread32Next(AllThreads, &te32 ) );
CloseHandle( AllThreads );
return true;
}
void Process::getMemRanges( vector<t_memrange> & ranges )
{
// code here is taken from hexsearch by Silas Dunmore.
// As this IMHO isn't a 'sunstantial portion' of anything, I'm not including the MIT license here
// I'm faking this, because there's no way I'm using VirtualQuery
t_memrange temp;
uint32_t base = d->my_descriptor->getBase();
temp.start = base + 0x1000; // more fakery.
temp.end = base + MreadDWord(base+MreadDWord(base+0x3C)+0x50)-1; // yay for magic.
temp.read = 1;
temp.write = 1;
temp.execute = 0; // fake
strcpy(temp.name,"pants");// that's right. I'm calling it pants. Windows can go to HELL
ranges.push_back(temp);
}

@ -0,0 +1,67 @@
/*
www.sourceforge.net/projects/dfhack
Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf
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.
*/
/*
DO NOT USE DIRECTLY, use DFProcess.h instead.
*/
#ifndef PROCESS_WIN_H_INCLUDED
#define PROCESS_WIN_H_INCLUDED
namespace DFHack
{
class DFHACK_EXPORT Process
{
friend class ProcessEnumerator;
private:
Process(DataModel * dm, memory_info* mi, ProcessHandle ph, uint32_t pid);
~Process();
DataModel* my_datamodel;
memory_info * my_descriptor;
ProcessHandle my_handle;
uint32_t my_pid;
string memFile;
bool attached;
bool suspended;
void freeResources();
void setMemFile(const string & memf);
public:
// Set up stuff so we can read memory
bool attach();
bool detach();
bool suspend();
bool resume();
bool isSuspended() {return suspended;};
bool isAttached() {return attached;};
bool getThreadIDs(vector<uint32_t> & threads );
void getMemRanges( vector<t_memrange> & ranges );
// is the process still there?
memory_info *getDescriptor();
DataModel *getDataModel();
};
}
#endif

@ -1,377 +0,0 @@
/*
www.sourceforge.net/projects/dfhack
Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf
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 "DFCommonInternal.h"
using namespace DFHack;
Process::Process(DataModel * dm, memory_info* mi, ProcessHandle ph, uint32_t pid)
{
my_datamodel = dm;
my_descriptor = mi;
my_handle = ph;
my_pid = pid;
attached = false;
suspended = false;
}
Process::~Process()
{
if(attached)
{
detach();
}
// destroy data model. this is assigned by processmanager
delete my_datamodel;
freeResources();
}
DataModel *Process::getDataModel()
{
return my_datamodel;
}
memory_info * Process::getDescriptor()
{
return my_descriptor;
}
void Process::setMemFile(const string & memf)
{
assert(!attached);
memFile = memf;
}
#ifdef LINUX_BUILD
/*
* LINUX PART
*/
bool Process::getThreadIDs(vector<uint32_t> & threads )
{
return false;
}
void Process::getMemRanges( vector<t_memrange> & ranges )
{
char buffer[1024];
char name[1024];
char permissions[5]; // r/-, w/-, x/-, p/s, 0
sprintf(buffer, "/proc/%lu/maps", my_pid);
FILE *mapFile = ::fopen(buffer, "r");
uint64_t begin, end, offset, device1, device2, node;
while (fgets(buffer, 1024, mapFile))
{
t_memrange temp;
temp.name[0] = 0;
sscanf(buffer, "%llx-%llx %s %llx %2llu:%2llu %llu %s",
&temp.start,
&temp.end,
(char*)&permissions,
&offset, &device1, &device2, &node,
(char*)&temp.name);
temp.read = permissions[0] == 'r';
temp.write = permissions[1] == 'w';
temp.execute = permissions[2] == 'x';
ranges.push_back(temp);
}
}
bool Process::suspend()
{
int status;
if(!attached)
return false;
if(suspended)
return true;
if (kill(my_handle, SIGSTOP) == -1)
{
// no, we got an error
perror("kill SIGSTOP error");
return false;
}
while(true)
{
// we wait on the pid
pid_t w = waitpid(my_handle, &status, 0);
if (w == -1)
{
// child died
perror("DF exited during suspend call");
return false;
}
// stopped -> let's continue
if (WIFSTOPPED(status))
{
break;
}
}
suspended = true;
}
bool Process::resume()
{
int status;
if(!attached)
return false;
if(!suspended)
return true;
if (ptrace(PTRACE_CONT, my_handle, NULL, NULL) == -1)
{
// no, we got an error
perror("ptrace resume error");
return false;
}
suspended = false;
}
bool Process::attach()
{
int status;
if(g_pProcess != NULL)
{
return false;
}
// can we attach?
if (ptrace(PTRACE_ATTACH , my_handle, NULL, NULL) == -1)
{
// no, we got an error
perror("ptrace attach error");
cerr << "attach failed on pid " << my_handle << endl;
return false;
}
while(true)
{
// we wait on the pid
pid_t w = waitpid(my_handle, &status, 0);
if (w == -1)
{
// child died
perror("wait inside attach()");
return false;
}
// stopped -> let's continue
if (WIFSTOPPED(status))
{
break;
}
}
suspended = true;
int proc_pid_mem = open(memFile.c_str(),O_RDONLY);
if(proc_pid_mem == -1)
{
ptrace(PTRACE_DETACH, my_handle, NULL, NULL);
cerr << "couldn't open /proc/" << my_handle << "/mem" << endl;
perror("open(memFile.c_str(),O_RDONLY)");
return false;
}
else
{
attached = true;
g_pProcess = this;
g_ProcessHandle = my_handle;
g_ProcessMemFile = proc_pid_mem;
return true; // we are attached
}
}
bool Process::detach()
{
if(!attached) return false;
if(!suspended) suspend();
int result = 0;
// close /proc/PID/mem
result = close(g_ProcessMemFile);
if(result == -1)
{
cerr << "couldn't close /proc/"<< my_handle <<"/mem" << endl;
perror("mem file close");
return false;
}
else
{
// detach
g_ProcessMemFile = -1;
result = ptrace(PTRACE_DETACH, my_handle, NULL, NULL);
if(result == -1)
{
cerr << "couldn't detach from process pid" << my_handle << endl;
perror("ptrace detach");
return false;
}
else
{
attached = false;
g_pProcess = NULL;
g_ProcessHandle = 0;
return true;
}
}
}
void Process::freeResources()
{
// nil
};
#else
/*
* WINDOWS PART
*/
bool Process::suspend()
{
if(!attached)
return false;
if(suspended)
{
return true;
}
vector<uint32_t> threads;
getThreadIDs( threads );
for(int i = 0; i < /*threads.size()*/ 1; i++)
{
HANDLE thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD) threads[i]);
SuspendThread(thread_handle);
CloseHandle(thread_handle);
}
suspended = true;
return true;
}
bool Process::resume()
{
if(!attached)
return false;
if(!suspended)
{
return true;
}
vector<uint32_t> threads;
getThreadIDs( threads );
for(int i = 0; i < /* threads.size() */ 1; i++)
{
HANDLE thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD) threads[i]);
ResumeThread(thread_handle);
CloseHandle(thread_handle);
}
suspended = false;
return true;
}
bool Process::attach()
{
if(attached)
{
return false;
}
attached = true;
g_pProcess = this;
g_ProcessHandle = my_handle;
suspend();
return true;
}
bool Process::detach()
{
if(!attached)
{
return false;
}
resume();
attached = false;
g_pProcess = NULL;
g_ProcessHandle = 0;
return true;
}
bool Process::getThreadIDs(vector<uint32_t> & threads )
{
HANDLE AllThreads = INVALID_HANDLE_VALUE;
THREADENTRY32 te32;
AllThreads = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 );
if( AllThreads == INVALID_HANDLE_VALUE )
{
return false;
}
te32.dwSize = sizeof(THREADENTRY32 );
if( !Thread32First( AllThreads, &te32 ) )
{
CloseHandle( AllThreads );
return false;
}
do
{
if( te32.th32OwnerProcessID == my_pid )
{
threads.push_back(te32.th32ThreadID);
}
} while( Thread32Next(AllThreads, &te32 ) );
CloseHandle( AllThreads );
return true;
}
void Process::getMemRanges( vector<t_memrange> & ranges )
{
// code here is taken from hexsearch by Silas Dunmore.
// As this IMHO isn't a 'sunstantial portion' of anything, I'm not including the MIT license here
// I'm faking this, because there's no way I'm using VirtualQuery
t_memrange temp;
uint32_t base = this->my_descriptor->getBase();
temp.start = base + 0x1000; // more fakery.
temp.end = base + MreadDWord(base+MreadDWord(base+0x3C)+0x50)-1; // yay for magic.
temp.read = 1;
temp.write = 1;
temp.execute = 0; // fake
strcpy(temp.name,"pants");// that's right. I'm calling it pants. Windows can go to HELL
ranges.push_back(temp);
}
void Process::freeResources()
{
// opened by process manager
CloseHandle(my_handle);
};
#endif

@ -22,33 +22,17 @@ must not be misrepresented as being the original software.
distribution.
*/
#ifndef PROCESSMANAGER_H_INCLUDED
#define PROCESSMANAGER_H_INCLUDED
#ifndef PROCESS_H_INCLUDED
#define PROCESS_H_INCLUDED
#include "Export.h"
class TiXmlElement;
namespace DFHack
{
class memory_info;
class DataModel;
class Process;
#ifdef LINUX_BUILD
typedef pid_t ProcessHandle;
#else
typedef HANDLE ProcessHandle;
#endif
/*
* Currently attached process and its handle
*/
extern Process * g_pProcess; ///< current process. non-NULL when picked
extern ProcessHandle g_ProcessHandle; ///< cache of handle to current process. used for speed reasons
extern int g_ProcessMemFile; ///< opened /proc/PID/mem, valid when attached
// structure describing a memory range
struct DFHACK_EXPORT t_memrange
{
@ -75,19 +59,12 @@ namespace DFHack
class DFHACK_EXPORT Process
{
friend class ProcessManager;
protected:
Process(DataModel * dm, memory_info* mi, ProcessHandle ph, uint32_t pid);
friend class ProcessEnumerator;
class Private;
private:
Private * const d;
Process(uint32_t pid, vector <memory_info> & known_versions);
~Process();
DataModel* my_datamodel;
memory_info * my_descriptor;
ProcessHandle my_handle;
uint32_t my_pid;
string memFile;
bool attached;
bool suspended;
void freeResources();
void setMemFile(const string & memf);
public:
// Set up stuff so we can read memory
bool attach();
@ -96,8 +73,9 @@ namespace DFHack
bool suspend();
bool resume();
bool isSuspended() {return suspended;};
bool isAttached() {return attached;};
bool isSuspended();
bool isAttached();
bool isIdentified();
bool getThreadIDs(vector<uint32_t> & threads );
void getMemRanges( vector<t_memrange> & ranges );
@ -105,33 +83,5 @@ namespace DFHack
memory_info *getDescriptor();
DataModel *getDataModel();
};
/*
* Process manager
*/
class DFHACK_EXPORT ProcessManager
{
public:
ProcessManager( string path_to_xml);
~ProcessManager();
bool findProcessess();
uint32_t size();
Process * operator[](uint32_t index);
private:
// memory info entries loaded from a file
std::vector<memory_info> meminfo;
// vector to keep track of dynamically created memory_info entries
std::vector<memory_info *> destroy_meminfo;
Process * currentProcess;
ProcessHandle currentProcessHandle;
std::vector<Process *> processes;
bool loadDescriptors( string path_to_xml);
void ParseVTable(TiXmlElement* vtable, memory_info& mem);
void ParseEntry (TiXmlElement* entry, memory_info& mem, map <string ,TiXmlElement *>& knownEntries);
#ifdef LINUX_BUILD
Process* addProcess(const string & exe,ProcessHandle PH,const string & memFile);
#endif
};
}
#endif // PROCESSMANAGER_H_INCLUDED
#endif

@ -22,7 +22,6 @@ must not be misrepresented as being the original software.
distribution.
*/
//#define LINUX_BUILD
#include "DFCommonInternal.h"
using namespace DFHack;
@ -31,51 +30,29 @@ Process * DFHack::g_pProcess; ///< current process. non-NULL when picked
ProcessHandle DFHack::g_ProcessHandle; ///< cache of handle to current process. used for speed reasons
int DFHack::g_ProcessMemFile; ///< opened /proc/PID/mem, valid when attached
class DFHack::ProcessEnumerator::Private
{
public:
Private(){};
// memory info entries loaded from a file
std::vector<memory_info> meminfo;
Process * currentProcess;
ProcessHandle currentProcessHandle;
std::vector<Process *> processes;
bool loadDescriptors( string path_to_xml);
void ParseVTable(TiXmlElement* vtable, memory_info& mem);
void ParseEntry (TiXmlElement* entry, memory_info& mem, map <string ,TiXmlElement *>& knownEntries);
#ifdef LINUX_BUILD
Process* addProcess(const string & exe,ProcessHandle PH,const string & memFile);
#endif
};
#ifdef LINUX_BUILD
/*
* LINUX version of the process finder.
*/
Process* ProcessManager::addProcess(const string & exe,ProcessHandle PH, const string & memFile)
{
md5wrapper md5;
// get hash of the running DF process
string hash = md5.getHashFromFile(exe);
vector<memory_info>::iterator it;
// iterate over the list of memory locations
for ( it=meminfo.begin() ; it < meminfo.end(); it++ )
{
if(hash == (*it).getString("md5")) // are the md5 hashes the same?
{
memory_info * m = &*it;
Process * ret;
//cout <<"Found process " << PH << ". It's DF version " << m->getVersion() << "." << endl;
// df can run under wine on Linux
if(memory_info::OS_WINDOWS == (*it).getOS())
{
ret= new Process(new DMWindows40d(),m,PH, PH);
}
else if (memory_info::OS_LINUX == (*it).getOS())
{
ret= new Process(new DMLinux40d(),m,PH, PH);
}
else
{
// some error happened, continue with next process
continue;
}
// tell Process about the /proc/PID/mem file
ret->setMemFile(memFile);
processes.push_back(ret);
return ret;
}
}
return NULL;
}
bool ProcessManager::findProcessess()
bool ProcessEnumerator::findProcessess()
{
DIR *dir_p;
struct dirent *dir_entry_p;
@ -86,8 +63,7 @@ bool ProcessManager::findProcessess()
string cmdline;
// ALERT: buffer overrun potential
char target_name[1024];
int target_result;
int errorcount;
int result;
@ -103,67 +79,19 @@ bool ProcessManager::findProcessess()
{
continue;
}
// string manipulation - get /proc/PID/exe link and /proc/PID/mem names
dir_name = "/proc/";
dir_name += dir_entry_p->d_name;
dir_name += "/";
exe_link = dir_name + "exe";
string mem_name = dir_name + "mem";
// resolve /proc/PID/exe link
target_result = readlink(exe_link.c_str(), target_name, sizeof(target_name)-1);
if (target_result == -1)
{
// bad result from link resolution, continue with another processed
continue;
}
// make sure we have a null terminated string...
target_name[target_result] = 0;
// is this the regular linux DF?
if (strstr(target_name, "dwarfort.exe") != NULL)
Process *p = new Process(atoi(dir_entry_p->d_name),d->meminfo);
if(p->isIdentified())
{
exe_link = target_name;
// get PID
result = atoi(dir_entry_p->d_name);
// create linux process, add it to the vector
addProcess(exe_link,result,mem_name);
// continue with next process
continue;
d->processes.push_back(p);
}
// FIXME: this fails when the wine process isn't started from the 'current working directory'. strip path data from cmdline
// is this windows version of Df running in wine?
if(strstr(target_name, "wine-preloader")!= NULL)
else
{
// get working directory
cwd_link = dir_name + "cwd";
target_result = readlink(cwd_link.c_str(), target_name, sizeof(target_name)-1);
target_name[target_result] = 0;
// got path to executable, do the same for its name
cmdline_path = dir_name + "cmdline";
ifstream ifs ( cmdline_path.c_str() , ifstream::in );
getline(ifs,cmdline);
if (cmdline.find("dwarfort-w.exe") != string::npos || cmdline.find("dwarfort.exe") != string::npos || cmdline.find("Dwarf Fortress.exe") != string::npos)
{
// put executable name and path together
exe_link = target_name;
exe_link += "/";
exe_link += cmdline;
// get PID
result = atoi(dir_entry_p->d_name);
// create wine process, add it to the vector
addProcess(exe_link,result,mem_name);
}
delete p;
}
}
closedir(dir_p);
// return value depends on if we found some DF processes
if(processes.size())
if(d->processes.size())
{
return true;
}
@ -199,18 +127,10 @@ bool EnableDebugPriv()
}
// WINDOWS version of the process finder
bool ProcessManager::findProcessess()
bool ProcessEnumerator::findProcessess()
{
// Get the list of process identifiers.
//TODO: make this dynamic. (call first to get the array size and second to really get process handles)
DWORD ProcArray[512], memoryNeeded, numProccesses;
HMODULE hmod = NULL;
DWORD junk;
HANDLE hProcess;
bool found = false;
IMAGE_NT_HEADERS32 pe_header;
IMAGE_SECTION_HEADER sections[16];
DWORD ProcArray[2048], memoryNeeded, numProccesses;
EnableDebugPriv();
if ( !EnumProcesses( ProcArray, sizeof(ProcArray), &memoryNeeded ) )
@ -224,71 +144,24 @@ bool ProcessManager::findProcessess()
// iterate through processes
for ( int i = 0; i < (int)numProccesses; i++ )
{
found = false;
// open process
hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, ProcArray[i] );
if (NULL == hProcess)
continue;
// try getting the first module of the process
if(EnumProcessModules(hProcess, &hmod, 1 * sizeof(HMODULE), &junk) == 0)
{
CloseHandle(hProcess);
continue;
}
// got base ;)
uint32_t base = (uint32_t)hmod;
// read from this process
g_ProcessHandle = hProcess;
uint32_t pe_offset = MreadDWord(base+0x3C);
Mread(base + pe_offset , sizeof(pe_header), (uint8_t *)&pe_header);
Mread(base + pe_offset+ sizeof(pe_header), sizeof(sections) , (uint8_t *)&sections );
// see if there's a version entry that matches this process
vector<memory_info>::iterator it;
for ( it=meminfo.begin() ; it < meminfo.end(); it++ )
Process *p = new Process(ProcArray[i],d->meminfo);
if(p->isIdentified())
{
// filter by OS
if(memory_info::OS_WINDOWS != (*it).getOS())
continue;
// filter by timestamp
uint32_t pe_timestamp = (*it).getHexValue("pe_timestamp");
if (pe_timestamp != pe_header.FileHeader.TimeDateStamp)
continue;
// all went well
{
printf("Match found! Using version %s.\n", (*it).getVersion().c_str());
// give the process a data model and memory layout fixed for the base of first module
memory_info *m = new memory_info(*it);
m->RebaseAll(base);
// keep track of created memory_info objects so we can destroy them later
destroy_meminfo.push_back(m);
// process is responsible for destroying its data model
Process *ret= new Process(new DMWindows40d(),m,hProcess, ProcArray[i]);
processes.push_back(ret);
found = true;
break; // break the iterator loop
}
d->processes.push_back(p);
}
// close handle of processes that aren't DF
if(!found)
else
{
CloseHandle(hProcess);
delete p;
}
}
if(processes.size())
if(d->processes.size())
return true;
return false;
}
#endif
void ProcessManager::ParseVTable(TiXmlElement* vtable, memory_info& mem)
void ProcessEnumerator::Private::ParseVTable(TiXmlElement* vtable, memory_info& mem)
{
TiXmlElement* pClassEntry;
TiXmlElement* pClassSubEntry;
@ -336,7 +209,7 @@ void ProcessManager::ParseVTable(TiXmlElement* vtable, memory_info& mem)
void ProcessManager::ParseEntry (TiXmlElement* entry, memory_info& mem, map <string ,TiXmlElement *>& knownEntries)
void ProcessEnumerator::Private::ParseEntry (TiXmlElement* entry, memory_info& mem, map <string ,TiXmlElement *>& knownEntries)
{
TiXmlElement* pMemEntry;
const char *cstr_version = entry->Attribute("version");
@ -459,7 +332,7 @@ void ProcessManager::ParseEntry (TiXmlElement* entry, memory_info& mem, map <str
// load the XML file with offsets
bool ProcessManager::loadDescriptors(string path_to_xml)
bool ProcessEnumerator::Private::loadDescriptors(string path_to_xml)
{
TiXmlDocument doc( path_to_xml.c_str() );
bool loadOkay = doc.LoadFile();
@ -526,37 +399,34 @@ bool ProcessManager::loadDescriptors(string path_to_xml)
}
uint32_t ProcessManager::size()
uint32_t ProcessEnumerator::size()
{
return processes.size();
return d->processes.size();
};
Process * ProcessManager::operator[](uint32_t index)
Process * ProcessEnumerator::operator[](uint32_t index)
{
assert(index < processes.size());
return processes[index];
assert(index < d->processes.size());
return d->processes[index];
};
ProcessManager::ProcessManager( string path_to_xml )
ProcessEnumerator::ProcessEnumerator( string path_to_xml )
: d(new Private())
{
currentProcess = NULL;
currentProcessHandle = 0;
loadDescriptors( path_to_xml );
d->currentProcess = NULL;
d->currentProcessHandle = 0;
d->loadDescriptors( path_to_xml );
}
ProcessManager::~ProcessManager()
ProcessEnumerator::~ProcessEnumerator()
{
// delete all processes
for(uint32_t i = 0;i < processes.size();i++)
{
delete processes[i];
}
//delete all generated memory_info stuff
for(uint32_t i = 0;i < destroy_meminfo.size();i++)
for(uint32_t i = 0;i < d->processes.size();i++)
{
delete destroy_meminfo[i];
delete d->processes[i];
}
delete d;
}

@ -0,0 +1,53 @@
/*
www.sourceforge.net/projects/dfhack
Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf
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.
*/
#ifndef PROCESSMANAGER_H_INCLUDED
#define PROCESSMANAGER_H_INCLUDED
#include "Export.h"
class TiXmlElement;
namespace DFHack
{
class memory_info;
class DataModel;
class Process;
/*
* Process manager
*/
class DFHACK_EXPORT ProcessEnumerator
{
class Private;
Private * const d;
public:
ProcessEnumerator( string path_to_xml);
~ProcessEnumerator();
bool findProcessess();
uint32_t size();
Process * operator[](uint32_t index);
};
}
#endif // PROCESSMANAGER_H_INCLUDED

@ -109,34 +109,34 @@ std::string md5wrapper::getHashFromString(std::string text)
*/
std::string md5wrapper::getHashFromFile(std::string filename)
{
FILE *file;
MD5_CTX context;
int len;
unsigned char buffer[1024], digest[16];
//open file
if ((file = fopen (filename.c_str(), "rb")) == NULL)
{
return "-1";
}
//init md5
md5->MD5Init (&context);
//read the filecontent
while ( (len = fread (buffer, 1, 1024, file)) )
{
md5->MD5Update (&context, buffer, len);
}
/*
generate hash, close the file and return the
hash as std::string
*/
md5->MD5Final (digest, &context);
fclose (file);
return convToString(digest);
FILE *file;
MD5_CTX context;
int len;
unsigned char buffer[1024], digest[16];
//open file
if ((file = fopen (filename.c_str(), "rb")) == NULL)
{
return "-1";
}
//init md5
md5->MD5Init (&context);
//read the filecontent
while ( (len = fread (buffer, 1, 1024, file)) )
{
md5->MD5Update (&context, buffer, len);
}
/*
generate hash, close the file and return the
hash as std::string
*/
md5->MD5Final (digest, &context);
fclose (file);
return convToString(digest);
}
/*

@ -42,9 +42,16 @@ TARGET_LINK_LIBRARIES(dfmaterialtest dfhack)
ADD_EXECUTABLE(dfposition position.cpp)
TARGET_LINK_LIBRARIES(dfposition dfhack)
# incremental - incremental memory search tool, a foreshadowing of the future direction of dfhack
ADD_EXECUTABLE(dfincremental incrementalsearch.cpp)
TARGET_LINK_LIBRARIES(dfincremental dfhack)
#currently only stable on linux
IF(UNIX)
# incremental - incremental memory search tool, a foreshadowing of the future direction of dfhack
ADD_EXECUTABLE(dfincremental incrementalsearch.cpp)
TARGET_LINK_LIBRARIES(dfincremental dfhack)
ENDIF(UNIX)
# suspendtest - test if suspend works. df should stop responding when suspended by dfhack
ADD_EXECUTABLE(dfsuspend suspendtest.cpp)
TARGET_LINK_LIBRARIES(dfsuspend dfhack)
IF(UNIX)
install(TARGETS
@ -59,6 +66,7 @@ dfmaterialtest
dfbuildingsdump
dfposition
dfincremental
dfsuspend
RUNTIME DESTINATION bin
)
ENDIF(UNIX)

@ -21,7 +21,8 @@ using namespace std;
#include <DFTypes.h>
#include <DFHackAPI.h>
#include <DFProcessManager.h>
#include <DFProcessEnumerator.h>
#include <DFProcess.h>
#include <DFMemInfo.h>
//TODO: lots of optimization

@ -0,0 +1,45 @@
// Test suspend/resume
#include <iostream>
#include <climits>
#include <integers.h>
#include <vector>
#include <ctime>
#include <string>
using namespace std;
#include <DFTypes.h>
#include <DFHackAPI.h>
int main (void)
{
string blah;
DFHack::API DF("Memory.xml");
if(!DF.Attach())
{
cerr << "DF not found" << endl;
return 1;
}
cout << "Attached, DF should be suspended now" << endl;
getline(cin, blah);
DF.Resume();
cout << "Resumed, DF should be running" << endl;
getline(cin, blah);
DF.Suspend();
cout << "Suspended, DF should be suspended now" << endl;
getline(cin, blah);
if(!DF.Detach())
{
cerr << "Can't detach from DF" << endl;
return 1;
}
cout << "Detached, DF should be running again" << endl;
getline(cin, blah);
return 0;
}