| #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[shdr_info[cnt].shdr.sh_link].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); |
| } |