#include #include #include #include #include #include #include #include #include "mcpu-object.h" #include "mcpu-melf.h" typedef struct blob { unsigned char *p; size_t n; size_t cap; } blob; typedef struct wsec { const char *name; __mpu_uint32_t type; __mpu_uint64_t flags; __mpu_uint64_t align; __mpu_uint64_t entsize; __mpu_uint32_t link; __mpu_uint32_t info; unsigned char *owned; const unsigned char *payload; size_t size; __mpu_uint64_t off; __mpu_uint32_t name_off; unsigned logical; } wsec; typedef struct rsh { __mpu_uint32_t name; __mpu_uint32_t type; __mpu_uint64_t flags; __mpu_uint64_t addr; __mpu_uint64_t off; __mpu_uint64_t size; __mpu_uint32_t link; __mpu_uint32_t info; __mpu_uint64_t align; __mpu_uint64_t entsize; } rsh; static const unsigned object_sections[] = { CPU_OBJ_SEC_INIT, CPU_OBJ_SEC_TEXT, CPU_OBJ_SEC_FINI, CPU_OBJ_SEC_DATA, CPU_OBJ_SEC_BSS, CPU_OBJ_SEC_CONTROL, CPU_OBJ_SEC_INPUT, CPU_OBJ_SEC_OUTPUT, CPU_OBJ_SEC_TRACE }; #define OBJECT_SECTION_COUNT \ (sizeof(object_sections) / sizeof(object_sections[0])) static int object_add_symbol( cpu_asm_object *o, const char *name, unsigned section, unsigned binding, unsigned type, __mpu_uint64_t value, char *err, size_t es) { cpu_asm_symbol *q; if (!o || !name) return( -1 ); q = (cpu_asm_symbol *)realloc( o->symbols, (o->symbol_count + 1) * sizeof(*q)); if (!q) { snprintf( err, es, "out of memory" ); return( -1 ); } o->symbols = q; memset( &o->symbols[o->symbol_count], 0, sizeof(o->symbols[o->symbol_count])); o->symbols[o->symbol_count].name = strdup( name ); if (!o->symbols[o->symbol_count].name) { snprintf( err, es, "out of memory" ); return( -1 ); } o->symbols[o->symbol_count].section = section; o->symbols[o->symbol_count].binding = binding; o->symbols[o->symbol_count].type = type; o->symbols[o->symbol_count].value = value; ++o->symbol_count; return( 0 ); } static int object_add_reloc( cpu_asm_object *o, unsigned section, unsigned type, size_t symbol_index, __mpu_uint64_t offset, __mpu_int64_t addend, char *err, size_t es) { cpu_asm_reloc *q; q = (cpu_asm_reloc *)realloc( o->relocs, (o->reloc_count + 1) * sizeof(*q)); if (!q) { snprintf( err, es, "out of memory" ); return( -1 ); } o->relocs = q; o->relocs[o->reloc_count].section = section; o->relocs[o->reloc_count].type = type; o->relocs[o->reloc_count].symbol_index = symbol_index; o->relocs[o->reloc_count].offset = offset; o->relocs[o->reloc_count].addend = addend; ++o->reloc_count; return( 0 ); } static int blob_grow( blob *b, size_t add ) { size_t need; size_t cap; unsigned char *q; if (add > SIZE_MAX - b->n) return( -1 ); need = b->n + add; if (need <= b->cap) return( 0 ); cap = b->cap ? b->cap : 64; while (cap < need) { if (cap > SIZE_MAX / 2) return( -1 ); cap *= 2; } q = (unsigned char *)realloc( b->p, cap ); if (!q) return( -1 ); b->p = q; b->cap = cap; return( 0 ); } static int blob_put( blob *b, const void *p, size_t n ) { if( blob_grow( b, n ) ) return( -1 ); if (n) memcpy( b->p + b->n, p, n ); b->n += n; return( 0 ); } static int blob_string( blob *b, const char *s, __mpu_uint32_t *off ) { size_t n = strlen( s ) + 1; if (b->n > UINT32_MAX || n > (size_t)UINT32_MAX - b->n) return( -1 ); *off = (__mpu_uint32_t)b->n; return( blob_put( b, s, n ) ); } static void free_wsecs( wsec *s, size_t n ) { size_t i; for( i = 0; i < n; ++i ) free( s[i].owned ); } static void put_ehdr64( unsigned char *p, int data, __mpu_uint16_t type, __mpu_uint64_t shoff, __mpu_uint16_t shnum, __mpu_uint16_t shstr) { memset( p, 0, MELF64_EHDR_SIZE ); p[MELF_EI_MAG0] = MELFMAG0; p[MELF_EI_MAG1] = MELFMAG1; p[MELF_EI_MAG2] = MELFMAG2; p[MELF_EI_MAG3] = MELFMAG3; p[MELF_EI_CLASS] = MELF_CLASS_64; p[MELF_EI_DATA] = (unsigned char)data; p[MELF_EI_VERSION] = MELF_EV_CURRENT; p[MELF_EI_OSABI] = MELF_OSABI_MCPU; p[MELF_EI_ABIVERSION] = MELF_ABIVERSION_CURRENT; cpu_melf_put16( p + MELF64_EHDR_E_TYPE_OFF, type, data ); cpu_melf_put16( p + MELF64_EHDR_E_MACHINE_OFF, MELF_EM_MCPU, data ); cpu_melf_put32( p + MELF64_EHDR_E_VERSION_OFF, MELF_EV_CURRENT, data ); cpu_melf_put64( p + MELF64_EHDR_E_ENTRY_OFF, 0, data ); cpu_melf_put64( p + MELF64_EHDR_E_INIT_ENTRY_OFF, 0, data ); cpu_melf_put64( p + MELF64_EHDR_E_LIFE_ENTRY_OFF, 0, data ); cpu_melf_put64( p + MELF64_EHDR_E_FINI_ENTRY_OFF, 0, data ); cpu_melf_put64( p + MELF64_EHDR_E_PHOFF_OFF, 0, data ); cpu_melf_put64( p + MELF64_EHDR_E_SHOFF_OFF, shoff, data ); cpu_melf_put32( p + MELF64_EHDR_E_FLAGS_OFF, MELF_EF_MCPU_NONE, data ); cpu_melf_put16( p + MELF64_EHDR_E_EHSIZE_OFF, MELF64_EHDR_SIZE, data ); cpu_melf_put16( p + MELF64_EHDR_E_PHENTSIZE_OFF, 0, data ); cpu_melf_put16( p + MELF64_EHDR_E_PHNUM_OFF, 0, data ); cpu_melf_put16( p + MELF64_EHDR_E_SHENTSIZE_OFF, MELF64_SHDR_SIZE, data ); cpu_melf_put16( p + MELF64_EHDR_E_SHNUM_OFF, shnum, data ); cpu_melf_put16( p + MELF64_EHDR_E_SHSTRNDX_OFF, shstr, data ); } static void put_shdr64( unsigned char *p, const wsec *s, int data ) { memset( p, 0, MELF64_SHDR_SIZE ); cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, s->name_off, data ); cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, s->type, data ); cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, s->flags, data ); cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, 0, data ); cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, s->off, data ); cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, s->size, data ); cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, s->link, data ); cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, s->info, data ); cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, s->align, data ); cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, s->entsize, data ); } static void get_shdr64( const unsigned char *p, int data, rsh *s ) { memset( s, 0, sizeof( *s ) ); s->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); s->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); s->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); s->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); s->off = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); s->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); s->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); s->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); s->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); s->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); } static int shdr_is_zero( const rsh *s ) { return s->name == 0 && s->type == 0 && s->flags == 0 && s->addr == 0 && s->off == 0 && s->size == 0 && s->link == 0 && s->info == 0 && s->align == 0 && s->entsize == 0; } static __mpu_uint64_t sec_flags( unsigned logical ) { switch (logical) { case CPU_OBJ_SEC_INIT: return( MELF_SHFLAGS_INIT ); case CPU_OBJ_SEC_TEXT: return( MELF_SHFLAGS_TEXT ); case CPU_OBJ_SEC_FINI: return( MELF_SHFLAGS_FINI ); case CPU_OBJ_SEC_DATA: return( MELF_SHFLAGS_DATA ); case CPU_OBJ_SEC_BSS: return( MELF_SHFLAGS_BSS ); case CPU_OBJ_SEC_CONTROL: return( MELF_SHFLAGS_CONTROL ); case CPU_OBJ_SEC_INPUT: return( MELF_SHFLAGS_INPUT ); case CPU_OBJ_SEC_OUTPUT: return( MELF_SHFLAGS_OUTPUT ); case CPU_OBJ_SEC_TRACE: return( MELF_SHFLAGS_TRACE ); default: return( 0 ); } } static const char * sec_name( unsigned logical ) { switch (logical) { case CPU_OBJ_SEC_INIT: return( MELF_SEC_INIT ); case CPU_OBJ_SEC_TEXT: return( MELF_SEC_TEXT ); case CPU_OBJ_SEC_FINI: return( MELF_SEC_FINI ); case CPU_OBJ_SEC_DATA: return( MELF_SEC_DATA ); case CPU_OBJ_SEC_BSS: return( MELF_SEC_BSS ); case CPU_OBJ_SEC_CONTROL: return( MELF_SEC_CONTROL ); case CPU_OBJ_SEC_INPUT: return( MELF_SEC_INPUT ); case CPU_OBJ_SEC_OUTPUT: return( MELF_SEC_OUTPUT ); case CPU_OBJ_SEC_TRACE: return( MELF_SEC_TRACE ); default: return( "" ); } } static const char * rela_name( unsigned logical ) { switch (logical) { case CPU_OBJ_SEC_INIT: return( MELF_SEC_RELA_INIT ); case CPU_OBJ_SEC_TEXT: return( MELF_SEC_RELA_TEXT ); case CPU_OBJ_SEC_FINI: return( MELF_SEC_RELA_FINI ); case CPU_OBJ_SEC_DATA: return( MELF_SEC_RELA_DATA ); case CPU_OBJ_SEC_CONTROL: return( MELF_SEC_RELA_CONTROL ); case CPU_OBJ_SEC_INPUT: return( MELF_SEC_RELA_INPUT ); case CPU_OBJ_SEC_OUTPUT: return( MELF_SEC_RELA_OUTPUT ); case CPU_OBJ_SEC_TRACE: return( MELF_SEC_RELA_TRACE ); default: return( NULL ); } } static int obj_sec( const cpu_asm_object *o, unsigned logical, const unsigned char **p, size_t *n, __mpu_uint64_t *a, int *present) { switch (logical) { case CPU_OBJ_SEC_INIT: *p = o->init; *n = o->init_size; *a = o->init_align; *present = o->has_init; return( 0 ); case CPU_OBJ_SEC_TEXT: *p = o->text; *n = o->text_size; *a = o->text_align; *present = o->has_text; return( 0 ); case CPU_OBJ_SEC_FINI: *p = o->fini; *n = o->fini_size; *a = o->fini_align; *present = o->has_fini; return( 0 ); case CPU_OBJ_SEC_DATA: *p = o->data; *n = o->data_size; *a = o->data_align; *present = o->has_data || o->data_size != 0; return( 0 ); case CPU_OBJ_SEC_BSS: *p = NULL; *n = o->bss_size; *a = o->bss_align; *present = o->has_bss || o->bss_size != 0; return( 0 ); case CPU_OBJ_SEC_CONTROL: *p = o->control; *n = o->control_size; *a = o->control_align; *present = o->has_control || o->control_size != 0; return( 0 ); case CPU_OBJ_SEC_INPUT: *p = o->input; *n = o->input_size; *a = o->input_align; *present = o->has_input || o->input_size != 0; return( 0 ); case CPU_OBJ_SEC_OUTPUT: *p = o->output; *n = o->output_size; *a = o->output_align; *present = o->has_output || o->output_size != 0; return( 0 ); case CPU_OBJ_SEC_TRACE: *p = o->trace; *n = o->trace_size; *a = o->trace_align; *present = o->has_trace || o->trace_size != 0; return( 0 ); default: return( -1 ); } } static int logical_index( const __mpu_uint16_t idx[OBJECT_SECTION_COUNT], unsigned logical ) { size_t i; for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) if (object_sections[i] == logical) return( idx[i] ); return( 0 ); } static int validate_object_for_write( const cpu_asm_object *o, char *err, size_t es ) { size_t i; for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) { const unsigned char *p; size_t n; __mpu_uint64_t a; int present; if( obj_sec( o, object_sections[i], &p, &n, &a, &present ) ) return( -1 ); if (!present) continue; if( a == 0 || !cpu_melf_is_pow2( a ) ) { snprintf( err, es, "invalid alignment for %s", sec_name( object_sections[i] ) ); return( -1 ); } if (object_sections[i] != CPU_OBJ_SEC_BSS && n && !p) { snprintf( err, es, "missing payload for %s", sec_name( object_sections[i] ) ); return( -1 ); } } for( i = 0; i < o->symbol_count; ++i ) { const cpu_asm_symbol *s = &o->symbols[i]; const unsigned char *p; size_t n; __mpu_uint64_t a; int present; if (!s->name || !*s->name) { snprintf( err, es, "empty symbol name" ); return( -1 ); } if (s->binding > CPU_OBJ_BIND_WEAK || s->type > CPU_OBJ_TYPE_FUNC) { snprintf( err, es, "invalid symbol attributes: %s", s->name ); return( -1 ); } if (s->section == CPU_OBJ_SEC_UNDEF) { if (s->binding == CPU_OBJ_BIND_LOCAL || s->value != 0 || s->size != 0) { snprintf( err, es, "malformed undefined symbol: %s", s->name ); return( -1 ); } continue; } if( obj_sec( o, s->section, &p, &n, &a, &present ) || !present ) { snprintf( err, es, "symbol %s belongs to absent section", s->name ); return( -1 ); } if (s->value > n || s->size > (__mpu_uint64_t)n - s->value) { snprintf( err, es, "symbol range outside section: %s", s->name ); return( -1 ); } } for( i = 0; i < o->reloc_count; ++i ) { const cpu_asm_reloc *r = &o->relocs[i]; const unsigned char *p; size_t n; __mpu_uint64_t a; int present; if (r->type < CPU_RELOC_CODE64 || r->type > CPU_RELOC_CPTR64_DATA) { snprintf( err, es, "invalid relocation type" ); return( -1 ); } if (r->symbol_index >= o->symbol_count) { snprintf( err, es, "bad relocation symbol index" ); return( -1 ); } if (r->section == CPU_OBJ_SEC_BSS) { snprintf( err, es, "relocations cannot patch SHT_NOBITS .bss" ); return( -1 ); } if( obj_sec( o, r->section, &p, &n, &a, &present ) || !present ) { snprintf( err, es, "relocation refers to absent source section" ); return( -1 ); } if (r->offset > n || CPU_PTR64_SIZE > n - (size_t)r->offset) { snprintf( err, es, "relocation field outside source section" ); return( -1 ); } } return( 0 ); } int cpu_object_write( const char *path, const cpu_asm_object *o, char *err, size_t es) { wsec s[32]; size_t ns = 1; size_t i, j; __mpu_uint16_t secidx[OBJECT_SECTION_COUNT] = {0}; __mpu_uint16_t relaidx[OBJECT_SECTION_COUNT] = {0}; int data = cpu_melf_data_encoding(); int ov = 0; blob str = {0}; blob shstr = {0}; __mpu_uint32_t *nameoff = NULL; __mpu_uint32_t *symmap = NULL; size_t nlocal = 0, nonlocal = 0, symcount; unsigned char *sympay = NULL; unsigned char *file = NULL; size_t symsz; __mpu_uint16_t symsec, strsec, targetsec, shstrsec; MElf_McpuTarget mt; unsigned char mtbuf[MELF_MCPU_TARGET_SIZE]; __mpu_uint64_t cur, shoff, total; if (!path || !o) return( -1 ); if( validate_object_for_write( o, err, es ) ) return( -1 ); memset( s, 0, sizeof( s ) ); if( blob_put( &str, "", 1 ) || blob_put( &shstr, "", 1 ) ) { snprintf( err, es, "out of memory" ); goto fail; } for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) { const unsigned char *p; size_t n; __mpu_uint64_t a; int present; unsigned logical = object_sections[i]; if( obj_sec( o, logical, &p, &n, &a, &present ) ) goto fail; if (!present) continue; if (ns >= sizeof(s) / sizeof(s[0])) { snprintf( err, es, "too many MELF sections" ); goto fail; } secidx[i] = (__mpu_uint16_t)ns; s[ns].name = sec_name( logical ); s[ns].type = logical == CPU_OBJ_SEC_BSS ? MELF_SHT_NOBITS : MELF_SHT_PROGBITS; s[ns].flags = sec_flags( logical ); s[ns].align = a; s[ns].payload = p; s[ns].size = n; s[ns].logical = logical; ++ns; } for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) { size_t cnt = 0; const char *rn; for( j = 0; j < o->reloc_count; ++j ) if (o->relocs[j].section == object_sections[i]) ++cnt; if (!cnt) continue; rn = rela_name( object_sections[i] ); if (!rn || !secidx[i]) { snprintf( err, es, "invalid relocation source section" ); goto fail; } if (ns >= sizeof(s) / sizeof(s[0])) { snprintf( err, es, "too many MELF sections" ); goto fail; } relaidx[i] = (__mpu_uint16_t)ns; s[ns].name = rn; s[ns].type = MELF_SHT_RELA; s[ns].align = 8; s[ns].entsize = MELF64_RELA_SIZE; s[ns].info = secidx[i]; s[ns].size = cnt * MELF64_RELA_SIZE; s[ns].logical = object_sections[i]; ++ns; } symsec = (__mpu_uint16_t)ns; s[ns].name = MELF_SEC_SYMTAB; s[ns].type = MELF_SHT_SYMTAB; s[ns].align = 8; s[ns].entsize = MELF64_SYM_SIZE; ++ns; strsec = (__mpu_uint16_t)ns; s[ns].name = MELF_SEC_STRTAB; s[ns].type = MELF_SHT_STRTAB; s[ns].align = 1; ++ns; targetsec = (__mpu_uint16_t)ns; s[ns].name = MELF_SEC_MCPU_TARGET; s[ns].type = MELF_SHT_MCPU_TARGET; s[ns].align = 8; s[ns].entsize = MELF_MCPU_TARGET_SIZE; s[ns].size = MELF_MCPU_TARGET_SIZE; ++ns; shstrsec = (__mpu_uint16_t)ns; s[ns].name = MELF_SEC_SHSTRTAB; s[ns].type = MELF_SHT_STRTAB; s[ns].align = 1; ++ns; nameoff = (__mpu_uint32_t *)calloc( o->symbol_count ? o->symbol_count : 1, sizeof(*nameoff)); symmap = (__mpu_uint32_t *)calloc( o->symbol_count ? o->symbol_count : 1, sizeof(*symmap)); if (!nameoff || !symmap) { snprintf( err, es, "out of memory" ); goto fail; } for( i = 0; i < o->symbol_count; ++i ) { if( blob_string( &str, o->symbols[i].name, &nameoff[i] ) ) { snprintf( err, es, "symbol string table too large" ); goto fail; } if (o->symbols[i].binding == CPU_OBJ_BIND_LOCAL) ++nlocal; else ++nonlocal; } if (nlocal > UINT32_MAX - 1 || nonlocal > UINT32_MAX - 1 - nlocal) { snprintf( err, es, "too many symbols" ); goto fail; } symcount = 1 + nlocal + nonlocal; if (symcount > SIZE_MAX / MELF64_SYM_SIZE) { snprintf( err, es, "symbol table too large" ); goto fail; } symsz = symcount * MELF64_SYM_SIZE; sympay = (unsigned char *)calloc( symsz ? symsz : 1, 1 ); if (!sympay) { snprintf( err, es, "out of memory" ); goto fail; } { size_t li = 1; size_t gi = 1 + nlocal; for( i = 0; i < o->symbol_count; ++i ) { const cpu_asm_symbol *x = &o->symbols[i]; size_t mi = x->binding == CPU_OBJ_BIND_LOCAL ? li++ : gi++; unsigned bind; unsigned type; __mpu_uint16_t shn = MELF_SHN_UNDEF; unsigned char *q = sympay + mi * MELF64_SYM_SIZE; if (x->binding == CPU_OBJ_BIND_LOCAL) bind = MELF_STB_LOCAL; else if (x->binding == CPU_OBJ_BIND_WEAK) bind = MELF_STB_WEAK; else bind = MELF_STB_GLOBAL; if (x->type == CPU_OBJ_TYPE_FUNC) type = MELF_STT_FUNC; else if (x->type == CPU_OBJ_TYPE_OBJECT) type = MELF_STT_OBJECT; else type = MELF_STT_NOTYPE; if (x->section != CPU_OBJ_SEC_UNDEF) { int idx = logical_index( secidx, x->section ); if (!idx) { snprintf( err, es, "symbol %s belongs to absent section", x->name ); goto fail; } shn = (__mpu_uint16_t)idx; } symmap[i] = (__mpu_uint32_t)mi; cpu_melf_put32( q + MELF64_SYM_ST_NAME_OFF, nameoff[i], data ); q[MELF64_SYM_ST_INFO_OFF] = MELF64_ST_INFO( bind, type ); q[MELF64_SYM_ST_OTHER_OFF] = MELF_STV_DEFAULT; cpu_melf_put16( q + MELF64_SYM_ST_SHNDX_OFF, shn, data ); cpu_melf_put64( q + MELF64_SYM_ST_VALUE_OFF, x->value, data ); cpu_melf_put64( q + MELF64_SYM_ST_SIZE_OFF, x->size, data ); } } s[symsec].owned = sympay; s[symsec].payload = sympay; s[symsec].size = symsz; s[symsec].link = strsec; s[symsec].info = (__mpu_uint32_t)(1 + nlocal); sympay = NULL; s[strsec].owned = str.p; s[strsec].payload = str.p; s[strsec].size = str.n; str.p = NULL; cpu_melf_fill_target( &mt ); cpu_melf_encode_target( mtbuf, &mt, data ); s[targetsec].owned = (unsigned char *)malloc( MELF_MCPU_TARGET_SIZE ); if (!s[targetsec].owned) { snprintf( err, es, "out of memory" ); goto fail; } memcpy( s[targetsec].owned, mtbuf, MELF_MCPU_TARGET_SIZE ); s[targetsec].payload = s[targetsec].owned; for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) { if (relaidx[i]) { size_t cnt = 0; wsec *r = &s[relaidx[i]]; r->link = symsec; r->owned = (unsigned char *)calloc( r->size ? r->size : 1, 1 ); if (!r->owned) { snprintf( err, es, "out of memory" ); goto fail; } r->payload = r->owned; for( j = 0; j < o->reloc_count; ++j ) { const cpu_asm_reloc *x; unsigned char *q; if (o->relocs[j].section != object_sections[i]) continue; x = &o->relocs[j]; q = r->owned + cnt * MELF64_RELA_SIZE; cpu_melf_put64( q + MELF64_RELA_R_OFFSET_OFF, x->offset, data ); cpu_melf_put64( q + MELF64_RELA_R_INFO_OFF, MELF64_R_INFO( symmap[x->symbol_index], x->type ), data); cpu_melf_put64( q + MELF64_RELA_R_ADDEND_OFF, (__mpu_uint64_t)x->addend, data); ++cnt; } } } for( i = 1; i < ns; ++i ) if( blob_string( &shstr, s[i].name, &s[i].name_off ) ) { snprintf( err, es, "section-name string table too large" ); goto fail; } s[shstrsec].owned = shstr.p; s[shstrsec].payload = shstr.p; s[shstrsec].size = shstr.n; shstr.p = NULL; cur = MELF64_EHDR_SIZE; for( i = 1; i < ns; ++i ) { cur = cpu_melf_align_up( cur, s[i].align ? s[i].align : 1, &ov ); if (ov || cur > SIZE_MAX) { snprintf( err, es, "MELF file layout overflow" ); goto fail; } s[i].off = cur; if (s[i].type != MELF_SHT_NOBITS) { if ((__mpu_uint64_t)s[i].size > UINT64_MAX - cur) { snprintf( err, es, "MELF file layout overflow" ); goto fail; } cur += s[i].size; } } shoff = cpu_melf_align_up( cur, 8, &ov ); if( ov || shoff > UINT64_MAX - ns * MELF64_SHDR_SIZE ) { snprintf( err, es, "MELF section table overflow" ); goto fail; } total = shoff + ns * MELF64_SHDR_SIZE; if (total > SIZE_MAX) { snprintf( err, es, "MELF file too large for this host" ); goto fail; } file = (unsigned char *)calloc( (size_t)total ? (size_t)total : 1, 1 ); if (!file) { snprintf( err, es, "out of memory" ); goto fail; } put_ehdr64( file, data, MELF_ET_REL, shoff, (__mpu_uint16_t)ns, shstrsec ); for( i = 1; i < ns; ++i ) if (s[i].type != MELF_SHT_NOBITS && s[i].size) memcpy( file + (size_t)s[i].off, s[i].payload, s[i].size ); for( i = 0; i < ns; ++i ) put_shdr64( file + (size_t)shoff + i *MELF64_SHDR_SIZE, &s[i], data ); if( cpu_melf_writeall( path, file, (size_t)total, err, es ) ) goto fail; free( file ); free( nameoff ); free( symmap ); free_wsecs( s, ns ); return( 0 ); fail: free( file ); free( nameoff ); free( symmap ); free( sympay ); free( str.p ); free( shstr.p ); free_wsecs( s, ns ); return( -1 ); } static int validate_ident( const unsigned char *p, size_t n, __mpu_uint16_t want, int *data, char *err, size_t es) { size_t i; if (n < MELF64_EHDR_SIZE || p[MELF_EI_MAG0] != MELFMAG0 || p[MELF_EI_MAG1] != MELFMAG1 || p[MELF_EI_MAG2] != MELFMAG2 || p[MELF_EI_MAG3] != MELFMAG3) { snprintf( err, es, "not a MELF file" ); return( -1 ); } if (p[MELF_EI_CLASS] != MELF_CLASS_64) { snprintf( err, es, "unsupported MELF class" ); return( -1 ); } *data = p[MELF_EI_DATA]; if (*data != MELF_DATA_LSB && *data != MELF_DATA_MSB) { snprintf( err, es, "unsupported MELF data encoding" ); return( -1 ); } if (p[MELF_EI_VERSION] != MELF_EV_CURRENT || p[MELF_EI_OSABI] != MELF_OSABI_MCPU || p[MELF_EI_ABIVERSION] != MELF_ABIVERSION_CURRENT || cpu_melf_get16( p + MELF64_EHDR_E_TYPE_OFF, *data ) != want || cpu_melf_get16( p + MELF64_EHDR_E_MACHINE_OFF, *data ) != MELF_EM_MCPU || cpu_melf_get32( p + MELF64_EHDR_E_VERSION_OFF, *data ) != MELF_EV_CURRENT || cpu_melf_get32( p + MELF64_EHDR_E_FLAGS_OFF, *data ) != MELF_EF_MCPU_NONE || cpu_melf_get16( p + MELF64_EHDR_E_EHSIZE_OFF, *data ) != MELF64_EHDR_SIZE) { snprintf( err, es, "unsupported MELF header" ); return( -1 ); } for( i = MELF_EI_PAD; i < MELF_EI_NIDENT; ++i ) if (p[i] != 0) { snprintf( err, es, "nonzero reserved e_ident byte" ); return( -1 ); } return( 0 ); } static int name_at( const unsigned char *tab, size_t n, __mpu_uint32_t off, const char **s) { if (off >= n) return( -1 ); *s = (const char *)tab + off; return( memchr( *s, 0, n - off ) ? 0 : -1 ); } static int map_name_logical( const char *n, unsigned *logical ) { size_t i; for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) if( !strcmp( n, sec_name( object_sections[i] ) ) ) { *logical = object_sections[i]; return( 0 ); } return( -1 ); } static int load_payload( unsigned char **dst, size_t *dn, const unsigned char *f, size_t fn, const rsh *s, char *err, size_t es) { if (s->type == MELF_SHT_NOBITS) { snprintf( err, es, "attempt to load SHT_NOBITS payload" ); return( -1 ); } if( s->size > SIZE_MAX || !cpu_melf_span( fn, s->off, s->size ) ) { snprintf( err, es, "bad section file range" ); return( -1 ); } if (s->size) { *dst = (unsigned char *)malloc( (size_t)s->size ); if (!*dst) { snprintf( err, es, "out of memory" ); return( -1 ); } memcpy( *dst, f + (size_t)s->off, (size_t)s->size ); } *dn = (size_t)s->size; return( 0 ); } static int u64_to_s64( __mpu_uint64_t u, __mpu_int64_t *out ) { if (u <= (__mpu_uint64_t)INT64_MAX) { *out = (__mpu_int64_t)u; return( 0 ); } *out = -1 - (__mpu_int64_t)(UINT64_MAX - u); return( 0 ); } static int load_allocated_section( cpu_asm_object *o, unsigned l, const rsh *s, const unsigned char *f, size_t fn, char *err, size_t es) { switch (l) { case CPU_OBJ_SEC_INIT: if (o->has_init) goto duplicate; if( load_payload( (unsigned char **)&o->init, &o->init_size, f, fn, s, err, es)) return( -1 ); o->init_align = s->align; o->has_init = 1; return( 0 ); case CPU_OBJ_SEC_TEXT: if (o->has_text) goto duplicate; if( load_payload( (unsigned char **)&o->text, &o->text_size, f, fn, s, err, es)) return( -1 ); o->text_align = s->align; o->has_text = 1; return( 0 ); case CPU_OBJ_SEC_FINI: if (o->has_fini) goto duplicate; if( load_payload( (unsigned char **)&o->fini, &o->fini_size, f, fn, s, err, es)) return( -1 ); o->fini_align = s->align; o->has_fini = 1; return( 0 ); case CPU_OBJ_SEC_DATA: if (o->has_data) goto duplicate; if( load_payload( (unsigned char **)&o->data, &o->data_size, f, fn, s, err, es)) return( -1 ); o->data_align = s->align; o->has_data = 1; return( 0 ); case CPU_OBJ_SEC_BSS: if (o->has_bss) goto duplicate; if (s->size > SIZE_MAX) { snprintf( err, es, ".bss too large for this host" ); return( -1 ); } o->bss_size = (size_t)s->size; o->bss_align = s->align; o->has_bss = 1; return( 0 ); case CPU_OBJ_SEC_CONTROL: if (o->has_control) goto duplicate; if( load_payload( (unsigned char **)&o->control, &o->control_size, f, fn, s, err, es)) return( -1 ); o->control_align = s->align; o->has_control = 1; return( 0 ); case CPU_OBJ_SEC_INPUT: if (o->has_input) goto duplicate; if( load_payload( (unsigned char **)&o->input, &o->input_size, f, fn, s, err, es)) return( -1 ); o->input_align = s->align; o->has_input = 1; return( 0 ); case CPU_OBJ_SEC_OUTPUT: if (o->has_output) goto duplicate; if( load_payload( (unsigned char **)&o->output, &o->output_size, f, fn, s, err, es)) return( -1 ); o->output_align = s->align; o->has_output = 1; return( 0 ); case CPU_OBJ_SEC_TRACE: if (o->has_trace) goto duplicate; if( load_payload( (unsigned char **)&o->trace, &o->trace_size, f, fn, s, err, es)) return( -1 ); o->trace_align = s->align; o->has_trace = 1; return( 0 ); default: snprintf( err, es, "unsupported allocated section" ); return( -1 ); } duplicate: snprintf( err, es, "duplicate section %s", sec_name( l ) ); return( -1 ); } static int cpu_object_read_data_internal( const unsigned char *f, size_t fn, cpu_asm_object *o, char *err, size_t es ) { int data; __mpu_uint64_t shoff; __mpu_uint16_t shnum, shentsz, shstrndx; size_t i, j; rsh *sh = NULL; const unsigned char *shstr = NULL; size_t shstrn = 0; unsigned *logical = NULL; int target_found = 0; __mpu_uint32_t symsec = 0; MElf_McpuTarget mt; __mpu_uint32_t *smap = NULL; size_t symcount = 0; int rc = -1; if( !f || !o ) return( -1 ); memset( o, 0, sizeof( *o ) ); if( validate_ident( f, fn, MELF_ET_REL, &data, err, es ) ) goto out; if( cpu_melf_get64( f + MELF64_EHDR_E_ENTRY_OFF, data ) != 0 || cpu_melf_get64( f + MELF64_EHDR_E_INIT_ENTRY_OFF, data ) != 0 || cpu_melf_get64( f + MELF64_EHDR_E_LIFE_ENTRY_OFF, data ) != 0 || cpu_melf_get64( f + MELF64_EHDR_E_FINI_ENTRY_OFF, data ) != 0 || cpu_melf_get64( f + MELF64_EHDR_E_PHOFF_OFF, data ) != 0 || cpu_melf_get16( f + MELF64_EHDR_E_PHENTSIZE_OFF, data ) != 0 || cpu_melf_get16( f + MELF64_EHDR_E_PHNUM_OFF, data ) != 0) { snprintf( err, es, "ET_REL contains executable/program-header state" ); goto out; } shoff = cpu_melf_get64( f + MELF64_EHDR_E_SHOFF_OFF, data ); shentsz = cpu_melf_get16( f + MELF64_EHDR_E_SHENTSIZE_OFF, data ); shnum = cpu_melf_get16( f + MELF64_EHDR_E_SHNUM_OFF, data ); shstrndx = cpu_melf_get16( f + MELF64_EHDR_E_SHSTRNDX_OFF, data ); if (shentsz != MELF64_SHDR_SIZE || !shnum || shstrndx == MELF_SHN_UNDEF || shstrndx >= shnum || !cpu_melf_span( fn, shoff, (__mpu_uint64_t)shnum *MELF64_SHDR_SIZE )) { snprintf( err, es, "bad MELF section table" ); goto out; } sh = ( rsh * ) calloc( shnum, sizeof( *sh ) ); logical = (unsigned *)malloc( shnum *sizeof( *logical ) ); if (!sh || !logical) { snprintf( err, es, "out of memory" ); goto out; } for( i = 0; i < shnum; ++i ) { get_shdr64( f + (size_t)shoff + i *MELF64_SHDR_SIZE, data, &sh[i] ); logical[i] = CPU_OBJ_SEC_UNDEF; } if( !shdr_is_zero( &sh[0] ) ) { snprintf( err, es, "section header zero is not all-zero" ); goto out; } if (sh[shstrndx].type != MELF_SHT_STRTAB || sh[shstrndx].flags != 0 || sh[shstrndx].addr != 0 || sh[shstrndx].link != 0 || sh[shstrndx].info != 0 || sh[shstrndx].align != 1 || sh[shstrndx].entsize != 0 || sh[shstrndx].size == 0 || sh[shstrndx].size > SIZE_MAX || !cpu_melf_span( fn, sh[shstrndx].off, sh[shstrndx].size )) { snprintf( err, es, "bad .shstrtab" ); goto out; } shstr = f + (size_t)sh[shstrndx].off; shstrn = (size_t)sh[shstrndx].size; if (shstr[0] != 0) { snprintf( err, es, ".shstrtab does not start with NUL" ); goto out; } for( i = 1; i < shnum; ++i ) { const char *name; if( name_at( shstr, shstrn, sh[i].name, &name ) ) { snprintf( err, es, "bad section name offset" ); goto out; } if( i == shstrndx && strcmp( name, MELF_SEC_SHSTRTAB ) ) { snprintf( err, es, "section-name table has noncanonical name" ); goto out; } if ((sh[i].type == MELF_SHT_PROGBITS || sh[i].type == MELF_SHT_NOBITS) && (sh[i].flags & MELF_SHF_ALLOC)) { unsigned l; if( map_name_logical( name, &l ) ) { snprintf( err, es, "unsupported allocated MELF section: %s", name ); goto out; } logical[i] = l; if ((l == CPU_OBJ_SEC_BSS) != (sh[i].type == MELF_SHT_NOBITS)) { snprintf( err, es, "section type disagrees with section role: %s", name ); goto out; } if( sh[i].flags != sec_flags( l ) || sh[i].addr != 0 || sh[i].link != 0 || sh[i].info != 0 || sh[i].entsize != 0 || sh[i].align == 0 || !cpu_melf_is_pow2( sh[i].align )) { snprintf( err, es, "invalid ET_REL section attributes: %s", name ); goto out; } if (sh[i].type != MELF_SHT_NOBITS && !cpu_melf_span( fn, sh[i].off, sh[i].size )) { snprintf( err, es, "bad section file range: %s", name ); goto out; } if( load_allocated_section( o, l, &sh[i], f, fn, err, es ) ) goto out; } else if (sh[i].type == MELF_SHT_MCPU_TARGET) { if (target_found++) { snprintf( err, es, "duplicate .mcpu.target" ); goto out; } if( strcmp( name, MELF_SEC_MCPU_TARGET ) || sh[i].flags != 0 || sh[i].addr != 0 || sh[i].size != MELF_MCPU_TARGET_SIZE || sh[i].link != 0 || sh[i].info != 0 || sh[i].align != 8 || sh[i].entsize != MELF_MCPU_TARGET_SIZE || !cpu_melf_span( fn, sh[i].off, sh[i].size ) || cpu_melf_decode_target( f + (size_t)sh[i].off, &mt, data ) || cpu_melf_validate_target( &mt, data, err, es )) goto out; } else if (sh[i].type == MELF_SHT_SYMTAB) { if( strcmp( name, MELF_SEC_SYMTAB ) || symsec ) { snprintf( err, es, "multiple/noncanonical symbol table" ); goto out; } symsec = (__mpu_uint32_t)i; } } if (!target_found) { snprintf( err, es, "missing .mcpu.target" ); goto out; } if (!symsec) { snprintf( err, es, "missing .symtab" ); goto out; } { rsh *sy = &sh[symsec]; const unsigned char *str; size_t strn; const char *strname; if (sy->flags != 0 || sy->addr != 0 || sy->align != 8 || sy->entsize != MELF64_SYM_SIZE || sy->size % MELF64_SYM_SIZE || sy->link == 0 || sy->link >= shnum || sy->info == 0 || sy->info > sy->size / MELF64_SYM_SIZE || sh[sy->link].type != MELF_SHT_STRTAB || sh[sy->link].flags != 0 || sh[sy->link].addr != 0 || sh[sy->link].link != 0 || sh[sy->link].info != 0 || sh[sy->link].align != 1 || sh[sy->link].entsize != 0 || !cpu_melf_span( fn, sy->off, sy->size ) || !cpu_melf_span( fn, sh[sy->link].off, sh[sy->link].size ) || sh[sy->link].size == 0 || sh[sy->link].size > SIZE_MAX) { snprintf( err, es, "bad MELF symbol table" ); goto out; } if( name_at( shstr, shstrn, sh[sy->link].name, &strname ) || strcmp( strname, MELF_SEC_STRTAB )) { snprintf( err, es, "noncanonical symbol string table" ); goto out; } str = f + (size_t)sh[sy->link].off; strn = (size_t)sh[sy->link].size; if (str[0] != 0) { snprintf( err, es, ".strtab does not start with NUL" ); goto out; } symcount = (size_t)(sy->size / MELF64_SYM_SIZE); if (symcount == 0) { snprintf( err, es, "empty MELF symbol table" ); goto out; } { const unsigned char *q0 = f + (size_t)sy->off; size_t k; for( k = 0; k < MELF64_SYM_SIZE; ++k ) if (q0[k] != 0) { snprintf( err, es, "symbol table entry zero is not all-zero" ); goto out; } } smap = (__mpu_uint32_t *)malloc( symcount *sizeof( *smap ) ); if (!smap) { snprintf( err, es, "out of memory" ); goto out; } for( i = 0; i < symcount; ++i ) smap[i] = UINT32_MAX; for( i = 1; i < symcount; ++i ) { const unsigned char *q = f + (size_t)sy->off + i * MELF64_SYM_SIZE; __mpu_uint32_t no = cpu_melf_get32( q + MELF64_SYM_ST_NAME_OFF, data ); __mpu_uint16_t shn = cpu_melf_get16( q + MELF64_SYM_ST_SHNDX_OFF, data ); __mpu_uint64_t value = cpu_melf_get64( q + MELF64_SYM_ST_VALUE_OFF, data ); __mpu_uint64_t size = cpu_melf_get64( q + MELF64_SYM_ST_SIZE_OFF, data ); unsigned bind = MELF64_ST_BIND( q[MELF64_SYM_ST_INFO_OFF] ); unsigned type = MELF64_ST_TYPE( q[MELF64_SYM_ST_INFO_OFF] ); unsigned b, t; const char *n; if (i < sy->info) { if (bind != MELF_STB_LOCAL) { snprintf( err, es, "non-local symbol precedes sh_info boundary" ); goto out; } } else if (bind == MELF_STB_LOCAL) { snprintf( err, es, "local symbol follows sh_info boundary" ); goto out; } if (q[MELF64_SYM_ST_OTHER_OFF] != MELF_STV_DEFAULT) { snprintf( err, es, "unsupported symbol visibility" ); goto out; } if( name_at( str, strn, no, &n ) || !*n ) { snprintf( err, es, "bad/empty symbol name" ); goto out; } if (bind == MELF_STB_LOCAL) b = CPU_OBJ_BIND_LOCAL; else if (bind == MELF_STB_GLOBAL) b = CPU_OBJ_BIND_GLOBAL; else if (bind == MELF_STB_WEAK) b = CPU_OBJ_BIND_WEAK; else { snprintf( err, es, "unsupported symbol binding" ); goto out; } if (type == MELF_STT_NOTYPE) t = CPU_OBJ_TYPE_NOTYPE; else if (type == MELF_STT_OBJECT) t = CPU_OBJ_TYPE_OBJECT; else if (type == MELF_STT_FUNC) t = CPU_OBJ_TYPE_FUNC; else { snprintf( err, es, "unsupported symbol type" ); goto out; } if (shn == MELF_SHN_UNDEF) { if (b == CPU_OBJ_BIND_LOCAL || value != 0 || size != 0) { snprintf( err, es, "malformed undefined symbol: %s", n ); goto out; } if( object_add_symbol( o, n, CPU_OBJ_SEC_UNDEF, b, t, 0, err, es ) ) goto out; } else { if (shn >= MELF_SHN_LORESERVE || shn >= shnum || logical[shn] == CPU_OBJ_SEC_UNDEF) { snprintf( err, es, "symbol refers to unsupported section" ); goto out; } if (value > sh[shn].size || size > sh[shn].size - value) { snprintf( err, es, "symbol range outside section: %s", n ); goto out; } if( object_add_symbol( o, n, logical[shn], b, t, value, err, es ) ) goto out; o->symbols[o->symbol_count - 1].size = size; } smap[i] = (__mpu_uint32_t)(o->symbol_count - 1); } } for( i = 1; i < shnum; ++i ) { if (sh[i].type == MELF_SHT_RELA) { const char *name; unsigned target_logical; const char *expected_name; if (sh[i].link != symsec || sh[i].info == 0 || sh[i].info >= shnum || logical[sh[i].info] == CPU_OBJ_SEC_UNDEF || sh[sh[i].info].type == MELF_SHT_NOBITS || sh[i].flags != 0 || sh[i].addr != 0 || sh[i].align != 8 || sh[i].entsize != MELF64_RELA_SIZE || sh[i].size % MELF64_RELA_SIZE || !cpu_melf_span( fn, sh[i].off, sh[i].size )) { snprintf( err, es, "bad RELA section" ); goto out; } target_logical = logical[sh[i].info]; expected_name = rela_name( target_logical ); if( !expected_name || name_at( shstr, shstrn, sh[i].name, &name ) || strcmp( name, expected_name )) { snprintf( err, es, "noncanonical RELA section name" ); goto out; } for( j = 0; j < (size_t)( sh[i].size / MELF64_RELA_SIZE ); ++j ) { const unsigned char *q = f + (size_t)sh[i].off + j * MELF64_RELA_SIZE; __mpu_uint64_t roff = cpu_melf_get64( q + MELF64_RELA_R_OFFSET_OFF, data ); __mpu_uint64_t info = cpu_melf_get64( q + MELF64_RELA_R_INFO_OFF, data ); __mpu_uint32_t ms = MELF64_R_SYM( info ); __mpu_uint32_t rt = MELF64_R_TYPE( info ); __mpu_int64_t addend; if (ms == MELF_STN_UNDEF || ms >= symcount || smap[ms] == UINT32_MAX || rt < CPU_RELOC_CODE64 || rt > CPU_RELOC_CPTR64_DATA) { snprintf( err, es, "bad RELA symbol/type" ); goto out; } if (roff > sh[sh[i].info].size || CPU_PTR64_SIZE > sh[sh[i].info].size - roff) { snprintf( err, es, "RELA field outside section" ); goto out; } u64_to_s64( cpu_melf_get64( q + MELF64_RELA_R_ADDEND_OFF, data ), &addend ); if( object_add_reloc( o, target_logical, rt, smap[ms], roff, addend, err, es)) goto out; } } } if (o->init_size > SIZE_MAX - o->text_size || o->init_size + o->text_size > SIZE_MAX - o->fini_size) { snprintf( err, es, "PROGRAM compatibility image too large" ); goto out; } o->code_size = o->init_size + o->text_size + o->fini_size; o->code_align = 1; if (o->code_size) { o->code = (__mpu_UNIT_t *)malloc( o->code_size ); if (!o->code) { snprintf( err, es, "out of memory" ); goto out; } memcpy( o->code, o->init, o->init_size ); memcpy( o->code + o->init_size, o->text, o->text_size ); memcpy( o->code + o->init_size + o->text_size, o->fini, o->fini_size ); } rc = 0; out: free( sh ); free( logical ); free( smap ); if (rc) cpu_asm_object_free( o ); return( rc ); } int cpu_object_read_data( const void *data, size_t size, cpu_asm_object *o, char *err, size_t es ) { if( data == NULL || size == 0 || o == NULL ) return( -1 ); return( cpu_object_read_data_internal((const unsigned char *)data, size, o, err, es) ); } /* End of cpu_object_read_data() */ int cpu_object_read( const char *path, cpu_asm_object *o, char *err, size_t es ) { unsigned char *data = NULL; size_t size = 0; int rc; if( path == NULL || o == NULL ) return( -1 ); if( cpu_melf_readall(path, &data, &size, err, es) ) return( -1 ); rc = cpu_object_read_data_internal( data, size, o, err, es ); free( data ); return( rc ); } /* End of cpu_object_read() */