| /* |
| * 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++; |
| } |
| |
| idx = 0; |
| for (const auto &ph : ph_entries) { |
| if (ph.filez) { |
| elf_bytes.resize(std::max(ph.offset + ph.filez, (uint32_t)elf_bytes.size())); |
| memcpy(&elf_bytes[ph.offset], &ph_data[idx][0], ph.filez); |
| } |
| 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 |
| } |
| } |
| } |
| |
| // 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++; |
| } |
| } |
| |
| void elf_file::read_ph_data(void) { |
| int ph_idx = 0; |
| ph_data.resize(eh.ph_num); |
| for (const auto &ph: ph_entries) { |
| if (ph.filez) { |
| ph_data[ph_idx].resize(ph.filez); |
| read_bytes(ph.offset, ph.filez, &ph_data[ph_idx][0]); |
| } |
| ph_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) |
| 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); |
| 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(); |
| read_ph_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 |
| read_ph_data(); |
| } |
| |
| 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 |
| read_ph_data(); |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| // 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; |
| ph_data.push_back(std::vector<uint8_t>(size)); |
| |
| 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; |
| } |