| /* | |
| * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. | |
| * | |
| * SPDX-License-Identifier: BSD-3-Clause | |
| */ | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <cstdio> | |
| #include <cstdarg> | |
| #include <cstring> | |
| #include <fstream> | |
| #include <iostream> | |
| #include <iterator> | |
| #include <sstream> | |
| #include <string> | |
| #include <vector> | |
| #include <memory> | |
| #include "elf.h" | |
| #include "elf_file.h" | |
| #include "errors.h" | |
| #include "portable_endian.h" | |
| // tsk namespace is polluted on windows | |
| #ifdef _WIN32 | |
| #undef min | |
| #undef max | |
| #define _CRT_SECURE_NO_WARNINGS | |
| #endif | |
| void eh_he(elf32_header &eh) { | |
| // Swap to host endianness | |
| eh.common.magic = le32toh(eh.common.magic); | |
| eh.common.type = le16toh(eh.common.type); | |
| eh.common.machine = le16toh(eh.common.machine); | |
| eh.common.version2 = le32toh(eh.common.version2); | |
| eh.entry = le32toh(eh.entry); | |
| eh.ph_offset = le32toh(eh.ph_offset); | |
| eh.sh_offset = le32toh(eh.sh_offset); | |
| eh.flags = le32toh(eh.flags); | |
| eh.eh_size = le16toh(eh.eh_size); | |
| eh.ph_entry_size = le16toh(eh.ph_entry_size); | |
| eh.ph_num = le16toh(eh.ph_num); | |
| eh.sh_entry_size = le16toh(eh.sh_entry_size); | |
| eh.sh_num = le16toh(eh.sh_num); | |
| eh.sh_str_index = le16toh(eh.sh_str_index); | |
| } | |
| void eh_le(elf32_header &eh) { | |
| // Swap to little endianness | |
| eh.common.magic = htole32(eh.common.magic); | |
| eh.common.type = htole16(eh.common.type); | |
| eh.common.machine = htole16(eh.common.machine); | |
| eh.common.version2 = htole32(eh.common.version2); | |
| eh.entry = htole32(eh.entry); | |
| eh.ph_offset = htole32(eh.ph_offset); | |
| eh.sh_offset = htole32(eh.sh_offset); | |
| eh.flags = htole32(eh.flags); | |
| eh.eh_size = htole16(eh.eh_size); | |
| eh.ph_entry_size = htole16(eh.ph_entry_size); | |
| eh.ph_num = htole16(eh.ph_num); | |
| eh.sh_entry_size = htole16(eh.sh_entry_size); | |
| eh.sh_num = htole16(eh.sh_num); | |
| eh.sh_str_index = htole16(eh.sh_str_index); | |
| } | |
| void ph_he(elf32_ph_entry &ph) { | |
| // Swap to host endianness | |
| ph.type = le32toh(ph.type); | |
| ph.offset = le32toh(ph.offset); | |
| ph.vaddr = le32toh(ph.vaddr); | |
| ph.paddr = le32toh(ph.paddr); | |
| ph.filez = le32toh(ph.filez); | |
| ph.memsz = le32toh(ph.memsz); | |
| ph.flags = le32toh(ph.flags); | |
| ph.align = le32toh(ph.align); | |
| } | |
| void ph_le(elf32_ph_entry &ph) { | |
| // Swap to little endianness | |
| ph.type = htole32(ph.type); | |
| ph.offset = htole32(ph.offset); | |
| ph.vaddr = htole32(ph.vaddr); | |
| ph.paddr = htole32(ph.paddr); | |
| ph.filez = htole32(ph.filez); | |
| ph.memsz = htole32(ph.memsz); | |
| ph.flags = htole32(ph.flags); | |
| ph.align = htole32(ph.align); | |
| } | |
| void sh_he(elf32_sh_entry &sh) { | |
| // Swap to host endianness | |
| sh.name = le32toh(sh.name); | |
| sh.type = le32toh(sh.type); | |
| sh.flags = le32toh(sh.flags); | |
| sh.addr = le32toh(sh.addr); | |
| sh.offset = le32toh(sh.offset); | |
| sh.size = le32toh(sh.size); | |
| sh.link = le32toh(sh.link); | |
| sh.info = le32toh(sh.info); | |
| sh.addralign = le32toh(sh.addralign); | |
| sh.entsize = le32toh(sh.entsize); | |
| } | |
| void sh_le(elf32_sh_entry &sh) { | |
| // Swap to little endianness | |
| sh.name = htole32(sh.name); | |
| sh.type = htole32(sh.type); | |
| sh.flags = htole32(sh.flags); | |
| sh.addr = htole32(sh.addr); | |
| sh.offset = htole32(sh.offset); | |
| sh.size = htole32(sh.size); | |
| sh.link = htole32(sh.link); | |
| sh.info = htole32(sh.info); | |
| sh.addralign = htole32(sh.addralign); | |
| sh.entsize = htole32(sh.entsize); | |
| } | |
| void sym_he(elf32_sym_entry &sym) { | |
| // Swap to host endianness | |
| sym.name = le32toh(sym.name); | |
| sym.value = le32toh(sym.value); | |
| sym.size = le32toh(sym.size); | |
| sym.info = le32toh(sym.info); | |
| sym.other = le32toh(sym.other); | |
| sym.shndx = le32toh(sym.shndx); | |
| } | |
| void sym_le(elf32_sym_entry &sym) { | |
| // Swap to little endianness | |
| sym.name = htole32(sym.name); | |
| sym.value = htole32(sym.value); | |
| sym.size = htole32(sym.size); | |
| sym.info = htole32(sym.info); | |
| sym.other = htole32(sym.other); | |
| sym.shndx = htole32(sym.shndx); | |
| } | |
| // Checks whether an ELF header is compatible with RP2040 / RP3050 | |
| // Returns zero on success | |
| int rp_check_elf_header(const elf32_header &eh) { | |
| if (eh.common.magic != ELF_MAGIC) { | |
| fail(ERROR_FORMAT, "Not an ELF file"); | |
| } | |
| if (eh.common.version != 1 || eh.common.version2 != 1) { | |
| fail(ERROR_FORMAT, "Unrecognized ELF version"); | |
| } | |
| if (eh.common.arch_class != 1 || eh.common.endianness != 1) { | |
| fail(ERROR_INCOMPATIBLE, "Require 32 bit little-endian ELF"); | |
| } | |
| if (eh.eh_size != sizeof(struct elf32_header)) { | |
| fail(ERROR_FORMAT, "Invalid ELF32 format"); | |
| } | |
| if (eh.common.machine != EM_ARM && eh.common.machine != EM_RISCV) { | |
| fail(ERROR_FORMAT, "Not an Arm or RISC-V executable"); | |
| } | |
| // Accept either ELFOSABI_NONE or ELFOSABI_GNU for EI_OSABI. Compilers may | |
| // set the OS/ABI field to ELFOSABI_GNU when they use GNU features, such as | |
| // the SHF_GNU_RETAIN section flag, but the binary is still compatible. | |
| if (eh.common.abi != 0 /* NONE */ && eh.common.abi != 3 /* GNU */) { | |
| fail(ERROR_INCOMPATIBLE, "Unrecognized ABI"); | |
| } | |
| // todo amy not sure if this should be expected or not - we have HARD float in clang only for now | |
| if (eh.flags & EF_ARM_ABI_FLOAT_HARD) { | |
| // fail(ERROR_INCOMPATIBLE, "HARD-FLOAT not supported"); | |
| } | |
| return 0; | |
| } | |
| // Determine binary type (flash or ram) | |
| int rp_determine_binary_type(const elf32_header &eh, const std::vector<elf32_ph_entry>& entries, address_ranges flash_range, address_ranges ram_range, bool *ram_style) { | |
| for(const auto &entry : entries) { | |
| if (entry.type == PT_LOAD && entry.memsz) { | |
| unsigned int mapped_size = std::min(entry.filez, entry.memsz); | |
| if (mapped_size) { | |
| // we back convert the entrypoint from a VADDR to a PADDR to see if it originates in flash, and if | |
| // so call THAT a flash binary. | |
| if (eh.entry >= entry.vaddr && eh.entry < entry.vaddr + mapped_size) { | |
| uint32_t effective_entry = eh.entry + entry.paddr - entry.vaddr; | |
| if (is_address_initialized(ram_range, effective_entry)) { | |
| *ram_style = true; | |
| return 0; | |
| } else if (is_address_initialized(flash_range, effective_entry)) { | |
| *ram_style = false; | |
| return 0; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| fail(ERROR_INCOMPATIBLE, "entry point is not in mapped part of file"); | |
| return ERROR_INCOMPATIBLE; | |
| } | |
| void elf_file::read_bytes(unsigned offset, unsigned length, void *dest) { | |
| if (offset + length > elf_bytes.size()) { | |
| fail(ERROR_FORMAT, "ELF File Read from 0x%x with size 0x%x exceeds the file size 0x%zx", offset, length, elf_bytes.size()); | |
| } | |
| memcpy(dest, &elf_bytes[offset], length); | |
| } | |
| int elf_file::read_header(void) { | |
| read_bytes(0, sizeof(eh), &eh); | |
| eh_he(eh); // swap to Host for processing | |
| return rp_check_elf_header(eh); | |
| } | |
| // Flattens the data in the section array the elf_bytes blob | |
| void elf_file::flatten(void) { | |
| elf_bytes.resize(sizeof(eh)); | |
| auto eh_out = eh; | |
| eh_le(eh_out); // swap to LE for writing | |
| memcpy(&elf_bytes[0], &eh_out, sizeof(eh_out)); | |
| elf_bytes.resize(std::max(eh.ph_offset + sizeof(elf32_ph_entry) * eh.ph_num, elf_bytes.size())); | |
| auto ph_entries_out = ph_entries; | |
| for (auto ph : ph_entries_out) { | |
| ph_le(ph); // swap to LE for writing | |
| } | |
| memcpy(&elf_bytes[eh.ph_offset], &ph_entries_out[0], sizeof(elf32_ph_entry) * eh.ph_num); | |
| elf_bytes.resize(std::max(eh.sh_offset + sizeof(elf32_sh_entry) * eh.sh_num, elf_bytes.size())); | |
| auto sh_entries_out = sh_entries; | |
| for (auto sh : sh_entries_out) { | |
| sh_le(sh); // swap to LE for writing | |
| } | |
| memcpy(&elf_bytes[eh.sh_offset], &sh_entries_out[0], sizeof(elf32_sh_entry) * eh.sh_num); | |
| int idx = 0; | |
| for (const auto &sh : sh_entries) { | |
| if (sh.size && sh.type != SHT_NOBITS) { | |
| elf_bytes.resize(std::max(sh.offset + sh.size, (uint32_t)elf_bytes.size())); | |
| memcpy(&elf_bytes[sh.offset], &sh_data[idx][0], sh.size); | |
| } | |
| idx++; | |
| } | |
| if (verbose) printf("Elf file size %zu\n", elf_bytes.size()); | |
| } | |
| void elf_file::write(std::shared_ptr<std::iostream> out) { | |
| flatten(); | |
| out->exceptions(std::iostream::failbit | std::iostream::badbit); | |
| if (verbose) printf("Writing %lu bytes to file\n", elf_bytes.size()); | |
| out->write(reinterpret_cast<const char*>(&elf_bytes[0]), elf_bytes.size()); | |
| } | |
| void elf_file::read_sh(void) { | |
| if (verbose) printf("%s sh offset %u #entries %d\n", __func__, eh.sh_offset, eh.sh_num); | |
| if (eh.sh_num) { | |
| sh_entries.resize(eh.sh_num); | |
| read_bytes(eh.sh_offset, sizeof(elf32_sh_entry) * eh.sh_num, &sh_entries[0]); | |
| for (auto sh : sh_entries) { | |
| sh_he(sh); // swap to Host for processing | |
| } | |
| } | |
| } | |
| // If there are holes between segments, increase the segment size to plug the hole | |
| // This is necessary to ensure that segments are contiguous, which is required for encrypting, | |
| // but this is not necessary for signing/hashing | |
| void elf_file::remove_ph_holes(void) { | |
| auto sorted_ph_entries = sorted_segments(); | |
| for (int i=0; i+1 < ph_entries.size(); i++) { | |
| auto ph0 = &(ph_entries[i]); | |
| const elf32_ph_entry* ph1 = nullptr; | |
| // ph1 is the next segment by address | |
| for (int j=0; j+1 < sorted_ph_entries.size(); j++) { | |
| auto tst_ph0 = sorted_ph_entries[j]; | |
| if (tst_ph0->offset == ph0->offset) { | |
| ph1 = sorted_ph_entries[j+1]; | |
| } | |
| } | |
| if (ph1 == nullptr) { | |
| // ph0 was the last segment by address - so no gap to plug after it | |
| continue; | |
| } | |
| // ph1file is the next segment in the file | |
| elf32_ph_entry ph1file = ph_entries[i+1]; | |
| if ( | |
| (ph0->type == PT_LOAD && ph1->type == PT_LOAD) | |
| && (ph0->filez && ph1->filez) | |
| && (ph0->paddr + ph0->filez < ph1->paddr) | |
| ) { | |
| uint32_t gap = ph1->paddr - ph0->paddr - ph0->filez; | |
| if (gap > ph1->align) { | |
| fail(ERROR_INCOMPATIBLE, "Segment %d: Cannot plug gap greater than alignment - gap %d, alignment %d", i, gap, ph1->align); | |
| } | |
| if (ph0->offset + ph0->filez + gap > ph1file.offset) { | |
| fail(ERROR_INCOMPATIBLE, "Segment %d: Cannot plug gap without space in file - gap %d", i, gap); | |
| } | |
| if (verbose) printf("Segment %d: Moving end from 0x%08x to 0x%08x to plug gap\n", i, ph0->paddr + ph0->filez, ph1->paddr); | |
| ph0->filez = ph1->paddr - ph0->paddr; | |
| ph0->memsz = ph0->filez; | |
| } | |
| } | |
| } | |
| // If there are holes within segments, increase the section size to plug the hole | |
| // This is necessary to ensure the whole segment contains data defined in sections, otherwise you end up | |
| // signing/hashing/encrypting data that may not be written, as many tools write in sections not segments | |
| void elf_file::remove_sh_holes(void) { | |
| bool found_hole = false; | |
| for (int i=0; i+1 < sh_entries.size(); i++) { | |
| auto sh0 = &(sh_entries[i]); | |
| elf32_sh_entry sh1 = sh_entries[i+1]; | |
| if ( | |
| (sh0->type == SHT_PROGBITS && sh1.type == SHT_PROGBITS) | |
| && (sh0->size && sh1.size) | |
| && (sh0->addr + sh0->size < sh1.addr) | |
| && (segment_from_section(*sh0) == segment_from_section(sh1)) | |
| && segment_from_section(*sh0) != NULL | |
| ) { | |
| uint32_t gap = sh1.addr - sh0->addr - sh0->size; | |
| if (gap > sh1.addralign) { | |
| fail(ERROR_INCOMPATIBLE, "Section %d: Cannot plug gap greater than alignment - gap %d, alignment %d", i, gap, sh1.addralign); | |
| } | |
| if (verbose) printf("Section %d: Moving end from 0x%08x to 0x%08x to plug gap\n", i, sh0->addr + sh0->size, sh1.addr); | |
| sh0->size = sh1.addr - sh0->addr; | |
| found_hole = true; | |
| } else if ( | |
| (sh0->type == SHT_PROGBITS) | |
| && (segment_from_section(*sh0) != segment_from_section(sh1)) | |
| && segment_from_section(*sh0) != NULL | |
| && (sh0->offset + sh0->size < segment_from_section(*sh0)->offset + segment_from_section(*sh0)->filez) | |
| ) { | |
| const elf32_ph_entry *seg = segment_from_section(*sh0); | |
| uint32_t gap = seg->offset + seg->filez - sh0->offset - sh0->size; | |
| if (verbose) printf("Section %d: Moving end from 0x%08x to 0x%08x to plug gap at end of segment\n", i, sh0->addr + sh0->size, seg->offset + seg->filez); | |
| sh0->size = seg->offset + seg->filez - sh0->offset; | |
| found_hole = true; | |
| } | |
| } | |
| if (found_hole) read_sh_data(); | |
| } | |
| void elf_file::remove_empty_ph_entries(void) { | |
| for (int i = 0; i < ph_entries.size(); i++) { | |
| if (ph_entries[i].filez == 0) { | |
| ph_entries.erase(ph_entries.begin() + i); | |
| eh.ph_num--; i--; | |
| } | |
| } | |
| } | |
| // Read the section data from the internal byte array into discrete sections. | |
| // This is used after modifying segments but before inserting new segments | |
| void elf_file::read_sh_data(void) { | |
| int sh_idx = 0; | |
| sh_data.resize(eh.sh_num); | |
| for (const auto &sh: sh_entries) { | |
| if (sh.size && sh.type != SHT_NOBITS) { | |
| sh_data[sh_idx].resize(sh.size); | |
| read_bytes(sh.offset, sh.size, &sh_data[sh_idx][0]); | |
| } | |
| sh_idx++; | |
| } | |
| } | |
| const std::string elf_file::section_name(uint32_t sh_name) const { | |
| if (!eh.sh_str_index || eh.sh_str_index > eh.sh_num || eh.sh_str_index >= sh_data.size()) | |
| return ""; | |
| if (sh_name > sh_data[eh.sh_str_index].size()) | |
| return ""; | |
| const char * str =(const char *) &sh_data[eh.sh_str_index][0]; | |
| return &str[sh_name]; | |
| } | |
| const elf32_sh_entry* elf_file::get_section(const std::string &sh_name) { | |
| for (unsigned int i = 0; i < sh_entries.size(); i++) { | |
| if (section_name(sh_entries[i].name) == sh_name) { | |
| return &sh_entries[i]; | |
| } | |
| } | |
| return NULL; | |
| } | |
| uint32_t elf_file::get_symbol(const std::string &sym_name) { | |
| auto sym_tab = get_section(".symtab"); | |
| auto str_tab = get_section(".strtab"); | |
| if (!sym_tab || !str_tab) { | |
| return 0; | |
| } | |
| auto data = content(*sym_tab); | |
| auto strings = content(*str_tab); | |
| const char * str =(const char *) strings.data(); | |
| for (unsigned int i=0; i < sym_tab->size / sizeof(elf32_sym_entry); i++) { | |
| elf32_sym_entry sym; | |
| memcpy(&sym, data.data() + i*sizeof(elf32_sym_entry), sizeof(elf32_sym_entry)); | |
| sym_he(sym); // swap to Host for processing | |
| if (&str[sym.name] == sym_name) { | |
| return sym.value; | |
| } | |
| } | |
| return 0; | |
| } | |
| uint32_t elf_file::append_section_name(const std::string &sh_name_str) { | |
| // Create byte array with new section name | |
| std::vector<uint8_t> name_bytes(sh_name_str.begin(), sh_name_str.end()); | |
| name_bytes.push_back(0); | |
| // Append the byte array to section header table remembering the offset | |
| // of the start of the string for the new section | |
| elf32_sh_entry &shstrtab = sh_entries[eh.sh_str_index]; | |
| std::vector<uint8_t> &shstrtab_data = sh_data[eh.sh_str_index]; | |
| sh_entries[eh.sh_str_index].size += name_bytes.size(); | |
| uint32_t sh_name = shstrtab_data.size(); | |
| shstrtab_data.insert(shstrtab_data.end(), name_bytes.begin(), name_bytes.end()); | |
| // Move offsets for anything stored after the resized section header table | |
| for (auto &sh: sh_entries) { | |
| if (sh.offset > shstrtab.offset) | |
| sh.offset += name_bytes.size(); | |
| } | |
| for (auto &ph: ph_entries) { | |
| if (ph.offset > shstrtab.offset) | |
| ph.offset += name_bytes.size(); | |
| } | |
| return sh_name; | |
| } | |
| void elf_file::dump(void) const { | |
| for (const auto &ph: ph_entries) { | |
| printf("PH offset %08x vaddr %08x paddr %08x size %08x type %08x\n", | |
| ph.offset, ph.vaddr, ph.paddr, ph.memsz, ph.type); | |
| } | |
| int sh_idx = 0; | |
| for (const auto &sh: sh_entries) { | |
| printf("SH[%d] %20s addr %08x offset %08x size %08x type %08x\n", | |
| sh_idx, section_name(sh.name).c_str(), sh.addr, sh.offset, sh.size, sh.type); | |
| sh_idx++; | |
| } | |
| } | |
| void elf_file::move_all(int dist) { | |
| if (verbose) printf("Incrementing all paddr by %d\n", dist); | |
| // Remove empty ph entries, as they will likely be moved to invalid addresses | |
| remove_empty_ph_entries(); | |
| for (auto &ph: ph_entries) { | |
| ph.paddr += dist; | |
| } | |
| } | |
| void elf_file::read_ph(void) { | |
| if (verbose) printf("%s ph offset %u #entries %d\n", __func__, eh.ph_offset, eh.ph_num); | |
| if (eh.ph_num) { | |
| ph_entries.resize(eh.ph_num); | |
| read_bytes(eh.ph_offset, sizeof(elf32_ph_entry) * eh.ph_num, &ph_entries[0]); | |
| for (auto ph : ph_entries) { | |
| ph_he(ph); // swap to Host for processing | |
| } | |
| } | |
| } | |
| int elf_file::read_file(std::shared_ptr<std::iostream> file) { | |
| int rc = 0; | |
| try { | |
| elf_bytes = read_binfile(file); | |
| int rc = read_header(); | |
| if (!rc) { | |
| read_ph(); | |
| read_sh(); | |
| } | |
| read_sh_data(); | |
| } | |
| catch (const std::ios_base::failure &e) { | |
| std::cerr << "Failed to read elf file" << std::endl; | |
| rc = -1; | |
| } | |
| return rc; | |
| } | |
| uint32_t elf_file::lowest_section_offset(void) const { | |
| uint32_t offset = eh.sh_offset; // Section header offset is after the data | |
| for (const auto &sh: sh_entries) { | |
| if (sh.type != SHT_NULL && sh.offset > 0 && sh.offset < offset) { | |
| offset = sh.offset; | |
| } | |
| } | |
| return offset; | |
| } | |
| uint32_t elf_file::highest_section_offset(void) const { | |
| uint32_t offset = 0; // Section header offset is after the data | |
| for (const auto &sh: sh_entries) { | |
| if (sh.type != SHT_NULL && sh.offset > 0 && sh.offset >= offset) { | |
| offset = sh.offset + sh.size; | |
| } | |
| } | |
| return offset; | |
| } | |
| std::vector<uint8_t> elf_file::content(const elf32_ph_entry &ph) const { | |
| std::vector<uint8_t> content; | |
| std::copy(elf_bytes.begin() + ph.offset, elf_bytes.begin() + ph.offset + ph.filez, std::back_inserter(content)); | |
| return content; | |
| } | |
| std::vector<uint8_t> elf_file::content(const elf32_sh_entry &sh) const { | |
| std::vector<uint8_t> content; | |
| std::copy(elf_bytes.begin() + sh.offset, elf_bytes.begin() + sh.offset + sh.size, std::back_inserter(content)); | |
| return content; | |
| } | |
| void elf_file::content(const elf32_ph_entry &ph, const std::vector<uint8_t> &content) { | |
| if (!editable) return; | |
| assert(content.size() <= ph.filez); | |
| if (verbose) printf("Update segment content offset %x content size %zx physical size %x\n", ph.offset, content.size(), ph.filez); | |
| memcpy(&elf_bytes[ph.offset], &content[0], std::min(content.size(), (size_t) ph.filez)); | |
| read_sh_data(); // Extract the sections after modifying the content | |
| } | |
| void elf_file::content(const elf32_sh_entry &sh, const std::vector<uint8_t> &content) { | |
| if (!editable) return; | |
| assert(content.size() <= sh.size); | |
| if (verbose) printf("Update section content offset %x content size %zx section size %x\n", sh.offset, content.size(), sh.size); | |
| memcpy(&elf_bytes[sh.offset], &content[0], std::min(content.size(), (size_t) sh.size)); | |
| read_sh_data(); // Extract the sections after modifying the content | |
| } | |
| const elf32_ph_entry* elf_file::segment_from_physical_address(uint32_t paddr) { | |
| for (int i = 0; i < eh.ph_num; i++) { | |
| if (paddr >= ph_entries[i].paddr && paddr < ph_entries[i].paddr + ph_entries[i].filez) { | |
| if (verbose) printf("segment %d contains physical address %x\n", i, paddr); | |
| return &ph_entries[i]; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| bool elf_file::physical_address_range_is_occupied(uint32_t paddr, uint32_t psize) { | |
| uint32_t pstart = paddr; | |
| uint32_t pend = paddr + psize; | |
| for (int i = 0; i < eh.ph_num; i++) { | |
| if (!ph_entries[i].is_load() || ph_entries[i].filez == 0) continue; // allow overlapping with non-loaded segments | |
| if (pstart < ph_entries[i].paddr + ph_entries[i].filez && ph_entries[i].paddr < pend) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| const elf32_ph_entry* elf_file::segment_from_virtual_address(uint32_t vaddr) { | |
| for (int i = 0; i < eh.ph_num; i++) { | |
| if (vaddr >= ph_entries[i].vaddr && vaddr < ph_entries[i].vaddr + ph_entries[i].memsz) { | |
| if (verbose) printf("segment %d contains virtual address %x\n", i, vaddr); | |
| return &ph_entries[i]; | |
| } | |
| } | |
| return NULL; | |
| } | |
| const elf32_ph_entry* elf_file::segment_from_section(const elf32_sh_entry &sh) { | |
| for (int i = 0; i < eh.ph_num; i++) { | |
| if (sh.offset >= ph_entries[i].offset && sh.offset < ph_entries[i].offset + ph_entries[i].filez) { | |
| if (verbose) printf("segment %d contains section %s\n", i, section_name(sh.name).c_str()); | |
| return &ph_entries[i]; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| std::vector<elf32_ph_entry *> elf_file::sorted_segments_modifiable(void) { | |
| std::vector<elf32_ph_entry *> phys_sorted_segs; | |
| std::transform(ph_entries.begin(), ph_entries.end(), std::back_inserter(phys_sorted_segs), [](elf32_ph_entry &seg) { | |
| return &seg; | |
| }); | |
| std::sort(phys_sorted_segs.begin(), phys_sorted_segs.end(), [](elf32_ph_entry *first, elf32_ph_entry *second) { | |
| return first->physical_address() < second->physical_address(); | |
| }); | |
| return phys_sorted_segs; | |
| } | |
| std::vector<const elf32_ph_entry *> elf_file::sorted_segments(void) { | |
| std::vector<const elf32_ph_entry *> const_sorted_segs; | |
| auto sorted_segs = sorted_segments_modifiable(); | |
| std::transform(sorted_segs.begin(), sorted_segs.end(), std::back_inserter(const_sorted_segs), [](const elf32_ph_entry *seg) { | |
| return (const elf32_ph_entry *)seg; | |
| }); | |
| return const_sorted_segs; | |
| } | |
| void elf_file::store_squashed(model_t model, const std::vector<uint32_t> &pinned_addresses) { | |
| for(const auto &seg : sorted_segments()) { | |
| const uint32_t paddr = seg->physical_address(); | |
| const uint32_t psize = seg->physical_size(); | |
| if (psize == 0) continue; | |
| if (paddr >= model->flash_start() && paddr < model->flash_end()) { | |
| // cannot squash flash binaries | |
| return; | |
| } | |
| } | |
| uint32_t last_seg_end = 0; | |
| std::vector<elf32_ph_entry *> xip_sram_segs = {}; | |
| auto contains_pinned_address = [&](const elf32_ph_entry *seg) { | |
| for (uint32_t addr : pinned_addresses) { | |
| if (addr >= seg->physical_address() && addr < seg->physical_address() + seg->physical_size()) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| auto squash_seg = [&](elf32_ph_entry *seg) { | |
| const uint32_t paddr = seg->physical_address(); | |
| const uint32_t psize = seg->physical_size(); | |
| if (!seg->is_load()) return; | |
| if (psize == 0) return; | |
| if (last_seg_end) { | |
| if (paddr != last_seg_end && !contains_pinned_address(seg) && !physical_address_range_is_occupied(last_seg_end, psize)) { | |
| if (verbose) printf("squashing %08x to %08x\n", paddr, last_seg_end); | |
| seg->paddr = last_seg_end; | |
| } | |
| } | |
| // May have been modified, so read again. Use max so last_seg_end never moves backwards. | |
| last_seg_end = std::max(last_seg_end, seg->physical_address() + seg->physical_size()); | |
| }; | |
| for(auto &seg : sorted_segments_modifiable()) { | |
| const uint32_t paddr = seg->physical_address(); | |
| if (paddr >= model->xip_sram_start() && paddr < model->xip_sram_end()) { | |
| // XIP SRAM segments go at end, as we assume either the entry point | |
| // is in main SRAM, or there is no main SRAM | |
| xip_sram_segs.push_back(seg); | |
| continue; | |
| } | |
| squash_seg(seg); | |
| } | |
| for (auto &seg : xip_sram_segs) { | |
| squash_seg(seg); | |
| } | |
| } | |
| // Appends a new segment and section - filled with zeros | |
| // Use content to replace the content | |
| const elf32_ph_entry& elf_file::append_segment(uint32_t vaddr, uint32_t paddr, uint32_t size, const std::string &name) { | |
| elf32_ph_entry ph; | |
| read_sh_data(); // Convert the section data back into discreet chunks | |
| uint32_t sh_name = append_section_name(name); | |
| ph.type = PT_LOAD; | |
| ph.flags = PF_R; // Readable segment | |
| ph.paddr = paddr; | |
| ph.vaddr = vaddr; | |
| ph.filez = size; | |
| ph.memsz = size; | |
| ph.align = 2; | |
| if (verbose) { | |
| std::cout << "new segment " << name << | |
| " paddr " << std::hex << paddr << | |
| " vaddr " << std::hex << vaddr << | |
| " size " << std::hex << size << std::endl; | |
| } | |
| ph_entries.push_back(ph); | |
| eh.ph_num++; | |
| // There's normally space between the end of the program header table and the start of data to | |
| // squeeze in another program header. If not, shuffle everything by 4K; | |
| uint32_t lso = lowest_section_offset(); | |
| if (lso < eh.ph_offset + eh.ph_entry_size * eh.ph_num) { | |
| // Move the segment offsets | |
| for (auto &ph: ph_entries) { | |
| ph.offset += 0x1000; | |
| } | |
| // Move section header table and each section offset | |
| eh.sh_offset += 0x1000; | |
| for (auto &sh : sh_entries) { | |
| sh.offset += 0x1000; | |
| } | |
| } | |
| // Append the new signature section | |
| elf32_sh_entry sh = {}; | |
| sh.name = sh_name; | |
| sh.type = SHT_PROGBITS; | |
| sh.flags = SHF_ALLOC; | |
| sh.addr = ph.vaddr; | |
| sh.size = size; | |
| uint32_t hso = highest_section_offset(); | |
| sh.offset = hso; | |
| // Add the new segment for the signature and point to offset in file for data | |
| sh_entries.push_back(sh); | |
| sh_data.push_back(std::vector<uint8_t>(size)); | |
| ph_entries.back().offset = sh.offset; | |
| eh.sh_offset = sh.offset + sh.size; | |
| eh.sh_num++; | |
| if (verbose) printf("%s sig offset %08x num sections %u\n", __func__, sh.offset, eh.sh_num); | |
| flatten(); | |
| return ph_entries.back(); | |
| } | |
| std::vector<uint8_t> elf_file::read_binfile(std::shared_ptr<std::iostream> in) { | |
| std::vector<uint8_t> data; | |
| in->exceptions(std::iostream::failbit | std::iostream::badbit); | |
| in->seekg(0, in->end); | |
| data.resize(in->tellg()); | |
| in->seekg(0, in->beg); | |
| in->read(reinterpret_cast<char *>(&data[0]), data.size()); | |
| return data; | |
| } |