summaryrefslogtreecommitdiff
path: root/utils/mcpu-object-edit.c
diff options
context:
space:
mode:
authorkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
committerkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
commit065da3509019b15dd0e678072e6be629e5330bef (patch)
tree69c934c9e786ac048b4fbe8f4c4c5571661521f4 /utils/mcpu-object-edit.c
parentd130fe27fa753be02ed76fd9b277aa3c3d928eb9 (diff)
downloadlibmcpu-0.4.0.tar.xz
Version 0.4.00.4.0
Diffstat (limited to 'utils/mcpu-object-edit.c')
-rw-r--r--utils/mcpu-object-edit.c1195
1 files changed, 1195 insertions, 0 deletions
diff --git a/utils/mcpu-object-edit.c b/utils/mcpu-object-edit.c
new file mode 100644
index 0000000..ac0db3c
--- /dev/null
+++ b/utils/mcpu-object-edit.c
@@ -0,0 +1,1195 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libmcpu.h>
+#include <mcpu-elf.h>
+
+#include "mcpu-melf.h"
+#include "mcpu-object-edit.h"
+
+
+typedef struct mcpu_edit_shdr
+{
+ __mpu_uint32_t name;
+ __mpu_uint32_t type;
+ __mpu_uint64_t flags;
+ __mpu_uint64_t addr;
+ __mpu_uint64_t offset;
+ __mpu_uint64_t size;
+ __mpu_uint32_t link;
+ __mpu_uint32_t info;
+ __mpu_uint64_t align;
+ __mpu_uint64_t entsize;
+} mcpu_edit_shdr;
+
+
+typedef struct mcpu_edit_file
+{
+ const unsigned char *data;
+ size_t size;
+ int encoding;
+ __mpu_uint16_t type;
+ __mpu_uint64_t phoff;
+ __mpu_uint16_t phnum;
+ __mpu_uint64_t shoff;
+ __mpu_uint16_t shnum;
+ __mpu_uint16_t shstrndx;
+ mcpu_edit_shdr *shdr;
+ const unsigned char *shstr;
+ size_t shstr_size;
+} mcpu_edit_file;
+
+
+typedef struct mcpu_edit_section
+{
+ mcpu_edit_shdr shdr;
+ char *name;
+ const unsigned char *data;
+ unsigned char *owned_data;
+ size_t data_size;
+ __mpu_uint16_t old_index;
+ int is_new;
+ int removed;
+} mcpu_edit_section;
+
+
+static void
+get_shdr( const unsigned char *p, int data, mcpu_edit_shdr *sh )
+{
+ memset( sh, 0, sizeof( *sh ) );
+ sh->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data );
+ sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data );
+ sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data );
+ sh->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data );
+ sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data );
+ sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data );
+ sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data );
+ sh->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data );
+ sh->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data );
+ sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data );
+
+} /* End of get_shdr() */
+
+
+static void
+put_shdr( unsigned char *p, int data, const mcpu_edit_shdr *sh )
+{
+ memset( p, 0, MELF64_SHDR_SIZE );
+ cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, sh->name, data );
+ cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, sh->type, data );
+ cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, sh->flags, data );
+ cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, sh->addr, data );
+ cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, sh->offset, data );
+ cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, sh->size, data );
+ cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, sh->link, data );
+ cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, sh->info, data );
+ cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, sh->align, data );
+ cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, sh->entsize, data );
+
+} /* End of put_shdr() */
+
+
+static int
+shdr_is_zero( const mcpu_edit_shdr *sh )
+{
+ return( sh->name == 0 && sh->type == MELF_SHT_NULL && sh->flags == 0 &&
+ sh->addr == 0 && sh->offset == 0 && sh->size == 0 &&
+ sh->link == 0 && sh->info == 0 && sh->align == 0 &&
+ sh->entsize == 0 );
+
+} /* End of shdr_is_zero() */
+
+
+static const char *
+name_at( const mcpu_edit_file *file, __mpu_uint32_t offset )
+{
+ const unsigned char *p;
+
+ if( offset >= file->shstr_size )
+ return( NULL );
+ p = file->shstr + offset;
+ if( !memchr( p, 0, file->shstr_size - offset ) )
+ return( NULL );
+ return( (const char *)p );
+
+} /* End of name_at() */
+
+
+static void
+edit_file_free( mcpu_edit_file *file )
+{
+ if( !file )
+ return;
+ free( file->shdr );
+ memset( file, 0, sizeof( *file ) );
+
+} /* End of edit_file_free() */
+
+
+static int
+edit_file_open( const unsigned char *data, size_t size, mcpu_edit_file *file,
+ char *err, size_t errsz )
+{
+ __mpu_uint16_t ehsize;
+ __mpu_uint16_t phentsize;
+ __mpu_uint16_t shentsize;
+ size_t i;
+
+ memset( file, 0, sizeof( *file ) );
+ file->data = data;
+ file->size = size;
+
+ if( size < MELF64_EHDR_SIZE )
+ {
+ snprintf( err, errsz, "file is too small for a MELF64 header" );
+ return( -1 );
+ }
+ if( data[MELF_EI_MAG0] != MELFMAG0 || data[MELF_EI_MAG1] != MELFMAG1 ||
+ data[MELF_EI_MAG2] != MELFMAG2 || data[MELF_EI_MAG3] != MELFMAG3 )
+ {
+ snprintf( err, errsz, "not a MELF file" );
+ return( -1 );
+ }
+ if( data[MELF_EI_CLASS] != MELF_CLASS_64 )
+ {
+ snprintf( err, errsz, "unsupported MELF class %u",
+ (unsigned int)data[MELF_EI_CLASS] );
+ return( -1 );
+ }
+
+ file->encoding = data[MELF_EI_DATA];
+ if( file->encoding != MELF_DATA_LSB && file->encoding != MELF_DATA_MSB )
+ {
+ snprintf( err, errsz, "invalid MELF data encoding %d", file->encoding );
+ return( -1 );
+ }
+ if( data[MELF_EI_VERSION] != MELF_EV_CURRENT ||
+ data[MELF_EI_OSABI] != MELF_OSABI_MCPU ||
+ cpu_melf_get16( data + MELF64_EHDR_E_MACHINE_OFF, file->encoding ) !=
+ MELF_EM_MCPU ||
+ cpu_melf_get32( data + MELF64_EHDR_E_VERSION_OFF, file->encoding ) !=
+ MELF_EV_CURRENT )
+ {
+ snprintf( err, errsz, "unsupported MELF header" );
+ return( -1 );
+ }
+
+ ehsize = cpu_melf_get16( data + MELF64_EHDR_E_EHSIZE_OFF, file->encoding );
+ phentsize = cpu_melf_get16( data + MELF64_EHDR_E_PHENTSIZE_OFF,
+ file->encoding );
+ shentsize = cpu_melf_get16( data + MELF64_EHDR_E_SHENTSIZE_OFF,
+ file->encoding );
+ file->type = cpu_melf_get16( data + MELF64_EHDR_E_TYPE_OFF,
+ file->encoding );
+ file->phoff = cpu_melf_get64( data + MELF64_EHDR_E_PHOFF_OFF,
+ file->encoding );
+ file->phnum = cpu_melf_get16( data + MELF64_EHDR_E_PHNUM_OFF,
+ file->encoding );
+ file->shoff = cpu_melf_get64( data + MELF64_EHDR_E_SHOFF_OFF,
+ file->encoding );
+ file->shnum = cpu_melf_get16( data + MELF64_EHDR_E_SHNUM_OFF,
+ file->encoding );
+ file->shstrndx = cpu_melf_get16( data + MELF64_EHDR_E_SHSTRNDX_OFF,
+ file->encoding );
+
+ if( ehsize != MELF64_EHDR_SIZE ||
+ ( file->type != MELF_ET_REL && file->type != MELF_ET_EXEC &&
+ file->type != MELF_ET_DYN ) )
+ {
+ snprintf( err, errsz, "unsupported MELF64 file type/header size" );
+ return( -1 );
+ }
+
+ if( file->phnum )
+ {
+ if( phentsize != MELF64_PHDR_SIZE ||
+ !cpu_melf_span( size, file->phoff,
+ (__mpu_uint64_t)file->phnum * MELF64_PHDR_SIZE ) )
+ {
+ snprintf( err, errsz, "malformed program-header table" );
+ return( -1 );
+ }
+ }
+ else if( phentsize != 0 && file->type == MELF_ET_REL )
+ {
+ snprintf( err, errsz, "ET_REL has unexpected program-header entry size" );
+ return( -1 );
+ }
+
+ if( !file->shnum || shentsize != MELF64_SHDR_SIZE ||
+ file->shstrndx == MELF_SHN_UNDEF || file->shstrndx >= file->shnum ||
+ !cpu_melf_span( size, file->shoff,
+ (__mpu_uint64_t)file->shnum * MELF64_SHDR_SIZE ) )
+ {
+ snprintf( err, errsz, "malformed section-header table" );
+ return( -1 );
+ }
+
+ file->shdr = (mcpu_edit_shdr *)calloc( file->shnum, sizeof( *file->shdr ) );
+ if( !file->shdr )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+
+ for( i = 0; i < file->shnum; ++i )
+ {
+ get_shdr( data + (size_t)file->shoff + i * MELF64_SHDR_SIZE,
+ file->encoding, &file->shdr[i] );
+ if( file->shdr[i].type != MELF_SHT_NOBITS && file->shdr[i].size &&
+ !cpu_melf_span( size, file->shdr[i].offset, file->shdr[i].size ) )
+ {
+ snprintf( err, errsz, "section %zu has an invalid file range", i );
+ edit_file_free( file );
+ return( -1 );
+ }
+ }
+
+ if( !shdr_is_zero( &file->shdr[0] ) ||
+ file->shdr[file->shstrndx].type != MELF_SHT_STRTAB ||
+ !file->shdr[file->shstrndx].size ||
+ file->shdr[file->shstrndx].size > SIZE_MAX )
+ {
+ snprintf( err, errsz, "malformed MELF section-name table" );
+ edit_file_free( file );
+ return( -1 );
+ }
+
+ file->shstr = data + (size_t)file->shdr[file->shstrndx].offset;
+ file->shstr_size = (size_t)file->shdr[file->shstrndx].size;
+ for( i = 1; i < file->shnum; ++i )
+ if( !name_at( file, file->shdr[i].name ) )
+ {
+ snprintf( err, errsz, "section %zu has an invalid name", i );
+ edit_file_free( file );
+ return( -1 );
+ }
+
+ return( 0 );
+
+} /* End of edit_file_open() */
+
+
+static int
+copy_original( const unsigned char *data, size_t size,
+ unsigned char **out, size_t *out_size,
+ char *err, size_t errsz )
+{
+ unsigned char *p = (unsigned char *)malloc( size ? size : 1 );
+
+ if( !p )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+ if( size )
+ memcpy( p, data, size );
+ *out = p;
+ *out_size = size;
+ return( 0 );
+
+} /* End of copy_original() */
+
+
+static void
+sections_free( mcpu_edit_section *sections, size_t count )
+{
+ size_t i;
+
+ if( !sections )
+ return;
+ for( i = 0; i < count; ++i )
+ {
+ free( sections[i].name );
+ free( sections[i].owned_data );
+ }
+ free( sections );
+
+} /* End of sections_free() */
+
+
+static int
+sections_from_file( const mcpu_edit_file *file,
+ mcpu_edit_section **result, size_t *count,
+ char *err, size_t errsz )
+{
+ mcpu_edit_section *sections;
+ size_t i;
+
+ sections = (mcpu_edit_section *)calloc( file->shnum, sizeof( *sections ) );
+ if( !sections )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+
+ for( i = 0; i < file->shnum; ++i )
+ {
+ const char *name = i ? name_at( file, file->shdr[i].name ) : "";
+
+ sections[i].shdr = file->shdr[i];
+ sections[i].name = strdup( name ? name : "" );
+ sections[i].old_index = (__mpu_uint16_t)i;
+ if( !sections[i].name )
+ {
+ snprintf( err, errsz, "out of memory" );
+ sections_free( sections, file->shnum );
+ return( -1 );
+ }
+ if( file->shdr[i].type != MELF_SHT_NOBITS && file->shdr[i].size )
+ {
+ sections[i].data = file->data + (size_t)file->shdr[i].offset;
+ sections[i].data_size = (size_t)file->shdr[i].size;
+ }
+ }
+
+ *result = sections;
+ *count = file->shnum;
+ return( 0 );
+
+} /* End of sections_from_file() */
+
+
+static size_t
+find_section( const mcpu_edit_section *sections, size_t count,
+ const char *name )
+{
+ size_t i;
+
+ for( i = 1; i < count; ++i )
+ if( !sections[i].removed && !strcmp( sections[i].name, name ) )
+ return( i );
+ return( SIZE_MAX );
+
+} /* End of find_section() */
+
+
+static int
+name_selected( const char *name, const char *const *only, size_t count )
+{
+ size_t i;
+
+ for( i = 0; i < count; ++i )
+ if( !strcmp( name, only[i] ) )
+ return( 1 );
+ return( 0 );
+
+} /* End of name_selected() */
+
+
+
+static int
+reserved_section_name( const char *name )
+{
+ static const char *const reserved[] =
+ {
+ MELF_SEC_INIT, MELF_SEC_TEXT, MELF_SEC_FINI,
+ MELF_SEC_DATA, MELF_SEC_BSS, MELF_SEC_CONTROL,
+ MELF_SEC_INPUT, MELF_SEC_OUTPUT, MELF_SEC_TRACE,
+ MELF_SEC_SYMTAB, MELF_SEC_STRTAB, MELF_SEC_SHSTRTAB,
+ MELF_SEC_DYNSYM, MELF_SEC_DYNSTR, MELF_SEC_DYNAMIC,
+ MELF_SEC_MCPU_TARGET, MELF_SEC_MCPU_LIFECYCLE,
+ MELF_SEC_RELA_INIT, MELF_SEC_RELA_TEXT, MELF_SEC_RELA_FINI,
+ MELF_SEC_RELA_DATA, MELF_SEC_RELA_CONTROL, MELF_SEC_RELA_INPUT,
+ MELF_SEC_RELA_OUTPUT, MELF_SEC_RELA_TRACE
+ };
+ size_t i;
+
+ if( !name || !*name )
+ return( 1 );
+ if( !strncmp( name, ".rela.", 6 ) )
+ return( 1 );
+ for( i = 0; i < sizeof( reserved ) / sizeof( reserved[0] ); ++i )
+ if( !strcmp( name, reserved[i] ) )
+ return( 1 );
+ return( 0 );
+
+} /* End of reserved_section_name() */
+
+static int
+section_runtime_critical( const mcpu_edit_file *file,
+ const mcpu_edit_section *section )
+{
+ const mcpu_edit_shdr *sh = &section->shdr;
+
+ if( sh->flags & MELF_SHF_ALLOC )
+ return( 1 );
+ if( section->old_index == file->shstrndx )
+ return( 1 );
+ if( sh->type == MELF_SHT_DYNAMIC || sh->type == MELF_SHT_DYNSYM ||
+ sh->type == MELF_SHT_RELA || sh->type == MELF_SHT_MCPU_TARGET ||
+ sh->type == MELF_SHT_MCPU_LIFECYCLE )
+ return( 1 );
+ if( file->type == MELF_ET_REL &&
+ ( sh->type == MELF_SHT_SYMTAB || sh->type == MELF_SHT_STRTAB ) )
+ return( 1 );
+ return( 0 );
+
+} /* End of section_runtime_critical() */
+
+
+static int
+section_user_editable( const mcpu_edit_file *file,
+ const mcpu_edit_section *section )
+{
+ if( section_runtime_critical( file, section ) )
+ return( 0 );
+ return( section->shdr.type == MELF_SHT_PROGBITS &&
+ !( section->shdr.flags & MELF_SHF_ALLOC ) );
+
+} /* End of section_user_editable() */
+
+
+static int
+section_link_references( const mcpu_edit_section *sections, size_t count,
+ size_t target )
+{
+ size_t i;
+
+ for( i = 1; i < count; ++i )
+ {
+ if( sections[i].removed || sections[i].is_new )
+ continue;
+ if( sections[i].shdr.link == target )
+ return( 1 );
+ if( sections[i].shdr.type == MELF_SHT_RELA &&
+ sections[i].shdr.info == target )
+ return( 1 );
+ }
+ return( 0 );
+
+} /* End of section_link_references() */
+
+
+static int
+safe_remove_section( const mcpu_edit_file *file,
+ mcpu_edit_section *sections, size_t count,
+ size_t index, const char *reason,
+ char *err, size_t errsz )
+{
+ if( index == 0 || index >= count || sections[index].removed )
+ return( 0 );
+ if( section_runtime_critical( file, &sections[index] ) )
+ {
+ snprintf( err, errsz, "%s cannot remove section %s",
+ reason, sections[index].name );
+ return( -1 );
+ }
+ if( section_link_references( sections, count, index ) )
+ {
+ snprintf( err, errsz, "%s cannot remove referenced section %s",
+ reason, sections[index].name );
+ return( -1 );
+ }
+ sections[index].removed = 1;
+ return( 0 );
+
+} /* End of safe_remove_section() */
+
+
+static int
+replace_section_data( mcpu_edit_section *section,
+ const unsigned char *data, size_t size,
+ char *err, size_t errsz )
+{
+ unsigned char *p = NULL;
+
+ if( size )
+ {
+ p = (unsigned char *)malloc( size );
+ if( !p )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+ memcpy( p, data, size );
+ }
+ free( section->owned_data );
+ section->owned_data = p;
+ section->data = p;
+ section->data_size = size;
+ section->shdr.size = size;
+ return( 0 );
+
+} /* End of replace_section_data() */
+
+
+static int
+add_section( mcpu_edit_section **psections, size_t *pcount,
+ const char *name, const unsigned char *data, size_t size,
+ char *err, size_t errsz )
+{
+ mcpu_edit_section *sections = *psections;
+ size_t count = *pcount;
+ mcpu_edit_section *q;
+ mcpu_edit_section *s;
+
+ if( !name || !*name )
+ {
+ snprintf( err, errsz, "section name must not be empty" );
+ return( -1 );
+ }
+ if( reserved_section_name( name ) )
+ {
+ snprintf( err, errsz, "cannot add reserved section name %s", name );
+ return( -1 );
+ }
+ if( count == UINT16_MAX )
+ {
+ snprintf( err, errsz, "too many sections" );
+ return( -1 );
+ }
+ q = (mcpu_edit_section *)realloc( sections,
+ (count + 1) * sizeof( *q ) );
+ if( !q )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+ sections = q;
+ memset( &sections[count], 0, sizeof( sections[count] ) );
+ s = &sections[count];
+ s->name = strdup( name );
+ if( !s->name )
+ {
+ snprintf( err, errsz, "out of memory" );
+ *psections = sections;
+ return( -1 );
+ }
+ s->is_new = 1;
+ s->old_index = MELF_SHN_UNDEF;
+ s->shdr.type = MELF_SHT_PROGBITS;
+ s->shdr.align = 1;
+ if( replace_section_data( s, data, size, err, errsz ) )
+ {
+ free( s->name );
+ memset( s, 0, sizeof( *s ) );
+ *psections = sections;
+ return( -1 );
+ }
+
+ *psections = sections;
+ *pcount = count + 1;
+ return( 0 );
+
+} /* End of add_section() */
+
+
+static int
+apply_options( const mcpu_edit_file *file,
+ mcpu_edit_section **psections, size_t *pcount,
+ const mcpu_object_edit_options *options,
+ int *changed, char *err, size_t errsz )
+{
+ mcpu_edit_section *sections = *psections;
+ size_t count = *pcount;
+ size_t i;
+
+ if( options->only_count )
+ {
+ int any = 0;
+
+ /* Mark the whole filter first so dependencies between sections that are
+ both being discarded do not make the result depend on section order. */
+ for( i = 1; i < count; ++i )
+ if( !name_selected( sections[i].name, options->only_sections,
+ options->only_count ) )
+ {
+ if( section_runtime_critical( file, &sections[i] ) )
+ {
+ snprintf( err, errsz, "--only-section cannot remove section %s",
+ sections[i].name );
+ return( -1 );
+ }
+ sections[i].removed = 1;
+ any = 1;
+ }
+
+ for( i = 1; i < count; ++i )
+ if( sections[i].removed && section_link_references( sections, count, i ) )
+ {
+ snprintf( err, errsz,
+ "--only-section cannot remove referenced section %s",
+ sections[i].name );
+ return( -1 );
+ }
+ if( any )
+ *changed = 1;
+ }
+
+ for( i = 0; i < options->action_count; ++i )
+ {
+ const mcpu_object_edit_action *a = &options->actions[i];
+ size_t index;
+
+ if( a->type == MCPU_OBJECT_EDIT_ADD )
+ {
+ if( find_section( sections, count, a->name ) != SIZE_MAX )
+ {
+ snprintf( err, errsz, "section %s already exists", a->name );
+ return( -1 );
+ }
+ if( add_section( &sections, &count, a->name, a->data, a->size,
+ err, errsz ) )
+ return( -1 );
+ *psections = sections;
+ *pcount = count;
+ *changed = 1;
+ continue;
+ }
+
+ index = find_section( sections, count, a->name );
+ if( index == SIZE_MAX )
+ {
+ snprintf( err, errsz, "section %s does not exist", a->name );
+ return( -1 );
+ }
+
+ if( a->type == MCPU_OBJECT_EDIT_REMOVE )
+ {
+ if( safe_remove_section( file, sections, count, index,
+ "--remove-section", err, errsz ) )
+ return( -1 );
+ *changed = 1;
+ }
+ else if( a->type == MCPU_OBJECT_EDIT_RENAME )
+ {
+ char *p;
+
+ if( !section_user_editable( file, &sections[index] ) )
+ {
+ snprintf( err, errsz, "--rename-section cannot rename section %s",
+ a->name );
+ return( -1 );
+ }
+ if( !a->new_name || !*a->new_name )
+ {
+ snprintf( err, errsz, "new section name must not be empty" );
+ return( -1 );
+ }
+ if( reserved_section_name( a->new_name ) )
+ {
+ snprintf( err, errsz, "cannot rename metadata to reserved name %s",
+ a->new_name );
+ return( -1 );
+ }
+ if( find_section( sections, count, a->new_name ) != SIZE_MAX )
+ {
+ snprintf( err, errsz, "section %s already exists", a->new_name );
+ return( -1 );
+ }
+ p = strdup( a->new_name );
+ if( !p )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+ free( sections[index].name );
+ sections[index].name = p;
+ *changed = 1;
+ }
+ else if( a->type == MCPU_OBJECT_EDIT_UPDATE )
+ {
+ if( !section_user_editable( file, &sections[index] ) )
+ {
+ snprintf( err, errsz, "--update-section cannot update section %s",
+ a->name );
+ return( -1 );
+ }
+ if( replace_section_data( &sections[index], a->data, a->size,
+ err, errsz ) )
+ return( -1 );
+ *changed = 1;
+ }
+ else
+ {
+ snprintf( err, errsz, "internal error: invalid edit action" );
+ return( -1 );
+ }
+ }
+
+ *psections = sections;
+ *pcount = count;
+ return( 0 );
+
+} /* End of apply_options() */
+
+
+static int
+build_output( const mcpu_edit_file *file,
+ mcpu_edit_section *sections, size_t count,
+ unsigned char **out, size_t *out_size,
+ char *err, size_t errsz )
+{
+ __mpu_uint16_t *map = NULL;
+ mcpu_edit_shdr *nsh = NULL;
+ size_t *source_index = NULL;
+ unsigned char *result = NULL;
+ unsigned char *newshstr = NULL;
+ size_t newshstr_size = 1;
+ size_t newshstr_cap = 64;
+ __mpu_uint16_t newnum = 0;
+ __mpu_uint16_t newshstrndx = MELF_SHN_UNDEF;
+ __mpu_uint64_t fixed_end = MELF64_EHDR_SIZE;
+ __mpu_uint64_t cur;
+ __mpu_uint64_t shoff;
+ __mpu_uint64_t total;
+ int overflow = 0;
+ size_t i, j;
+ int rc = -1;
+
+ map = (__mpu_uint16_t *)malloc( (size_t)file->shnum * sizeof( *map ) );
+ if( !map )
+ {
+ snprintf( err, errsz, "out of memory" );
+ goto out;
+ }
+ for( i = 0; i < file->shnum; ++i )
+ map[i] = MELF_SHN_UNDEF;
+
+ for( i = 0; i < count; ++i )
+ if( !sections[i].removed )
+ {
+ if( newnum == UINT16_MAX )
+ {
+ snprintf( err, errsz, "too many retained sections" );
+ goto out;
+ }
+ if( !sections[i].is_new )
+ map[sections[i].old_index] = newnum;
+ if( !sections[i].is_new && sections[i].old_index == file->shstrndx )
+ newshstrndx = newnum;
+ ++newnum;
+ }
+
+ if( newshstrndx == MELF_SHN_UNDEF )
+ {
+ snprintf( err, errsz, "internal error: .shstrtab was removed" );
+ goto out;
+ }
+
+ nsh = (mcpu_edit_shdr *)calloc( newnum, sizeof( *nsh ) );
+ source_index = (size_t *)calloc( newnum, sizeof( *source_index ) );
+ newshstr = (unsigned char *)malloc( newshstr_cap );
+ if( !nsh || !source_index || !newshstr )
+ {
+ snprintf( err, errsz, "out of memory" );
+ goto out;
+ }
+ newshstr[0] = 0;
+
+ for( i = 0, j = 0; i < count; ++i )
+ if( !sections[i].removed )
+ {
+ size_t n = strlen( sections[i].name ) + 1;
+ mcpu_edit_shdr sh = sections[i].shdr;
+
+ if( i == 0 )
+ sh.name = 0;
+ else
+ {
+ if( n > SIZE_MAX - newshstr_size ||
+ newshstr_size + n > UINT32_MAX )
+ {
+ snprintf( err, errsz, "section-name table overflow" );
+ goto out;
+ }
+ if( newshstr_size + n > newshstr_cap )
+ {
+ size_t cap = newshstr_cap;
+ unsigned char *q;
+
+ while( cap < newshstr_size + n )
+ {
+ if( cap > SIZE_MAX / 2 )
+ {
+ snprintf( err, errsz, "section-name table overflow" );
+ goto out;
+ }
+ cap *= 2;
+ }
+ q = (unsigned char *)realloc( newshstr, cap );
+ if( !q )
+ {
+ snprintf( err, errsz, "out of memory" );
+ goto out;
+ }
+ newshstr = q;
+ newshstr_cap = cap;
+ }
+ sh.name = (__mpu_uint32_t)newshstr_size;
+ memcpy( newshstr + newshstr_size, sections[i].name, n );
+ newshstr_size += n;
+ }
+
+ if( !sections[i].is_new )
+ {
+ if( sh.link )
+ {
+ if( sh.link >= file->shnum || map[sh.link] == MELF_SHN_UNDEF )
+ {
+ snprintf( err, errsz, "retained section %s depends on removed section",
+ sections[i].name );
+ goto out;
+ }
+ sh.link = map[sh.link];
+ }
+ if( sh.type == MELF_SHT_RELA && sh.info )
+ {
+ if( sh.info >= file->shnum || map[sh.info] == MELF_SHN_UNDEF )
+ {
+ snprintf( err, errsz, "retained relocation section targets removed section" );
+ goto out;
+ }
+ sh.info = map[sh.info];
+ }
+ }
+ if( !sections[i].is_new && sections[i].old_index == file->shstrndx )
+ sh.size = newshstr_size;
+ else
+ sh.size = sections[i].data_size;
+ nsh[j] = sh;
+ source_index[j] = i;
+ ++j;
+ }
+
+ /* .shstrtab size is final only after all retained names were appended. */
+ nsh[newshstrndx].size = newshstr_size;
+
+ if( file->phnum )
+ {
+ __mpu_uint64_t end = file->phoff +
+ (__mpu_uint64_t)file->phnum * MELF64_PHDR_SIZE;
+
+ if( end > fixed_end )
+ fixed_end = end;
+ for( i = 0; i < file->phnum; ++i )
+ {
+ const unsigned char *p = file->data + (size_t)file->phoff +
+ i * MELF64_PHDR_SIZE;
+ __mpu_uint32_t type = cpu_melf_get32( p + MELF64_PHDR_P_TYPE_OFF,
+ file->encoding );
+ __mpu_uint64_t off = cpu_melf_get64( p + MELF64_PHDR_P_OFFSET_OFF,
+ file->encoding );
+ __mpu_uint64_t filesz = cpu_melf_get64( p + MELF64_PHDR_P_FILESZ_OFF,
+ file->encoding );
+
+ if( type == MELF_PT_LOAD && filesz )
+ {
+ if( !cpu_melf_span( file->size, off, filesz ) )
+ {
+ snprintf( err, errsz, "PT_LOAD has an invalid file range" );
+ goto out;
+ }
+ if( off + filesz > fixed_end )
+ fixed_end = off + filesz;
+ }
+ }
+ }
+
+ for( i = 0; i < count; ++i )
+ if( !sections[i].removed && !sections[i].is_new &&
+ ( sections[i].shdr.flags & MELF_SHF_ALLOC ) &&
+ sections[i].shdr.type != MELF_SHT_NOBITS &&
+ sections[i].shdr.size )
+ {
+ __mpu_uint64_t end = sections[i].shdr.offset + sections[i].shdr.size;
+
+ if( end > fixed_end )
+ fixed_end = end;
+ }
+
+ if( fixed_end > file->size )
+ {
+ snprintf( err, errsz, "fixed MELF image exceeds file size" );
+ goto out;
+ }
+
+ cur = fixed_end;
+ for( i = 0; i < newnum; ++i )
+ {
+ size_t si = source_index[i];
+
+ if( nsh[i].flags & MELF_SHF_ALLOC )
+ continue;
+ if( nsh[i].type == MELF_SHT_NOBITS || !nsh[i].size )
+ continue;
+ cur = cpu_melf_align_up( cur, nsh[i].align ? nsh[i].align : 1,
+ &overflow );
+ if( overflow )
+ {
+ snprintf( err, errsz, "MELF layout overflow" );
+ goto out;
+ }
+ nsh[i].offset = cur;
+ if( nsh[i].size > UINT64_MAX - cur )
+ {
+ snprintf( err, errsz, "MELF layout overflow" );
+ goto out;
+ }
+ cur += nsh[i].size;
+ (void)si;
+ }
+
+ shoff = cpu_melf_align_up( cur, 8, &overflow );
+ if( overflow || (__mpu_uint64_t)newnum > UINT64_MAX / MELF64_SHDR_SIZE ||
+ shoff > UINT64_MAX - (__mpu_uint64_t)newnum * MELF64_SHDR_SIZE )
+ {
+ snprintf( err, errsz, "section table overflow" );
+ goto out;
+ }
+ total = shoff + (__mpu_uint64_t)newnum * MELF64_SHDR_SIZE;
+ if( total > SIZE_MAX )
+ {
+ snprintf( err, errsz, "MELF file is too large for this host" );
+ goto out;
+ }
+
+ result = (unsigned char *)calloc( (size_t)total ? (size_t)total : 1, 1 );
+ if( !result )
+ {
+ snprintf( err, errsz, "out of memory" );
+ goto out;
+ }
+ if( fixed_end )
+ memcpy( result, file->data, (size_t)fixed_end );
+
+ for( i = 0; i < newnum; ++i )
+ {
+ size_t si = source_index[i];
+ const mcpu_edit_section *s = &sections[si];
+
+ if( nsh[i].type == MELF_SHT_NOBITS || !nsh[i].size )
+ continue;
+ if( !cpu_melf_span( (size_t)total, nsh[i].offset, nsh[i].size ) )
+ {
+ snprintf( err, errsz, "section copy exceeds output bounds" );
+ goto out;
+ }
+ if( !s->is_new && s->old_index == file->shstrndx )
+ memcpy( result + (size_t)nsh[i].offset, newshstr, newshstr_size );
+ else if( s->data_size )
+ memcpy( result + (size_t)nsh[i].offset, s->data, s->data_size );
+ }
+
+ /* Remap section indices stored inside retained symbol tables. */
+ for( i = 0; i < newnum; ++i )
+ if( nsh[i].type == MELF_SHT_SYMTAB || nsh[i].type == MELF_SHT_DYNSYM )
+ {
+ size_t n;
+
+ if( nsh[i].entsize != MELF64_SYM_SIZE ||
+ nsh[i].size % MELF64_SYM_SIZE )
+ {
+ snprintf( err, errsz, "retained symbol table is malformed" );
+ goto out;
+ }
+ n = (size_t)( nsh[i].size / MELF64_SYM_SIZE );
+ for( j = 0; j < n; ++j )
+ {
+ unsigned char *p = result + (size_t)nsh[i].offset +
+ j * MELF64_SYM_SIZE;
+ __mpu_uint16_t shndx = cpu_melf_get16(
+ p + MELF64_SYM_ST_SHNDX_OFF, file->encoding );
+
+ if( shndx == MELF_SHN_UNDEF || shndx >= MELF_SHN_LORESERVE )
+ continue;
+ if( shndx >= file->shnum || map[shndx] == MELF_SHN_UNDEF )
+ {
+ snprintf( err, errsz, "retained symbol refers to removed section" );
+ goto out;
+ }
+ cpu_melf_put16( p + MELF64_SYM_ST_SHNDX_OFF, map[shndx],
+ file->encoding );
+ }
+ }
+
+ cpu_melf_put64( result + MELF64_EHDR_E_SHOFF_OFF, shoff, file->encoding );
+ cpu_melf_put16( result + MELF64_EHDR_E_SHNUM_OFF, newnum, file->encoding );
+ cpu_melf_put16( result + MELF64_EHDR_E_SHSTRNDX_OFF,
+ newshstrndx, file->encoding );
+
+ for( i = 0; i < newnum; ++i )
+ put_shdr( result + (size_t)shoff + i * MELF64_SHDR_SIZE,
+ file->encoding, &nsh[i] );
+
+ *out = result;
+ *out_size = (size_t)total;
+ result = NULL;
+ rc = 0;
+
+out:
+ free( result );
+ free( newshstr );
+ free( source_index );
+ free( nsh );
+ free( map );
+ return( rc );
+
+} /* End of build_output() */
+
+
+int
+mcpu_object_edit_data( const unsigned char *data, size_t size,
+ const mcpu_object_edit_options *options,
+ unsigned char **out, size_t *out_size,
+ int *changed, char *err, size_t errsz )
+{
+ mcpu_edit_file file;
+ mcpu_edit_section *sections = NULL;
+ size_t count = 0;
+ int local_changed = 0;
+ int rc = -1;
+
+ if( !data || !options || !out || !out_size || !changed )
+ return( -1 );
+ *out = NULL;
+ *out_size = 0;
+ *changed = 0;
+ memset( &file, 0, sizeof( file ) );
+
+ if( edit_file_open( data, size, &file, err, errsz ) ||
+ sections_from_file( &file, &sections, &count, err, errsz ) )
+ goto out;
+
+ if( apply_options( &file, &sections, &count, options, &local_changed,
+ err, errsz ) )
+ goto out;
+
+ if( !local_changed )
+ {
+ rc = copy_original( data, size, out, out_size, err, errsz );
+ goto out;
+ }
+ if( build_output( &file, sections, count, out, out_size, err, errsz ) )
+ goto out;
+
+ *changed = 1;
+ rc = 0;
+
+out:
+ sections_free( sections, count );
+ edit_file_free( &file );
+ return( rc );
+
+} /* End of mcpu_object_edit_data() */
+
+
+int
+mcpu_object_strip_data( const unsigned char *data, size_t size,
+ unsigned char **out, size_t *out_size,
+ int *changed, char *err, size_t errsz )
+{
+ mcpu_edit_file file;
+ mcpu_edit_section *sections = NULL;
+ size_t count = 0;
+ size_t i;
+ int any = 0;
+ int rc = -1;
+
+ if( !data || !out || !out_size || !changed )
+ return( -1 );
+ *out = NULL;
+ *out_size = 0;
+ *changed = 0;
+ memset( &file, 0, sizeof( file ) );
+
+ if( edit_file_open( data, size, &file, err, errsz ) ||
+ sections_from_file( &file, &sections, &count, err, errsz ) )
+ goto out;
+
+ if( file.type != MELF_ET_REL )
+ {
+ for( i = 1; i < count; ++i )
+ if( sections[i].shdr.type == MELF_SHT_SYMTAB &&
+ !( sections[i].shdr.flags & MELF_SHF_ALLOC ) &&
+ !section_link_references( sections, count, i ) )
+ {
+ __mpu_uint32_t link = sections[i].shdr.link;
+
+ sections[i].removed = 1;
+ any = 1;
+ if( link && link < file.shnum && link != file.shstrndx &&
+ sections[link].shdr.type == MELF_SHT_STRTAB )
+ {
+ size_t k;
+ int referenced = 0;
+
+ for( k = 1; k < count; ++k )
+ if( !sections[k].removed && !sections[k].is_new &&
+ sections[k].shdr.link == link )
+ referenced = 1;
+ if( !referenced )
+ sections[link].removed = 1;
+ }
+ }
+ }
+
+ if( !any )
+ {
+ rc = copy_original( data, size, out, out_size, err, errsz );
+ goto out;
+ }
+ if( build_output( &file, sections, count, out, out_size, err, errsz ) )
+ goto out;
+
+ *changed = 1;
+ rc = 0;
+
+out:
+ sections_free( sections, count );
+ edit_file_free( &file );
+ return( rc );
+
+} /* End of mcpu_object_strip_data() */
+
+
+int
+mcpu_object_dump_section( const unsigned char *data, size_t size,
+ const char *name,
+ unsigned char **out, size_t *out_size,
+ char *err, size_t errsz )
+{
+ mcpu_edit_file file;
+ size_t i;
+ int rc = -1;
+
+ if( !data || !name || !out || !out_size )
+ return( -1 );
+ *out = NULL;
+ *out_size = 0;
+ memset( &file, 0, sizeof( file ) );
+
+ if( edit_file_open( data, size, &file, err, errsz ) )
+ return( -1 );
+
+ for( i = 1; i < file.shnum; ++i )
+ {
+ const char *s = name_at( &file, file.shdr[i].name );
+
+ if( s && !strcmp( s, name ) )
+ {
+ if( file.shdr[i].type == MELF_SHT_NOBITS )
+ {
+ snprintf( err, errsz, "section %s has no file contents", name );
+ goto out;
+ }
+ rc = copy_original( file.data + (size_t)file.shdr[i].offset,
+ (size_t)file.shdr[i].size,
+ out, out_size, err, errsz );
+ goto out;
+ }
+ }
+ snprintf( err, errsz, "section %s does not exist", name );
+
+out:
+ edit_file_free( &file );
+ return( rc );
+
+} /* End of mcpu_object_dump_section() */