summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--Makefile2
-rw-r--r--README.md61
-rw-r--r--main.c319
-rw-r--r--make.bat6
5 files changed, 362 insertions, 31 deletions
diff --git a/.gitignore b/.gitignore
index 6de5a42..f7798eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,8 @@ TAGS
*.lib
*.exp
*.dll
+*.pdb
+*.obj
+_dlsub_temp*
+.vs
+*.swp
diff --git a/Makefile b/Makefile
index 324430b..4c914ab 100644
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,2 @@
dlsub: main.c
- $(CC) -O0 -g -o dlsub main.c -std=c89 -Wpedantic -pedantic -Wall -Wextra -Wshadow -Wconversion -Wimplicit-fallthrough
+ $(CC) -O2 -g -o dlsub main.c -std=c89 -Wpedantic -pedantic -Wall -Wextra -Wshadow -Wconversion -Wimplicit-fallthrough
diff --git a/README.md b/README.md
index 7ff7ea3..b8c0c06 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# dlsub
-A tool for replacing a subset of functions in dynamic libraries.
+A tool (x86-64 only) for replacing a subset of functions in dynamic libraries.
Let's say you're meddling around with a program that uses
[SDL](https://libsdl.org). One thing you might want to do is replace an SDL
@@ -29,15 +29,26 @@ To install these on Ubuntu/Debian:
sudo apt install nasm tcc
```
+On Windows, you can get yourself a copy of Microsoft Visual Studio, search for
+a file called `vcvarsall.bat`, add it to your PATH, and the run `vcvarsall x64`
+to set up the C compiler. You can install NASM from their website, and to make
+things more convenient, you can add nasm.exe to your PATH (or just copy
+the file to the same directory as dlsub.exe).
+
On Unix-like systems, the default is to use TCC (for faster preprocessing and
less likelihood of weird syntax messing dlsub up). You can, however, override
this by setting the C_PREPROCESSOR environment variable.
+## Compiling dlsub
+
+You can use the `Makefile` and `make.bat` provided, or you can just compile
+`main.c` with any C compiler.
+
## Figuring out which library file is being used
On Windows, it may just be a DLL file in the same directory as the exe.
-Otherwise, you can install [depends.exe](https://www.dependencywalker.com/);
-good luck.
+Otherwise, you can install [depends.exe](https://www.dependencywalker.com/)
+to figure it out.
On Unix-like systems, if you want to know what specific library files an
executable is using, run:
@@ -60,9 +71,14 @@ You can specify multiple header files if the library has more than one.
Here is an example invocation for replacing SDL:
```bash
-dlsub --no-warn -l /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so -I /usr/include/SDL2 -i SDL.h -i SDL_syswm.h -i SDL_vulkan.h -C -DSDL_DISABLE_IMMINTRIN_H -o sdl
+dlsub --no-warn -l <SDL library file> -I <SDL include directory> -i SDL.h -i SDL_syswm.h -i SDL_vulkan.h -C <- on Unix, / on Windows>DSDL_DISABLE_IMMINTRIN_H -o sdl
```
+Substitute `<SDL library file>` and `<SDL include directory>`
+with `/lib/x86_64-linux-gnu/libSDL2-2.0.so` and `/usr/include/SDL2` on Linux, and
+something like `C:\\SDL2-2.0.14\\lib\\x64\\SDL2.dll` and
+`C:\\SDL2-2.0.14\\include` on Windows
+
(the `-DSDL_DISABLE_IMMINTRIN_H` is needed for tcc, and it also speeds up
processing)
@@ -71,11 +87,19 @@ Now let's say you want to replace `SDL_SetWindowTitle`. First, delete the line
in `sdl.asm`:
```
-global SDL_SetWindowTitle:function
+GLOBAL SDL_SetWindowTitle
```
(This deletes the default replacement, i.e. to redirect to the real SDL
function).
+
+(This is specific to SDL.h on Windows)
+At the start of sdl.c, add:
+
+```c
+#define DLL_EXPORT
+```
+
Now at the end of sdl.c, add:
```c
@@ -87,7 +111,8 @@ DLSUB_EXPORT void SDL_SetWindowTitle(SDL_Window *window, const char *title) {
The `DLSUB_EXPORT` ensures that the function is exported out to the dynamic
library (on Windows, where that distinction is made).
-You can now compile libSDL2-2.0.so.0 on Linux, with:
+On Linux, you can now compile libSDL2-2.0.so.0 with:
+
```
nasm -f elf64 sdl.asm
cc -fPIC -shared sdl.o sdl.c -o libSDL2-2.0.so.0 -I/usr/include/SDL2
@@ -98,6 +123,15 @@ And run a program that uses SDL like this:
LD_LIBRARY_PATH=/directory/where/your/library/file/is ./some_application
```
+And on Windows:
+
+```
+nasm -f win64 sdl.asm -o sdl_asm.obj
+cl /nologo /Fe:SDL2 /LD sdl_asm.obj sdl.c /I C:\\SDL2-2.0.14\\include
+```
+
+And just copy `SDL2.dll` to the same directory as the target application.
+
Note that dlsub *cannot* handle dynamic libraries' objects (e.g.
`extern int foo;`), so if there are any you will have to make your own
substitutes for those.
@@ -114,7 +148,7 @@ without assembly.
## More examples...
-### Replacing `XNextEvent` from libX11
+### (Unix-y) Replacing `XNextEvent` from libX11
```bash
dlsub --no-warn -l /usr/lib/x86_64-linux-gnu/libX11.so.6 -I /usr/include/X11 -i Xlib.h -i Xutil.h -o x11
@@ -125,7 +159,7 @@ dlsub --no-warn -l /usr/lib/x86_64-linux-gnu/libX11.so.6 -I /usr/include/X11 -i
Delete the line from x11.asm:
```
-global XNextEvent:function
+GLOBAL XNextEvent
```
Add to the bottom of x11.c:
@@ -152,7 +186,7 @@ nasm -f elf64 x11.asm
cc -fPIC -shared x11.c x11.o -o libX11.so.6 -I/usr/include/X11
```
-### Replacing `exp` from libm
+### (Unix-y) Replacing `exp` from libm
Here's a silly example. This could cause some... interesting behavior.
@@ -165,7 +199,7 @@ dlsub --no-warn -l /lib/x86_64-linux-gnu/libm.so.6 -I /usr/include -i math.h -o
Delete from math.asm:
```
-global exp:function
+GLOBAL exp
```
Add to math.c:
@@ -179,3 +213,10 @@ double exp(double x) {
nasm -f elf64 math.asm
cc -fPIC -shared math.c math.o -o libm.so.6
```
+
+### Report a bug
+
+Bugs can be sent to `pommicket at pommicket.com`. Please only report bugs that
+could/do actually occur in real usage of dlsub; in theory, it might not
+correctly parse a function returning a function pointer that returns a function
+pointer but that doesn't happen in real libraries.
diff --git a/main.c b/main.c
index 235c935..46de394 100644
--- a/main.c
+++ b/main.c
@@ -1,3 +1,11 @@
+/*
+Anyone is free to modify/distribute/use/sell/etc
+this software for any purpose by any means.
+Any copyright protections are relinquished by the author(s).
+This software is provided "as is", without any warranty.
+The author(s) shall not be held liable in connection with this software.
+*/
+
#define VERSION "0.0"
#if __GNUC__
@@ -92,14 +100,73 @@
#endif
#elif _WIN32
- #define C_PREPROCESSOR_DEFAULT "cl"
+ #define C_PREPROCESSOR_DEFAULT "cl.exe /nologo /P"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>
#include <assert.h>
- #error "@TODO"
+ #include <windows.h>
+
+ #if _WIN64
+ #define fseek _fseeki64
+ #define ftell _ftelli64
+ typedef __int64 fseek_t;
+ #else
+ typedef long fseek_t;
+ #endif
+
+ typedef struct {
+ unsigned signature;
+ unsigned short machine;
+ unsigned short n_sections;
+ unsigned time_date_stamp;
+ unsigned ptr_symbol_table;
+ unsigned n_symbols;
+ unsigned short size_of_optional_header;
+ unsigned short characteristics;
+ } PEHeader;
+
+ typedef struct {
+ char name[8];
+ unsigned virtual_size;
+ unsigned virtual_address;
+ unsigned size_of_raw_data;
+ unsigned ptr_to_raw_data;
+ unsigned ptr_to_relocations;
+ unsigned ptr_to_line_numbers;
+ unsigned short number_of_relocations;
+ unsigned short number_of_linenumbers;
+ unsigned characteristics;
+ } PESectionHeader;
+
+ typedef struct {
+ unsigned flags;
+ unsigned time_date_stamp;
+ unsigned short major_version;
+ unsigned short minor_version;
+ unsigned name_rva;
+ unsigned ordinal_base;
+ unsigned address_table_entries;
+ unsigned n_name_pointers;
+ unsigned export_address_table_rva;
+ unsigned name_pointer_rva;
+ unsigned ordinal_table_rva;
+ } PEExportDirectoryTable;
+
+ static char PEHeader__static_assertion[2 * (sizeof(PEHeader) == 24) - 1];
+ static char PESectionHeader__static_assertion[2 * (sizeof(PESectionHeader) == 40) - 1];
+ static char PEExportDirectoryTable__static_assertion[2 * (sizeof(PEExportDirectoryTable) == 40) - 1];
+
+ static int file_is_readable(const char *filename) {
+ FILE *f = fopen(filename, "rb");
+ if (f) {
+ fclose(f);
+ return 1;
+ }
+ return 0;
+ }
#else
#error "Unsupported operating system."
#endif
@@ -170,7 +237,7 @@ static void symbol_hash_table_grow(SymbolHashTable *table) {
for (i = 0; i < table->n_entries; ++i) {
SymbolHashEntry *entry = table->entries[i];
- unsigned long p;
+ size_t p;
if (!entry) continue;
p = str_hash(entry->symbol) % new_n_entries;
while (new_entries[p]) {
@@ -186,7 +253,7 @@ static void symbol_hash_table_grow(SymbolHashTable *table) {
}
static void symbol_hash_table_insert(SymbolHashTable *table, const char *sym_name) {
- unsigned long p;
+ size_t p;
SymbolHashEntry *entry;
if (table->n_present_entries * 2 >= table->n_entries) {
symbol_hash_table_grow(table);
@@ -211,7 +278,7 @@ static void symbol_hash_table_insert(SymbolHashTable *table, const char *sym_nam
}
static SymbolHashEntry *symbol_hash_table_get(SymbolHashTable *table, const char *name) {
- unsigned long p = str_hash(name) % table->n_entries;
+ size_t p = str_hash(name) % table->n_entries;
SymbolHashEntry *entry;
while ((entry = table->entries[p])) {
if (strcmp(entry->symbol, name) == 0)
@@ -437,12 +504,70 @@ int main(int argc, char **argv) {
}
}
#else
- #error "@TODO"
+ {
+ PEHeader pe_header = {0};
+
+ { /* check if this is an actual DLL, find offset to PE header */
+ unsigned short signature1 = 0;
+ unsigned pe_header_offset = 0;
+
+ fread(&signature1, sizeof signature1, 1, fp);
+ if (signature1 != 0x5a4d) {
+ fprintf(stderr, "%s is not a DLL file.\n", libname);
+ exit(2);
+ }
+
+ fseek(fp, 0x3c, SEEK_SET);
+ fread(&pe_header_offset, sizeof pe_header_offset, 1, fp);
+ fseek(fp, (fseek_t)pe_header_offset, SEEK_SET);
+ }
+
+ fread(&pe_header, sizeof pe_header, 1, fp);
+
+ if (pe_header.signature != 0x00004550 || (pe_header.characteristics & 0x2000) == 0) {
+ fprintf(stderr, "%s is not a DLL file.\n", libname);
+ exit(2);
+ }
+
+ fseek(fp, (fseek_t)pe_header.size_of_optional_header, SEEK_CUR);
+
+ {
+ unsigned section;
+ PESectionHeader section_header = {0};
+ for (section = 1; section <= pe_header.n_sections; ++section) {
+ fread(&section_header, sizeof section_header, 1, fp);
+ if (strncmp(section_header.name, ".edata", 8) == 0) {
+ /* offset for all "RVA" pointers */
+ fseek_t rva_offset = (fseek_t)section_header.ptr_to_raw_data - (fseek_t)section_header.virtual_address;
+
+ PEExportDirectoryTable edt = {0};
+ unsigned s;
+ unsigned name_ptr = 0;
+ char name[256];
+
+ fseek(fp, (fseek_t)section_header.ptr_to_raw_data, SEEK_SET);
+ fread(&edt, sizeof edt, 1, fp);
+
+ for (s = 0; s < edt.n_name_pointers; ++s) {
+ fseek(fp, rva_offset + (fseek_t)edt.name_pointer_rva + s * 4, SEEK_SET);
+ fread(&name_ptr, sizeof name_ptr, 1, fp);
+ fseek(fp, rva_offset + (fseek_t)name_ptr, SEEK_SET);
+ fread(name, 1, sizeof name, fp);
+ symbol_hash_table_insert(&all_symbols, name);
+ /* printf("%s\n",name); */
+ }
+ break;
+ }
+ }
+ }
+
+ }
#endif
fclose(fp);
}
+ /* preprocess headers */
#if __unix__
{
int preprocessed_headers_fd = fileno(tmpfile());
@@ -534,9 +659,7 @@ int main(int argc, char **argv) {
}
if (fd == -1) {
- char prefix[128];
- sprintf(prefix, "Couldn't open %.100s", header);
- perror(prefix);
+ fprintf(stderr, "Couldn't find %.100s\n", header);
kill(SIGKILL, compiler_process);
exit(2);
}
@@ -594,9 +717,114 @@ int main(int argc, char **argv) {
}
#else
- #error "@TODO"
-#endif
+ {
+ const char *in_headers_name = "_dlsub_temp";
+ const char *out_headers_name = "_dlsub_temp.i";
+ FILE *in_headers;
+
+ in_headers = fopen(in_headers_name, "wb");
+ if (!in_headers) {
+ perror("Couldn't create temporary file");
+ exit(2);
+ }
+
+ for (i = 1; i < argc-1; ++i) {
+ if (strcmp(argv[i], "-i") == 0) {
+ const char *header_name = argv[i+1];
+ FILE *header_fp = fopen(header_name, "rb");
+ char buf[4096];
+ size_t bytes_read;
+
+ if (!header_fp) {
+ /* check include directories */
+ char path[520];
+ int j;
+ for (j = 1; j < argc-1; ++j) {
+ if (strcmp(argv[j], "-I") == 0) {
+ const char *dir = argv[j+1];
+ sprintf(path, "%.256s\\%.256s", dir, header_name);
+ header_fp = fopen(path, "rb");
+ if (header_fp) break;
+ }
+ if (argv[j][0] == '-' && argv[j][1] != '-') ++j;
+ }
+
+ if (!header_fp) {
+ fprintf(stderr, "Couldn't find %.100s.\n", header_name);
+ exit(2);
+ }
+ }
+
+ while ((bytes_read = fread(buf, 1, sizeof buf, header_fp))) {
+ fwrite(buf, 1, bytes_read, in_headers);
+ }
+
+ }
+ if (argv[i][0] == '-' && argv[i][1] != '-') ++i;
+ }
+
+ fclose(in_headers);
+
+ {
+ static char command[L_tmpnam + 50000] = {0};
+ size_t p = 0;
+ sprintf(&command[p], "%.300s %.1000s ", preprocessor_program, in_headers_name);
+ p += strlen(&command[p]);
+ for (i = 1; i < argc-1; ++i) {
+ if (strcmp(argv[i], "-I") == 0) {
+ const char *inc = argv[i+1];
+ if (p + strlen(inc) + 10 > sizeof command) {
+ fprintf(stderr, "Too many arguments to C preprocessor.\n");
+ exit(-1);
+ }
+ sprintf(&command[p], "/I %s ", inc);
+ p += strlen(&command[p]);
+ } else if (strcmp(argv[i], "-C") == 0) {
+ const char *arg = argv[i+1];
+ if (p + strlen(arg) + 10 > sizeof command) {
+ fprintf(stderr, "Too many arguments to C preprocessor.\n");
+ exit(-1);
+ }
+ sprintf(&command[p], "%s ", arg);
+ p += strlen(&command[p]);
+ }
+ if (argv[i][0] == '-' && argv[i][1] != '-') ++i;
+ }
+ system(command);
+ if (!file_is_readable(out_headers_name)) {
+ fprintf(stderr, "C header preprocessing failed.\n");
+ exit(2);
+ }
+ }
+
+ {
+ HANDLE output_file = CreateFileA(out_headers_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ HANDLE mapping;
+
+ if (output_file == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "Couldn't open %s (Windows error code %u).\n", out_headers_name, GetLastError());
+ exit(2);
+ }
+
+ mapping = CreateFileMappingA(output_file, NULL, PAGE_READONLY, 0, 0, NULL);
+ if (mapping == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "Couldn't create mapping for %s (Windows error code %u).\n", out_headers_name, GetLastError());
+ exit(2);
+ }
+
+ /* lets hope nobody's preprocessed headers are >4GB in size ... */
+ preprocessed_headers_len = GetFileSize(output_file, NULL);
+ preprocessed_headers = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, preprocessed_headers_len);
+ if (!preprocessed_headers) {
+ fprintf(stderr, "Couldn't map file %s into memory (Windows error code %u).\n", out_headers_name, GetLastError());
+ exit(2);
+ }
+ }
+
+
+ }
+#endif
/* figure out functions! */
@@ -638,7 +866,24 @@ int main(int argc, char **argv) {
#else
fprintf(c_output, "#if _WIN32\n");
#endif
- fprintf(c_output, "#define DLSUB_REAL_DL_NAME \"%s\"\n", input_filename);
+ fprintf(c_output, "#define DLSUB_REAL_DL_NAME \"");
+ {
+ const char *p;
+ for (p = input_filename; *p; ++p) {
+ switch (*p) {
+ case '\\':
+ fputs("\\\\", c_output);
+ break;
+ case '"':
+ fputs("\\\"", c_output);
+ break;
+ default:
+ putc(*p, c_output);
+ break;
+ }
+ }
+ }
+ fprintf(c_output, "\"\n");
fprintf(c_output,
"#else\n"
"#error \"Please define DLSUB_REAL_DLNAME here.\"\n"
@@ -652,17 +897,23 @@ int main(int argc, char **argv) {
"#define DLSUB_GET_DLHANDLE(filename) dlopen(filename, RTLD_LAZY)\n"
"#define DLSUB_GET_SYM(handle, name) ((void(*)(void))dlsym(handle, name))\n"
"#define DLSUB_EXPORT\n"
+ "#define DLSUB_CALL\n"
"static void __attribute__((constructor)) dlsub_constructor(void) {\n"
"\tdlsub_init();\n"
+ "\t}\n"
"}\n"
);
fprintf(c_output,
"#elif _WIN32\n"
- "extern void *__stdcall LoadLibraryA(const char *);\n"
- "extern int (*__stdcall GetProcAddress(void *, const char *))(void);\n"
+ "typedef struct HINSTANCE__ *HMODULE;\n"
+ "__declspec(dllimport) HMODULE __stdcall LoadLibraryA(const char *);\n"
+ "__declspec(dllimport) __int64 (*__stdcall GetProcAddress(HMODULE, const char *))();\n"
"#define DLSUB_GET_DLHANDLE LoadLibraryA\n"
"#define DLSUB_GET_SYM GetProcAddress\n"
- "#define DLSUB_EXPORT __declspec((dllexport))\n"
+ "#define DLSUB_CALL\n"
+ "#define DLSUB_EXPORT __declspec(dllexport)\n"
+ );
+ fprintf(c_output,
"unsigned __stdcall DllMain(void *instDLL, unsigned reason, void *_reserved) {\n"
"\t(void)instDLL; (void)_reserved;\n"
"\tswitch (reason) {\n"
@@ -671,6 +922,8 @@ int main(int argc, char **argv) {
"\t\tbreak;\n"
"\tcase 0: /* DLL unloaded */\n"
"\t\tbreak;\n"
+ "\t}\n"
+ "\treturn 1;\n"
"}\n"
);
fprintf(c_output,
@@ -789,6 +1042,18 @@ int main(int argc, char **argv) {
memmove(attr, p, (size_t)(statement + strlen(statement) + 1 - p));
}
}
+ {
+ /* remove MSVC calling conventions */
+ char *cc = strstr(statement, "__cdecl");
+ if (cc)
+ memmove(cc, cc + 7, (size_t)(statement + strlen(statement) + 1 - (cc+7)));
+ cc = strstr(statement, "__stdcall");
+ if (cc)
+ memmove(cc, cc + 9, (size_t)(statement + strlen(statement) + 1 - (cc+9)));
+ cc = strstr(statement, "__fastcall");
+ if (cc)
+ memmove(cc, cc + 10, (size_t)(statement + strlen(statement) + 1 - (cc+10)));
+ }
}
if (
@@ -807,7 +1072,7 @@ int main(int argc, char **argv) {
/* not a function declaration */
} else {
/* possibly a function declaration */
- char *func_name = statement, *func_name_end;
+ char *func_name = statement, *func_name_end = 0;
if (strncmp(func_name, "const ", 6) == 0) func_name += 6;
while (is_ident(*func_name)) ++func_name;
if (*func_name == ' ') ++func_name;
@@ -839,7 +1104,7 @@ int main(int argc, char **argv) {
} else {
entry->declared = 1;
fprintf(c_output,
- "typedef %.*s (*PTR_%.*s)%.*s;\n"
+ "typedef %.*s (*DLSUB_CALL PTR_%.*s)%.*s;\n"
"PTR_%.*s REAL_%.*s;\n",
(int)(func_name - statement),
statement,
@@ -866,7 +1131,21 @@ int main(int argc, char **argv) {
const SymbolHashEntry *entry;
const char *symbol;
- fprintf(nasm_output, "default rel\n");
+ fputs("default rel\n"
+ "%ifidn __OUTPUT_FORMAT__, elf64\n"
+ "\t%define FUNCPTR(p) [p wrt ..gotpc]\n"
+ "\t%macro GLOBAL 1\n"
+ "\t\tglobal %1 %+ :function\n"
+ "\t%endmacro\n"
+ "%else\n"
+ "\t%define FUNCPTR(p) p\n"
+ "\t%macro GLOBAL 1\n"
+ "\t\tglobal %1\n"
+ "\t\texport %1\n"
+ "\t%endmacro\n"
+ "%endif\n",
+ nasm_output);
+
for (s = 0; s < all_symbols.n_entries; ++s) {
entry = all_symbols.entries[s];
@@ -890,7 +1169,7 @@ int main(int argc, char **argv) {
symbol = entry->symbol;
fprintf(c_output, "\tREAL_%s = (%s%s)DLSUB_GET_SYM(handle, \"%s\");\n", symbol,
entry->declared ? "PTR_" : "void (*)(void)", entry->declared ? symbol : "", symbol);
- fprintf(nasm_output, "global %s:function\n", symbol);
+ fprintf(nasm_output, "GLOBAL %s\n", symbol);
}
@@ -898,7 +1177,7 @@ int main(int argc, char **argv) {
entry = all_symbols.entries[s];
if (!entry) continue;
symbol = entry->symbol;
- fprintf(nasm_output, "%s: mov r11, [REAL_%s wrt ..gotpc]\njmp [r11]\n", symbol, symbol);
+ fprintf(nasm_output, "%s: mov r11, FUNCPTR(REAL_%s)\njmp [r11]\n", symbol, symbol);
}
}
diff --git a/make.bat b/make.bat
new file mode 100644
index 0000000..081bfa2
--- /dev/null
+++ b/make.bat
@@ -0,0 +1,6 @@
+@echo off
+if "%VCVARS%" == "" (
+ set "VCVARS=1"
+ call vcvarsall x64
+)
+cl /O2 /Zi /DEBUG /nologo /wd4996 /wd4706 /W4 main.c /Fe:dlsub.exe