diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a7dd2e02..51629a558 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ find_package(yaml-cpp REQUIRED) find_package(Catch2 QUIET) find_package(Threads REQUIRED) find_package(ZLIB REQUIRED) +find_package(elfio QUIET) if(MSVC) add_compile_options(/vmg /MP /W3 /wd4244 /wd4267 /wd4996 -DNOMINMAX /EHsc) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index d846df610..5cc6961bd 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,9 +1,12 @@ project(scc-util VERSION 0.0.1 LANGUAGES CXX) -set(SRC util/io-redirector.cpp util/watchdog.cpp util/ihex_parser.cpp) +set(SRC util/io-redirector.cpp util/watchdog.cpp util/ihex.cpp) if(TARGET lz4::lz4) list(APPEND SRC util/lz4_streambuf.cpp) endif() +if(TARGET elfio::elfio) + list(APPEND SRC util/elf.cpp) +endif() add_library(${PROJECT_NAME} ${SRC}) add_library(scc::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) @@ -18,6 +21,9 @@ endif() if(TARGET lz4::lz4) target_link_libraries(${PROJECT_NAME} PUBLIC lz4::lz4) endif() +if(TARGET elfio::elfio) + target_link_libraries(${PROJECT_NAME} PRIVATE elfio::elfio) +endif() if(CLANG_TIDY_EXE) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_CLANG_TIDY "${DO_CLANG_TIDY}" ) diff --git a/src/common/util/elf.cpp b/src/common/util/elf.cpp new file mode 100644 index 000000000..6738560e0 --- /dev/null +++ b/src/common/util/elf.cpp @@ -0,0 +1,73 @@ +#include "elf.h" +#include +#include +#include + +namespace util { +uint64_t load_elf_file(std::string const& name, std::function cb, uint8_t expected_elf_class, + uint16_t expected_elf_machine) { + // Create elfio reader + ELFIO::elfio reader; + // Load ELF data + if(!reader.load(name)) + throw std::runtime_error("Could not load file"); + // check elf properties + if(reader.get_class() != expected_elf_class) + throw std::runtime_error("ELF Class missmatch"); + if(reader.get_type() != ELFIO::ET_EXEC && reader.get_type() != ELFIO::ET_DYN) + throw std::runtime_error("Input is neither an executable nor a pie executable (dyn)"); + if(reader.get_machine() != expected_elf_machine) + throw std::runtime_error("ELF Machine type missmatch"); + auto entry_address = reader.get_entry(); + for(const auto& pseg : reader.segments) { + const auto fsize = pseg->get_file_size(); // 0x42c/0x0 + const auto seg_data = pseg->get_data(); + const auto type = pseg->get_type(); + if(type == ELFIO::PT_LOAD && fsize > 0) { + if(cb(pseg->get_physical_address(), fsize, reinterpret_cast(seg_data))) { + std::ostringstream oss; + oss << "Problem writing " << fsize << " bytes to 0x" << std::hex << pseg->get_physical_address(); + throw std::runtime_error(oss.str()); + } + } + } + return entry_address; +}; + +std::unordered_map read_elf_symbols(std::string const& name, uint8_t expected_elf_class, + uint16_t expected_elf_machine) { + // Create elfio reader + ELFIO::elfio reader; + // Load ELF data + if(!reader.load(name)) + throw std::runtime_error("Could not load file"); + // check elf properties + if(reader.get_class() != expected_elf_class) + throw std::runtime_error("ELF Class missmatch"); + if(reader.get_type() != ELFIO::ET_EXEC && reader.get_type() != ELFIO::ET_DYN) + throw std::runtime_error("Input is neither an executable nor a pie executable (dyn)"); + if(reader.get_machine() != expected_elf_machine) + throw std::runtime_error("ELF Machine type missmatch"); + std::unordered_map symbol_table; + const auto sym_sec = reader.sections[".symtab"]; + if(ELFIO::SHT_SYMTAB == sym_sec->get_type() || ELFIO::SHT_DYNSYM == sym_sec->get_type()) { + ELFIO::symbol_section_accessor symbols(reader, sym_sec); + auto sym_no = symbols.get_symbols_num(); + std::string name; + ELFIO::Elf64_Addr value = 0; + ELFIO::Elf_Xword size = 0; + unsigned char bind = 0; + unsigned char type = 0; + ELFIO::Elf_Half section = 0; + unsigned char other = 0; + for(auto i = 0U; i < sym_no; ++i) { + symbols.get_symbol(i, name, value, size, bind, type, section, other); + if(name != "") { + symbol_table[name] = value; + } + } + } + return std::move(symbol_table); +}; + +} // namespace util diff --git a/src/common/util/elf.h b/src/common/util/elf.h new file mode 100644 index 000000000..23037e289 --- /dev/null +++ b/src/common/util/elf.h @@ -0,0 +1,14 @@ +#ifndef UTIL_ELF_IO_H_ +#define UTIL_ELF_IO_H_ +#include +#include +#include +#include + +namespace util { +uint64_t load_elf_file(std::string const& name, std::function, + uint8_t expected_elf_class = ELFIO::ELFCLASS32, uint16_t expected_elf_machine = ELFIO::EM_RISCV); +std::unordered_map read_elf_symbols(std::string const& name, uint8_t expected_elf_class = ELFIO::ELFCLASS32, + uint16_t expected_elf_machine = ELFIO::EM_RISCV); +} // namespace util +#endif \ No newline at end of file diff --git a/src/common/util/ihex_parser.cpp b/src/common/util/ihex.cpp similarity index 62% rename from src/common/util/ihex_parser.cpp rename to src/common/util/ihex.cpp index 8a03892b9..2f804b772 100644 --- a/src/common/util/ihex_parser.cpp +++ b/src/common/util/ihex.cpp @@ -1,9 +1,14 @@ -#include "ihex_parser.h" +#include "ihex.h" #include +#include #include +#include +#include #include +#include #include +#include // IHEX file parser state machine enum { @@ -25,6 +30,7 @@ enum record_type_e { DATA, END_OF_FILE, EXTENDED_SEGMENT_ADDRESS, START_SEGMENT_ #define INVALID_HEX_CHAR 'x' namespace util { +namespace ihex { namespace { uint8_t hex2dec(uint8_t h) { if(h >= '0' && h <= '9') @@ -36,9 +42,40 @@ uint8_t hex2dec(uint8_t h) { else return INVALID_HEX_CHAR; } + +static uint8_t ihex_checksum(const std::vector& bytes) { + // Checksum is two's complement of the LSB of the sum + uint32_t sum = 0; + for(uint8_t b : bytes) + sum += b; + return static_cast(0u - static_cast(sum)); +} + +static void write_record(std::ostream& os, uint8_t len, uint16_t addr, uint8_t type, const uint8_t* data) { + std::vector payload; + payload.reserve(4 + len); + payload.push_back(len); + payload.push_back(static_cast((addr >> 8) & 0xFF)); + payload.push_back(static_cast(addr & 0xFF)); + payload.push_back(type); + for(uint8_t i = 0; i < len; ++i) + payload.push_back(data[i]); + + uint8_t csum = ihex_checksum(payload); + + os << ':' << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast(len) << std::setw(4) + << static_cast(addr) << std::setw(2) << static_cast(type); + + for(uint8_t i = 0; i < len; ++i) { + os << std::setw(2) << static_cast(data[i]); + } + + os << std::setw(2) << static_cast(csum) << "\n"; +} + } // namespace -bool ihex_parser::parse(std::istream& is, std::function cb) { +bool parse(std::istream& is, std::function cb) { char c; uint8_t i; unsigned state{0}; @@ -149,4 +186,50 @@ bool ihex_parser::parse(std::istream& is, std::function const& bytes, uint32_t base_address, uint32_t record_len) { + if(record_len == 0 || record_len > 255) { + throw std::invalid_argument("record_len must be 1..255"); + } + + uint32_t addr = base_address; + uint32_t upper = 0xFFFFFFFFu; // force first extended address if needed + + size_t i = 0; + while(i < bytes.size()) { + uint32_t new_upper = (addr >> 16) & 0xFFFF; + + // Emit Extended Linear Address record when upper 16 bits change + if(new_upper != upper) { + upper = new_upper; + uint8_t ext[2] = {static_cast((upper >> 8) & 0xFF), static_cast(upper & 0xFF)}; + write_record(os, 2, 0x0000, 0x04, ext); + } + + uint16_t low = static_cast(addr & 0xFFFF); + + // Don't cross a 64KiB boundary inside a data record + size_t max_before_boundary = 0x10000u - low; + size_t chunk = record_len; + if(chunk > bytes.size() - i) + chunk = bytes.size() - i; + if(chunk > max_before_boundary) + chunk = max_before_boundary; + + // Copy to uint8_t buffer for record writer + std::vector buf(chunk); + for(size_t k = 0; k < chunk; ++k) + buf[k] = static_cast(bytes[i + k]); + + write_record(os, static_cast(chunk), low, 0x00, buf.data()); + + i += chunk; + addr += static_cast(chunk); + } + + // End-of-file record + write_record(os, 0, 0x0000, 0x01, nullptr); +} + +} // namespace ihex } // namespace util diff --git a/src/common/util/ihex_parser.h b/src/common/util/ihex.h similarity index 62% rename from src/common/util/ihex_parser.h rename to src/common/util/ihex.h index 65a7cf40d..6559f4ff6 100644 --- a/src/common/util/ihex_parser.h +++ b/src/common/util/ihex.h @@ -17,6 +17,7 @@ #ifndef _UTIL_IHEX_PARSER_H_ #define _UTIL_IHEX_PARSER_H_ +#include "nonstd/span.h" #include #include #include @@ -36,21 +37,24 @@ namespace util { * @author Your Name * @date YYYY-MM-DD */ -struct ihex_parser { - /** - * @brief The maximum size of data in a single IHEX record. - */ - enum { IHEX_DATA_SIZE = 255 }; - /** - * @brief Parses an IHEX file from the given input stream and invokes a callback function for each data record found. - * - * @param input The input stream containing the IHEX file. - * @param callback A function object that will be invoked for each data record found in the IHEX file. - * The callback function should return true to continue parsing, or false to stop parsing. - * - * @return True if the parsing was successful, false otherwise. - */ - static bool parse(std::istream&, std::function); -}; +namespace ihex { +/** + * @brief The maximum size of data in a single IHEX record. + */ +enum { IHEX_DATA_SIZE = 255 }; +/** + * @brief Parses an IHEX file from the given input stream and invokes a callback function for each data record found. + * + * @param input The input stream containing the IHEX file. + * @param callback A function object that will be invoked for each data record found in the IHEX file. + * The callback function takes the address, the length and the data as parameter and should return + * true to continue parsing, or false to stop parsing. + * + * @return True if the parsing was successful, false otherwise. + */ +bool parse(std::istream&, std::function); + +void dump(std::ostream&, nonstd::span const&, uint32_t = 0, uint32_t = 16); +}; // namespace ihex } // namespace util #endif