aboutsummaryrefslogtreecommitdiff
path: root/libpsn00b
diff options
context:
space:
mode:
authorspicyjpeg <88942473+spicyjpeg@users.noreply.github.com>2021-08-17 11:37:03 +0000
committerspicyjpeg <88942473+spicyjpeg@users.noreply.github.com>2021-08-17 11:37:03 +0000
commitf2fc18f82dd7900465d6ab3ae2080726d5589d39 (patch)
tree74bcf61c346cdc4f108b8295dd82eca421a0eb1d /libpsn00b
parent89751f29467b359339a8c8b57c218cc3328bae43 (diff)
downloadpsn00bsdk-f2fc18f82dd7900465d6ab3ae2080726d5589d39.tar.gz
Added dynamic linker API and example, updated README and changelog
Diffstat (limited to 'libpsn00b')
-rw-r--r--libpsn00b/include/dlfcn.h176
-rw-r--r--libpsn00b/include/elf.h138
-rw-r--r--libpsn00b/psxetc/_dl_resolve_wrapper.s54
-rw-r--r--libpsn00b/psxetc/dl.c665
-rw-r--r--libpsn00b/psxetc/readme.txt16
5 files changed, 1042 insertions, 7 deletions
diff --git a/libpsn00b/include/dlfcn.h b/libpsn00b/include/dlfcn.h
new file mode 100644
index 0000000..5fdd343
--- /dev/null
+++ b/libpsn00b/include/dlfcn.h
@@ -0,0 +1,176 @@
+/*
+ * PSn00bSDK dynamic linker
+ * (C) 2021 spicyjpeg - MPL licensed
+ */
+
+#ifndef __DLFCN_H
+#define __DLFCN_H
+
+#include <sys/types.h>
+#include <elf.h>
+
+/* Helper macro for setting $t9 before calling a function */
+
+#define DL_CALL(func, ...) { \
+ __asm__ volatile("move $t9, %0;" :: "r"(func) : "$t9"); \
+ func(__VA_ARGS__); \
+}
+
+/* Types */
+
+#define RTLD_DEFAULT ((DLL *) 0)
+
+typedef enum _DL_ResolveMode {
+ RTLD_LAZY = 1, // Resolve functions when they are first called (default)
+ RTLD_NOW = 2 // Resolve all symbols immediately on load
+} DL_ResolveMode;
+
+// Members of this struct should not be accessed directly in most cases, but
+// they are intentionally exposed for easier expandability.
+typedef struct _DLL {
+ void *ptr;
+ void *malloc_ptr;
+ size_t size;
+ const uint32_t *hash;
+ uint32_t *got;
+ Elf32_Sym *symtab;
+ const char *strtab;
+ uint16_t symbol_count;
+ uint16_t got_length;
+} DLL;
+
+/* API */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// TODO: rewrite these javadoc comments into proper documentation
+
+/**
+ * @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.
+ *
+ * This function expects the string buffer to contain one more lines, each of
+ * which must follow this format:
+ *
+ * <SYMBOL_NAME> <T|R|D|B> <HEX_ADDRESS> <HEX_SIZE> [DEBUG_INFO...]
+ *
+ * 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:
+ *
+ * mipsel-unknown-elf-nm -f posix -l -n executable.elf >executable.map
+ *
+ * @param ptr
+ * @param size
+ * @return -1 or number of entries parsed
+ */
+int32_t DL_ParseSymbolMap(const char *ptr, size_t size);
+/**
+ * @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.
+ *
+ * @param filename Must always contain device name, e.g. "cdrom:MODULE.DLL;1"
+ * @return -1 or number of entries parsed
+ */
+int32_t DL_LoadSymbolMap(const char *filename);
+/**
+ * @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.
+ */
+void DL_UnloadSymbolMap(void);
+/**
+ * @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.
+ *
+ * @param name
+ * @return NULL or pointer to symbol (any type)
+ */
+void *DL_GetSymbolByName(const char *name);
+/**
+ * @brief 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
+ * find the symbol in the currently loaded symbol map.
+ *
+ * @param callback NULL or pointer to callback function
+ */
+void DL_SetResolveCallback(void *(*callback)(DLL *, const char *));
+
+/**
+ * @brief 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,
+ * 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 dlclose() 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_DEFAULT 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.
+ *
+ * @param ptr
+ * @param size
+ * @param mode RTLD_LAZY or RTLD_NOW
+ * @return NULL or pointer to a new DLL struct
+ */
+DLL *dlinit(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
+ * @return NULL or pointer to a new DLL struct
+ */
+DLL *dlopen(const char *filename, 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 dlclose() should no longer
+ * be used after the call. If the DLL was initialized in-place using dlinit(),
+ * dlclose() will *NOT* free the buffer initially passed to dlinit().
+ *
+ * @param dll
+ */
+void dlclose(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()).
+ *
+ * @param dll DLL struct or RTLD_DEFAULT
+ * @param name
+ * @return NULL or pointer to symbol (any type)
+ */
+void *dlsym(DLL *dll, const char *name);
+/**
+ * @brief Returns a string describing the last error that occurred, or null if
+ * no error has occurred since the last call to dlerror() (i.e. calling this
+ * also resets the internal error flags).
+ *
+ * @return NULL or pointer to const string
+ */
+const char *const dlerror(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libpsn00b/include/elf.h b/libpsn00b/include/elf.h
new file mode 100644
index 0000000..b4c4408
--- /dev/null
+++ b/libpsn00b/include/elf.h
@@ -0,0 +1,138 @@
+/*
+ * PSn00bSDK dynamic linker
+ * (C) 2021 spicyjpeg - MPL licensed
+ *
+ * This file is used internally by the dynamic linker to parse the "header"
+ * (.dynamic and .dynsym sections) of dynamically-linked libraries built with
+ * the provided linker script. Most of it is copied from the standard Unix
+ * elf.h header, with the only changes being removed typedefs and #defines
+ * converted to enums.
+ */
+
+#ifndef __ELF_H
+#define __ELF_H
+
+#include <sys/types.h>
+
+typedef enum {
+ DT_NULL = 0, /* Marks end of dynamic section */
+ DT_NEEDED = 1, /* Name of needed library */
+ DT_PLTRELSZ = 2, /* Size in bytes of PLT relocs */
+ DT_PLTGOT = 3, /* Processor defined value */
+ DT_HASH = 4, /* Address of symbol hash table */
+ DT_STRTAB = 5, /* Address of string table */
+ DT_SYMTAB = 6, /* Address of symbol table */
+ DT_RELA = 7, /* Address of Rela relocs */
+ DT_RELASZ = 8, /* Total size of Rela relocs */
+ DT_RELAENT = 9, /* Size of one Rela reloc */
+ DT_STRSZ = 10, /* Size of string table */
+ DT_SYMENT = 11, /* Size of one symbol table entry */
+ DT_INIT = 12, /* Address of init function */
+ DT_FINI = 13, /* Address of termination function */
+ DT_SONAME = 14, /* Name of shared object */
+ DT_RPATH = 15, /* Library search path (deprecated) */
+ DT_SYMBOLIC = 16, /* Start symbol search here */
+ DT_REL = 17, /* Address of Rel relocs */
+ DT_RELSZ = 18, /* Total size of Rel relocs */
+ DT_RELENT = 19, /* Size of one Rel reloc */
+ DT_PLTREL = 20, /* Type of reloc in PLT */
+ DT_DEBUG = 21, /* For debugging; unspecified */
+ DT_TEXTREL = 22, /* Reloc might modify .text */
+ DT_JMPREL = 23, /* Address of PLT relocs */
+ DT_BIND_NOW = 24, /* Process relocations of object */
+ DT_INIT_ARRAY = 25, /* Array with addresses of init fct */
+ DT_FINI_ARRAY = 26, /* Array with addresses of fini fct */
+ DT_INIT_ARRAYSZ = 27, /* Size in bytes of DT_INIT_ARRAY */
+ DT_FINI_ARRAYSZ = 28, /* Size in bytes of DT_FINI_ARRAY */
+ DT_RUNPATH = 29, /* Library search path */
+ DT_FLAGS = 30, /* Flags for the object being loaded */
+ DT_ENCODING = 32, /* Start of encoded range */
+ DT_PREINIT_ARRAY = 32, /* Array with addresses of preinit fct*/
+ DT_PREINIT_ARRAYSZ = 33, /* size in bytes of DT_PREINIT_ARRAY */
+ DT_SYMTAB_SHNDX = 34, /* Address of SYMTAB_SHNDX section */
+ DT_NUM = 35, /* Number used */
+ DT_LOOS = 0x6000000d, /* Start of OS-specific */
+ DT_HIOS = 0x6ffff000, /* End of OS-specific */
+ DT_LOPROC = 0x70000000, /* Start of processor-specific */
+ DT_HIPROC = 0x7fffffff, /* End of processor-specific */
+
+ DT_MIPS_RLD_VERSION = 0x70000001,
+ DT_MIPS_FLAGS = 0x70000005,
+ DT_MIPS_BASE_ADDRESS = 0x70000006,
+ DT_MIPS_LOCAL_GOTNO = 0x7000000a,
+ DT_MIPS_SYMTABNO = 0x70000011,
+ DT_MIPS_UNREFEXTNO = 0x70000012,
+ DT_MIPS_GOTSYM = 0x70000013,
+ DT_MIPS_HIPAGENO = 0x70000014
+} Elf32_d_tag;
+
+typedef enum {
+ RHF_NONE = 0, /* No flags */
+ RHF_QUICKSTART = 1, /* Use quickstart */
+ RHF_NOTPOT = 2, /* Hash size not power of 2 */
+ RHF_NO_LIBRARY_REPLACEMENT = 4 /* Ignore LD_LIBRARY_PATH */
+} Elf32_d_MIPS_FLAGS;
+
+typedef struct {
+ Elf32_d_tag d_tag; /* Dynamic entry type */
+ union {
+ uint32_t d_val; /* Integer value */
+ void *d_ptr; /* Address value */
+ } d_un;
+} Elf32_Dyn;
+
+typedef struct {
+ uint32_t st_name; /* Symbol name (string tbl index) */
+ void *st_value; /* Symbol value */
+ size_t st_size; /* Symbol size */
+ uint8_t st_info; /* Symbol type and binding */
+ uint8_t st_other; /* Symbol visibility */
+ uint16_t st_shndx; /* Section index */
+} Elf32_Sym;
+
+#define ELF32_ST_BIND(val) ((Elf32_st_bind) (((uint8_t) (val)) >> 4))
+#define ELF32_ST_TYPE(val) ((Elf32_st_type) ((val) & 0xf))
+#define ELF32_ST_INFO(bind, type) ((((uint8_t) (bind)) << 4) + (((uint8_t) (type)) & 0xf))
+
+typedef enum {
+ STB_LOCAL = 0, /* Local symbol */
+ STB_GLOBAL = 1, /* Global symbol */
+ STB_WEAK = 2, /* Weak symbol */
+ STB_NUM = 3, /* Number of defined types. */
+ STB_LOOS = 10, /* Start of OS-specific */
+ STB_GNU_UNIQUE = 10, /* Unique symbol. */
+ STB_HIOS = 12, /* End of OS-specific */
+ STB_LOPROC = 13, /* Start of processor-specific */
+ STB_HIPROC = 15 /* End of processor-specific */
+} Elf32_st_bind;
+
+typedef enum {
+ STT_NOTYPE = 0, /* Symbol type is unspecified */
+ STT_OBJECT = 1, /* Symbol is a data object */
+ STT_FUNC = 2, /* Symbol is a code object */
+ STT_SECTION = 3, /* Symbol associated with a section */
+ STT_FILE = 4, /* Symbol's name is file name */
+ STT_COMMON = 5, /* Symbol is a common data object */
+ STT_TLS = 6, /* Symbol is thread-local data object*/
+ STT_NUM = 7, /* Number of defined types. */
+ STT_LOOS = 10, /* Start of OS-specific */
+ STT_GNU_IFUNC = 10, /* Symbol is indirect code object */
+ STT_HIOS = 12, /* End of OS-specific */
+ STT_LOPROC = 13, /* Start of processor-specific */
+ STT_HIPROC = 15 /* End of processor-specific */
+} Elf32_st_type;
+
+// If you need to add more constants, you may use the following Python snippet
+// to quickly convert #defines to enums:
+/*
+import re
+t = """<paste #defines here>"""
+t = re.sub(
+ r"(0x[0-9a-f]+|0b[01]+|[0-9]+)",
+ lambda m: f"= {m.group(1)},",
+ t.replace("#define ", "\t").replace("#define\t", "\t")
+)
+print("typedef enum {\n" + t + "\n} NAME;")
+*/
+
+#endif
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/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)