/* Loads bFLT binaries
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Original author: Daniel Tang
* Original source code: https://github.com/tangrs/ndless-bflt-loader
*
*/
#include
#include "ndless.h"
#include "flat.h"
#include "bflt_config.h"
#include "bflt.h"
#if VERBOSE_LEVEL > 2
#define info(f, args...) printf("bFLT: "f"\n", ##args)
#else
#define info(f, args...) (void)0
#endif
#if VERBOSE_LEVEL > 1
#define error_return(x) return (printf("bFLT: %s\n",x), -1)
#define error_goto_error(x) do { printf("bFLT: %s\n",x); goto error; } while(0)
#else
#define error_return(x) return -1
#define error_goto_error(x) goto error
#endif
#if VERBOSE_LEVEL > 0
#define error_user_return(x, args...) do { \
char err_buffer[128]; \
sprintf(err_buffer,x,##args); \
show_msgbox("bFLT loader",err_buffer); \
error_return(err_buffer); \
} while (0)
#define error_user_goto_error(x, args...) do { \
char err_buffer[128]; \
sprintf(err_buffer,x,##args); \
show_msgbox("bFLT loader",err_buffer); \
error_goto_error(err_buffer); \
} while (0)
#else
#define error_user_return(x, args...) return -1
#define error_user_goto_error(x, args...) goto error
#endif
#ifdef SHARED_LIB_SUPPORT
typedef struct {
void *base;
unsigned long build_date;
} shared_lib_t;
/* we exclude index 0 because that referres to the current executable - all indexes are one behind */
static shared_lib_t lib_cache[sizeof(shared_lib_t) * (MAX_SHARED_LIB_ID-1)];
#endif
static inline void endian_fix32(uint32_t * tofix, size_t count) {
/* bFLT is always big endian */
/* endianness test */
union {
uint16_t int_val;
uint8_t char_val[2];
} endian;
endian.int_val = 1;
if (endian.char_val[0]) {
/* we are little endian, do a byteswap */
size_t i;
for (i=0; irev, ( &header->build_date - &header->rev ) + 1);
if (bytes_read == sizeof(struct flat_hdr)) {
return 0;
}else{
error_return("Error reading header");
}
}
static int check_header(struct flat_hdr * header) {
if (memcmp(header->magic, "bFLT", 4) != 0) error_return("Magic number does not match");
if (header->rev != FLAT_VERSION) error_return("Version number does not match");
/* check for unsupported flags */
if (header->flags & (FLAT_FLAG_GZIP | FLAT_FLAG_GZDATA)) error_user_return("Unsupported flags detected - GZip'd data is not supported");
return 0;
}
#ifdef SHARED_LIB_SUPPORT
static int load_shared_library(int id, void **base, unsigned long current_build_date) {
FILE* fp = NULL;
info("Trying to load library ID %d", id);
if (id < 0 || id > 0xfe) error_goto_error("Attempted to load library with invalid ID");
if (id > MAX_SHARED_LIB_ID) error_goto_error("Library ID too high");
char filename[128];
sprintf(filename,"%s/ndless/lib/lib%d.so.tns",get_documents_dir(), id);
fp = fopen(filename, "rb");
if (!fp) {
error_user_goto_error("Could not open shared library %s", filename);
}
/* get build date */
struct flat_hdr header;
if (read_header(fp,&header) != 0) error_goto_error("Could not read library header");
/* check cache */
if (lib_cache[id-1].base != NULL && /* check if library is already loaded */
lib_cache[id-1].build_date == header.build_date && /* check cached library is same version as file */
lib_cache[id-1].build_date <= current_build_date /* check build dates */
) {
info("Linking library from cache");
*base = lib_cache[id-1].base;
goto success;
}
if (header.build_date > current_build_date) error_user_goto_error("Library ID %d build date is newer than current executable. Refusing to load.", id);
int (*entry_point)(int,char*[]);
size_t dummy;
/* load into memory - nts: potential circular dependancy problem */
if (bflt_fload(fp, base, &dummy, &entry_point) != 0) error_goto_error("Could not load library");
/* initialize the library */
clear_cache();
if (entry_point(0,NULL) != 0) info("Warning: Library (ID:%d) init routine returned nonzero",id);
/* add to lib_cache */
/* if there's a stale library loaded, replace it */
if (lib_cache[id-1].base) free(lib_cache[id-1].base);
lib_cache[id-1].base = *base;
lib_cache[id-1].build_date = header.build_date;
/* successfully loaded library */
success:
info("Library ID %d loaded successfully", id);
fclose(fp);
return 0;
error:
*base = NULL;
if (fp) fclose(fp);
return -1;
}
#endif /* SHARED_LIB_SUPPORT */
static int copy_segments(FILE* fp, struct flat_hdr * header, void * mem, size_t max_size) {
/* each segment follows on one after the other */
/* [ .text ][ .data ][ .bss ] */
/* ^entry ^data_start ^data_end ^bss_end */
/* that means we can copy them all at once */
fseek(fp, header->entry, SEEK_SET);
size_t size_required = header->bss_end - header->entry;
size_t size_to_copy = header->data_end - header->entry;
if (size_required > max_size) error_return("Segment buffer not large enough");
/* zero out memory for bss */
memset( (char*)mem + size_to_copy, 0, size_required - size_to_copy );
if (fread(mem, 1, size_to_copy, fp) == size_to_copy) {
return 0;
}else{
error_return("Could not read all segments");
}
}
static inline void* calc_reloc(uint32_t offset, struct flat_hdr * header, void *base) {
/* the library id is located in high byte of offset entry */
int id = (offset >> 24) & 0xff;
offset &= 0x00ffffff;
/* fix up offset */
if (id != 0) {
/* need to load shared library */
#ifndef SHARED_LIB_SUPPORT
error_user_return("No support for bFLT shared libraries built in.");
#else
if (load_shared_library(id, &base, header->build_date) != 0) return (void*)-1;
#endif
}
return (void*)((uint32_t)base + offset);
}
static int process_relocs(FILE *fp, struct flat_hdr * header, void * base) {
if (!header->reloc_count) { info("No relocation needed"); return 0; }
fseek(fp, header->reloc_start, SEEK_SET);
size_t size_to_copy = sizeof(uint32_t) * header->reloc_count;
uint32_t * offset_list = malloc(size_to_copy);
if (!offset_list) error_return("Failed to allocate temporary memory in process_relocs");
size_t size_read = fread(offset_list, sizeof(uint32_t), header->reloc_count, fp);
if (size_read < size_to_copy/sizeof(uint32_t)) {
free(offset_list);
error_return("Failed to read relocs");
}
endian_fix32(offset_list, header->reloc_count);
size_t i;
for (i=0; ireloc_count; i++) {
uint32_t fixme = *(uint32_t*)((uint32_t)base + offset_list[i]);
void* relocd_addr = calc_reloc(fixme, header, base);
if (relocd_addr == (void*)-1) {
free(offset_list);
error_return("Unable to calculate relocation address");
}
*(uint32_t*)((uint32_t)base + offset_list[i]) = (uint32_t)relocd_addr;
}
free(offset_list);
return 0;
}
static int process_got(struct flat_hdr * header, void * base) {
uint32_t *got = (uint32_t*)((uint32_t)base + header->data_start - header->entry);
for (; *got != 0xffffffff; got++) {
void* relocd_addr = calc_reloc(*got, header, base);
if (relocd_addr == (void*)-1) {
error_return("Unable to calculate got address");
}
*got = (uint32_t)relocd_addr;
}
return 0;
}
int bflt_load(char* filename, void **mem_ptr, size_t *mem_size, int (**entry_address_ptr)(int,char*[])) {
FILE* fp = fopen(filename, "rb");
int ret;
if (!fp) error_user_return("Can't open file %s", filename);
ret = bflt_fload(fp, mem_ptr, mem_size, entry_address_ptr);
fclose(fp);
return ret;
}
int bflt_fload(FILE* fp, void **mem_ptr, size_t *mem_size, int (**entry_address_ptr)(int,char*[])) {
void * mem = NULL;
struct flat_hdr header;
info("Begin loading");
if (!fp) error_goto_error("Recieved bad file pointer");
if (read_header(fp, &header) != 0) error_goto_error("Could not parse header");
if (check_header(&header) != 0) error_goto_error("Bad header");
size_t binary_size = header.bss_end - header.entry;
info("Attempting to alloc %u bytes",binary_size);
mem = emu_debug_alloc_ptr ? emu_debug_alloc_ptr : malloc(binary_size);
if (!mem) error_goto_error("Failed to alloc binary memory");
if (copy_segments(fp, &header, mem, binary_size) != 0) error_goto_error("Failed to copy segments");
if (process_relocs(fp, &header, mem) != 0) error_goto_error("Failed to relocate");
/* only attempt to process GOT if the flags tell us a GOT exists AND
if the Ndless startup file is not already doing so */
if (header.flags & FLAT_FLAG_GOTPIC && strcmp(mem, PRGMSIG) != 0) {
if (process_got(&header, mem) != 0) error_goto_error("Failed to process got");
}else{
info("No need to process got - skipping");
}
*mem_ptr = mem;
*mem_size = binary_size;
if (memcmp(mem, "PRG\0", 4) == 0) {
info("Detected as ndless program packaged in a bFLT file");
*entry_address_ptr = (int (*)(int,char*[]))((char*)mem + 4);
}else{
info("Detected as ordinary bFLT executable");
*entry_address_ptr = (int (*)(int,char*[]))mem;
}
info("Successfully loaded bFLT executable to memory");
return 0;
error:
if (mem) free(mem);
*mem_ptr = NULL;
*entry_address_ptr = NULL;
*mem_size = 0;
error_return("Caught error - exiting");
}
void bflt_free(void* ptr) {
info("Free'd bFLT executable");
#ifdef SHARED_LIB_SUPPORT
#ifndef CACHE_LIBS_AFTER_EXEC
bflt_free_cached();
#endif
#endif
free(ptr);
}
void bflt_free_cached() {
#ifdef SHARED_LIB_SUPPORT
info("Free'd bFLT cached libraries");
size_t i;
for (i=0; i