use of memory barriers in the shm server and client code

develop
Petr Mrázek 2009-12-27 03:51:54 +00:00
parent b0d65de269
commit aef02eddce
10 changed files with 232 additions and 30 deletions

@ -969,6 +969,10 @@ bool API::Attach()
{
d->pm = new ProcessEnumerator (d->xml); // FIXME: handle bad XML better
}
else
{
d->pm->purge();
}
// find a process (ProcessManager can find multiple when used properly)
if (!d->pm->findProcessess())

@ -28,8 +28,50 @@ distribution.
#include <sys/ipc.h>
#include <time.h>
#include "shmserver/dfconnect.h"
#include <sys/time.h>
#include <time.h>
#include <linux/futex.h>
#include <sys/syscall.h>
using namespace DFHack;
// a full memory barrier! better be safe than sorry.
#define gcc_barrier asm volatile("" ::: "memory"); __sync_synchronize();
/*
* wait for futex
* futex has to be aligned to 4 bytes
* futex has to be equal to val (returns EWOULDBLOCK otherwise)
* wait can be broken by arriving signals (returns EINTR)
* returns 0 when broken by futex_wake
*/
inline int futex_wait(int * futex, int val)
{
return syscall(SYS_futex, futex, FUTEX_WAIT, val, 0, 0, 0);
}
/*
* wait for futex
* futex has to be aligned to 4 bytes
* futex has to be equal to val (returns EWOULDBLOCK otherwise)
* wait can be broken by arriving signals (returns EINTR)
* returns 0 when broken by futex_wake
* returns ETIMEDOUT on timeout
*/
inline int futex_wait_timed(int * futex, int val, const struct timespec *timeout)
{
return syscall(SYS_futex, futex, FUTEX_WAIT, val, timeout, 0, 0);
}
/*
* wake up futex. returns number of waked processes
*/
inline int futex_wake(int * futex)
{
return syscall(SYS_futex, futex, FUTEX_WAKE, 1, 0, 0, 0);
}
static timespec one_second = { 1,0 };
static timespec five_second = { 5,0 };
class SHMProcess::Private
{
public:
@ -38,6 +80,8 @@ class SHMProcess::Private
my_datamodel = NULL;
my_descriptor = NULL;
my_pid = 0;
my_shm = 0;
my_shmid = -1;
my_window = NULL;
attached = false;
suspended = false;
@ -54,6 +98,7 @@ class SHMProcess::Private
bool attached;
bool suspended;
bool identified;
bool validate(char * exe_file, uint32_t pid, vector <memory_info> & known_versions);
bool waitWhile (DF_PINGPONG state);
bool DF_TestBridgeVersion(bool & ret);
@ -71,6 +116,7 @@ bool SHMProcess::Private::waitWhile (DF_PINGPONG state)
shmctl(my_shmid, IPC_STAT, &descriptor);
if(descriptor.shm_nattch == 1)// DF crashed?
{
gcc_barrier
((shm_cmd *)my_shm)->pingpong = DFPP_RUNNING;
attached = suspended = false;
return false;
@ -96,8 +142,10 @@ bool SHMProcess::Private::waitWhile (DF_PINGPONG state)
bool SHMProcess::Private::DF_TestBridgeVersion(bool & ret)
{
((shm_cmd *)my_shm)->pingpong = DFPP_VERSION;
gcc_barrier
if(!waitWhile(DFPP_VERSION))
return false;
gcc_barrier
((shm_cmd *)my_shm)->pingpong = DFPP_SUSPENDED;
ret =( ((shm_retval *)my_shm)->value == PINGPONG_VERSION );
return true;
@ -106,8 +154,10 @@ bool SHMProcess::Private::DF_TestBridgeVersion(bool & ret)
bool SHMProcess::Private::DF_GetPID(pid_t & ret)
{
((shm_cmd *)my_shm)->pingpong = DFPP_PID;
gcc_barrier
if(!waitWhile(DFPP_PID))
return false;
gcc_barrier
((shm_cmd *)my_shm)->pingpong = DFPP_SUSPENDED;
ret = ((shm_retval *)my_shm)->value;
return true;
@ -139,11 +189,12 @@ SHMProcess::SHMProcess(vector <memory_info> & known_versions)
/*
* Check if there are two processes connected to the segment
*/
struct shmid_ds descriptor;
shmid_ds descriptor;
shmctl(d->my_shmid, IPC_STAT, &descriptor);
if(descriptor.shm_nattch != 2)// badness
{
fprintf(stderr,"dfhack: no DF or different client already connected\n");
fprintf(stderr,"dfhack: %d : invalid no. of processes connected\n", descriptor.shm_nattch);
fprintf(stderr,"detach: %d",shmdt(d->my_shm));
return;
}
@ -182,8 +233,10 @@ SHMProcess::SHMProcess(vector <memory_info> & known_versions)
d->validate(target_name, d->my_pid, known_versions);
d->my_window = new DFWindow(this);
}
gcc_barrier
// at this point, DF is stopped and waiting for commands. make it run again
((shm_cmd *)d->my_shm)->pingpong = DFPP_RUNNING;
fprintf(stderr,"detach: %d",shmdt(d->my_shm));
}
bool SHMProcess::isSuspended()
@ -239,6 +292,10 @@ SHMProcess::~SHMProcess()
{
delete d->my_window;
}
if(d->my_shm)
{
fprintf(stderr,"detach: %d",shmdt(d->my_shm));
}
delete d;
}
@ -341,15 +398,23 @@ bool SHMProcess::attach()
cerr << "there's already a different process attached" << endl;
return false;
}
d->attached = true;
if(suspend())
/*
* Attach the segment
*/
if ((d->my_shm = (char *) shmat(d->my_shmid, NULL, 0)) != (char *) -1)
{
d->suspended = true;
g_pProcess = this;
return true;
d->attached = true;
if(suspend())
{
d->suspended = true;
g_pProcess = this;
return true;
}
d->attached = false;
cerr << "unable to suspend" << endl;
return false;
}
d->attached = false;
cerr << "unable to suspend" << endl;
cerr << "unable to attach" << endl;
return false;
}
@ -375,6 +440,7 @@ void SHMProcess::read (const uint32_t offset, const uint32_t size, uint8_t *targ
assert (size < (SHM_SIZE - sizeof(shm_read)));
((shm_read *)d->my_shm)->address = offset;
((shm_read *)d->my_shm)->length = size;
gcc_barrier
((shm_read *)d->my_shm)->pingpong = DFPP_READ;
d->waitWhile(DFPP_READ);
memcpy (target, d->my_shm + sizeof(shm_ret_data),size);
@ -383,6 +449,7 @@ void SHMProcess::read (const uint32_t offset, const uint32_t size, uint8_t *targ
uint8_t SHMProcess::readByte (const uint32_t offset)
{
((shm_read_small *)d->my_shm)->address = offset;
gcc_barrier
((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_BYTE;
d->waitWhile(DFPP_READ_BYTE);
return ((shm_retval *)d->my_shm)->value;
@ -391,6 +458,7 @@ uint8_t SHMProcess::readByte (const uint32_t offset)
void SHMProcess::readByte (const uint32_t offset, uint8_t &val )
{
((shm_read_small *)d->my_shm)->address = offset;
gcc_barrier
((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_BYTE;
d->waitWhile(DFPP_READ_BYTE);
val = ((shm_retval *)d->my_shm)->value;
@ -399,6 +467,7 @@ void SHMProcess::readByte (const uint32_t offset, uint8_t &val )
uint16_t SHMProcess::readWord (const uint32_t offset)
{
((shm_read_small *)d->my_shm)->address = offset;
gcc_barrier
((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_WORD;
d->waitWhile(DFPP_READ_WORD);
return ((shm_retval *)d->my_shm)->value;
@ -407,6 +476,7 @@ uint16_t SHMProcess::readWord (const uint32_t offset)
void SHMProcess::readWord (const uint32_t offset, uint16_t &val)
{
((shm_read_small *)d->my_shm)->address = offset;
gcc_barrier
((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_WORD;
d->waitWhile(DFPP_READ_WORD);
val = ((shm_retval *)d->my_shm)->value;
@ -415,6 +485,7 @@ void SHMProcess::readWord (const uint32_t offset, uint16_t &val)
uint32_t SHMProcess::readDWord (const uint32_t offset)
{
((shm_read_small *)d->my_shm)->address = offset;
gcc_barrier
((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_DWORD;
d->waitWhile(DFPP_READ_DWORD);
return ((shm_retval *)d->my_shm)->value;
@ -422,6 +493,7 @@ uint32_t SHMProcess::readDWord (const uint32_t offset)
void SHMProcess::readDWord (const uint32_t offset, uint32_t &val)
{
((shm_read_small *)d->my_shm)->address = offset;
gcc_barrier
((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_DWORD;
d->waitWhile(DFPP_READ_DWORD);
val = ((shm_retval *)d->my_shm)->value;
@ -435,6 +507,7 @@ void SHMProcess::writeDWord (uint32_t offset, uint32_t data)
{
((shm_write_small *)d->my_shm)->address = offset;
((shm_write_small *)d->my_shm)->value = data;
gcc_barrier
((shm_write_small *)d->my_shm)->pingpong = DFPP_WRITE_DWORD;
d->waitWhile(DFPP_WRITE_DWORD);
}
@ -444,6 +517,7 @@ void SHMProcess::writeWord (uint32_t offset, uint16_t data)
{
((shm_write_small *)d->my_shm)->address = offset;
((shm_write_small *)d->my_shm)->value = data;
gcc_barrier
((shm_write_small *)d->my_shm)->pingpong = DFPP_WRITE_WORD;
d->waitWhile(DFPP_WRITE_WORD);
}
@ -452,6 +526,7 @@ void SHMProcess::writeByte (uint32_t offset, uint8_t data)
{
((shm_write_small *)d->my_shm)->address = offset;
((shm_write_small *)d->my_shm)->value = data;
gcc_barrier
((shm_write_small *)d->my_shm)->pingpong = DFPP_WRITE_BYTE;
d->waitWhile(DFPP_WRITE_BYTE);
}
@ -461,6 +536,7 @@ void SHMProcess::write (uint32_t offset, uint32_t size, const uint8_t *source)
((shm_write *)d->my_shm)->address = offset;
((shm_write *)d->my_shm)->length = size;
memcpy(d->my_shm+sizeof(shm_write),source, size);
gcc_barrier
((shm_write *)d->my_shm)->pingpong = DFPP_WRITE;
d->waitWhile(DFPP_WRITE);
}

@ -110,14 +110,19 @@ ProcessEnumerator::ProcessEnumerator( string path_to_xml )
d->meminfo = new MemInfoManager(path_to_xml);
}
ProcessEnumerator::~ProcessEnumerator()
void ProcessEnumerator::purge()
{
// delete all processes
for(uint32_t i = 0;i < d->processes.size();i++)
{
delete d->processes[i];
}
d->processes.clear();
}
ProcessEnumerator::~ProcessEnumerator()
{
// delete all processes
purge();
delete d->meminfo;
delete d;
}

@ -115,14 +115,19 @@ ProcessEnumerator::ProcessEnumerator( string path_to_xml )
d->meminfo = new MemInfoManager(path_to_xml);
}
ProcessEnumerator::~ProcessEnumerator()
ProcessEnumerator::purge()
{
// delete all processes
for(uint32_t i = 0;i < d->processes.size();i++)
{
delete d->processes[i];
}
d->processes.clear();
}
ProcessEnumerator::~ProcessEnumerator()
{
// delete all processes
purge();
delete d->meminfo;
delete d;
}

@ -48,6 +48,7 @@ namespace DFHack
bool findProcessess();
uint32_t size();
Process * operator[](uint32_t index);
void purge(void);
};
}
#endif // PROCESSMANAGER_H_INCLUDED

@ -0,0 +1,14 @@
#!/bin/sh
DF_DIR=$(dirname "$0")
cd "${DF_DIR}"
export SDL_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch.
#export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
ldd dwarfort.exe | grep SDL_image | grep -qv "not found$"
if [ $? -eq 0 ]; then
mkdir unused_libs
mv libs/libSDL* unused_libs/
fi
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./libs" # Update library search path.
export LD_PRELOAD="./libs/dfconnect.so" # Hack DF!
./dwarfort.exe $* # Go, go, go! :)

@ -25,8 +25,6 @@ distribution.
/**
* This is the source for the DF <-> dfhack shm bridge
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <stdint.h>
@ -37,27 +35,77 @@ distribution.
#include <sys/ipc.h>
#include <unistd.h>
#include "dfconnect.h"
#include <sys/time.h>
#include <time.h>
#include <linux/futex.h>
#include <sys/syscall.h>
// a full memory barrier! better be safe than sorry.
#define gcc_barrier asm volatile("" ::: "memory"); __sync_synchronize();
/*
* wait for futex
* futex has to be aligned to 4 bytes
* futex has to be equal to val (returns EWOULDBLOCK otherwise)
* wait can be broken by arriving signals (returns EINTR)
* returns 0 when broken by futex_wake
*/
inline int futex_wait(int * futex, int val)
{
return syscall(SYS_futex, futex, FUTEX_WAIT, val, 0, 0, 0);
}
/*
* wait for futex
* futex has to be aligned to 4 bytes
* futex has to be equal to val (returns EWOULDBLOCK otherwise)
* wait can be broken by arriving signals (returns EINTR)
* returns 0 when broken by futex_wake
* returns ETIMEDOUT on timeout
*/
inline int futex_wait_timed(int * futex, int val, const struct timespec *timeout)
{
return syscall(SYS_futex, futex, FUTEX_WAIT, val, timeout, 0, 0);
}
/*
* wake up futex. returns number of waked processes
*/
inline int futex_wake(int * futex)
{
return syscall(SYS_futex, futex, FUTEX_WAKE, 1, 0, 0, 0);
}
static timespec one_second = { 1,0 };
static timespec five_second = { 5,0 };
// ptr to the real functions
static void (*_SDL_GL_SwapBuffers)(void) = 0;
static void (*_SDL_Quit)(void) = 0;
static int (*_SDL_Init)(uint32_t flags) = 0;
static int (*_SDL_Flip)(void * some_ptr) = 0;
// various crud
int counter = 0;
int errorstate = 0;
char *shm;
int shmid;
static unsigned char * BigFat;
void SHM_Init ( void )
{
// name for the segment
key_t key = 123466;
BigFat = (unsigned char *) malloc(SHM_SIZE);
// create the segment
if ((shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0600)) < 0)
// find previous segment, check if it's used by some processes. if it isn't, kill it with fire
if ((shmid = shmget(key, SHM_SIZE, 0600)) != -1)
{
shmid_ds descriptor;
shmctl(shmid, IPC_STAT, &descriptor);
if(descriptor.shm_nattch == 0)
{
shmctl(shmid,IPC_RMID,NULL);
fprintf(stderr,"dfhack: killed dangling resources from crashed DF.\n");
}
}
// create the segment, make sure only ww are really creating it
if ((shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0600)) < 0)
{
perror("shmget");
errorstate = 1;
@ -71,30 +119,40 @@ void SHM_Init ( void )
errorstate = 1;
return;
}
gcc_barrier
((shm_cmd *)shm)->pingpong = DFPP_RUNNING; // make sure we don't stall or do crazy stuff
}
void SHM_Destroy ( void )
{
// blah, I don't care
shmid_ds descriptor;
shmctl(shmid, IPC_STAT, &descriptor);
shmdt(shm);
while(descriptor.shm_nattch != 0)
{
shmctl(shmid, IPC_STAT, &descriptor);
}
shmctl(shmid,IPC_RMID,NULL);
fprintf(stderr,"dfhack: destroyed shared segment.\n");
}
void SHM_Act (void)
{
struct shmid_ds descriptor;
uint32_t numwaits = 0;
if(errorstate)
{
return;
}
shmid_ds descriptor;
uint32_t numwaits = 0;
uint32_t length;
uint32_t address;
check_again: // goto target!!!
if(numwaits == 10000)
{
shmctl(shmid, IPC_STAT, &descriptor);
shmctl(shmid, IPC_STAT, &descriptor);
if(descriptor.shm_nattch == 1)// other guy crashed
{
gcc_barrier
((shm_cmd *)shm)->pingpong = DFPP_RUNNING;
fprintf(stderr,"dfhack: Broke out of loop, other process disappeared.\n");
return;
@ -117,23 +175,26 @@ void SHM_Act (void)
numwaits++;
goto check_again;
case DFPP_SUSPEND:
gcc_barrier
((shm_cmd *)shm)->pingpong = DFPP_SUSPENDED;
goto check_again;
/*
case DFPP_BOUNCE:
length = ((shm_bounce *)shm)->length;
memcpy(BigFat,shm + sizeof(shm_bounce),length);
memcpy(shm + sizeof(shm_ret_data),BigFat,length);
((shm_cmd *)shm)->pingpong = DFPP_RET_DATA;
goto check_again;
*/
case DFPP_PID:
((shm_retval *)shm)->value = getpid();
gcc_barrier
((shm_retval *)shm)->pingpong = DFPP_RET_PID;
goto check_again;
case DFPP_VERSION:
((shm_retval *)shm)->value = PINGPONG_VERSION;
gcc_barrier
((shm_retval *)shm)->pingpong = DFPP_RET_VERSION;
goto check_again;
@ -141,24 +202,28 @@ void SHM_Act (void)
length = ((shm_read *)shm)->length;
address = ((shm_read *)shm)->address;
memcpy(shm + sizeof(shm_ret_data), (void *) address,length);
gcc_barrier
((shm_cmd *)shm)->pingpong = DFPP_RET_DATA;
goto check_again;
case DFPP_READ_DWORD:
address = ((shm_read_small *)shm)->address;
((shm_retval *)shm)->value = *((uint32_t*) address);
gcc_barrier
((shm_retval *)shm)->pingpong = DFPP_RET_DWORD;
goto check_again;
case DFPP_READ_WORD:
address = ((shm_read_small *)shm)->address;
((shm_retval *)shm)->value = *((uint16_t*) address);
gcc_barrier
((shm_retval *)shm)->pingpong = DFPP_RET_WORD;
goto check_again;
case DFPP_READ_BYTE:
address = ((shm_read_small *)shm)->address;
((shm_retval *)shm)->value = *((uint8_t*) address);
gcc_barrier
((shm_retval *)shm)->pingpong = DFPP_RET_BYTE;
goto check_again;
@ -166,21 +231,25 @@ void SHM_Act (void)
address = ((shm_write *)shm)->address;
length = ((shm_write *)shm)->length;
memcpy((void *)address, shm + sizeof(shm_write),length);
gcc_barrier
((shm_cmd *)shm)->pingpong = DFPP_SUSPENDED;
goto check_again;
case DFPP_WRITE_DWORD:
(*(uint32_t*)((shm_write_small *)shm)->address) = ((shm_write_small *)shm)->value;
gcc_barrier
((shm_cmd *)shm)->pingpong = DFPP_SUSPENDED;
goto check_again;
case DFPP_WRITE_WORD:
(*(uint16_t*)((shm_write_small *)shm)->address) = ((shm_write_small *)shm)->value;
gcc_barrier
((shm_cmd *)shm)->pingpong = DFPP_SUSPENDED;
goto check_again;
case DFPP_WRITE_BYTE:
(*(uint8_t*)((shm_write_small *)shm)->address) = ((shm_write_small *)shm)->value;
gcc_barrier
((shm_cmd *)shm)->pingpong = DFPP_SUSPENDED;
goto check_again;
@ -191,12 +260,13 @@ void SHM_Act (void)
default:
((shm_retval *)shm)->value = DFEE_INVALID_COMMAND;
gcc_barrier
((shm_retval *)shm)->pingpong = DFPP_SV_ERROR;
break;
}
}
// hook - called every tick
// hook - called every tick in OpenGL mode of DF
extern "C" void SDL_GL_SwapBuffers(void)
{
if(_SDL_GL_SwapBuffers)
@ -210,6 +280,20 @@ extern "C" void SDL_GL_SwapBuffers(void)
}
}
// hook - called every tick in the 2D mode of DF
extern "C" int SDL_Flip(void * some_ptr)
{
if(_SDL_Flip)
{
if(((shm_cmd *)shm)->pingpong != DFPP_RUNNING)
{
SHM_Act();
}
counter ++;
return _SDL_Flip(some_ptr);
}
}
// hook - called at program exit
extern "C" void SDL_Quit(void)
{
@ -217,7 +301,8 @@ extern "C" void SDL_Quit(void)
{
_SDL_Quit();
}
fprintf(stderr,"dfhack: DF called SwapBuffers %d times, lol\n", counter);
fprintf(stderr,"dfhack: DF called SwapBuffers %d times\n", counter);
SHM_Destroy();
}
// hook - called at program start, initialize some stuffs we'll use later
@ -226,6 +311,7 @@ extern "C" int SDL_Init(uint32_t flags)
// find real functions
_SDL_GL_SwapBuffers = (void (*)( void )) dlsym(RTLD_NEXT, "SDL_GL_SwapBuffers");
_SDL_Init = (int (*)( uint32_t )) dlsym(RTLD_NEXT, "SDL_Init");
_SDL_Flip = (int (*)( void * )) dlsym(RTLD_NEXT, "SDL_Flip");
_SDL_Quit = (void (*)( void )) dlsym(RTLD_NEXT, "SDL_Quit");
// check if we got them

@ -12,6 +12,14 @@
* end - sent to DF for breaking out of the wait
*/
enum DF_SHM_ERRORSTATE
{
SHM_OK, // all OK
SHM_CANT_GET_SHM, // getting the SHM ID failed for some reason
SHM_CANT_ATTACH, // we can't attach the shm for some reason
SHM_SECOND_DF // we are a second DF process, can't use SHM at all
};
enum DF_PINGPONG
{
DFPP_RUNNING = 0, // no command, normal server execution

Binary file not shown.

@ -9,3 +9,6 @@ export LD_PRELOAD="./libs/dfconnect.so" # Hack DF!
save and run the script!
Has to be compiled for 32bit arch, otherwise the library isn't recognised. Client can be any arch.
Precompiled dfconnect library is made available. dfconnect.so goes in DF/libs, df-hacked script goes in DF/
Run ./df-hacked to use the shared memory API