diff options
Diffstat (limited to 'utils/nwztools/database/nvp/nvptool.cpp')
| -rw-r--r-- | utils/nwztools/database/nvp/nvptool.cpp | 754 |
1 files changed, 754 insertions, 0 deletions
diff --git a/utils/nwztools/database/nvp/nvptool.cpp b/utils/nwztools/database/nvp/nvptool.cpp new file mode 100644 index 0000000..1371e93 --- /dev/null +++ b/utils/nwztools/database/nvp/nvptool.cpp @@ -0,0 +1,754 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2016 Amaury Pouly + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include <cstdio> +#include <stdint.h> +#include <cstdlib> +#include <cstring> +#include <getopt.h> +#include <cstdarg> +#include <string> +#include <fstream> +#include <elf.h> + +bool g_verbose = false; +bool g_unsafe = false; + +uint8_t *read_file(const std::string& path, size_t& size) +{ + std::ifstream fin(path.c_str(), std::ios::binary); + if(!fin) + { + printf("Error: cannot open '%s'\n", path.c_str()); + return 0; + } + fin.seekg(0, std::ios::end); + size = fin.tellg(); + fin.seekg(0, std::ios::beg); + uint8_t *buf = new uint8_t[size]; + fin.read((char *)buf, size); + return buf; +} + +bool write_file(const std::string& path, uint8_t *buf, size_t size) +{ + std::ofstream fout(path.c_str(), std::ios::binary); + if(!fout) + { + printf("Error: cannot open '%s'\n", path.c_str()); + return false; + } + fout.write((char *)buf, size); + fout.close(); + return true; +} + +/* ELF code */ +uint8_t *g_elf_buf; +size_t g_elf_size; +Elf32_Shdr *g_elf_symtab; +Elf32_Shdr *g_elf_symtab_strtab; +Elf32_Shdr *g_elf_shstrtab; + +Elf32_Ehdr *elf_ehdr() +{ + return (Elf32_Ehdr *)g_elf_buf; +} + +#define NTH_SHDR_OFF(n) \ + (elf_ehdr()->e_shoff + elf_ehdr()->e_shentsize * (n)) + +Elf32_Shdr *elf_shdr(size_t index) +{ + if(index >= elf_ehdr()->e_shnum) + { + printf("Warning: section index is out of bounds\n"); + return nullptr; + } + return (Elf32_Shdr *)(g_elf_buf + NTH_SHDR_OFF(index)); +} + +size_t elf_shnum() +{ + return elf_ehdr()->e_shnum; +} + +const char *elf_get_str(Elf32_Shdr *strtab, Elf32_Word index) +{ + /* sanity checks */ + if(strtab->sh_type != SHT_STRTAB) + { + printf("Warning: string access to a non-string-table section\n"); + return nullptr; + } + if(strtab->sh_offset + strtab->sh_size > g_elf_size) + { + printf("Warning: string table section does not fit in the file\n"); + return nullptr; + } + if(index >= strtab->sh_size) + { + printf("Warning: string access to string table is out of bounds\n"); + return nullptr; + } + char *buf = (char *)(g_elf_buf + strtab->sh_offset); + if(buf[strtab->sh_size - 1] != 0) + { + printf("Warning: string table is not zero terminated\n"); + return nullptr; + } + return buf + index; +} + +const char *elf_get_section_name(size_t index) +{ + Elf32_Shdr *shdr = elf_shdr(index); + return shdr ? elf_get_str(g_elf_shstrtab, shdr->sh_name) : nullptr; +} + +const char *elf_get_symbol_name(Elf32_Sym *sym) +{ + if(ELF32_ST_TYPE(sym->st_info) == STT_SECTION) + return elf_get_section_name(sym->st_shndx); + else + return elf_get_str(g_elf_symtab_strtab, sym->st_name); +} + +Elf32_Sym *elf_get_symbol_by_name(const char *name) +{ + Elf32_Sym *sym = (Elf32_Sym *)(g_elf_buf + g_elf_symtab->sh_offset); + size_t nr_syms = g_elf_symtab->sh_size / sizeof(Elf32_Sym); + for(size_t i = 0; i < nr_syms; i++) + { + const char *s = elf_get_symbol_name(&sym[i]); + if(s != nullptr && strcmp(name, s) == 0) + return &sym[i]; + } + return nullptr; +} + +Elf32_Sym *elf_get_symbol_by_address(size_t shndx, Elf32_Word address) +{ + Elf32_Sym *sym = (Elf32_Sym *)(g_elf_buf + g_elf_symtab->sh_offset); + size_t nr_syms = g_elf_symtab->sh_size / sizeof(Elf32_Sym); + for(size_t i = 0; i < nr_syms; i++) + { + if(sym[i].st_shndx == shndx && sym[i].st_value == address) + return &sym[i]; + } + return nullptr; +} + +Elf32_Sym *elf_get_symbol_by_index(size_t index) +{ + Elf32_Sym *sym = (Elf32_Sym *)(g_elf_buf + g_elf_symtab->sh_offset); + size_t nr_syms = g_elf_symtab->sh_size / sizeof(Elf32_Sym); + if(index >= nr_syms) + return nullptr; + return &sym[index]; +} + +void *elf_get_section_ptr(size_t shndx, Elf32_Word address, size_t size) +{ + Elf32_Shdr *shdr = elf_shdr(shndx); + if(shdr == nullptr) + return nullptr; + if(address + size > shdr->sh_size) + return nullptr; + if(shdr->sh_offset + shdr->sh_size > g_elf_size) + return nullptr; + return g_elf_buf + shdr->sh_offset + address; +} + +/* make sure the string has a final zero in the section, optionally check characters + * are printable */ +const char *elf_get_string_ptr_safe(size_t shndx, Elf32_Word offset, bool want_print = true) +{ + Elf32_Shdr *shdr = elf_shdr(shndx); + if(shdr == nullptr) + return nullptr; + /* address must be in the section */ + if(offset >= shdr->sh_size) + return nullptr; + /* determine maximum size */ + size_t max_sz = shdr->sh_size - offset; + const char *ptr = (const char *)(g_elf_buf + shdr->sh_offset + offset); + for(size_t i = 0; i < max_sz; i++) + { + if(ptr[i] == 0) /* found final 0, everything is fine */ + return ptr; + if(want_print && !isprint(ptr[i])) + return nullptr; + } + return nullptr; +} + +size_t elf_find_reloc_section(size_t shndx) +{ + /* find the relocation section */ + for(size_t i = 0; i < elf_ehdr()->e_shnum; i++) + { + Elf32_Shdr *shdr = elf_shdr(i); + if(shdr->sh_type != SHT_REL && shdr->sh_type != SHT_RELA) + continue; + if(shdr->sh_info != shndx) + continue; + return i; + } + return 0; +} + +void *elf_get_symbol_ptr(Elf32_Sym *sym, size_t size) +{ + /* NOTE: also works for STT_SECTION since offset will be 0 */ + return elf_get_section_ptr(sym->st_shndx, sym->st_value, size); +} + +/* take the position of a 32-bit address in the section and apply relocation if + * any */ +void *elf_reloc_addr32(size_t shndx, Elf32_Word offset) +{ + /* read value */ + uint32_t *val = (uint32_t *)elf_get_section_ptr(shndx, offset, 4); + if(val == nullptr) + return 0; /* invalid */ + /* find reloc section if any */ + size_t relshndx = elf_find_reloc_section(shndx); + if(relshndx == 0) + return g_elf_buf + *val; /* no relocation applies */ + Elf32_Shdr *shdr = elf_shdr(relshndx); + /* find relocation that applies */ + if(shdr->sh_type == SHT_RELA) + { + printf("Warning: unsupported RELA relocation type\n"); + return 0; + } + Elf32_Rel *rel = (Elf32_Rel *)elf_get_section_ptr(relshndx, 0, shdr->sh_size); + if(rel == nullptr) + { + printf("Warning: invalid relocation section\n"); + return 0; + } + size_t sym_count = shdr->sh_size / sizeof(Elf32_Rel); + for(size_t i = 0; i < sym_count; i++) + { + /* for relocatable files, r_offset is the offset in the section */ + if(rel[i].r_offset != offset) + continue; + /* find symbol, ignore shdr->sh_link and assume it is g_elf_symtab + * since the file should have only one symbol table anyway */ + Elf32_Sym *sym = elf_get_symbol_by_index(ELF32_R_SYM(rel[i].r_info)); + /* found it! */ + if(g_verbose) + { + printf("[section %zu (%s) offset %#x reloc val %#x type %d sym %d (%s)]\n", + shndx, elf_get_section_name(shndx), offset, *val, + ELF32_R_TYPE(rel[i].r_info), ELF32_R_SYM(rel[i].r_info), + sym ? elf_get_symbol_name(sym) : "<undef>"); + } + /* apply reloc */ + if(ELF32_R_TYPE(rel[i].r_info) == R_ARM_ABS32) + { + if(sym == nullptr) + { + printf("Warning: R_ARM_ABS32 reloc with invalid symbol reference\n"); + return 0; + } + return *val + (uint8_t *)elf_get_symbol_ptr(sym, 0); + } + else + { + printf("Warning: unsupported relocation type %d\n", ELF32_R_TYPE(rel[i].r_info)); + return 0; + } + } + /* no reloc applies */ + if(g_verbose) + { + printf("[section %zu (%s) offset %#x no reloc found]\n", shndx, + elf_get_section_name(shndx), offset); + } + return g_elf_buf + *val; /* no relocation applies */ +} + +size_t elf_map_virt_addr(uint32_t address, Elf32_Word& out_off) +{ + /* for relocatable file, this is trivial */ + for(size_t i = 0; i < elf_ehdr()->e_shnum; i++) + { + Elf32_Shdr *shdr = elf_shdr(i); + if(shdr->sh_offset <= address && address < shdr->sh_offset + shdr->sh_size) + { + out_off = address - shdr->sh_offset; + if(g_verbose) + { + printf("[map %#x to section %zi (%s) at %#x]\n", address, i, + elf_get_section_name(i), out_off); + } + return i; + } + } + return 0; /* section 0 is always invalid */ +} + +size_t elf_map_ptr(void *ptr, Elf32_Word& out_off) +{ + uint32_t addr = (uint32_t)((uint8_t *)ptr - g_elf_buf); + return elf_map_virt_addr(addr, out_off); +} + +/* same as elf_reloc_addr32 but find section automatically from pointer */ +void *elf_reloc_addr32_ptr(uint32_t *val) +{ + Elf32_Word off; + size_t sec = elf_map_ptr((void *)val, off); + /* if it does not belong to any section, don't do anything */ + if(sec == 0) + { + printf("Warning: reloc addr pointer not in any section\n"); + return g_elf_buf + *val; + } + return elf_reloc_addr32(sec, off); +} + +Elf32_Sym *elf_get_symbol_by_ptr(void *ptr) +{ + Elf32_Word off; + size_t sec = elf_map_ptr(ptr, off); + return sec ? elf_get_symbol_by_address(sec, off) : nullptr; +} + +/* check if a string is safe */ +bool elf_is_str_ptr_safe(const char *str) +{ + Elf32_Word name_off; + /* find the section it belongs to */ + size_t name_shndx = elf_map_ptr((void *)str, name_off); + if(name_shndx == 0) + return false; + /* check the string fit in the section */ + return elf_get_string_ptr_safe(name_shndx, name_off) != nullptr; +} + +bool elf_is_ptr_safe(void *ptr, size_t sz) +{ + Elf32_Word ptr_off; + /* find the section it belongs to */ + size_t ptr_shndx = elf_map_ptr((void *)ptr, ptr_off); + if(ptr_shndx == 0) + return false; + /* check the string fit in the section */ + return elf_get_section_ptr(ptr_shndx, ptr_off, sz) != nullptr; +} + +bool elf_init() +{ + if(g_elf_size < sizeof(Elf32_Ehdr)) + { + printf("Invalid ELF file: too small\n"); + return false; + } + Elf32_Ehdr *ehdr = elf_ehdr(); + if(ehdr->e_ident[EI_MAG0] != ELFMAG0 || + ehdr->e_ident[EI_MAG1] != ELFMAG1 || + ehdr->e_ident[EI_MAG2] != ELFMAG2 || + ehdr->e_ident[EI_MAG3] != ELFMAG3) + { + printf("Invalid ELF file: invalid ident\n"); + return false; + } + /* we only support relocatable files */ + if(ehdr->e_type != ET_REL) + { + printf("Unsupported ELF file: this is not a relocatable file\n"); + return false; + } + if(ehdr->e_ident[EI_CLASS] != ELFCLASS32 || ehdr->e_machine != EM_ARM) + { + printf("Unsupported ELF file: this is not a 32-bit ARM ELF file\n"); + return false; + } + /* go through sections */ + if(ehdr->e_shoff == 0) + { + printf("Invalid ELF file: no sections\n"); + return false; + } + if(ehdr->e_shentsize < sizeof(Elf32_Shdr)) + { + printf("Invalid ELF file: section entry size too small\n"); + return false; + } + if(NTH_SHDR_OFF(ehdr->e_shnum) > g_elf_size) + { + printf("Invalid ELF file: sections header does not fit in the file\n"); + return false; + } + for(size_t i = 0; i < ehdr->e_shnum; i++) + { + Elf32_Shdr *shdr = (Elf32_Shdr *)(g_elf_buf + NTH_SHDR_OFF(i)); + if(shdr->sh_type == SHT_SYMTAB) + g_elf_symtab = shdr; + } + /* handle symbol table */ + if(g_elf_symtab) + { + if(g_elf_symtab->sh_offset + g_elf_symtab->sh_size > g_elf_size) + { + printf("Invalid ELF file: symtab does not file in the file\n"); + return false; + } + g_elf_symtab_strtab = elf_shdr(g_elf_symtab->sh_link); + if(g_elf_symtab_strtab == nullptr) + { + printf("Invalid ELF file: symtab's strtab is not valid\n"); + } + if(g_elf_symtab_strtab->sh_type != SHT_STRTAB) + { + printf("Invalid ELF file: symtab's strtab is not a string table\n"); + return false; + } + } + /* handle section string table */ + if(ehdr->e_shstrndx != SHN_UNDEF) + { + g_elf_shstrtab = elf_shdr(ehdr->e_shstrndx); + if(g_elf_shstrtab == nullptr) + { + printf("Invalid ELF file: section string table is invalid\n"); + return false; + } + } + + return true; +} + +/* main code */ + +void usage() +{ + printf("usage: nvptool [options] inputs...\n"); + printf("options:\n"); + printf(" -h/--help Display help\n"); + printf(" -x/--extract Extract nvp map from icx_nvp_emmc.ko\n"); + printf(" -o/--output Set output file\n"); + printf(" -v/--verbose Enable debug output\n"); + printf(" -u/--unsafe Perform potentially unsafe operations\n"); + exit(1); +} + +struct zone_info_v1_t +{ + uint32_t node; + uint32_t start; + uint32_t count; + uint32_t size; + uint32_t semaphore[4]; /* a 16-byte structure, useless for us */ + uint32_t name; /* pointer to string */ +} __attribute__((packed)); + +struct zone_info_v2_t +{ + uint32_t node; + uint32_t start; + uint32_t count; + uint32_t size; + uint32_t semaphore[3]; /* a 12-byte structure, useless for us */ + uint32_t name; /* pointer to string */ +} __attribute__((packed)); + +struct area_info_v1_t +{ + uint32_t type; /* 1 = large, 2 = small */ + uint32_t zoneinfo; /* pointer to zone_info_t[] */ + uint32_t zonecount; + uint32_t semaphore[4]; /* a 16-byte structure, useless for us */ + uint32_t name; /* pointer to string */ +} __attribute__((packed)); + +struct area_info_v2_t +{ + uint32_t type; /* 1 = large, 2 = small */ + uint32_t zoneinfo; /* pointer to zone_info_t[] */ + uint32_t zonecount; + uint32_t semaphore[3]; /* a 16-byte structure, useless for us */ + uint32_t name; /* pointer to string */ +} __attribute__((packed)); + +int guess_version(void *area_info_ptr) +{ + /* the "semaphore" part is always filled with zeroes, so simply check if there + * are 3 or 4 of them */ + area_info_v1_t *ai_v1 = (area_info_v1_t *)area_info_ptr; + if(ai_v1->semaphore[3] == 0) + return 1; /* v1: semaphore has 4 fields */ + else + return 2; /* v2: semaphore has 3 fields */ +} + +int do_extract(const char *output, int argc, char **argv) +{ + if(argc != 1) + { + printf("You need to specify exactly one input file to extract from.\n"); + return 3; + } + FILE *fout = NULL; + if(output) + { + fout = fopen(output, "w"); + if(fout == NULL) + { + printf("Cannot open output file '%s'\n", output); + return 4; + } + } + /* read elf file */ + g_elf_buf = read_file(argv[0], g_elf_size); + if(g_elf_buf == nullptr) + { + printf("Cannot open input file '%s'\n", argv[0]); + return 1; + } + if(!elf_init()) + { + printf("This is not a valid ELF file\n"); + return 1; + } + if(g_elf_symtab == nullptr) + { + printf("This ELF file does not have a symbol table\n"); + return 1; + } + /* look for symbol 'AreaInfo' */ + Elf32_Sym *sym_AreaInfo = elf_get_symbol_by_name("AreaInfo"); + if(sym_AreaInfo == nullptr) + { + printf("Cannot find symbol 'AreaInfo'\n"); + return 1; + } + printf("AreaInfo:\n"); + if(g_verbose) + { + printf("[%u bytes at address %#x in section %u (%s)]\n", + (unsigned)sym_AreaInfo->st_size, (unsigned)sym_AreaInfo->st_value, + (unsigned)sym_AreaInfo->st_shndx, elf_get_section_name(sym_AreaInfo->st_shndx)); + } + /* guess version */ + int ver = guess_version(elf_get_symbol_ptr(sym_AreaInfo, sizeof(area_info_v1_t))); + if(g_verbose) + printf("[guessed version: %d]\n", ver); + size_t sizeof_area_info = (ver == 1) ? sizeof(area_info_v1_t) : sizeof(area_info_v2_t); + size_t sizeof_zone_info = (ver == 1) ? sizeof(zone_info_v1_t) : sizeof(zone_info_v2_t); + /* sanity check AreaInfo */ + size_t area_count = sym_AreaInfo->st_size / sizeof_area_info; + if(!g_unsafe && (sym_AreaInfo->st_size % sizeof_area_info) != 0) + { + printf("AreaInfo size (%u) is a not a multiple of area_info_t size (%zu).\n", + (unsigned)sym_AreaInfo->st_size, sizeof_area_info); + printf("Use unsafe option to override this check\n"); + return 1; + } + area_info_v1_t *AreaInfo_v1 = (area_info_v1_t *)elf_get_symbol_ptr(sym_AreaInfo, + sym_AreaInfo->st_size); + area_info_v2_t *AreaInfo_v2 = (area_info_v2_t *)AreaInfo_v1; + if(AreaInfo_v1 == nullptr) + { + printf("Symbol does not point to a valid address\n"); + return 1; + } + for(size_t i = 0; i < area_count; i++) + { + uint32_t type; + uint32_t *zoneinfo_ptr; + uint32_t zonecount; + uint32_t *name_ptr; + + if(ver == 1) + { + type = AreaInfo_v1[i].type; + zoneinfo_ptr = &AreaInfo_v1[i].zoneinfo; + zonecount = AreaInfo_v1[i].zonecount; + name_ptr = &AreaInfo_v1[i].name; + } + else + { + type = AreaInfo_v2[i].type; + zoneinfo_ptr = &AreaInfo_v2[i].zoneinfo; + zonecount = AreaInfo_v2[i].zonecount; + name_ptr = &AreaInfo_v2[i].name; + } + + if(g_verbose) + { + printf(" [type=%u info=%#x count=%u name=%#x]\n", type, *zoneinfo_ptr, + zonecount, *name_ptr); + } + /* translate name address */ + const char *name = (const char *)elf_reloc_addr32_ptr(name_ptr); + if(name == nullptr || !elf_is_str_ptr_safe(name)) + { + printf(" Entry name is not a string\n"); + continue; + } + /* skip reserved entries */ + if(*zoneinfo_ptr == 0) + { + printf(" %s\n", name); + continue; + } + /* relocate the zoneinfo pointer */ + void *Zone = elf_reloc_addr32_ptr(zoneinfo_ptr);; + if(Zone == nullptr) + { + printf(" %s\n", name); + printf(" Zone info pointer is not valid\n"); + continue; + } + /* in safe mode, make sure the zone info pointer is a symbol */ + Elf32_Sym *zoneinfo_sym = elf_get_symbol_by_ptr((void *)Zone); + const char *zoneinfo_sym_name = "<no symbol>"; + if(zoneinfo_sym) + zoneinfo_sym_name = elf_get_symbol_name(zoneinfo_sym); + printf(" %s (%s)\n", name, zoneinfo_sym_name); + if(!g_unsafe && !zoneinfo_sym) + { + printf(" Zone info pointer does not correspond to any symbol.\n"); + printf(" Use unsafe option to override this check\n"); + continue; + } + /* if we have the symbol, make sure the claimed size match */ + if(!g_unsafe && zoneinfo_sym) + { + if(zoneinfo_sym->st_size != sizeof_zone_info * zonecount) + { + printf(" Zone info symbol size (%u) does not match expected size (%zu)\n", + (unsigned)zoneinfo_sym->st_size, sizeof_zone_info * zonecount); + printf(" Use unsafe option to override this check\n"); + continue; + } + } + /* sanity check */ + if(!elf_is_ptr_safe((void *)Zone, sizeof_zone_info * zonecount)) + { + printf(" Zone info pointer is not valid\n"); + continue; + } + /* read zone */ + zone_info_v1_t *Zone_v1 = (zone_info_v1_t *)Zone; + zone_info_v2_t *Zone_v2 = (zone_info_v2_t *)Zone; + for(size_t j = 0; j < zonecount; j++) + { + uint32_t node, start, count, size; + uint32_t *name_ptr; + + if(ver == 1) + { + node = Zone_v1[j].node; + start = Zone_v1[j].start; + count = Zone_v1[j].count; + size = Zone_v1[j].size; + name_ptr = &Zone_v1[j].name; + } + else + { + node = Zone_v2[j].node; + start = Zone_v2[j].start; + count = Zone_v2[j].count; + size = Zone_v2[j].size; + name_ptr = &Zone_v2[j].name; + } + + if(g_verbose) + { + printf(" [node=%u start=%#x count=%u size=%u name=%#x]\n", + node, start, count, size, *name_ptr); + } + /* translate name address */ + const char *name = (const char *)elf_reloc_addr32_ptr(name_ptr); + if(name == nullptr || !elf_is_str_ptr_safe(name)) + { + printf(" Entry name is not a string\n"); + continue; + } + printf(" %s: node %03u, size %u\n", name, node, size); + if(fout) + fprintf(fout, "%u,%u,%s\n", node, size, name); + } + } + if(fout) + fclose(fout); + /* success */ + return 0; +} + +int main(int argc, char **argv) +{ + const char *output = NULL; + bool extract = false; + + if(argc <= 1) + usage(); + + while(1) + { + static struct option long_options[] = + { + {"help", no_argument, 0, 'h'}, + {"extract", no_argument, 0, 'x'}, + {"output", required_argument, 0, 'o'}, + {"verbose", no_argument, 0, 'v'}, + {"unsafe", no_argument, 0, 'u'}, + {0, 0, 0, 0} + }; + + int c = getopt_long(argc, argv, "hxo:vu", long_options, NULL); + if(c == -1) + break; + switch(c) + { + case -1: + break; + case 'h': + usage(); + break; + case 'o': + output = optarg; + break; + case 'x': + extract = true; + break; + case 'v': + g_verbose = true; + break; + case 'u': + g_unsafe = true; + break; + default: + abort(); + } + } + + if(extract) + return do_extract(output, argc - optind, argv + optind); + printf("You need to specify an operation. Run nvptool -h for help\n"); + return 1; +} |