summaryrefslogtreecommitdiff
path: root/gpxe/src/util/efilink.c
diff options
context:
space:
mode:
Diffstat (limited to 'gpxe/src/util/efilink.c')
-rw-r--r--gpxe/src/util/efilink.c507
1 files changed, 507 insertions, 0 deletions
diff --git a/gpxe/src/util/efilink.c b/gpxe/src/util/efilink.c
new file mode 100644
index 00000000..e21f4a90
--- /dev/null
+++ b/gpxe/src/util/efilink.c
@@ -0,0 +1,507 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <bfd.h>
+
+struct bfd_file {
+ bfd *bfd;
+ asymbol **symtab;
+ long symcount;
+};
+
+struct pe_relocs {
+ struct pe_relocs *next;
+ unsigned long start_rva;
+ unsigned int used_relocs;
+ unsigned int total_relocs;
+ uint16_t *relocs;
+};
+
+/**
+ * Allocate memory
+ *
+ * @v len Length of memory to allocate
+ * @ret ptr Pointer to allocated memory
+ */
+static void * xmalloc ( size_t len ) {
+ void *ptr;
+
+ ptr = malloc ( len );
+ if ( ! ptr ) {
+ fprintf ( stderr, "Could not allocate %zd bytes\n", len );
+ exit ( 1 );
+ }
+
+ return ptr;
+}
+
+/**
+ * Generate entry in PE relocation table
+ *
+ * @v pe_reltab PE relocation table
+ * @v rva RVA
+ * @v size Size of relocation entry
+ */
+static void generate_pe_reloc ( struct pe_relocs **pe_reltab,
+ unsigned long rva, size_t size ) {
+ unsigned long start_rva;
+ uint16_t reloc;
+ struct pe_relocs *pe_rel;
+ uint16_t *relocs;
+
+ /* Construct */
+ start_rva = ( rva & ~0xfff );
+ reloc = ( rva & 0xfff );
+ switch ( size ) {
+ case 4:
+ reloc |= 0x3000;
+ break;
+ case 2:
+ reloc |= 0x2000;
+ break;
+ default:
+ fprintf ( stderr, "Unsupported relocation size %zd\n", size );
+ exit ( 1 );
+ }
+
+ /* Locate or create PE relocation table */
+ for ( pe_rel = *pe_reltab ; pe_rel ; pe_rel = pe_rel->next ) {
+ if ( pe_rel->start_rva == start_rva )
+ break;
+ }
+ if ( ! pe_rel ) {
+ pe_rel = xmalloc ( sizeof ( *pe_rel ) );
+ memset ( pe_rel, 0, sizeof ( *pe_rel ) );
+ pe_rel->next = *pe_reltab;
+ *pe_reltab = pe_rel;
+ pe_rel->start_rva = start_rva;
+ }
+
+ /* Expand relocation list if necessary */
+ if ( pe_rel->used_relocs < pe_rel->total_relocs ) {
+ relocs = pe_rel->relocs;
+ } else {
+ pe_rel->total_relocs = ( pe_rel->total_relocs ?
+ ( pe_rel->total_relocs * 2 ) : 256 );
+ relocs = xmalloc ( pe_rel->total_relocs *
+ sizeof ( pe_rel->relocs[0] ) );
+ memset ( relocs, 0,
+ pe_rel->total_relocs * sizeof ( pe_rel->relocs[0] ) );
+ memcpy ( relocs, pe_rel->relocs,
+ pe_rel->used_relocs * sizeof ( pe_rel->relocs[0] ) );
+ free ( pe_rel->relocs );
+ pe_rel->relocs = relocs;
+ }
+
+ /* Store relocation */
+ pe_rel->relocs[ pe_rel->used_relocs++ ] = reloc;
+}
+
+/**
+ * Calculate size of binary PE relocation table
+ *
+ * @v pe_reltab PE relocation table
+ * @v buffer Buffer to contain binary table, or NULL
+ * @ret size Size of binary table
+ */
+static size_t output_pe_reltab ( struct pe_relocs *pe_reltab,
+ void *buffer ) {
+ struct pe_relocs *pe_rel;
+ unsigned int num_relocs;
+ size_t size;
+ size_t total_size = 0;
+
+ for ( pe_rel = pe_reltab ; pe_rel ; pe_rel = pe_rel->next ) {
+ num_relocs = ( ( pe_rel->used_relocs + 1 ) & ~1 );
+ size = ( sizeof ( uint32_t ) /* VirtualAddress */ +
+ sizeof ( uint32_t ) /* SizeOfBlock */ +
+ ( num_relocs * sizeof ( uint16_t ) ) );
+ if ( buffer ) {
+ *( (uint32_t *) ( buffer + total_size + 0 ) )
+ = pe_rel->start_rva;
+ *( (uint32_t *) ( buffer + total_size + 4 ) ) = size;
+ memcpy ( ( buffer + total_size + 8 ), pe_rel->relocs,
+ ( num_relocs * sizeof ( uint16_t ) ) );
+ }
+ total_size += size;
+ }
+
+ return total_size;
+}
+
+/**
+ * Read symbol table
+ *
+ * @v bfd BFD file
+ */
+static void read_symtab ( struct bfd_file *bfd ) {
+ long symtab_size;
+
+ /* Get symbol table size */
+ symtab_size = bfd_get_symtab_upper_bound ( bfd->bfd );
+ if ( symtab_size < 0 ) {
+ bfd_perror ( "Could not get symbol table upper bound" );
+ exit ( 1 );
+ }
+
+ /* Allocate and read symbol table */
+ bfd->symtab = xmalloc ( symtab_size );
+ bfd->symcount = bfd_canonicalize_symtab ( bfd->bfd, bfd->symtab );
+ if ( bfd->symcount < 0 ) {
+ bfd_perror ( "Cannot read symbol table" );
+ exit ( 1 );
+ }
+}
+
+/**
+ * Read relocation table
+ *
+ * @v bfd BFD file
+ * @v section Section
+ * @v symtab Symbol table
+ * @ret reltab Relocation table
+ */
+static arelent ** read_reltab ( struct bfd_file *bfd, asection *section ) {
+ long reltab_size;
+ arelent **reltab;
+ long numrels;
+
+ /* Get relocation table size */
+ reltab_size = bfd_get_reloc_upper_bound ( bfd->bfd, section );
+ if ( reltab_size < 0 ) {
+ bfd_perror ( "Could not get relocation table upper bound" );
+ exit ( 1 );
+ }
+
+ /* Allocate and read relocation table */
+ reltab = xmalloc ( reltab_size );
+ numrels = bfd_canonicalize_reloc ( bfd->bfd, section, reltab,
+ bfd->symtab );
+ if ( numrels < 0 ) {
+ bfd_perror ( "Cannot read relocation table" );
+ exit ( 1 );
+ }
+
+ return reltab;
+}
+
+
+/**
+ * Open input BFD file
+ *
+ * @v filename File name
+ * @ret ibfd BFD file
+ */
+static struct bfd_file * open_input_bfd ( const char *filename ) {
+ struct bfd_file *ibfd;
+
+ /* Create BFD file */
+ ibfd = xmalloc ( sizeof ( *ibfd ) );
+ memset ( ibfd, 0, sizeof ( *ibfd ) );
+
+ /* Open the file */
+ ibfd->bfd = bfd_openr ( filename, NULL );
+ if ( ! ibfd->bfd ) {
+ fprintf ( stderr, "Cannot open %s: ", filename );
+ bfd_perror ( NULL );
+ exit ( 1 );
+ }
+
+ /* The call to bfd_check_format() must be present, otherwise
+ * we get a segfault from later BFD calls.
+ */
+ if ( bfd_check_format ( ibfd->bfd, bfd_object ) < 0 ) {
+ fprintf ( stderr, "%s is not an object file\n", filename );
+ exit ( 1 );
+ }
+
+ /* Read symbols and relocation entries */
+ read_symtab ( ibfd );
+
+ return ibfd;
+}
+
+/**
+ * Open output BFD file
+ *
+ * @v filename File name
+ * @v ibfd Input BFD file
+ * @ret obfd BFD file
+ */
+static struct bfd_file * open_output_bfd ( const char *filename,
+ struct bfd_file *ibfd ) {
+ struct bfd_file *obfd;
+ asection *isection;
+ asection *osection;
+
+ /*
+ * Most of this code is based on what objcopy.c does.
+ *
+ */
+
+ /* Create BFD file */
+ obfd = xmalloc ( sizeof ( *obfd ) );
+ memset ( obfd, 0, sizeof ( *obfd ) );
+
+ /* Open the file */
+ obfd->bfd = bfd_openw ( filename, ibfd->bfd->xvec->name );
+ if ( ! obfd->bfd ) {
+ fprintf ( stderr, "Cannot open %s: ", filename );
+ bfd_perror ( NULL );
+ exit ( 1 );
+ }
+
+ /* Copy per-file data */
+ if ( ! bfd_set_arch_mach ( obfd->bfd, bfd_get_arch ( ibfd->bfd ),
+ bfd_get_mach ( ibfd->bfd ) ) ) {
+ bfd_perror ( "Cannot copy architecture" );
+ exit ( 1 );
+ }
+ if ( ! bfd_set_format ( obfd->bfd, bfd_get_format ( ibfd->bfd ) ) ) {
+ bfd_perror ( "Cannot copy format" );
+ exit ( 1 );
+ }
+ if ( ! bfd_copy_private_header_data ( ibfd->bfd, obfd->bfd ) ) {
+ bfd_perror ( "Cannot copy private header data" );
+ exit ( 1 );
+ }
+
+ /* Create sections */
+ for ( isection = ibfd->bfd->sections ; isection ;
+ isection = isection->next ) {
+ osection = bfd_make_section_anyway ( obfd->bfd,
+ isection->name );
+ if ( ! osection ) {
+ bfd_perror ( "Cannot create section" );
+ exit ( 1 );
+ }
+ if ( ! bfd_set_section_flags ( obfd->bfd, osection,
+ isection->flags ) ) {
+ bfd_perror ( "Cannot copy section flags" );
+ exit ( 1 );
+ }
+ if ( ! bfd_set_section_size ( obfd->bfd, osection,
+ bfd_section_size ( ibfd->bfd, isection ) ) ) {
+ bfd_perror ( "Cannot copy section size" );
+ exit ( 1 );
+ }
+ if ( ! bfd_set_section_vma ( obfd->bfd, osection,
+ bfd_section_vma ( ibfd->bfd, isection ) ) ) {
+ bfd_perror ( "Cannot copy section VMA" );
+ exit ( 1 );
+ }
+ osection->lma = bfd_section_lma ( ibfd->bfd, isection );
+ if ( ! bfd_set_section_alignment ( obfd->bfd, osection,
+ bfd_section_alignment ( ibfd->bfd, isection ) ) ) {
+ bfd_perror ( "Cannot copy section alignment" );
+ exit ( 1 );
+ }
+ osection->entsize = isection->entsize;
+ isection->output_section = osection;
+ isection->output_offset = 0;
+ if ( ! bfd_copy_private_section_data ( ibfd->bfd, isection,
+ obfd->bfd, osection ) ){
+ bfd_perror ( "Cannot copy section private data" );
+ exit ( 1 );
+ }
+ }
+
+ /* Copy symbol table */
+ bfd_set_symtab ( obfd->bfd, ibfd->symtab, ibfd->symcount );
+ obfd->symtab = ibfd->symtab;
+
+ return obfd;
+}
+
+/**
+ * Copy section from input BFD file to output BFD file
+ *
+ * @v obfd Output BFD file
+ * @v ibfd Input BFD file
+ * @v section Section
+ */
+static void copy_bfd_section ( struct bfd_file *obfd, struct bfd_file *ibfd,
+ asection *isection ) {
+ size_t size;
+ void *buf;
+ arelent **reltab;
+ arelent **rel;
+ char *errmsg;
+
+ /* Read in original section */
+ size = bfd_section_size ( ibfd->bfd, isection );
+ if ( ! size )
+ return;
+ buf = xmalloc ( size );
+ if ( ( ! bfd_get_section_contents ( ibfd->bfd, isection,
+ buf, 0, size ) ) ) {
+ fprintf ( stderr, "Cannot read section %s: ", isection->name );
+ bfd_perror ( NULL );
+ exit ( 1 );
+ }
+
+ /* Perform relocations. We do this here, rather than letting
+ * ld do it for us when creating the input ELF file, so that
+ * we can change symbol values as a result of having created
+ * the .reloc section.
+ */
+ reltab = read_reltab ( ibfd, isection );
+ for ( rel = reltab ; *rel ; rel++ ) {
+ bfd_perform_relocation ( ibfd->bfd, *rel, buf, isection,
+ NULL, &errmsg );
+ }
+ free ( reltab );
+
+ /* Write out modified section */
+ if ( ( ! bfd_set_section_contents ( obfd->bfd,
+ isection->output_section,
+ buf, 0, size ) ) ) {
+ fprintf ( stderr, "Cannot write section %s: ",
+ isection->output_section->name );
+ bfd_perror ( NULL );
+ exit ( 1 );
+ }
+
+ free ( buf );
+}
+
+/**
+ * Process relocation record
+ *
+ * @v section Section
+ * @v rel Relocation entry
+ * @v pe_reltab PE relocation table to fill in
+ */
+static void process_reloc ( asection *section, arelent *rel,
+ struct pe_relocs **pe_reltab ) {
+ reloc_howto_type *howto = rel->howto;
+ asymbol *sym = *(rel->sym_ptr_ptr);
+ unsigned long offset = ( section->lma + rel->address );
+
+ if ( bfd_is_abs_section ( sym->section ) ) {
+ /* Skip absolute symbols; the symbol value won't
+ * change when the object is loaded.
+ */
+ } else if ( strcmp ( howto->name, "R_386_32" ) == 0 ) {
+ /* Generate a 4-byte PE relocation */
+ generate_pe_reloc ( pe_reltab, offset, 4 );
+ } else if ( strcmp ( howto->name, "R_386_16" ) == 0 ) {
+ /* Generate a 2-byte PE relocation */
+ generate_pe_reloc ( pe_reltab, offset, 2 );
+ } else if ( strcmp ( howto->name, "R_386_PC32" ) == 0 ) {
+ /* Skip PC-relative relocations; all relative offsets
+ * remain unaltered when the object is loaded.
+ */
+ } else {
+ fprintf ( stderr, "Unrecognised relocation type %s\n",
+ howto->name );
+ exit ( 1 );
+ }
+}
+
+/**
+ * Create .reloc section
+ *
+ * obfd Output BFD file
+ * section .reloc section in output file
+ * pe_reltab PE relocation table
+ */
+static void create_reloc_section ( struct bfd_file *obfd, asection *section,
+ struct pe_relocs *pe_reltab ) {
+ size_t raw_size;
+ size_t size;
+ size_t old_size;
+ void *buf;
+ asymbol **sym;
+
+ /* Build binary PE relocation table */
+ raw_size = output_pe_reltab ( pe_reltab, NULL );
+ size = ( ( raw_size + 31 ) & ~31 );
+ buf = xmalloc ( size );
+ memset ( buf, 0, size );
+ output_pe_reltab ( pe_reltab, buf );
+
+ /* Write .reloc section */
+ old_size = bfd_section_size ( obfd->bfd, section );
+ if ( ! bfd_set_section_size ( obfd->bfd, section, size ) ) {
+ bfd_perror ( "Cannot resize .reloc section" );
+ exit ( 1 );
+ }
+ if ( ! bfd_set_section_contents ( obfd->bfd, section,
+ buf, 0, size ) ) {
+ bfd_perror ( "Cannot set .reloc section contents" );
+ exit ( 1 );
+ }
+
+ /* Update symbols pertaining to the relocation directory */
+ for ( sym = obfd->symtab ; *sym ; sym++ ) {
+ if ( strcmp ( (*sym)->name, "_reloc_memsz" ) == 0 ) {
+ (*sym)->value = size;
+ } else if ( strcmp ( (*sym)->name, "_reloc_filesz" ) == 0 ){
+ (*sym)->value = raw_size;
+ } else if ( strcmp ( (*sym)->name, "_filesz" ) == 0 ) {
+ (*sym)->value += ( size - old_size );
+ }
+ }
+}
+
+int main ( int argc, const char *argv[] ) {
+ const char *iname;
+ const char *oname;
+ struct bfd_file *ibfd;
+ struct bfd_file *obfd;
+ asection *section;
+ arelent **reltab;
+ arelent **rel;
+ struct pe_relocs *pe_reltab = NULL;
+ asection *reloc_section;
+
+ /* Initialise libbfd */
+ bfd_init();
+
+ /* Identify intput and output files */
+ if ( argc != 3 ) {
+ fprintf ( stderr, "Syntax: %s infile outfile\n", argv[0] );
+ exit ( 1 );
+ }
+ iname = argv[1];
+ oname = argv[2];
+
+ /* Open BFD files */
+ ibfd = open_input_bfd ( iname );
+ obfd = open_output_bfd ( oname, ibfd );
+
+ /* Process relocations in all sections */
+ for ( section = ibfd->bfd->sections ; section ;
+ section = section->next ) {
+ reltab = read_reltab ( ibfd, section );
+ for ( rel = reltab ; *rel ; rel++ ) {
+ process_reloc ( section, *rel, &pe_reltab );
+ }
+ free ( reltab );
+ }
+
+ /* Create modified .reloc section */
+ reloc_section = bfd_get_section_by_name ( obfd->bfd, ".reloc" );
+ if ( ! reloc_section ) {
+ fprintf ( stderr, "Cannot find .reloc section\n" );
+ exit ( 1 );
+ }
+ create_reloc_section ( obfd, reloc_section, pe_reltab );
+
+ /* Copy other section contents */
+ for ( section = ibfd->bfd->sections ; section ;
+ section = section->next ) {
+ if ( section->output_section != reloc_section )
+ copy_bfd_section ( obfd, ibfd, section );
+ }
+
+ /* Write out files and clean up */
+ bfd_close ( obfd->bfd );
+ bfd_close ( ibfd->bfd );
+
+ return 0;
+}