diff options
Diffstat (limited to 'util/grub-pe2elf.c')
-rw-r--r-- | util/grub-pe2elf.c | 573 |
1 files changed, 573 insertions, 0 deletions
diff --git a/util/grub-pe2elf.c b/util/grub-pe2elf.c new file mode 100644 index 0000000..1133129 --- /dev/null +++ b/util/grub-pe2elf.c @@ -0,0 +1,573 @@ +/* grub-pe2elf.c - tool to convert pe image to elf. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <grub/types.h> +#include <grub/util/misc.h> +#include <grub/elf.h> +#include <grub/efi/pe32.h> +#include <grub/misc.h> + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <getopt.h> + + +/* Please don't internationalise this file. It's pointless. */ + +/* + * Section layout + * + * null + * .text + * .rdata + * .data + * .bss + * .modname + * .moddeps + * .symtab + * .strtab + * relocation sections + */ + +#if GRUB_TARGET_WORDSIZE == 64 +typedef Elf64_Rela elf_reloc_t; +#define GRUB_PE32_MACHINE GRUB_PE32_MACHINE_X86_64 +#else +typedef Elf32_Rel elf_reloc_t; +#define GRUB_PE32_MACHINE GRUB_PE32_MACHINE_I386 +#endif + +#define STRTAB_BLOCK 256 + +static char *strtab; +static int strtab_max, strtab_len; + +static Elf_Ehdr ehdr; +static Elf_Shdr *shdr; +static int num_sections, first_reloc_section, reloc_sections_end, symtab_section, strtab_section; +static grub_uint32_t offset, image_base; + +static int +insert_string (const char *name) +{ + int len, result; + + if (*name == '_') + name++; + + len = strlen (name); + if (strtab_len + len >= strtab_max) + { + strtab_max += STRTAB_BLOCK; + strtab = xrealloc (strtab, strtab_max); + } + + strcpy (strtab + strtab_len, name); + result = strtab_len; + strtab_len += len + 1; + + return result; +} + +static int * +write_section_data (FILE* fp, const char *name, char *image, + struct grub_pe32_coff_header *pe_chdr, + struct grub_pe32_section_table *pe_shdr) +{ + int *section_map; + int i; + grub_uint32_t last_category = 0; + grub_uint32_t idx, idx_reloc; + char *pe_strtab = (image + pe_chdr->symtab_offset + + pe_chdr->num_symbols * sizeof (struct grub_pe32_symbol)); + + section_map = xcalloc (2 * pe_chdr->num_sections + 5, sizeof (int)); + section_map[0] = 0; + shdr = xcalloc (2 * pe_chdr->num_sections + 5, sizeof (shdr[0])); + idx = 1; + idx_reloc = pe_chdr->num_sections + 1; + + for (i = 0; i < pe_chdr->num_sections; i++, pe_shdr++) + { + grub_uint32_t category; + const char *shname = pe_shdr->name; + grub_size_t secsize; + + if (shname[0] == '/' && grub_isdigit (shname[1])) + { + char t[sizeof (pe_shdr->name) + 1]; + memcpy (t, shname, sizeof (pe_shdr->name)); + t[sizeof (pe_shdr->name)] = 0; + shname = pe_strtab + atoi (t + 1); + } + + secsize = pe_shdr->raw_data_size; + + shdr[idx].sh_type = SHT_PROGBITS; + + if (! strcmp (shname, ".text")) + { + category = 0; + shdr[idx].sh_flags = SHF_ALLOC | SHF_EXECINSTR; + } + else if (! strncmp (shname, ".rdata", 6)) + { + category = 1; + shdr[idx].sh_flags = SHF_ALLOC; + } + else if (! strcmp (shname, ".data")) + { + category = 2; + shdr[idx].sh_flags = SHF_ALLOC | SHF_WRITE; + } + else if (! strcmp (shname, ".bss")) + { + category = 3; + shdr[idx].sh_type = SHT_NOBITS; + shdr[idx].sh_flags = SHF_ALLOC | SHF_WRITE; + if (secsize < pe_shdr->virtual_size) + secsize = pe_shdr->virtual_size; + } + else if (strcmp (shname, ".modname") == 0 || strcmp (shname, ".moddeps") == 0 + || strcmp (shname, ".module_license") == 0) + category = 4; + else + { + section_map[i + 1] = -1; + continue; + } + + if (category < last_category) + grub_util_error ("out of order sections"); + + section_map[i + 1] = idx; + + if (pe_shdr->virtual_size + && pe_shdr->virtual_size < secsize) + secsize = pe_shdr->virtual_size; + + shdr[idx].sh_size = secsize; + shdr[idx].sh_addralign = 1 << (((pe_shdr->characteristics >> + GRUB_PE32_SCN_ALIGN_SHIFT) & + GRUB_PE32_SCN_ALIGN_MASK) - 1); + shdr[idx].sh_addr = pe_shdr->virtual_address + image_base; + + if (shdr[idx].sh_type != SHT_NOBITS) + { + shdr[idx].sh_offset = offset; + grub_util_write_image_at (image + pe_shdr->raw_data_offset, + pe_shdr->raw_data_size, offset, fp, + shname); + + offset += secsize; + } + + if (pe_shdr->relocations_offset) + { + char relname[5 + strlen (shname)]; + + sprintf (relname, ".rel%s", shname); + + shdr[idx_reloc].sh_name = insert_string (relname); + shdr[idx_reloc].sh_link = i; + shdr[idx_reloc].sh_info = idx; + + shdr[idx].sh_name = shdr[idx_reloc].sh_name + 4; + + idx_reloc++; + } + else + shdr[idx].sh_name = insert_string (shname); + idx++; + } + + idx_reloc -= pe_chdr->num_sections + 1; + num_sections = idx + idx_reloc + 2; + first_reloc_section = idx; + reloc_sections_end = idx + idx_reloc; + memmove (shdr + idx, shdr + pe_chdr->num_sections + 1, + idx_reloc * sizeof (shdr[0])); + memset (shdr + idx + idx_reloc, 0, 3 * sizeof (shdr[0])); + memset (shdr, 0, sizeof (shdr[0])); + + symtab_section = idx + idx_reloc; + strtab_section = idx + idx_reloc + 1; + + return section_map; +} + +static void +write_reloc_section (FILE* fp, const char *name, char *image, + struct grub_pe32_coff_header *pe_chdr, + struct grub_pe32_section_table *pe_shdr, + Elf_Sym *symtab, + int *symtab_map) +{ + int i; + + for (i = first_reloc_section; i < reloc_sections_end; i++) + { + struct grub_pe32_section_table *pe_sec; + struct grub_pe32_reloc *pe_rel; + elf_reloc_t *rel; + int num_rels, j, modified; + + pe_sec = pe_shdr + shdr[i].sh_link; + pe_rel = (struct grub_pe32_reloc *) (image + pe_sec->relocations_offset); + rel = (elf_reloc_t *) xcalloc (pe_sec->num_relocations, sizeof (elf_reloc_t)); + num_rels = 0; + modified = 0; + + for (j = 0; j < pe_sec->num_relocations; j++, pe_rel++) + { + int type; + grub_uint32_t ofs, *addr; + + if ((pe_rel->symtab_index >= pe_chdr->num_symbols) || + (symtab_map[pe_rel->symtab_index] == -1)) + grub_util_error ("invalid symbol"); + + ofs = pe_rel->offset - pe_sec->virtual_address; + addr = (grub_uint32_t *)(image + pe_sec->raw_data_offset + ofs); + + switch (pe_rel->type) + { +#if GRUB_TARGET_WORDSIZE == 64 + case 1: + type = R_X86_64_64; + rel[num_rels].r_addend = *(grub_int64_t *)addr; + *(grub_int64_t *)addr = 0; + modified = 1; + break; + case 4: + type = R_X86_64_PC32; + rel[num_rels].r_addend = *(grub_int32_t *)addr; + *addr = 0; + modified = 1; + break; + case 14: + type = R_X86_64_PC64; + rel[num_rels].r_addend = *(grub_uint64_t *)addr - 8; + *(grub_uint64_t *)addr = 0; + modified = 1; + break; +#else + case GRUB_PE32_REL_I386_DIR32: + type = R_386_32; + break; + case GRUB_PE32_REL_I386_REL32: + type = R_386_PC32; + break; +#endif + default: + grub_util_error ("unknown pe relocation type %d", pe_rel->type); + } + + if (type == +#if GRUB_TARGET_WORDSIZE == 64 + R_386_PC32 +#else + R_X86_64_PC32 +#endif + + ) + { + unsigned char code; + + code = image[pe_sec->raw_data_offset + ofs - 1]; + +#if GRUB_TARGET_WORDSIZE == 32 + if (((code != 0xe8) && (code != 0xe9)) || (*addr)) + grub_util_error ("invalid relocation (%x %x)", code, *addr); +#endif + + if (symtab[symtab_map[pe_rel->symtab_index]].st_shndx + && symtab[symtab_map[pe_rel->symtab_index]].st_shndx + == shdr[i].sh_info) + { + modified = 1; + *addr += (symtab[symtab_map[pe_rel->symtab_index]].st_value + - ofs - 4); + + continue; + } + else + { +#if GRUB_TARGET_WORDSIZE == 64 + rel[num_rels].r_addend -= 4; +#else + modified = 1; + *addr = -4; +#endif + } + } + + rel[num_rels].r_offset = ofs; + rel[num_rels].r_info = ELF_R_INFO (symtab_map[pe_rel->symtab_index], + type); + num_rels++; + } + + if (modified) + grub_util_write_image_at (image + pe_sec->raw_data_offset, + shdr[shdr[i].sh_info].sh_size, + shdr[shdr[i].sh_info].sh_offset, + fp, name); + +#if GRUB_TARGET_WORDSIZE == 64 + shdr[i].sh_type = SHT_RELA; +#else + shdr[i].sh_type = SHT_REL; +#endif + shdr[i].sh_offset = offset; + shdr[i].sh_link = symtab_section; + shdr[i].sh_addralign = 4; + shdr[i].sh_entsize = sizeof (elf_reloc_t); + shdr[i].sh_size = num_rels * sizeof (elf_reloc_t); + + grub_util_write_image_at (rel, shdr[i].sh_size, offset, fp, name); + offset += shdr[i].sh_size; + free (rel); + } +} + +static void +write_symbol_table (FILE* fp, const char *name, char *image, + struct grub_pe32_coff_header *pe_chdr, + struct grub_pe32_section_table *pe_shdr, + int *section_map) +{ + struct grub_pe32_symbol *pe_symtab; + char *pe_strtab; + Elf_Sym *symtab; + int *symtab_map, num_syms; + int i; + + pe_symtab = (struct grub_pe32_symbol *) (image + pe_chdr->symtab_offset); + pe_strtab = (char *) (pe_symtab + pe_chdr->num_symbols); + + symtab = (Elf_Sym *) xcalloc (pe_chdr->num_symbols + 1, sizeof (Elf_Sym)); + num_syms = 1; + + symtab_map = (int *) xcalloc (pe_chdr->num_symbols, sizeof (int)); + + for (i = 0; i < (int) pe_chdr->num_symbols; + i += pe_symtab->num_aux + 1, pe_symtab += pe_symtab->num_aux + 1) + { + int bind, type; + + symtab_map[i] = -1; + if ((pe_symtab->section > pe_chdr->num_sections) || + (section_map[pe_symtab->section] == -1)) + continue; + + if (! pe_symtab->section) + type = STT_NOTYPE; + else if (pe_symtab->type == GRUB_PE32_DT_FUNCTION) + type = STT_FUNC; + else + type = STT_OBJECT; + + if (pe_symtab->storage_class == GRUB_PE32_SYM_CLASS_EXTERNAL) + bind = STB_GLOBAL; + else + bind = STB_LOCAL; + + if ((pe_symtab->type != GRUB_PE32_DT_FUNCTION) && (pe_symtab->num_aux)) + { + if (! pe_symtab->value) + type = STT_SECTION; + + symtab[num_syms].st_name = shdr[section_map[pe_symtab->section]].sh_name; + } + else + { + char short_name[9]; + char *symname; + + if (pe_symtab->long_name[0]) + { + strncpy (short_name, pe_symtab->short_name, 8); + short_name[8] = 0; + symname = short_name; + } + else + symname = pe_strtab + pe_symtab->long_name[1]; + + if ((strcmp (symname, "_grub_mod_init")) && + (strcmp (symname, "_grub_mod_fini")) && + (strcmp (symname, "grub_mod_init")) && + (strcmp (symname, "grub_mod_fini")) && + (bind == STB_LOCAL)) + continue; + + symtab[num_syms].st_name = insert_string (symname); + } + + symtab[num_syms].st_shndx = section_map[pe_symtab->section]; + symtab[num_syms].st_value = pe_symtab->value; + symtab[num_syms].st_info = ELF_ST_INFO (bind, type); + + symtab_map[i] = num_syms; + num_syms++; + } + + write_reloc_section (fp, name, image, pe_chdr, pe_shdr, + symtab, symtab_map); + + shdr[symtab_section].sh_name = insert_string (".symtab"); + shdr[symtab_section].sh_type = SHT_SYMTAB; + shdr[symtab_section].sh_offset = offset; + shdr[symtab_section].sh_size = num_syms * sizeof (Elf_Sym); + shdr[symtab_section].sh_entsize = sizeof (Elf_Sym); + shdr[symtab_section].sh_link = strtab_section; + shdr[symtab_section].sh_addralign = 4; + + grub_util_write_image_at (symtab, shdr[symtab_section].sh_size, + offset, fp, name); + offset += shdr[symtab_section].sh_size; + + free (symtab); + free (symtab_map); +} + +static void +write_string_table (FILE *fp, const char *name) +{ + shdr[strtab_section].sh_name = insert_string (".strtab"); + shdr[strtab_section].sh_type = SHT_STRTAB; + shdr[strtab_section].sh_offset = offset; + shdr[strtab_section].sh_size = strtab_len; + shdr[strtab_section].sh_addralign = 1; + grub_util_write_image_at (strtab, strtab_len, offset, fp, + name); + offset += strtab_len; + + free (strtab); +} + +static void +write_section_header (FILE *fp, const char *name) +{ + ehdr.e_ident[EI_MAG0] = ELFMAG0; + ehdr.e_ident[EI_MAG1] = ELFMAG1; + ehdr.e_ident[EI_MAG2] = ELFMAG2; + ehdr.e_ident[EI_MAG3] = ELFMAG3; + ehdr.e_ident[EI_VERSION] = EV_CURRENT; + ehdr.e_version = EV_CURRENT; + ehdr.e_type = ET_REL; + +#if GRUB_TARGET_WORDSIZE == 64 + ehdr.e_ident[EI_CLASS] = ELFCLASS64; + ehdr.e_ident[EI_DATA] = ELFDATA2LSB; + ehdr.e_machine = EM_X86_64; +#else + ehdr.e_ident[EI_CLASS] = ELFCLASS32; + ehdr.e_ident[EI_DATA] = ELFDATA2LSB; + ehdr.e_machine = EM_386; +#endif + ehdr.e_ehsize = sizeof (ehdr); + ehdr.e_shentsize = sizeof (Elf_Shdr); + ehdr.e_shstrndx = strtab_section; + + ehdr.e_shoff = offset; + ehdr.e_shnum = num_sections; + grub_util_write_image_at (shdr, sizeof (Elf_Shdr) * num_sections, + offset, fp, name); + + grub_util_write_image_at (&ehdr, sizeof (Elf_Ehdr), 0, fp, name); +} + +static void +convert_pe (FILE* fp, const char *name, char *image) +{ + struct grub_pe32_coff_header *pe_chdr; + struct grub_pe32_section_table *pe_shdr; + int *section_map; + + if (image[0] == 'M' && image[1] == 'Z') + pe_chdr = (struct grub_pe32_coff_header *) (image + (grub_le_to_cpu32 (((grub_uint32_t *)image)[0xf]) + 4)); + else + pe_chdr = (struct grub_pe32_coff_header *) image; + if (grub_le_to_cpu16 (pe_chdr->machine) != GRUB_PE32_MACHINE) + grub_util_error ("invalid coff image (%x != %x)", + grub_le_to_cpu16 (pe_chdr->machine), GRUB_PE32_MACHINE); + + strtab = xmalloc (STRTAB_BLOCK); + strtab_max = STRTAB_BLOCK; + strtab[0] = 0; + strtab_len = 1; + + offset = sizeof (ehdr); + if (pe_chdr->optional_header_size) + { +#if GRUB_TARGET_WORDSIZE == 64 + struct grub_pe64_optional_header *o; +#else + struct grub_pe32_optional_header *o; +#endif + o = (void *) (pe_chdr + 1); + image_base = o->image_base; + } + pe_shdr = (struct grub_pe32_section_table *) ((char *) (pe_chdr + 1) + pe_chdr->optional_header_size); + + section_map = write_section_data (fp, name, image, pe_chdr, pe_shdr); + + write_symbol_table (fp, name, image, pe_chdr, pe_shdr, section_map); + free (section_map); + + write_string_table (fp, name); + + write_section_header (fp, name); +} + +int +main (int argc, char *argv[]) +{ + char *image; + FILE* fp; + char *in, *out; + + /* Obtain PATH. */ + if (1 >= argc) + { + fprintf (stderr, "Filename not specified.\n"); + return 1; + } + + in = argv[1]; + if (argc > 2) + out = argv[2]; + else + out = in; + image = grub_util_read_image (in); + + fp = grub_util_fopen (out, "wb"); + if (! fp) + grub_util_error ("cannot open %s", out); + + convert_pe (fp, out, image); + + fclose (fp); + + return 0; +} |