From 9d503515dd3cbcd734a9a7a8f6622fe63f98fedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Fri, 12 Mar 2010 00:13:50 +0100 Subject: [PATCH 1/5] Another try at locking --- library/DFError.h | 55 ++++ library/DFHackAPI.cpp | 12 +- library/DFProcess-linux-SHM.cpp | 474 +++++++++++++++++------------- library/DFProcess-windows-SHM.cpp | 76 ++--- library/DFProcess.h | 24 +- shmserver/mod-core.cpp | 136 +++++---- shmserver/mod-core.h | 8 +- shmserver/mod-maps.cpp | 18 +- shmserver/mod-maps.h | 2 +- shmserver/shms-linux.cpp | 134 ++++++--- shmserver/shms-windows.cpp | 4 +- shmserver/shms.h | 26 +- 12 files changed, 599 insertions(+), 370 deletions(-) diff --git a/library/DFError.h b/library/DFError.h index f30c5e0c5..375a5c581 100644 --- a/library/DFError.h +++ b/library/DFError.h @@ -221,6 +221,61 @@ namespace DFHack return "The server process has disappeared"; } }; + class DFHACK_EXPORT SHMLockingError : public std::exception + { + public: + SHMLockingError(const char* _type) : type(_type) {} + virtual ~SHMLockingError() throw(){}; + + std::string type; + + virtual const char* what() const throw() + { + std::stringstream s; + s << "SHM locking error: " << type; + return s.str().c_str(); + } + }; + class DFHACK_EXPORT SHMAccessDenied : public std::exception + { + public: + SHMAccessDenied() {} + virtual ~SHMAccessDenied() throw(){}; + + std::string type; + + virtual const char* what() const throw() + { + return "SHM ACCESS DENIED"; + } + }; + class DFHACK_EXPORT SHMVersionMismatch : public std::exception + { + public: + SHMVersionMismatch() {} + virtual ~SHMVersionMismatch() throw(){}; + + std::string type; + + virtual const char* what() const throw() + { + return "SHM VERSION MISMATCH"; + } + }; + class DFHACK_EXPORT SHMAttachFailure : public std::exception + { + public: + SHMAttachFailure() {} + virtual ~SHMAttachFailure() throw(){}; + + std::string type; + + virtual const char* what() const throw() + { + return "SHM ATTACH FAILURE"; + } + }; + } } diff --git a/library/DFHackAPI.cpp b/library/DFHackAPI.cpp index 6e8f76cf5..f1f9b22e3 100644 --- a/library/DFHackAPI.cpp +++ b/library/DFHackAPI.cpp @@ -190,7 +190,7 @@ API::~API() delete d; } -#define SHMCMD ((shm_cmd *)d->shm_start)->pingpong +#define SHMCMD(num) ((shm_cmd *)d->shm_start)[num]->pingpong #define SHMHDR ((shm_core_hdr *)d->shm_start) #define SHMMAPSHDR ((Maps::shm_maps_hdr *)d->shm_start) #define SHMDATA(type) ((type *)(d->shm_start + SHM_HEADER)) @@ -243,8 +243,7 @@ bool API::InitMap() off->z_count_offset = z_count_offset; full_barrier const uint32_t cmd = Maps::MAP_INIT + d->maps_module << 16; - SHMCMD = cmd; - g_pProcess->waitWhile(cmd); + g_pProcess->SetAndWait(cmd); //cerr << "Map acceleration enabled!" << endl; } @@ -324,13 +323,8 @@ bool API::ReadBlock40d(uint32_t x, uint32_t y, uint32_t z, mapblock40d * buffer) SHMMAPSHDR->y = y; SHMMAPSHDR->z = z; volatile uint32_t cmd = Maps::MAP_READ_BLOCK_BY_COORDS + (d->maps_module << 16); - full_barrier - SHMCMD = cmd; - full_barrier - if(!g_pProcess->waitWhile(cmd)) - { + if(!g_pProcess->SetAndWait(cmd)) return false; - } memcpy(buffer,SHMDATA(mapblock40d),sizeof(mapblock40d)); return true; } diff --git a/library/DFProcess-linux-SHM.cpp b/library/DFProcess-linux-SHM.cpp index 89dc8a308..f8a83275d 100644 --- a/library/DFProcess-linux-SHM.cpp +++ b/library/DFProcess-linux-SHM.cpp @@ -43,27 +43,35 @@ class SHMProcess::Private public: Private() { - my_descriptor = NULL; - my_pid = 0; - my_shm = 0; - my_shmid = -1; - my_window = NULL; + memdescriptor = NULL; + process_ID = 0; + shm_addr = 0; + //shm_addr_with_cl_idx = 0; + shm_ID = -1; + window = NULL; attached = false; suspended = false; identified = false; useYield = false; - my_SVfileLock = -1; - my_CLfileLock = -1; + server_lock = -1; + client_lock = -1; + suspend_lock = -1; + attachmentIdx = 0; + locked = false; }; ~Private(){}; - memory_info * my_descriptor; - DFWindow * my_window; - pid_t my_pid; - char *my_shm; - int my_shmid; + memory_info * memdescriptor; + DFWindow * window; + pid_t process_ID; + char *shm_addr; + int shm_ID; Process* q; - int my_SVfileLock; - int my_CLfileLock; + int server_lock; + int client_lock; + int suspend_lock; + int attachmentIdx; + + bool locked; bool attached; bool suspended; @@ -73,60 +81,36 @@ class SHMProcess::Private bool validate(char* exe_file, uint32_t pid, std::vector< memory_info* >& known_versions); bool Aux_Core_Attach(bool & versionOK, pid_t & PID); - bool waitWhile (uint32_t state); + //bool waitWhile (uint32_t state); + bool SetAndWait (uint32_t state); bool GetLocks(); bool AreLocksOk(); void FreeLocks(); }; -// some helpful macros to keep the code bloat in check -#define SHMCMD ((shm_cmd *)my_shm)->pingpong -#define D_SHMCMD ((shm_cmd *)d->my_shm)->pingpong +#define SHMCMD ( (uint32_t *) shm_addr)[attachmentIdx] +#define D_SHMCMD ( (uint32_t *) (d->shm_addr))[d->attachmentIdx] -#define SHMHDR ((shm_core_hdr *)my_shm) -#define D_SHMHDR ((shm_core_hdr *)d->my_shm) +#define SHMHDR ((shm_core_hdr *)shm_addr) +#define D_SHMHDR ((shm_core_hdr *)(d->shm_addr)) -#define SHMDATA(type) ((type *)(my_shm + SHM_HEADER)) -#define D_SHMDATA(type) ((type *)(d->my_shm + SHM_HEADER)) +#define SHMDATA(type) ((type *)(shm_addr + SHM_HEADER)) +#define D_SHMDATA(type) ((type *)(d->shm_addr + SHM_HEADER)) -/* -Yeah. with no way to synchronize things (locks are slow, the OS doesn't give us enough control over scheduling) -we end up with this silly thing -*/ -bool SHMProcess::Private::waitWhile (uint32_t state) +bool SHMProcess::Private::SetAndWait (uint32_t state) { uint32_t cnt = 0; - struct shmid_ds descriptor; + if(!locked) return false; + SHMCMD = state; while (SHMCMD == state) { - if(cnt == 10000)// check if the other process is still there + if(cnt == 10000)// check if the other process is still there, don't hammer the kernel too much. { - - /* - shmctl(my_shmid, IPC_STAT, &descriptor); - if(descriptor.shm_nattch == 1)// DF crashed or exited - no way to tell? - { - //detach the shared memory - shmdt(my_shm); - attached = suspended = false; - - // we aren't the current process anymore - g_pProcess = NULL; - - throw Error::SHMServerDisappeared(); - return false; - } - else - { - cnt = 0; - } - */ if(!AreLocksOk()) { //detach the shared memory - shmdt(my_shm); - attached = suspended = false; - + shmdt(shm_addr); + attached = suspended = identified = false; // we aren't the current process anymore g_pProcess = NULL; FreeLocks(); @@ -145,12 +129,9 @@ bool SHMProcess::Private::waitWhile (uint32_t state) } cnt++; } + // server returned a generic error if(SHMCMD == CORE_ERROR) { - SHMCMD = CORE_RUNNING; - attached = suspended = false; - cerr << "shm server error!" << endl; - assert (false); return false; } return true; @@ -160,11 +141,19 @@ bool SHMProcess::Private::waitWhile (uint32_t state) Yeah. with no way to synchronize things (locks are slow, the OS doesn't give us enough control over scheduling) we end up with this silly thing */ -bool SHMProcess::waitWhile (uint32_t state) +bool SHMProcess::SetAndWait (uint32_t state) { - return d->waitWhile(state); + return d->SetAndWait(state); } +/* +// set SHM command. +void SHMProcess::setCmd (uint32_t newstate) +{ + if(d->attached && d->suspended) + D_SHMCMD = newstate; +}; +*/ uint32_t OS_getAffinity() { cpu_set_t mask; @@ -174,15 +163,16 @@ uint32_t OS_getAffinity() return affinity; } - bool SHMProcess::Private::Aux_Core_Attach(bool & versionOK, pid_t & PID) { + if(!locked) return false; + SHMDATA(coreattach)->cl_affinity = OS_getAffinity(); - gcc_barrier - SHMCMD = CORE_ATTACH; - if(!waitWhile(CORE_ATTACH)) - return false; - gcc_barrier + if(!SetAndWait(CORE_ATTACH)) return false; + /* + cerr <<"CORE_VERSION" << CORE_VERSION << endl; + cerr <<"server CORE_VERSION" << SHMDATA(coreattach)->sv_version << endl; + */ versionOK =( SHMDATA(coreattach)->sv_version == CORE_VERSION ); PID = SHMDATA(coreattach)->sv_PID; useYield = SHMDATA(coreattach)->sv_useYield; @@ -192,12 +182,13 @@ bool SHMProcess::Private::Aux_Core_Attach(bool & versionOK, pid_t & PID) return true; } +// test if we have client and server locks and the server is present bool SHMProcess::Private::AreLocksOk() { // both locks are inited (we hold our lock) - if(my_CLfileLock != -1 && my_SVfileLock != -1) + if(client_lock != -1 && server_lock != -1) { - if(lockf(my_SVfileLock,F_TEST,0) == -1) // and server holds its lock + if(lockf(server_lock,F_TEST,0) == -1) // and server holds its lock { return true; // OK, locks are good } @@ -208,16 +199,23 @@ bool SHMProcess::Private::AreLocksOk() void SHMProcess::Private::FreeLocks() { - if(my_CLfileLock != -1) + attachmentIdx = -1; + if(client_lock != -1) + { + lockf(client_lock,F_ULOCK,0); + close(client_lock); + client_lock = -1; + } + if(server_lock != -1) { - lockf(my_CLfileLock,F_ULOCK,0); - close(my_CLfileLock); - my_CLfileLock = -1; + close(server_lock); + server_lock = -1; } - if(my_SVfileLock != -1) + if(suspend_lock != -1) { - close(my_SVfileLock); - my_SVfileLock = -1; + close(suspend_lock); + locked = false; + suspend_lock = -1; } } @@ -226,36 +224,65 @@ bool SHMProcess::Private::GetLocks() char name[256]; // try to acquire locks // look at the server lock, if it's locked, the server is present - sprintf(name, "/tmp/DFHack/%d/SVlock",my_pid,name); - my_SVfileLock = open(name,O_WRONLY); - if(my_SVfileLock == -1) + sprintf(name, "/tmp/DFHack/%d/SVlock",process_ID); + server_lock = open(name,O_WRONLY); + if(server_lock == -1) { + cerr << "can't open sv lock" << endl; return false; } - if(lockf( my_SVfileLock, F_TEST, 0 ) != -1) + if(lockf( server_lock, F_TEST, 0 ) != -1) { - close(my_SVfileLock); + cerr << "sv lock not locked" << endl; + close(server_lock); + server_lock = -1; return false; } - // open the client lock, try to lock it - sprintf(name, "/tmp/DFHack/%d/CLlock",my_pid,name); - my_CLfileLock = open(name,O_WRONLY); - if(my_CLfileLock == -1) - { - close(my_SVfileLock); - return false; - } - if(lockf(my_CLfileLock,F_TLOCK, 0) == -1) + for(int i = 0; i < SHM_MAX_CLIENTS; i++) { - // couldn't acquire lock - close(my_SVfileLock); - close(my_CLfileLock); - return false; + // open the client suspend locked + sprintf(name, "/tmp/DFHack/%d/CLSlock%d",process_ID,i); + suspend_lock = open(name,O_WRONLY); + if(suspend_lock == -1) + { + cerr << "can't open cl S-lock " << i << endl; + // couldn't open lock + continue; + } + + // open the client lock, try to lock it + sprintf(name, "/tmp/DFHack/%d/CLlock%d",process_ID,i); + client_lock = open(name,O_WRONLY); + if(client_lock == -1) + { + cerr << "can't open cl lock " << i << endl; + close(suspend_lock); + locked = false; + suspend_lock = -1; + // couldn't open lock + continue; + } + if(lockf(client_lock,F_TLOCK, 0) == -1) + { + // couldn't acquire lock + cerr << "can't acquire cl lock " << i << endl; + close(suspend_lock); + locked = false; + suspend_lock = -1; + close(client_lock); + client_lock = -1; + continue; + } + // ok, we have all the locks we need! + attachmentIdx = i; + return true; } - // ok, we have all the locks! - return true; + close(server_lock); + server_lock = -1; + cerr << "can't get any client locks" << endl; + return false; } SHMProcess::SHMProcess(uint32_t PID, vector< memory_info* >& known_versions) @@ -268,7 +295,7 @@ SHMProcess::SHMProcess(uint32_t PID, vector< memory_info* >& known_versions) /* * Locate the segment. */ - if ((d->my_shmid = shmget(SHM_KEY + PID, SHM_SIZE, 0666)) < 0) + if ((d->shm_ID = shmget(SHM_KEY + PID, /*SHM_ALL_CLIENTS*/SHM_SIZE, 0666)) < 0) { return; } @@ -276,68 +303,54 @@ SHMProcess::SHMProcess(uint32_t PID, vector< memory_info* >& known_versions) /* * Attach the segment */ - if ((d->my_shm = (char *) shmat(d->my_shmid, NULL, 0)) == (char *) -1) + /* + if ((d->shm_addr = (char *) shmat(d->shm_ID, NULL, 0)) == (char *) -1) { return; } - - // set pid and gets lock for it - d->my_pid = PID; - if(!d->GetLocks()) + */ + d->process_ID = PID; + if(!attach()) { - fprintf(stderr,"Couldn't get locks for PID %d'\n", PID); - shmdt(d->my_shm); + // couldn't attach to process return; } - /* * Test bridge version, get PID, sync Yield */ bool bridgeOK; - if(!d->Aux_Core_Attach(bridgeOK,d->my_pid)) + if(!d->Aux_Core_Attach(bridgeOK,d->process_ID)) { - fprintf(stderr,"DF terminated during reading\n"); - shmdt(d->my_shm); - // free locks - d->FreeLocks(); + detach(); + throw Error::SHMAttachFailure(); return; } if(!bridgeOK) { - fprintf(stderr,"SHM bridge version mismatch\n"); - shmdt(d->my_shm); - // free locks - d->FreeLocks(); + detach(); + throw Error::SHMVersionMismatch(); return; } // find the binary - sprintf(exe_link_name,"/proc/%d/exe", d->my_pid); + sprintf(exe_link_name,"/proc/%d/exe", d->process_ID); target_result = readlink(exe_link_name, target_name, sizeof(target_name)-1); if (target_result == -1) { perror("readlink"); - shmdt(d->my_shm); - // free locks - d->FreeLocks(); + detach(); return; } - // make sure we have a null terminated string... // see http://www.opengroup.org/onlinepubs/000095399/functions/readlink.html target_name[target_result] = 0; - // try to identify the DF version - 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 - D_SHMCMD = CORE_RUNNING; - shmdt(d->my_shm); // detach so we don't attach twice when attach() is called + // try to identify the DF version (md5 the binary, compare with known versions) + d->validate(target_name, d->process_ID, known_versions); + d->window = new DFWindow(this); - // free locks - d->FreeLocks(); + // detach + detach(); } bool SHMProcess::isSuspended() @@ -360,7 +373,7 @@ bool SHMProcess::Private::validate(char * exe_file, uint32_t pid, vector ::iterator it; - cerr << exe_file << " " << hash << endl; + // cerr << exe_file << " " << hash << endl; // iterate over the list of memory locations for ( it=known_versions.begin() ; it < known_versions.end(); it++ ) { @@ -368,10 +381,10 @@ bool SHMProcess::Private::validate(char * exe_file, uint32_t pid, vector getString("md5")) // are the md5 hashes the same? { memory_info * m = *it; - my_descriptor = m; - my_pid = pid; + memdescriptor = m; + process_ID = pid; identified = true; - cerr << "identified " << m->getVersion() << endl; + // cerr << "identified " << m->getVersion() << endl; return true; } } @@ -390,26 +403,26 @@ SHMProcess::~SHMProcess() detach(); } // destroy data model. this is assigned by processmanager - if(d->my_window) + if(d->window) { - delete d->my_window; + delete d->window; } delete d; } memory_info * SHMProcess::getDescriptor() { - return d->my_descriptor; + return d->memdescriptor; } DFWindow * SHMProcess::getWindow() { - return d->my_window; + return d->window; } int SHMProcess::getPID() { - return d->my_pid; + return d->process_ID; } //FIXME: implement @@ -424,7 +437,7 @@ void SHMProcess::getMemRanges( vector & ranges ) char buffer[1024]; char permissions[5]; // r/-, w/-, x/-, p/s, 0 - sprintf(buffer, "/proc/%lu/maps", d->my_pid); + sprintf(buffer, "/proc/%lu/maps", d->process_ID); FILE *mapFile = ::fopen(buffer, "r"); uint64_t offset, device1, device2, node; @@ -455,13 +468,27 @@ bool SHMProcess::suspend() { return true; } - D_SHMCMD = CORE_SUSPEND; - if(!waitWhile(CORE_SUSPEND)) + + // did we just resume a moment ago? + if(D_SHMCMD == CORE_RUN) { - return false; + //fprintf(stderr,"%d invokes step\n",d->attachmentIdx); + D_SHMCMD = CORE_STEP; } - d->suspended = true; - return true; + else + { + //fprintf(stderr,"%d invokes suspend\n",d->attachmentIdx); + D_SHMCMD = CORE_SUSPEND; + } + //fprintf(stderr,"waiting for lock\n"); + // we wait for the server to give up our suspend lock (held by default) + if(lockf(d->suspend_lock,F_LOCK,0) == 0) + { + d->suspended = true; + d->locked = true; + return true; + } + return false; } bool SHMProcess::asyncSuspend() @@ -474,14 +501,37 @@ bool SHMProcess::asyncSuspend() { return true; } + if(D_SHMCMD == CORE_SUSPENDED) { - d->suspended = true; - return true; + // we have to hold the lock to be really suspended + if(lockf(d->suspend_lock,F_LOCK,0) == 0) + { + d->locked = true; + d->suspended = true; + return true; + } + return false; } else { - D_SHMCMD = CORE_SUSPEND; + // did we just resume a moment ago? + if(D_SHMCMD == CORE_RUN) + { + D_SHMCMD = CORE_STEP; + } + else + { + D_SHMCMD = CORE_SUSPEND; + } + // try locking + if(lockf(d->suspend_lock,F_TLOCK,0) == 0) + { + d->locked = true; + d->suspended = true; + return true; + } + return false; } } @@ -491,21 +541,29 @@ bool SHMProcess::forceresume() return resume(); } +// FIXME: wait for the server to advance a step! bool SHMProcess::resume() { if(!d->attached) return false; if(!d->suspended) return true; - D_SHMCMD = CORE_RUNNING; + // set core to run + D_SHMCMD = CORE_RUN; d->suspended = false; - return true; + // unlock the suspend lock + if(lockf(d->suspend_lock,F_ULOCK,0) == 0) + { + d->locked = false; + return true; + } + throw Error::SHMLockingError("bool SHMProcess::resume()"); + return false; } bool SHMProcess::attach() { - int status; if(g_pProcess != 0) { // FIXME: throw exception here - programmer error @@ -521,7 +579,7 @@ bool SHMProcess::attach() /* * Attach the segment */ - if ((d->my_shm = (char *) shmat(d->my_shmid, NULL, 0)) != (char *) -1) + if ((d->shm_addr = (char *) shmat(d->shm_ID, NULL, 0)) != (char *) -1) { d->attached = true; if(suspend()) @@ -532,7 +590,7 @@ bool SHMProcess::attach() } d->attached = false; cerr << "unable to suspend" << endl; - shmdt(d->my_shm); + shmdt(d->shm_addr); d->FreeLocks(); return false; } @@ -552,11 +610,11 @@ bool SHMProcess::detach() resume(); } // detach segment - if(shmdt(d->my_shm) != -1) + if(shmdt(d->shm_addr) != -1) { d->attached = false; d->suspended = false; - d->my_shm = 0; + d->shm_addr = 0; g_pProcess = 0; d->FreeLocks(); return true; @@ -569,15 +627,16 @@ bool SHMProcess::detach() void SHMProcess::read (uint32_t src_address, uint32_t size, uint8_t *target_buffer) { + if(!d->locked) throw Error::SHMAccessDenied(); + // normal read under 1MB if(size <= SHM_BODY) { D_SHMHDR->address = src_address; D_SHMHDR->length = size; gcc_barrier - D_SHMCMD = CORE_DFPP_READ; - waitWhile(CORE_DFPP_READ); - memcpy (target_buffer, d->my_shm + SHM_HEADER,size); + d->SetAndWait(CORE_READ); + memcpy (target_buffer, D_SHMDATA(void),size); } // a big read, we pull data over the shm in iterations else @@ -590,9 +649,8 @@ void SHMProcess::read (uint32_t src_address, uint32_t size, uint8_t *target_buff D_SHMHDR->address = src_address; D_SHMHDR->length = to_read; gcc_barrier - D_SHMCMD = CORE_DFPP_READ; - waitWhile(CORE_DFPP_READ); - memcpy (target_buffer, d->my_shm + SHM_HEADER,size); + d->SetAndWait(CORE_READ); + memcpy (target_buffer, D_SHMDATA(void) ,size); // decrease size by bytes read size -= to_read; // move the cursors @@ -606,54 +664,60 @@ void SHMProcess::read (uint32_t src_address, uint32_t size, uint8_t *target_buff uint8_t SHMProcess::readByte (const uint32_t offset) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; gcc_barrier - D_SHMCMD = CORE_READ_BYTE; - waitWhile(CORE_READ_BYTE); + d->SetAndWait(CORE_READ_BYTE); return D_SHMHDR->value; } void SHMProcess::readByte (const uint32_t offset, uint8_t &val ) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; gcc_barrier - D_SHMCMD = CORE_READ_BYTE; - waitWhile(CORE_READ_BYTE); + d->SetAndWait(CORE_READ_BYTE); val = D_SHMHDR->value; } uint16_t SHMProcess::readWord (const uint32_t offset) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; gcc_barrier - D_SHMCMD = CORE_READ_WORD; - waitWhile(CORE_READ_WORD); + d->SetAndWait(CORE_READ_WORD); return D_SHMHDR->value; } void SHMProcess::readWord (const uint32_t offset, uint16_t &val) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; gcc_barrier - D_SHMCMD = CORE_READ_WORD; - waitWhile(CORE_READ_WORD); + d->SetAndWait(CORE_READ_WORD); val = D_SHMHDR->value; } uint32_t SHMProcess::readDWord (const uint32_t offset) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; gcc_barrier - D_SHMCMD = CORE_READ_DWORD; - waitWhile(CORE_READ_DWORD); + d->SetAndWait(CORE_READ_DWORD); return D_SHMHDR->value; } void SHMProcess::readDWord (const uint32_t offset, uint32_t &val) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; gcc_barrier - D_SHMCMD = CORE_READ_DWORD; - waitWhile(CORE_READ_DWORD); + d->SetAndWait(CORE_READ_DWORD); val = D_SHMHDR->value; } @@ -663,43 +727,47 @@ void SHMProcess::readDWord (const uint32_t offset, uint32_t &val) void SHMProcess::writeDWord (uint32_t offset, uint32_t data) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; D_SHMHDR->value = data; gcc_barrier - D_SHMCMD = CORE_WRITE_DWORD; - waitWhile(CORE_WRITE_DWORD); + d->SetAndWait(CORE_WRITE_DWORD); } // using these is expensive. void SHMProcess::writeWord (uint32_t offset, uint16_t data) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; D_SHMHDR->value = data; gcc_barrier - D_SHMCMD = CORE_WRITE_WORD; - waitWhile(CORE_WRITE_WORD); + d->SetAndWait(CORE_WRITE_WORD); } void SHMProcess::writeByte (uint32_t offset, uint8_t data) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; D_SHMHDR->value = data; gcc_barrier - D_SHMCMD = CORE_WRITE_BYTE; - waitWhile(CORE_WRITE_BYTE); + d->SetAndWait(CORE_WRITE_BYTE); } void SHMProcess::write (uint32_t dst_address, uint32_t size, uint8_t *source_buffer) { + if(!d->locked) throw Error::SHMAccessDenied(); + // normal write under 1MB if(size <= SHM_BODY) { D_SHMHDR->address = dst_address; D_SHMHDR->length = size; - memcpy(d->my_shm+SHM_HEADER,source_buffer, size); + memcpy(D_SHMDATA(void),source_buffer, size); gcc_barrier - D_SHMCMD = CORE_WRITE; - waitWhile(CORE_WRITE); + d->SetAndWait(CORE_WRITE); } // a big write, we push this over the shm in iterations else @@ -711,10 +779,9 @@ void SHMProcess::write (uint32_t dst_address, uint32_t size, uint8_t *source_buf // write to_write bytes to dst_cursor D_SHMHDR->address = dst_address; D_SHMHDR->length = to_write; - memcpy(d->my_shm+SHM_HEADER,source_buffer, to_write); + memcpy(D_SHMDATA(void),source_buffer, to_write); gcc_barrier - D_SHMCMD = CORE_WRITE; - waitWhile(CORE_WRITE); + d->SetAndWait(CORE_WRITE); // decrease size by bytes written size -= to_write; // move the cursors @@ -729,6 +796,8 @@ void SHMProcess::write (uint32_t dst_address, uint32_t size, uint8_t *source_buf // FIXME: butt-fugly const std::string SHMProcess::readCString (uint32_t offset) { + if(!d->locked) throw Error::SHMAccessDenied(); + std::string temp; char temp_c[256]; int counter = 0; @@ -746,6 +815,8 @@ const std::string SHMProcess::readCString (uint32_t offset) DfVector SHMProcess::readVector (uint32_t offset, uint32_t item_size) { + if(!d->locked) throw Error::SHMAccessDenied(); + /* GNU libstdc++ vector is three pointers long ptr start @@ -762,38 +833,43 @@ DfVector SHMProcess::readVector (uint32_t offset, uint32_t item_size) const std::string SHMProcess::readSTLString(uint32_t offset) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; full_barrier - D_SHMCMD = CORE_READ_STL_STRING; - waitWhile(CORE_READ_STL_STRING); + d->SetAndWait(CORE_READ_STL_STRING); //int length = ((shm_retval *)d->my_shm)->value; - return(string( (char *)d->my_shm+SHM_HEADER)); + return(string( D_SHMDATA(char) )); } size_t SHMProcess::readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; full_barrier - D_SHMCMD = CORE_READ_STL_STRING; - waitWhile(CORE_READ_STL_STRING); + d->SetAndWait(CORE_READ_STL_STRING); size_t length = D_SHMHDR->value; size_t fit = min(bufcapacity - 1, length); - strncpy(buffer,(char *)d->my_shm+SHM_HEADER,fit); + strncpy(buffer,D_SHMDATA(char),fit); buffer[fit] = 0; return fit; } void SHMProcess::writeSTLString(const uint32_t address, const std::string writeString) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = address; - strncpy(d->my_shm+SHM_HEADER,writeString.c_str(),writeString.length()+1); // length + 1 for the null terminator + strncpy(D_SHMDATA(char),writeString.c_str(),writeString.length()+1); // length + 1 for the null terminator full_barrier - D_SHMCMD = CORE_WRITE_STL_STRING; - waitWhile(CORE_WRITE_STL_STRING); + d->SetAndWait(CORE_WRITE_STL_STRING); } string SHMProcess::readClassName (uint32_t vptr) { + if(!d->locked) throw Error::SHMAccessDenied(); + int typeinfo = readDWord(vptr - 0x4); int typestring = readDWord(typeinfo + 0x4); string raw = readCString(typestring); @@ -802,22 +878,18 @@ string SHMProcess::readClassName (uint32_t vptr) return raw.substr(start,end-start - 2); // trim the 'st' from the end } -// FIXME: having this around could lead to bad things in the hands of unsuspecting fools -// *!!DON'T BE AN UNSUSPECTING FOOL!!* -// the whole SHM thing works only because copying DWORDS is an atomic operation on i386 and x86_64 archs -// get module index by name and version. bool 1 = error +// get module index by name and version. bool 0 = error bool SHMProcess::getModuleIndex (const char * name, const uint32_t version, uint32_t & OUTPUT) { - modulelookup * payload = (modulelookup *) (d->my_shm + SHM_HEADER); + if(!d->locked) throw Error::SHMAccessDenied(); + + modulelookup * payload = D_SHMDATA(modulelookup); payload->version = version; strncpy(payload->name,name,255); payload->name[255] = 0; - full_barrier - - D_SHMCMD = CORE_ACQUIRE_MODULE; - if(!waitWhile(CORE_ACQUIRE_MODULE)) + if(!SetAndWait(CORE_ACQUIRE_MODULE)) { return false; // FIXME: throw a fatal exception instead } @@ -832,5 +904,7 @@ bool SHMProcess::getModuleIndex (const char * name, const uint32_t version, uint char * SHMProcess::getSHMStart (void) { - return d->my_shm; + if(!d->locked) return 0; //THROW HERE! + + return /*d->shm_addr_with_cl_idx*/ d->shm_addr; } \ No newline at end of file diff --git a/library/DFProcess-windows-SHM.cpp b/library/DFProcess-windows-SHM.cpp index 8886c0e7c..115dc5180 100644 --- a/library/DFProcess-windows-SHM.cpp +++ b/library/DFProcess-windows-SHM.cpp @@ -32,10 +32,10 @@ class SHMProcess::Private public: Private() { - my_descriptor = NULL; - my_pid = 0; - my_shm = 0; - my_window = NULL; + memdescriptor = NULL; + process_ID = 0; + shm_addr = 0; + window = NULL; attached = false; suspended = false; identified = false; @@ -44,10 +44,10 @@ class SHMProcess::Private DFCLMutex = 0; }; ~Private(){}; - memory_info * my_descriptor; - DFWindow * my_window; - uint32_t my_pid; - char *my_shm; + memory_info * memdescriptor; + DFWindow * window; + uint32_t process_ID; + char *shm_addr; HANDLE DFSVMutex; HANDLE DFCLMutex; @@ -120,7 +120,7 @@ bool SHMProcess::Private::waitWhile (uint32_t state) { attached = suspended = false; ReleaseMutex(DFCLMutex); - UnmapViewOfFile(my_shm); + UnmapViewOfFile(shm_addr); throw Error::SHMServerDisappeared(); return false; } @@ -185,7 +185,7 @@ SHMProcess::SHMProcess(uint32_t PID, vector & known_versions) { return; } - d->my_pid = PID; + d->process_ID = PID; // attach the SHM if(!attach()) @@ -196,7 +196,7 @@ SHMProcess::SHMProcess(uint32_t PID, vector & known_versions) // Test bridge version, get PID, sync Yield bool bridgeOK; bool error = 0; - if(!d->Aux_Core_Attach(bridgeOK,d->my_pid)) + if(!d->Aux_Core_Attach(bridgeOK,d->process_ID)) { fprintf(stderr,"DF terminated during reading\n"); error = 1; @@ -209,7 +209,7 @@ SHMProcess::SHMProcess(uint32_t PID, vector & known_versions) if(error) { D_SHMCMD = CORE_RUNNING; - UnmapViewOfFile(d->my_shm); + UnmapViewOfFile(d->shm_addr); ReleaseMutex(d->DFCLMutex); CloseHandle(d->DFSVMutex); d->DFSVMutex = 0; @@ -229,7 +229,7 @@ SHMProcess::SHMProcess(uint32_t PID, vector & known_versions) bool found = false; d->identified = false; // open process, we only need the process open - hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, d->my_pid ); + hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, d->process_ID ); if (NULL == hProcess) break; @@ -265,7 +265,7 @@ SHMProcess::SHMProcess(uint32_t PID, vector & known_versions) { memory_info *m = new memory_info(**it); m->RebaseAll(base); - d->my_descriptor = m; + d->memdescriptor = m; d->identified = true; cerr << "identified " << m->getVersion() << endl; break; @@ -276,13 +276,13 @@ SHMProcess::SHMProcess(uint32_t PID, vector & known_versions) if(d->identified) { - d->my_window = new DFWindow(this); + d->window = new DFWindow(this); } else { D_SHMCMD = CORE_RUNNING; - UnmapViewOfFile(d->my_shm); - d->my_shm = 0; + UnmapViewOfFile(d->shm_addr); + d->shm_addr = 0; ReleaseMutex(d->DFCLMutex); CloseHandle(d->DFSVMutex); d->DFSVMutex = 0; @@ -316,13 +316,13 @@ SHMProcess::~SHMProcess() detach(); } // destroy data model. this is assigned by processmanager - if(d->my_descriptor) + if(d->memdescriptor) { - delete d->my_descriptor; + delete d->memdescriptor; } - if(d->my_window) + if(d->window) { - delete d->my_window; + delete d->window; } // release mutex handles we have if(d->DFCLMutex) @@ -338,17 +338,17 @@ SHMProcess::~SHMProcess() memory_info * SHMProcess::getDescriptor() { - return d->my_descriptor; + return d->memdescriptor; } DFWindow * SHMProcess::getWindow() { - return d->my_window; + return d->window; } int SHMProcess::getPID() { - return d->my_pid; + return d->process_ID; } //FIXME: implement @@ -363,7 +363,7 @@ void SHMProcess::getMemRanges( vector & ranges ) char buffer[1024]; char permissions[5]; // r/-, w/-, x/-, p/s, 0 - sprintf(buffer, "/proc/%lu/maps", d->my_pid); + sprintf(buffer, "/proc/%lu/maps", d->process_ID); FILE *mapFile = ::fopen(buffer, "r"); uint64_t offset, device1, device2, node; @@ -476,7 +476,7 @@ bool SHMProcess::attach() } char shmname [256]; - sprintf(shmname,"DFShm-%d",d->my_pid); + sprintf(shmname,"DFShm-%d",d->process_ID); // now try getting and attaching the shared memory HANDLE shmHandle = OpenFileMapping(FILE_MAP_ALL_ACCESS,false,shmname); @@ -487,8 +487,8 @@ bool SHMProcess::attach() } // attempt to attach the opened mapping - d->my_shm = (char *) MapViewOfFile(shmHandle,FILE_MAP_ALL_ACCESS, 0,0, SHM_SIZE); - if(!d->my_shm) + d->shm_addr = (char *) MapViewOfFile(shmHandle,FILE_MAP_ALL_ACCESS, 0,0, SHM_ALL_CLIENTS); + if(!d->shm_addr) { CloseHandle(shmHandle); ReleaseMutex(d->DFCLMutex); @@ -509,7 +509,7 @@ bool SHMProcess::detach() return false; } // detach segment - UnmapViewOfFile(d->my_shm); + UnmapViewOfFile(d->shm_addr); // release it for some other client ReleaseMutex(d->DFCLMutex); // we keep the mutex handles d->attached = false; @@ -528,7 +528,7 @@ void SHMProcess::read (uint32_t src_address, uint32_t size, uint8_t *target_buff full_barrier D_SHMCMD = CORE_DFPP_READ; d->waitWhile(CORE_DFPP_READ); - memcpy (target_buffer, d->my_shm + SHM_HEADER,size); + memcpy (target_buffer, d->shm_addr + SHM_HEADER,size); } // a big read, we pull data over the shm in iterations else @@ -543,7 +543,7 @@ void SHMProcess::read (uint32_t src_address, uint32_t size, uint8_t *target_buff full_barrier D_SHMCMD = CORE_DFPP_READ; d->waitWhile(CORE_DFPP_READ); - memcpy (target_buffer, d->my_shm + SHM_HEADER,size); + memcpy (target_buffer, d->shm_addr + SHM_HEADER,size); // decrease size by bytes read size -= to_read; // move the cursors @@ -647,7 +647,7 @@ void SHMProcess::write (uint32_t dst_address, uint32_t size, uint8_t *source_buf { D_SHMHDR->address = dst_address; D_SHMHDR->length = size; - memcpy(d->my_shm+SHM_HEADER,source_buffer, size); + memcpy(d->shm_addr+SHM_HEADER,source_buffer, size); full_barrier D_SHMCMD = CORE_WRITE; d->waitWhile(CORE_WRITE); @@ -662,7 +662,7 @@ void SHMProcess::write (uint32_t dst_address, uint32_t size, uint8_t *source_buf // write to_write bytes to dst_cursor D_SHMHDR->address = dst_address; D_SHMHDR->length = to_write; - memcpy(d->my_shm+SHM_HEADER,source_buffer, to_write); + memcpy(d->shm_addr+SHM_HEADER,source_buffer, to_write); full_barrier D_SHMCMD = CORE_WRITE; d->waitWhile(CORE_WRITE); @@ -723,7 +723,7 @@ const std::string SHMProcess::readSTLString(uint32_t offset) int length = D_SHMHDR->value; // char temp_c[256]; // strncpy(temp_c, d->my_shm+SHM_HEADER,length+1); // length + 1 for the null terminator - return(string(d->my_shm+SHM_HEADER)); + return(string(d->shm_addr+SHM_HEADER)); } size_t SHMProcess::readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) @@ -735,7 +735,7 @@ size_t SHMProcess::readSTLString (uint32_t offset, char * buffer, size_t bufcapa d->waitWhile(CORE_READ_STL_STRING); size_t length = D_SHMHDR->value; size_t real = min(length, bufcapacity - 1); - strncpy(buffer, d->my_shm+SHM_HEADER,real); // length + 1 for the null terminator + strncpy(buffer, d->shm_addr+SHM_HEADER,real); // length + 1 for the null terminator buffer[real] = 0; return real; } @@ -743,7 +743,7 @@ size_t SHMProcess::readSTLString (uint32_t offset, char * buffer, size_t bufcapa void SHMProcess::writeSTLString(const uint32_t address, const std::string writeString) { D_SHMHDR->address = address/*-4*/; - strncpy(d->my_shm+SHM_HEADER,writeString.c_str(),writeString.length()+1); // length + 1 for the null terminator + strncpy(d->shm_addr+SHM_HEADER,writeString.c_str(),writeString.length()+1); // length + 1 for the null terminator full_barrier D_SHMCMD = CORE_WRITE_STL_STRING; d->waitWhile(CORE_WRITE_STL_STRING); @@ -761,7 +761,7 @@ string SHMProcess::readClassName (uint32_t vptr) // get module index by name and version. bool 1 = error bool SHMProcess::getModuleIndex (const char * name, const uint32_t version, uint32_t & OUTPUT) { - modulelookup * payload = (modulelookup *) (d->my_shm + SHM_HEADER); + modulelookup * payload = (modulelookup *) (d->shm_addr + SHM_HEADER); payload->version = version; strcpy(payload->name,name); full_barrier @@ -774,5 +774,5 @@ bool SHMProcess::getModuleIndex (const char * name, const uint32_t version, uint char * SHMProcess::getSHMStart (void) { - return d->my_shm; + return d->shm_addr; } \ No newline at end of file diff --git a/library/DFProcess.h b/library/DFProcess.h index 878e68d37..3a023ca2d 100644 --- a/library/DFProcess.h +++ b/library/DFProcess.h @@ -123,8 +123,14 @@ namespace DFHack virtual bool getModuleIndex (const char * name, const uint32_t version, uint32_t & OUTPUT) = 0; // get the SHM start if available virtual char * getSHMStart (void) = 0; - // wait for a SHM state. returns 0 without the SHM + // set a SHM command and wait for a response, return 0 on error or throw exception + virtual bool SetAndWait (uint32_t state) = 0; + /* + // wait while SHM command == state. returns 0 without the SHM virtual bool waitWhile (uint32_t state) = 0; + // set SHM command. + virtual void setCmd (uint32_t newstate) = 0; + */ }; class DFHACK_EXPORT NormalProcess : virtual public Process @@ -181,8 +187,14 @@ namespace DFHack bool getModuleIndex (const char * name, const uint32_t version, uint32_t & OUTPUT) {return false;}; // get the SHM start if available char * getSHMStart (void){return 0;}; + // set a SHM command and wait for a response + bool SetAndWait (uint32_t state){return false;}; + /* // wait for a SHM state. returns 0 without the SHM bool waitWhile (uint32_t state){return false;}; + // set SHM command. + void setCmd (uint32_t newstate){}; + */ }; class DFHACK_EXPORT SHMProcess : virtual public Process @@ -240,8 +252,13 @@ namespace DFHack bool getModuleIndex (const char * name, const uint32_t version, uint32_t & OUTPUT); // get the SHM start if available char * getSHMStart (void); + bool SetAndWait (uint32_t state); + /* // wait for a SHM state. returns 0 without the SHM bool waitWhile (uint32_t state); + // set SHM command. + void setCmd (uint32_t newstate); + */ }; #ifdef LINUX_BUILD @@ -299,8 +316,13 @@ namespace DFHack bool getModuleIndex (const char * name, const uint32_t version, uint32_t & OUTPUT) {return false;}; // get the SHM start if available char * getSHMStart (void){return 0;}; + bool SetAndWait (uint32_t state){return false;}; + /* // wait for a SHM state. returns 0 without the SHM bool waitWhile (uint32_t state){return false;}; + // set SHM command. + void setCmd (uint32_t newstate){}; + */ }; #endif } diff --git a/shmserver/mod-core.cpp b/shmserver/mod-core.cpp index cd3137d62..7b5f0a1ab 100644 --- a/shmserver/mod-core.cpp +++ b/shmserver/mod-core.cpp @@ -41,19 +41,22 @@ distribution. std::vector module_registry; -// various crud +// shared by shms_OS extern int errorstate; extern char *shm; extern int shmid; + +// file-globals bool useYield = 0; +int currentClient = -1; #define SHMHDR ((shm_core_hdr *)shm) -#define SHMCMD ((shm_cmd *)shm)->pingpong +#define SHMCMDPP ((shm_core_hdr *) shm)->cmd[currentClient].pingpong #define SHMDATA(type) ((type *)(shm + SHM_HEADER)) void ReadRaw (void * data) { - memcpy(shm + SHM_HEADER, (void *) SHMHDR->address,SHMHDR->length); + memcpy(SHMDATA(void), (void *) SHMHDR->address,SHMHDR->length); } void ReadDWord (void * data) @@ -73,7 +76,7 @@ void ReadByte (void * data) void WriteRaw (void * data) { - memcpy((void *)SHMHDR->address, shm + SHM_HEADER,SHMHDR->length); + memcpy((void *)SHMHDR->address, SHMDATA(void),SHMHDR->length); } void WriteDWord (void * data) @@ -97,14 +100,14 @@ void ReadSTLString (void * data) unsigned int l = myStringPtr->length(); SHMHDR->value = l; // FIXME: there doesn't have to be a null terminator! - strncpy(shm+SHM_HEADER,myStringPtr->c_str(),l+1); + strncpy( SHMDATA(char),myStringPtr->c_str(),l+1); } void WriteSTLString (void * data) { std::string * myStringPtr = (std::string *) SHMHDR->address; // here we DO expect a 0 terminator - myStringPtr->assign((const char *) (shm + SHM_HEADER)); + myStringPtr->assign( SHMDATA(const char) ); } // MIT HAKMEM bitcount @@ -133,7 +136,7 @@ void CoreAttach (void * data) void FindModule (void * data) { bool found = false; - modulelookup * payload = (modulelookup *) (shm + SHM_HEADER); + modulelookup * payload = SHMDATA(modulelookup); std::string test = payload->name; uint32_t version = payload->version; for(unsigned int i = 0; i < module_registry.size();i++) @@ -175,6 +178,11 @@ void FindCommand (void * data) SHMHDR->error = true; } +void ReleaseSuspendLock( void * data ) +{ + OS_releaseSuspendLock(currentClient); +} + DFPP_module InitCore(void) { DFPP_module core; @@ -185,7 +193,9 @@ DFPP_module InitCore(void) core.reserve(NUM_CORE_CMDS); // basic states core.set_command(CORE_RUNNING, CANCELLATION, "Running"); - core.set_command(CORE_SUSPEND, CLIENT_WAIT, "Suspend", 0 , CORE_SUSPENDED); + core.set_command(CORE_RUN, FUNCTION, "Run!",0,CORE_RUNNING); + core.set_command(CORE_STEP, CANCELLATION, "Suspend on next step",0,CORE_SUSPEND);// set command to CORE_SUSPEND, check next client + core.set_command(CORE_SUSPEND, FUNCTION, "Suspend", ReleaseSuspendLock , CORE_SUSPENDED); core.set_command(CORE_SUSPENDED, CLIENT_WAIT, "Suspended"); core.set_command(CORE_ERROR, CANCELLATION, "Error"); @@ -195,7 +205,7 @@ DFPP_module InitCore(void) core.set_command(CORE_ACQUIRE_COMMAND, FUNCTION, "Command lookup", FindCommand, CORE_SUSPENDED); // raw reads - core.set_command(CORE_DFPP_READ, FUNCTION,"Raw read",ReadRaw, CORE_SUSPENDED); + core.set_command(CORE_READ, FUNCTION,"Raw read",ReadRaw, CORE_SUSPENDED); core.set_command(CORE_READ_DWORD, FUNCTION,"Read DWORD",ReadDWord, CORE_SUSPENDED); core.set_command(CORE_READ_WORD, FUNCTION,"Read WORD",ReadWord, CORE_SUSPENDED); core.set_command(CORE_READ_BYTE, FUNCTION,"Read BYTE",ReadByte, CORE_SUSPENDED); @@ -233,60 +243,84 @@ void KillModules (void) void SHM_Act (void) { + volatile uint32_t atomic = 0; if(errorstate) { return; } - uint32_t numwaits = 0; - check_again: // goto target!!! - if(numwaits == 10000) + //static uint oldcl = 88; + for(currentClient = 0; currentClient < SHM_MAX_CLIENTS;currentClient++) { - // this tests if there's a process on the other side - if(isValidSHM()) + // set the offset for the shared memory used for the client + uint32_t numwaits = 0; + check_again: // goto target!!! + if(numwaits == 10000) { - numwaits = 0; + // this tests if there's a process on the other side + if(isValidSHM(currentClient)) + { + numwaits = 0; + } + else + { + full_barrier + SHMCMDPP = CORE_RUNNING; + fprintf(stderr,"dfhack: Broke out of loop, other process disappeared.\n"); + } } - else + full_barrier // I don't want the compiler to reorder my code. + + + //fprintf(stderr,"%d: %x %x\n",currentClient, (uint) SHMHDR, (uint) &(SHMHDR->cmd[currentClient])); + + // this is very important! copying two words separately from the command variable leads to inconsistency. + // Always copy the thing in one go. + // Also, this whole SHM thing probably only works on intel processors + atomic = *(uint32_t *) (shm + 4*currentClient); //SHMHDR->cmd[currentClient]; + full_barrier + + DFPP_module & mod = module_registry[ ((shm_cmd)atomic).parts.module ]; + DFPP_command & cmd = mod.commands[ ((shm_cmd)atomic).parts.command ]; + /* + if(atomic == CORE_RUNNING) { - full_barrier - SHMCMD = CORE_RUNNING; - fprintf(stderr,"dfhack: Broke out of loop, other process disappeared.\n"); + // we are running again for this process + // reaquire the suspend lock + OS_lockSuspendLock(currentClient); + continue; } - } - - // this is very important! copying two words separately from the command variable leads to inconsistency. - // Always copy the thing in one go. - // Also, this whole SHM thing probably only works on intel processors - - volatile shm_cmd atomic = SHMHDR->cmd; - full_barrier - DFPP_module & mod = module_registry[atomic.parts.module]; - DFPP_command & cmd = mod.commands[atomic.parts.command]; - full_barrier - /* - fprintf(stderr, "Called %x\0", cmd._function); - fprintf(stderr, "Client invoked %d:%d = ",atomic.parts.module,atomic.parts.command); - fprintf(stderr, "%s\n",cmd.name.c_str()); - */ - full_barrier - if(cmd._function) - { - cmd._function(mod.modulestate); - } full_barrier - if(cmd.nextState != -1) - { - SHMCMD = cmd.nextState; - } + */ + if(cmd._function) + { + cmd._function(mod.modulestate); + } full_barrier - if(cmd.type != CANCELLATION) - { - if(useYield) + + if(cmd.nextState != -1) { - SCHED_YIELD + fprintf(stderr, "Client %d invoked %d:%d = %x = ", + currentClient,((shm_cmd)atomic).parts.module,((shm_cmd)atomic).parts.command, cmd._function); + fprintf(stderr, "%s\n",cmd.name.c_str()); + // FIXME: WHAT HAPPENS WHEN A 'NEXTSTATE' IS FROM A DIFFERENT MODULE THAN 'CORE'? Yeah. It doesn't work. + SHMCMDPP = cmd.nextState; + fprintf(stderr, "Server set %d\n",cmd.nextState); } - numwaits ++; // watchdog timeout - goto check_again; + full_barrier + + if(cmd.type != CANCELLATION) + { + if(useYield) + { + SCHED_YIELD + } + numwaits ++; // watchdog timeout + goto check_again; + } + full_barrier + + // we are running again for this process + // reaquire the suspend lock + OS_lockSuspendLock(currentClient); } } - diff --git a/shmserver/mod-core.h b/shmserver/mod-core.h index 37717cb6f..ad0765cb1 100644 --- a/shmserver/mod-core.h +++ b/shmserver/mod-core.h @@ -26,11 +26,11 @@ distribution. #define SHMS_CORE_H // increment on every core change -#define CORE_VERSION 7 +#define CORE_VERSION 8 typedef struct { - shm_cmd cmd; + shm_cmd cmd[SHM_MAX_CLIENTS]; // MANDATORY! uint32_t address; uint32_t value; uint32_t length; @@ -62,6 +62,8 @@ enum CORE_COMMAND { // basic states CORE_RUNNING = 0, // no command, normal server execution + CORE_RUN, // sent by the client to restart the server execution + CORE_STEP, // client suspend sets step CORE_SUSPEND, // client notifies server to wait for commands (server is stalled in busy wait) CORE_SUSPENDED, // response to WAIT, server is stalled in busy wait CORE_ERROR, // there was a server error @@ -72,7 +74,7 @@ enum CORE_COMMAND CORE_ACQUIRE_COMMAND, // get module::command callsign by module name, command name and module version // raw reads - CORE_DFPP_READ, // cl -> sv, read some data + CORE_READ, // cl -> sv, read some data CORE_READ_DWORD, // cl -> sv, read a dword CORE_READ_WORD, // cl -> sv, read a word CORE_READ_BYTE, // cl -> sv, read a byte diff --git a/shmserver/mod-maps.cpp b/shmserver/mod-maps.cpp index 54fe1cb57..f10629cdc 100644 --- a/shmserver/mod-maps.cpp +++ b/shmserver/mod-maps.cpp @@ -19,7 +19,7 @@ extern char *shm; #define SHMHDR ((shm_maps_hdr *)shm) #define SHMCMD ((shm_cmd *)shm)->pingpong -#define SHMDATA ((char *)(shm + SHM_HEADER)) +#define SHMDATA(type) ((type *)(shm + SHM_HEADER)) void NullCommand (void* data) { @@ -28,7 +28,7 @@ void NullCommand (void* data) void InitOffsets (void* data) { maps_modulestate * state = (maps_modulestate *) data; - memcpy((void *) &(state->offsets), SHMDATA, sizeof(maps_offsets)); + memcpy((void *) &(state->offsets), SHMDATA(void), sizeof(maps_offsets)); ((maps_modulestate *) data)->inited = true; } @@ -53,8 +53,6 @@ struct mblock uint32_t * ptr_to_dirty; }; -#define SHMBLOCK ((mapblock40d *)(shm + SHM_HEADER)) - inline void ReadBlockByAddress (void * data) { maps_modulestate * state = (maps_modulestate *) data; @@ -62,13 +60,13 @@ inline void ReadBlockByAddress (void * data) mblock * block = (mblock *) SHMHDR->address; if(block) { - memcpy(&(SHMBLOCK->tiletypes), ((char *) block) + offsets.tile_type_offset, sizeof(SHMBLOCK->tiletypes)); - memcpy(&(SHMBLOCK->designaton), ((char *) block) + offsets.designation_offset, sizeof(SHMBLOCK->designaton)); - memcpy(&(SHMBLOCK->occupancy), ((char *) block) + offsets.occupancy_offset, sizeof(SHMBLOCK->occupancy)); - memcpy(&(SHMBLOCK->biome_indices), ((char *) block) + offsets.biome_stuffs, sizeof(SHMBLOCK->biome_indices)); - SHMBLOCK->dirty_dword = *block->ptr_to_dirty; + memcpy(&(SHMDATA(mapblock40d)->tiletypes), ((char *) block) + offsets.tile_type_offset, sizeof(SHMDATA(mapblock40d)->tiletypes)); + memcpy(&(SHMDATA(mapblock40d)->designaton), ((char *) block) + offsets.designation_offset, sizeof(SHMDATA(mapblock40d)->designaton)); + memcpy(&(SHMDATA(mapblock40d)->occupancy), ((char *) block) + offsets.occupancy_offset, sizeof(SHMDATA(mapblock40d)->occupancy)); + memcpy(&(SHMDATA(mapblock40d)->biome_indices), ((char *) block) + offsets.biome_stuffs, sizeof(SHMDATA(mapblock40d)->biome_indices)); + SHMDATA(mapblock40d)->dirty_dword = *block->ptr_to_dirty; - SHMBLOCK->origin = (uint32_t)block; + SHMDATA(mapblock40d)->origin = (uint32_t)block; SHMHDR->error = false; } else diff --git a/shmserver/mod-maps.h b/shmserver/mod-maps.h index 5ff5f0f2e..e4c4ff664 100644 --- a/shmserver/mod-maps.h +++ b/shmserver/mod-maps.h @@ -71,7 +71,7 @@ typedef struct typedef struct { - shm_cmd cmd; + shm_cmd cmd[SHM_MAX_CLIENTS]; // MANDATORY! uint32_t x; uint32_t y; uint32_t z; diff --git a/shmserver/shms-linux.cpp b/shmserver/shms-linux.cpp index e79fef3bb..3e79753bf 100644 --- a/shmserver/shms-linux.cpp +++ b/shmserver/shms-linux.cpp @@ -51,38 +51,27 @@ char *shm = 0; int shmid = 0; bool inited = 0; -int fd_svlock = 0; -int fd_cllock = 0; - +int fd_svlock = -1; +int fd_cllock[SHM_MAX_CLIENTS]; +int fd_clSlock[SHM_MAX_CLIENTS]; +int held_clSlock[SHM_MAX_CLIENTS]; +int numheld = SHM_MAX_CLIENTS; /******************************************************************************* * SHM part starts here * *******************************************************************************/ -/* -// FIXME: add error checking? -bool isValidSHM() -{ - shmid_ds descriptor; - shmctl(shmid, IPC_STAT, &descriptor); - //fprintf(stderr,"ID %d, attached: %d\n",shmid, descriptor.shm_nattch); - return (descriptor.shm_nattch == 2); -} -*/ - // is the other side still there? -bool isValidSHM() +bool isValidSHM(int which) { - // try if CL lock file is free - int result = lockf(fd_cllock,F_TEST,0); - /* - F_TEST: Test the lock: - return 0 if the specified section is unlocked or locked by this process; - return -1, set errno to EAGAIN (EACCES on some other systems), if another process holds a lock. - */ - - // if nobody holds the lock, the SHM isn't valid. file locks are unlocked when the owner closes or crashes. - if(result == 0) return false; - + // if we get the client lock here, the client process failed and we need to reclaim our suspend lock + if(lockf(fd_cllock[which],F_TLOCK,0) == 0) + { + // we get back our suspend lock from the cold, dead hands of the former client :P + OS_lockSuspendLock(which); + // free the client lock again + lockf(fd_cllock[which],F_ULOCK,0); + return false; + } return true; } @@ -100,6 +89,45 @@ uint32_t OS_getAffinity() return affinity; } +void OS_lockSuspendLock(int which) +{ + if(numheld == SHM_MAX_CLIENTS) + return; + // lock not held by server and can be picked up. OK. + if(held_clSlock[which] == 0 && lockf(fd_clSlock[which],F_LOCK,0) == 0) + { + held_clSlock[which] = 1; + numheld++; + } + // lock couldn't be picked up! + else if (held_clSlock[which] == 0) + { + fprintf(stderr,"lock %d failed to lock\n", which); + } +} + +void OS_releaseSuspendLock(int which) +{ + /* + if(which >=0 && which < SHM_MAX_CLIENTS) + return; + */ + if(numheld != SHM_MAX_CLIENTS) + { + fprintf(stderr,"TOTAL FAILURE OF LOCKING SYSTEM\n"); + return; + } + // lock hel by server and can be released -> OK + if(held_clSlock[which] == 1 && lockf(fd_clSlock[which],F_ULOCK,0) == 0) + { + numheld--; + held_clSlock[which] = 0; + } + // locked and not can't be released? FAIL! + else if (held_clSlock[which] == 1) + fprintf(stderr,"lock %d failed to unlock\n", which); +} + void SHM_Init ( void ) { // check that we do this only once per process @@ -111,7 +139,6 @@ void SHM_Init ( void ) inited = true; char name[256]; char name2[256]; - // make folder structure for our lock files sprintf(name, "/tmp/DFHack/%d",OS_getPID()); mode_t createmode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH; @@ -123,9 +150,17 @@ void SHM_Init ( void ) fd_svlock = open(name2,O_WRONLY | O_CREAT, createmode); lockf( fd_svlock, F_LOCK, 0 ); - // create the client lock file - sprintf(name2, "%s/CLlock",name); - fd_cllock = open(name2,O_WRONLY | O_CREAT, createmode); + for(int i = 0; i < SHM_MAX_CLIENTS; i++) + { + // create the client lock file + sprintf(name2, "%s/CLlock%d",name,i); + fd_cllock[i] = open(name2,O_WRONLY | O_CREAT, createmode); + // get and lock the suspend locks + sprintf(name2, "%s/CLSlock%d",name,i); + fd_clSlock[i] = open(name2,O_WRONLY | O_CREAT, createmode); + lockf(fd_clSlock[i],F_LOCK,0); + held_clSlock[i] = 1; + } // name for the segment, an accident waiting to happen key_t key = SHM_KEY + OS_getPID(); @@ -159,7 +194,10 @@ void SHM_Init ( void ) } full_barrier // make sure we don't stall or do crazy stuff - ((shm_cmd *)shm)->pingpong = CORE_RUNNING; + for(int i = 0; i < SHM_MAX_CLIENTS;i++) + { + ((shm_cmd *)shm)[i].pingpong = CORE_RUNNING; + } InitModules(); } @@ -177,21 +215,29 @@ void SHM_Destroy ( void ) } shmctl(shmid,IPC_RMID,NULL); - // unlock and close server lock, close client lock - lockf(fd_svlock,F_ULOCK,0); - close(fd_svlock); - close(fd_cllock); - fd_svlock = 0; - fd_cllock = 0; - - // destroy lock files char name[256]; char name2[256]; sprintf(name, "/tmp/DFHack/%d",OS_getPID()); + + // unlock and close server lock, close client lock, destroy files + lockf(fd_svlock,F_ULOCK,0); + for(int i = 0; i < SHM_MAX_CLIENTS; i++) + { + close(fd_cllock[i]); + fd_cllock[i] = 0; + close(fd_clSlock[i]); + fd_clSlock[i] = 0; + held_clSlock[i] = 0; + sprintf(name2, "%s/CLlock%d",name,i); + unlink(name2); + sprintf(name2, "%s/CLSlock%d",name,i); + unlink(name2); + } + close(fd_svlock); + fd_svlock = 0; sprintf(name2, "%s/SVlock",name); unlink(name2); - sprintf(name2, "%s/CLlock",name); - unlink(name2); + // remove the PID folder rmdir(name); fprintf(stderr,"dfhack: destroyed shared segment.\n"); inited = false; @@ -213,7 +259,7 @@ DFhackCExport void SDL_GL_SwapBuffers(void) { if(_SDL_GL_SwapBuffers) { - if(!errorstate && ((shm_cmd *)shm)->pingpong != CORE_RUNNING) + if(!errorstate) { SHM_Act(); } @@ -227,7 +273,7 @@ DFhackCExport int SDL_Flip(void * some_ptr) { if(_SDL_Flip) { - if(!errorstate && ((shm_cmd *)shm)->pingpong != CORE_RUNNING) + if(!errorstate) { SHM_Act(); } @@ -285,7 +331,7 @@ DFhackCExport int refresh (void) { if(_refresh) { - if(!errorstate && ((shm_cmd *)shm)->pingpong != CORE_RUNNING) + if(!errorstate) { SHM_Act(); } diff --git a/shmserver/shms-windows.cpp b/shmserver/shms-windows.cpp index 9581ad68f..ee4fff0d5 100644 --- a/shmserver/shms-windows.cpp +++ b/shmserver/shms-windows.cpp @@ -117,7 +117,7 @@ void SHM_Init ( void ) } // create virtual memory mapping - shmHandle = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,SHM_SIZE,shmname); + shmHandle = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,SHM_ALL_CLIENTS,shmname); // if can't create or already exists -> nothing happens if(GetLastError() == ERROR_ALREADY_EXISTS) { @@ -138,7 +138,7 @@ void SHM_Init ( void ) return; } // attempt to attach the created mapping - shm = (char *) MapViewOfFile(shmHandle,FILE_MAP_ALL_ACCESS, 0,0, SHM_SIZE); + shm = (char *) MapViewOfFile(shmHandle,FILE_MAP_ALL_ACCESS, 0,0, SHM_ALL_CLIENTS); if(shm) { ((shm_cmd *)shm)->pingpong = CORE_RUNNING; diff --git a/shmserver/shms.h b/shmserver/shms.h index 174614c8e..48fa2af5d 100644 --- a/shmserver/shms.h +++ b/shmserver/shms.h @@ -2,10 +2,12 @@ #define DFCONNECT_H #define SHM_KEY 123466 +#define SHM_MAX_CLIENTS 4 #define SHM_HEADER 1024 // 1kB reserved for a header -#define SHM_BODY 1024*1024 // 1MB reserved for bulk data transfer +#define SHM_BODY 1024*1024 // 4MB reserved for bulk data transfer #define SHM_SIZE SHM_HEADER+SHM_BODY - +//#define SHM_ALL_CLIENTS SHM_MAX_CLIENTS*(SHM_SIZE) +//#define SHM_CL(client_idx) client_idx*(SHM_SIZE) // FIXME: add YIELD for linux, add single-core and multi-core compile targets for optimal speed #ifdef LINUX_BUILD @@ -79,26 +81,28 @@ struct DFPP_module void * modulestate; }; -typedef union +union shm_cmd { struct - { - volatile uint16_t command; - volatile uint16_t module; - } parts; + { + volatile uint16_t command; + volatile uint16_t module; + } parts; volatile uint32_t pingpong; - inline void set(uint16_t module, uint16_t command) + shm_cmd(volatile uint32_t z) { - pingpong = module + command << 16; + pingpong = z; } -} shm_cmd; +}; void SHM_Act (void); void InitModules (void); void KillModules (void); -bool isValidSHM(); +bool isValidSHM(int current); uint32_t OS_getPID(); DFPP_module InitMaps(void); uint32_t OS_getAffinity(); // limited to 32 processors. Silly, eh? +void OS_releaseSuspendLock(int currentClient); +void OS_lockSuspendLock(int currentClient); #endif From 0dd14bb9c864d1acb94f9463f7c310c3351acee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Fri, 12 Mar 2010 12:14:20 +0100 Subject: [PATCH 2/5] Cosmetic changes, really --- library/DFProcess-linux-SHM.cpp | 30 ++++++++++++++++-------------- shmserver/mod-core.cpp | 8 ++++---- shmserver/shms-linux.cpp | 2 +- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/library/DFProcess-linux-SHM.cpp b/library/DFProcess-linux-SHM.cpp index f8a83275d..c1f8b954a 100644 --- a/library/DFProcess-linux-SHM.cpp +++ b/library/DFProcess-linux-SHM.cpp @@ -215,6 +215,7 @@ void SHMProcess::Private::FreeLocks() { close(suspend_lock); locked = false; + suspended = false; suspend_lock = -1; } } @@ -327,8 +328,9 @@ SHMProcess::SHMProcess(uint32_t PID, vector< memory_info* >& known_versions) } if(!bridgeOK) { - detach(); throw Error::SHMVersionMismatch(); + detach(); + return; } @@ -473,11 +475,13 @@ bool SHMProcess::suspend() if(D_SHMCMD == CORE_RUN) { //fprintf(stderr,"%d invokes step\n",d->attachmentIdx); + // wait for the next window D_SHMCMD = CORE_STEP; } else { //fprintf(stderr,"%d invokes suspend\n",d->attachmentIdx); + // lock now D_SHMCMD = CORE_SUSPEND; } //fprintf(stderr,"waiting for lock\n"); @@ -579,24 +583,22 @@ bool SHMProcess::attach() /* * Attach the segment */ - if ((d->shm_addr = (char *) shmat(d->shm_ID, NULL, 0)) != (char *) -1) + if ((d->shm_addr = (char *) shmat(d->shm_ID, NULL, 0)) == (char *) -1) + { + d->FreeLocks(); + cerr << "can't attach segment" << endl; + return false; + } + d->attached = true; + if(!suspend()) { - d->attached = true; - if(suspend()) - { - d->suspended = true; - g_pProcess = this; - return true; - } - d->attached = false; - cerr << "unable to suspend" << endl; shmdt(d->shm_addr); d->FreeLocks(); + cerr << "unable to suspend" << endl; return false; } - cerr << "unable to attach" << endl; - d->FreeLocks(); - return false; + g_pProcess = this; + return true; } bool SHMProcess::detach() diff --git a/shmserver/mod-core.cpp b/shmserver/mod-core.cpp index 7b5f0a1ab..3bc27b12e 100644 --- a/shmserver/mod-core.cpp +++ b/shmserver/mod-core.cpp @@ -51,7 +51,7 @@ bool useYield = 0; int currentClient = -1; #define SHMHDR ((shm_core_hdr *)shm) -#define SHMCMDPP ((shm_core_hdr *) shm)->cmd[currentClient].pingpong +#define SHMCMD ((uint32_t *)shm )[currentClient] #define SHMDATA(type) ((type *)(shm + SHM_HEADER)) void ReadRaw (void * data) @@ -264,7 +264,7 @@ void SHM_Act (void) else { full_barrier - SHMCMDPP = CORE_RUNNING; + SHMCMD = CORE_RUNNING; fprintf(stderr,"dfhack: Broke out of loop, other process disappeared.\n"); } } @@ -276,7 +276,7 @@ void SHM_Act (void) // this is very important! copying two words separately from the command variable leads to inconsistency. // Always copy the thing in one go. // Also, this whole SHM thing probably only works on intel processors - atomic = *(uint32_t *) (shm + 4*currentClient); //SHMHDR->cmd[currentClient]; + atomic = SHMCMD; full_barrier DFPP_module & mod = module_registry[ ((shm_cmd)atomic).parts.module ]; @@ -303,7 +303,7 @@ void SHM_Act (void) currentClient,((shm_cmd)atomic).parts.module,((shm_cmd)atomic).parts.command, cmd._function); fprintf(stderr, "%s\n",cmd.name.c_str()); // FIXME: WHAT HAPPENS WHEN A 'NEXTSTATE' IS FROM A DIFFERENT MODULE THAN 'CORE'? Yeah. It doesn't work. - SHMCMDPP = cmd.nextState; + SHMCMD = cmd.nextState; fprintf(stderr, "Server set %d\n",cmd.nextState); } full_barrier diff --git a/shmserver/shms-linux.cpp b/shmserver/shms-linux.cpp index 3e79753bf..aa157c1be 100644 --- a/shmserver/shms-linux.cpp +++ b/shmserver/shms-linux.cpp @@ -196,7 +196,7 @@ void SHM_Init ( void ) // make sure we don't stall or do crazy stuff for(int i = 0; i < SHM_MAX_CLIENTS;i++) { - ((shm_cmd *)shm)[i].pingpong = CORE_RUNNING; + ((uint32_t *)shm)[i] = CORE_RUNNING; } InitModules(); } From 834a64c28287f115aa26407a5462f5d3e2048fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Fri, 12 Mar 2010 18:29:11 +0100 Subject: [PATCH 3/5] Fix horrible race conditions in suspend and resume --- library/DFProcess-linux-SHM.cpp | 27 +++++++++++++++++++-------- shmserver/mod-core.cpp | 5 +++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/library/DFProcess-linux-SHM.cpp b/library/DFProcess-linux-SHM.cpp index c1f8b954a..3f9b0e6d0 100644 --- a/library/DFProcess-linux-SHM.cpp +++ b/library/DFProcess-linux-SHM.cpp @@ -100,7 +100,7 @@ class SHMProcess::Private bool SHMProcess::Private::SetAndWait (uint32_t state) { uint32_t cnt = 0; - if(!locked) return false; + if(!attached) return false; SHMCMD = state; while (SHMCMD == state) { @@ -471,18 +471,26 @@ bool SHMProcess::suspend() return true; } + // FIXME: this should be controlled on the server side + // FIXME: IF server got CORE_RUN in this frame, interpret CORE_SUSPEND as CORE_STEP // did we just resume a moment ago? if(D_SHMCMD == CORE_RUN) { //fprintf(stderr,"%d invokes step\n",d->attachmentIdx); // wait for the next window - D_SHMCMD = CORE_STEP; + if(!d->SetAndWait(CORE_STEP)) + { + throw Error::SHMLockingError("if(!d->SetAndWait(CORE_STEP))"); + } } else { //fprintf(stderr,"%d invokes suspend\n",d->attachmentIdx); // lock now - D_SHMCMD = CORE_SUSPEND; + if(!d->SetAndWait(CORE_SUSPEND)) + { + throw Error::SHMLockingError("if(!d->SetAndWait(CORE_SUSPEND))"); + } } //fprintf(stderr,"waiting for lock\n"); // we wait for the server to give up our suspend lock (held by default) @@ -495,6 +503,7 @@ bool SHMProcess::suspend() return false; } +// FIXME: needs a good think-through bool SHMProcess::asyncSuspend() { if(!d->attached) @@ -552,16 +561,18 @@ bool SHMProcess::resume() return false; if(!d->suspended) return true; - // set core to run - D_SHMCMD = CORE_RUN; - d->suspended = false; // unlock the suspend lock if(lockf(d->suspend_lock,F_ULOCK,0) == 0) { + d->suspended = false; d->locked = false; - return true; + if(d->SetAndWait(CORE_RUN)) // we have to make sure the server responds! + { + return true; + } + throw Error::SHMLockingError("if(d->SetAndWait(CORE_RUN))"); } - throw Error::SHMLockingError("bool SHMProcess::resume()"); + throw Error::SHMLockingError("if(lockf(d->suspend_lock,F_ULOCK,0) == 0)"); return false; } diff --git a/shmserver/mod-core.cpp b/shmserver/mod-core.cpp index 3bc27b12e..76fd9dfe9 100644 --- a/shmserver/mod-core.cpp +++ b/shmserver/mod-core.cpp @@ -299,12 +299,17 @@ void SHM_Act (void) if(cmd.nextState != -1) { + /* fprintf(stderr, "Client %d invoked %d:%d = %x = ", currentClient,((shm_cmd)atomic).parts.module,((shm_cmd)atomic).parts.command, cmd._function); fprintf(stderr, "%s\n",cmd.name.c_str()); + */ // FIXME: WHAT HAPPENS WHEN A 'NEXTSTATE' IS FROM A DIFFERENT MODULE THAN 'CORE'? Yeah. It doesn't work. SHMCMD = cmd.nextState; + /* fprintf(stderr, "Server set %d\n",cmd.nextState); + fflush(stderr); // make sure this finds its way to the terminal! + */ } full_barrier From 310669737e5a8a6ab688493e0a54d6022baa334b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 13 Mar 2010 17:44:36 +0100 Subject: [PATCH 4/5] Port multiple client SHM to Windows --- library/DFProcess-linux-SHM.cpp | 181 +++----- library/DFProcess-windows-SHM.cpp | 748 ++++++++++++++++++------------ shmserver/mod-core.cpp | 24 +- shmserver/shms-windows.cpp | 167 ++++--- 4 files changed, 661 insertions(+), 459 deletions(-) diff --git a/library/DFProcess-linux-SHM.cpp b/library/DFProcess-linux-SHM.cpp index 3f9b0e6d0..3ca1bdebc 100644 --- a/library/DFProcess-linux-SHM.cpp +++ b/library/DFProcess-linux-SHM.cpp @@ -50,7 +50,6 @@ class SHMProcess::Private shm_ID = -1; window = NULL; attached = false; - suspended = false; identified = false; useYield = false; server_lock = -1; @@ -71,14 +70,14 @@ class SHMProcess::Private int suspend_lock; int attachmentIdx; - bool locked; + bool attached; - bool suspended; + bool locked; bool identified; bool useYield; - bool validate(char* exe_file, uint32_t pid, std::vector< memory_info* >& known_versions); + bool validate(std::vector< memory_info* >& known_versions); bool Aux_Core_Attach(bool & versionOK, pid_t & PID); //bool waitWhile (uint32_t state); @@ -110,13 +109,11 @@ bool SHMProcess::Private::SetAndWait (uint32_t state) { //detach the shared memory shmdt(shm_addr); - attached = suspended = identified = false; + FreeLocks(); + attached = locked = identified = false; // we aren't the current process anymore g_pProcess = NULL; - FreeLocks(); - throw Error::SHMServerDisappeared(); - return false; } else { @@ -146,14 +143,6 @@ bool SHMProcess::SetAndWait (uint32_t state) return d->SetAndWait(state); } -/* -// set SHM command. -void SHMProcess::setCmd (uint32_t newstate) -{ - if(d->attached && d->suspended) - D_SHMCMD = newstate; -}; -*/ uint32_t OS_getAffinity() { cpu_set_t mask; @@ -163,25 +152,6 @@ uint32_t OS_getAffinity() return affinity; } -bool SHMProcess::Private::Aux_Core_Attach(bool & versionOK, pid_t & PID) -{ - if(!locked) return false; - - SHMDATA(coreattach)->cl_affinity = OS_getAffinity(); - if(!SetAndWait(CORE_ATTACH)) return false; - /* - cerr <<"CORE_VERSION" << CORE_VERSION << endl; - cerr <<"server CORE_VERSION" << SHMDATA(coreattach)->sv_version << endl; - */ - versionOK =( SHMDATA(coreattach)->sv_version == CORE_VERSION ); - PID = SHMDATA(coreattach)->sv_PID; - useYield = SHMDATA(coreattach)->sv_useYield; - #ifdef DEBUG - if(useYield) cerr << "Using Yield!" << endl; - #endif - return true; -} - // test if we have client and server locks and the server is present bool SHMProcess::Private::AreLocksOk() { @@ -215,7 +185,6 @@ void SHMProcess::Private::FreeLocks() { close(suspend_lock); locked = false; - suspended = false; suspend_lock = -1; } } @@ -229,7 +198,7 @@ bool SHMProcess::Private::GetLocks() server_lock = open(name,O_WRONLY); if(server_lock == -1) { - cerr << "can't open sv lock" << endl; + // cerr << "can't open sv lock" << endl; return false; } @@ -289,27 +258,6 @@ bool SHMProcess::Private::GetLocks() SHMProcess::SHMProcess(uint32_t PID, vector< memory_info* >& known_versions) : d(new Private()) { - char exe_link_name [256]; - char target_name[1024]; - int target_result; - - /* - * Locate the segment. - */ - if ((d->shm_ID = shmget(SHM_KEY + PID, /*SHM_ALL_CLIENTS*/SHM_SIZE, 0666)) < 0) - { - return; - } - - /* - * Attach the segment - */ - /* - if ((d->shm_addr = (char *) shmat(d->shm_ID, NULL, 0)) == (char *) -1) - { - return; - } - */ d->process_ID = PID; if(!attach()) { @@ -324,31 +272,15 @@ SHMProcess::SHMProcess(uint32_t PID, vector< memory_info* >& known_versions) { detach(); throw Error::SHMAttachFailure(); - return; } if(!bridgeOK) { - throw Error::SHMVersionMismatch(); - detach(); - - return; - } - - // find the binary - sprintf(exe_link_name,"/proc/%d/exe", d->process_ID); - target_result = readlink(exe_link_name, target_name, sizeof(target_name)-1); - if (target_result == -1) - { - perror("readlink"); detach(); - return; + throw Error::SHMVersionMismatch(); } - // make sure we have a null terminated string... - // see http://www.opengroup.org/onlinepubs/000095399/functions/readlink.html - target_name[target_result] = 0; // try to identify the DF version (md5 the binary, compare with known versions) - d->validate(target_name, d->process_ID, known_versions); + d->validate(known_versions); d->window = new DFWindow(this); // detach @@ -357,7 +289,7 @@ SHMProcess::SHMProcess(uint32_t PID, vector< memory_info* >& known_versions) bool SHMProcess::isSuspended() { - return d->suspended; + return d->locked; } bool SHMProcess::isAttached() { @@ -369,11 +301,26 @@ bool SHMProcess::isIdentified() return d->identified; } -bool SHMProcess::Private::validate(char * exe_file, uint32_t pid, vector & known_versions) +bool SHMProcess::Private::validate(vector & known_versions) { + char exe_link_name [256]; + char target_name[1024]; + int target_result; + // find the binary + sprintf(exe_link_name,"/proc/%d/exe", process_ID); + target_result = readlink(exe_link_name, target_name, sizeof(target_name)-1); + if (target_result == -1) + { + perror("readlink"); + return false; + } + // make sure we have a null terminated string... + // see http://www.opengroup.org/onlinepubs/000095399/functions/readlink.html + target_name[target_result] = 0; + md5wrapper md5; // get hash of the running DF process - string hash = md5.getHashFromFile(exe_file); + string hash = md5.getHashFromFile(target_name); vector::iterator it; // cerr << exe_file << " " << hash << endl; // iterate over the list of memory locations @@ -384,7 +331,6 @@ bool SHMProcess::Private::validate(char * exe_file, uint32_t pid, vector getVersion() << endl; return true; @@ -427,9 +373,15 @@ int SHMProcess::getPID() return d->process_ID; } -//FIXME: implement +// there is only one we care about. bool SHMProcess::getThreadIDs(vector & threads ) { + if(d->attached) + { + threads.clear(); + threads.push_back(d->process_ID); + return true; + } return false; } @@ -466,7 +418,7 @@ bool SHMProcess::suspend() { return false; } - if(d->suspended) + if(d->locked) { return true; } @@ -496,7 +448,6 @@ bool SHMProcess::suspend() // we wait for the server to give up our suspend lock (held by default) if(lockf(d->suspend_lock,F_LOCK,0) == 0) { - d->suspended = true; d->locked = true; return true; } @@ -510,18 +461,17 @@ bool SHMProcess::asyncSuspend() { return false; } - if(d->suspended) + if(d->locked) { return true; } - - if(D_SHMCMD == CORE_SUSPENDED) + uint32_t cmd = D_SHMCMD; + if(cmd == CORE_SUSPENDED) { // we have to hold the lock to be really suspended if(lockf(d->suspend_lock,F_LOCK,0) == 0) { d->locked = true; - d->suspended = true; return true; } return false; @@ -529,7 +479,11 @@ bool SHMProcess::asyncSuspend() else { // did we just resume a moment ago? - if(D_SHMCMD == CORE_RUN) + if(cmd == CORE_STEP) + { + return false; + } + else if(cmd == CORE_RUN) { D_SHMCMD = CORE_STEP; } @@ -537,14 +491,6 @@ bool SHMProcess::asyncSuspend() { D_SHMCMD = CORE_SUSPEND; } - // try locking - if(lockf(d->suspend_lock,F_TLOCK,0) == 0) - { - d->locked = true; - d->suspended = true; - return true; - } - return false; } } @@ -559,12 +505,11 @@ bool SHMProcess::resume() { if(!d->attached) return false; - if(!d->suspended) + if(!d->locked) return true; // unlock the suspend lock if(lockf(d->suspend_lock,F_ULOCK,0) == 0) { - d->suspended = false; d->locked = false; if(d->SetAndWait(CORE_RUN)) // we have to make sure the server responds! { @@ -587,17 +532,27 @@ bool SHMProcess::attach() } if(!d->GetLocks()) { - cerr << "server is full or not really there!" << endl; + //cerr << "server is full or not really there!" << endl; return false; } + /* + * Locate the segment. + */ + if ((d->shm_ID = shmget(SHM_KEY + d->process_ID, SHM_SIZE, 0666)) < 0) + { + d->FreeLocks(); + cerr << "can't find segment" << endl; // FIXME: throw + return false; + } + /* * Attach the segment */ if ((d->shm_addr = (char *) shmat(d->shm_ID, NULL, 0)) == (char *) -1) { d->FreeLocks(); - cerr << "can't attach segment" << endl; + cerr << "can't attach segment" << endl; // FIXME: throw return false; } d->attached = true; @@ -618,18 +573,18 @@ bool SHMProcess::detach() { return false; } - if(d->suspended) + if(d->locked) { resume(); } // detach segment if(shmdt(d->shm_addr) != -1) { + d->FreeLocks(); + d->locked = false; d->attached = false; - d->suspended = false; d->shm_addr = 0; g_pProcess = 0; - d->FreeLocks(); return true; } // fail if we can't detach @@ -851,7 +806,6 @@ const std::string SHMProcess::readSTLString(uint32_t offset) D_SHMHDR->address = offset; full_barrier d->SetAndWait(CORE_READ_STL_STRING); - //int length = ((shm_retval *)d->my_shm)->value; return(string( D_SHMDATA(char) )); } @@ -920,4 +874,23 @@ char * SHMProcess::getSHMStart (void) if(!d->locked) return 0; //THROW HERE! return /*d->shm_addr_with_cl_idx*/ d->shm_addr; +} + +bool SHMProcess::Private::Aux_Core_Attach(bool & versionOK, pid_t & PID) +{ + if(!locked) throw Error::SHMAccessDenied(); + + SHMDATA(coreattach)->cl_affinity = OS_getAffinity(); + if(!SetAndWait(CORE_ATTACH)) return false; + /* + cerr <<"CORE_VERSION" << CORE_VERSION << endl; + cerr <<"server CORE_VERSION" << SHMDATA(coreattach)->sv_version << endl; + */ + versionOK =( SHMDATA(coreattach)->sv_version == CORE_VERSION ); + PID = SHMDATA(coreattach)->sv_PID; + useYield = SHMDATA(coreattach)->sv_useYield; + #ifdef DEBUG + if(useYield) cerr << "Using Yield!" << endl; + #endif + return true; } \ No newline at end of file diff --git a/library/DFProcess-windows-SHM.cpp b/library/DFProcess-windows-SHM.cpp index 115dc5180..016ea2dad 100644 --- a/library/DFProcess-windows-SHM.cpp +++ b/library/DFProcess-windows-SHM.cpp @@ -37,76 +37,60 @@ class SHMProcess::Private shm_addr = 0; window = NULL; attached = false; - suspended = false; + locked = false; identified = false; useYield = 0; DFSVMutex = 0; DFCLMutex = 0; + DFCLSuspendMutex = 0; + attachmentIdx = -1; }; ~Private(){}; memory_info * memdescriptor; DFWindow * window; + SHMProcess * q; uint32_t process_ID; char *shm_addr; HANDLE DFSVMutex; HANDLE DFCLMutex; + HANDLE DFCLSuspendMutex; + int attachmentIdx; bool attached; - bool suspended; + bool locked; bool identified; bool useYield; - bool waitWhile (uint32_t state); - bool isValidSV(); + bool validate(std::vector< memory_info* >& known_versions); + bool Aux_Core_Attach(bool & versionOK, uint32_t & PID); + bool SetAndWait (uint32_t state); + bool GetLocks(); + bool AreLocksOk(); + void FreeLocks(); }; // some helpful macros to keep the code bloat in check -#define SHMCMD ((shm_cmd *)my_shm)->pingpong -#define D_SHMCMD ((shm_cmd *)d->my_shm)->pingpong - -#define SHMHDR ((shm_core_hdr *)my_shm) -#define D_SHMHDR ((shm_core_hdr *)d->my_shm) +#define SHMCMD ( (uint32_t *) shm_addr)[attachmentIdx] +#define D_SHMCMD ( (uint32_t *) (d->shm_addr))[d->attachmentIdx] -#define SHMDATA(type) ((type *)(my_shm + SHM_HEADER)) -#define D_SHMDATA(type) ((type *)(d->my_shm + SHM_HEADER)) +#define SHMHDR ((shm_core_hdr *)shm_addr) +#define D_SHMHDR ((shm_core_hdr *)(d->shm_addr)) -// is the other side still there? -bool SHMProcess::Private::isValidSV() -{ - // try if CL mutex is free - uint32_t result = WaitForSingleObject(DFSVMutex,0); - - switch (result) - { - case WAIT_ABANDONED: - case WAIT_OBJECT_0: - { - ReleaseMutex(DFSVMutex); - return false; - } - case WAIT_TIMEOUT: - { - // mutex is held by DF - return true; - } - default: - case WAIT_FAILED: - { - // TODO: now how do I respond to this? - return false; - } - } -} +#define SHMDATA(type) ((type *)(shm_addr + SHM_HEADER)) +#define D_SHMDATA(type) ((type *)(d->shm_addr + SHM_HEADER)) -bool SHMProcess::waitWhile (uint32_t state) +bool SHMProcess::SetAndWait (uint32_t state) { - return d->waitWhile(state); + return d->SetAndWait(state); } -bool SHMProcess::Private::waitWhile (uint32_t state) +bool SHMProcess::Private::SetAndWait (uint32_t state) { uint32_t cnt = 0; + if(!attached) return false; + SHMCMD = state; + while (SHMCMD == state) { // yield the CPU, only on single-core CPUs @@ -116,13 +100,14 @@ bool SHMProcess::Private::waitWhile (uint32_t state) } if(cnt == 10000) { - if(!isValidSV())// DF not there anymore? + if(!AreLocksOk())// DF not there anymore? { - attached = suspended = false; - ReleaseMutex(DFCLMutex); UnmapViewOfFile(shm_addr); + FreeLocks(); + attached = locked = identified = false; + // we aren't the current process anymore + g_pProcess = NULL; throw Error::SHMServerDisappeared(); - return false; } else { @@ -133,9 +118,6 @@ bool SHMProcess::Private::waitWhile (uint32_t state) } if(SHMCMD == CORE_ERROR) { - SHMCMD = CORE_RUNNING; - attached = suspended = false; - cerr << "shm server error!" << endl; return false; } return true; @@ -149,28 +131,133 @@ uint32_t OS_getAffinity() return dwProcessAffinityMask; } -bool SHMProcess::Private::Aux_Core_Attach(bool & versionOK, uint32_t & PID) +void SHMProcess::Private::FreeLocks() { - SHMDATA(coreattach)->cl_affinity = OS_getAffinity(); - full_barrier - SHMCMD = CORE_ATTACH; - if(!waitWhile(CORE_ATTACH)) + attachmentIdx = -1; + if(DFCLMutex != 0) + { + ReleaseMutex(DFCLMutex); + CloseHandle(DFCLMutex); + DFCLMutex = 0; + } + if(DFSVMutex != 0) + { + CloseHandle(DFSVMutex); + DFSVMutex = 0; + } + if(DFCLSuspendMutex != 0) + { + ReleaseMutex(DFCLSuspendMutex); + CloseHandle(DFCLSuspendMutex); + // FIXME: maybe also needs ReleaseMutex! + DFCLSuspendMutex = 0; + locked = false; + } +} + +bool SHMProcess::Private::GetLocks() +{ + char name[256]; + // try to acquire locks + // look at the server lock, if it's locked, the server is present + sprintf(name,"DFSVMutex-%d",process_ID); + DFSVMutex = OpenMutex(SYNCHRONIZE,0, name); + if(DFSVMutex == 0) + { + // cerr << "can't open sv lock" << endl; return false; - full_barrier - versionOK =( SHMDATA(coreattach)->sv_version == CORE_VERSION ); - PID = SHMDATA(coreattach)->sv_PID; - useYield = SHMDATA(coreattach)->sv_useYield; - #ifdef DEBUG - if(useYield) cerr << "Using Yield!" << endl; - #endif - return true; + } + // unlike the F_TEST of lockf, this one actually locks. we have to release + if(WaitForSingleObject(DFSVMutex,0) == 0) + { + ReleaseMutex(DFSVMutex); + // cerr << "sv lock not locked" << endl; + CloseHandle(DFSVMutex); + DFSVMutex = 0; + return false; + } + + for(int i = 0; i < SHM_MAX_CLIENTS; i++) + { + // open the client suspend locked + sprintf(name, "DFCLSuspendMutex-%d-%d",process_ID,i); + DFCLSuspendMutex = OpenMutex(SYNCHRONIZE,0, name); + if(DFCLSuspendMutex == 0) + { + //cerr << "can't open cl S-lock " << i << endl; + // couldn't open lock + continue; + } + + // open the client lock, try to lock it + + sprintf(name,"DFCLMutex-%d-%d",process_ID,i); + DFCLMutex = OpenMutex(SYNCHRONIZE,0,name); + if(DFCLMutex == 0) + { + //cerr << "can't open cl lock " << i << endl; + CloseHandle(DFCLSuspendMutex); + locked = false; + DFCLSuspendMutex = 0; + continue; + } + uint32_t waitstate = WaitForSingleObject(DFCLMutex,0); + if(waitstate == WAIT_FAILED || waitstate == WAIT_TIMEOUT ) + { + //cerr << "can't acquire cl lock " << i << endl; + CloseHandle(DFCLSuspendMutex); + locked = false; + DFCLSuspendMutex = 0; + CloseHandle(DFCLMutex); + DFCLMutex = 0; + continue; + } + // ok, we have all the locks we need! + attachmentIdx = i; + return true; + } + CloseHandle(DFSVMutex); + DFSVMutex = 0; + // cerr << "can't get any client locks" << endl; + return false; } -SHMProcess::SHMProcess(uint32_t PID, vector & known_versions) -: d(new Private()) +// is the other side still there? +bool SHMProcess::Private::AreLocksOk() { + // both locks are inited (we hold our lock) + if(DFCLMutex != 0 && DFSVMutex != 0) + { + // try if CL mutex is free + switch (WaitForSingleObject(DFSVMutex,0)) + { + case WAIT_ABANDONED: + case WAIT_OBJECT_0: + { + ReleaseMutex(DFSVMutex); + return false; + } + case WAIT_TIMEOUT: + { + // mutex is held by DF + return true; + } + default: + case WAIT_FAILED: + { + // TODO: now how do I respond to this? + return false; + } + } + } + return false; +} + + + + /* char svmutexname [256]; - sprintf(svmutexname,"DFSVMutex-%d",PID); + char clmutexname [256]; sprintf(clmutexname,"DFCLMutex-%d",PID); @@ -185,119 +272,39 @@ SHMProcess::SHMProcess(uint32_t PID, vector & known_versions) { return; } + */ + +SHMProcess::SHMProcess(uint32_t PID, vector & known_versions) +: d(new Private()) +{ d->process_ID = PID; - + d->q = this; // attach the SHM if(!attach()) { return; } - // Test bridge version, get PID, sync Yield bool bridgeOK; - bool error = 0; if(!d->Aux_Core_Attach(bridgeOK,d->process_ID)) { - fprintf(stderr,"DF terminated during reading\n"); - error = 1; + detach(); + throw Error::SHMAttachFailure(); } else if(!bridgeOK) { - fprintf(stderr,"SHM bridge version mismatch\n"); - error = 1; - } - if(error) - { - D_SHMCMD = CORE_RUNNING; - UnmapViewOfFile(d->shm_addr); - ReleaseMutex(d->DFCLMutex); - CloseHandle(d->DFSVMutex); - d->DFSVMutex = 0; - CloseHandle(d->DFCLMutex); - d->DFCLMutex = 0; - return; - } - - // try to identify the DF version - do // glorified goto - { - IMAGE_NT_HEADERS32 pe_header; - IMAGE_SECTION_HEADER sections[16]; - HMODULE hmod = NULL; - DWORD junk; - HANDLE hProcess; - bool found = false; - d->identified = false; - // open process, we only need the process open - hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, d->process_ID ); - if (NULL == hProcess) - break; - - // try getting the first module of the process - if(EnumProcessModules(hProcess, &hmod, 1 * sizeof(HMODULE), &junk) == 0) - { - CloseHandle(hProcess); - // cout << "EnumProcessModules fail'd" << endl; - break; - } - // got base ;) - uint32_t base = (uint32_t)hmod; - - // read from this process - uint32_t pe_offset = readDWord(base+0x3C); - read(base + pe_offset , sizeof(pe_header), (uint8_t *)&pe_header); - read(base + pe_offset+ sizeof(pe_header), sizeof(sections) , (uint8_t *)§ions ); - - // iterate over the list of memory locations - vector::iterator it; - for ( it=known_versions.begin() ; it < known_versions.end(); it++ ) - { - uint32_t pe_timestamp; - try - { - pe_timestamp = (*it)->getHexValue("pe_timestamp"); - } - catch(Error::MissingMemoryDefinition& e) - { - continue; - } - if (pe_timestamp == pe_header.FileHeader.TimeDateStamp) - { - memory_info *m = new memory_info(**it); - m->RebaseAll(base); - d->memdescriptor = m; - d->identified = true; - cerr << "identified " << m->getVersion() << endl; - break; - } - } - CloseHandle(hProcess); - } while (0); // glorified goto end - - if(d->identified) - { - d->window = new DFWindow(this); - } - else - { - D_SHMCMD = CORE_RUNNING; - UnmapViewOfFile(d->shm_addr); - d->shm_addr = 0; - ReleaseMutex(d->DFCLMutex); - CloseHandle(d->DFSVMutex); - d->DFSVMutex = 0; - CloseHandle(d->DFCLMutex); - d->DFCLMutex = 0; - return; + detach(); + throw Error::SHMVersionMismatch(); } - full_barrier + d->validate(known_versions); + d->window = new DFWindow(this); // at this point, DF is attached and suspended, make it run detach(); } bool SHMProcess::isSuspended() { - return d->suspended; + return d->locked; } bool SHMProcess::isAttached() { @@ -308,7 +315,62 @@ bool SHMProcess::isIdentified() { return d->identified; } - +bool SHMProcess::Private::validate(vector & known_versions) +{ + // try to identify the DF version + IMAGE_NT_HEADERS32 pe_header; + IMAGE_SECTION_HEADER sections[16]; + HMODULE hmod = NULL; + DWORD junk; + HANDLE hProcess; + bool found = false; + identified = false; + // open process, we only need the process open + hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, process_ID ); + if (NULL == hProcess) + return false; + + // try getting the first module of the process + if(EnumProcessModules(hProcess, &hmod, 1 * sizeof(HMODULE), &junk) == 0) + { + CloseHandle(hProcess); + // cout << "EnumProcessModules fail'd" << endl; + return false; + } + // got base ;) + uint32_t base = (uint32_t)hmod; + + // read from this process + uint32_t pe_offset = q->readDWord(base+0x3C); + q->read(base + pe_offset , sizeof(pe_header), (uint8_t *)&pe_header); + q->read(base + pe_offset+ sizeof(pe_header), sizeof(sections) , (uint8_t *)§ions ); + + // iterate over the list of memory locations + vector::iterator it; + for ( it=known_versions.begin() ; it < known_versions.end(); it++ ) + { + uint32_t pe_timestamp; + try + { + pe_timestamp = (*it)->getHexValue("pe_timestamp"); + } + catch(Error::MissingMemoryDefinition& e) + { + continue; + } + if (pe_timestamp == pe_header.FileHeader.TimeDateStamp) + { + memory_info *m = new memory_info(**it); + m->RebaseAll(base); + memdescriptor = m; + identified = true; + cerr << "identified " << m->getVersion() << endl; + CloseHandle(hProcess); + return true; + } + } + return false; +} SHMProcess::~SHMProcess() { if(d->attached) @@ -324,15 +386,6 @@ SHMProcess::~SHMProcess() { delete d->window; } - // release mutex handles we have - if(d->DFCLMutex) - { - CloseHandle(d->DFCLMutex); - } - if(d->DFSVMutex) - { - CloseHandle(d->DFSVMutex); - } delete d; } @@ -351,79 +404,135 @@ int SHMProcess::getPID() return d->process_ID; } -//FIXME: implement bool SHMProcess::getThreadIDs(vector & threads ) { - return false; + 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->process_ID ) + { + threads.push_back(te32.th32ThreadID); + } + } while( Thread32Next(AllThreads, &te32 ) ); + + CloseHandle( AllThreads ); + return true; } -//FIXME: cross-reference with ELF segment entries? +//FIXME: use VirtualQuery to probe for memory ranges, cross-reference with base-corrected PE segment entries void SHMProcess::getMemRanges( vector & ranges ) { - char buffer[1024]; - char permissions[5]; // r/-, w/-, x/-, p/s, 0 + // 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 - sprintf(buffer, "/proc/%lu/maps", d->process_ID); - FILE *mapFile = ::fopen(buffer, "r"); - uint64_t offset, device1, device2, node; + // I'm faking this, because there's no way I'm using VirtualQuery - 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); - } + t_memrange temp; + uint32_t base = d->memdescriptor->getBase(); + temp.start = base + 0x1000; // more fakery. + temp.end = base + readDWord(base+readDWord(base+0x3C)+0x50)-1; // yay for magic. + temp.read = 1; + temp.write = 1; + temp.execute = 0; // fake + strcpy(temp.name,"pants"); + ranges.push_back(temp); } bool SHMProcess::suspend() { if(!d->attached) { - cerr << "couldn't suspend, not attached" << endl; return false; } - if(d->suspended) + if(d->locked) { - cerr << "couldn't suspend, already suspended" << endl; return true; } - D_SHMCMD = CORE_SUSPEND; - if(!d->waitWhile(CORE_SUSPEND)) + //cerr << "suspend" << endl;// FIXME: throw + // FIXME: this should be controlled on the server side + // FIXME: IF server got CORE_RUN in this frame, interpret CORE_SUSPEND as CORE_STEP + // did we just resume a moment ago? + if(D_SHMCMD == CORE_RUN) { - cerr << "couldn't suspend, DF not responding to commands" << endl; - return false; + //fprintf(stderr,"%d invokes step\n",d->attachmentIdx); + // wait for the next window + if(!d->SetAndWait(CORE_STEP)) + { + throw Error::SHMLockingError("if(!d->SetAndWait(CORE_STEP))"); + } } - d->suspended = true; - return true; + else + { + //fprintf(stderr,"%d invokes suspend\n",d->attachmentIdx); + // lock now + if(!d->SetAndWait(CORE_SUSPEND)) + { + throw Error::SHMLockingError("if(!d->SetAndWait(CORE_SUSPEND))"); + } + } + //fprintf(stderr,"waiting for lock\n"); + // we wait for the server to give up our suspend lock (held by default) + if( WaitForSingleObject(d->DFCLSuspendMutex,INFINITE) == 0 ) + { + d->locked = true; + return true; + } + return false; } +// FIXME: needs a good think-through bool SHMProcess::asyncSuspend() { if(!d->attached) { return false; } - if(d->suspended) + if(d->locked) { return true; } - if(D_SHMCMD == CORE_SUSPENDED) + //cerr << "async suspend" << endl;// FIXME: throw + uint32_t cmd = D_SHMCMD; + if(cmd == CORE_SUSPENDED) { - d->suspended = true; - return true; + // we have to hold the lock to be really suspended + if( WaitForSingleObject(d->DFCLSuspendMutex,INFINITE) == 0 ) + { + d->locked = true; + return true; + } + return false; } else { - D_SHMCMD = CORE_SUSPEND; + // did we just resume a moment ago? + if(cmd == CORE_STEP) + { + return false; + } + else if(cmd == CORE_RUN) + { + D_SHMCMD = CORE_STEP; + } + else + { + D_SHMCMD = CORE_SUSPEND; + } return false; } } @@ -433,21 +542,26 @@ bool SHMProcess::forceresume() return resume(); } +// FIXME: wait for the server to advance a step! bool SHMProcess::resume() { if(!d->attached) - { - cerr << "couldn't resume because of no attachment" << endl; return false; - } - if(!d->suspended) - { - cerr << "couldn't resume because of not being suspended" << endl; + if(!d->locked) return true; + //cerr << "resume" << endl;// FIXME: throw + // unlock the suspend lock + if( ReleaseMutex(d->DFCLSuspendMutex) != 0) + { + d->locked = false; + if(d->SetAndWait(CORE_RUN)) // we have to make sure the server responds! + { + return true; + } + throw Error::SHMLockingError("if(d->SetAndWait(CORE_RUN))"); } - D_SHMCMD = CORE_RUNNING; - d->suspended = false; - return true; + throw Error::SHMLockingError("if( ReleaseMutex(d->DFCLSuspendMutex) != 0)"); + return false; } @@ -455,49 +569,66 @@ bool SHMProcess::attach() { if(g_pProcess != 0) { - cerr << "there's already a different process attached" << endl; + cerr << "there's already a process attached" << endl; return false; } - if(d->attached) + //cerr << "attach" << endl;// FIXME: throw + if(!d->GetLocks()) { - cerr << "already attached" << endl; + //cerr << "server is full or not really there!" << endl; return false; } + /* // check if DF is there if(!d->isValidSV()) { return false; // NOT } + */ + /* // try locking client mutex uint32_t result = WaitForSingleObject(d->DFCLMutex,0); if( result != WAIT_OBJECT_0 && result != WAIT_ABANDONED) { return false; // we couldn't lock it } - + */ + + /* + * Locate the segment. + */ char shmname [256]; sprintf(shmname,"DFShm-%d",d->process_ID); - - // now try getting and attaching the shared memory HANDLE shmHandle = OpenFileMapping(FILE_MAP_ALL_ACCESS,false,shmname); if(!shmHandle) { - ReleaseMutex(d->DFCLMutex); + d->FreeLocks(); + //ReleaseMutex(d->DFCLMutex); return false; // we couldn't lock it } - // attempt to attach the opened mapping - d->shm_addr = (char *) MapViewOfFile(shmHandle,FILE_MAP_ALL_ACCESS, 0,0, SHM_ALL_CLIENTS); + /* + * Attach the segment + */ + d->shm_addr = (char *) MapViewOfFile(shmHandle,FILE_MAP_ALL_ACCESS, 0,0, SHM_SIZE); if(!d->shm_addr) { CloseHandle(shmHandle); - ReleaseMutex(d->DFCLMutex); - return false; // we couldn't attach the mapping + //ReleaseMutex(d->DFCLMutex); + d->FreeLocks(); + return false; // we couldn't attach the mapping // FIXME: throw } - // we close the handle right here so we don't have to keep track of it + // we close the handle right here - it's not needed anymore CloseHandle(shmHandle); + d->attached = true; - suspend(); + if(!suspend()) + { + UnmapViewOfFile(d->shm_addr); + d->FreeLocks(); + //cerr << "unable to suspend" << endl;// FIXME: throw + return false; + } g_pProcess = this; return true; } @@ -508,27 +639,36 @@ bool SHMProcess::detach() { return false; } + //cerr << "detach" << endl;// FIXME: throw + if(d->locked) + { + resume(); + } + //cerr << "detach after resume" << endl;// FIXME: throw // detach segment UnmapViewOfFile(d->shm_addr); // release it for some other client - ReleaseMutex(d->DFCLMutex); // we keep the mutex handles + //ReleaseMutex(d->DFCLMutex); // we keep the mutex handles + d->FreeLocks(); d->attached = false; - d->suspended = false; + d->locked = false; + d->shm_addr = false; g_pProcess = 0; return true; } void SHMProcess::read (uint32_t src_address, uint32_t size, uint8_t *target_buffer) { + if(!d->locked) throw Error::SHMAccessDenied(); + // normal read under 1MB if(size <= SHM_BODY) { D_SHMHDR->address = src_address; D_SHMHDR->length = size; full_barrier - D_SHMCMD = CORE_DFPP_READ; - d->waitWhile(CORE_DFPP_READ); - memcpy (target_buffer, d->shm_addr + SHM_HEADER,size); + d->SetAndWait(CORE_READ); + memcpy (target_buffer, D_SHMDATA(void),size); } // a big read, we pull data over the shm in iterations else @@ -541,9 +681,8 @@ void SHMProcess::read (uint32_t src_address, uint32_t size, uint8_t *target_buff D_SHMHDR->address = src_address; D_SHMHDR->length = to_read; full_barrier - D_SHMCMD = CORE_DFPP_READ; - d->waitWhile(CORE_DFPP_READ); - memcpy (target_buffer, d->shm_addr + SHM_HEADER,size); + d->SetAndWait(CORE_READ); + memcpy (target_buffer, D_SHMDATA(void) ,size); // decrease size by bytes read size -= to_read; // move the cursors @@ -557,54 +696,60 @@ void SHMProcess::read (uint32_t src_address, uint32_t size, uint8_t *target_buff uint8_t SHMProcess::readByte (const uint32_t offset) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; full_barrier - D_SHMCMD = CORE_READ_BYTE; - d->waitWhile(CORE_READ_BYTE); + d->SetAndWait(CORE_READ_BYTE); return D_SHMHDR->value; } void SHMProcess::readByte (const uint32_t offset, uint8_t &val ) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; full_barrier - D_SHMCMD = CORE_READ_BYTE; - d->waitWhile(CORE_READ_BYTE); + d->SetAndWait(CORE_READ_BYTE); val = D_SHMHDR->value; } uint16_t SHMProcess::readWord (const uint32_t offset) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; full_barrier - D_SHMCMD = CORE_READ_WORD; - d->waitWhile(CORE_READ_WORD); + d->SetAndWait(CORE_READ_WORD); return D_SHMHDR->value; } void SHMProcess::readWord (const uint32_t offset, uint16_t &val) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; full_barrier - D_SHMCMD = CORE_READ_WORD; - d->waitWhile(CORE_READ_WORD); + d->SetAndWait(CORE_READ_WORD); val = D_SHMHDR->value; } uint32_t SHMProcess::readDWord (const uint32_t offset) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; full_barrier - D_SHMCMD = CORE_READ_DWORD; - d->waitWhile(CORE_READ_DWORD); + d->SetAndWait(CORE_READ_DWORD); return D_SHMHDR->value; } void SHMProcess::readDWord (const uint32_t offset, uint32_t &val) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; full_barrier - D_SHMCMD = CORE_READ_DWORD; - d->waitWhile(CORE_READ_DWORD); + d->SetAndWait(CORE_READ_DWORD); val = D_SHMHDR->value; } @@ -614,43 +759,47 @@ void SHMProcess::readDWord (const uint32_t offset, uint32_t &val) void SHMProcess::writeDWord (uint32_t offset, uint32_t data) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; D_SHMHDR->value = data; full_barrier - D_SHMCMD = CORE_WRITE_DWORD; - d->waitWhile(CORE_WRITE_DWORD); + d->SetAndWait(CORE_WRITE_DWORD); } // using these is expensive. void SHMProcess::writeWord (uint32_t offset, uint16_t data) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; D_SHMHDR->value = data; full_barrier - D_SHMCMD = CORE_WRITE_WORD; - d->waitWhile(CORE_WRITE_WORD); + d->SetAndWait(CORE_WRITE_WORD); } void SHMProcess::writeByte (uint32_t offset, uint8_t data) { + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; D_SHMHDR->value = data; full_barrier - D_SHMCMD = CORE_WRITE_BYTE; - d->waitWhile(CORE_WRITE_BYTE); + d->SetAndWait(CORE_WRITE_BYTE); } void SHMProcess::write (uint32_t dst_address, uint32_t size, uint8_t *source_buffer) { + if(!d->locked) throw Error::SHMAccessDenied(); + // normal write under 1MB if(size <= SHM_BODY) { D_SHMHDR->address = dst_address; D_SHMHDR->length = size; - memcpy(d->shm_addr+SHM_HEADER,source_buffer, size); + memcpy(D_SHMDATA(void),source_buffer, size); full_barrier - D_SHMCMD = CORE_WRITE; - d->waitWhile(CORE_WRITE); + d->SetAndWait(CORE_WRITE); } // a big write, we push this over the shm in iterations else @@ -662,10 +811,9 @@ void SHMProcess::write (uint32_t dst_address, uint32_t size, uint8_t *source_buf // write to_write bytes to dst_cursor D_SHMHDR->address = dst_address; D_SHMHDR->length = to_write; - memcpy(d->shm_addr+SHM_HEADER,source_buffer, to_write); + memcpy(D_SHMDATA(void),source_buffer, to_write); full_barrier - D_SHMCMD = CORE_WRITE; - d->waitWhile(CORE_WRITE); + d->SetAndWait(CORE_WRITE); // decrease size by bytes written size -= to_write; // move the cursors @@ -680,6 +828,8 @@ void SHMProcess::write (uint32_t dst_address, uint32_t size, uint8_t *source_buf // FIXME: butt-fugly const std::string SHMProcess::readCString (uint32_t offset) { + if(!d->locked) throw Error::SHMAccessDenied(); + std::string temp; char temp_c[256]; int counter = 0; @@ -697,6 +847,8 @@ const std::string SHMProcess::readCString (uint32_t offset) DfVector SHMProcess::readVector (uint32_t offset, uint32_t item_size) { + if(!d->locked) throw Error::SHMAccessDenied(); + /* MSVC++ vector is four pointers long ptr allocator @@ -715,38 +867,36 @@ DfVector SHMProcess::readVector (uint32_t offset, uint32_t item_size) const std::string SHMProcess::readSTLString(uint32_t offset) { - //offset -= 4; //msvc std::string pointers are 8 bytes ahead of their data, not 4 + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; full_barrier - D_SHMCMD = CORE_READ_STL_STRING; - d->waitWhile(CORE_READ_STL_STRING); - int length = D_SHMHDR->value; -// char temp_c[256]; -// strncpy(temp_c, d->my_shm+SHM_HEADER,length+1); // length + 1 for the null terminator - return(string(d->shm_addr+SHM_HEADER)); + d->SetAndWait(CORE_READ_STL_STRING); + return(string( D_SHMDATA(char) )); } size_t SHMProcess::readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) { - //offset -= 4; //msvc std::string pointers are 8 bytes ahead of their data, not 4 + if(!d->locked) throw Error::SHMAccessDenied(); + D_SHMHDR->address = offset; full_barrier - D_SHMCMD = CORE_READ_STL_STRING; - d->waitWhile(CORE_READ_STL_STRING); + d->SetAndWait(CORE_READ_STL_STRING); size_t length = D_SHMHDR->value; - size_t real = min(length, bufcapacity - 1); - strncpy(buffer, d->shm_addr+SHM_HEADER,real); // length + 1 for the null terminator - buffer[real] = 0; - return real; + size_t fit = min(bufcapacity - 1, length); + strncpy(buffer,D_SHMDATA(char),fit); + buffer[fit] = 0; + return fit; } void SHMProcess::writeSTLString(const uint32_t address, const std::string writeString) { - D_SHMHDR->address = address/*-4*/; - strncpy(d->shm_addr+SHM_HEADER,writeString.c_str(),writeString.length()+1); // length + 1 for the null terminator + if(!d->locked) throw Error::SHMAccessDenied(); + + D_SHMHDR->address = address; + strncpy(D_SHMDATA(char),writeString.c_str(),writeString.length()+1); // length + 1 for the null terminator full_barrier - D_SHMCMD = CORE_WRITE_STL_STRING; - d->waitWhile(CORE_WRITE_STL_STRING); + d->SetAndWait(CORE_WRITE_STL_STRING); } string SHMProcess::readClassName (uint32_t vptr) @@ -758,21 +908,51 @@ string SHMProcess::readClassName (uint32_t vptr) return raw; } -// get module index by name and version. bool 1 = error +// get module index by name and version. bool 0 = error bool SHMProcess::getModuleIndex (const char * name, const uint32_t version, uint32_t & OUTPUT) { - modulelookup * payload = (modulelookup *) (d->shm_addr + SHM_HEADER); + if(!d->locked) throw Error::SHMAccessDenied(); + + modulelookup * payload = D_SHMDATA(modulelookup); payload->version = version; - strcpy(payload->name,name); - full_barrier - D_SHMCMD = CORE_ACQUIRE_MODULE; - d->waitWhile(CORE_ACQUIRE_MODULE); - if(D_SHMHDR->error) return false; + + strncpy(payload->name,name,255); + payload->name[255] = 0; + + if(!SetAndWait(CORE_ACQUIRE_MODULE)) + { + return false; // FIXME: throw a fatal exception instead + } + if(D_SHMHDR->error) + { + return false; + } + //fprintf(stderr,"%s v%d : %d\n", name, version, D_SHMHDR->value); OUTPUT = D_SHMHDR->value; return true; } char * SHMProcess::getSHMStart (void) { - return d->shm_addr; + if(!d->locked) throw Error::SHMAccessDenied(); + return /*d->shm_addr_with_cl_idx*/ d->shm_addr; +} + +bool SHMProcess::Private::Aux_Core_Attach(bool & versionOK, uint32_t & PID) +{ + if(!locked) throw Error::SHMAccessDenied(); + + SHMDATA(coreattach)->cl_affinity = OS_getAffinity(); + if(!SetAndWait(CORE_ATTACH)) return false; + /* + cerr <<"CORE_VERSION" << CORE_VERSION << endl; + cerr <<"server CORE_VERSION" << SHMDATA(coreattach)->sv_version << endl; + */ + versionOK =( SHMDATA(coreattach)->sv_version == CORE_VERSION ); + PID = SHMDATA(coreattach)->sv_PID; + useYield = SHMDATA(coreattach)->sv_useYield; + #ifdef DEBUG + if(useYield) cerr << "Using Yield!" << endl; + #endif + return true; } \ No newline at end of file diff --git a/shmserver/mod-core.cpp b/shmserver/mod-core.cpp index 76fd9dfe9..63622731d 100644 --- a/shmserver/mod-core.cpp +++ b/shmserver/mod-core.cpp @@ -183,6 +183,12 @@ void ReleaseSuspendLock( void * data ) OS_releaseSuspendLock(currentClient); } +void AcquireSuspendLock( void * data ) +{ + OS_lockSuspendLock(currentClient); +} + + DFPP_module InitCore(void) { DFPP_module core; @@ -193,7 +199,8 @@ DFPP_module InitCore(void) core.reserve(NUM_CORE_CMDS); // basic states core.set_command(CORE_RUNNING, CANCELLATION, "Running"); - core.set_command(CORE_RUN, FUNCTION, "Run!",0,CORE_RUNNING); + //core.set_command(CORE_RUN, FUNCTION, "Run!",AcquireSuspendLock,CORE_RUNNING); + core.set_command(CORE_RUN, CANCELLATION, "Run!",0,CORE_RUNNING); core.set_command(CORE_STEP, CANCELLATION, "Suspend on next step",0,CORE_SUSPEND);// set command to CORE_SUSPEND, check next client core.set_command(CORE_SUSPEND, FUNCTION, "Suspend", ReleaseSuspendLock , CORE_SUSPENDED); core.set_command(CORE_SUSPENDED, CLIENT_WAIT, "Suspended"); @@ -300,16 +307,17 @@ void SHM_Act (void) if(cmd.nextState != -1) { /* - fprintf(stderr, "Client %d invoked %d:%d = %x = ", - currentClient,((shm_cmd)atomic).parts.module,((shm_cmd)atomic).parts.command, cmd._function); - fprintf(stderr, "%s\n",cmd.name.c_str()); + char text [512]; + char text2 [512]; + sprintf (text,"Client %d invoked %d:%d = %x = %s\n",currentClient,((shm_cmd)atomic).parts.module,((shm_cmd)atomic).parts.command, cmd._function,cmd.name.c_str()); + sprintf(text2, "Server set %d\n",cmd.nextState); */ // FIXME: WHAT HAPPENS WHEN A 'NEXTSTATE' IS FROM A DIFFERENT MODULE THAN 'CORE'? Yeah. It doesn't work. SHMCMD = cmd.nextState; - /* - fprintf(stderr, "Server set %d\n",cmd.nextState); - fflush(stderr); // make sure this finds its way to the terminal! - */ + //MessageBox(0,text,text2, MB_OK); + + //fflush(stderr); // make sure this finds its way to the terminal! + } full_barrier diff --git a/shmserver/shms-windows.cpp b/shmserver/shms-windows.cpp index ee4fff0d5..0762e8599 100644 --- a/shmserver/shms-windows.cpp +++ b/shmserver/shms-windows.cpp @@ -44,8 +44,65 @@ char *shm = 0; int shmid = 0; bool inited = 0; HANDLE shmHandle = 0; + HANDLE DFSVMutex = 0; -HANDLE DFCLMutex = 0; +HANDLE DFCLMutex[SHM_MAX_CLIENTS]; +HANDLE DFCLSuspendMutex[SHM_MAX_CLIENTS]; +int held_DFCLSuspendMutex[SHM_MAX_CLIENTS]; +int numheld = SHM_MAX_CLIENTS; + + +void OS_lockSuspendLock(int which) +{ + if(numheld == SHM_MAX_CLIENTS) + return; + // lock not held by server and can be picked up. OK. + if(held_DFCLSuspendMutex[which] == 0) + { + uint32_t state = WaitForSingleObject(DFCLSuspendMutex[which],INFINITE); + if(state == WAIT_ABANDONED || state == WAIT_OBJECT_0) + { + held_DFCLSuspendMutex[which] = 1; + numheld++; + return; + } + // lock couldn't be picked up! + errorstate = 1; + MessageBox(0,"Suspend lock locking failed. Further communication disabled!","Error", MB_OK); + return; + } + errorstate = 1; + MessageBox(0,"Server tried to lock already locked suspend lock? Further communication disabled!","Error", MB_OK); + return; +} + +void OS_releaseSuspendLock(int which) +{ + /* + if(which >=0 && which < SHM_MAX_CLIENTS) + return; + */ + if(numheld != SHM_MAX_CLIENTS) + { + MessageBox(0,"Locking system failure. Further communication disabled!","Error", MB_OK); + errorstate = 1; + return; + } + // lock hel by server and can be released -> OK + if(held_DFCLSuspendMutex[which] == 1 && ReleaseMutex(DFCLSuspendMutex[which])) + { + numheld--; + held_DFCLSuspendMutex[which] = 0; + } + // locked and not can't be released? FAIL! + else if (held_DFCLSuspendMutex[which] == 1) + { + MessageBox(0,"Suspend lock failed to unlock. Further communication disabled!","Error", MB_OK); + return; + } +} + + void SHM_Init ( void ) { // check that we do this only once per process @@ -56,102 +113,81 @@ void SHM_Init ( void ) } inited = true; - char svmutexname [256]; - sprintf(svmutexname,"DFSVMutex-%d",OS_getPID()); + char clmutexname [256]; - sprintf(clmutexname,"DFCLMutex-%d",OS_getPID()); + char clsmutexname [256]; char shmname [256]; sprintf(shmname,"DFShm-%d",OS_getPID()); - // create or open mutexes + // create a locked server mutex + char svmutexname [256]; + sprintf(svmutexname,"DFSVMutex-%d",OS_getPID()); DFSVMutex = CreateMutex( 0, 1, svmutexname); if(DFSVMutex == 0) { - DFSVMutex = OpenMutex(SYNCHRONIZE,false, svmutexname); - if(DFSVMutex == 0) - { - errorstate = 1; - return; - } + MessageBox(0,"Server mutex creation failed. Further communication disabled!","Error", MB_OK); + errorstate = 1; + return; } - DFCLMutex = CreateMutex( 0, 0, clmutexname); - if(DFCLMutex == 0) + // the mutex already existed. we don't want to know. + if(GetLastError() == ERROR_ALREADY_EXISTS) { - DFCLMutex = OpenMutex(SYNCHRONIZE,false, clmutexname); - if(DFCLMutex == 0) - { - CloseHandle(DFSVMutex); - errorstate = 1; - return; - } + MessageBox(0,"Server mutex already existed. Further communication disabled!","Error", MB_OK); + errorstate = 1; + return; } - // try locking server mutex - uint32_t result; - result = WaitForSingleObject(DFSVMutex,0); - switch (result) + // create client and suspend mutexes + for(int i = 0; i < SHM_MAX_CLIENTS; i++) { - case WAIT_ABANDONED: + sprintf(clmutexname,"DFCLMutex-%d-%d",OS_getPID(),i); + sprintf(clsmutexname,"DFCLSuspendMutex-%d-%d",OS_getPID(),i); + + DFCLMutex[i] = CreateMutex( 0, 0, clmutexname); // client mutex, not held + DFCLSuspendMutex[i] = CreateMutex( 0, 1, clsmutexname); // suspend mutexes held on start + held_DFCLSuspendMutex[i] = 1; + + if(DFCLMutex[i] == 0 || DFCLSuspendMutex[i] == 0 || GetLastError() == ERROR_ALREADY_EXISTS) { - // picked up after a crashed DF process - // do some sanity checks on client attached - // otherwise the same thing as WAIT_OBJECT_0 - break; - } - case WAIT_OBJECT_0: - { - // all right, we have the mutex and are the one and only DF server - break; - } - case WAIT_TIMEOUT: - case WAIT_FAILED: - default: - { - // error, bail + MessageBox(0,"Client mutex creation failed. Close all tools before starting DF.","Error", MB_OK); errorstate = 1; - MessageBox(0,"Could not aquire mutex","Error", MB_OK); - CloseHandle(DFSVMutex); - CloseHandle(DFCLMutex); return; } } - + // create virtual memory mapping - shmHandle = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,SHM_ALL_CLIENTS,shmname); + shmHandle = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,SHM_SIZE,shmname); // if can't create or already exists -> nothing happens if(GetLastError() == ERROR_ALREADY_EXISTS) { MessageBox(0,"SHM bridge already in use","Error", MB_OK); errorstate = 1; - ReleaseMutex(DFSVMutex); - CloseHandle(DFSVMutex); - CloseHandle(DFCLMutex); return; } if(!shmHandle) { MessageBox(0,"Couldn't create SHM bridge","Error", MB_OK); errorstate = 1; - ReleaseMutex(DFSVMutex); - CloseHandle(DFSVMutex); - CloseHandle(DFCLMutex); return; } // attempt to attach the created mapping - shm = (char *) MapViewOfFile(shmHandle,FILE_MAP_ALL_ACCESS, 0,0, SHM_ALL_CLIENTS); + shm = (char *) MapViewOfFile(shmHandle,FILE_MAP_ALL_ACCESS, 0,0, SHM_SIZE); if(shm) { - ((shm_cmd *)shm)->pingpong = CORE_RUNNING; + // make sure we don't stall or do crazy stuff + for(int i = 0; i < SHM_MAX_CLIENTS;i++) + { + ((uint32_t *)shm)[i] = CORE_RUNNING; + } + // init modules :) + InitModules(); } else { MessageBox(0,"Couldn't attach SHM bridge","Error", MB_OK); errorstate = 1; - ReleaseMutex(DFSVMutex); - CloseHandle(DFSVMutex); - CloseHandle(DFCLMutex); + return; } - InitModules(); } void SHM_Destroy ( void ) @@ -159,9 +195,13 @@ void SHM_Destroy ( void ) if(errorstate) return; KillModules(); - ReleaseMutex(DFSVMutex); + // get rid of all the locks CloseHandle(DFSVMutex); - CloseHandle(DFCLMutex); + for(int i=0; i < SHM_MAX_CLIENTS; i++) + { + CloseHandle(DFCLSuspendMutex[i]); + CloseHandle(DFCLMutex[i]); + } } uint32_t OS_getPID() @@ -181,17 +221,18 @@ uint32_t OS_getAffinity() // is the other side still there? -bool isValidSHM() +bool isValidSHM(int which) { - // try if CL mutex is free - uint32_t result = WaitForSingleObject(DFCLMutex,0); + // try if CL mutex is free (by locking client mutex) + uint32_t result = WaitForSingleObject(DFCLMutex[which],0); switch (result) { case WAIT_ABANDONED: case WAIT_OBJECT_0: { - ReleaseMutex(DFCLMutex); + OS_lockSuspendLock(which); + ReleaseMutex(DFCLMutex[which]); return false; } case WAIT_TIMEOUT: From 48edd3a1ea1c538ca451d9b2d1947cf5f009ccea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 13 Mar 2010 18:09:44 +0100 Subject: [PATCH 5/5] Updated precompiled shm libs --- precompiled/linux/libdfconnect.so | Bin 12584 -> 35902 bytes precompiled/windows/SDL.dll | Bin 13824 -> 32768 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/precompiled/linux/libdfconnect.so b/precompiled/linux/libdfconnect.so index 6db5d6d3ba942dcf4e003eaa8e2125ab9f579caf..7e76d2c3730f90956d773391021bc9968de0a812 100755 GIT binary patch literal 35902 zcmeHwe|%KM)%Q)Zfdy7J+O(pjKC7lJRKx@+5}_I(8$c6`gcU^X5|RyxQ%z7F?oxNJB+li|*T zI|ptK+-$fkxbMJGJ}mz~;P_kucP`xb;JyKOA>1@Lw$J5oe5S+AfFsZU+h=S-#_s|? zJs~a!@HjX}LOemrcdjKKs-U>wE`z%ijxzfe9G~;yroc^vn+i7xZaf^@fX`Rp&VoA= z4yqV^GK~;*?KC6IHk$zFG~lnoC*N!j(qp}nlN4LyZOT;pH`1HBZ1zP=BQ46Da%20k zzI@nzW;>B@)`_xbJt*%SI6g_8=QIt!`2NHvLLW?gdEJW1%Z5J7+WqE_H$A&ESzKbt`BuLkX~q!hvN0Fsj8{=)&+|F0k2o_R#aC66mJ2RhBAsfl{B}kMy!ye?@hm%v(~vR`GhO zH&j%YqOiJP70O*%6qqr?>kU?uw+g{bskfx2cB8j=abeaBQD{j`RaJ3yX;GG!r7p|# zF7p;$>@Aw%T?$5)WqI9#)w%^)?xj~2fp1Z@)u>Iix1=~wQZAD%%=&$Sa(~SR1Jeg~ z3_S0e`g(7z&tF$Vxdb+P*Jq*{{_2w2jY^fTiXOm9U$rDT3xcXxQ|&IivgAe)6BAcF z=W+6aSnFm&0JSqRy+vMMRV~W#`)bilHU8o?KGvqRvJireB4%ZJ3%sR16kW5Cbu3<7 z600)`w?_R77P4l7R9Qu3C6uAg=MT7-WJ61;gOFK$)+JtVb&bEO7>Uq>VzzrBL=H*2 z-8Z6;Oz6J754b!P3Q(AkigUat_W7ZO?%DEFfIYy99(Df9c-)>eHwsep=$SiKo6 zrYJymB)QearQYJo%9@h(N-bi1085IiOMI0A??nBns#)(lx&9apA$4db)o3XcNI{iB zUGe+CXkEEdl$-C(tE{LMfra_rq7B8h^Mhq&Pz(ilYs(BhU*iiDm$5qqHYy0$R+K`e z>MYt`wYIdvuhi5+Vd~1OfI_b%P)V;ipg_|gAcX|#O8iP`W!=UqQRZTHRN#~bl#6yyH z{KfMZcxOzX84b;dhAy_mW=+318lR~wSg~T>{KejA|2>lF2iO|OaV}w2y2YZk|iHiyNV=?a7=tIDE`ry++pS;lrr|I-D zo=&2V@+Q+qja~FHG0&h6iB6&K!1{weB$!DbllYnRF=o%E40?sA zh<>`FAMuAnLft!E%eV-l-uc_rzoxT&&QmG{x=onPWsB}Y_oWeL@=(8k(+M+js7t^QmQv0? zLmdK!aFlWv;dTKtSU!bttAH~IXA*7{Fq6t>6Rs67Dy5X?5MCuB4Cm(uO~cm91-{B6K*6tC}0*`Pq>|MyMR{_-a)ukz@>yc2sa9ttd;K~Tr1#8!d-+{37D*w z_Yp1>aDZ?>;T!>z_3}f6GX=bf@E~EAfEx)%2&W0SneYf1n9?SwN4Hwt(M;n{?11-z4R z4&hY-?jXE~aG`)Z2^SL15%4a;D+p%_cn{%Kgk69EhzI@Rnxe?B5pL?atx8eUqiK!1 z2bdJRL07*(#ap@p6Lqx9fX`hZHMu9>YuxhC#&xzWslUkLXRO) zQM-~ffi}8LC5FtCJ>q|RpGxvcYH!+g-@l=U{W607iQ*@S;?woEBfv+u{Hm@VmgP?f zi8w7iW3*rGO~3Aj9({5~Pb;6uhH1##{XDb-r1Yj^{98`Mf9&M=pTOuebzgM?zMhk=C8PY8MEXT1OD`irV)^$A{L!^1 z)F&r>3(#3fxBUjKTibhZsOj}rAFFzG>NDMuWbNQL7yme8@sp|Z(ri<^wZ861O2}bv zdOf^ua#-v8d{fAdNf^EBamTOJ&(IMgq~&JVLjnUKo30Kb2espCpO22p25Jp;!b(fK8(AXgUtrSjMLe;&MwHm0 z^-5?DMn{3a911yEuAe!2zsBF8|6pog|?82o-+xAY-ks%VdTH#$OwD#SEylA&+df$i_$`^ zW`5))3Fcp&m|s0QNxR{r$X$ZSdJxfq$FznoBJ(gkkvgYe(zK71Cgj=vmmM)_Z}+>Y zT7UQ8n5q3!yVXyVoZGs#s8k;3V_jOFBa*?wgi>mGcIfX?n!Al9d{7H^ zwRCCjK43 zf<1HekI)IU!pXr))!yqxPl4Pd;*;zRdeZPA-7_d-)OQ`4XOEsFQii)&y*ofs-8}*# zO2DP|jgC0G=h<7jZh1K5ew$Lz-Ft{O{*%$s;h*csT(C!(1PunBO}s>WPLXc zSzJ3b?3sGZ(hluxht9wP(FT1$Rt!s|xjS@EhiHnOq9M9avlMrqUeKjyW-s|DFo#x6 zcXz7pzDe2cUjLxpCZt`kT~7|@Ocs^}Y;^wvY`Aw&CXXRF^b-`(syB3G??+?p(Hq*c z>uteKz0j`bK-T?K5*Sa|?ZAKO1&6b`G_-ZY0d>fs73|WNLm{?7hA?|`+yhQ^Bbou4 zpmle@Zqoy&+x3ENU@bU^9D@E!Mh`9M)gNWM>t}Yqo|YZl6|yC1^V7rgC$qxA3+4oO z1}oIwO+p>?h8>#w9t}NYj9~p9vVJ#&Is8`Z@P}s8Pt!fSWb4mo>z|=F>{CqN}0?UC!oevj&|Z&s%yB$vetgWBhv(_$RNryALg&eazXi1*y9G zkO8?t3W5u@-AUo*2uZ75cFK1w<%?mE+`dL`iPlauNV!-GV>Ni(Ql`pBI{W58s8nt&KMCDR>wJN~kScP&kUPr0@|!f`?-WBudh2 zQaB(ej0g(3g2ES33LJ9<1+G03DSX4E@P(kD043CRm7w52Sn|O05`qGE1xSJK&`f=J z^?gGQJB1v$kq~P03JM%qC4~%x@Lx&gz5yv%HR>*tLb=H0A|jq}<1~as4{Dr=kl=v( z3e2Ad1e1en&HU5YA6mH;5pFckU8ud4+6YI>y*7pRJml#@KF5>kAb_PL7Zje3P@Wy; zjPxRh-@`!m)@PVG?dOE)3M{CcTT=n+1sH6%bMlS}c;ruiLyUX-(D0}6n0KFm`H+Fx z#1srJ1#fGhgaITvu;^fg1B<&e`0mgzNKj7NoXvdUsy;(L3hfx9wq6GsAL^H)Wl?L$|fWkLmSY5nol3C^obLe)&S<1fUaXqfw5?L;$D24XW6{FfB`|$6EQouwM9(p-`J;?dS z?Wk^3PsMeRBi93i5Vfwee`0|=wJ$ciEI5E=aIW@@UeKR4n)NZp&D=}V5tUBa>>5TX zy2vY6Hht>8q^bRCcv6%=_CEg~HO~R9pkHt3&u%ynsMp;GLIv;WqAB$@F_kaCC_kVz z^on_fdqA&u=(YCjPXg!ZI=Uf59$4QBlfp5$Ke$NUmzMoW@O{h@^m=>CXkelS)kOdU?8rf1)*X zM$5gJuq&_FKT1wTztx&I80* zH>%azhaY48xt`EoLJPf%c5Uhj<25~IgqnL77gmo_)^i>`g1^q@-=h<>eC%v4fGLrJ z4CO_D1qBaX!9|Z&(4|d`pGX>0NURq^c_W;I>6n^f8ulEyDdgkHCMG>Kb19ExO z#a{LR3tgBlYo)md^@TR|y-e=Vj9lQ}6;&ZFC3l@o(A)*9p?GWdI7Vdtc6(bb&7n)Fq32Rbu%;A z2@YuBuHg_gG0W6MXYyo-TtTzlmG3_iS9h+}ZJt*WH z_a0|UC6Rh7c%DOqx*S>( z8ZkVNgGO|1&_i#EK_yqftx)^aE4$ z&Q{caAp{#*ZVxXM1&0@kQo{>nX=B-1&`~xR6tIA$8jX4fMTW*hso0N!QnxeH?o{d$ON#Ha8+xa-oJZ1sA)p%xd{KP^i0ig|10hQkgwr zjPCAb*Gw}xf-gi)ZtS_XH8&6zcz_NLgb{rW)w_x48CwRAHNduO!gL6E+&St)X zl2(qR0ay)oX$^Z?^_v{JpT_Mwu?0#mda~4`Iij(gE!=XKy7Z2IlQeR3(Tmz6PrpTO z+Vx~Al!5#@k@rqQsli_u+XXDE7YxEuPhbVnkUDb~?4XVaGZL~bhMH*7aEp!Lm|jw2 zLWO9@J=(#nuAy<*zQbY}S_notI}qDILJL6=gt`Y)hSuRF1myh$vT|UiU3G4q4S>xg z4J#|qDJw8^4pQQAyI2b39>d*Y;QmUa>;b{%MlcYcEg6JD1xF0q%Ob8evPCfLnd{iV ziYI{=n=#abB-XR$Kzk0d43)B&hEDWa&D}2LB@`plg0*h8B@GCIr- z-ya<=WXl8>vk5U|ygPIWS%LX+2-2A-jdPFk7~Q{-=Z5Xs#{*83j}bJ|hNWiq@!$uM zH{O&*nghv$@d7O&5l{PY8@8zo`u&Ka?Xkz`hd@6Y{qzhh(NQ$IQ)C7U_yh$sp#WNb zRQjjPQP4r_{49%=l`fRbp-W#*9zo{p7#S}AvxD1%BZxw0n?MCahn|e=k#CbS$i5~S z8GghtDvu-2@bkk*nAQ_x@)&|mpVHDfZykf(TA_10(ckzMo)3QIqK8|Mq@9XSr$7QdhJTZP{11FcG6NXD!yAGT) zIH7j0p^mKGQ}8AK(IFt@-jgs2#Ojp~!CLASB8c4|)@v8X=)`(;=?!~OuW2zWdb+3= zOl@4fg2l2(q$XTio`Kgu$?%A##i>$%! zoRRP#d$NdJipZflSOCjJ5Sx5a5=boXBaahi+KN04@iurQ`^9M-L_OSrS zz7^7@oft$LAX2LChrnd#z5qTnk<*ZN&9hxM+A(+`>)Ws$>x@~TCB~k(^b4Zt?~)7H zva2~mEwtZ)Vrk=qo}WhQzq6+HSyJDB4XKY06`@)~C9IoVm{FesS6kOJ+sQ&~VfHxk ziPHh*2I@sty#$F;(Og7QM9NuP6jvZJ|STA4wBN+o3lc2|lYeq=AOo zJ54ADQQeTsoQAXj)H2POF8()itM0V4;ddgJAgJw&jDT~t+Y}(1{LRH%-pJPke;*d0 z+tk~z2meW6y6l&7{*Hx?hr6Wif#|$)Z_p*p64pC*eYQEPeCjVKvv;mw%c6dGa{^m& zz*N6Qte5A&@b86|^lJ?R#%y(OaJ;DH09G^ivysiQWeBS!g8-(0k+BAW7tFWmt*vqO z$wLL3dd?U1G1hEYOmbpsR0zATchL@rN{RQG=6pFg$5JnI9e03={l;OzwO)W%nsOa? zu~}6~b@&BKm7j>OvIl};mH*q+u(;vi46Js_6BNRKPZ(UX~ zy&5m3LCNuOFT`ZZ=X$nRfjx3QbfKw7$w!mt*z|E&-DLM^1q06JX2?a#D%9|fteDn- zaidSZFXl3KqeEXOr;5fp*xB5PT=8~V8YW|D- zakY<^FR+Ufu)yc2??a}OERgTU`#MZ-O~ra&k{)hW8~T*M_bkovbFmWcHD+UxeFoy@ z=+p=+cqwA6q=yz!Q_R`aaH5FK5~+y|2BBV4zLk1GT4Wy@v8m_2DmelXy zmei=zx%m+6uO4)Wl@``IjGX#h_i>ws4H(#hYrzv1S=wH_91C4#*Mn&q<~{|xaO5DV zAEy<#IBCcG4AEYg8l9())y8lMh1E0$HLNvghCnFu6^H?Eb@E_;y3)joHP{vWBP&wS z5q66ikUj$@O?w_|&Or9RvS))YgPvi}dTGy)6>H$&voKmaS-hn=!g_7;Gp06B2DM7yG;S>(*wC&n8HXi4I$g$`) z7tv!w!NXizVLJ$-|QYuGhfA{j8|lu6=-+Xi}l=f&xePr#1|h1-&<48r_dbQ{PWJ)yT3h))L%@ z@btF4aLXU{21iQ6t~m{kz)WbDL5DBBP{%_;Il3&uRs|1(R1UH6^=I9EIJWp}{^)pg z_;oh_l%llki@t=!kmnt9l`|Xi80ujh-j}-_q90(2>)zpP;kmM~y93q*{ikE-JO4z* zJ!uzscHv!`oXfHMAJ-Z>41Uo{=UaI$=#t7T<~`n*C^x}#Sm?8MJop^jvY`XxFZP_e z-#{0EesedWzybaosIh$Y4I`Avc60AS26m-98${)7PNl7Y9-m51*NIFPISsvtnxgfF zs!$p2n8EjOlT|>?qMp54fta#!f}c*-3+@@Z5h)xlutgE|j^mD#JJ|Aent>!^0tU4OD?xIe((zxhJwkh*{A1waD0^HtbXa3$lYd15=Fb zHZyzVKaE^VW4UfcP;KZ?X#T~buM9Ymb@+ol=9FMs_uICVV8@(bd*EfvK8^X#BS=== z?U?UyboApTF*YxqTi>R%gV=NwqQq8CWIyRZln0n%PTYn{*&3+C=M(_W4jd26Gz4}n z2}584Ovj-x^gpzo2p z5oH;l^Pw4$&8UAU#R^B4&CF>vv@!UXBqwubDn}=a;{Uu4bv1xvDp7xUF8E7>v12#4m3VTm8DyN+fP(Gqt zW9EsS57b>9O2T$D>@-ft3{d$oO7+2%%H49YG~wFnKAO^ow>hC}Fy{;N&2`+%RMEX@ z*)PGKHo(Fk(2k+IrJSi1+RT$>kVp4B7zZu`=Pjc*7vp8cXIfa!D^4~?79)S;Q%Eqz zFAn*D-*=N>(VnHm#B>Ly4T2LxO22+OB!agAgCi+pxq9iF6YS?cf7sdlE%uVmZ_(O( z*}Qzxi=sd=FCUQO7+xa`U>w6ZfqM0^UZ)S856jTnvOnzZBf-Eonm!f7_pN+k21!DU zRH%nQ3=Cxl`vW;#S&|dT;mgXu^M!=+ky)0Hk@+T@J_FkM-S`Q-QMZ%KcWb2ij-SVW zd=MoRU}*)@XxGM@jg|C_w%k5+rLbS*KM7L|`_LS~v8k4G>zPdFgg-hjTTX^Oy+i** zAB$n$(h`rAWnis^m?Fz47Ko=3{+x!sKptdcboBjT4o8E~(TQ*+8zb14{fV>r8Hm~F zl)oenI%NdQo=-uSqh=N`Bh!$;x!_Bf>?W*D`!MrcP4h}ASymn3c5 z+=Ic|bO2n4wdp#LmTOaU*@7cYShhsPPzVHwVfbM%RcbJGJA5%)g{kY~O!o)<;Bqtn zG~v(CTn;ae44$9GIXp~IV&`*;XezElrJ8zX=O{|!Y$bS>akvX-8Tc|*!3kz_9A9KN zPEn1+$;d0x^hFNL`madWbJBDrJbxOH6>a_`W)SD9QQkFB=c34GV6ds@CH$l!G|y&` zm=XFZUli-|{M|a6?zd^R_-lv7)%^~w7JJEQT5X!{PuFVGb^j!-c9QO&tkq7|{VuK6 zrTa6q+6;Z^lzmA!J~zcUpN%8oJYT4Z0?SY!4;KdQIAzGeSsnpX^zr(-G<{>bKC?rg zH%XuQly2LMcM9{SIGcZgDnMPEy2%?RxdkInG$JZx174PleE3WB_3=od4|)2`w|hKwidTp7uom9^yL%%y{*g#Lq6OY~ zN=v**wyDR)J2yXtR&MHUN8R{ht1~b|j%Xaq%1w^3bJu}=53!GB^7}FJ#rVY>nAR!w z=HnXX6^FCT419&L6a)OJ~Sj z;IP_Znk zbP|f~X}dNn%I z^5{q>-=~0o43N+47km$0LpGw*_s@VH+YOBEcZvPiS*TxA&sXqs$^WbU*G!i5KihwO z8Z0O5zn+KO#{TQ$eGvJ+NG*^k`5o{vz8d4*U1*P{p7pb#^Wyx!G186K=S|`E#C=c! z%aCRcT=7ND10XA`ZW4o$p9`m4e=4 zK@Y#+;+as;I}4Lt(%T=HggKEx68cH1{1q41SrY_ZW*B}IFyxQle+X)y&yw7!Jc9CsS0K%U zJqG*VCHdelLTWy?e`5r3iop?2^!2CF&DNtV(p4WKi%DVI<0LoaWwe+G#^#&m??0f- z`27cdQ|ZcG1&j~>Lb3;j zNk>%$`@_1q{ub?fnPBznV72M!HeiLsPoyXlN}>@lnD*eW68S24&_!xfFEQEJav>P2 ze^KN;NVuuzSqy9ZiSN2zbZ}EIP#({(9S|4~0Hf*XJs3cJzeoo)yzR;AYTW$=i*mL+ zfEL@Q5=aXDp1(nid>fQPzdHg`DcV)GuV{Y0PtDCp!?AiP6G!Aqa2irW09l8B0+%xU zM~?-D?S2bzNWF`hu=ThpBMns8{C18S%t)a{Mj=R~GoFMPKacUZN@`IJBeJF*;9r#W zGPzanBkdM$RE5;vif|*sx`>f9S##x-WvizsdjiT%$e+l!rm_}#3kzhuBA1+k)*tp7 z5(Oh>r8SlPhxIre^@#DSvS+fQK;!}1Q**u_`A#Oat5KT4xtae&&M9~c=MATzm3mUn z$rm|a4jEZE&pBjvcEc8cHwVRFKw52iyKmRe95?U?;l`^yTvf5 z?x7Jtwa%tw#Q07@4c{m2G&Uvxos4zjhtvaKQp#+UB0dIy@i(OQ809>I*jPD3voT^t z-iGj^`xix#%OJa^p08g-dz}tb4-^!&p9IikkyWH1V>OjilaPAQOv|;V@i==(J!Ga$ zL7F+2(NrFB56shbB4Gw{35K$ZLn`0BqL7o0=H*X;)n~aI*v8Dp97R>WB+GY^1*`&F ziD(cQ-{%%Zz7Gv*>X|b=zJ6brd^XBVCo94<mp{Ft{CS7q|emc=Y0&Rcrut8qT0nDBeod$@$O46V0fy7erN1#Aj@TFBf zVQz2{8%K=OCOP7QMZx2_DEyS;wir@b+#FFaF~<~fX72MDFGlyU7(p2y7K5e;VdxQ< zr2*e~1=iAu+Sp*-eNUt ztP&^ds<8cCf${uo2p5X@UAf^$NAG6wA`Y-w_gUgYbb zGyInJacn)J9-Yno@@oZ=Ss)T!j|s`Lwa|~K6QSJ-2x3{rW-oE3VML8Cd;_iUs6`WVcef;eg^zZ1j79jl1%Hx1%tU?c|;yB6PSDlIF=r`9fY z&G*-=^|@+-0as0#tFor1cABduQ10`)YW+1OzPdVBX+>ReZLP1^?<<`?UXfp5y!6ue z5BDh^>n9Uk^23bEf*QY1SsJWH+8X-jDn-FMe15LfRa5P%_SFYm_`)N;YbPU3-&d-* z#h2)q-BldG7vaj40`aLw^0ziv3&P?Xj3&6Wc!P`I;Zv64k1KciRZDYaFczAB?J~DQ zybXSQO;B0RzXqlZ3sf1DxFl7yEFUc5dy!z@jjsV-k3%II^Yh88t!lkRC+Lbk{p8s8eib|hrH9zE6Cz&RN4dpdv ztRw@ml~j~f_)1+RH8uWH&=Yk{#4YpJRJn?)UB#uP_;w)K66F#tfUh31iA7Pa+8TTW z6JHQ?72`j6s$PTgP}N{bAc#i8Bj^{q{KeI4V)^P^t9^kDK3}yfu%X7qijkzrlCtco zW%Ke~dGi+MdzQLgSLM0#uUc^B;w1}RMb{QBa~E)1JNiXI*9H__f=?ZyPJs;-B`CxX z$@zWfj#o_GT#HXbBDoYi;ERK6(8zT)K|i$8B`R9tFRsHEK3%zaP)svVT}>4}s7dS% zzUqLhyvFaZSdE6+;IFA(0}f0);|rF^URPcW-9~NJR3QgQDQpebH%ndo1|d=cH70q3w}TRx$sBeuYjKei^lgH?eKYqx*z^d_=T`9X&6fz z;qy20{qPsTPlNR;gcEV=X^CZf@$A2_^Yt_w2RX{>_C12zXLCj zGw|brOdOCn%JSg%AzwQVcw{2I>(8U3h45X6Mn`Mmulnog=pp!xZ=ihSYlUA5e*}IX z{8evqCJO%u%Efkb#}N1eT=*{df}c4Ix`=OtpAWwuemQ*C(b3Un_{!f#M|Z&QfPaAS zd!Wa3_%^KEIz9xy@LN9`9nFE?{*Teo>)|V(jgD@DUkLwBro-=qKM6^El5Q$osw8c) zkGcEfbMb|Bht0MniD@plMV)91_)7jn`}~&V)4-<4HECDbfN+3!H*x zE7Iyuk#8r`Za+o7KBToGtq{I4FP_gMi0VZed5Wgxt0|)H&^i0>Mn}JukTw}GCv20E zRu4a#Z;6_^*>-CR)A>Md`9g4G@BY0Df|HVya?3#aTr7dw;oDw=QkZ-#mNZy?BWTZt zZYP${w0n@2k2E*J(YyxVoO`gZuniK^29UP>Bxx?7jv%cMX}`ASyIM`nyESDqI8=zk zne#IAt2kK#X9+8gbk=<~a1_*SAG~P!%VfP=h;pHvRiJS-eA_5;nRPehN1R-w-Ge#D zEYPwIoruG7+L2a{a=roFC=H_=rgt$NryW*V)0fBi*n#*RIN!nVTifollwskO_GcOF z1HTYy8*6DNOIuGt*7m)lqimBXAC|hLfe)UYos8_ZPfjc+1KDN*=L>9{{Sy7o_8*pV z7TlUrK$d~yg1a7g&3Hw!5oByvS@4YVs2jVGmid>_QTD|sKL|2y0BIRWTWL+ZN=}W*^9<{W1E$9;8isYjhO9Q4oAu(`bWN zA=yE|+>em94QVeL`9zxJE#KsA5_l?n8@BcoIF$2T;2Z|dd@GK@+X|$my#u{X zNTcimNJ~fBZSbS=wY2Rv#5cp<^XFF4bc1dO(iXw?^ZV!4G}1kQw7D1uDEnxdh3-+L zZ~FVm`L&~ebRZ3H1!H*)d1WAN0BN6F)1g`VD-HD)ks}p~O3>GI{ep}M-K;xm5 z)NNx@jnR>cP`e{_aQ} zJ}u?IX{moZP5GQ7^?`{v9Ai=y(*Nc+5M7fVu_>QZ%;Dtp#xcr~l+@2tl}}SrU$iN2 zvJ_FDFKN^Nf7)qEernS=w9`G~l+NVTSM18m$*G&iDJ{02B_rykF{$n2l`q7Xi$tyd z{pa5j__qZ9ErEYa;NKGXw*(R;z!#mX#0w?P6=L5W#|%Q@ehGcv%|JgH^Gy2OKcUY% zA#k2s;a7|JVx0F5od%!x5g|&M4Wxe@AFbhUFFC{5hIJ68mP#||ag**5AWeXu2FGdT zN4Ll?pp_#ZSvno=Ot?W1Ww;(bK2spR^#2w3SibRd@HKrX9IV%n#t3|VXcOBVHF8#i z$p6LD*@*ig;LImtgn8$l`TPj=y%+8`a8JQK2lrRF_u!7hO+>}cftwC@8Qc=M@55EY z-3<35xO?G#1NRi%b8vr!dk^k7+(cCR9JuLlm%%N8`#xMX+|6)5g1Z;)H*inEJqPz! zxcA_W!%c*+&Vic_cNyFgxK7l=Y-jpxCtPcCscVX7b+9@Rbj_STb9&Z=GlK$}JypVA zf|6~bDARG1OQ3i){D5EjQK1A0JXQtDq!x;wUW9T{Y*vII08fowZ z4)JEgv7Zr-eUB~;2`mF-1s>E*$$?`ZA|CrH9s8*H5SPy)IQBQBM7SuYxo2XcOpYE11sunRx3FCa<(Lv`KWC z2%8V__^g97@#=v?H%KOohj?_G;7q(#=tFcn@(d(+lpUWR!kKuqfplD_n~z!U7DNz_ z?VJG|y3Z&O;~^f?TMeHqS?}uvC?1pEy?}^Myw2}Sw}_QD9usc|U=#0)QtVB^ZBM8# zvpi|wktJ~tX?6Ual_tGz3tm6$Vn6WMPUd6c^&x`glHMWU9Ri-2MqD%eM})~X@x=Y6 zz#Bo>wBy7heV)`Z>G6)A12ytKOhk)^cyv4-X5w*d`3QJjh%+A(?+7AHyv|0*uU3vf zRy>B^g=6{Tudfjfc=ZUI59#rFACB}g3E&}~MO{Q1(_x|oE#mN*2!%W*gW@l7_|UgB zld=m6-hly>drfQMSZ*2|`v!S4pYkTiiUY0jnDk88>YE2YQbcLe<}h8}K_zXAgk=e| zArh9grmc{$Y%SUZ2}?y}-iFG!fJBK2Ov=dR%6-=cbJCuiIQ2b?mo0VYiTQz5X~7MORgW?JBBfTvhsj<3@!up9793tR+vt_3az+37x zjq;NxDO&-Lpg#5RlaYQm{Kgzb`2c(pew67@i@65e4Y>abdFLk6{{+|tc!!bxH^8-+ z@SbbHp94Epv8R>Td&RL8NT?2lU>65U(Zoqwj zdmCf?_5-d(xA?V@{yJgc^Ia0l|2yEu4EcVHa0>VwjOIT}nE==YrKUY(`dNV6E&TA# z*iPW{NkaQv0=RJ=-bcVx5kD93fT_>PUs0|GoZcjDD&_Tkz_n;EsHDK(0Qg9w$^Tgj z?-0%e|7Lq`1-vVoK1umG;CAG98~Gmw+y(tG={*DZXcYe}r62G;Xg@yW?;XG$E996& z_&DG+CV*QMljF^ec#ublMr# ze`u%0lqT^{w`qaox^89kIjejIUlg~j^P^tTkkObDPWGDe0Znwe<$ScOo00VTkl-H zSWa<7ewLS;0GA}dKeWIu;NJl_2mPH7%l|20>z&H~1=xCr@(xRWme-pAziWZnK4)3x zjKrUr051h>t^c(N={F_7KeoWknjNz>8>`a9T z-ip$CLNjnI5ndXw_YaBWS^TnR8|SY}}G8@8|Wd z%+K~lPgw!VlHkJlt9p@e3Gc4uWw{8?7WZHCqTOhIaa=13iKANHm04L!f|ZrVfk)iK zoVy%bEoIYjP4bDCLwi?VQnaYRJFg_b`((A&WK1mA6bwo-DPQ zCyzIRGB=rgrJ#__~OE9jq$nCE;Y8 zr%vaIL#KjJR%RY<2$!76h^#AdDly7xNewpVd~C&RDagXbxq{zJQA(5}Z)shPw;UIT zV_OT7r095X6sWSNNUbFo-jYgjE|A(#Gx3yhq!J!G+VA#+BRF7;)dWHA3D6FE6K$#q-+fxNGTSNxX#`J-w=?T3&ul zi+9C(+}2Jh`TPqiapk+O&U(*%EN>>{#GBiV7=vrm0ErD*$&YdWd`^z#8f>&dHSU+U zQbVg12dokCH>hXj<`ovwj-nZHB|L6tce{;-!42~ZvfR9wJ)W1?4dV%>lAW5CQQkr?p8GLihIptF*D@#<*^6~$GrIdWcS&}lQ%6rjXV`h!L8=#gI01?zN$od z1^l>p-ijlyO}EAfCLkN!N)PcAFD`M%7fcl`vu3C(!FgwzYxAb}SfbDs-g`g8>kV2@ z~DEjXgs&Huu?K(BfKKTo4~)igG>~OC>e68@_>0&0 z5)>CBMlF6e5h(MPl&_6RSlTo4vKl6=`efYr#EYDhO>g44_EcjcRSJgcf>rGM3l?}U jh83134b}}K%S{%WKqKp`uZS~XuoCD=G47C?mPh$N2iox! literal 12584 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 Qi4-|6FwE{0bz}K|0VS`b8UO$Q diff --git a/precompiled/windows/SDL.dll b/precompiled/windows/SDL.dll index 1bc32d95425d3ee873f65d93ba3bbe329f4d6007..c03b5e0dff744a41d294aff73f87d92c3fa3098c 100644 GIT binary patch literal 32768 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 B^U?qS literal 13824 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 Jz5nUw-vLf3kPH9-