aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorspicyjpeg <thatspicyjpeg@gmail.com>2022-10-30 08:28:44 +0100
committerspicyjpeg <thatspicyjpeg@gmail.com>2022-10-30 08:28:44 +0100
commit68daf6d338aba6e32e687d4151eaddc8735227b3 (patch)
tree8cb84440219dd8041c4e84219c3742561d020819
parentf6c41f3783c4fce49a9899b710ebb50ba9f647ab (diff)
downloadpsn00bsdk-68daf6d338aba6e32e687d4151eaddc8735227b3.tar.gz
Refactor dynamic linker, misc. cleanups
-rw-r--r--examples/sound/spustream/main.c4
-rw-r--r--examples/system/dynlink/main.c44
-rw-r--r--libpsn00b/include/dlfcn.h237
-rw-r--r--libpsn00b/include/stdlib.h12
-rw-r--r--libpsn00b/libc/memset.s6
-rw-r--r--libpsn00b/psxetc/dl.c399
6 files changed, 287 insertions, 415 deletions
diff --git a/examples/sound/spustream/main.c b/examples/sound/spustream/main.c
index 68cf9b0..d240433 100644
--- a/examples/sound/spustream/main.c
+++ b/examples/sound/spustream/main.c
@@ -19,8 +19,8 @@
* As the loop point doesn't necessarily have to be within the chunk itself, it
* can be used to "queue" another chunk to be played immediately after the
* current one. This allows for double buffering: two chunks are always kept in
- * SPU RAM and one is overwritten with while the other is playing. Chunks are
- * laid out in SPU RAM as follows:
+ * SPU RAM and one is overwritten with a new chunk while the other is playing.
+ * Chunks are laid out in SPU RAM as follows:
*
* ________________________________________________
* / __________________ \
diff --git a/examples/system/dynlink/main.c b/examples/system/dynlink/main.c
index fcce5b1..d813c07 100644
--- a/examples/system/dynlink/main.c
+++ b/examples/system/dynlink/main.c
@@ -7,8 +7,8 @@
* symbol map file, which is generated at compile time by GCC's nm command and
* included into the CD image. The symbol map lists all functions/variables in
* the executable and their type, address and size. Currently only searching
- * for a symbol's address by its name (DL_GetSymbolByName()) is supported,
- * however this may be expanded in the future.
+ * for a symbol's address by its name (DL_GetMapSymbol()) is supported, however
+ * this may be expanded in the future.
*
* Being able to introspect local symbols at runtime, in turn, allows us to use
* another set of APIs to load, link and execute code from an external file
@@ -140,18 +140,18 @@ void display(RenderContext *ctx) {
/* Symbol overriding example */
-static volatile uint32_t resolve_counter = 0;
+static volatile int resolve_counter = 0;
// This function will override printf(), i.e. DLLs will use this instead of the
// "real" printf() present in the executable, thanks to the custom resolver
-// defined below. We'll use this to redirect the DLL's output to the debug text
-// window.
+// defined below. We'll use this to redirect the DLL's output to be shown on
+// screen.
int dll_printf(const char *format, ...) {
va_list args;
va_start(args, format);
- char buffer[256];
- int32_t return_value = vsprintf(buffer, format, args);
+ char buffer[256];
+ int return_value = vsprintf(buffer, format, args);
va_end(args);
FntPrint(-1, "DLL: %s", buffer);
@@ -163,7 +163,7 @@ int dll_printf(const char *format, ...) {
// This function will be called by the linker for each undefined symbol
// (function or variable) in the DLL, and should return the address of the
// symbol so the dynamic linker can patch it in. The default resolver tries to
-// find them in the currently loaded symbol map using DL_GetSymbolByName().
+// find them in the currently loaded symbol map using DL_GetMapSymbol().
void *custom_resolver(DLL *dll, const char *name) {
if (!strcmp(name, "printf")) {
printf("Resolving printf() -> dll_printf() (#%d)\n", resolve_counter++);
@@ -173,7 +173,7 @@ void *custom_resolver(DLL *dll, const char *name) {
printf("Resolving %s() (#%d)\n", name, resolve_counter++);
// Custom resolvers should always fall back to the default behavior.
- return DL_GetSymbolByName(name);
+ return DL_GetMapSymbol(name);
}
/* Global variables and structs */
@@ -187,7 +187,7 @@ typedef struct {
void (*render)(RenderContext *, uint16_t buttons);
} DLL_API;
-static DLL *dll = 0;
+static DLL dll;
static DLL_API dll_api;
static RenderContext ctx;
@@ -225,20 +225,18 @@ size_t load_file(const char *filename, void **ptr) {
}
void load_dll(const char *filename) {
- // As we're passing RTLD_FREE_ON_DESTROY to DL_CreateDLL(), calling
+ // As we're passing DL_FREE_ON_DESTROY to DL_CreateDLL(), calling
// DL_DestroyDLL() will also deallocate the buffer the DLL was loaded into.
- if (dll)
- DL_DestroyDLL(dll);
+ DL_DestroyDLL(&dll);
void *ptr;
size_t len = load_file(filename, &ptr);
- dll = DL_CreateDLL(ptr, len, RTLD_LAZY | RTLD_FREE_ON_DESTROY);
- if (!dll)
- SHOW_ERROR("FAILED TO PARSE %s\nERROR=%d\n", filename, (int32_t) DL_GetLastError());
+ if (!DL_CreateDLL(&dll, ptr, len, DL_LAZY | DL_FREE_ON_DESTROY))
+ SHOW_ERROR("FAILED TO PARSE %s\n", filename);
- dll_api.init = DL_GetDLLSymbol(dll, "init");
- dll_api.render = DL_GetDLLSymbol(dll, "render");
+ dll_api.init = DL_GetDLLSymbol(&dll, "init");
+ dll_api.render = DL_GetDLLSymbol(&dll, "render");
printf("DLL init() @ %08x, render() @ %08x\n", dll_api.init, dll_api.render);
@@ -266,14 +264,14 @@ int main(int argc, const char* argv[]) {
size_t len = load_file("\\MAIN.MAP;1", &ptr);
if (!DL_ParseSymbolMap(ptr, len))
- SHOW_ERROR("FAILED TO PARSE SYMBOL MAP\nERROR=%d\n", (int32_t) DL_GetLastError());
+ SHOW_ERROR("FAILED TO PARSE SYMBOL MAP\n");
free(ptr);
// Try to obtain a reference to a local function.
- void (*_display)() = DL_GetSymbolByName("display");
+ void (*_display)() = DL_GetMapSymbol("display");
if (!_display)
- SHOW_ERROR("FAILED TO LOOK UP LOCAL FUNCTION\nERROR=%d\n", (int32_t) DL_GetLastError());
+ SHOW_ERROR("FAILED TO LOOK UP LOCAL FUNCTION\n");
printf("Symbol map test, display() @ %08x\n", _display);
@@ -295,7 +293,7 @@ int main(int argc, const char* argv[]) {
DL_PRE_CALL(dll_api.render);
dll_api.render(&ctx, last_buttons);
- FntPrint(-1, "MAIN: DLL ADDR=%08x SIZE=%d\n", dll->ptr, dll->size);
+ FntPrint(-1, "MAIN: DLL ADDR=%08x SIZE=%d\n", dll.ptr, dll.size);
FntPrint(-1, "MAIN: %d FUNCTIONS RESOLVED\n", resolve_counter);
FntPrint(-1, "[START] LOAD NEXT DLL\n");
FntFlush(-1);
@@ -320,7 +318,7 @@ int main(int argc, const char* argv[]) {
last_buttons = pad->btn;
}
- //DL_DestroyDLL(dll);
+ //DL_DestroyDLL(&dll);
//DL_UnloadSymbolMap();
return 0;
}
diff --git a/libpsn00b/include/dlfcn.h b/libpsn00b/include/dlfcn.h
index 5848a95..3c5260d 100644
--- a/libpsn00b/include/dlfcn.h
+++ b/libpsn00b/include/dlfcn.h
@@ -7,38 +7,31 @@
#define __DLFCN_H
#include <stdint.h>
+#include <stddef.h>
#include <elf.h>
-/* Helper macro for setting $t9 before calling a function */
+/* Macros */
-#define DL_PRE_CALL(func) { \
- __asm__ volatile("move $t9, %0;" :: "r"(func) : "$t9"); \
-}
+/**
+ * @brief Prepares for a DLL function call.
+ *
+ * @details Sets the $t9 register to the specified value (which should be a
+ * pointer to a DLL function obtained using DL_GetDLLSymbol()). This must be
+ * done prior to calling a DLL function from the main executable to ensure the
+ * DLL can correctly invoke the symbol resolver if necessary.
+ *
+ * This macro is not required when calling a DLL function from another DLL, as
+ * GCC will generate code to set $t9 appropriately.
+ */
+#define DL_PRE_CALL(func) \
+ __asm__ volatile("move $t9, %0;" :: "r"(func) : "$t9");
-/* Types */
-
-#define RTLD_DEFAULT ((DLL *) 0)
-
-typedef enum _DL_Error {
- RTLD_E_NONE = 0, // No error
- RTLD_E_FILE_OPEN = 1, // Unable to find or open file
- RTLD_E_FILE_ALLOC = 2, // Unable to allocate buffer to load file into
- RTLD_E_FILE_READ = 3, // Failed to read file
- RTLD_E_NO_MAP = 4, // No symbol map has been loaded yet
- RTLD_E_MAP_ALLOC = 5, // Unable to allocate symbol map structures
- RTLD_E_NO_SYMBOLS = 6, // No symbols found in symbol map
- RTLD_E_DLL_NULL = 7, // Unable to initialize DLL from null pointer
- RTLD_E_DLL_ALLOC = 8, // Unable to allocate DLL metadata structures
- RTLD_E_DLL_FORMAT = 9, // Unsupported DLL type or format
- RTLD_E_MAP_SYMBOL = 10, // Symbol not found in symbol map
- RTLD_E_DLL_SYMBOL = 11, // Symbol not found in DLL
- RTLD_E_HASH_LOOKUP = 12 // Hash table lookup failed due to internal error
-} DL_Error;
+/* Structure and enum definitions */
typedef enum _DL_ResolveMode {
- RTLD_LAZY = 1, // Resolve functions when they are first called (default)
- RTLD_NOW = 2, // Resolve all symbols immediately on load
- RTLD_FREE_ON_DESTROY = 4 // Automatically free DLL buffer when closing DLL
+ DL_LAZY = 1, // Resolve functions when they are first called (default)
+ DL_NOW = 2, // Resolve all symbols immediately on load
+ DL_FREE_ON_DESTROY = 4 // Automatically free DLL buffer when closing DLL
} DL_ResolveMode;
// Members of this struct should not be accessed directly in most cases, but
@@ -55,151 +48,171 @@ typedef struct _DLL {
uint16_t got_length;
} DLL;
-/* API */
+/* Public API */
#ifdef __cplusplus
extern "C" {
#endif
/**
- * @brief Reads the symbol table from the provided string buffer (which may or
- * may not be null-terminated), parses it and stores the parsed entries into a
- * private hash table; the buffer won't be further referenced and can be safely
- * deallocated after parsing. Returns the number of entries successfully parsed
- * or -1 if an error occurred.
+ * @brief Creates an empty symbol map in memory.
*
- * This function expects the string buffer to contain one more lines, each of
- * which must follow this format:
+ * @details Initializes the internal symbol hash table to contain at most the
+ * given number of symbols. Once this function is called, symbols can be
+ * registered using DL_AddMapSymbol() and then looked up using
+ * DL_GetMapSymbol(). The default DLL resolver will search the hash table for
+ * external symbols required by DLLs.
*
- * <SYMBOL_NAME> <T|R|D|B> <HEX_ADDRESS> <HEX_SIZE> [DEBUG_INFO...]
+ * This function is normally not required when loading a map file through
+ * DL_ParseSymbolMap(), but it can be used alongside DL_AddMapSymbol() to
+ * implement a custom symbol map parser.
*
- * The "nm" tool included in the GCC toolchain can be used to generate a map
- * file in the appropriate format after building the executable, by using this
- * command:
+ * @param num_entries
+ * @return 0 or -1 in case of error
*
- * mipsel-none-elf-nm -f posix -l -n executable.elf >executable.map
+ * @see DL_AddMapSymbol(), DL_GetMapSymbol()
+ */
+int DL_InitSymbolMap(int num_entries);
+
+/**
+ * @brief Destroys the currently loaded symbol map.
*
- * @param ptr
- * @param size
- * @return -1 or number of entries parsed
+ * @details Frees the internal hash table allocated by DL_InitSymbolMap() or
+ * DL_ParseSymbolMap(), containing the currently loaded symbol map. Freeing the
+ * table manually before loading a new symbol map is normally unnecessary as it
+ * is done automatically, however this function can be useful to recover heap
+ * space once the map is no longer needed.
*/
-int32_t DL_ParseSymbolMap(const char *ptr, size_t size);
+void DL_UnloadSymbolMap(void);
/**
- * @brief File wrapper around DL_ParseSymbolMap(). Allocates a temporary buffer
- * then loads the specified map file into it (using BIOS APIs) and calls
- * DL_ParseSymbolMap() to parse it. The buffer is deallocated immediately after
- * parsing.
+ * @brief Adds a symbol to the currently loaded symbol map.
*
- * @param filename Must always contain device name, e.g. "cdrom:MODULE.DLL;1"
- * @return -1 or number of entries parsed
+ * @details Registers a new symbol (function or variable) with the given name
+ * and address, and adds it to the internal hash table. The symbol can then be
+ * looked up using DL_GetMapSymbol(). The default DLL resolver will search the
+ * hash table for external symbols required by DLLs.
+ *
+ * This function shall only be called after DL_InitSymbolMap() or
+ * DL_ParseSymbolMap() is called.
+ *
+ * @param name
+ * @param ptr
+ *
+ * @see DL_GetMapSymbol()
*/
-//int32_t DL_LoadSymbolMapFromFile(const char *filename);
+void DL_AddMapSymbol(const char *name, void *ptr);
/**
- * @brief Frees internal buffers containing the currently loaded symbol map.
- * This is automatically done before loading a new symbol map so there is no
- * need to call this function in most cases, however it can still be useful to
- * free up space on the heap once the symbol map is no longer needed.
+ * @brief Creates a symbol map in memory from a map file in text format.
+ *
+ * @details Initializes the internal symbol hash table, then parses entries
+ * from the provided string buffer (which may or may not be null-terminated)
+ * and adds each one to the table. The string buffer won't be further
+ * referenced and can be safely deallocated after parsing. Returns the number
+ * of entries successfully parsed.
+ *
+ * The string buffer shall contain one or more lines, each of which must follow
+ * this format:
+ *
+ * <SYMBOL_NAME> <T|R|D|B> <HEX_ADDRESS> <HEX_SIZE> [...]
+ *
+ * The "nm" tool included in the GCC toolchain can be used to generate a map
+ * file in the appropriate format after building the executable:
+ *
+ * mipsel-none-elf-nm -f posix -l -n executable.elf >executable.map
+ *
+ * @param ptr
+ * @param size
+ * @return Number of entries parsed, -1 in case of failure
+ *
+ * @see DL_UnloadSymbolMap(), DL_GetMapSymbol()
*/
-void DL_UnloadSymbolMap(void);
+int DL_ParseSymbolMap(const char *ptr, size_t size);
/**
- * @brief Queries the currently loaded symbol map for the symbol with the given
- * name and returns a pointer to it, which can then be used to directly access
- * the symbol. If the symbol can't be found, null is returned instead.
+ * @brief Gets a pointer to a symbol in the currently loaded map by its name.
+ *
+ * @details Queries the currently loaded symbol map for the symbol with the
+ * given name and returns a pointer to it, which can then be used to directly
+ * access the symbol. If the symbol can't be found, a null pointer is returned.
*
* @param name
- * @return NULL or pointer to symbol (any type)
+ * @return NULL or pointer to symbol
*/
-void *DL_GetSymbolByName(const char *name);
+void *DL_GetMapSymbol(const char *name);
/**
- * @brief Sets a custom function to be called for resolving symbols in DLLs.
+ * @brief Sets a custom handler for resolving symbols in DLLs.
+ *
+ * @details Sets a custom function to be called for resolving symbols in DLLs.
* The function will be given a pointer to the current DLL and the unresolved
* symbol's name, and should return the address of the symbol in the executable
* (the dynamic linker will lock up if it returns null). Passing null instead
- * of a function resets the default behavior of calling DL_GetSymbolByName() to
+ * of a function resets the default behavior of calling DL_GetMapSymbol() to
* find the symbol in the currently loaded symbol map.
- *
+ *
* @param callback NULL or pointer to callback function
+ * @return Previously set callback or NULL
*/
-void DL_SetResolveCallback(void *(*callback)(DLL *, const char *));
+void *DL_SetResolveCallback(void *(*callback)(DLL *, const char *));
/**
- * @brief Initializes a buffer holding the contents of a dynamically-loaded
+ * @brief Initializes a DLL structure.
+ *
+ * @details Initializes a buffer holding the contents of a dynamically-loaded
* library file (compiled with the dll.ld linker script and converted to a raw
- * binary) *in-place*. A new DLL struct is allocated to store metadata but,
+ * binary) *in-place*. Metadata is written to the provided DLL struct but,
* unlike DL_ParseSymbolMap(), the DLL's actual code, data and tables are
* referenced directly from the provided buffer. The buffer must not be moved
* or deallocated, at least not before calling DL_DestroyDLL() on the DLL
* struct returned by this function.
*
* The third argument specifies when symbols in the DLL should be resolved.
- * Setting it to RTLD_LAZY defers resolution of undefined functions to when
- * they are first called, while RTLD_NOW forces all symbols to be resolved
- * immediately. If a custom resolver has been set via DL_SetResolveCallback(),
- * it will be called for each symbol to resolve.
+ * Setting it to DL_LAZY defers resolution of undefined functions to when they
+ * are first called, while DL_NOW forces all symbols to be resolved
+ * immediately. Either mode can be OR'd with DL_FREE_ON_DESTROY to
+ * automatically deallocate the provided buffer when DL_DestroyDLL() is called.
*
+ * If a custom resolver has been set via DL_SetResolveCallback(), it will be
+ * called for each symbol to resolve.
+ *
+ * @param dll
* @param ptr
* @param size
- * @param mode RTLD_LAZY or RTLD_NOW
- * @return NULL or pointer to a new DLL struct
- */
-DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode);
-
-/**
- * @brief File wrapper around dlinit(). Allocates a new buffer, loads the
- * specified file into it (using BIOS APIs) and calls dlinit() on that. When
- * calling dlclose() on a DLL loaded from a file, the file buffer is
- * automatically destroyed.
- *
- * @param filename Must always contain device name, e.g. "cdrom:MODULE.DLL;1"
- * @param mode RTLD_LAZY or RTLD_NOW + optionally RTLD_FREE_ON_DESTROY
- * @return NULL or pointer to a new DLL struct
+ * @param mode DL_LAZY or DL_NOW, optionally with DL_FREE_ON_DESTROY
+ * @return Pointer to DLL structure or NULL in case of failure
+ *
+ * @see DL_DestroyDLL(), DL_GetDLLSymbol()
*/
-//DLL *DL_LoadDLLFromFile(const char *filename, DL_ResolveMode mode);
+DLL *DL_CreateDLL(DLL *dll, void *ptr, size_t size, DL_ResolveMode mode);
/**
- * @brief Destroys a loaded DLL by calling its global destructors and freeing
- * the buffer it's loaded in. Any pointer passed to DL_DestroyDLL() should no
- * longer be used after the call. If the DLL was initialized in-place using
- * DL_CreateDLL(), DL_DestroyDLL() will only free the buffer initially passed
- * to DL_CreateDLL() if RTLD_FREE_ON_DESTROY was used.
+ * @brief Destroys a DLL structure.
+ *
+ * @details Destroys a loaded DLL by calling its global destructors. If the DLL
+ * was initialized with the DL_FREE_ON_DESTROY flag, the buffer associated with
+ * the DLL is also deallocated. Note that the DLL structure itself is *not*
+ * deallocated.
*
* @param dll
*/
void DL_DestroyDLL(DLL *dll);
/**
- * @brief Returns a pointer to the DLL symbol with the given name, or null if
- * it can't be found. If null or RTLD_DEFAULT is passed as first argument, the
- * executable itself is searched instead using the symbol map (behaving the
- * same as DL_GetSymbolByName()).
+ * @brief Gets a pointer to a symbol in a DLL by its name.
+ *
+ * @details Returns a pointer to the DLL symbol with the given name, or null if
+ * it can't be found. If a null pointer is passed as first argument, the
+ * executable itself is searched instead using the symbol map (behaving
+ * identically to DL_GetMapSymbol()).
*
- * @param dll DLL struct or RTLD_DEFAULT
+ * @param dll Pointer to DLL structure or NULL
* @param name
* @return NULL or pointer to symbol (any type)
*/
void *DL_GetDLLSymbol(const DLL *dll, const char *name);
-/**
- * @brief Returns a code describing the last error that occurred, or DL_E_NONE
- * if no error has occurred since the last call to dlerror() (i.e. calling this
- * also resets the internal error flags).
- *
- * @return NULL or member of DL_Error enum
- */
-DL_Error DL_GetLastError(void);
-
-/* POSIX "compatibility" macros */
-
-#define dlinit(ptr, size, mode) DL_CreateDLL(ptr, size, mode)
-//#define dlopen(filename, mode) DL_LoadDLLFromFile(filename, mode)
-#define dlsym(dll, name) DL_GetDLLSymbol(dll, name)
-#define dlclose(dll) DL_DestroyDLL(dll)
-#define dlerror() DL_GetLastError()
-
#ifdef __cplusplus
}
#endif
diff --git a/libpsn00b/include/stdlib.h b/libpsn00b/include/stdlib.h
index f0753c1..049d067 100644
--- a/libpsn00b/include/stdlib.h
+++ b/libpsn00b/include/stdlib.h
@@ -31,17 +31,19 @@ extern "C" {
extern int __argc;
extern const char **__argv;
+void abort(void);
+
int rand(void);
-void srand(unsigned long seed);
+void srand(int seed);
int abs(int j);
long labs(long i);
-long long strtoll(const char *nptr, char **endptr, int base);
-long strtol(const char *nptr, char **endptr, int base);
-long double strtold(const char *nptr, char **endptr);
-double strtod(const char *nptr, char **endptr);
+long strtol(const char *nptr, char **endptr, int base);
+long long strtoll(const char *nptr, char **endptr, int base);
float strtof(const char *nptr, char **endptr);
+double strtod(const char *nptr, char **endptr);
+long double strtold(const char *nptr, char **endptr);
void InitHeap(void *addr, size_t size);
void *sbrk(ptrdiff_t incr);
diff --git a/libpsn00b/libc/memset.s b/libpsn00b/libc/memset.s
index 73f92bd..6ef84ec 100644
--- a/libpsn00b/libc/memset.s
+++ b/libpsn00b/libc/memset.s
@@ -9,7 +9,6 @@
memset:
# If more than 16 bytes have to be written then take the "large" path,
# otherwise use the code below.
- blez $a2, .Lnull_count
addiu $t0, $a2, -16
bgtz $t0, .Llarge_fill
move $v0, $a0 # return_value = dest
@@ -39,12 +38,9 @@ memset:
sb $a1, 0xc($a0)
sb $a1, 0xd($a0)
sb $a1, 0xe($a0)
- jr $ra
sb $a1, 0xf($a0)
-
-.Lnull_count:
jr $ra
- move $v0, $a0 # return_value = dest
+ nop
.Llarge_fill:
# Initialize fast filling by repeating the fill byte 4 times, so it can be
diff --git a/libpsn00b/psxetc/dl.c b/libpsn00b/psxetc/dl.c
index fa5e74d..ccf7a7c 100644
--- a/libpsn00b/psxetc/dl.c
+++ b/libpsn00b/psxetc/dl.c
@@ -1,6 +1,6 @@
/*
* PSn00bSDK dynamic linker
- * (C) 2021 spicyjpeg - MPL licensed
+ * (C) 2021-2022 spicyjpeg - MPL licensed
*
* The bulk of this code is MIPS-specific but not PS1-specific, so the whole
* dynamic linker could be ported to other MIPS platforms that do not have one
@@ -27,8 +27,10 @@
#define SDK_LIBRARY_NAME "psxetc/dl"
#include <stdint.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <elf.h>
@@ -36,13 +38,6 @@
#include <string.h>
#include <psxapi.h>
-/* Compile options */
-
-// Comment before building to disable functions that rely on BIOS file APIs,
-// i.e. DL_LoadSymbolMapFromFile() and DL_LoadDLLFromFile().
-// FIXME: those seem to be broken currently, and shouldn't be used anyway
-//#define USE_FILE_API
-
/* Private types */
typedef struct {
@@ -51,17 +46,15 @@ typedef struct {
} MapEntry;
typedef struct {
- uint32_t nbucket;
- uint32_t nchain;
+ int nbucket, nchain, index;
MapEntry *entries;
uint32_t *bucket;
uint32_t *chain;
} SymbolMap;
-/* Data */
+/* Internal globals */
-static DL_Error _error_code = RTLD_E_NONE;
static SymbolMap _symbol_map;
// Accessed by _dl_resolve_helper, stores the pointer to the current resolver
@@ -70,11 +63,6 @@ void *(*_dl_resolve_callback)(DLL *, const char *) = 0;
/* Private utilities */
-#define _ERROR(code, ret) { \
- _error_code = code; \
- return ret; \
-}
-
void _dl_resolve_wrapper(void);
// Called by _dl_resolve_wrapper() (which is in turn called by GCC stubs) to
@@ -82,29 +70,28 @@ void _dl_resolve_wrapper(void);
void *_dl_resolve_helper(DLL *dll, uint32_t index) {
Elf32_Sym *sym = &(dll->symtab[index]);
const char *_name = &(dll->strtab[sym->st_name]);
- void *address;
+ void *addr;
if (_dl_resolve_callback)
- address = _dl_resolve_callback(dll, _name);
+ addr = _dl_resolve_callback(dll, _name);
else
- address = DL_GetSymbolByName(_name);
+ addr = DL_GetMapSymbol(_name);
- if (!address) {
- _sdk_log("FATAL! can't resolve %s, locking up\n", _name);
- while (1)
- __asm__ volatile("nop");
+ if (!addr) {
+ _sdk_log("FATAL! can't resolve %s, aborting\n", _name);
+ abort();
}
// Patch the GOT entry to "cache" the resolved address. This can probably
// be implemented in a faster way, but this thing is already too complex.
- for (uint32_t i = 0; i < dll->got_length; i++) {
+ for (int i = 0; i < dll->got_length; i++) {
if (dll->got[2 + i] == (uint32_t) sym->st_value) {
- dll->got[2 + i] = (uint32_t) address;
+ dll->got[2 + i] = (uint32_t) addr;
break;
}
}
- return address;
+ return addr;
}
// Implementation of the weird obscure hashing function used in the ELF .hash
@@ -127,120 +114,109 @@ static uint32_t _elf_hash(const char *str) {
return value;
}
-#ifdef USE_FILE_API
-static uint8_t *_dl_load_file(const char *filename, size_t *size_output) {
- int32_t fd = open(filename, 1);
- if (fd < 0) {
- _sdk_log("can't open %s, error = %d\n", filename, fd);
- _ERROR(RTLD_E_FILE_OPEN, 0);
- }
+/* Symbol map loading/introspection API */
- // Extract file size from the file's associated control block.
- // https://problemkaputt.de/psx-spx.htm#biosmemorymap
- FCB *fcb = (FCB *) *((FCB **) 0x80000140);
- size_t size = fcb[fd].filesize;
+int DL_InitSymbolMap(int num_entries) {
+ if (_symbol_map.entries)
+ DL_UnloadSymbolMap();
- uint8_t *buffer = malloc(size);
- if (!buffer) {
- _sdk_log("unable to allocate %d bytes for %s\n", size, filename);
- _ERROR(RTLD_E_FILE_ALLOC, 0);
- }
+ // TODO: find a way to calculate the optimal number of hash table "buckets"
+ // in order to minimize hash table size
+ _symbol_map.nbucket = num_entries;
+ _symbol_map.nchain = num_entries;
+ _symbol_map.index = 0;
+ _sdk_log(
+ "allocating nbucket = %d, nchain = %d\n",
+ _symbol_map.nbucket, num_entries
+ );
- //_sdk_log("loading %s (%d bytes)..", filename, size);
+ _symbol_map.entries = malloc(sizeof(MapEntry) * num_entries);
+ _symbol_map.bucket = malloc(sizeof(uint32_t) * num_entries);
+ _symbol_map.chain = malloc(sizeof(uint32_t) * num_entries);
- for (uint32_t offset = 0; offset < size; ) {
- int32_t length = read(fd, &(buffer[offset]), 0x800);
+ if (!_symbol_map.entries || !_symbol_map.bucket || !_symbol_map.chain) {
+ _sdk_log("unable to allocate symbol map table\n");
+ return -1;
+ }
- if (length <= 0) {
- close(fd);
- free(buffer);
+ memset(_symbol_map.bucket, 0xff, sizeof(uint32_t) * num_entries);
+ memset(_symbol_map.chain, 0xff, sizeof(uint32_t) * num_entries);
- _sdk_log("failed, error = %d\n", length);
- _ERROR(RTLD_E_FILE_READ, 0);
- }
+ return 0;
+}
- //_sdk_log(".");
- offset += length;
- }
+void DL_UnloadSymbolMap(void) {
+ if (!_symbol_map.entries)
+ return;
- close(fd);
- _sdk_log(" done\n");
+ free(_symbol_map.entries);
+ free(_symbol_map.bucket);
+ free(_symbol_map.chain);
- if (size_output)
- *size_output = size;
- return buffer;
+ _symbol_map.entries = 0;
+ _symbol_map.bucket = 0;
+ _symbol_map.chain = 0;
}
-#endif
-/* Symbol map loading/parsing API */
+void DL_AddMapSymbol(const char *name, void *ptr) {
+ uint32_t hash = _elf_hash(name);
+ int index = _symbol_map.index;
+ _symbol_map.index = index + 1;
-int32_t DL_ParseSymbolMap(const char *ptr, size_t size) {
- DL_UnloadSymbolMap();
+ MapEntry *entry = &(_symbol_map.entries[index]);
+ entry->hash = hash;
+ entry->ptr = ptr;
+
+ // Append a reference to the entry to the hash table's chain.
+ uint32_t *hash_entry = &(_symbol_map.bucket[hash % _symbol_map.nbucket]);
+ while (*hash_entry != 0xffffffff)
+ hash_entry = &(_symbol_map.chain[*hash_entry]);
+
+ *hash_entry = index;
+}
+
+int DL_ParseSymbolMap(const char *ptr, size_t size) {
+ int entries = 0;
// Perform a quick scan over the entire map text and count the number of
// newlines. This allows us to (over)estimate the number of entries and
- // allocate a sufficiently large hash/entry table.
- uint32_t entries = 0;
- for (uint32_t pos = 0; pos < size; pos++) {
+ // allocate a sufficiently large hash table.
+ for (int pos = 0; pos < size; pos++) {
if (ptr[pos] == '\n')
entries++;
}
- // TODO: find a way to calculate the optimal number of hash table "buckets"
- // in order to minimize hash table size
- _symbol_map.nbucket = entries;
- _symbol_map.nchain = entries;
- _sdk_log(
- "allocating nbucket = %d, nchain = %d\n",
- _symbol_map.nbucket,
- entries
- );
-
- // Allocate an entry table to store parsed symbols in, and an associated
- // hash table (same format as .hash section, with 8-byte header).
- _symbol_map.entries = malloc(sizeof(MapEntry) * entries);
- _symbol_map.bucket = malloc(sizeof(uint32_t) * _symbol_map.nbucket);
- _symbol_map.chain = malloc(sizeof(uint32_t) * entries);
+ int err = DL_InitSymbolMap(entries);
+ if (err)
+ return err;
- if (!_symbol_map.entries || !_symbol_map.bucket || !_symbol_map.chain) {
- _sdk_log("unable to allocate symbol map table\n");
- _ERROR(RTLD_E_MAP_ALLOC, -1);
- }
+ // Go again through the symbol map and fill in the hash table by calling
+ // DL_AddMapSymbol() for each valid entry.
+ entries = 0;
- for (uint32_t i = 0; i < _symbol_map.nbucket; i++)
- _symbol_map.bucket[i] = 0xffffffff;
- for (uint32_t i = 0; i < entries; i++)
- _symbol_map.chain[i] = 0xffffffff;
-
- // Go again through the symbol map and fill in the hash table.
- uint32_t index = 0;
- for (uint32_t pos = 0; (pos < size) && ptr[pos]; pos++) {
- char name[64];
- char type_string[2];
- uint64_t address64;
+ for (int pos = 0; (pos < size) && ptr[pos]; pos++) {
+ uint64_t full_addr;
+ char name[64], type_string[4];
size_t _size;
// e.g. "main T ffffffff80000000 100 ...\n"
- int32_t parsed = sscanf(
+ int parsed = sscanf(
&(ptr[pos]),
"%63s %1s %Lx %x",
name,
type_string,
- &address64,
+ &full_addr,
&_size // Optional, unused (yet)
);
if (parsed >= 3) {
// Drop the upper 32 bits of the address (for some reason MIPS nm
- // insists on printing 64-bit addresses... wtf) and normalize the
- // type letter to upper case, then check if the entry is valid and
- // non-null.
- void *address = (void *) ((uint32_t) address64);
- char _type = toupper(type_string[0]);
- uint32_t hash = _elf_hash(name);
- uint32_t hash_mod = hash % _symbol_map.nbucket;
-
- if (address && (
+ // insists on printing 64-bit addresses... wtf) and check if the
+ // entry is valid and non-null.
+ void *addr = (void *) ((uint32_t) full_addr);
+ char _type = toupper(type_string[0]);
+
+ if (addr && (
(_type == 'T') || // .text
(_type == 'R') || // .rodata
(_type == 'D') || // .data
@@ -248,21 +224,11 @@ int32_t DL_ParseSymbolMap(const char *ptr, size_t size) {
)) {
//_sdk_log(
//"map sym: %08x,%08x [%c %s]\n",
- //address, _size, _type, name
+ //addr, _size, _type, name
//);
- MapEntry *entry = &(_symbol_map.entries[index]);
- entry->hash = hash;
- entry->ptr = address;
-
- // Append a reference to the entry to the hash table's chain
- // for the current hash_mod. I can't explain this properly.
- uint32_t *hash_entry = &(_symbol_map.bucket[hash_mod]);
- while (*hash_entry != 0xffffffff)
- hash_entry = &(_symbol_map.chain[*hash_entry]);
-
- *hash_entry = index;
- index++;
+ DL_AddMapSymbol(name, addr);
+ entries++;
}
}
@@ -273,55 +239,27 @@ int32_t DL_ParseSymbolMap(const char *ptr, size_t size) {
}
_sdk_log("parsed %d symbols\n", entries);
- if (!entries)
- _ERROR(RTLD_E_NO_SYMBOLS, -1);
-
return entries;
}
-#ifdef USE_FILE_API
-int32_t DL_LoadSymbolMapFromFile(const char *filename) {
- size_t size;
- char *ptr = _dl_load_file(filename, &size);
- if (!ptr)
- return -1;
-
- int32_t entries = DL_ParseSymbolMap(ptr, size);
- free(ptr);
-
- return entries;
-}
-#endif
-
-void DL_UnloadSymbolMap(void) {
- if (!_symbol_map.entries)
- return;
-
- free(_symbol_map.entries);
- free(_symbol_map.bucket);
- free(_symbol_map.chain);
- _symbol_map.entries = 0;
-}
-
-void *DL_GetSymbolByName(const char *name) {
+void *DL_GetMapSymbol(const char *name) {
if (!_symbol_map.entries) {
- _sdk_log("attempted lookup with no map loaded\n");
- _ERROR(RTLD_E_NO_MAP, 0);
+ _sdk_log("DL_GetMapSymbol() with no map loaded\n");
+ return 0;
}
- // https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-48031.html
- uint32_t hash = _elf_hash(name);
- uint32_t hash_mod = hash % _symbol_map.nbucket;
-
// Go through the hash table's chain until the symbol hash matches the one
// calculated.
- for (uint32_t i = _symbol_map.bucket[hash_mod]; i != 0xffffffff;) {
+ // https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-48031.html
+ uint32_t hash = _elf_hash(name);
+
+ for (int i = _symbol_map.bucket[hash % _symbol_map.nbucket]; i != 0xffffffff;) {
if (i >= _symbol_map.nchain) {
_sdk_log(
- "GetSymbolByName() index out of bounds (%d >= %d)\n",
+ "DL_GetMapSymbol() index out of bounds (%d >= %d)\n",
i, _symbol_map.nchain
);
- _ERROR(RTLD_E_HASH_LOOKUP, 0);
+ return 0;
}
MapEntry *entry = &(_symbol_map.entries[i]);
@@ -335,27 +273,24 @@ void *DL_GetSymbolByName(const char *name) {
}
_sdk_log("map lookup [%s not found]\n", name);
- _ERROR(RTLD_E_MAP_SYMBOL, 0);
+ return 0;
}
-void DL_SetResolveCallback(void *(*callback)(DLL *, const char *)) {
+void *DL_SetResolveCallback(void *(*callback)(DLL *, const char *)) {
+ void *old_callback = _dl_resolve_callback;
_dl_resolve_callback = callback;
+
+ return old_callback;
}
/* Library loading and linking API */
-DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) {
- if (!ptr)
- _ERROR(RTLD_E_DLL_NULL, 0);
-
- DLL *dll = malloc(sizeof(DLL));
- if (!dll) {
- _sdk_log("unable to allocate DLL struct\n");
- _ERROR(RTLD_E_DLL_ALLOC, 0);
- }
+DLL *DL_CreateDLL(DLL *dll, void *ptr, size_t size, DL_ResolveMode mode) {
+ if (!dll || !ptr)
+ return 0;
dll->ptr = ptr;
- dll->malloc_ptr = (mode & RTLD_FREE_ON_DESTROY) ? ptr : 0;
+ dll->malloc_ptr = (mode & DL_FREE_ON_DESTROY) ? ptr : 0;
dll->size = size;
_sdk_log("initializing DLL at %08x\n", ptr);
@@ -371,47 +306,30 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) {
switch (dyn->d_tag) {
// Offset of .got section
case DT_PLTGOT:
- //_sdk_log("[PLTGOT]\n");
-
dll->got = (void *) (ptr + dyn->d_un.d_val);
break;
// Offset of .hash section
case DT_HASH:
- //_sdk_log("[HASH]\n");
-
dll->hash = (void *) (ptr + dyn->d_un.d_val);
break;
// Offset of .dynstr (NOT .strtab) section
case DT_STRTAB:
- //_sdk_log("[STRTAB]\n");
-
dll->strtab = (void *) (ptr + dyn->d_un.d_val);
break;
// Offset of .dynsym (NOT .symtab) section
case DT_SYMTAB:
- //_sdk_log("[SYMTAB]\n");
-
dll->symtab = (void *) (ptr + dyn->d_un.d_val);
break;
- // Length of .dynstr section
- //case DT_STRSZ:
- //_sdk_log("[STRSZ]\n");
- //break;
-
// Length of each .dynsym entry
case DT_SYMENT:
- //_sdk_log("[SYMENT]\n");
-
// Only 16-byte symbol table entries are supported.
if (dyn->d_un.d_val != sizeof(Elf32_Sym)) {
- free(dll);
-
_sdk_log("invalid DLL symtab entry size %d\n", dyn->d_un.d_val);
- _ERROR(RTLD_E_DLL_FORMAT, 0);
+ return 0;
}
break;
@@ -421,73 +339,44 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) {
// Versions other than 1 are unsupported (do they even exist?).
if (dyn->d_un.d_val != 1) {
- free(dll);
-
_sdk_log("invalid DLL version %d\n", dyn->d_un.d_val);
- _ERROR(RTLD_E_DLL_FORMAT, 0);
+ return 0;
}
break;
// DLL/ABI flags
case DT_MIPS_FLAGS:
- //_sdk_log("[MIPS_FLAGS]\n");
-
// Shortcut pointers (whatever they are) are not supported.
if (dyn->d_un.d_val & RHF_QUICKSTART) {
- free(dll);
-
_sdk_log("invalid DLL flags\n");
- _ERROR(RTLD_E_DLL_FORMAT, 0);
+ return 0;
}
break;
// Number of local (not to resolve) GOT entries
case DT_MIPS_LOCAL_GOTNO:
- //_sdk_log("[MIPS_LOCAL_GOTNO]\n");
-
local_got_len = dyn->d_un.d_val;
break;
// Base address DLL was compiled for
case DT_MIPS_BASE_ADDRESS:
- //_sdk_log("[MIPS_BASE_ADDRESS]\n");
-
// Base addresses other than zero are not supported. It would
// be easy enough to support them, but why?
if (dyn->d_un.d_val) {
- free(dll);
-
_sdk_log("invalid DLL base address %08x\n", dyn->d_un.d_val);
- _ERROR(RTLD_E_DLL_FORMAT, 0);
+ return 0;
}
break;
// Number of symbol table entries
case DT_MIPS_SYMTABNO:
- //_sdk_log("[MIPS_SYMTABNO]\n");
-
dll->symbol_count = dyn->d_un.d_val;
break;
- // Index of first unresolved symbol table entry
- //case DT_MIPS_UNREFEXTNO:
- //_sdk_log("[MIPS_UNREFEXTNO]\n");
- //break;
-
// Index of first symbol table entry which has a matching GOT entry
case DT_MIPS_GOTSYM:
- //_sdk_log("[MIPS_GOTSYM]\n");
-
first_got_sym = dyn->d_un.d_val;
break;
-
- // Number of pages the GOT is split into (does not apply to PS1)
- //case DT_MIPS_HIPAGENO:
- //_sdk_log("[MIPS_HIPAGENO]\n");
- //break;
-
- //default:
- //_sdk_log("[ignored]\n");
}
}
@@ -513,14 +402,14 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) {
dll->got[0] = (uint32_t) &_dl_resolve_wrapper;
dll->got[1] = (uint32_t) dll;
- for (uint32_t i = 0; i < dll->got_length; i++)
+ for (int i = 0; i < dll->got_length; i++)
dll->got[2 + i] += (uint32_t) ptr;
// Fix addresses in the symbol table.
// TODO: clean this shit up
uint32_t got_offset = first_got_sym;
- for (uint32_t i = 0; i < dll->symbol_count; i++) {
+ for (int i = 0; i < dll->symbol_count; i++) {
Elf32_Sym *sym = &(dll->symtab[i]);
const char *_name = &(dll->strtab[sym->st_name]);
@@ -533,12 +422,12 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) {
//sym->st_value, sym->st_size, _name
//);
- // If RTLD_NOW was passed, resolve GOT entries ahead of time by
+ // If DL_NOW was passed, resolve GOT entries ahead of time by
// cross-referencing them with the symbol table.
- if (!(mode & RTLD_NOW))
+ if (!(mode & DL_NOW))
continue;
- for (uint32_t j = got_offset; j < dll->got_length; j++) {
+ for (int j = got_offset; j < dll->got_length; j++) {
if (dll->got[2 + j] != (uint32_t) sym->st_value)
continue;
@@ -553,10 +442,8 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) {
)) {
dll->got[2 + j] = (uint32_t) _dl_resolve_callback(dll, _name);
- if (!dll->got[2 + j]) {
- free(dll);
- _ERROR(RTLD_E_MAP_SYMBOL, 0);
- }
+ if (!dll->got[2 + j])
+ return 0;
}
break;
@@ -573,7 +460,7 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) {
// DLL itself.
const uint32_t *ctor_list = DL_GetDLLSymbol(dll, "__CTOR_LIST__");
if (ctor_list) {
- for (uint32_t i = ((uint32_t) ctor_list[0]); i >= 1; i--) {
+ for (int i = ((int) ctor_list[0]); i >= 1; i--) {
void (*ctor)(void) = (void (*)(void)) ctor_list[i];
DL_PRE_CALL(ctor);
ctor();
@@ -583,64 +470,47 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) {
return dll;
}
-#ifdef USE_FILE_API
-DLL *DL_LoadDLLFromFile(const char *filename, DL_ResolveMode mode) {
- size_t size;
- char *ptr = _dl_load_file(filename, &size);
- if (!ptr)
- return 0;
-
- DLL *dll = DL_CreateDLL(ptr, size, mode | RTLD_FREE_ON_DESTROY);
- if (!dll)
- free(ptr);
-
- return dll;
-}
-#endif
-
void DL_DestroyDLL(DLL *dll) {
- if (dll == RTLD_DEFAULT)
+ if (!dll)
return;
if (dll->ptr) {
// Call the DLL's global destructors.
const uint32_t *dtor_list = DL_GetDLLSymbol(dll, "__DTOR_LIST__");
if (dtor_list) {
- for (uint32_t i = 0; i < ((uint32_t) dtor_list[0]); i++) {
+ for (int i = 0; i < ((int) dtor_list[0]); i++) {
void (*dtor)(void) = (void (*)(void)) dtor_list[i + 1];
DL_PRE_CALL(dtor);
dtor();
}
}
+
+ dll->ptr = 0;
}
- // If the DLL is associated to a buffer allocated by DL_LoadDLLFromFile(),
- // free that buffer.
- if (dll->malloc_ptr)
+ // If the DLL is associated to a buffer, free that buffer.
+ if (dll->malloc_ptr) {
free(dll->malloc_ptr);
-
- free(dll);
+ dll->malloc_ptr = 0;
+ }
}
void *DL_GetDLLSymbol(const DLL *dll, const char *name) {
- if (dll == RTLD_DEFAULT)
- return DL_GetSymbolByName(name);
- //return _dl_resolve_callback(RTLD_DEFAULT, name);
+ if (!dll)
+ return DL_GetMapSymbol(name);
+ //return _dl_resolve_callback(0, name);
- // https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-48031.html
uint32_t nbucket = dll->hash[0];
uint32_t nchain = dll->hash[1];
const uint32_t *bucket = &(dll->hash[2]);
const uint32_t *chain = &(dll->hash[2 + nbucket]);
- uint32_t hash_mod = _elf_hash(name) % nbucket;
-
// Go through the hash table's chain until the symbol name matches the one
// provided.
- for (uint32_t i = bucket[hash_mod]; i != 0xffffffff;) {
+ for (int i = bucket[_elf_hash(name) % nbucket]; i != 0xffffffff;) {
if (i >= nchain) {
_sdk_log("DL_GetDLLSymbol() index out of bounds (%d >= %d)\n", i, nchain);
- _ERROR(RTLD_E_HASH_LOOKUP, 0);
+ return 0;
}
Elf32_Sym *sym = &(dll->symtab[i]);
@@ -655,12 +525,5 @@ void *DL_GetDLLSymbol(const DLL *dll, const char *name) {
}
_sdk_log("DLL lookup [%s not found]\n", name);
- _ERROR(RTLD_E_DLL_SYMBOL, 0);
-}
-
-DL_Error DL_GetLastError(void) {
- DL_Error last = _error_code;
- _error_code = RTLD_E_NONE;
-
- return last;
+ return 0;
}