diff options
Diffstat (limited to 'tools/soslim')
-rw-r--r-- | tools/soslim/Android.mk | 49 | ||||
-rw-r--r-- | tools/soslim/cmdline.c | 141 | ||||
-rw-r--r-- | tools/soslim/cmdline.h | 16 | ||||
-rw-r--r-- | tools/soslim/common.c | 35 | ||||
-rw-r--r-- | tools/soslim/common.h | 49 | ||||
-rw-r--r-- | tools/soslim/debug.c | 40 | ||||
-rw-r--r-- | tools/soslim/debug.h | 88 | ||||
-rw-r--r-- | tools/soslim/main.c | 360 | ||||
-rw-r--r-- | tools/soslim/prelink_info.c | 106 | ||||
-rw-r--r-- | tools/soslim/prelink_info.h | 9 | ||||
-rw-r--r-- | tools/soslim/soslim.c | 528 | ||||
-rw-r--r-- | tools/soslim/soslim.h | 32 | ||||
-rw-r--r-- | tools/soslim/symfilter.c | 242 | ||||
-rw-r--r-- | tools/soslim/symfilter.h | 50 |
14 files changed, 1745 insertions, 0 deletions
diff --git a/tools/soslim/Android.mk b/tools/soslim/Android.mk new file mode 100644 index 0000000..60a860a --- /dev/null +++ b/tools/soslim/Android.mk @@ -0,0 +1,49 @@ +# Copyright 2005 The Android Open Source Project +# +# Android.mk for soslim +# + +LOCAL_PATH:= $(call my-dir) + +ifeq ($(TARGET_ARCH),arm) +include $(CLEAR_VARS) + +LOCAL_LDLIBS += -ldl +LOCAL_CFLAGS += -O2 -g +LOCAL_CFLAGS += -fno-function-sections -fno-data-sections -fno-inline +LOCAL_CFLAGS += -Wall -Wno-unused-function #-Werror +LOCAL_CFLAGS += -DBIG_ENDIAN=1 +LOCAL_CFLAGS += -DARM_SPECIFIC_HACKS +LOCAL_CFLAGS += -DSUPPORT_ANDROID_PRELINK_TAGS +LOCAL_CFLAGS += -DDEBUG +LOCAL_CFLAGS += -DSTRIP_STATIC_SYMBOLS +LOCAL_CFLAGS += -DMOVE_SECTIONS_IN_RANGES + +ifeq ($(HOST_OS),windows) +# Cygwin stat does not support ACCESSPERMS bitmask +LOCAL_CFLAGS += -DACCESSPERMS=0777 +LOCAL_LDLIBS += -lintl +endif + +LOCAL_SRC_FILES := \ + cmdline.c \ + common.c \ + debug.c \ + soslim.c \ + main.c \ + prelink_info.c \ + symfilter.c + +LOCAL_C_INCLUDES:= \ + $(LOCAL_PATH)/ \ + external/elfutils/lib/ \ + external/elfutils/libelf/ \ + external/elfutils/libebl/ \ + external/elfcopy/ + +LOCAL_STATIC_LIBRARIES := libelfcopy libelf libebl libebl_arm #dl + +LOCAL_MODULE := soslim + +include $(BUILD_HOST_EXECUTABLE) +endif #TARGET_ARCH==arm diff --git a/tools/soslim/cmdline.c b/tools/soslim/cmdline.c new file mode 100644 index 0000000..c2d5e71 --- /dev/null +++ b/tools/soslim/cmdline.c @@ -0,0 +1,141 @@ +#include <debug.h> +#include <cmdline.h> +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <string.h> +#include <ctype.h> + +extern char *optarg; +extern int optind, opterr, optopt; + +static struct option long_options[] = +{ + {"verbose", no_argument, 0, 'V'}, + {"quiet", no_argument, 0, 'Q'}, + {"shady", no_argument, 0, 'S'}, + {"print", no_argument, 0, 'p'}, + {"help", no_argument, 0, 'h'}, + {"outfile", required_argument, 0, 'o'}, + {"filter", required_argument, 0, 'f'}, + {"dry", no_argument, 0, 'n'}, + {"strip", no_argument, 0, 's'}, + {0, 0, 0, 0}, +}; + +/* This array must parallel long_options[] */ +static +const char *descriptions[sizeof(long_options)/sizeof(long_options[0])] = { + "print verbose output", + "suppress errors and warnings", + "patch ABS symbols whose values coincide with section starts and ends", + "print the symbol table (if specified, only -V is allowed)", + "this help screen", + "specify an output file (if not provided, input file is modified)", + "specify a symbol-filter file", + "dry run (perform all calculations but do not modify the ELF file)", + "strip debug sections, if they are present" +}; + +void print_help(void) +{ + fprintf(stdout, + "invokation:\n" + "\tsoslim file1 [file2 file3 ... fileN] [-Ldir1 -Ldir2 ... -LdirN] " + "[-Vpn]\n" + "or\n" + "\tsoslim -h\n\n"); + fprintf(stdout, "options:\n"); + struct option *opt = long_options; + const char **desc = descriptions; + while (opt->name) { + fprintf(stdout, "\t-%c/--%-15s %s\n", + opt->val, + opt->name, + *desc); + opt++; + desc++; + } +} + +int get_options(int argc, char **argv, + char **outfile, + char **symsfile, + int *print_symtab, + int *verbose, + int *quiet, + int *shady, + int *dry_run, + int *strip_debug) +{ + int c; + + ASSERT(outfile); + *outfile = NULL; + ASSERT(symsfile); + *symsfile = NULL; + ASSERT(print_symtab); + *print_symtab = 0; + ASSERT(verbose); + *verbose = 0; + ASSERT(quiet); + *quiet = 0; + ASSERT(shady); + *shady = 0; + ASSERT(dry_run); + *dry_run = 0; + ASSERT(strip_debug); + *strip_debug = 0; + + while (1) { + /* getopt_long stores the option index here. */ + int option_index = 0; + + c = getopt_long (argc, argv, + "QVSphi:o:y:Y:f:ns", + long_options, + &option_index); + /* Detect the end of the options. */ + if (c == -1) break; + + if (isgraph(c)) { + INFO ("option -%c with value `%s'\n", c, (optarg ?: "(null)")); + } + +#define SET_STRING_OPTION(name) do { \ + ASSERT(optarg); \ + *name = strdup(optarg); \ +} while(0) + + switch (c) { + case 0: + /* If this option set a flag, do nothing else now. */ + if (long_options[option_index].flag != 0) + break; + INFO ("option %s", long_options[option_index].name); + if (optarg) + INFO (" with arg %s", optarg); + INFO ("\n"); + break; + case 'p': *print_symtab = 1; break; + case 'h': print_help(); exit(1); break; + case 'V': *verbose = 1; break; + case 'Q': *quiet = 1; break; + case 'S': *shady = 1; break; + case 'n': *dry_run = 1; break; + case 's': *strip_debug = 1; break; + case 'o': SET_STRING_OPTION(outfile); break; + case 'f': SET_STRING_OPTION(symsfile); break; + case '?': + /* getopt_long already printed an error message. */ + break; + +#undef SET_STRING_OPTION + + default: + FAILIF(1, "Unknown option"); + } + } + + return optind; +} diff --git a/tools/soslim/cmdline.h b/tools/soslim/cmdline.h new file mode 100644 index 0000000..bfc431e --- /dev/null +++ b/tools/soslim/cmdline.h @@ -0,0 +1,16 @@ +#ifndef CMDLINE_H +#define CMDLINE_H + +void print_help(void); + +int get_options(int argc, char **argv, + char **outfile, + char **symsfile, + int *print_symtab, + int *verbose, + int *quiet, + int *shady, + int *dry_run, + int *strip_debug); + +#endif/*CMDLINE_H*/ diff --git a/tools/soslim/common.c b/tools/soslim/common.c new file mode 100644 index 0000000..b90cf41 --- /dev/null +++ b/tools/soslim/common.c @@ -0,0 +1,35 @@ +#include <stdlib.h> +#include <common.h> +#include <debug.h> + +void map_over_sections(Elf *elf, + section_match_fn_t match, + void *user_data) +{ + Elf_Scn* section = NULL; + while ((section = elf_nextscn(elf, section)) != NULL) { + if (match(elf, section, user_data)) + return; + } +} + +void map_over_segments(Elf *elf, + segment_match_fn_t match, + void *user_data) +{ + Elf32_Ehdr *ehdr; + Elf32_Phdr *phdr; + int index; + + ehdr = elf32_getehdr(elf); + phdr = elf32_getphdr(elf); + + INFO("Scanning over %d program segments...\n", + ehdr->e_phnum); + + for (index = ehdr->e_phnum; index; index--) { + if (match(elf, phdr++, user_data)) + return; + } +} + diff --git a/tools/soslim/common.h b/tools/soslim/common.h new file mode 100644 index 0000000..dacf930 --- /dev/null +++ b/tools/soslim/common.h @@ -0,0 +1,49 @@ +#ifndef COMMON_H +#define COMMON_H + +#include <libelf.h> +#include <elf.h> + +#define unlikely(expr) __builtin_expect (expr, 0) +#define likely(expr) __builtin_expect (expr, 1) + +#define MIN(a,b) ((a)<(b)?(a):(b)) /* no side effects in arguments allowed! */ + +typedef int (*section_match_fn_t)(Elf *, Elf_Scn *, void *); +void map_over_sections(Elf *, section_match_fn_t, void *); + +typedef int (*segment_match_fn_t)(Elf *, Elf32_Phdr *, void *); +void map_over_segments(Elf *, segment_match_fn_t, void *); + +typedef struct { + Elf_Scn *sect; + Elf32_Shdr *hdr; + Elf_Data *data; + size_t index; +} section_info_t; + +static inline void get_section_info(Elf_Scn *sect, section_info_t *info) +{ + info->sect = sect; + info->data = elf_getdata(sect, 0); + info->hdr = elf32_getshdr(sect); + info->index = elf_ndxscn(sect); +} + +static inline int is_host_little(void) +{ + short val = 0x10; + return ((char *)&val)[0] != 0; +} + +static inline long switch_endianness(long val) +{ + long newval; + ((char *)&newval)[3] = ((char *)&val)[0]; + ((char *)&newval)[2] = ((char *)&val)[1]; + ((char *)&newval)[1] = ((char *)&val)[2]; + ((char *)&newval)[0] = ((char *)&val)[3]; + return newval; +} + +#endif/*COMMON_H*/ diff --git a/tools/soslim/debug.c b/tools/soslim/debug.c new file mode 100644 index 0000000..b8365af --- /dev/null +++ b/tools/soslim/debug.c @@ -0,0 +1,40 @@ +#include <debug.h> +#include <stdio.h> +#include <ctype.h> + +#if 0 + +#define NUM_COLS (32) + +int dump_hex_buffer(FILE *s, void *b, size_t len, size_t elsize) { + int num_nonprintable = 0; + int i, last; + char *pchr = (char *)b; + fputc('\n', s); + for (i = last = 0; i < len; i++) { + if (!elsize) { + if (i && !(i % 4)) fprintf(s, " "); + if (i && !(i % 8)) fprintf(s, " "); + } else { + if (i && !(i % elsize)) fprintf(s, " "); + } + + if (i && !(i % NUM_COLS)) { + while (last < i) { + if (isprint(pchr[last])) + fputc(pchr[last], s); + else { + fputc('.', s); + num_nonprintable++; + } + last++; + } + fprintf(s, " (%d)\n", i); + } + fprintf(s, "%02x", (unsigned char)pchr[i]); + } + if (i && (i % NUM_COLS)) fputs("\n", s); + return num_nonprintable; +} + +#endif diff --git a/tools/soslim/debug.h b/tools/soslim/debug.h new file mode 100644 index 0000000..e7a2f9a --- /dev/null +++ b/tools/soslim/debug.h @@ -0,0 +1,88 @@ +#ifndef DEBUG_H +#define DEBUG_H + +#include <stdlib.h> +#include <stdio.h> +#include <common.h> + +#ifdef DEBUG + + #define FAILIF(cond, msg...) do { \ + if (unlikely(cond)) { \ + fprintf(stderr, "%s(%d): ", __FILE__, __LINE__); \ + fprintf(stderr, ##msg); \ + exit(1); \ + } \ +} while(0) + +/* Debug enabled */ + #define ASSERT(x) do { \ + if (unlikely(!(x))) { \ + fprintf(stderr, \ + "ASSERTION FAILURE %s:%d: [%s]\n", \ + __FILE__, __LINE__, #x); \ + exit(1); \ + } \ +} while(0) + +#else + + #define FAILIF(cond, msg...) do { \ + if (unlikely(cond)) { \ + fprintf(stderr, ##msg); \ + exit(1); \ + } \ +} while(0) + +/* No debug */ + #define ASSERT(x) do { } while(0) + +#endif/* DEBUG */ + +#define FAILIF_LIBELF(cond, function) \ + FAILIF(cond, "%s(): %s\n", #function, elf_errmsg(elf_errno())); + +static inline void *MALLOC(unsigned int size) { + void *m = malloc(size); + FAILIF(NULL == m, "malloc(%d) failed!\n", size); + return m; +} + +static inline void *CALLOC(unsigned int num_entries, unsigned int entry_size) { + void *m = calloc(num_entries, entry_size); + FAILIF(NULL == m, "calloc(%d, %d) failed!\n", num_entries, entry_size); + return m; +} + +static inline void *REALLOC(void *ptr, unsigned int size) { + void *m = realloc(ptr, size); + FAILIF(NULL == m, "realloc(%p, %d) failed!\n", ptr, size); + return m; +} + +static inline void FREE(void *ptr) { + free(ptr); +} + +static inline void FREEIF(void *ptr) { + if (ptr) FREE(ptr); +} + +#define PRINT(x...) do { \ + extern int quiet_flag; \ + if(likely(!quiet_flag)) \ + fprintf(stdout, ##x); \ +} while(0) + +#define ERROR(x...) fprintf(stderr, ##x) + +#define INFO(x...) do { \ + extern int verbose_flag; \ + if(unlikely(verbose_flag)) \ + fprintf(stdout, ##x); \ +} while(0) + +/* Prints a hex and ASCII dump of the selected buffer to the selected stream. */ +int dump_hex_buffer(FILE *s, void *b, size_t l, size_t elsize); + +#endif/*DEBUG_H*/ diff --git a/tools/soslim/main.c b/tools/soslim/main.c new file mode 100644 index 0000000..fa5a315 --- /dev/null +++ b/tools/soslim/main.c @@ -0,0 +1,360 @@ +/* TODO: + 1. check the ARM EABI version--this works for versions 1 and 2. + 2. use a more-intelligent approach to finding the symbol table, symbol-string + table, and the .dynamic section. + 3. fix the determination of the host and ELF-file endianness + 4. write the help screen +*/ + +#include <stdio.h> +#include <common.h> +#include <debug.h> +#include <hash.h> +#include <libelf.h> +#include <elf.h> +#include <gelf.h> +#include <cmdline.h> +#include <string.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <soslim.h> +#include <symfilter.h> +#ifdef SUPPORT_ANDROID_PRELINK_TAGS +#include <prelink_info.h> +#endif + +/* Flag set by --verbose. This variable is global as it is accessed by the + macro INFO() in multiple compilation unites. */ +int verbose_flag = 0; +/* Flag set by --quiet. This variable is global as it is accessed by the + macro PRINT() in multiple compilation unites. */ +int quiet_flag = 0; +static void print_dynamic_symbols(Elf *elf, const char *symtab_name); + +int main(int argc, char **argv) +{ + int elf_fd = -1, newelf_fd = -1; + Elf *elf = NULL, *newelf = NULL; + char *infile = NULL; + char *outfile = NULL; + char *symsfile_name = NULL; + int print_symtab = 0; + int shady = 0; + int dry_run = 0; + int strip_debug = 0; + + /* Do not issue INFO() statements before you call get_options() to set + the verbose flag as necessary. + */ + + int first = get_options(argc, argv, + &outfile, + &symsfile_name, + &print_symtab, + &verbose_flag, + &quiet_flag, + &shady, + &dry_run, + &strip_debug); + + if ((print_symtab && (first == argc)) || + (!print_symtab && first + 1 != argc)) { + print_help(); + FAILIF(1, "You must specify an input ELF file!\n"); + } + FAILIF(print_symtab && (outfile || symsfile_name || shady), + "You cannot provide --print and --outfile, --filter options, or " + "--shady simultaneously!\n"); + FAILIF(dry_run && outfile, + "You cannot have a dry run and output a file at the same time."); + + /* Check to see whether the ELF library is current. */ + FAILIF (elf_version(EV_CURRENT) == EV_NONE, "libelf is out of date!\n"); + + if (print_symtab) { + + while (first < argc) { + infile = argv[first++]; + + INFO("Opening %s...\n", infile); + elf_fd = open(infile, O_RDONLY); + FAILIF(elf_fd < 0, "open(%s): %s (%d)\n", + infile, + strerror(errno), + errno); + INFO("Calling elf_begin(%s)...\n", infile); + elf = elf_begin(elf_fd, ELF_C_READ, NULL); + FAILIF_LIBELF(elf == NULL, elf_begin); + + /* libelf can recognize COFF and A.OUT formats, but we handle only + ELF. */ + FAILIF(elf_kind(elf) != ELF_K_ELF, + "Input file %s is not in ELF format!\n", + infile); + + /* Make sure this is a shared library or an executable. */ + { + GElf_Ehdr elf_hdr; + INFO("Making sure %s is a shared library or an executable.\n", + infile); + FAILIF_LIBELF(0 == gelf_getehdr(elf, &elf_hdr), gelf_getehdr); + FAILIF(elf_hdr.e_type != ET_DYN && + elf_hdr.e_type != ET_EXEC, + "%s must be a shared library or an executable " + "(elf type is %d).\n", + infile, + elf_hdr.e_type); + } + + print_dynamic_symbols(elf, infile); + + FAILIF_LIBELF(elf_end(elf), elf_end); + FAILIF(close(elf_fd) < 0, "Could not close file %s: %s (%d)!\n", + infile, strerror(errno), errno); + } + } + else { + int elf_fd = -1; + Elf *elf = NULL; + infile = argv[first]; + + INFO("Opening %s...\n", infile); + elf_fd = open(infile, ((outfile == NULL && dry_run == 0) ? O_RDWR : O_RDONLY)); + FAILIF(elf_fd < 0, "open(%s): %s (%d)\n", + infile, + strerror(errno), + errno); + INFO("Calling elf_begin(%s)...\n", infile); + elf = elf_begin(elf_fd, + ((outfile == NULL && dry_run == 0) ? ELF_C_RDWR : ELF_C_READ), + NULL); + FAILIF_LIBELF(elf == NULL, elf_begin); + + /* libelf can recognize COFF and A.OUT formats, but we handle only ELF. */ + FAILIF(elf_kind(elf) != ELF_K_ELF, + "Input file %s is not in ELF format!\n", + infile); + + /* We run a better check in adjust_elf() itself. It is permissible to call adjust_elf() + on an executable if we are only stripping sections from the executable, not rearranging + or moving sections. + */ + if (0) { + /* Make sure this is a shared library. */ + GElf_Ehdr elf_hdr; + INFO("Making sure %s is a shared library...\n", infile); + FAILIF_LIBELF(0 == gelf_getehdr(elf, &elf_hdr), gelf_getehdr); + FAILIF(elf_hdr.e_type != ET_DYN, + "%s must be a shared library (elf type is %d, expecting %d).\n", + infile, + elf_hdr.e_type, + ET_DYN); + } + + if (outfile != NULL) { + ASSERT(!dry_run); + struct stat st; + FAILIF(fstat (elf_fd, &st) != 0, + "Cannot stat input file %s: %s (%d)!\n", + infile, strerror(errno), errno); + newelf_fd = open (outfile, O_RDWR | O_CREAT | O_TRUNC, + st.st_mode & ACCESSPERMS); + FAILIF(newelf_fd < 0, "Cannot create file %s: %s (%d)!\n", + outfile, strerror(errno), errno); + INFO("Output file is [%s].\n", outfile); + newelf = elf_begin(newelf_fd, ELF_C_WRITE_MMAP, NULL); + } else { + INFO("Modifying [%s] in-place.\n", infile); + newelf = elf_clone(elf, ELF_C_EMPTY); + } + + symfilter_t symfilter; + + symfilter.symbols_to_keep = NULL; + symfilter.num_symbols_to_keep = 0; + if (symsfile_name) { + /* Make sure that the file is not empty. */ + struct stat s; + FAILIF(stat(symsfile_name, &s) < 0, + "Cannot stat file %s.\n", symsfile_name); + if (s.st_size) { + INFO("Building symbol filter.\n"); + build_symfilter(symsfile_name, elf, &symfilter, s.st_size); + } + else INFO("Not building symbol filter, filter file is empty.\n"); + } +#ifdef SUPPORT_ANDROID_PRELINK_TAGS + int prelinked = 0; + int elf_little; /* valid if prelinked != 0 */ + long prelink_addr; /* valid if prelinked != 0 */ +#endif + clone_elf(elf, newelf, + infile, outfile, + symfilter.symbols_to_keep, + symfilter.num_symbols_to_keep, + shady +#ifdef SUPPORT_ANDROID_PRELINK_TAGS + , &prelinked, + &elf_little, + &prelink_addr +#endif + , + true, /* rebuild the section-header-strings table */ + strip_debug, + dry_run); + + if (symsfile_name && symfilter.symbols_to_keep != NULL) { + destroy_symfilter(&symfilter); + } + + if (outfile != NULL) INFO("Closing %s...\n", outfile); + FAILIF_LIBELF(elf_end (newelf) != 0, elf_end); + FAILIF(newelf_fd >= 0 && close(newelf_fd) < 0, + "Could not close file %s: %s (%d)!\n", + outfile, strerror(errno), errno); + + INFO("Closing %s...\n", infile); + FAILIF_LIBELF(elf_end(elf), elf_end); + FAILIF(close(elf_fd) < 0, "Could not close file %s: %s (%d)!\n", + infile, strerror(errno), errno); + +#ifdef SUPPORT_ANDROID_PRELINK_TAGS + if (prelinked) { + INFO("File is prelinked, putting prelink TAG back in place.\n"); + setup_prelink_info(outfile != NULL ? outfile : infile, + elf_little, + prelink_addr); + } +#endif + } + + FREEIF(outfile); + return 0; +} + +static void print_dynamic_symbols(Elf *elf, const char *file) +{ + Elf_Scn *scn = NULL; + GElf_Shdr shdr; + + GElf_Ehdr ehdr; + FAILIF_LIBELF(0 == gelf_getehdr(elf, &ehdr), gelf_getehdr); + while ((scn = elf_nextscn (elf, scn)) != NULL) { + FAILIF_LIBELF(NULL == gelf_getshdr(scn, &shdr), gelf_getshdr); + if (SHT_DYNSYM == shdr.sh_type) { + /* This failure is too restrictive. There is no reason why + the symbol table couldn't be called something else, but + there is a standard name, and chances are that if we don't + see it, there's something wrong. + */ + size_t shstrndx; + FAILIF_LIBELF(elf_getshstrndx(elf, &shstrndx) < 0, + elf_getshstrndx); + /* Now print the symbols. */ + { + Elf_Data *symdata; + size_t elsize; + symdata = elf_getdata (scn, NULL); /* get the symbol data */ + FAILIF_LIBELF(NULL == symdata, elf_getdata); + /* Get the number of section. We need to compare agains this + value for symbols that have special info in their section + references */ + size_t shnum; + FAILIF_LIBELF(elf_getshnum (elf, &shnum) < 0, elf_getshnum); + /* Retrieve the size of a symbol entry */ + elsize = gelf_fsize(elf, ELF_T_SYM, 1, ehdr.e_version); + + size_t index; + for (index = 0; index < symdata->d_size / elsize; index++) { + GElf_Sym sym_mem; + GElf_Sym *sym; + /* Get the symbol. */ + sym = gelf_getsymshndx (symdata, NULL, + index, &sym_mem, NULL); + FAILIF_LIBELF(sym == NULL, gelf_getsymshndx); + /* Print the symbol. */ + char bind = '?'; + switch(ELF32_ST_BIND(sym->st_info)) + { + case STB_LOCAL: bind = 'l'; break; + case STB_GLOBAL: bind = 'g'; break; + case STB_WEAK: bind = 'w'; break; + default: break; + } + char type = '?'; + switch(ELF32_ST_TYPE(sym->st_info)) + { + case STT_NOTYPE: /* Symbol type is unspecified */ + type = '?'; + break; + case STT_OBJECT: /* Symbol is a data object */ + type = 'o'; + break; + case STT_FUNC: /* Symbol is a code object */ + type = 'f'; + break; + case STT_SECTION:/* Symbol associated with a section */ + type = 's'; + break; + case STT_FILE: /* Symbol's name is file name */ + type = 'f'; + break; + case STT_COMMON: /* Symbol is a common data object */ + type = 'c'; + break; + case STT_TLS: /* Symbol is thread-local data object*/ + type = 't'; + break; + } + { + int till_lineno; + int lineno; + const char *section_name = "(unknown)"; + FAILIF(sym->st_shndx == SHN_XINDEX, + "Can't handle symbol's st_shndx == SHN_XINDEX!\n"); + if (sym->st_shndx != SHN_UNDEF && + sym->st_shndx < shnum) { + Elf_Scn *symscn = elf_getscn(elf, sym->st_shndx); + FAILIF_LIBELF(NULL == symscn, elf_getscn); + GElf_Shdr symscn_shdr; + FAILIF_LIBELF(NULL == gelf_getshdr(symscn, + &symscn_shdr), + gelf_getshdr); + section_name = elf_strptr(elf, shstrndx, + symscn_shdr.sh_name); + } + else if (sym->st_shndx == SHN_ABS) { + section_name = "SHN_ABS"; + } + else if (sym->st_shndx == SHN_COMMON) { + section_name = "SHN_COMMON"; + } + else if (sym->st_shndx == SHN_UNDEF) { + section_name = "(undefined)"; + } + /* value size binding type section symname */ + PRINT("%-15s %8d: %08llx %08llx %c%c %5d %n%s%n", + file, + index, + sym->st_value, sym->st_size, bind, type, + sym->st_shndx, + &till_lineno, + section_name, + &lineno); + lineno -= till_lineno; + /* Create padding for section names of 15 chars. + This limit is somewhat arbitratry. */ + while (lineno++ < 15) PRINT(" "); + PRINT("(%d) %s\n", + sym->st_name, + elf_strptr(elf, shdr.sh_link, sym->st_name)); + } + } + } + } /* if (shdr.sh_type = SHT_DYNSYM) */ + } /* while ((scn = elf_nextscn (elf, scn)) != NULL) */ +} diff --git a/tools/soslim/prelink_info.c b/tools/soslim/prelink_info.c new file mode 100644 index 0000000..36516b1 --- /dev/null +++ b/tools/soslim/prelink_info.c @@ -0,0 +1,106 @@ +#ifdef SUPPORT_ANDROID_PRELINK_TAGS + +#include <sys/types.h> +#include <fcntl.h> +#include <sys/types.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <prelink_info.h> +#include <debug.h> +#include <common.h> + +typedef struct { + uint32_t mmap_addr; + char tag[4]; /* 'P', 'R', 'E', ' ' */ +} prelink_info_t __attribute__((packed)); + +static inline void set_prelink(long *prelink_addr, + int elf_little, + prelink_info_t *info) +{ + FAILIF(sizeof(prelink_info_t) != 8, "Unexpected sizeof(prelink_info_t) == %d!\n", sizeof(prelink_info_t)); + if (prelink_addr) { + if (!(elf_little ^ is_host_little())) { + /* Same endianness */ + *prelink_addr = info->mmap_addr; + } + else { + /* Different endianness */ + *prelink_addr = switch_endianness(info->mmap_addr); + } + } +} + +int check_prelinked(const char *fname, int elf_little, long *prelink_addr) +{ + FAILIF(sizeof(prelink_info_t) != 8, "Unexpected sizeof(prelink_info_t) == %d!\n", sizeof(prelink_info_t)); + int fd = open(fname, O_RDONLY); + FAILIF(fd < 0, "open(%s, O_RDONLY): %s (%d)!\n", + fname, strerror(errno), errno); + off_t end = lseek(fd, 0, SEEK_END); + + int nr = sizeof(prelink_info_t); + + off_t sz = lseek(fd, -nr, SEEK_CUR); + ASSERT((long)(end - sz) == (long)nr); + FAILIF(sz == (off_t)-1, + "lseek(%d, 0, SEEK_END): %s (%d)!\n", + fd, strerror(errno), errno); + + prelink_info_t info; + int num_read = read(fd, &info, nr); + FAILIF(num_read < 0, + "read(%d, &info, sizeof(prelink_info_t)): %s (%d)!\n", + fd, strerror(errno), errno); + FAILIF(num_read != sizeof(info), + "read(%d, &info, sizeof(prelink_info_t)): did not read %d bytes as " + "expected (read %d)!\n", + fd, sizeof(info), num_read); + + int prelinked = 0; + if (!strncmp(info.tag, "PRE ", 4)) { + set_prelink(prelink_addr, elf_little, &info); + prelinked = 1; + } + FAILIF(close(fd) < 0, "close(%d): %s (%d)!\n", fd, strerror(errno), errno); + return prelinked; +} + +void setup_prelink_info(const char *fname, int elf_little, long base) +{ + FAILIF(sizeof(prelink_info_t) != 8, "Unexpected sizeof(prelink_info_t) == %d!\n", sizeof(prelink_info_t)); + int fd = open(fname, O_WRONLY); + FAILIF(fd < 0, + "open(%s, O_WRONLY): %s (%d)\n" , + fname, strerror(errno), errno); + prelink_info_t info; + off_t sz = lseek(fd, 0, SEEK_END); + FAILIF(sz == (off_t)-1, + "lseek(%d, 0, SEEK_END): %s (%d)!\n", + fd, strerror(errno), errno); + + if (!(elf_little ^ is_host_little())) { + /* Same endianness */ + INFO("Host and ELF file [%s] have same endianness.\n", fname); + info.mmap_addr = base; + } + else { + /* Different endianness */ + INFO("Host and ELF file [%s] have different endianness.\n", fname); + info.mmap_addr = switch_endianness(base); + } + strncpy(info.tag, "PRE ", 4); + + int num_written = write(fd, &info, sizeof(info)); + FAILIF(num_written < 0, + "write(%d, &info, sizeof(info)): %s (%d)\n", + fd, strerror(errno), errno); + FAILIF(sizeof(info) != num_written, + "Could not write %d bytes (wrote only %d bytes) as expected!\n", + sizeof(info), num_written); + FAILIF(close(fd) < 0, "close(%d): %s (%d)!\n", fd, strerror(errno), errno); +} + +#endif /*SUPPORT_ANDROID_PRELINK_TAGS*/ diff --git a/tools/soslim/prelink_info.h b/tools/soslim/prelink_info.h new file mode 100644 index 0000000..e2787cb --- /dev/null +++ b/tools/soslim/prelink_info.h @@ -0,0 +1,9 @@ +#ifndef PRELINK_INFO_H +#define PRELINK_INFO_H +#ifdef SUPPORT_ANDROID_PRELINK_TAGS + +int check_prelinked(const char *fname, int elf_little, long *prelink_addr); +void setup_prelink_info(const char *fname, int elf_little, long base); + +#endif +#endif/*PRELINK_INFO_H*/ diff --git a/tools/soslim/soslim.c b/tools/soslim/soslim.c new file mode 100644 index 0000000..4e59c24 --- /dev/null +++ b/tools/soslim/soslim.c @@ -0,0 +1,528 @@ +#include <stdio.h> +//#include <common.h> +#include <debug.h> +#include <libelf.h> +#include <libebl.h> +#include <libebl_arm.h> +#include <elf.h> +#include <gelf.h> +#include <string.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#ifdef SUPPORT_ANDROID_PRELINK_TAGS +#include <prelink_info.h> +#endif + +#include <elfcopy.h> + +void clone_elf(Elf *elf, Elf *newelf, + const char *elf_name, + const char *newelf_name, + bool *sym_filter, int num_symbols, + int shady +#ifdef SUPPORT_ANDROID_PRELINK_TAGS + , int *prelinked, + int *elf_little, + long *prelink_addr +#endif + , bool rebuild_shstrtab, + bool strip_debug, + bool dry_run) +{ + GElf_Ehdr ehdr_mem, *ehdr; /* store ELF header of original library */ + size_t shstrndx; /* section-strings-section index */ + size_t shnum; /* number of sections in the original file */ + /* string table for section headers in new file */ + struct Ebl_Strtab *shst = NULL; + int dynamic_idx = -1; /* index in shdr_info[] of .dynamic section */ + int dynsym_idx = -1; /* index in shdr_info[] of dynamic symbol table + section */ + + int cnt; /* general-purpose counter */ + /* This flag is true when at least one section is dropped or when the + relative order of sections has changed, so that section indices in + the resulting file will be different from those in the original. */ + bool sections_dropped_or_rearranged; + Elf_Scn *scn; /* general-purpose section */ + size_t idx; /* general-purporse section index */ + + shdr_info_t *shdr_info = NULL; + int shdr_info_len = 0; + GElf_Phdr *phdr_info = NULL; + + /* Get the information from the old file. */ + ehdr = gelf_getehdr (elf, &ehdr_mem); + FAILIF_LIBELF(NULL == ehdr, gelf_getehdr); + + /* Create new program header for the elf file */ + FAILIF(gelf_newehdr (newelf, gelf_getclass (elf)) == 0 || + (ehdr->e_type != ET_REL && gelf_newphdr (newelf, + ehdr->e_phnum) == 0), + "Cannot create new file: %s", elf_errmsg (-1)); + +#ifdef SUPPORT_ANDROID_PRELINK_TAGS + ASSERT(prelinked); + ASSERT(prelink_addr); + ASSERT(elf_little); + *elf_little = (ehdr->e_ident[EI_DATA] == ELFDATA2LSB); + *prelinked = check_prelinked(elf_name, *elf_little, prelink_addr); +#endif + + INFO("\n\nCALCULATING MODIFICATIONS\n\n"); + + /* Copy out the old program header: notice that if the ELF file does not + have a program header, this loop won't execute. + */ + INFO("Copying ELF program header...\n"); + phdr_info = (GElf_Phdr *)CALLOC(ehdr->e_phnum, sizeof(GElf_Phdr)); + for (cnt = 0; cnt < ehdr->e_phnum; ++cnt) { + INFO("\tRetrieving entry %d\n", cnt); + FAILIF_LIBELF(NULL == gelf_getphdr(elf, cnt, phdr_info + cnt), + gelf_getphdr); + /* -- we update the header at the end + FAILIF_LIBELF(gelf_update_phdr (newelf, cnt, phdr_info + cnt) == 0, + gelf_update_phdr); + */ + } + + /* Get the section-header strings section. This section contains the + strings used to name the other sections. */ + FAILIF_LIBELF(elf_getshstrndx(elf, &shstrndx) < 0, elf_getshstrndx); + + /* Get the number of sections. */ + FAILIF_LIBELF(elf_getshnum (elf, &shnum) < 0, elf_getshnum); + INFO("Original ELF file has %d sections.\n", shnum); + + /* Allocate the section-header-info buffer. We allocate one more entry + for the section-strings section because we regenerate that one and + place it at the very end of the file. Note that just because we create + an extra entry in the shdr_info array, it does not mean that we create + one more section the header. We just mark the old section for removal + and create one as the last section. + */ + INFO("Allocating section-header info structure (%d) bytes...\n", + shnum*sizeof (shdr_info_t)); + shdr_info_len = rebuild_shstrtab ? shnum + 1 : shnum; + shdr_info = (shdr_info_t *)CALLOC(shdr_info_len, sizeof (shdr_info_t)); + + /* Iterate over all the sections and initialize the internal section-info + array... + */ + INFO("Initializing section-header info structure...\n"); + /* Gather information about the sections in this file. */ + scn = NULL; + cnt = 1; + while ((scn = elf_nextscn (elf, scn)) != NULL) { + ASSERT(elf_ndxscn(scn) == cnt); + shdr_info[cnt].scn = scn; + FAILIF_LIBELF(NULL == gelf_getshdr(scn, &shdr_info[cnt].shdr), + gelf_getshdr); + + /* Get the name of the section. */ + shdr_info[cnt].name = elf_strptr (elf, shstrndx, + shdr_info[cnt].shdr.sh_name); + + INFO("\tname: %s\n", shdr_info[cnt].name); + FAILIF(shdr_info[cnt].name == NULL, + "Malformed file: section %d name is null\n", + cnt); + + /* Mark them as present but not yet investigated. By "investigating" + sections, we mean that we check to see if by stripping other + sections, the sections under investigation will be compromised. For + example, if we are removing a section of code, then we want to make + sure that the symbol table does not contain symbols that refer to + this code, so we investigate the symbol table. If we do find such + symbols, we will not strip the code section. + */ + shdr_info[cnt].idx = 1; + + /* Remember the shdr.sh_link value. We need to remember this value + for those sections that refer to other sections. For example, + we need to remember it for relocation-entry sections, because if + we modify the symbol table that a relocation-entry section is + relative to, then we need to patch the relocation section. By the + time we get to deciding whether we need to patch the relocation + section, we will have overwritten its header's sh_link field with + a new value. + */ + shdr_info[cnt].old_shdr = shdr_info[cnt].shdr; + INFO("\t\toriginal sh_link: %08d\n", shdr_info[cnt].old_shdr.sh_link); + INFO("\t\toriginal sh_addr: %lld\n", shdr_info[cnt].old_shdr.sh_addr); + INFO("\t\toriginal sh_offset: %lld\n", + shdr_info[cnt].old_shdr.sh_offset); + INFO("\t\toriginal sh_size: %lld\n", shdr_info[cnt].old_shdr.sh_size); + + if (shdr_info[cnt].shdr.sh_type == SHT_DYNAMIC) { + INFO("\t\tthis is the SHT_DYNAMIC section [%s] at index %d\n", + shdr_info[cnt].name, + cnt); + dynamic_idx = cnt; + } + else if (shdr_info[cnt].shdr.sh_type == SHT_DYNSYM) { + INFO("\t\tthis is the SHT_DYNSYM section [%s] at index %d\n", + shdr_info[cnt].name, + cnt); + dynsym_idx = cnt; + } + + FAILIF(shdr_info[cnt].shdr.sh_type == SHT_SYMTAB_SHNDX, + "Cannot handle sh_type SHT_SYMTAB_SHNDX!\n"); + FAILIF(shdr_info[cnt].shdr.sh_type == SHT_GROUP, + "Cannot handle sh_type SHT_GROUP!\n"); + FAILIF(shdr_info[cnt].shdr.sh_type == SHT_GNU_versym, + "Cannot handle sh_type SHT_GNU_versym!\n"); + + /* Increment the counter. */ + ++cnt; + } /* while */ + + /* Get the EBL handling. */ + Ebl *ebl = ebl_openbackend (elf); + FAILIF_LIBELF(NULL == ebl, ebl_openbackend); + FAILIF_LIBELF(0 != arm_init(elf, ehdr->e_machine, ebl, sizeof(Ebl)), + arm_init); + + if (strip_debug) { + + /* This will actually strip more than just sections. It will strip + anything not essential to running the image. + */ + + INFO("Finding debug sections to strip.\n"); + + /* Now determine which sections can go away. The general rule is that + all sections which are not used at runtime are stripped out. But + there are a few exceptions: + + - special sections named ".comment" and ".note" are kept + - OS or architecture specific sections are kept since we might not + know how to handle them + - if a section is referred to from a section which is not removed + in the sh_link or sh_info element it cannot be removed either + */ + for (cnt = 1; cnt < shnum; ++cnt) { + /* Check whether the section can be removed. */ + if (SECTION_STRIP_P (ebl, elf, ehdr, &shdr_info[cnt].shdr, + shdr_info[cnt].name, + 1, /* remove .comment sections */ + 1 /* remove all debug sections */) || + /* The macro above is broken--check for .comment explicitly */ + !strcmp(".comment", shdr_info[cnt].name) +#ifdef ARM_SPECIFIC_HACKS + || + /* We ignore this section, that's why we can remove it. */ + !strcmp(".stack", shdr_info[cnt].name) +#endif + ) + { + /* For now assume this section will be removed. */ + INFO("Section [%s] will be stripped from image.\n", + shdr_info[cnt].name); + shdr_info[cnt].idx = 0; + } +#ifdef STRIP_STATIC_SYMBOLS + else if (shdr_info[cnt].shdr.sh_type == SHT_SYMTAB) { + /* Mark the static symbol table for removal */ + INFO("Section [%s] (static symbol table) will be stripped from image.\n", + shdr_info[cnt].name); + shdr_info[cnt].idx = 0; + if (shdr_info[shdr_info[cnt].shdr.sh_link].shdr.sh_type == + SHT_STRTAB) + { + /* Mark the symbol table's string table for removal. */ + INFO("Section [%s] (static symbol-string table) will be stripped from image.\n", + shdr_info[cnt].name); + shdr_info[shdr_info[cnt].shdr.sh_link].idx = 0; + } + else { + ERROR("Expecting the sh_link field of a symbol table to point to" + " associated symbol-strings table! This is not mandated by" + " the standard, but is a common practice and the only way " + " to know for sure which strings table corresponds to which" + " symbol table!\n"); + } + } +#endif + } + + /* Mark the SHT_NULL section as handled. */ + shdr_info[0].idx = 2; + + /* Handle exceptions: section groups and cross-references. We might have + to repeat this a few times since the resetting of the flag might + propagate. + */ + int exceptions_pass = 0; + bool changes; + do { + changes = false; + INFO("\nHandling exceptions, pass %d\n\n", exceptions_pass++); + for (cnt = 1; cnt < shnum; ++cnt) { + if (shdr_info[cnt].idx == 0) { + /* If a relocation section is marked as being removed but the + section it is relocating is not, then do not remove the + relocation section. + */ + if ((shdr_info[cnt].shdr.sh_type == SHT_REL + || shdr_info[cnt].shdr.sh_type == SHT_RELA) + && shdr_info[shdr_info[cnt].shdr.sh_info].idx != 0) { + PRINT("\tSection [%s] will not be removed because the " + "section it is relocating (%s) stays.\n", + shdr_info[cnt].name, + shdr_info[shdr_info[cnt].shdr.sh_info].name); + } + } + if (shdr_info[cnt].idx == 1) { + INFO("Processing section [%s]...\n", shdr_info[cnt].name); + + /* The content of symbol tables we don't remove must not + reference any section which we do remove. Otherwise + we cannot remove the referred section. + */ + if (shdr_info[cnt].shdr.sh_type == SHT_DYNSYM || + shdr_info[cnt].shdr.sh_type == SHT_SYMTAB) + { + Elf_Data *symdata; + size_t elsize; + + INFO("\tSection [%s] is a symbol table that's not being" + " removed.\n\tChecking to make sure that no symbols" + " refer to sections that are being removed.\n", + shdr_info[cnt].name); + + /* Make sure the data is loaded. */ + symdata = elf_getdata (shdr_info[cnt].scn, NULL); + FAILIF_LIBELF(NULL == symdata, elf_getdata); + + /* Go through all symbols and make sure the section they + reference is not removed. */ + elsize = gelf_fsize (elf, ELF_T_SYM, 1, ehdr->e_version); + + /* Check the length of the dynamic-symbol filter. */ + FAILIF(sym_filter != NULL && + num_symbols != symdata->d_size / elsize, + "Length of dynsym filter (%d) must equal the number" + " of dynamic symbols (%d)!\n", + num_symbols, + symdata->d_size / elsize); + + size_t inner; + for (inner = 0; + inner < symdata->d_size / elsize; + ++inner) + { + GElf_Sym sym_mem; + GElf_Sym *sym; + size_t scnidx; + + sym = gelf_getsymshndx (symdata, NULL, + inner, &sym_mem, NULL); + FAILIF_LIBELF(sym == NULL, gelf_getsymshndx); + + scnidx = sym->st_shndx; + FAILIF(scnidx == SHN_XINDEX, + "Can't handle SHN_XINDEX!\n"); + if (scnidx == SHN_UNDEF || + scnidx >= shnum || + (scnidx >= SHN_LORESERVE && + scnidx <= SHN_HIRESERVE) || + GELF_ST_TYPE (sym->st_info) == STT_SECTION) + { + continue; + } + + /* If the symbol is going to be thrown and it is a + global or weak symbol that is defined (not imported), + then continue. Since the symbol is going away, we + do not care whether it refers to a section that is + also going away. + */ + if (sym_filter && !sym_filter[inner]) + { + bool global_or_weak = + ELF32_ST_BIND(sym->st_info) == STB_GLOBAL || + ELF32_ST_BIND(sym->st_info) == STB_WEAK; + if (!global_or_weak && sym->st_shndx != SHN_UNDEF) + continue; + } + + /* -- far too much output + INFO("\t\t\tSymbol [%s] (%d)\n", + elf_strptr(elf, + shdr_info[cnt].shdr.sh_link, + sym->st_name), + shdr_info[cnt].shdr.sh_info); + */ + + if (shdr_info[scnidx].idx == 0) + { + PRINT("\t\t\tSymbol [%s] refers to section [%s], " + "which is being removed. Will keep that " + "section.\n", + elf_strptr(elf, + shdr_info[cnt].shdr.sh_link, + sym->st_name), + shdr_info[scnidx].name); + /* Mark this section as used. */ + shdr_info[scnidx].idx = 1; + changes |= scnidx < cnt; + } + } /* for each symbol */ + } /* section type is SHT_DYNSYM or SHT_SYMTAB */ + /* Cross referencing happens: + - for the cases the ELF specification says. That are + + SHT_DYNAMIC in sh_link to string table + + SHT_HASH in sh_link to symbol table + + SHT_REL and SHT_RELA in sh_link to symbol table + + SHT_SYMTAB and SHT_DYNSYM in sh_link to string table + + SHT_GROUP in sh_link to symbol table + + SHT_SYMTAB_SHNDX in sh_link to symbol table + Other (OS or architecture-specific) sections might as + well use this field so we process it unconditionally. + - references inside section groups + - specially marked references in sh_info if the SHF_INFO_LINK + flag is set + */ + + if (shdr_info[shdr_info[cnt].shdr.sh_link].idx == 0) { + shdr_info[shdr_info[cnt].shdr.sh_link].idx = 1; + changes |= shdr_info[cnt].shdr.sh_link < cnt; + } + + /* Handle references through sh_info. */ + if (SH_INFO_LINK_P (&shdr_info[cnt].shdr) && + shdr_info[shdr_info[cnt].shdr.sh_info].idx == 0) { + PRINT("\tSection [%s] links to section [%s], which was " + "marked for removal--it will not be removed.\n", + shdr_info[cnt].name, + shdr_info[shdr_info[cnt].shdr.sh_info].name); + + shdr_info[shdr_info[cnt].shdr.sh_info].idx = 1; + changes |= shdr_info[cnt].shdr.sh_info < cnt; + } + + /* Mark the section as investigated. */ + shdr_info[cnt].idx = 2; + } /* if (shdr_info[cnt].idx == 1) */ + } /* for (cnt = 1; cnt < shnum; ++cnt) */ + } while (changes); + } + else { + INFO("Not stripping sections.\n"); + /* Mark the SHT_NULL section as handled. */ + shdr_info[0].idx = 2; + } + + /* Mark the section header string table as unused, we will create + a new one as the very last section in the new ELF file. + */ + shdr_info[shstrndx].idx = rebuild_shstrtab ? 0 : 2; + + /* We need a string table for the section headers. */ + FAILIF_LIBELF((shst = ebl_strtabinit (1 /* null-terminated */)) == NULL, + ebl_strtabinit); + + /* Assign new section numbers. */ + INFO("Creating new sections...\n"); + //shdr_info[0].idx = 0; + for (cnt = idx = 1; cnt < shnum; ++cnt) { + if (shdr_info[cnt].idx > 0) { + shdr_info[cnt].idx = idx++; + + /* Create a new section. */ + FAILIF_LIBELF((shdr_info[cnt].newscn = + elf_newscn(newelf)) == NULL, elf_newscn); + ASSERT(elf_ndxscn (shdr_info[cnt].newscn) == shdr_info[cnt].idx); + + /* Add this name to the section header string table. */ + shdr_info[cnt].se = ebl_strtabadd (shst, shdr_info[cnt].name, 0); + + INFO("\tsection [%s] (old offset %lld, old size %lld) will have index %d " + "(was %d).\n", + shdr_info[cnt].name, + shdr_info[cnt].old_shdr.sh_offset, + shdr_info[cnt].old_shdr.sh_size, + shdr_info[cnt].idx, + elf_ndxscn(shdr_info[cnt].scn)); + } else { + INFO("\tIgnoring section [%s] (offset %lld, size %lld, index %d), " + "it will be discarded.\n", + shdr_info[cnt].name, + shdr_info[cnt].shdr.sh_offset, + shdr_info[cnt].shdr.sh_size, + elf_ndxscn(shdr_info[cnt].scn)); + } + } /* for */ + + sections_dropped_or_rearranged = idx != cnt; + + Elf_Data *shstrtab_data = NULL; + +#if 0 + /* Fail if sections are being dropped or rearranged (except for moving shstrtab) or the + symbol filter is not empty, AND the file is an executable. + */ + FAILIF(((idx != cnt && !(cnt - idx == 1 && rebuild_shstrtab)) || sym_filter != NULL) && + ehdr->e_type != ET_DYN, + "You may not rearrange sections or strip symbols on an executable file!\n"); +#endif + + INFO("\n\nADJUSTING ELF FILE\n\n"); + + adjust_elf(elf, elf_name, + newelf, newelf_name, + ebl, + ehdr, /* store ELF header of original library */ + sym_filter, num_symbols, + shdr_info, shdr_info_len, + phdr_info, + idx, /* highest_scn_num */ + shnum, + shstrndx, + shst, + sections_dropped_or_rearranged, + dynamic_idx, /* index in shdr_info[] of .dynamic section */ + dynsym_idx, /* index in shdr_info[] of dynamic symbol table */ + shady, + &shstrtab_data, + ehdr->e_type == ET_DYN, /* adjust section ofsets only when the file is a shared library */ + rebuild_shstrtab); + + /* We have everything from the old file. */ + FAILIF_LIBELF(elf_cntl(elf, ELF_C_FDDONE) != 0, elf_cntl); + + /* The ELF library better follows our layout when this is not a + relocatable object file. */ + elf_flagelf (newelf, + ELF_C_SET, + (ehdr->e_type != ET_REL ? ELF_F_LAYOUT : 0)); + + /* Finally write the file. */ + FAILIF_LIBELF(!dry_run && elf_update(newelf, ELF_C_WRITE) == -1, elf_update); + + if (shdr_info != NULL) { + /* For some sections we might have created an table to map symbol + table indices. */ + for (cnt = 1; cnt < shdr_info_len; ++cnt) { + FREEIF(shdr_info[cnt].newsymidx); + FREEIF(shdr_info[cnt].symse); + if(shdr_info[cnt].dynsymst != NULL) + ebl_strtabfree (shdr_info[cnt].dynsymst); + } + /* Free the memory. */ + FREE (shdr_info); + } + FREEIF(phdr_info); + + ebl_closebackend(ebl); + + /* Free other resources. */ + if (shst != NULL) ebl_strtabfree (shst); + if (shstrtab_data != NULL) + FREEIF(shstrtab_data->d_buf); +} diff --git a/tools/soslim/soslim.h b/tools/soslim/soslim.h new file mode 100644 index 0000000..dfcb085 --- /dev/null +++ b/tools/soslim/soslim.h @@ -0,0 +1,32 @@ +#ifndef ELFCOPY_H +#define ELFCOPY_H + +#include <libelf.h> +#include <libebl.h> +#include <elf.h> +#include <gelf.h> + +/* +symbol_filter: + On input: symbol_filter[i] indicates whether to keep a symbol (1) or to + remove it from the symbol table. + On output: symbol_filter[i] indicates whether a symbol was removed (0) or + kept (1) in the symbol table. +*/ + +void clone_elf(Elf *elf, Elf *newelf, + const char *elf_name, + const char *newelf_name, + bool *symbol_filter, + int num_symbols, + int shady +#ifdef SUPPORT_ANDROID_PRELINK_TAGS + , int *prelinked, + int *elf_little, + long *prelink_addr +#endif + , bool rebuild_shstrtab, + bool strip_debug, + bool dry_run); + +#endif/*ELFCOPY_H*/ diff --git a/tools/soslim/symfilter.c b/tools/soslim/symfilter.c new file mode 100644 index 0000000..c21ab2e --- /dev/null +++ b/tools/soslim/symfilter.c @@ -0,0 +1,242 @@ +#include <debug.h> +#include <common.h> +#include <symfilter.h> +#include <hash.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <libelf.h> +#include <gelf.h> +#include <ctype.h> + +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +static int match_hash_table_section(Elf *elf, Elf_Scn *sect, void *data); +static int match_dynsym_section(Elf *elf, Elf_Scn *sect, void *data); + +void build_symfilter(const char *name, Elf *elf, symfilter_t *filter, + off_t fsize) +{ + char *line = NULL; + symfilter_list_t *symbol; + + FAILIF(NULL == name, + "You must provide a list of symbols to filter on!\n"); + + filter->num_symbols = 0; + filter->total_name_length = 0; + + /* Open the file. */ + INFO("Opening symbol-filter file %s...\n", name); + filter->fd = open(name, O_RDONLY); + FAILIF(filter->fd < 0, "open(%s): %s (%d)\n", + name, + strerror(errno), + errno); + + INFO("Symbol-filter file %s is %ld bytes long...\n", + name, + fsize); + filter->fsize = fsize; + + /* mmap the symbols file */ + filter->mmap = mmap(NULL, fsize, + PROT_READ | PROT_WRITE, MAP_PRIVATE, + filter->fd, 0); + FAILIF(MAP_FAILED == filter->mmap, + "mmap(NULL, %ld, PROT_READ, MAP_PRIVATE, %d, 0): %s (%d)\n", + fsize, + filter->fd, + strerror(errno), + errno); + INFO("Memory-mapped symbol-filter file at %p\n", filter->mmap); + + /* Make sure that the ELF file has a hash table. We will use the hash + table to look up symbols quickly. If the library does not have a hash- + table section, we can still do a linear scan, but the code for that is + not written, as practically every shared library has a hash table. + */ + + filter->symtab.sect = NULL; + map_over_sections(elf, match_dynsym_section, filter); + FAILIF(NULL == filter->symtab.sect, + "There is no dynamic-symbol table in this library.\n"); + filter->hash.sect = NULL; + map_over_sections(elf, match_hash_table_section, filter); + FAILIF(NULL == filter->hash.sect, + "There is no hash table in this library.\n"); + INFO("Hash table size 0x%lx, data size 0x%lx.\n", + (unsigned long)filter->hash.hdr->sh_size, + (unsigned long)filter->hash.data->d_size); + + INFO("Hash table file offset: 0x%x\n", filter->hash.hdr->sh_offset); + + GElf_Ehdr *ehdr, ehdr_mem; + ehdr = gelf_getehdr(elf, &ehdr_mem); + size_t symsize = gelf_fsize (elf, ELF_T_SYM, 1, ehdr->e_version); + ASSERT(symsize); + filter->num_symbols_to_keep = filter->symtab.data->d_size / symsize; + filter->symbols_to_keep = (bool *)CALLOC(filter->num_symbols_to_keep, + sizeof(bool)); + + /* Build the symbol-name chain. */ + INFO("Building symbol list...\n"); + + line = (char *)filter->mmap; + + filter->symbols = NULL; +#define NOT_DONE ((off_t)(line - (char *)filter->mmap) < fsize) + do { + char *name = line; + + /* Advance to the next line. We seek out spaces or new lines. At the + first space or newline character we find, we place a '\0', and + continue till we've consumed the line. For new lines, we scan both + '\r' and '\n'. For spaces, we look for ' ', '\t', and '\f' + */ + + while (NOT_DONE && !isspace(*line)) line++; + if (likely(NOT_DONE)) { + *line++ = '\0'; + if (line - name > 1) { + /* Add the entry to the symbol-filter list */ + symbol = (symfilter_list_t *)MALLOC(sizeof(symfilter_list_t)); + symbol->next = filter->symbols; + symbol->name = name; + filter->symbols = symbol; + +#if 0 + /* SLOW! For debugging only! */ + { + size_t idx; + size_t elsize = gelf_fsize(elf, ELF_T_SYM, 1, + ehdr->e_version); + symbol->index = SHN_UNDEF; + for (idx = 0; idx < filter->symtab.data->d_size / elsize; + idx++) { + GElf_Sym sym_mem; + GElf_Sym *sym; + const char *symname; + sym = gelf_getsymshndx (filter->symtab.data, NULL, + idx, &sym_mem, NULL); + ASSERT(sym); + + symname = elf_strptr(elf, + filter->symtab.hdr->sh_link, + sym->st_name); + if(!strcmp(symname, symbol->name)) { + symbol->index = idx; + break; + } + } + } +#else + /* Look up the symbol in the ELF file and associate it with the + entry in the filter. */ + symbol->index = hash_lookup(elf, + &filter->hash, + &filter->symtab, + symbol->name, + &symbol->symbol); +#endif + symbol->len = line - name - 1; + ASSERT(symbol->len == strlen(symbol->name)); + + /* If we didn't find the symbol, then it's not in the library. + */ + + if(STN_UNDEF == symbol->index) { + PRINT("%s: symbol was not found!\n", symbol->name); + } + else { + /* If we found the symbol but it's an undefined symbol, then + it's not in the library as well. */ + GElf_Sym sym_mem; + GElf_Sym *sym; + sym = gelf_getsymshndx (filter->symtab.data, NULL, + symbol->index, &sym_mem, NULL); + FAILIF_LIBELF(NULL == sym, gelf_getsymshndx); + /* Make sure the hash lookup worked. */ + ASSERT(!strcmp(elf_strptr(elf, + filter->symtab.hdr->sh_link, + sym->st_name), + symbol->name)); + if (sym->st_shndx == SHN_UNDEF) { + PRINT("%s: symbol was not found (undefined)!\n", symbol->name); + } + else { + filter->num_symbols++; + /* Total count includes null terminators */ + filter->total_name_length += symbol->len + 1; + + /* Set the flag in the symbols_to_keep[] array. This indicates + to function copy_elf() that we want to keep the symbol. + */ + filter->symbols_to_keep[symbol->index] = true; + INFO("FILTER-SYMBOL: [%s] [%d bytes]\n", + symbol->name, + symbol->len); + } + } + } + } + } while (NOT_DONE); +#undef NOT_DONE +} + +void destroy_symfilter(symfilter_t *filter) +{ + symfilter_list_t *old; + INFO("Destroying symbol list...\n"); + while ((old = filter->symbols)) { + filter->symbols = old->next; + FREE(old); + } + munmap(filter->mmap, filter->fsize); + close(filter->fd); +} + +static int match_hash_table_section(Elf *elf, Elf_Scn *sect, void *data) +{ + symfilter_t *filter = (symfilter_t *)data; + Elf32_Shdr *shdr; + + ASSERT(filter); + ASSERT(sect); + shdr = elf32_getshdr(sect); + + /* The section must be marked both as a SHT_HASH, and it's sh_link field + must contain the index of our symbol table (per ELF-file spec). + */ + if (shdr->sh_type == SHT_HASH) + { + FAILIF(filter->hash.sect != NULL, + "There is more than one hash table!\n"); + get_section_info(sect, &filter->hash); + } + + return 0; /* keep looking */ +} + +static int match_dynsym_section(Elf *elf, Elf_Scn *sect, void *data) +{ + symfilter_t *filter = (symfilter_t *)data; + Elf32_Shdr *shdr; + + ASSERT(filter); + ASSERT(sect); + shdr = elf32_getshdr(sect); + + if (shdr->sh_type == SHT_DYNSYM) + { + FAILIF(filter->symtab.sect != NULL, + "There is more than one dynamic symbol table!\n"); + get_section_info(sect, &filter->symtab); + } + + return 0; /* keep looking */ +} diff --git a/tools/soslim/symfilter.h b/tools/soslim/symfilter.h new file mode 100644 index 0000000..f73fd50 --- /dev/null +++ b/tools/soslim/symfilter.h @@ -0,0 +1,50 @@ +#ifndef SYMFILTER_H +#define SYMFILTER_H + +/* This file describes the interface for parsing the list of symbols. Currently, + this is just a text file with each symbol on a separate line. We build an + in-memory linked list of symbols out of this image. +*/ + +#include <stdio.h> +#include <libelf.h> +#include <gelf.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <libebl.h> /* defines bool */ + +typedef struct symfilter_list_t symfilter_list_t; +struct symfilter_list_t { + symfilter_list_t *next; + const char *name; + unsigned int len; /* strlen(name) */ + Elf32_Word index; + GElf_Sym symbol; +}; + +typedef struct symfilter_t { + + int fd; /* symbol-filter-file descriptor */ + off_t fsize; /* size of file */ + void *mmap; /* symbol-fiter-file memory mapping */ + + section_info_t symtab; + section_info_t hash; + symfilter_list_t *symbols; + + /* The total number of symbols in the symfilter. */ + unsigned int num_symbols; + /* The total number of bytes occupied by the names of the symbols, including + the terminating null characters. + */ + unsigned int total_name_length; + + bool *symbols_to_keep; + /* must be the same as the number of symbols in the dynamic table! */ + int num_symbols_to_keep; +} symfilter_t; + +void build_symfilter(const char *name, Elf *elf, symfilter_t *filter, off_t); +void destroy_symfilter(symfilter_t *); + +#endif/*SYMFILTER_H*/ |