You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libr/src/libr-bfd.c

620 lines
16 KiB

10 years ago
/*
*
* Copyright (c) 2008-2011 Erich Hoover
* Copyright (c) 2015 Timothy Pearson <kb9vqf@pearsoncomputing.net>
10 years ago
*
* 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>
#include <limits.h>
10 years ago
/*
* 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(
#ifdef HAVE_BFD_2_34
bfd_section_size(chkscn) == 0
#else
bfd_get_section_size(chkscn) == 0
#endif
)
{
10 years ago
return false;
}
10 years ago
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)
{
#ifdef HAVE_BFD_2_34
chkscn = bfd_asymbol_section(symbol);
#else
10 years ago
chkscn = bfd_get_section(symbol);
#endif
}
10 years ago
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(
#ifdef HAVE_BFD_2_34
bfd_section_size(iscn) == 0
#else
bfd_get_section_size(iscn) == 0
#endif
)
10 years ago
{
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(
#ifdef HAVE_BFD_2_34
!bfd_set_section_size(oscn, iscn->size)
#else
!bfd_set_section_size(ohandle, oscn, iscn->size)
#endif
)
10 years ago
{
printf("failed to set data size: %s\n", bfd_errmsg(bfd_get_error()));
return false;
}
#ifdef HAVE_BFD_2_34
vma = bfd_section_vma(iscn);
#else
10 years ago
vma = bfd_section_vma(ihandle, iscn);
#endif
if(
#ifdef HAVE_BFD_2_34
!bfd_set_section_vma(oscn, vma)
#else
!bfd_set_section_vma(ohandle, oscn, vma)
#endif
)
10 years ago
{
printf("failed to set virtual memory address: %s\n", bfd_errmsg(bfd_get_error()));
return false;
}
oscn->lma = iscn->lma;
if(
#ifdef HAVE_BFD_2_34
!bfd_set_section_alignment(oscn, bfd_section_alignment(iscn))
#else
!bfd_set_section_alignment(ohandle, oscn, bfd_section_alignment(ihandle, iscn))
#endif
)
10 years ago
{
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)
{
#ifdef HAVE_BFD_2_34
size = bfd_section_size(iscn);
#else
10 years ago
size = bfd_get_section_size(iscn);
#endif
10 years ago
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(
#ifdef HAVE_BFD_2_34
bfd_section_flags(iscn) & SEC_HAS_CONTENTS
#else
bfd_get_section_flags(ihandle, iscn) & SEC_HAS_CONTENTS
#endif
)
10 years ago
{
/* 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_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;
size_t read;
int status_in;
int status_out;
char linkdest[PATH_MAX];
char linkdest_test[PATH_MAX];
int ret;
int is_symlink = 0;
ret = readlink(new, linkdest_test, PATH_MAX);
while (ret >= 0) {
// Symlink encountered
is_symlink = 1;
linkdest_test[ret] = 0;
char* cwd = getcwd(NULL, 0);
snprintf(linkdest, PATH_MAX, "%s/%s", cwd, linkdest_test);
free(cwd);
ret = readlink(linkdest, linkdest_test, PATH_MAX);
}
10 years ago
in = fopen(old, "r");
if(!in) {
10 years ago
return -1;
}
// Avoid bus error if modifying a library we are currently using
unlink((is_symlink)?linkdest:new);
out = fopen((is_symlink)?linkdest:new, "w");
if(!out) {
fclose(in);
10 years ago
return -1;
}
10 years ago
while(!feof(in)) {
10 years ago
read = fread(buffer, 1, sizeof(buffer), in);
if (read > 0) {
fwrite(buffer, 1, read, out);
}
if (ferror(in) || ferror(out)) {
fclose(in);
fclose(out);
remove((is_symlink)?linkdest:new);
return -1;
}
10 years ago
}
status_in = ferror(in);
status_out = ferror(out);
10 years ago
fclose(in);
fclose(out);
if(status_in || status_out) {
remove((is_symlink)?linkdest:new);
10 years ago
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(
#ifdef HAVE_BFD_2_34
!bfd_set_section_flags(scn, SEC_HAS_CONTENTS | SEC_DATA | SEC_IN_MEMORY)
#else
!bfd_set_section_flags(file_handle->bfd_read, scn, SEC_HAS_CONTENTS | SEC_DATA | SEC_IN_MEMORY)
#endif
)
{
10 years ago
RETURN(LIBR_ERROR_SETFLAGS, "Failed to set flags for section");
}
10 years ago
*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();
}