aboutsummaryrefslogtreecommitdiff
path: root/libpsn00b/psxetc
diff options
context:
space:
mode:
authorJohn "Lameguy" Wilbert Villamor <lameguy64@gmail.com>2021-08-31 13:23:20 +0800
committerGitHub <noreply@github.com>2021-08-31 13:23:20 +0800
commitffa679d4d24b891cb59aba10946368f2ec00c391 (patch)
tree0cf6061915ebf48acdedf6d77b0c1b76eec5b8c3 /libpsn00b/psxetc
parent317dc2b91d3afcdbaddb035f38611d12af161970 (diff)
parentf2fc18f82dd7900465d6ab3ae2080726d5589d39 (diff)
downloadpsn00bsdk-ffa679d4d24b891cb59aba10946368f2ec00c391.tar.gz
Merge pull request #36 from spicyjpeg/dynlink
Dynamic linker, gp-relative addressing, ldscripts and more
Diffstat (limited to 'libpsn00b/psxetc')
-rw-r--r--libpsn00b/psxetc/_dl_resolve_wrapper.s54
-rw-r--r--libpsn00b/psxetc/dl.c665
-rw-r--r--libpsn00b/psxetc/makefile59
-rw-r--r--libpsn00b/psxetc/readme.txt16
4 files changed, 764 insertions, 30 deletions
diff --git a/libpsn00b/psxetc/_dl_resolve_wrapper.s b/libpsn00b/psxetc/_dl_resolve_wrapper.s
new file mode 100644
index 0000000..069ee84
--- /dev/null
+++ b/libpsn00b/psxetc/_dl_resolve_wrapper.s
@@ -0,0 +1,54 @@
+# PSn00bSDK dynamic linker
+# (C) 2021 spicyjpeg - MPL licensed
+#
+# This function is called by the lazy loader stubs generated by GCC in the
+# .plt/.MIPS.stubs section when attempting to call a GOT entry whose address
+# hasn't yet been resolved. The generated stubs conform to the MIPS ABI and
+# uses the following registers to pass the following parameters:
+# - $t7 = address the resolved function should return to (i.e. $ra of the
+# caller that triggered the stub)
+# - $t8 = index of the function in the .dynsym symbol table
+# - $t9 = _dl_resolve_wrapper itself's address
+
+.set noreorder
+.section .text
+
+.global _dl_resolve_wrapper
+.type _dl_resolve_wrapper, @function
+_dl_resolve_wrapper:
+ # Push the registers we're going to use onto the stack.
+ addiu $sp, -16
+ sw $a0, 0($sp)
+ sw $a1, 4($sp)
+ sw $v0, 8($sp)
+ sw $t7, 12($sp) # (will be restored directly to $ra)
+
+ # Figure out where the DLL's struct is. dlinit() places a pointer to the
+ # struct in the second GOT entry, so it's just a matter of indexing the GOT
+ # using GP. Then call _dl_resolve_helper with the struct and $t8 as
+ # arguments, and store the return value into $t0.
+ lw $a0, -0x7fec($gp) # sizeof(uint32_t) - 0x7ff0 (see dll.ld)
+ move $a1, $t8
+
+ jal _dl_resolve_helper
+ addiu $sp, -8 # (branch delay)
+ addiu $sp, 8
+
+ move $t0, $v0
+
+ # Restore registers from the stack and tail-call the function at the
+ # address returned by the resolver. This will cause the resolved function
+ # to run and return directly to the code that called the stub.
+ lw $a0, 0($sp)
+ lw $a1, 4($sp)
+ lw $v0, 8($sp)
+ lw $ra, 12($sp)
+ addiu $sp, 16
+
+ jr $t0
+ nop
+
+.global _dl_credits
+.type _dl_credits, @object
+_dl_credits:
+ .asciiz "psxetc runtime dynamic linker by spicyjpeg\n"
diff --git a/libpsn00b/psxetc/dl.c b/libpsn00b/psxetc/dl.c
new file mode 100644
index 0000000..1485183
--- /dev/null
+++ b/libpsn00b/psxetc/dl.c
@@ -0,0 +1,665 @@
+/*
+ * PSn00bSDK dynamic linker
+ * (C) 2021 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
+ * in their OS/SDK (e.g. N64, PIC32 microcontrollers). Note that, despite the
+ * various ELF references, I did *not* implement a full ELF file parser: this
+ * implementation just expects all library files to begin with several metadata
+ * sections (.dynamic, .dynsym, .hash, .dynstr) laid out in a fixed order. Use
+ * the dll.ld linker script to generate compatible libraries.
+ *
+ * Most of the stuff implemented here, despite looking like black magic, is
+ * actually well-documented across several PDFs scattered around the internet:
+ * - http://www.sco.com/developers/devspecs/gabi41.pdf
+ * - http://math-atlas.sourceforge.net/devel/assembly/mipsabi32.pdf
+ * - http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf
+ *
+ * TODO:
+ * - Clean up duplicated code, especially hash-table-related loops
+ * - Fix bugs and improve allocation checks
+ * - Find a way to predict the smallest ELF hash table size for a certain set
+ * of entries
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <malloc.h>
+#include <ctype.h>
+#include <elf.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <psxapi.h>
+
+/* Compile options */
+
+// Uncomment before building to enable debug logging (via printf()).
+#define DEBUG
+
+// Comment before building to disable functions that rely on BIOS file APIs,
+// i.e. DL_LoadSymbolMap() and dlopen().
+#define USE_FILE_API
+
+/* Private types */
+
+typedef struct {
+ uint32_t hash;
+ void *ptr;
+} MapEntry;
+
+typedef enum {
+ ERR_NONE,
+ ERR_FILE,
+ ERR_FILE_MALLOC,
+ ERR_FILE_READ,
+ ERR_NO_MAP,
+ ERR_MAP_MALLOC,
+ ERR_NO_SYMBOLS,
+ ERR_DLL_NULL,
+ ERR_DLL_MALLOC,
+ ERR_DLL_FORMAT,
+ ERR_NO_FILE_API,
+ ERR_MAP_SYMBOL,
+ ERR_DLL_SYMBOL
+} ErrorCode;
+
+/* Data */
+
+static const char *const DL_ERROR_MESSAGES[] = {
+ "Unable to find file",
+ "Unable to allocate buffer to load file into",
+ "Failed to read file",
+ "No symbol map has been loaded yet",
+ "Unable to allocate symbol map structures",
+ "No symbols found in symbol map",
+ "Unable to initialize DLL from null pointer",
+ "Unable to allocate DLL metadata structures",
+ "Unsupported DLL type or format",
+ "psxetc has been built without file support",
+ "Symbol not found in symbol map",
+ "Symbol not found in DLL"
+};
+
+static ErrorCode _error_code = ERR_NONE;
+static uint32_t *_map_hash_table = 0;
+static MapEntry *_map_entry_table = 0;
+
+// Accessed by _dl_resolve_helper, stores the pointer to the current resolver
+// function. Can be changed using DL_SetResolveCallback().
+void *(*_dl_resolve_callback)(DLL *, const char *) = 0;
+
+/* Private utilities */
+
+#ifdef DEBUG
+#define _LOG(...) printf(__VA_ARGS__)
+#define _ERROR(code, ret) { \
+ _LOG("psxetc: ERROR! %s\n", DL_ERROR_MESSAGES[code - 1]); \
+ _error_code = code; \
+ return ret; \
+}
+#else
+#define _LOG(...)
+#define _ERROR(code, ret) { \
+ _error_code = code; \
+ return ret; \
+}
+#endif
+
+void _dl_resolve_wrapper(void);
+
+// Called by _dl_resolve_wrapper() (which is in turn called by GCC stubs) to
+// resolve a function.
+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;
+
+ if (_dl_resolve_callback)
+ address = _dl_resolve_callback(dll, _name);
+ else
+ address = DL_GetSymbolByName(_name);
+
+ if (!address) {
+ _LOG("psxetc: FATAL! Can't resolve %s, locking up\n", _name);
+ while (1)
+ __asm__ volatile("nop");
+ }
+
+ // 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++) {
+ if (dll->got[2 + i] == (uint32_t) sym->st_value) {
+ dll->got[2 + i] = (uint32_t) address;
+ break;
+ }
+ }
+
+ return address;
+}
+
+// Implementation of the weird obscure hashing function used in the ELF .hash
+// section. Not sure how collision-prone this is.
+// https://en.wikipedia.org/wiki/PJW_hash_function
+static uint32_t _elf_hash(const char *str) {
+ uint32_t value = 0;
+
+ while (*str) {
+ value <<= 4;
+ value += (uint32_t) *(str++);
+
+ uint32_t nibble = value & 0xf0000000;
+ if (nibble)
+ value ^= nibble >> 24;
+
+ value &= ~nibble;
+ }
+
+ return value;
+}
+
+#ifdef USE_FILE_API
+static uint8_t *_load_file(const char *filename, size_t *size_output) {
+ int32_t fd = open(filename, 1);
+ if (fd < 0)
+ _ERROR(ERR_FILE, 0);
+
+ // 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;
+
+ uint8_t *buffer = malloc(size);
+ if (!buffer)
+ _ERROR(ERR_FILE_MALLOC, 0);
+
+ _LOG("psxetc: Loading %s (%d bytes)..", filename, size);
+
+ for (uint32_t offset = 0; offset < size; ) {
+ int32_t length = read(fd, &(buffer[offset]), 0x800);
+
+ if (length <= 0) {
+ close(fd);
+ free(buffer);
+ _ERROR(ERR_FILE_READ, 0);
+ }
+
+ _LOG(".");
+ offset += length;
+ }
+
+ close(fd);
+ _LOG(" done\n");
+
+ if (size_output)
+ *size_output = size;
+ return buffer;
+}
+#endif
+
+/* Symbol map loading/parsing API */
+
+int32_t DL_ParseSymbolMap(const char *ptr, size_t size) {
+ DL_UnloadSymbolMap();
+
+ // 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++) {
+ 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
+ uint32_t nbucket = entries;
+ _LOG("psxetc: Predicted %d entries, %d hash buckets\n", entries, nbucket);
+
+ // Allocate an entry table to store parsed symbols in, and an associated
+ // hash table (same format as .hash section, with 8-byte header).
+ _map_hash_table = malloc(sizeof(uint32_t) * (2 + nbucket + entries));
+ _map_entry_table = malloc(sizeof(MapEntry) * entries);
+ if (!_map_hash_table || !_map_entry_table)
+ _ERROR(ERR_MAP_MALLOC, -1);
+
+ _map_hash_table[0] = nbucket;
+ _map_hash_table[1] = entries;
+ for (uint32_t i = 0; i < (nbucket + entries); i++)
+ _map_hash_table[2 + 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;
+ size_t _size;
+
+ // e.g. "main T ffffffff80000000 100 ...\n"
+ int32_t parsed = sscanf(
+ &(ptr[pos]),
+ "%63s %1s %Lx %x",
+ name,
+ type_string,
+ &address64,
+ &_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 *) address64;
+ char _type = toupper(type_string[0]);
+ uint32_t hash = _elf_hash(name);
+ uint32_t hash_mod = hash % nbucket;
+
+ if (address && (
+ (_type == 'T') || // .text
+ (_type == 'R') || // .rodata
+ (_type == 'D') || // .data
+ (_type == 'B') // .bss
+ )) {
+ _LOG(
+ "psxetc: Map sym: %08x,%08x [%c %s]\n",
+ address,
+ _size,
+ _type,
+ name
+ );
+
+ MapEntry *entry = &(_map_entry_table[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 = &(_map_hash_table[2 + hash_mod]);
+ while (*hash_entry != 0xffffffff)
+ hash_entry = &(_map_hash_table[2 + nbucket + *hash_entry]);
+
+ *hash_entry = index;
+ index++;
+ }
+ }
+
+ // Skip the line by advancing the pointer until a newline is found. The
+ // for loop will then skip the newline itself.
+ while ((pos < size) && (ptr[pos] != '\n'))
+ pos++;
+ }
+
+ _LOG("psxetc: Parsed %d symbols from map\n", entries);
+ if (!entries)
+ _ERROR(ERR_NO_SYMBOLS, -1);
+
+ return entries;
+}
+
+int32_t DL_LoadSymbolMap(const char *filename) {
+#ifdef USE_FILE_API
+ size_t size;
+ char *ptr = _load_file(filename, &size);
+ if (!ptr)
+ return -1;
+
+ int32_t entries = DL_ParseSymbolMap(ptr, size);
+ free(ptr);
+
+ return entries;
+#else
+ _ERROR(ERR_NO_FILE_API, -1);
+#endif
+}
+
+void DL_UnloadSymbolMap(void) {
+ if (!_map_hash_table)
+ return;
+
+ free(_map_hash_table);
+ free(_map_entry_table);
+ _map_hash_table = 0;
+}
+
+void *DL_GetSymbolByName(const char *name) {
+ if (!_map_hash_table)
+ _ERROR(ERR_NO_MAP, 0);
+
+ // https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-48031.html
+ uint32_t nbucket = _map_hash_table[0];
+ uint32_t hash = _elf_hash(name);
+ uint32_t hash_mod = hash % nbucket;
+
+ // Go through the hash table's chain until the symbol hash matches the one
+ // calculated.
+ for (uint32_t i = _map_hash_table[2 + hash_mod]; i;) {
+ MapEntry *entry = &(_map_entry_table[i]);
+
+ if (hash == entry->hash) {
+ _LOG("psxetc: Map lookup [%s = %08x]\n", name, entry->ptr);
+ return entry->ptr;
+ }
+
+ i = _map_hash_table[2 + nbucket + i];
+ }
+
+ _ERROR(ERR_MAP_SYMBOL, 0);
+}
+
+void DL_SetResolveCallback(void *(*callback)(DLL *, const char *)) {
+ _dl_resolve_callback = callback;
+}
+
+/* Library loading and linking API */
+
+DLL *dlinit(void *ptr, size_t size, DL_ResolveMode mode) {
+ if (!ptr)
+ _ERROR(ERR_DLL_NULL, 0);
+
+ DLL *dll = malloc(sizeof(DLL));
+ if (!dll)
+ _ERROR(ERR_DLL_MALLOC, 0);
+
+ dll->ptr = ptr;
+ dll->malloc_ptr = 0;
+ dll->size = size;
+ _LOG("psxetc: Initializing DLL at %08x\n", ptr);
+
+ // Interpret the key-value pairs in the .dynamic section to obtain info
+ // about all the other sections. The pairs are null-terminated, which makes
+ // parsing trivial.
+ uint32_t local_got_len = 0;
+ uint32_t first_got_sym = 0;
+
+ for (Elf32_Dyn *dyn = (Elf32_Dyn *) ptr; dyn->d_tag; dyn++) {
+ _LOG("psxetc: .dynamic %08x=%08x ", dyn->d_tag, dyn->d_un.d_val);
+
+ switch (dyn->d_tag) {
+ // Offset of .got section
+ case DT_PLTGOT:
+ _LOG("[PLTGOT]\n");
+
+ dll->got = (void *) (ptr + dyn->d_un.d_val);
+ break;
+
+ // Offset of .hash section
+ case DT_HASH:
+ _LOG("[HASH]\n");
+
+ dll->hash = (void *) (ptr + dyn->d_un.d_val);
+ break;
+
+ // Offset of .dynstr (NOT .strtab) section
+ case DT_STRTAB:
+ _LOG("[STRTAB]\n");
+
+ dll->strtab = (void *) (ptr + dyn->d_un.d_val);
+ break;
+
+ // Offset of .dynsym (NOT .symtab) section
+ case DT_SYMTAB:
+ _LOG("[SYMTAB]\n");
+
+ dll->symtab = (void *) (ptr + dyn->d_un.d_val);
+ break;
+
+ // Length of .dynstr section
+ //case DT_STRSZ:
+ //_LOG("[STRSZ]\n");
+ //break;
+
+ // Length of each .dynsym entry
+ case DT_SYMENT:
+ _LOG("[SYMENT]\n");
+
+ // Only 16-byte symbol table entries are supported.
+ if (dyn->d_un.d_val != sizeof(Elf32_Sym)) {
+ free(dll);
+ _ERROR(ERR_DLL_FORMAT, 0);
+ }
+ break;
+
+ // MIPS ABI (?) version
+ case DT_MIPS_RLD_VERSION:
+ _LOG("[MIPS_RLD_VERSION]\n");
+
+ // Versions other than 1 are unsupported (do they even exist?).
+ if (dyn->d_un.d_val != 1) {
+ free(dll);
+ _ERROR(ERR_DLL_FORMAT, 0);
+ }
+ break;
+
+ // DLL/ABI flags
+ case DT_MIPS_FLAGS:
+ _LOG("[MIPS_FLAGS]\n");
+
+ // Shortcut pointers (whatever they are) are not supported.
+ if (dyn->d_un.d_val & RHF_QUICKSTART) {
+ free(dll);
+ _ERROR(ERR_DLL_FORMAT, 0);
+ }
+ break;
+
+ // Number of local (not to resolve) GOT entries
+ case DT_MIPS_LOCAL_GOTNO:
+ _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:
+ _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);
+ _ERROR(ERR_DLL_FORMAT, 0);
+ }
+ break;
+
+ // Number of symbol table entries
+ case DT_MIPS_SYMTABNO:
+ _LOG("[MIPS_SYMTABNO]\n");
+
+ dll->symbol_count = dyn->d_un.d_val;
+ break;
+
+ // Index of first unresolved symbol table entry
+ //case DT_MIPS_UNREFEXTNO:
+ //_LOG("[MIPS_UNREFEXTNO]\n");
+ //break;
+
+ // Index of first symbol table entry which has a matching GOT entry
+ case DT_MIPS_GOTSYM:
+ _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:
+ //_LOG("[MIPS_HIPAGENO]\n");
+ //break;
+
+ default:
+ _LOG("[ignored]\n");
+ }
+ }
+
+ // Calculate the number of GOT entries (and symbols, if MIPS_SYMTABNO was
+ // not found in the .dynamic section).
+ //dll->symbol_count = \
+ ((uint32_t) dll->hash - (uint32_t) dll->symtab) / sizeof(Elf32_Sym);
+ //dll->got_length = \
+ ((uint32_t) ptr + size - (uint32_t) dll->got) / sizeof(uint32_t) - 2;
+
+ dll->got_length = local_got_len + (dll->symbol_count - first_got_sym) - 2;
+ _LOG(
+ "psxetc: %d symbols, %d GOT entries\n",
+ dll->symbol_count,
+ dll->got_length
+ );
+
+ // Relocate the DLL by adding its base address to all pointers in the GOT
+ // except the first two, which are reserved. The first entry in particular
+ // is a pointer to the lazy resolver, called by auto-generated stubs when a
+ // function is first used. got[1] is normally unused, but here we'll set it
+ // to the DLL's metadata struct so we can look that up when resolving
+ // functions (see _dl_resolve_wrapper()).
+ dll->got[0] = (uint32_t) &_dl_resolve_wrapper;
+ dll->got[1] = (uint32_t) dll;
+
+ for (uint32_t 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++) {
+ Elf32_Sym *sym = &(dll->symtab[i]);
+ const char *_name = &(dll->strtab[sym->st_name]);
+
+ if (!sym->st_value)
+ continue;
+
+ sym->st_value += (uint32_t) ptr;
+ _LOG(
+ "psxetc: DLL sym: %08x,%08x [%s]\n",
+ sym->st_value,
+ sym->st_size,
+ _name
+ );
+
+ // If RTLD_NOW was passed, resolve GOT entries ahead of time by
+ // cross-referencing them with the symbol table.
+ if (mode != RTLD_NOW)
+ continue;
+
+ for (uint32_t j = got_offset; j < dll->got_length; j++) {
+ if (dll->got[2 + j] != (uint32_t) sym->st_value)
+ continue;
+
+ got_offset = j;
+
+ // If the symbol is undefined (st_shndx = 0) and is either a
+ // variable or a function, resolve it immediately.
+ // TODO: linking of external variables needs more testing
+ if (!(sym->st_shndx) && (
+ ELF32_ST_TYPE(sym->st_info) == STT_OBJECT ||
+ ELF32_ST_TYPE(sym->st_info) == STT_FUNC
+ )) {
+ dll->got[2 + j] = (uint32_t) _dl_resolve_callback(dll, _name);
+
+ if (!dll->got[2 + j]) {
+ free(dll);
+ _ERROR(ERR_MAP_SYMBOL, 0);
+ }
+ }
+
+ break;
+ }
+ }
+
+ EnterCriticalSection();
+ FlushCache();
+ ExitCriticalSection();
+
+ // Call the DLL's global constructors. This is the same thing we'd do in
+ // _start() for regular executables, but we have to do it outside of the
+ // DLL as there's no _start() or even a defined entry point within the
+ // DLL itself.
+ const uint32_t *ctor_list = dlsym(dll, "__CTOR_LIST__");
+ if (ctor_list) {
+ for (uint32_t i = ((uint32_t) ctor_list[0]); i >= 1; i--) {
+ void (*ctor)(void) = (void (*)(void)) ctor_list[i];
+ DL_CALL(ctor);
+ }
+ }
+
+ return dll;
+}
+
+DLL *dlopen(const char *filename, DL_ResolveMode mode) {
+#ifdef USE_FILE_API
+ size_t size;
+ char *ptr = _load_file(filename, &size);
+ if (!ptr)
+ return 0;
+
+ DLL *dll = dlinit(ptr, size, mode);
+ if (dll)
+ dll->malloc_ptr = dll->ptr;
+ else
+ free(ptr);
+
+ return dll;
+#else
+ _ERROR(ERR_NO_FILE_API, 0);
+#endif
+}
+
+void dlclose(DLL *dll) {
+ if (dll == RTLD_DEFAULT)
+ return;
+
+ if (dll->ptr) {
+ // Call the DLL's global destructors.
+ const uint32_t *dtor_list = dlsym(dll, "__DTOR_LIST__");
+ if (dtor_list) {
+ for (uint32_t i = 0; i < ((uint32_t) dtor_list[0]); i++) {
+ void (*dtor)(void) = (void (*)(void)) dtor_list[i + 1];
+ DL_CALL(dtor);
+ }
+ }
+ }
+
+ // If the DLL is associated to a buffer allocated by dlopen(), free that
+ // buffer.
+ if (dll->malloc_ptr)
+ free(dll->malloc_ptr);
+
+ free(dll);
+}
+
+void *dlsym(DLL *dll, const char *name) {
+ if (dll == RTLD_DEFAULT)
+ return DL_GetSymbolByName(name);
+ //return _dl_resolve_callback(RTLD_DEFAULT, name);
+
+ // https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-48031.html
+ const uint32_t *hash_table = dll->hash;
+ uint32_t nbucket = hash_table[0];
+ 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 = hash_table[2 + hash_mod]; i;) {
+ Elf32_Sym *sym = &(dll->symtab[i]);
+ const char *_name = &(dll->strtab[sym->st_name]);
+
+ if (!strcmp(name, _name)) {
+ _LOG("psxetc: DLL lookup [%s = %08x]\n", name, sym->st_value);
+ return sym->st_value;
+ }
+
+ i = hash_table[2 + nbucket + i];
+ }
+
+ _ERROR(ERR_DLL_SYMBOL, 0);
+}
+
+const char *const dlerror(void) {
+ uint32_t last = _error_code;
+ _error_code = ERR_NONE;
+
+ if (last)
+ return DL_ERROR_MESSAGES[last - 1];
+
+ return 0;
+}
diff --git a/libpsn00b/psxetc/makefile b/libpsn00b/psxetc/makefile
index cc12944..84bb64b 100644
--- a/libpsn00b/psxetc/makefile
+++ b/libpsn00b/psxetc/makefile
@@ -1,46 +1,59 @@
-# Run using make (Linux) or gmake (BSD)
+# PSn00bSDK library makefile
# Part of the PSn00bSDK Project
-# 2019 - 2020 Lameguy64 / Meido-Tek Productions
+# 2019 - 2021 Lameguy64 / Meido-Tek Productions
-include ../../template/psn00bsdk-setup.mk
+## Settings
-TARGET = libpsxetc.a
+PSN00BSDK_LIBS ?= ..
-CFILES = $(notdir $(wildcard ./*.c))
-AFILES = $(notdir $(wildcard ./*.s))
-OFILES = $(addprefix build/,$(CFILES:.c=.o) $(AFILES:.s=.o))
+include ../../psn00bsdk-setup.mk
-CFLAGS = -g -O2 -msoft-float -fno-builtin -nostdlib -fdata-sections \
- -ffunction-sections -Wa,--strip-local-absolute
-AFLAGS = -g -msoft-float -strip-local-absolute
+# Project target name
+TARGET = libpsxetc.a
-INCLUDE = -I../include
+## Files
-ifndef PSN00BSDK_LIBS
+# Searches for C, C++ and S (assembler) files in local directory
+CFILES = $(notdir $(wildcard *.c))
+CXXFILES= $(notdir $(wildcard *.cxx))
+AFILES = $(notdir $(wildcard *.s))
-PSN00BSDK_LIBS = ..
+# Create names for object files
+OFILES = $(addprefix build/,$(CFILES:.c=.o)) \
+ $(addprefix build/,$(CXXFILES:.cxx=.o)) \
+ $(addprefix build/,$(AFILES:.s=.o))
-endif
+# Project specific includes and libraries
+# (use -I for include dirs, -L for library dirs, -l for static libraries)
+INCLUDE +=
+LIBDIRS +=
+LIBS +=
-all: $(TARGET)
+## Build rules
-$(TARGET): $(OFILES)
- $(AR) cr $(TARGET) $(OFILES)
- $(RANLIB) $(TARGET)
+all: build/$(TARGET)
+
+build/$(TARGET): $(OFILES)
+ @mkdir -p $(dir $@)
+ $(AR) crs $@ $(OFILES)
build/%.o: %.c
@mkdir -p $(dir $@)
- $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@
+ $(CC) $(CFLAGS_LIB) $(INCLUDE) -c $< -o $@
+
+build/%.o: %.cxx
+ @mkdir -p $(dir $@)
+ $(CXX) $(CPPFLAGS_LIB) $(INCLUDE) -c $< -o $@
build/%.o: %.s
@mkdir -p $(dir $@)
- $(AS) $(AFLAGS) $(INCLUDE) $< -o $@
+ $(CC) $(AFLAGS_LIB) $(INCLUDE) -c $< -o $@
install:
ifneq ($(PSN00BSDK_LIBS), "..")
@mkdir -p $(PSN00BSDK_LIBS)
endif
- cp $(TARGET) $(PSN00BSDK_LIBS)/$(TARGET)
-
+ cp build/$(TARGET) $(PSN00BSDK_LIBS)/$(TARGET)
+
clean:
- rm -Rf build $(TARGET)
+ rm -rf build
diff --git a/libpsn00b/psxetc/readme.txt b/libpsn00b/psxetc/readme.txt
index f2e7649..24645f7 100644
--- a/libpsn00b/psxetc/readme.txt
+++ b/libpsn00b/psxetc/readme.txt
@@ -1,18 +1,20 @@
PSX Misc library, part of PSn00bSDK
-2019 Lameguy64 / Meido-Tek Productions
+2021 Lameguy64 / Meido-Tek Productions
Licensed under Mozilla Public License
-Open source implementation of the ETC library. Doesn't really provide much
-aside from some debug font stuff at the moment.
-
+Open source implementation of the ETC library. Currently provides the interrupt
+and DMA callback dispatchers (used by other libraries) as well as the DL_* and
+dl* functions for dynamic library loading (original, not present in the official
+SDK but similar to the standard dlopen() API).
Library developer(s):
Lameguy64
-
-
+ spicyjpeg
+
Library header(s):
psxetc.h
-
+ dlfcn.h
+ elf.h (used internally)