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 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/LyUgi(p-Cjp~MzR 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 literal 12584 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 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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 NIFAyeAut3&005rv^U?qS delta 7478 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