/*
* dj64 - 64bit djgpp-compatible tool-chain
* Copyright (C) 2021-2024 @stsp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include "djdev64/dj64init.h"
#include "djdev64/djdev64.h"
#include "elf_priv.h"
#ifdef __GLIBC__
#define HAVE_DLMOPEN 1
#endif
static int handles;
#define HNDL_MAX 5
struct dj64handle {
void *dlobj;
dj64cdispatch_t *cdisp;
dj64cdispatch_t *ctrl;
dj64done_t *done;
char *path;
};
static struct dj64handle dlhs[HNDL_MAX];
static pthread_mutex_t init_mtx = PTHREAD_MUTEX_INITIALIZER;
static const struct elf_ops eops = {
djelf_open,
djelf_open_dyn,
djelf_close,
djelf_getsymoff,
};
#define __S(x) #x
#define _S(x) __S(x)
#define FORMAT(T,A,B) __attribute__((format(T,A,B)))
FORMAT(printf, 2, 3)
static void loudprintf(const struct dj64_api *api, const char *str, ...)
{
va_list args;
va_start(args, str);
api->print(DJ64_PRINT_LOG, str, args);
va_end(args);
va_start(args, str);
api->print(DJ64_PRINT_TERMINAL, str, args);
va_end(args);
va_start(args, str);
api->print(DJ64_PRINT_SCREEN, str, args);
va_end(args);
}
static char *findmnt(const char *path)
{
const char *tmpl = "findmnt -n -o target --target %s";
static char buf[1024];
FILE *f;
int rd;
snprintf(buf, sizeof(buf), tmpl, path);
f = popen(buf, "r");
if (!f)
return NULL;
rd = fread(buf, 1, sizeof(buf), f);
if (rd <= 0)
goto err;
if (buf[rd - 1] == '\n')
rd--;
buf[rd] = '\0';
pclose(f);
return buf;
err:
pclose(f);
return NULL;
}
static const char *var_list[] = { "_crt0_startup_flags", NULL };
static void *emu_dlmopen(int handle, const char *filename, int flags,
char **r_path)
{
int err;
int len = strlen(filename) + 1 + 16;
char *path = malloc(len);
const char *p;
void *ret;
/* fake soname to cheat same-lib detection */
snprintf(path, len, "%.*s.%i", (int)strlen(filename), filename, handle);
p = strrchr(filename, '/');
if (!p)
p = filename;
else
p++;
unlink(path);
err = symlink(filename, path);
if (err) {
perror("symlink()");
goto err_free;
}
ret = dlopen(path, flags);
if (!ret)
goto err_free;
*r_path = path;
return ret;
err_free:
unlink(path);
free(path);
return NULL;
}
#define FLG_STATIC 1
static int _djdev64_open(const char *path, const struct dj64_api *api,
int api_ver, unsigned flags)
{
int rc;
dj64init_t *init;
dj64done_t *done;
dj64init_once_t *init_once;
dj64cdispatch_t **cdisp;
void *main;
void *dlh;
const char **v;
char *path2 = NULL;
struct dj64handle *h;
int use_dlm = 0;
int full;
if (handles >= HNDL_MAX) {
fprintf(stderr, "out of handles\n");
return -1;
}
/* RTLD_DEEPBIND has similar effect to -Wl,-Bsymbolic, but we don't
* use it with dlmopen() as asan doesn't work with that flag.
* So if we don't have dlmopen() then no asan support is possible. */
if (flags & DJ64F_DLMOPEN) {
#ifdef HAVE_DLMOPEN
use_dlm = 1;
dlh = dlmopen(LM_ID_NEWLM, path, RTLD_LOCAL | RTLD_NOW);
#else
fprintf(stderr, "dlmopen() not supported, asan won't work\n");
return -1;
#endif
} else if (flags & FLG_STATIC) {
dlh = emu_dlmopen(handles, path, RTLD_LOCAL | RTLD_NOW | RTLD_DEEPBIND,
&path2);
} else {
#define WANT_DLMOPEN 0
#if WANT_DLMOPEN
#ifdef HAVE_DLMOPEN
use_dlm = 1;
dlh = dlmopen(LM_ID_NEWLM, path, RTLD_LOCAL | RTLD_NOW);
#else
fprintf(stderr, "dlmopen() not supported, use static linking\n");
dlh = emu_dlmopen(handles, path, RTLD_LOCAL | RTLD_NOW | RTLD_DEEPBIND,
&path2);
#endif
#else
dlh = emu_dlmopen(handles, path, RTLD_LOCAL | RTLD_NOW | RTLD_DEEPBIND,
&path2);
#endif
}
if (!dlh) {
char cmd[1024];
const char *d = findmnt(path);
fprintf(stderr, "cannot dlopen %s: %s\n", path, dlerror());
snprintf(cmd, sizeof(cmd), "grep %.256s /proc/mounts | grep noexec", d);
if (system(cmd) == 0) {
loudprintf(api, "\nDJ64 ERROR: Your %s is mounted with noexec option.\n"
"Please execute:\n"
"\tsudo mount -o remount,exec %s\n"
"and try running the program again.\n",
d, d
);
}
return -1;
}
main = dlsym(dlh, "main");
if (!main) {
fprintf(stderr, "cannot find main\n");
goto err_close;
}
init_once = dlsym(dlh, _S(DJ64_INIT_ONCE_FN));
if (!init_once) {
fprintf(stderr, "cannot find " _S(DJ64_INIT_ONCE_FN) "\n");
goto err_close;
}
init = dlsym(dlh, _S(DJ64_INIT_FN));
if (!init) {
fprintf(stderr, "cannot find " _S(DJ64_INIT_FN) "\n");
goto err_close;
}
done = dlsym(dlh, _S(DJ64_DONE_FN));
if (!done) {
fprintf(stderr, "cannot find " _S(DJ64_DONE_FN) "\n");
goto err_close;
}
rc = init_once(api, api_ver);
if (rc == -1) {
fprintf(stderr, _S(DJ64_INIT_ONCE_FN) " failed\n");
goto err_close;
}
full = (use_dlm || (flags & FLG_STATIC) || !handles);
cdisp = init(handles, &eops, main, full);
if (!cdisp) {
fprintf(stderr, _S(DJ64_INIT_FN) " failed\n");
goto err_close;
}
for (v = var_list; *v; v++) {
char buf[256];
int *vh1, *vh2;
snprintf(buf, sizeof(buf), "_%s", *v);
vh1 = dlsym(dlh, *v);
vh2 = dlsym(dlh, buf);
if (vh1 && vh2)
*vh2 = *vh1;
}
h = &dlhs[handles];
h->dlobj = dlh;
h->cdisp = cdisp[0];
h->ctrl = cdisp[1];
h->done = done;
h->path = path2;
return handles++;
err_close:
dlclose(dlh);
if (path2) {
int err = unlink(path2);
if (err)
perror("unlink()");
free(path2);
}
return -1;
}
int djdev64_open(const char *path, const struct dj64_api *api, int api_ver,
unsigned flags)
{
int ret;
/* Init sequence is inherently thread-unsafe: at dlmopen() the ctors
* register the dispatch fn, which is stored in a global pointer until
* init() is called. Also we increment handles non-atomically. */
pthread_mutex_lock(&init_mtx);
ret = _djdev64_open(path, api, api_ver, flags);
pthread_mutex_unlock(&init_mtx);
return ret;
}
int djdev64_call(int handle, int libid, int fn, unsigned esi,
unsigned char *sp)
{
if (handle >= handles || !dlhs[handle].dlobj)
return -1;
return dlhs[handle].cdisp(handle, libid, fn, esi, sp);
}
int djdev64_ctrl(int handle, int libid, int fn, unsigned esi,
unsigned char *sp)
{
if (handle >= handles || !dlhs[handle].dlobj)
return -1;
return dlhs[handle].ctrl(handle, libid, fn, esi, sp);
}
void djdev64_close(int handle)
{
struct dj64handle *h;
int err;
if (handle >= handles)
return;
h = &dlhs[handle];
h->done(handle);
dlclose(h->dlobj);
h->dlobj = NULL;
if (h->path) {
err = unlink(h->path);
if (err)
perror("unlink()");
free(h->path);
}
while (handles > 0 && !dlhs[handles - 1].dlobj)
handles--;
}