[About] [News] [Documentation] [Screenshots]

Chapter 5. Hacking bugle

5.1. Porting

5.1.1. Introduction

While other projects may have simply a Windows port and a GNU port, for example, it is not so simple for bugle. For example, one might run on Windows but using Cygwin with an X server, or on Linux but using EGL rather than GLX. Instead, a target system is based on a number of categories:

Binary format

The binary format determines the details of how dynamic linking is done and how it may be subverted. For example, the Windows PE format explicitly indicates which symbols come from which library, and hence require an entire library to be impersonated, while ELF files just list dependent libraries and dependent symbols, making it easy to replace certain symbols.

Operating system API

This determines how things like segfault handling and other low-level operations are performed. Examples are POSIX and Win32.

Windowing system

The main effect of the windowing system is to determine how keyboard and mouse events may be intercepted.

GL-Window system integration

This determines how GL interacts with the windowing system. Usually there is a one-to-one mapping (e.g., AGL, WGL, GLX), but EGL applies on any windowing system.

Filesystem layout

This determines where to look for files, namely bugle configuration files and system libraries, and potentially temporary files.

Rather than conditionally compiling code based on the particular system in use, each system is characterised by a set of defines that describe its essential properties. This makes it easier to port bugle to similar but not identical systems in future: new code is needed only for those parts either specific to the system in question, or where the new system behaves in an entirely novel way.

5.1.2. Binary format

The following properties are defined as either 0 or 1, meaning false or true; the descriptions indicate the meaning if true:

BUGLE_BINFMT_SYMBOL_BY_DSO

The binary format lists the external library that is to be used for each external symbol.

BUGLE_BINFMT_LDPRELOAD

Bugle is injected by LD_PRELOAD or an equivalent mechanism. If false, the bugle library is given the same name as the usual OpenGL library (e.g., opengl32.dll on Windows), and loads the original library with a full path.

At present the supported binary formats are PE (BUGLE_BINFMT_PE) and ELF (BUGLE_BINFMT_ELF), although other formats could well be similar enough to be put in the same category.

5.1.3. Operating system API

For the most part, code that needs to be OS API aware needs to have code implemented for each target OS. The only use of this in the API at present is the use of signal and siglongjmp to return useful information in the face of an access violation, and these functions may be detected via autoconf.

For this reason, no preprocessor symbols are currently defined to indicate the OS API. This is subject to change should a need arise.

5.1.4. Windowing system

As for OS porting, code that is windowing system specific needs to be written for each windowing system. The following window systems are defined:

  • BUGLE_WINSYS_X11

  • BUGLE_WINSYS_WINDOWS

5.1.5. GL-Windowing integration

The primary difference between GL-Windowing integration APIs is whether extension function pointers are context-dependent or constant. This is determined by BUGLE_GLWIN_CONTEXT_DEPENDENT. The only currently supported instance of this is WGL (BUGLE_GLWIN_WGL). EGL (BUGLE_GLWIN_EGL) and GLX (BUGLE_GLWIN_GLX) both require function pointers to be the same in any context.

5.1.6. Filesystem layout

Filesystem layout determines the conventional placement of files. Three variants are defined

BUGLE_FS_UNIX

Configuration files are stored in $HOME/.bugle and system libraries are searched for in /usr/lib.

BUGLE_FS_CYGMING

Configuration files are stored in $HOME/.bugle, but system libraries are searched for in GetSystemDir().

5.1.7. Platforms

The term platform has a specific meaning in bugle porting: it is the set of standard runtime libraries available, together with the associated header files. The currently available platforms are:

null

This is a dummy platform which provides all the interfaces but no implementation. It will compile and link, and is intended as a stepping stone for porting to new platforms.

posix

This is IEEE 1003.1-2001 with the _POSIX_THREADS option. Other optional features will be used if present (including GNU extensions), but are (in theory) not required.

msvcrt

Microsoft Visual C++ runtime. This is only tested with Visual C++ 9.0.

mingw

MinGW runtime, based on msvcrt. It is treated separately because MinGW provides its own header files.

Adding a new interface requires implementing a number of functions and header files, described below. Because some implementations can be shared between platforms (particularly implementations that bypass system libraries), each function (or closely-related group of functions) is implemented in a separate file in src/platform.

5.1.7.1. String functions

Some of these functions return newly allocated memory. These functions must terminate the program if memory could not be allocated, by calling bugle_alloc_die (or by allocating the memory from bugle_malloc).

Failure behaviour is loosely defined, since the methods of reporting failure vary from one platform to another. Functions may modify errno but are not required to.

int bugle_snprintf(char *str,
 size_t size,
 const char *format,
 ...); 
 
int bugle_vsnprintf(char *str,
 size_t size,
 const char *format,
 va_list ap);
 

These functions must implement the C99 semantics of the corresponding functions, in particular, returning the number of characters that would have been written if truncation had not occurred.

char *bugle_strdup(char *str);
 
char *bugle_strndup(char *str,
 size_t n);
 

Returns a copy of str in newly allocated memory. bugle_strndup truncates the string to at most n characters.

char *bugle_asprintf(const char *format,
 ...); 
 

Performs a printf, but returns the written data in newly allocated memory.

5.1.7.2. Time functions

int bugle_gettime(bugle_timespec *ts);
 

Returns a timestamp, measured in seconds since an arbitrary point in time (bugle_timespec is defined in bugle/time.h). There are no specific requirements on precision, but since this function is used for statistics it should have at least millisecond precision. If possible, it should also use a monotonic clock that will not be affected by changes to the system time, daylight savings, leap seconds etc.

5.1.7.3. Mathematical functions

int bugle_isfinite(double x);
 
int bugle_isnan(double x);
 

Returns a non-zero value if x is respectively finite or not-a-number.

double bugle_nan(void); 
 

Returns a non-signalling not-a-number value.

double bugle_round(double x);
 

Returns x rounded to the nearest integral value. Half-integer values may be rounded in either direction.

float bugle_sinf(float x);
 
float bugle_cosf(float x);
 

These correspond to the C99 functions sinf and cosf.

5.1.7.4. Dynamic library functions

bugle_dl_module bugle_dl_open(const char *filename,
 int flag);
 
int bugle_dl_close(bugle_dl_module module);
 

bugle_dl_open loads a dynamical module. filename is the name of the file to open and will not be NULL. If it is not an absolute pathname, it is platform-dependent how the file is located. The possible value for flags are

BUGLE_DL_FLAG_GLOBAL

Place the symbols in the module in the global symbol table. This flag may be ignored.

BUGLE_DL_FLAG_NOW

Resolve all symbols in the module immediately, rather than on first use. This flag may be ignored.

BUGLE_DL_FLAG_SUFFIX

Append a platform-specific suffix to the filename before trying to locate it. If this flag is absent, it is still permitted (but not required) for the implementation to append a suffix.

The type bugle_dl_module is an opaque pointer, which the implementation may interpret as it wishes. This function is not required to be reentrant nor to support multiple loading of the same library. However, it must be possible to open a library that is already linked into the application by other means.

A module is closed with bugle_dl_close. bugle_dl_open supports opening the same library multiple times, then the library must not actually be released until it has been closed as many times as it has been opened. Returns zero on success, non-zero on failure.

void *bugle_dl_sym_data(bugle_dl_module module,
 const char *symbol);
 
BUDGIEAPIPROC bugle_dl_sym_function(bugle_dl_module module,
 const char *symbol);
 

These functions retrieve a symbol. module must be a handle previously returned by bugle_dl_open — there are no magic values for retrieving global symbols. These functions return NULL if the function is not declared.

void bugle_dl_foreach(const char *path,
 void (*callback) (const char *filename, void *arg));
 

Search path (non-recursively) for loadable modules (files with appropriate filenames), and calls callback for each file found. arg is passed on to callback.

If this function fails, it is responsible for logging an appropriate error and terminating the process.

5.1.7.5. platform/macros.h

This file provides miscellaneous macros. At present, the only macro that is defined in BUGLE_VA_COPY, which must implement the C99 va_copy function.

5.1.7.6. platform/threads.h

This file provides thread primitives. While this section lists a number of functions, they may be implemented as function-like macros or inline functions, and probably should where they are thin wrappers around platform functions.

The header file needs to define the following types and defines. With the exception of bugle_thread_once_t, there is no requirement that variables of these types are useable before being initialised by an appropriate function call.

bugle_thread_once_t

A type used to allow an initialisation function to be run only once.

BUGLE_THREAD_ONCE_INIT

A static initialiser for a bugle_thread_once_t. There is no guarantee that this can be used in an assignment expression.

bugle_thread_lock_t

A mutual exclusion lock.

bugle_thread_rwlock_t

A multiple-reader, single-writer lock.

bugle_thread_key_t

A key used to identify thread-local storage.

bugle_thread_id

A thread identifier, useful only for comparisons.

bugle_thread_handle

A thread handle, used to join with a thread.

int bugle_thread_once(bugle_thread_once *once,
 void (* once_function) (void));
 

Call once_function if once has not previously been used with this function; otherwise waits until the previous call has completed. once must have been staticly initialised to BUGLE_THREAD_ONCE_INIT.

int bugle_thread_lock_init(bugle_thread_lock_t *lock);
 
int bugle_thread_lock_destroy(bugle_pthread_lock_t *lock);
 

These functions respectively create and destroy a lock. Locks may only be used when initialised. Initialising an already initialised lock, destroying an uninitialised lock, or destroying a lock that is held, all lead to undefined behaviour. These functions return zero on success, non-zero on failure.

int bugle_thread_lock_lock(bugle_thread_lock_t *lock);
 
int bugle_thread_lock_unlock(bugle_thread_lock_t *lock);
 

These functions respectively obtain or release a lock. Attempting to obtain a lock that is already held, release a lock that is not held by this thread, or perform an operation that results in deadlock all have undefined behaviour. These functions return zero on success, non-zero on failure.

int bugle_thread_rwlock_init(bugle_thread_rwlock_t *lock);
 
int bugle_thread_rwlock_destroy(bugle_thread_rwlock_t *lock);
 

These functions respectively create and destroy a read-write lock. Locks may only be used when initialised. Initialising an already initialised lock, destroying an uninitialised lock, or destroying a lock that is held, all lead to undefined behaviour. These functions return zero on success, non-zero on failure.

int bugle_thread_rwlock_rdlock(bugle_thread_rwlock_t *lock);
 
int bugle_thread_rwlock_wrlock(bugle_thread_rwlock_t *lock);
 
int bugle_thread_rwlock_unlock(bugle_thread_rwlock_t *lock);
 

These functions obtain a read-write lock for reading or writing, or release a held lock. Attempting to obtain a lock that is already held, release a lock that is not held by this thread, or perform an operation that results in deadlock all have undefined behaviour.

Bugle does not itself depend on multiple concurrent readers, but some drivers make synchronous callbacks from different threads that will deadlock if this is not implemented. It is also recommended that implementations are designed to avoid writer starvation.

int bugle_thread_sem_init(bugle_thread_sem_t *sem,
 unsigned int value);
 
int bugle_thread_sem_destroy(bugle_thread_sem_t *sem);
 

These functions respectively create and destroy a counting semaphore. Semaphores may only be used when initialised. Initialising an already initialised semaphore, destroying an uninitialised semaphore, or destroying a semaphore on which any thread is blocked, all lead to undefined behaviour. These functions return zero on success, non-zero on failure.

The underlying platform may impose an implementation limit on the maximum value of a semaphore. You should not expect to be able to use enormous semaphore values. Values up to 65536 should be safe.

int bugle_thread_sem_post(bugle_thread_sem_t *sem);
 
int bugle_thread_sem_wait(bugle_thread_sem_t *sem);
 
int bugle_thread_sem_trywait(bugle_thread_sem_t *sem);
 

These functions increment or decrement a semaphore count. If the count is currently zero, bugle_thread_sem_wait will block until it can decrement it, while bugle_thread_sem_trywait will fail. These functions return zero on success or non-zero on failure; bugle_thread_sem_trywait will return a positive value if it failed because the count was zero and a negative value if it failed for some other reason.

The value of errno after calling any of these functions in undefined.

int bugle_thread_key_create(bugle_thread_key_t *key,
 void (*destructor) (void *));
 
int bugle_thread_key_delete(bugle_thread_key_t key);
 

Respectively create or destroy a key for thread-local storage. On key creation, the value NULL is associated with the key in all threads. On thread creation, the value NULL is associated with all keys in that thread. These functions return zero on success, non-zero on failure.

When a thread terminates, if the destructor and the associated value are both non-NULL then the destructor should be called with the value. The order of destructors is unspecified, and the destructors will have undefined behaviour if they try to set or query any thread-local storage. If a key is destroyed, the destructor will not be called on any of its associated data.

At present, not all platforms implement destructors. These platforms will leak resources and should be fixed.

int bugle_thread_setspecific(bugle_thread_key_t key,
 const void *value);
 
void *bugle_thread_getspecific(bugle_thread_key_t key);
 

Respectively set or query a thread-local value. Thread-local values are of type void *. bugle_thread_setspecific returns zero on success, non-zero on failure; bugle_thread_getspecific must never fail given a valid key (an invalid key leads to undefined behaviour).

int bugle_thread_raise(int sig);
 

Send sig to the current thread, similar to the C raise function. This function is not required to wait for signal handlers to complete.

bugle_thread_id bugle_thread_self(void); 
 

Returns an OS identifier for the current thread. Two concurrently executing threads must have different identifiers. Ideally, identifiers should not be recycled during a process, but this may be impossible to guarantee.

Thread IDs must not be compared directly, but with

bugle_bool bugle_thread_equal(bugle_thread_id thread1,
 bugle_thread_id thread2);
 

which will return true if the thread IDs refer to the same thread.

void bugle_flockfile(FILE *filehandle);
 
void bugle_funlockfile(FILE *filehandle);
 

These functions lock or unlock a file handle for concurrent access. While locked, other threads attempting to use the filehandle will be blocked, even if they do not explicitly lock. Locking is recursive, so the handle becomes unlocked when bugle_funlockfile has been called as many times as bugle_flockfile. Calling bugle_funlockfile in a thread which does not hold a lock yields undefined behaviour.

Since not all platforms provide this mechanism, and since bugle only relies on it to avoid interleaving output from different threads, platforms are permitted to implement these functions as no-ops.

BUGLE_CONSTRUCTOR(function);
BUGLE_RUN_CONSTRUCTOR(function);

These macros implement an initialise-once mechanism at a higher level than bugle_thread_once_t. The function must be static and have no parameters or return value. BUGLE_CONSTRUCTOR is used at file scope prior to the declaration of the function, while BUGLE_RUN_CONSTRUCTOR is used inside code to ensure that the function has been run.

Implementations are free to execute the function at any point up to BUGLE_RUN_CONSTRUCTOR returning. In particular, they may choose to implement these macros using constructor semantics. However, the function must be run in an environment where it may safely execute all the functions in this section, particularly the dynamic library functions.

Threads can be created and destroyed with

bugle_bool bugle_thread_create(bugle_thread_handle thread,
 unsigned int (*start) (void *),
 void *arg);
 
bugle_bool bugle_thread_join(bugle_thread_handle thread,
 unsigned int *retval);
 

These functions return zero on success. POSIX detached threads are not supported; all threads must be joined to avoid leaking resources. Joining a thread also invalidates the handle.


Get BuGLe at SourceForge.net. Fast, secure and Free Open Source software downloads