#include #include #include #include #include #include #ifndef MAX //! Get the maximum of two values #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #endif #ifndef MIN //! Get the minimum of two values #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif #define ARRAY_COUNT(arr) (sizeof(arr) / sizeof((arr)[0])) #define EI_NIDENT 16 typedef struct { unsigned char e_ident[EI_NIDENT]; uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint32_t e_entry; uint32_t e_phoff; uint32_t e_shoff; uint32_t e_flags; uint16_t e_ehsize; uint16_t e_phentsize; uint16_t e_phnum; uint16_t e_shentsize; uint16_t e_shnum; uint16_t e_shstrndx; } Elf32_Ehdr; #define EI_CLASS 4 #define EI_DATA 5 #define EI_VERSION 6 #define EI_PAD 7 #define EI_NIDENT 16 #define ELFCLASS32 1 #define ELFDATA2MSB 2 #define EV_CURRENT 1 #define ET_EXEC 2 #define EM_PPC 20 typedef struct { uint32_t p_type; uint32_t p_offset; uint32_t p_vaddr; uint32_t p_paddr; uint32_t p_filesz; uint32_t p_memsz; uint32_t p_flags; uint32_t p_align; } Elf32_Phdr; #define PT_LOAD 1 #define PF_R 4 #define PF_W 2 #define PF_X 1 int verbosity = 0; #if BYTE_ORDER == BIG_ENDIAN #define swap32(x) (x) #define swap16(x) (x) #else static inline uint32_t swap32(uint32_t v) { return (v >> 24) | ((v >> 8) & 0x0000FF00) | ((v << 8) & 0x00FF0000) | (v << 24); } static inline uint16_t swap16(uint16_t v) { return (v >> 8) | (v << 8); } #endif /* BIG_ENDIAN */ typedef struct { uint32_t text_off[7]; uint32_t data_off[11]; uint32_t text_addr[7]; uint32_t data_addr[11]; uint32_t text_size[7]; uint32_t data_size[11]; uint32_t bss_addr; uint32_t bss_size; uint32_t entry; uint32_t pad[7]; } DOL_hdr; #define HAVE_BSS 1 #define MAX_TEXT_SEGMENTS 7 #define MAX_DATA_SEGMENTS 11 #define DOL_ALIGNMENT 32 #define DOL_ALIGN(x) (((x) + DOL_ALIGNMENT - 1) & ~(DOL_ALIGNMENT - 1)) typedef struct { DOL_hdr header; int text_cnt; int data_cnt; uint32_t text_elf_off[7]; uint32_t data_elf_off[11]; uint32_t flags; FILE* elf; } DOL_map; void usage(const char* name) { fprintf(stderr, "Usage: %s [-h] [-v] [--] elf-file dol-file\n", name); fprintf(stderr, " Convert an ELF file to a DOL file (by segments)\n"); fprintf(stderr, " Options:\n"); fprintf(stderr, " -h Show this help\n"); fprintf(stderr, " -v Be more verbose (twice for even more)\n"); } #define die(x) \ { \ fprintf(stderr, x "\n"); \ exit(1); \ } #define perrordie(x) \ { \ perror(x); \ exit(1); \ } void ferrordie(FILE* f, const char* str) { if (ferror(f)) { fprintf(stderr, "Error while "); perrordie(str); } else if (feof(f)) { fprintf(stderr, "EOF while %s\n", str); exit(1); } else { fprintf(stderr, "Unknown error while %s\n", str); exit(1); } } void add_bss(DOL_map* map, uint32_t paddr, uint32_t memsz) { if (map->flags & HAVE_BSS) { uint32_t curr_start = swap32(map->header.bss_addr); uint32_t curr_size = swap32(map->header.bss_size); if (paddr < curr_start) map->header.bss_addr = swap32(paddr); // Total BSS size should be the end of the last bss section minus the // start of the first bss section. if (paddr + memsz > curr_start + curr_size) map->header.bss_size = swap32(paddr + memsz - curr_start); } else { map->header.bss_addr = swap32(paddr); map->header.bss_size = swap32(memsz); map->flags |= HAVE_BSS; } } void read_elf_segments(DOL_map* map, const char* elf) { int read, i; Elf32_Ehdr ehdr; if (verbosity >= 2) fprintf(stderr, "Reading ELF file...\n"); map->elf = fopen(elf, "rb"); if (!map->elf) perrordie("Could not open ELF file"); read = fread(&ehdr, sizeof(ehdr), 1, map->elf); if (read != 1) ferrordie(map->elf, "reading ELF header"); if (memcmp(&ehdr.e_ident[0], "\177ELF", 4)) die("Invalid ELF header"); if (ehdr.e_ident[EI_CLASS] != ELFCLASS32) die("Invalid ELF class"); if (ehdr.e_ident[EI_DATA] != ELFDATA2MSB) die("Invalid ELF byte order"); if (ehdr.e_ident[EI_VERSION] != EV_CURRENT) die("Invalid ELF ident version"); if (swap32(ehdr.e_version) != EV_CURRENT) die("Invalid ELF version"); if (swap16(ehdr.e_type) != ET_EXEC) die("ELF is not an executable"); if (swap16(ehdr.e_machine) != EM_PPC) die("Machine is not PowerPC"); if (!swap32(ehdr.e_entry)) die("ELF has no entrypoint"); map->header.entry = ehdr.e_entry; if (verbosity >= 2) fprintf(stderr, "Valid ELF header found\n"); uint16_t phnum = swap16(ehdr.e_phnum); uint32_t phoff = swap32(ehdr.e_phoff); Elf32_Phdr* phdrs; if (!phnum || !phoff) die("ELF has no program headers"); if (swap16(ehdr.e_phentsize) != sizeof(Elf32_Phdr)) die("Invalid program header entry size"); phdrs = malloc(phnum * sizeof(Elf32_Phdr)); if (fseek(map->elf, phoff, SEEK_SET) < 0) ferrordie(map->elf, "reading ELF program headers"); read = fread(phdrs, sizeof(Elf32_Phdr), phnum, map->elf); if (read != phnum) ferrordie(map->elf, "reading ELF program headers"); for (i = 0; i < phnum; i++) { if (swap32(phdrs[i].p_type) == PT_LOAD) { uint32_t offset = swap32(phdrs[i].p_offset); uint32_t paddr = swap32(phdrs[i].p_vaddr); uint32_t filesz = swap32(phdrs[i].p_filesz); uint32_t memsz = swap32(phdrs[i].p_memsz); uint32_t flags = swap32(phdrs[i].p_flags); if (memsz) { if (verbosity >= 2) fprintf(stderr, "PHDR %d: 0x%x [0x%x] -> 0x%08x [0x%x] flags 0x%x\n", i, offset, filesz, paddr, memsz, flags); if (flags & PF_X) { // TEXT segment if (!(flags & PF_R)) fprintf(stderr, "Warning: non-readable segment %d\n", i); if (flags & PF_W) fprintf(stderr, "Warning: writable and executable segment %d\n", i); if (filesz > memsz) { fprintf(stderr, "Error: TEXT segment %d memory size (0x%x) smaller than " "file size (0x%x)\n", i, memsz, filesz); exit(1); } else if (memsz > filesz) { add_bss(map, paddr + filesz, memsz - filesz); } if (map->text_cnt >= MAX_TEXT_SEGMENTS) { die("Error: Too many TEXT segments"); } map->header.text_addr[map->text_cnt] = swap32(paddr); map->header.text_size[map->text_cnt] = swap32(filesz); map->text_elf_off[map->text_cnt] = offset; map->text_cnt++; } else { // DATA or BSS segment if (!(flags & PF_R)) fprintf(stderr, "Warning: non-readable segment %d\n", i); if (filesz == 0) { // BSS segment add_bss(map, paddr, memsz); } else { // DATA segment if (filesz > memsz) { fprintf(stderr, "Error: segment %d memory size (0x%x) is smaller than " "file size (0x%x)\n", i, memsz, filesz); exit(1); } if (map->data_cnt >= MAX_DATA_SEGMENTS) { die("Error: Too many DATA segments"); } map->header.data_addr[map->data_cnt] = swap32(paddr); map->header.data_size[map->data_cnt] = swap32(filesz); map->data_elf_off[map->data_cnt] = offset; map->data_cnt++; } } } else { if (verbosity >= 1) fprintf(stderr, "Skipping empty program header %d\n", i); } } else if (verbosity >= 1) { fprintf(stderr, "Skipping program header %d of type %d\n", i, swap32(phdrs[i].p_type)); } } if (verbosity >= 2) { fprintf(stderr, "Segments:\n"); for (i = 0; i < map->text_cnt; i++) { fprintf(stderr, " TEXT %d: 0x%08x [0x%x] from ELF offset 0x%x\n", i, swap32(map->header.text_addr[i]), swap32(map->header.text_size[i]), map->text_elf_off[i]); } for (i = 0; i < map->data_cnt; i++) { fprintf(stderr, " DATA %d: 0x%08x [0x%x] from ELF offset 0x%x\n", i, swap32(map->header.data_addr[i]), swap32(map->header.data_size[i]), map->data_elf_off[i]); } if (map->flags & HAVE_BSS) fprintf(stderr, " BSS segment: 0x%08x [0x%x]\n", swap32(map->header.bss_addr), swap32(map->header.bss_size)); } } void map_dol(DOL_map* map) { uint32_t fpos; int i; if (verbosity >= 2) fprintf(stderr, "Laying out DOL file...\n"); fpos = DOL_ALIGN(sizeof(DOL_hdr)); for (i = 0; i < map->text_cnt; i++) { if (verbosity >= 2) fprintf(stderr, " TEXT segment %d at 0x%x\n", i, fpos); map->header.text_off[i] = swap32(fpos); fpos = DOL_ALIGN(fpos + swap32(map->header.text_size[i])); } for (i = 0; i < map->data_cnt; i++) { if (verbosity >= 2) fprintf(stderr, " DATA segment %d at 0x%x\n", i, fpos); map->header.data_off[i] = swap32(fpos); fpos = DOL_ALIGN(fpos + swap32(map->header.data_size[i])); } if (map->text_cnt == 0) { if (verbosity >= 1) fprintf(stderr, "Note: adding dummy TEXT segment to work around IOS bug\n"); map->header.text_off[0] = swap32(DOL_ALIGN(sizeof(DOL_hdr))); } if (map->data_cnt == 0) { if (verbosity >= 1) fprintf(stderr, "Note: adding dummy DATA segment to work around IOS bug\n"); map->header.data_off[0] = swap32(DOL_ALIGN(sizeof(DOL_hdr))); } } #define BLOCK (1024 * 1024) void fcpy(FILE* dst, FILE* src, uint32_t dst_off, uint32_t src_off, uint32_t size) { int left = size; int read; int written; int block; void* blockbuf; if (fseek(src, src_off, SEEK_SET) < 0) ferrordie(src, "reading ELF segment data"); if (fseek(dst, dst_off, SEEK_SET) < 0) ferrordie(dst, "writing DOL segment data"); blockbuf = malloc(MIN(BLOCK, left)); while (left) { block = MIN(BLOCK, left); read = fread(blockbuf, 1, block, src); if (read != block) { free(blockbuf); ferrordie(src, "reading ELF segment data"); } written = fwrite(blockbuf, 1, block, dst); if (written != block) { free(blockbuf); ferrordie(dst, "writing DOL segment data"); } left -= block; } free(blockbuf); } void fpad(FILE* dst, uint32_t dst_off, uint32_t size) { uint32_t i; if (fseek(dst, dst_off, SEEK_SET) < 0) ferrordie(dst, "writing DOL segment data"); for (i = 0; i < size; i++) fputc(0, dst); } void write_dol(DOL_map* map, const char* dol) { FILE* dolf; int written; int i; if (verbosity >= 2) fprintf(stderr, "Writing DOL file...\n"); dolf = fopen(dol, "wb"); if (!dolf) perrordie("Could not open DOL file"); if (verbosity >= 2) { fprintf(stderr, "DOL header:\n"); for (i = 0; i < MAX(1, map->text_cnt); i++) fprintf(stderr, " TEXT %d @ 0x%08x [0x%x] off 0x%x\n", i, swap32(map->header.text_addr[i]), swap32(map->header.text_size[i]), swap32(map->header.text_off[i])); for (i = 0; i < MAX(1, map->data_cnt); i++) fprintf(stderr, " DATA %d @ 0x%08x [0x%x] off 0x%x\n", i, swap32(map->header.data_addr[i]), swap32(map->header.data_size[i]), swap32(map->header.data_off[i])); if (swap32(map->header.bss_addr) && swap32(map->header.bss_size)) fprintf(stderr, " BSS @ 0x%08x [0x%x]\n", swap32(map->header.bss_addr), swap32(map->header.bss_size)); fprintf(stderr, " Entry: 0x%08x\n", swap32(map->header.entry)); fprintf(stderr, "Writing DOL header...\n"); } // Write DOL header with aligned text and data section sizes DOL_hdr aligned_header = map->header; for (i = 0; i < ARRAY_COUNT(aligned_header.text_size); i++) aligned_header.text_size[i] = swap32(DOL_ALIGN(swap32(aligned_header.text_size[i]))); for (i = 0; i < ARRAY_COUNT(aligned_header.data_size); i++) aligned_header.data_size[i] = swap32(DOL_ALIGN(swap32(aligned_header.data_size[i]))); written = fwrite(&aligned_header, sizeof(DOL_hdr), 1, dolf); if (written != 1) ferrordie(dolf, "writing DOL header"); for (i = 0; i < map->text_cnt; i++) { uint32_t size = swap32(map->header.text_size[i]); uint32_t padded_size = DOL_ALIGN(size); if (verbosity >= 2) fprintf(stderr, "Writing TEXT segment %d...\n", i); fcpy(dolf, map->elf, swap32(map->header.text_off[i]), map->text_elf_off[i], size); if (padded_size > size) fpad(dolf, swap32(map->header.text_off[i]) + size, padded_size - size); } for (i = 0; i < map->data_cnt; i++) { uint32_t size = swap32(map->header.data_size[i]); uint32_t padded_size = DOL_ALIGN(size); if (verbosity >= 2) fprintf(stderr, "Writing DATA segment %d...\n", i); fcpy(dolf, map->elf, swap32(map->header.data_off[i]), map->data_elf_off[i], size); if (padded_size > size) fpad(dolf, swap32(map->header.data_off[i]) + size, padded_size - size); } if (verbosity >= 2) fprintf(stderr, "All done!\n"); fclose(map->elf); fclose(dolf); } int main(int argc, char** argv) { char** arg; if (argc < 2) { usage(argv[0]); return 1; } arg = &argv[1]; argc--; while (argc && *arg[0] == '-') { if (!strcmp(*arg, "-h")) { usage(argv[0]); return 1; } else if (!strcmp(*arg, "-v")) { verbosity++; } else if (!strcmp(*arg, "--")) { arg++; argc--; break; } else { fprintf(stderr, "Unrecognized option %s\n", *arg); usage(argv[0]); return 1; } arg++; argc--; } if (argc < 2) { usage(argv[0]); exit(1); } const char* elf_file = arg[0]; const char* dol_file = arg[1]; DOL_map map; memset(&map, 0, sizeof(map)); read_elf_segments(&map, elf_file); map_dol(&map); write_dol(&map, dol_file); return 0; }