/*
* dj64 - 64bit djgpp-compatible tool-chain
* Copyright (C) 2021-2024 @stsp
*
* FDPP - freedos port to modern C++
* Copyright (C) 2018 Stas Sergeev (stsp)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
/*
* NOTE: this file is a port of thunks.cc file from fdpp project.
* In fdpp project it is distributed under the terms of GNU GPLv3+
* and is copyrighted (C) 2018-2023 Stas Sergeev (stsp).
* As a sole author of the aforementioned dosobj.cc, I donate the
* code to dj64dev project, allowing to re-license it under the terms
* of GNU LGPLv3+.
*
* --stsp
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "thunks_a.h"
#include "thunks_c.h"
#include "plt.h"
#include "dosobj.h"
int __crt0_startup_flags;
struct udisp {
dj64dispatch_t *disp;
const struct elf_ops *eops;
struct athunks *at;
struct athunks *pt;
struct athunks core_at;
struct athunks core_pt;
unsigned cs;
main_t *main;
int full_init;
dpmi_regs s_regs;
int recur_cnt;
int objcnt;
jmp_buf *noret_jmp;
#define MAX_OBJS 50
#define MAX_RECUR 10
uint32_t objs[MAX_RECUR][MAX_OBJS];
};
#define MAX_HANDLES 10
static struct udisp udisps[MAX_HANDLES];
static const struct dj64_api *dj64api;
static dj64dispatch_t *disp_fn;
static struct athunks *u_athunks;
static struct athunks *u_pthunks;
static int *u_handle_p;
struct ctx_hooks {
void (*init)(int);
void (*deinit)(void);
void (*save)(void);
void (*restore)(int);
};
#define MAX_CTX_HOOKS 100
static struct ctx_hooks chooks[MAX_CTX_HOOKS];
static int num_chooks;
static void do_rm_dosobj(struct udisp *u, uint32_t fa);
void djloudvprintf(const char *format, va_list vl)
{
va_list cpy;
va_copy(cpy, vl);
dj64api->print(DJ64_PRINT_LOG, format, cpy);
va_end(cpy);
dj64api->print(DJ64_PRINT_TERMINAL, format, vl);
}
void djloudprintf(const char *format, ...)
{
va_list vl;
va_start(vl, format);
djloudvprintf(format, vl);
va_end(vl);
}
void djlogprintf(const char *format, ...)
{
va_list vl;
va_start(vl, format);
dj64api->print(DJ64_PRINT_LOG, format, vl);
va_end(vl);
}
uint8_t *djaddr2ptr(uint32_t addr)
{
return dj64api->addr2ptr(addr);
}
uint8_t *djaddr2ptr2(uint32_t addr, uint32_t len)
{
return dj64api->addr2ptr2(addr, len);
}
uint32_t djptr2addr(const uint8_t *ptr)
{
return dj64api->ptr2addr(ptr);
}
static int _dj64_call(struct udisp *u, int libid, int fn, dpmi_regs *regs,
uint8_t *sp, unsigned esi, dj64dispatch_t *disp, int handle)
{
int len;
UDWORD res;
int rc;
jmp_buf noret;
u->s_regs = *regs;
if ((rc = setjmp(noret))) {
int i;
for (i = 0; i < num_chooks; i++)
chooks[i].restore(handle);
/* gc lost objects, esp in ABORT case */
for (i = 0; i < MAX_OBJS; i++) {
if (u->objs[u->recur_cnt - 1][i])
do_rm_dosobj(u, u->objs[u->recur_cnt - 1][i]);
}
return (rc == ASM_NORET ? DJ64_RET_NORET : DJ64_RET_ABORT);
}
u->noret_jmp = &noret;
res = (libid ? disp : dj64_thunk_call)(fn, sp, &len);
*regs = u->s_regs;
switch (len) {
case 0:
break;
case 1:
case 2:
case 4:
regs->eax = res;
break;
default:
// _fail();
break;
}
return DJ64_RET_OK;
}
static int dj64_call(int handle, int libid, int fn, unsigned esi, uint8_t *sp)
{
int i;
int ret;
int last_objcnt;
struct udisp *u;
jmp_buf *saved_noret;
dpmi_regs *regs = (dpmi_regs *)sp;
sp += sizeof(*regs) + 8; // skip regs, ebp, eip to get stack args
assert(handle < MAX_HANDLES);
u = &udisps[handle];
assert(u->recur_cnt < MAX_RECUR);
u->recur_cnt++;
for (i = 0; i < num_chooks; i++)
chooks[i].restore(handle);
saved_noret = u->noret_jmp;
last_objcnt = u->objcnt;
ret = _dj64_call(u, libid, fn, regs, sp, esi, u->disp, handle);
assert(u->objcnt == last_objcnt); // make sure no leaks, esp on NORETURN
u->noret_jmp = saved_noret;
if (ret == DJ64_RET_OK) {
for (i = 0; i < num_chooks; i++)
chooks[i].save();
}
u->recur_cnt--;
return ret;
}
static int process_athunks(struct athunks *at, uint32_t mem_base,
const struct elf_ops *eops, void *eh)
{
int i, ret = 0;
for (i = 0; i < at->num; i++) {
const struct athunk *t = &at->at[i];
uint32_t off = eops->getsym(eh, t->name);
if (off) {
at->tab[i] = mem_base + off;
} else {
djloudprintf("symbol %s not resolved\n", t->name);
ret = -1;
break;
}
}
return ret;
}
static int process_pthunks(struct athunks *pt,
const struct elf_ops *eops, void *eh)
{
int i, ret = 0;
for (i = 0; i < pt->num; i++) {
const struct athunk *t = &pt->at[i];
pt->tab[i] = eops->getsym(eh, t->name);
if (!pt->tab[i]) {
djloudprintf("symbol %s not resolved\n", t->name);
ret = -1;
break;
}
}
return ret;
}
static ASMh(int, _crt0_startup_flags)
static void do_early_init(int handle)
{
*____crt0_startup_flags(handle) = __crt0_startup_flags;
}
static int dj64_ctrl(int handle, int libid, int fn, unsigned esi, uint8_t *sp)
{
dpmi_regs *regs = (dpmi_regs *)sp;
int ver = libid >> 8;
assert(handle < MAX_HANDLES);
if (ver != DL_API_VER) {
djloudprintf("dj64: API version mismatch, got %i want %i\n",
ver, DL_API_VER);
if (ver == 0) // this doesn't even handle errors, so terminate
dj64api->exit(1);
return -1;
}
switch (fn) {
case DL_SET_SYMTAB: {
struct udisp *u = &udisps[handle];
uint32_t flags = regs->eax;
int have_core = (flags & 0x4000);
uint32_t addr = regs->ebx;
uint32_t size = regs->ecx;
uint32_t mem_base = regs->edx;
void *eh;
int ret;
u->cs = esi;
djlogprintf("addr 0x%x mem_base 0x%x\n", addr, mem_base);
if (addr) {
char *elf = (char *)djaddr2ptr(addr);
djlogprintf("data %p(%s)\n", elf, elf);
eh = u->eops->open(elf, size);
if (!eh)
return -1;
if (have_core) {
ret = process_athunks(&u->core_at, mem_base, u->eops, eh);
if (ret)
goto err;
ret = process_pthunks(&u->core_pt, u->eops, eh);
if (ret)
goto err;
}
if (u->at) {
ret = process_athunks(u->at, mem_base, u->eops, eh);
if (ret)
goto err;
}
if (u->pt) {
ret = process_pthunks(u->pt, u->eops, eh);
if (ret)
goto err;
}
u->eops->close(eh);
}
if (!have_core) {
eh = u->eops->open_dyn();
if (!eh)
return -1;
ret = process_athunks(&u->core_at, mem_base, u->eops, eh);
if (ret)
goto err;
ret = process_pthunks(&u->core_pt, u->eops, eh);
if (ret)
goto err;
u->eops->close(eh);
}
do_early_init(handle);
return 0;
err:
u->eops->close(eh);
break;
}
}
return -1;
}
static dj64cdispatch_t *dops[] = { dj64_call, dj64_ctrl };
dj64cdispatch_t **DJ64_INIT_FN(int handle, const struct elf_ops *ops,
void *main, int full_init)
{
int i;
struct udisp *u;
assert(handle < MAX_HANDLES);
u = &udisps[handle];
u->disp = disp_fn;
disp_fn = NULL;
u->eops = ops;
u->main = main;
u->full_init = full_init;
u->at = u_athunks;
u_athunks = NULL;
u->pt = u_pthunks;
if (u_handle_p)
*u_handle_p = handle;
u_pthunks = NULL;
u_handle_p = NULL;
u->core_at = asm_thunks;
u->core_at.tab = dj64api->malloc(sizeof(asm_thunks.tab[0]) * asm_thunks.num);
u->core_pt = pthunks;
u->core_pt.tab = dj64api->malloc(sizeof(pthunks.tab[0]) * pthunks.num);
for (i = 0; i < num_chooks; i++)
chooks[i].init(handle);
return dops;
}
void DJ64_DONE_FN(int handle)
{
int i;
struct udisp *u;
for (i = 0; i < num_chooks; i++)
chooks[i].deinit();
assert(handle < MAX_HANDLES);
u = &udisps[handle];
dj64api->free(u->core_at.tab);
dj64api->free(u->core_pt.tab);
}
int DJ64_INIT_ONCE_FN(const struct dj64_api *api, int api_ver)
{
int ret = 0;
if (api_ver != DJ64_API_VER)
return -1;
if (!dj64api)
ret++;
dj64api = api;
return ret;
}
static uint64_t do_asm_call(struct udisp *u, struct athunks *pt,
unsigned cs, int num, uint8_t *sp, uint8_t len, int flags)
{
int rc;
dpmi_paddr pma;
assert(num < pt->num);
pma.selector = cs;
pma.offset32 = pt->tab[num];
if (flags & _TFLG_NORET) {
djlogprintf("NORET call %s: 0x%x:0x%x\n", pt->at[num].name,
pma.selector, pma.offset32);
dj64api->asm_noret(&u->s_regs, pma, sp, len);
longjmp(*u->noret_jmp, ASM_NORET);
}
djlogprintf("asm call %s: 0x%x:0x%x\n", pt->at[num].name,
pma.selector, pma.offset32);
rc = dj64api->asm_call(&u->s_regs, pma, sp, len);
djlogprintf("asm call %s returned %i:0x%x\n", pt->at[num].name,
rc, u->s_regs.eax);
switch (rc) {
case ASM_CALL_OK:
break;
case ASM_CALL_ABORT:
djlogprintf("reboot jump, %i\n", u->recur_cnt);
longjmp(*u->noret_jmp, ASM_ABORT);
break;
}
return u->s_regs.eax | ((uint64_t)u->s_regs.edx << 32);
}
uint64_t dj64_asm_call(int num, uint8_t *sp, uint8_t len, int flags)
{
int i;
int ret;
struct udisp *u;
int handle = dj64api->get_handle();
assert(handle < MAX_HANDLES);
for (i = 0; i < num_chooks; i++)
chooks[i].save();
u = &udisps[handle];
ret = do_asm_call(u, &u->core_pt, u->cs, num, sp, len, flags);
/* asm call can recursively invoke dj64, so restore context here */
for (i = 0; i < num_chooks; i++)
chooks[i].restore(handle);
return ret;
}
uint64_t dj64_asm_call_u(int handle, int num, uint8_t *sp, uint8_t len,
int flags)
{
int i;
int ret;
struct udisp *u;
assert(handle < MAX_HANDLES);
for (i = 0; i < num_chooks; i++)
chooks[i].save();
u = &udisps[handle];
ret = do_asm_call(u, u->pt, u->cs, num, sp, len, flags);
/* asm call can recursively invoke dj64, so restore context here */
for (i = 0; i < num_chooks; i++)
chooks[i].restore(handle);
return ret;
}
uint8_t *dj64_clean_stk(size_t len)
{
return dj64api->inc_esp(len);
}
static uint32_t *find_oh(uint32_t addr, uint32_t objs[MAX_RECUR][MAX_OBJS],
int recur_cnt)
{
int i;
for (i = 0; i < MAX_OBJS; i++) {
if (objs[recur_cnt - 1][i] == addr)
return &objs[recur_cnt - 1][i];
}
return NULL; // should not happen
}
static uint32_t do_obj_init(const void *data, uint16_t len, int is_out)
{
uint32_t ret;
int handle;
struct udisp *u;
if (dj64api->is_dos_ptr(data))
return PTR_DATA(data);
handle = dj64api->get_handle();
assert(handle < MAX_HANDLES);
u = &udisps[handle];
ret = mk_dosobj(len);
if (!is_out)
pr_dosobj(ret, data, len);
u->objcnt++;
*find_oh(0, u->objs, u->recur_cnt) = ret;
return ret;
}
uint32_t dj64_obj_init(const void *data, uint16_t len)
{
return do_obj_init(data, len, 0);
}
uint32_t dj64_obj_oinit(const void *data, uint16_t len)
{
return do_obj_init(data, len, 1);
}
void dj64_obj_done(void *data, uint32_t fa, uint16_t len)
{
int handle;
struct udisp *u;
if (dj64api->is_dos_ptr(data))
return;
handle = dj64api->get_handle();
assert(handle < MAX_HANDLES);
u = &udisps[handle];
cp_dosobj(data, fa, len);
rm_dosobj(fa);
*find_oh(fa, u->objs, u->recur_cnt) = 0;
u->objcnt--;
}
static void do_rm_dosobj(struct udisp *u, uint32_t fa)
{
rm_dosobj(fa);
*find_oh(fa, u->objs, u->recur_cnt) = 0;
u->objcnt--;
}
void dj64_rm_dosobj(const void *data, uint32_t fa)
{
int handle;
struct udisp *u;
if (dj64api->is_dos_ptr(data))
return;
handle = dj64api->get_handle();
assert(handle < MAX_HANDLES);
u = &udisps[handle];
do_rm_dosobj(u, fa);
}
void register_dispatch_fn(dj64dispatch_t *fn)
{
assert(!disp_fn);
disp_fn = fn;
}
void register_athunks(struct athunks *at)
{
assert(!u_athunks);
u_athunks = at;
}
void register_pthunks(struct athunks *pt, int *handle_p)
{
assert(!u_pthunks);
u_pthunks = pt;
u_handle_p = handle_p;
}
void crt1_startup(int handle)
{
struct udisp *u;
assert(handle < MAX_HANDLES);
__djgpp_nearptr_enable(); // speeds up things considerably
dosobj_init(dosobj_page, 4096);
u = &udisps[handle];
__crt1_startup(u->main);
}
static uint32_t do_thunk_get(const struct athunks *at, const char *name)
{
int i;
for (i = 0; i < at->num; i++) {
if (strcmp(at->at[i].name, name) == 0)
return at->tab[i];
}
return (uint32_t)-1;
}
uint32_t djthunk_get_h(int handle, const char *name)
{
struct udisp *u;
uint32_t ret = (uint32_t)-1;
assert(handle < MAX_HANDLES);
u = &udisps[handle];
if (u->at)
ret = do_thunk_get(u->at, name);
if (ret == (uint32_t)-1)
ret = do_thunk_get(&u->core_at, name);
assert(ret != (uint32_t)-1);
return ret;
}
uint32_t djthunk_get(const char *name)
{
return djthunk_get_h(dj64api->get_handle(), name);
}
void djregister_ctx_hooks(void (*init)(int), void (*deinit)(void),
void (*save)(void), void (*restore)(int))
{
struct ctx_hooks *c;
assert(num_chooks < MAX_CTX_HOOKS);
c = &chooks[num_chooks++];
c->init = init;
c->deinit = deinit;
c->save = save;
c->restore = restore;
}
void *djsbrk(int increment)
{
assert(increment > 0);
return dj64api->malloc(increment);
}