diff --git a/library/DFHackAPI.cpp b/library/DFHackAPI.cpp index 264cf1a25..aa5537119 100644 --- a/library/DFHackAPI.cpp +++ b/library/DFHackAPI.cpp @@ -969,6 +969,10 @@ bool API::Attach() { d->pm = new ProcessEnumerator (d->xml); // FIXME: handle bad XML better } + else + { + d->pm->purge(); + } // find a process (ProcessManager can find multiple when used properly) if (!d->pm->findProcessess()) diff --git a/library/DFProcess-linux-SHM.cpp b/library/DFProcess-linux-SHM.cpp index 195801df5..01d8a3c59 100644 --- a/library/DFProcess-linux-SHM.cpp +++ b/library/DFProcess-linux-SHM.cpp @@ -28,8 +28,50 @@ distribution. #include #include #include "shmserver/dfconnect.h" +#include +#include +#include +#include using namespace DFHack; +// a full memory barrier! better be safe than sorry. +#define gcc_barrier asm volatile("" ::: "memory"); __sync_synchronize(); + +/* +* wait for futex +* futex has to be aligned to 4 bytes +* futex has to be equal to val (returns EWOULDBLOCK otherwise) +* wait can be broken by arriving signals (returns EINTR) +* returns 0 when broken by futex_wake +*/ +inline int futex_wait(int * futex, int val) +{ + return syscall(SYS_futex, futex, FUTEX_WAIT, val, 0, 0, 0); +} +/* +* wait for futex +* futex has to be aligned to 4 bytes +* futex has to be equal to val (returns EWOULDBLOCK otherwise) +* wait can be broken by arriving signals (returns EINTR) +* returns 0 when broken by futex_wake +* returns ETIMEDOUT on timeout +*/ +inline int futex_wait_timed(int * futex, int val, const struct timespec *timeout) +{ + return syscall(SYS_futex, futex, FUTEX_WAIT, val, timeout, 0, 0); +} +/* +* wake up futex. returns number of waked processes +*/ +inline int futex_wake(int * futex) +{ + return syscall(SYS_futex, futex, FUTEX_WAKE, 1, 0, 0, 0); +} +static timespec one_second = { 1,0 }; +static timespec five_second = { 5,0 }; + + + class SHMProcess::Private { public: @@ -38,6 +80,8 @@ class SHMProcess::Private my_datamodel = NULL; my_descriptor = NULL; my_pid = 0; + my_shm = 0; + my_shmid = -1; my_window = NULL; attached = false; suspended = false; @@ -54,6 +98,7 @@ class SHMProcess::Private bool attached; bool suspended; bool identified; + bool validate(char * exe_file, uint32_t pid, vector & known_versions); bool waitWhile (DF_PINGPONG state); bool DF_TestBridgeVersion(bool & ret); @@ -71,6 +116,7 @@ bool SHMProcess::Private::waitWhile (DF_PINGPONG state) shmctl(my_shmid, IPC_STAT, &descriptor); if(descriptor.shm_nattch == 1)// DF crashed? { + gcc_barrier ((shm_cmd *)my_shm)->pingpong = DFPP_RUNNING; attached = suspended = false; return false; @@ -96,8 +142,10 @@ bool SHMProcess::Private::waitWhile (DF_PINGPONG state) bool SHMProcess::Private::DF_TestBridgeVersion(bool & ret) { ((shm_cmd *)my_shm)->pingpong = DFPP_VERSION; + gcc_barrier if(!waitWhile(DFPP_VERSION)) return false; + gcc_barrier ((shm_cmd *)my_shm)->pingpong = DFPP_SUSPENDED; ret =( ((shm_retval *)my_shm)->value == PINGPONG_VERSION ); return true; @@ -106,8 +154,10 @@ bool SHMProcess::Private::DF_TestBridgeVersion(bool & ret) bool SHMProcess::Private::DF_GetPID(pid_t & ret) { ((shm_cmd *)my_shm)->pingpong = DFPP_PID; + gcc_barrier if(!waitWhile(DFPP_PID)) return false; + gcc_barrier ((shm_cmd *)my_shm)->pingpong = DFPP_SUSPENDED; ret = ((shm_retval *)my_shm)->value; return true; @@ -139,11 +189,12 @@ SHMProcess::SHMProcess(vector & known_versions) /* * Check if there are two processes connected to the segment */ - struct shmid_ds descriptor; + shmid_ds descriptor; shmctl(d->my_shmid, IPC_STAT, &descriptor); if(descriptor.shm_nattch != 2)// badness { - fprintf(stderr,"dfhack: no DF or different client already connected\n"); + fprintf(stderr,"dfhack: %d : invalid no. of processes connected\n", descriptor.shm_nattch); + fprintf(stderr,"detach: %d",shmdt(d->my_shm)); return; } @@ -182,8 +233,10 @@ SHMProcess::SHMProcess(vector & known_versions) d->validate(target_name, d->my_pid, known_versions); d->my_window = new DFWindow(this); } + gcc_barrier // at this point, DF is stopped and waiting for commands. make it run again ((shm_cmd *)d->my_shm)->pingpong = DFPP_RUNNING; + fprintf(stderr,"detach: %d",shmdt(d->my_shm)); } bool SHMProcess::isSuspended() @@ -239,6 +292,10 @@ SHMProcess::~SHMProcess() { delete d->my_window; } + if(d->my_shm) + { + fprintf(stderr,"detach: %d",shmdt(d->my_shm)); + } delete d; } @@ -341,15 +398,23 @@ bool SHMProcess::attach() cerr << "there's already a different process attached" << endl; return false; } - d->attached = true; - if(suspend()) + /* + * Attach the segment + */ + if ((d->my_shm = (char *) shmat(d->my_shmid, NULL, 0)) != (char *) -1) { - d->suspended = true; - g_pProcess = this; - return true; + d->attached = true; + if(suspend()) + { + d->suspended = true; + g_pProcess = this; + return true; + } + d->attached = false; + cerr << "unable to suspend" << endl; + return false; } - d->attached = false; - cerr << "unable to suspend" << endl; + cerr << "unable to attach" << endl; return false; } @@ -375,6 +440,7 @@ void SHMProcess::read (const uint32_t offset, const uint32_t size, uint8_t *targ assert (size < (SHM_SIZE - sizeof(shm_read))); ((shm_read *)d->my_shm)->address = offset; ((shm_read *)d->my_shm)->length = size; + gcc_barrier ((shm_read *)d->my_shm)->pingpong = DFPP_READ; d->waitWhile(DFPP_READ); memcpy (target, d->my_shm + sizeof(shm_ret_data),size); @@ -383,6 +449,7 @@ void SHMProcess::read (const uint32_t offset, const uint32_t size, uint8_t *targ uint8_t SHMProcess::readByte (const uint32_t offset) { ((shm_read_small *)d->my_shm)->address = offset; + gcc_barrier ((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_BYTE; d->waitWhile(DFPP_READ_BYTE); return ((shm_retval *)d->my_shm)->value; @@ -391,6 +458,7 @@ uint8_t SHMProcess::readByte (const uint32_t offset) void SHMProcess::readByte (const uint32_t offset, uint8_t &val ) { ((shm_read_small *)d->my_shm)->address = offset; + gcc_barrier ((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_BYTE; d->waitWhile(DFPP_READ_BYTE); val = ((shm_retval *)d->my_shm)->value; @@ -399,6 +467,7 @@ void SHMProcess::readByte (const uint32_t offset, uint8_t &val ) uint16_t SHMProcess::readWord (const uint32_t offset) { ((shm_read_small *)d->my_shm)->address = offset; + gcc_barrier ((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_WORD; d->waitWhile(DFPP_READ_WORD); return ((shm_retval *)d->my_shm)->value; @@ -407,6 +476,7 @@ uint16_t SHMProcess::readWord (const uint32_t offset) void SHMProcess::readWord (const uint32_t offset, uint16_t &val) { ((shm_read_small *)d->my_shm)->address = offset; + gcc_barrier ((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_WORD; d->waitWhile(DFPP_READ_WORD); val = ((shm_retval *)d->my_shm)->value; @@ -415,6 +485,7 @@ void SHMProcess::readWord (const uint32_t offset, uint16_t &val) uint32_t SHMProcess::readDWord (const uint32_t offset) { ((shm_read_small *)d->my_shm)->address = offset; + gcc_barrier ((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_DWORD; d->waitWhile(DFPP_READ_DWORD); return ((shm_retval *)d->my_shm)->value; @@ -422,6 +493,7 @@ uint32_t SHMProcess::readDWord (const uint32_t offset) void SHMProcess::readDWord (const uint32_t offset, uint32_t &val) { ((shm_read_small *)d->my_shm)->address = offset; + gcc_barrier ((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_DWORD; d->waitWhile(DFPP_READ_DWORD); val = ((shm_retval *)d->my_shm)->value; @@ -435,6 +507,7 @@ void SHMProcess::writeDWord (uint32_t offset, uint32_t data) { ((shm_write_small *)d->my_shm)->address = offset; ((shm_write_small *)d->my_shm)->value = data; + gcc_barrier ((shm_write_small *)d->my_shm)->pingpong = DFPP_WRITE_DWORD; d->waitWhile(DFPP_WRITE_DWORD); } @@ -444,6 +517,7 @@ void SHMProcess::writeWord (uint32_t offset, uint16_t data) { ((shm_write_small *)d->my_shm)->address = offset; ((shm_write_small *)d->my_shm)->value = data; + gcc_barrier ((shm_write_small *)d->my_shm)->pingpong = DFPP_WRITE_WORD; d->waitWhile(DFPP_WRITE_WORD); } @@ -452,6 +526,7 @@ void SHMProcess::writeByte (uint32_t offset, uint8_t data) { ((shm_write_small *)d->my_shm)->address = offset; ((shm_write_small *)d->my_shm)->value = data; + gcc_barrier ((shm_write_small *)d->my_shm)->pingpong = DFPP_WRITE_BYTE; d->waitWhile(DFPP_WRITE_BYTE); } @@ -461,6 +536,7 @@ void SHMProcess::write (uint32_t offset, uint32_t size, const uint8_t *source) ((shm_write *)d->my_shm)->address = offset; ((shm_write *)d->my_shm)->length = size; memcpy(d->my_shm+sizeof(shm_write),source, size); + gcc_barrier ((shm_write *)d->my_shm)->pingpong = DFPP_WRITE; d->waitWhile(DFPP_WRITE); } diff --git a/library/DFProcessEnumerator-linux.cpp b/library/DFProcessEnumerator-linux.cpp index d477a1a7d..6755bf9e7 100644 --- a/library/DFProcessEnumerator-linux.cpp +++ b/library/DFProcessEnumerator-linux.cpp @@ -110,14 +110,19 @@ ProcessEnumerator::ProcessEnumerator( string path_to_xml ) d->meminfo = new MemInfoManager(path_to_xml); } - -ProcessEnumerator::~ProcessEnumerator() +void ProcessEnumerator::purge() { - // delete all processes for(uint32_t i = 0;i < d->processes.size();i++) { delete d->processes[i]; } + d->processes.clear(); +} + +ProcessEnumerator::~ProcessEnumerator() +{ + // delete all processes + purge(); delete d->meminfo; delete d; } diff --git a/library/DFProcessEnumerator-windows.cpp b/library/DFProcessEnumerator-windows.cpp index d6156f196..50532e42b 100644 --- a/library/DFProcessEnumerator-windows.cpp +++ b/library/DFProcessEnumerator-windows.cpp @@ -115,14 +115,19 @@ ProcessEnumerator::ProcessEnumerator( string path_to_xml ) d->meminfo = new MemInfoManager(path_to_xml); } - -ProcessEnumerator::~ProcessEnumerator() +ProcessEnumerator::purge() { - // delete all processes for(uint32_t i = 0;i < d->processes.size();i++) { delete d->processes[i]; } + d->processes.clear(); +} + +ProcessEnumerator::~ProcessEnumerator() +{ + // delete all processes + purge(); delete d->meminfo; delete d; } diff --git a/library/DFProcessEnumerator.h b/library/DFProcessEnumerator.h index f369330e7..601ac7f41 100644 --- a/library/DFProcessEnumerator.h +++ b/library/DFProcessEnumerator.h @@ -48,6 +48,7 @@ namespace DFHack bool findProcessess(); uint32_t size(); Process * operator[](uint32_t index); + void purge(void); }; } #endif // PROCESSMANAGER_H_INCLUDED diff --git a/library/shmserver/df-hacked b/library/shmserver/df-hacked new file mode 100755 index 000000000..bf56b33e2 --- /dev/null +++ b/library/shmserver/df-hacked @@ -0,0 +1,14 @@ +#!/bin/sh +DF_DIR=$(dirname "$0") +cd "${DF_DIR}" +export SDL_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch. +#export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing. +ldd dwarfort.exe | grep SDL_image | grep -qv "not found$" +if [ $? -eq 0 ]; then + mkdir unused_libs + mv libs/libSDL* unused_libs/ +fi +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./libs" # Update library search path. +export LD_PRELOAD="./libs/dfconnect.so" # Hack DF! +./dwarfort.exe $* # Go, go, go! :) + diff --git a/library/shmserver/dfconnect.c b/library/shmserver/dfconnect.c index cfb93e0c7..655611250 100644 --- a/library/shmserver/dfconnect.c +++ b/library/shmserver/dfconnect.c @@ -25,8 +25,6 @@ distribution. /** * This is the source for the DF <-> dfhack shm bridge */ -#define _GNU_SOURCE - #include #include #include @@ -37,27 +35,77 @@ distribution. #include #include #include "dfconnect.h" +#include +#include +#include +#include + +// a full memory barrier! better be safe than sorry. +#define gcc_barrier asm volatile("" ::: "memory"); __sync_synchronize(); + +/* + * wait for futex + * futex has to be aligned to 4 bytes + * futex has to be equal to val (returns EWOULDBLOCK otherwise) + * wait can be broken by arriving signals (returns EINTR) + * returns 0 when broken by futex_wake + */ +inline int futex_wait(int * futex, int val) +{ + return syscall(SYS_futex, futex, FUTEX_WAIT, val, 0, 0, 0); +} +/* + * wait for futex + * futex has to be aligned to 4 bytes + * futex has to be equal to val (returns EWOULDBLOCK otherwise) + * wait can be broken by arriving signals (returns EINTR) + * returns 0 when broken by futex_wake + * returns ETIMEDOUT on timeout + */ +inline int futex_wait_timed(int * futex, int val, const struct timespec *timeout) +{ + return syscall(SYS_futex, futex, FUTEX_WAIT, val, timeout, 0, 0); +} +/* + * wake up futex. returns number of waked processes + */ +inline int futex_wake(int * futex) +{ + return syscall(SYS_futex, futex, FUTEX_WAKE, 1, 0, 0, 0); +} +static timespec one_second = { 1,0 }; +static timespec five_second = { 5,0 }; + // ptr to the real functions static void (*_SDL_GL_SwapBuffers)(void) = 0; static void (*_SDL_Quit)(void) = 0; static int (*_SDL_Init)(uint32_t flags) = 0; +static int (*_SDL_Flip)(void * some_ptr) = 0; // various crud int counter = 0; int errorstate = 0; char *shm; int shmid; -static unsigned char * BigFat; - void SHM_Init ( void ) { // name for the segment key_t key = 123466; - BigFat = (unsigned char *) malloc(SHM_SIZE); - // create the segment - if ((shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0600)) < 0) + // find previous segment, check if it's used by some processes. if it isn't, kill it with fire + if ((shmid = shmget(key, SHM_SIZE, 0600)) != -1) + { + shmid_ds descriptor; + shmctl(shmid, IPC_STAT, &descriptor); + if(descriptor.shm_nattch == 0) + { + shmctl(shmid,IPC_RMID,NULL); + fprintf(stderr,"dfhack: killed dangling resources from crashed DF.\n"); + } + } + // create the segment, make sure only ww are really creating it + if ((shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0600)) < 0) { perror("shmget"); errorstate = 1; @@ -71,30 +119,40 @@ void SHM_Init ( void ) errorstate = 1; return; } + gcc_barrier ((shm_cmd *)shm)->pingpong = DFPP_RUNNING; // make sure we don't stall or do crazy stuff } void SHM_Destroy ( void ) { - // blah, I don't care + shmid_ds descriptor; + shmctl(shmid, IPC_STAT, &descriptor); + shmdt(shm); + while(descriptor.shm_nattch != 0) + { + shmctl(shmid, IPC_STAT, &descriptor); + } + shmctl(shmid,IPC_RMID,NULL); + fprintf(stderr,"dfhack: destroyed shared segment.\n"); } void SHM_Act (void) { - struct shmid_ds descriptor; - uint32_t numwaits = 0; if(errorstate) { return; } + shmid_ds descriptor; + uint32_t numwaits = 0; uint32_t length; uint32_t address; check_again: // goto target!!! if(numwaits == 10000) { - shmctl(shmid, IPC_STAT, &descriptor); + shmctl(shmid, IPC_STAT, &descriptor); if(descriptor.shm_nattch == 1)// other guy crashed { + gcc_barrier ((shm_cmd *)shm)->pingpong = DFPP_RUNNING; fprintf(stderr,"dfhack: Broke out of loop, other process disappeared.\n"); return; @@ -117,23 +175,26 @@ void SHM_Act (void) numwaits++; goto check_again; case DFPP_SUSPEND: + gcc_barrier ((shm_cmd *)shm)->pingpong = DFPP_SUSPENDED; goto check_again; - + /* case DFPP_BOUNCE: length = ((shm_bounce *)shm)->length; memcpy(BigFat,shm + sizeof(shm_bounce),length); memcpy(shm + sizeof(shm_ret_data),BigFat,length); ((shm_cmd *)shm)->pingpong = DFPP_RET_DATA; goto check_again; - + */ case DFPP_PID: ((shm_retval *)shm)->value = getpid(); + gcc_barrier ((shm_retval *)shm)->pingpong = DFPP_RET_PID; goto check_again; case DFPP_VERSION: ((shm_retval *)shm)->value = PINGPONG_VERSION; + gcc_barrier ((shm_retval *)shm)->pingpong = DFPP_RET_VERSION; goto check_again; @@ -141,24 +202,28 @@ void SHM_Act (void) length = ((shm_read *)shm)->length; address = ((shm_read *)shm)->address; memcpy(shm + sizeof(shm_ret_data), (void *) address,length); + gcc_barrier ((shm_cmd *)shm)->pingpong = DFPP_RET_DATA; goto check_again; case DFPP_READ_DWORD: address = ((shm_read_small *)shm)->address; ((shm_retval *)shm)->value = *((uint32_t*) address); + gcc_barrier ((shm_retval *)shm)->pingpong = DFPP_RET_DWORD; goto check_again; case DFPP_READ_WORD: address = ((shm_read_small *)shm)->address; ((shm_retval *)shm)->value = *((uint16_t*) address); + gcc_barrier ((shm_retval *)shm)->pingpong = DFPP_RET_WORD; goto check_again; case DFPP_READ_BYTE: address = ((shm_read_small *)shm)->address; ((shm_retval *)shm)->value = *((uint8_t*) address); + gcc_barrier ((shm_retval *)shm)->pingpong = DFPP_RET_BYTE; goto check_again; @@ -166,21 +231,25 @@ void SHM_Act (void) address = ((shm_write *)shm)->address; length = ((shm_write *)shm)->length; memcpy((void *)address, shm + sizeof(shm_write),length); + gcc_barrier ((shm_cmd *)shm)->pingpong = DFPP_SUSPENDED; goto check_again; case DFPP_WRITE_DWORD: (*(uint32_t*)((shm_write_small *)shm)->address) = ((shm_write_small *)shm)->value; + gcc_barrier ((shm_cmd *)shm)->pingpong = DFPP_SUSPENDED; goto check_again; case DFPP_WRITE_WORD: (*(uint16_t*)((shm_write_small *)shm)->address) = ((shm_write_small *)shm)->value; + gcc_barrier ((shm_cmd *)shm)->pingpong = DFPP_SUSPENDED; goto check_again; case DFPP_WRITE_BYTE: (*(uint8_t*)((shm_write_small *)shm)->address) = ((shm_write_small *)shm)->value; + gcc_barrier ((shm_cmd *)shm)->pingpong = DFPP_SUSPENDED; goto check_again; @@ -191,12 +260,13 @@ void SHM_Act (void) default: ((shm_retval *)shm)->value = DFEE_INVALID_COMMAND; + gcc_barrier ((shm_retval *)shm)->pingpong = DFPP_SV_ERROR; break; } } -// hook - called every tick +// hook - called every tick in OpenGL mode of DF extern "C" void SDL_GL_SwapBuffers(void) { if(_SDL_GL_SwapBuffers) @@ -210,6 +280,20 @@ extern "C" void SDL_GL_SwapBuffers(void) } } +// hook - called every tick in the 2D mode of DF +extern "C" int SDL_Flip(void * some_ptr) +{ + if(_SDL_Flip) + { + if(((shm_cmd *)shm)->pingpong != DFPP_RUNNING) + { + SHM_Act(); + } + counter ++; + return _SDL_Flip(some_ptr); + } +} + // hook - called at program exit extern "C" void SDL_Quit(void) { @@ -217,7 +301,8 @@ extern "C" void SDL_Quit(void) { _SDL_Quit(); } - fprintf(stderr,"dfhack: DF called SwapBuffers %d times, lol\n", counter); + fprintf(stderr,"dfhack: DF called SwapBuffers %d times\n", counter); + SHM_Destroy(); } // hook - called at program start, initialize some stuffs we'll use later @@ -226,6 +311,7 @@ extern "C" int SDL_Init(uint32_t flags) // find real functions _SDL_GL_SwapBuffers = (void (*)( void )) dlsym(RTLD_NEXT, "SDL_GL_SwapBuffers"); _SDL_Init = (int (*)( uint32_t )) dlsym(RTLD_NEXT, "SDL_Init"); + _SDL_Flip = (int (*)( void * )) dlsym(RTLD_NEXT, "SDL_Flip"); _SDL_Quit = (void (*)( void )) dlsym(RTLD_NEXT, "SDL_Quit"); // check if we got them diff --git a/library/shmserver/dfconnect.h b/library/shmserver/dfconnect.h index a75b3ea09..a86e7f4cf 100644 --- a/library/shmserver/dfconnect.h +++ b/library/shmserver/dfconnect.h @@ -12,6 +12,14 @@ * end - sent to DF for breaking out of the wait */ +enum DF_SHM_ERRORSTATE +{ + SHM_OK, // all OK + SHM_CANT_GET_SHM, // getting the SHM ID failed for some reason + SHM_CANT_ATTACH, // we can't attach the shm for some reason + SHM_SECOND_DF // we are a second DF process, can't use SHM at all +}; + enum DF_PINGPONG { DFPP_RUNNING = 0, // no command, normal server execution diff --git a/library/shmserver/dfconnect.so b/library/shmserver/dfconnect.so new file mode 100755 index 000000000..17960daa8 Binary files /dev/null and b/library/shmserver/dfconnect.so differ diff --git a/library/shmserver/readme.txt b/library/shmserver/readme.txt index 16a1c6c7d..54ab2d6d4 100644 --- a/library/shmserver/readme.txt +++ b/library/shmserver/readme.txt @@ -9,3 +9,6 @@ export LD_PRELOAD="./libs/dfconnect.so" # Hack DF! save and run the script! Has to be compiled for 32bit arch, otherwise the library isn't recognised. Client can be any arch. + +Precompiled dfconnect library is made available. dfconnect.so goes in DF/libs, df-hacked script goes in DF/ +Run ./df-hacked to use the shared memory API \ No newline at end of file