diff options
Diffstat (limited to 'src/libr-bfd.c')
-rw-r--r-- | src/libr-bfd.c | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/src/libr-bfd.c b/src/libr-bfd.c new file mode 100644 index 0000000..c4bc8b1 --- /dev/null +++ b/src/libr-bfd.c @@ -0,0 +1,533 @@ +/* + * + * Copyright (c) 2008-2011 Erich Hoover + * + * libr libbfd Backend - Add resources into ELF binaries using libbfd + * + * *** PLEASE READ THE README FILE FOR LICENSE DETAILS SPECIFIC TO THIS FILE *** + * + * 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 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * To provide feedback, report bugs, or otherwise contact me: + * ehoover at mines dot edu + * + */ + +/* Include compile-time parameters */ +#include "config.h" + +#include "libr.h" +#include "libr-internal.h" + +/* File access */ +#include <fcntl.h> + +/* Safe rename requires some errno() knowledge */ +#include <errno.h> + +#include <sys/stat.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +/* + * Build the libr_file handle for processing with libbfd + */ +libr_intstatus open_handles(libr_file *file_handle, char *filename, libr_access_t access) +{ + bfd *handle = NULL; + + handle = bfd_openr(filename, "default"); + if(!handle) + RETURN(LIBR_ERROR_OPENFAILED, "Failed to open input file"); + if(!bfd_check_format(handle, bfd_object)) + RETURN(LIBR_ERROR_WRONGFORMAT, "Invalid input file format: not a libbfd object"); + if(bfd_get_flavour(handle) != bfd_target_elf_flavour) + RETURN(LIBR_ERROR_WRONGFORMAT, "Invalid input file format: not an ELF file"); + bfd_set_error(bfd_error_no_error); + file_handle->filename = filename; + file_handle->bfd_read = handle; + file_handle->access = access; + if(access == LIBR_READ_WRITE) + { + struct stat file_stat; + int fd; + + /* Check for write permission on the file */ + fd = open(filename, O_WRONLY); + if(fd == ERROR) + RETURN(LIBR_ERROR_WRITEPERM, "No write permission for file"); + close(fd); + /* Obtain the access mode of the input file */ + if(stat(filename, &file_stat) == ERROR) + RETURN(LIBR_ERROR_NOSIZE, "Failed to obtain file size"); + file_handle->filemode = file_stat.st_mode; + file_handle->fileowner = file_stat.st_uid; + file_handle->filegroup = file_stat.st_gid; + /* Open a temporary file with the same settings as the input file */ + strcpy(file_handle->tempfile, LIBR_TEMPFILE); + file_handle->fd_handle = mkstemp(file_handle->tempfile); + handle = bfd_openw(file_handle->tempfile, bfd_get_target(file_handle->bfd_read)); + if(!bfd_set_format(handle, bfd_get_format(file_handle->bfd_read))) + RETURN(LIBR_ERROR_SETFORMAT, "Failed to set output file format to input file format"); + if(!bfd_set_arch_mach(handle, bfd_get_arch(file_handle->bfd_read), bfd_get_mach(file_handle->bfd_read))) + RETURN(LIBR_ERROR_SETARCH, "Failed to set output file architecture to input file architecture"); + /* twice needed ? */ + if(!bfd_set_format(handle, bfd_get_format(file_handle->bfd_read))) + RETURN(LIBR_ERROR_SETFORMAT, "Failed to set output file format to input file format"); + file_handle->bfd_write = handle; + } + else + { + file_handle->fd_handle = 0; + file_handle->bfd_write = NULL; + } + RETURN_OK; +} + +/* + * Check to see if a symbol should be kept + */ +int keep_symbol(libr_section *sections, libr_section *chkscn) +{ + libr_section *scn; + + /* Check that the section is publicly exposed */ + for(scn = sections; scn != NULL; scn = scn->next) + { + if(scn == chkscn) + { + /* if it is, and has size zero, then it was marked for deletion */ + if(bfd_get_section_size(chkscn) == 0) + return false; + return true; + } + } + return true; +} + +/* + * Remove the symbol corresponding to a deleted section + */ +void remove_sections(libr_section *sections, void *symtab_buffer, long *symtab_count) +{ + asymbol **symtab = (asymbol **) symtab_buffer; + long i, cnt = *symtab_count; + + for(i=0;i<cnt;i++) + { + libr_section *chkscn = NULL; + asymbol *symbol = symtab[i]; + + if(symbol != NULL) + chkscn = bfd_get_section(symbol); + if(chkscn != NULL && !keep_symbol(sections, chkscn)) + { + /* remove the symbol from the table */ + asymbol **tmp = (asymbol **) malloc(sizeof(asymbol *) * (cnt-(i+1))); + memcpy(&tmp[0], &symtab[i+1], sizeof(asymbol *) * (cnt-(i+1))); + memcpy(&symtab[i], &tmp[0], sizeof(asymbol *) * (cnt-(i+1))); + free(tmp); + cnt--; + } + } + *symtab_count = cnt; +} + +int setup_sections(bfd *ihandle, bfd *ohandle) +{ + libr_section *iscn, *oscn; + bfd_vma vma; + + for(iscn = ihandle->sections; iscn != NULL; iscn = iscn->next) + { + if(bfd_get_section_size(iscn) == 0) + { + continue; /* Section has been marked for deletion */ + } + /* Use SEC_LINKER_CREATED to ask the libbfd backend to take care of configuring the section */ + + // Keep the ARM_ATTRIBUTES section type intact on armhf systems + // If this is not done, readelf -A will not print any architecture information for the modified library, + // and ldd will report that the library cannot be found + if (strcmp(iscn->name, ".ARM.attributes") == 0) + { + oscn = bfd_make_section_anyway_with_flags(ohandle, iscn->name, iscn->flags); + } + else + { + oscn = bfd_make_section_anyway_with_flags(ohandle, iscn->name, iscn->flags | SEC_LINKER_CREATED); + } + if(oscn == NULL) + { + printf("failed to create out section: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + if(!bfd_set_section_size(ohandle, oscn, iscn->size)) + { + printf("failed to set data size: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + vma = bfd_section_vma(ihandle, iscn); + if(!bfd_set_section_vma(ohandle, oscn, vma)) + { + printf("failed to set virtual memory address: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + oscn->lma = iscn->lma; + if(!bfd_set_section_alignment(ohandle, oscn, bfd_section_alignment(ihandle, iscn))) + { + printf("failed to compute section alignment: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + oscn->entsize = iscn->entsize; + iscn->output_section = oscn; + iscn->output_offset = vma; + if(!bfd_copy_private_section_data(ihandle, iscn, ohandle, oscn)) + { + printf("failed to compute section alignment: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + } + return true; +} + +/* + * Go through the rather complicated process of using libbfd to build the output file + */ +int build_output(libr_file *file_handle) +{ + void *symtab_buffer = NULL, *reloc_buffer = NULL, *buffer = NULL; + bfd_size_type symtab_size, reloc_size, size; + bfd *ohandle = file_handle->bfd_write; + bfd *ihandle = file_handle->bfd_read; + long symtab_count, reloc_count; + libr_section *iscn, *oscn; + + if(!bfd_set_start_address(ohandle, bfd_get_start_address(ihandle))) + { + printf("failed to set start address: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + if(!bfd_set_file_flags(ohandle, bfd_get_file_flags(ihandle))) + { + printf("failed to set file flags: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + /* Setup the sections in the output file */ + if(!setup_sections(ihandle, ohandle)) + return false; /* error already printed */ + if(!bfd_copy_private_header_data(ihandle, ohandle)) + { + printf("failed to copy header: %s\n", bfd_errmsg(bfd_get_error())); + return false; /* failed to create section */ + } + /* Get the old symbol table */ + if((bfd_get_file_flags(ihandle) & HAS_SYMS) == 0) + { + printf("file has no symbol table: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + symtab_size = bfd_get_symtab_upper_bound(ihandle); + if((signed)symtab_size < 0) + { + printf("failed to get symbol table size: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + symtab_buffer = malloc(symtab_size); + symtab_count = bfd_canonicalize_symtab(ihandle, symtab_buffer); + if(symtab_count < 0) + { + printf("failed to get symbol table number of entries: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + /* Tweak the symbol table to remove sections that no-longer exist */ + remove_sections(ihandle->sections, symtab_buffer, &symtab_count); + bfd_set_symtab(ohandle, symtab_buffer, symtab_count); + /* Actually copy section data */ + for(iscn = ihandle->sections; iscn != NULL; iscn = iscn->next) + { + size = bfd_get_section_size(iscn); + if(size == 0) + continue; /* Section has been marked for deletion */ + oscn = iscn->output_section; + reloc_size = bfd_get_reloc_upper_bound(ihandle, iscn); + if(reloc_size == 0) + bfd_set_reloc(ohandle, oscn, NULL, 0); + else + { + reloc_buffer = malloc(reloc_size); + reloc_count = bfd_canonicalize_reloc(ihandle, iscn, reloc_buffer, symtab_buffer); + bfd_set_reloc(ohandle, oscn, reloc_buffer, reloc_count); + } + + if(bfd_get_section_flags(ihandle, iscn) & SEC_HAS_CONTENTS) + { + /* NOTE: if the section is just being copied then do that, otherwise grab + * the user data for the section (stored previously by set_data) + */ + if(iscn->userdata == NULL) + { + buffer = malloc(size); + if(!bfd_get_section_contents(ihandle, iscn, buffer, 0, size)) + { + printf("failed to get section contents: %s\n", bfd_errmsg(bfd_get_error())); + free(buffer); + return false; + } + } + else + buffer = iscn->userdata; + if(!bfd_set_section_contents(ohandle, oscn, buffer, 0, size)) + { + printf("failed to set section contents: %s\n", bfd_errmsg(bfd_get_error())); + free(buffer); + return false; + } + free(buffer); + if(!bfd_copy_private_section_data(ihandle, iscn, ohandle, oscn)) + { + printf("failed to copy private section data: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + } + } + if(!bfd_copy_private_bfd_data(ihandle, ohandle)) + { + printf("failed to copy private data: %s\n", bfd_errmsg(bfd_get_error())); + return false; + } + return true; +} + +/* + * Perform a cross-device safe rename + */ +int safe_rename(const char *old, const char *new) +{ + char buffer[1024]; + FILE *in, *out; + int read; + + in = fopen(old, "r"); + if(!in) + return -1; + out = fopen(new, "w"); + if(!out) + return -1; + while(!feof(in) && !ferror(in)) + { + read = fread(buffer, 1, sizeof(buffer), in); + fwrite(buffer, read, 1, out); + } + fclose(in); + fclose(out); + if(ferror(in)) + { + remove(new); + return -1; + } + return remove(old); +} + +/* + * Write the output file using the libbfd method + */ +void write_output(libr_file *file_handle) +{ + int write_ok = false; + + if(file_handle->bfd_write != NULL) + { + write_ok = true; + if(!build_output(file_handle)) + { + printf("failed to build output file.\n"); + write_ok = false; + } + if(!bfd_close(file_handle->bfd_write)) + { + printf("failed to close write handle.\n"); + write_ok = false; + } + if(file_handle->fd_handle != 0 && close(file_handle->fd_handle)) + { + write_ok = false; + printf("failed to close write file descriptor.\n"); + } + } + /* The read handle must be closed last since it is used in the write process */ + if(!bfd_close(file_handle->bfd_read)) + printf("failed to close read handle.\n"); + /* Copy the temporary output over the input */ + if(write_ok) + { + if(rename(file_handle->tempfile, file_handle->filename) < 0) + { + if(errno != EXDEV || safe_rename(file_handle->tempfile, file_handle->filename) < 0) + printf("failed to rename output file: %m\n"); + } + if(chmod(file_handle->filename, file_handle->filemode) < 0) + printf("failed to set file mode.\n"); + if(chown(file_handle->filename, file_handle->fileowner, file_handle->filegroup) < 0) + printf("failed to set file ownership.\n"); + } +} + +/* + * Find a named section from the ELF file using libbfd + */ +libr_intstatus find_section(libr_file *file_handle, char *section_name, libr_section **retscn) +{ + libr_section *scn; + + for(scn = file_handle->bfd_read->sections; scn != NULL; scn = scn->next) + { + if(strcmp(scn->name, section_name) == 0) + { + *retscn = scn; + RETURN_OK; + } + } + RETURN(LIBR_ERROR_NOSECTION, "ELF resource section not found"); +} + +/* + * Obtain the data from a section using libbfd + */ +libr_data *get_data(libr_file *file_handle, libr_section *scn) +{ + libr_data *data = malloc(scn->size); + + if(!bfd_get_section_contents(file_handle->bfd_read, scn, data, 0, scn->size)) + { + free(data); + data = NULL; + } + scn->userdata = data; + return data; +} + +/* + * Create new data for a section using libbfd + */ +libr_data *new_data(libr_file *file_handle, libr_section *scn) +{ + /* NOTE: expanding data is handled by set_data for libbfd */ + if(scn->userdata != NULL) + return scn->userdata; + scn->size = 0; + scn->userdata = malloc(0); + return scn->userdata; +} + +/* + * Create new data for a section using libbfd (at least, do so memory-wise) + */ +libr_intstatus set_data(libr_file *file_handle, libr_section *scn, libr_data *data, off_t offset, char *buffer, size_t size) +{ + char *intbuffer = NULL; + + /* special case: clear buffer */ + if(buffer == NULL) + { + scn->size = 0; + if(scn->userdata != NULL) + free(scn->userdata); + RETURN_OK; + } + /* normal case: add new data to the buffer */ + scn->size = offset + size; + scn->userdata = realloc(data, scn->size); + if(scn->userdata == NULL) + RETURN(LIBR_ERROR_MEMALLOC, "Failed to allocate memory for data"); + intbuffer = scn->userdata; + memcpy(&intbuffer[offset], buffer, size); + RETURN_OK; +} + +/* + * Create a new section using libbfd + */ +libr_intstatus add_section(libr_file *file_handle, char *resource_name, libr_section **retscn) +{ + libr_section *scn = NULL; + + scn = bfd_make_section(file_handle->bfd_read, resource_name); + if(scn == NULL) + RETURN(LIBR_ERROR_NEWSECTION, "Failed to create new section"); + if(!bfd_set_section_flags(file_handle->bfd_read, scn, SEC_HAS_CONTENTS | SEC_DATA | SEC_IN_MEMORY)) + RETURN(LIBR_ERROR_SETFLAGS, "Failed to set flags for section"); + *retscn = scn; + RETURN_OK; +} + +/* + * Remove a section and eliminate it from the ELF string table using libbfd + */ +libr_intstatus remove_section(libr_file *file_handle, libr_section *scn) +{ + scn->size = 0; + RETURN_OK; +} + +/* + * Return the pointer to the actual data in the section + */ +void *data_pointer(libr_section *scn, libr_data *data) +{ + return data; +} + +/* + * Return the size of the data in the section + */ +size_t data_size(libr_section *scn, libr_data *data) +{ + return scn->size; +} + +/* + * Return the next section in the ELF file + */ +libr_section *next_section(libr_file *file_handle, libr_section *scn) +{ + /* get the first section */ + if(scn == NULL) + { + if(file_handle->bfd_read == NULL) + return NULL; + return file_handle->bfd_read->sections; + } + return scn->next; +} + +/* + * Return the name of a section + */ +char *section_name(libr_file *file_handle, libr_section *scn) +{ + return (char *) scn->name; +} + +/* + * Initialize libbfd + */ +void initialize_backend(void) +{ + bfd_init(); +} + |