diff options
-rw-r--r-- | CMakeLists.txt | 40 | ||||
-rw-r--r-- | src/coff.h | 156 | ||||
-rw-r--r-- | src/coff2bin.cpp | 146 | ||||
-rw-r--r-- | src/coff_image.cpp | 118 | ||||
-rw-r--r-- | src/coff_image.h | 45 | ||||
-rw-r--r-- | src/common.h | 87 | ||||
-rw-r--r-- | src/data_buffer.cpp | 36 | ||||
-rw-r--r-- | src/data_buffer.h | 2 | ||||
-rw-r--r-- | src/elf.h (renamed from src/elf_format.h) | 109 | ||||
-rw-r--r-- | src/elf_image.h | 2 | ||||
-rw-r--r-- | src/main.cpp | 43 | ||||
-rw-r--r-- | src/readcoff.cpp | 251 |
12 files changed, 918 insertions, 117 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index ad1d511..fb6d9b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,16 +18,7 @@ set(common_debug_flags # -pg ) -add_executable(m68k-disasm - src/main.cpp - src/data_buffer.cpp - src/disasm.cpp - src/gnu.cpp - src/m68k.cpp - src/elf_image.cpp - ) - -target_compile_options(m68k-disasm PRIVATE +set(common_compile_options $<$<CONFIG:Debug>:${common_debug_flags}> $<$<COMPILE_LANGUAGE:C>:-Wno-nested-anon-types> # Speed up compilation with -fno-exceptions and -fno-rtti @@ -50,6 +41,35 @@ target_compile_options(m68k-disasm PRIVATE -Wshadow ) +add_executable(m68k-disasm + src/main.cpp + src/data_buffer.cpp + src/disasm.cpp + src/gnu.cpp + src/m68k.cpp + src/elf_image.cpp + ) +target_compile_options(m68k-disasm PRIVATE ${common_compile_options}) target_compile_definitions(m68k-disasm PRIVATE $<$<CONFIG:Debug>:_FORTIFY_SOURCE=2>) target_link_options(m68k-disasm PRIVATE $<$<CONFIG:Debug>:${common_debug_flags}>) target_include_directories(m68k-disasm PRIVATE . lib) + +add_executable(readcoff + src/readcoff.cpp + src/coff_image.cpp + src/data_buffer.cpp + ) +target_compile_options(readcoff PRIVATE ${common_compile_options}) +target_compile_definitions(readcoff PRIVATE $<$<CONFIG:Debug>:_FORTIFY_SOURCE=2>) +target_link_options(readcoff PRIVATE $<$<CONFIG:Debug>:${common_debug_flags}>) +target_include_directories(readcoff PRIVATE . lib) + +add_executable(coff2bin + src/coff2bin.cpp + src/coff_image.cpp + src/data_buffer.cpp + ) +target_compile_options(coff2bin PRIVATE ${common_compile_options}) +target_compile_definitions(coff2bin PRIVATE $<$<CONFIG:Debug>:_FORTIFY_SOURCE=2>) +target_link_options(coff2bin PRIVATE $<$<CONFIG:Debug>:${common_debug_flags}>) +target_include_directories(coff2bin PRIVATE . lib) diff --git a/src/coff.h b/src/coff.h new file mode 100644 index 0000000..c91ef3c --- /dev/null +++ b/src/coff.h @@ -0,0 +1,156 @@ +#pragma once + +/* SPDX-License-Identifier: Unlicense + * + * Common Object File Format loader implementation. + * + * A section "11. COMMON OBJECT FILE FORMAT (COFF)" from the document named + * "SYSTEM V/68 Release 3 Programmer's Guide" or "MU43715PG/D2" dated 12/01/87 + * was used for reference to implement this functionality. + * + * A "TI-89 / TI-92 Plus Sierra C Assembler Reference Manual, Beta Version" + * dated "February 2, 2001" was used for reference to implement this + * functionality. + */ + +#include "common.h" + +#include <cstdint> + +namespace COFF { + +constexpr size_t kFileHeaderSize = 20; +constexpr size_t kOptionalHeaderSize = 28; +constexpr size_t kSectionHeaderSize = 40; + +/// Relocation information stripped from the file +constexpr uint16_t F_RELFG = 0x1; +/// File is executable (i.be. no unresolved external references) +constexpr uint16_t F_EXEC = 0x2; +/// Line numbers stripped from the file +constexpr uint16_t F_LNNO = 0x4; +/// Local symbols stripped from the file +constexpr uint16_t F_LSYMS = 0x8; +/// Global symbols stripped from the file +constexpr uint16_t F_GSYMS = 0x10; +/// Error in object file +constexpr uint16_t F_ERROR = 0x80; + +struct FileHeader { + uint16_t magic; ///< Magic number + /// Number of section headers (equals to number of sections) + uint16_t nsections; + /// Time and date stamp indicating when the file was created, expressed as + /// the number of elapsed seconds since 00:00:00 GMT, January 1, 1970 + int32_t timedate; + /// File pointer containing the starting address of the symbol table + uint32_t symtable_offset; + uint32_t nsymbols; ///< Number of entries in the symbol table + uint16_t optional_header_nbytes; ///< Number of bytes in the optional header + uint16_t flags; ///< Flags (see F_* constexpr values) + static constexpr inline auto FromBytes(const uint8_t *data) + { + const bool be = true; + return FileHeader{ + /* .magic */ GetU16(data + 0, be), + /* .nsections */ GetU16(data + 2, be), + /* .timedate */ GetI32(data + 4, be), + /* .symtable_offset */ GetU32(data + 8, be), + /* .nsymbols */ GetU32(data + 12, be), + /* .optional_header_nbytes */ GetU16(data + 16, be), + /* .flags */ GetU16(data + 18, be), + }; + } + static constexpr uint16_t kMagicSierraM68k = 0x150; +}; + +struct OptionalHeader { + uint16_t magic; ///< Magic number + uint16_t version; ///< Version stamp + uint32_t tsize; ///< Size of text in bytes + uint32_t dsize; ///< Size of initialized data in bytes + uint32_t bsize; ///< Size of uninitialized data in bytes + uint32_t entry; ///< Program entry point + uint32_t text_start; ///< Base address of text + uint32_t data_start; ///< Base address of data + static constexpr inline auto FromBytes(const uint8_t *data) + { + const bool be = true; + return OptionalHeader{ + /* .magic */ GetU16(data + 0, be), + /* .version */ GetU16(data + 2, be), + /* .tsize */ GetU32(data + 4, be), + /* .dsize */ GetU32(data + 8, be), + /* .bsize */ GetU32(data + 12, be), + /* .entry */ GetU32(data + 16, be), + /* .text_start */ GetU32(data + 20, be), + /* .data_start */ GetU32(data + 24, be), + }; + } +}; + +enum class SectionType: uint32_t { + /// Regular section (allocated, relocated, loaded) + Regular = 0, + /// Dummy section (not allocated, relocated, not loaded) + Dummy = 0x1, + /// Noload section (allocated, relocated, not loaded) + NoLoad = 0x2, + /// Grouped section (formed from input sections) + Grouped = 0x4, + /// Padding section (not allocated, not relocated, loaded) + Padding = 0x8, + /// Copy section (for a decision function used in updating fields; not + /// allocated, not relocated, loaded, relocation and line number entries + /// processed normally) + Copy = 0x10, + /// Section contains executable text + Text = 0x20, + /// Section contains initialized data + Data = 0x40, + /// Section contains only uninitialized data + Bss = 0x80, + /// Section contains ORG'd (absolute) data + Org = 0x100, + /// Comment section (not allocated, not relocated, not loaded) + Info = 0x200, + /// Overlay section (not allocated, relocated, not loaded) + Overlay = 0x400, + /// For .lib section (treated like STYP_INFO) + Lib = 0x800, +}; + +struct SectionHeader { + char name[8]; ///< 8-character null-padded section name + uint32_t paddr; ///< Physical address of section + uint32_t vaddr; ///< Virtual address of section + uint32_t size; ///< Seciton size in bytes + uint32_t section_offset; ///< File pointer to raw data + uint32_t reloc_offset; ///< File pointer to relocation entries + uint32_t lineno_offset; ///< File pointer to line number entries + uint16_t nreloc; ///< Number of relocation entries + uint16_t nlineno; ///< Number of line number entries + uint32_t flags; ///< Flags (see STYP_* constexpr values) + static constexpr inline auto FromBytes(const void *data) + { + const uint8_t *d = static_cast<const uint8_t *>(data); + const char *c = static_cast<const char *>(data); + const bool be = true; + return SectionHeader{ + /* .name */ { + c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], + }, + /* .paddr */ GetU32(d + 8, be), + /* .vaddr */ GetU32(d + 12, be), + /* .size */ GetU32(d + 16, be), + /* .section_offset */ GetU32(d + 20, be), + /* .reloc_offset */ GetU32(d + 24, be), + /* .lineno_offset */ GetU32(d + 28, be), + /* .nreloc */ GetU16(d + 32, be), + /* .nlineno */ GetU16(d + 34, be), + /* .flags */ GetU32(d + 36, be), + }; + } +}; + +} diff --git a/src/coff2bin.cpp b/src/coff2bin.cpp new file mode 100644 index 0000000..6438d36 --- /dev/null +++ b/src/coff2bin.cpp @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: Unlicense + */ + +#include "coff_image.h" +#include "data_buffer.h" + +#define OPTPARSE_IMPLEMENTATION +#define OPTPARSE_API static +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wshadow" +#endif +#include "optparse/optparse.h" +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#include <cassert> +#include <cerrno> +#include <cstdio> +#include <cstdlib> +#include <cstring> + +static int Coff2Bin(FILE *input_stream, FILE *output_stream) +{ + auto input = DataBuffer::FromStream(input_stream); + const size_t input_size = input.occupied_size; + if (input_size == 0) { + fprintf(stderr, "DataBuffer::FromStream(input, input_stream): " + "Error: No data has been read\n"); + return EXIT_FAILURE; + } + const COFF::Image coff(static_cast<DataBuffer&&>(input)); + if (!coff.IsValid()) { + fprintf(stderr, "Error: COFF image is not valid: %s\n", coff.Error()); + return EXIT_FAILURE; + } + const auto &file_header = coff.FileHeader(); + if (file_header.nsections == 0) { + fprintf(stderr, "Error: COFF image does not contain sections\n"); + return EXIT_FAILURE; + } + for (size_t i = 0; i < file_header.nsections; i++) { + const auto section = coff.GetSectionHeader(i); + const auto type = static_cast<COFF::SectionType>(section.flags & 0x8ff); + if (COFF::SectionType::Text == type) { + const auto data = coff.GetSectionDataView(section); + const size_t ret = fwrite( + data.buffer, data.size, 1, output_stream); + (void) ret; + assert(ret == 1); + break; + } + } + return EXIT_SUCCESS; +} + +static void PrintUsage(FILE *s, const char *argv0) +{ + // Please, keep all lines in 80 columns range when printed. + fprintf(s, + "Usage: %s [options] <input-file-name>\n" + "Options:\n" + " -h, --help Show this message.\n" + " -o, --output FILE Where to write binary data to (stdout if not set).\n" + " <input_file_name> COFF file with the machine code to extract\n" + " ('-' means stdin).\n" + , argv0); +} + +int main(int, char* argv[]) +{ + struct optparse_long longopts[] = { + {"help", 'h', OPTPARSE_NONE}, + {"output", 'o', OPTPARSE_OPTIONAL}, + {}, + }; + const char *input_file_name = nullptr; + const char *output_file_name = nullptr; + struct optparse options; + optparse_init(&options, argv); + // Parse opts + int option; + while ((option = optparse_long(&options, longopts, NULL)) != -1) { + switch (option) { + case 'h': + PrintUsage(stdout, argv[0]); + return EXIT_SUCCESS; + break; + case 'o': + output_file_name = options.optarg; + break; + case '?': + fprintf(stderr, "main: optparse_long: Error: \"%s\"\n", options.errmsg); + return EXIT_FAILURE; + } + } + // Parse input file name + char *arg; + while ((arg = optparse_arg(&options))) { + if (input_file_name == nullptr) { + input_file_name = arg; + } else { + fprintf(stderr, "error: too many free arguments provided\n"); + return EXIT_FAILURE; + } + } + // Open the files + FILE *input_stream = nullptr; + FILE *output_stream = stdout; + if (input_file_name) { + if (0 == strcmp(input_file_name, "-")) { + input_stream = stdin; + } else { + input_stream = fopen(input_file_name, "r"); + } + if (input_stream == nullptr) { + const int err = errno; + fprintf(stderr, + "main: fopen(\"%s\", \"r\"): Error (%d): \"%s\"\n", + input_file_name, err, strerror(err)); + return EXIT_FAILURE; + } + } else { + fprintf(stderr, "main: Error: no input file name specified, see usage below.\n"); + PrintUsage(stderr, argv[0]); + return EXIT_FAILURE; + } + if (output_file_name) { + output_stream = fopen(output_file_name, "w"); + if (output_stream == nullptr) { + const int err = errno; + fprintf(stderr, + "main: fopen(\"%s\", \"w\"): Error (%d): \"%s\"\n", + output_file_name, err, strerror(err)); + fclose(input_stream); + return EXIT_FAILURE; + } + } + // Run the program + const int ret = Coff2Bin(input_stream, output_stream); + fclose(output_stream); + fclose(input_stream); + return ret; +} diff --git a/src/coff_image.cpp b/src/coff_image.cpp new file mode 100644 index 0000000..c9717c1 --- /dev/null +++ b/src/coff_image.cpp @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: Unlicense + */ + +#include "coff_image.h" + +#include <cassert> +#include <cstdarg> +#include <cstdlib> +#include <cstring> + +#ifdef __GNUC__ +#define _PRINTF(strindex, first) __attribute__((format(printf, strindex, first))) +#else +#define _PRINTF(strindex, first) +#endif + + +static _PRINTF(1, 2) char *Error(const char *fmt, ...) +{ + if (0 == strlen(fmt)) { + return nullptr; + } + char *error{}; + size_t size{}; + FILE *error_stream = open_memstream(&error, &size); + assert(error_stream); + va_list ap; + va_start(ap, fmt); + vfprintf(error_stream, fmt, ap); + va_end(ap); + fclose(error_stream); + assert(error != nullptr); + assert(*error != '\0'); + return error; +} + +static char *ValidateCOFF(const DataView& d) +{ + using namespace COFF; + size_t expected_size = kFileHeaderSize; + if (d.size < expected_size) { + return Error( + "data size (%zu) is too low, expected at least %zu: " + "COFF header could not fit", + d.size, + expected_size); + } + const auto header = FileHeader::FromBytes(d.buffer); + if (header.magic != FileHeader::kMagicSierraM68k) { + return Error( + "COFF Magic is invalid: expected 0x%04x, got 0x%04x", + FileHeader::kMagicSierraM68k, + header.magic); + } + const size_t oh_nbytes = header.optional_header_nbytes; + if (oh_nbytes && oh_nbytes < kOptionalHeaderSize) { + return Error( + "COFF optional header size is invalid: expected 0 or at least %zu, got %zu", + kOptionalHeaderSize, + oh_nbytes); + } + expected_size += oh_nbytes; + if (d.size < expected_size) { + return Error( + "data size (%zu) is too low, expected at least %zu: " + "COFF optional header could not fit", + d.size, + expected_size); + } + expected_size += header.nsections * kSectionHeaderSize; + if (d.size < expected_size) { + return Error( + "data size (%zu) is too low, expected at least %zu: " + "%u sections headers could not fit", + d.size, + expected_size, + header.nsections); + } + if (header.symtable_offset > d.size) { + return Error( + "COFF symbol table offset is too big to fit into the file: " + "expected (<=%zu), got (%zu)", + size_t(d.size), + size_t(header.symtable_offset)); + } + for (size_t i = 0; i < header.nsections; i++) { + const auto section = SectionHeader::FromBytes(d.buffer + COFF::kFileHeaderSize + + header.optional_header_nbytes + COFF::kSectionHeaderSize * i); + const size_t section_end = section.section_offset + section.size; + if (section_end > d.size) { + return Error( + "data size (%zu) is too low, expected at least %zu: " + "section %zu (%.8s) data could not fit", + d.size, + section_end, + i, + section.name); + } + } + return nullptr; +} + +COFF::Image::Image(DataBuffer&& data) + : _data(static_cast<DataBuffer&&>(data)) + , _error(ValidateCOFF(_data.View())) + , _file_header(_error ? COFF::FileHeader{} : COFF::FileHeader::FromBytes(_data.View().buffer)) + , _has_optional_header(_error ? false : _file_header.optional_header_nbytes >= kOptionalHeaderSize) + , _optional_header(_has_optional_header + ? COFF::OptionalHeader::FromBytes(_data.View().buffer) + : COFF::OptionalHeader{}) +{} + +COFF::Image::~Image() +{ + if (_error) { + free(_error); + } +} diff --git a/src/coff_image.h b/src/coff_image.h new file mode 100644 index 0000000..83b10dd --- /dev/null +++ b/src/coff_image.h @@ -0,0 +1,45 @@ +#pragma once + +/* SPDX-License-Identifier: Unlicense + */ + +#include "coff.h" + +#include "data_buffer.h" + +namespace COFF { + +class Image { + const DataBuffer _data; + char *const _error; + const COFF::FileHeader _file_header; + bool _has_optional_header; + const COFF::OptionalHeader _optional_header; +public: + explicit Image(DataBuffer&&); + ~Image(); + constexpr bool IsValid() const { return _error == nullptr; } + constexpr const DataBuffer &Data() const { return _data; }; + constexpr const char *Error() const { return _error; } + constexpr const COFF::FileHeader &FileHeader() const { return _file_header; } + constexpr bool HasOptionalHeader() const { return _has_optional_header; } + constexpr const COFF::OptionalHeader &OptionalHeader() const { return _optional_header; } + constexpr const COFF::SectionHeader GetSectionHeader(unsigned index) const + { + if (index > _file_header.nsections) { + return SectionHeader{}; + } + const size_t offset = COFF::kFileHeaderSize + _file_header.optional_header_nbytes + + COFF::kSectionHeaderSize * index; + if (offset + kSectionHeaderSize > _data.buffer_size) { + return SectionHeader{}; + } + return SectionHeader::FromBytes(_data.buffer + offset); + } + constexpr DataView GetSectionDataView(const COFF::SectionHeader &header) const + { + return DataView{_data.buffer + header.section_offset, header.size}; + } +}; + +} diff --git a/src/common.h b/src/common.h index e1c57c5..1cd0d9c 100644 --- a/src/common.h +++ b/src/common.h @@ -80,20 +80,89 @@ constexpr uint32_t kDisasmMapSizeElements = kRomSizeBytes / kInstructionSizeStep static inline constexpr size_t Min(size_t a, size_t b) { return a < b ? a : b; } -static inline constexpr uint16_t GetU16BE(const uint8_t *buffer) +static inline constexpr uint16_t GetU16BE(const void *buffer) { - return (static_cast<uint16_t>(buffer[0]) << 8) | static_cast<uint16_t>(buffer[1]); + const uint8_t *b = static_cast<const uint8_t *>(buffer); + return (static_cast<uint16_t>(b[0]) << 8) | static_cast<uint16_t>(b[1]); } -static inline constexpr int16_t GetI16BE(const uint8_t *buffer) +static inline constexpr uint16_t GetU16LE(const void *buffer) { - return (static_cast<uint16_t>(buffer[0]) << 8) | static_cast<uint16_t>(buffer[1]); + const uint8_t *b = static_cast<const uint8_t *>(buffer); + return (static_cast<uint16_t>(b[1]) << 8) | static_cast<uint16_t>(b[0]); } -static inline constexpr int32_t GetI32BE(const uint8_t *buffer) +static inline constexpr int16_t GetI16BE(const void *buffer) { - return (static_cast<uint32_t>(buffer[0]) << 24) | - (static_cast<uint32_t>(buffer[1]) << 16) | - (static_cast<uint32_t>(buffer[2]) << 8) | - static_cast<uint32_t>(buffer[3]); + return GetU16BE(buffer); +} + +static inline constexpr int16_t GetI16LE(const void *buffer) +{ + return GetU16LE(buffer); +} + +static inline constexpr uint32_t GetU32BE(const void *buffer) +{ + const uint8_t *b = static_cast<const uint8_t *>(buffer); + return (static_cast<uint32_t>(b[0]) << 24) | + (static_cast<uint32_t>(b[1]) << 16) | + (static_cast<uint32_t>(b[2]) << 8) | + static_cast<uint32_t>(b[3]); +} + +static inline constexpr uint32_t GetU32LE(const void *buffer) +{ + const uint8_t *b = static_cast<const uint8_t *>(buffer); + return (static_cast<uint32_t>(b[3]) << 24) | + (static_cast<uint32_t>(b[2]) << 16) | + (static_cast<uint32_t>(b[1]) << 8) | + static_cast<uint32_t>(b[0]); +} + +static inline constexpr int32_t GetI32BE(const void *buffer) +{ + return GetU32BE(buffer); +} + +static inline constexpr int32_t GetI32LE(const void *buffer) +{ + return GetU32LE(buffer); +} + +static constexpr inline uint8_t GetU8(const void *d) +{ + return *static_cast<const uint8_t *>(d); +} + +static constexpr inline uint8_t GetU8(const void *d, bool is_big_endian) +{ + return (void)is_big_endian, GetU8(d); +} + +static constexpr inline int8_t GetI8(const void *d) { return GetU8(d); } + +static constexpr inline int8_t GetI8(const void *d, bool is_big_endian) +{ + return (void)is_big_endian, GetI8(d); +} + +static constexpr inline uint16_t GetU16(const void *d, bool is_big_endian) +{ + return is_big_endian ? GetU16BE(d) : GetU16LE(d); +} + +static constexpr inline int16_t GetI16(const void *d, bool is_big_endian) +{ + return is_big_endian ? GetI16BE(d) : GetI16LE(d); +} + +static constexpr inline uint32_t GetU32(const void *d, bool is_big_endian) +{ + return is_big_endian ? GetU32BE(d) : GetU32LE(d); +} + +static constexpr inline int32_t GetI32(const void *d, bool is_big_endian) +{ + return is_big_endian ? GetI32BE(d) : GetI32LE(d); } diff --git a/src/data_buffer.cpp b/src/data_buffer.cpp index 33cb0b3..c4c1d8b 100644 --- a/src/data_buffer.cpp +++ b/src/data_buffer.cpp @@ -4,6 +4,8 @@ #include "data_buffer.h" #include <cassert> +#include <cerrno> +#include <cstdlib> #include <cstring> void DataBuffer::Expand(size_t new_size) @@ -27,3 +29,37 @@ DataBuffer::~DataBuffer() buffer_size = 0; occupied_size = 0; } + +DataBuffer DataBuffer::FromStream(FILE *stream) +{ + DataBuffer db{}; + assert(db.buffer && db.buffer_size >= db.kInitialSize); + while (1) { + const size_t read_size = db.buffer_size - db.occupied_size; + const size_t fread_ret = fread( + db.buffer + db.occupied_size, sizeof(*db.buffer), read_size, stream); + db.occupied_size += fread_ret; + if (fread_ret >= db.buffer_size) { + assert(fread_ret == db.buffer_size); + db.Expand(db.buffer_size * 2); + } else { + const int err = errno; + if (feof(stream)) { + break; + } else if (ferror(stream)) { + fprintf( + stderr, + "DataBuffer::FromStream: fread(%zu): " + "Error (%d): \"%s\"\n", + read_size, + err, + strerror(err)); + } else if (db.buffer_size == db.occupied_size) { + db.Expand(db.buffer_size * 2); + } else { + assert(false); + } + } + } + return db; +} diff --git a/src/data_buffer.h b/src/data_buffer.h index bc264d2..5206806 100644 --- a/src/data_buffer.h +++ b/src/data_buffer.h @@ -7,6 +7,7 @@ #include <cstddef> #include <cstdint> +#include <cstdio> struct DataView { const uint8_t *const buffer{}; @@ -38,4 +39,5 @@ struct DataBuffer { return DataView{buffer + offset, Min(occupied_size - offset, size)}; }; ~DataBuffer(); + static DataBuffer FromStream(FILE *stream); }; diff --git a/src/elf_format.h b/src/elf.h index c60ac64..0fb9715 100644 --- a/src/elf_format.h +++ b/src/elf.h @@ -3,6 +3,8 @@ /* SPDX-License-Identifier: Unlicense */ +#include "common.h" + #include <cstddef> #include <cstdint> @@ -144,29 +146,6 @@ enum class Machine : uint16_t { kUnknown, }; -static constexpr inline uint8_t ParseU8(const uint8_t *d) { return *d; } - -static constexpr inline uint8_t ParseU8(const uint8_t *d, DataEncoding) -{ - return ParseU8(d); -} - -static constexpr inline uint16_t ParseU16(const uint8_t *d, DataEncoding e) -{ - if (e == DataEncoding::k2MSB) { - return uint16_t(d[0]) << 8 | d[1]; - } - return uint16_t(d[1]) << 8 | d[0]; -} - -static constexpr inline uint32_t ParseU32(const uint8_t *d, DataEncoding e) -{ - if (e == DataEncoding::k2MSB) { - return uint32_t(d[0]) << 24 | uint32_t(d[1]) << 16 | uint32_t(d[2]) << 8 | d[3]; - } - return uint32_t(d[3]) << 24 | uint32_t(d[2]) << 16 | uint32_t(d[1]) << 8 | d[0]; -} - static constexpr inline auto ParseObjectType(const uint16_t type) { switch (type) { @@ -214,22 +193,23 @@ struct Header32Raw { static constexpr inline auto FromBytes(const uint8_t *data) { const auto ident = Ident32Raw::FromBytes(data); - const DataEncoding e = ParseDataEncoding(ident.data_encoding); + const bool be = DataEncoding::k2MSB == ParseDataEncoding( + ident.data_encoding); return Header32Raw{ /* .ident */ ident, - /* .type */ ParseU16(data + kIdentSize + 0, e), - /* .machine */ ParseU16(data + kIdentSize + 2, e), - /* .version */ ParseU32(data + kIdentSize + 4, e), - /* .entry */ ParseU32(data + kIdentSize + 8, e), - /* .phoff */ ParseU32(data + kIdentSize + 12, e), - /* .shoff */ ParseU32(data + kIdentSize + 16, e), - /* .flags */ ParseU32(data + kIdentSize + 20, e), - /* .ehsize */ ParseU16(data + kIdentSize + 24, e), - /* .phentsize */ ParseU16(data + kIdentSize + 26, e), - /* .phnum */ ParseU16(data + kIdentSize + 28, e), - /* .shentsize */ ParseU16(data + kIdentSize + 30, e), - /* .shnum */ ParseU16(data + kIdentSize + 32, e), - /* .shstrndx */ ParseU16(data + kIdentSize + 34, e), + /* .type */ GetU16(data + kIdentSize + 0, be), + /* .machine */ GetU16(data + kIdentSize + 2, be), + /* .version */ GetU32(data + kIdentSize + 4, be), + /* .entry */ GetU32(data + kIdentSize + 8, be), + /* .phoff */ GetU32(data + kIdentSize + 12, be), + /* .shoff */ GetU32(data + kIdentSize + 16, be), + /* .flags */ GetU32(data + kIdentSize + 20, be), + /* .ehsize */ GetU16(data + kIdentSize + 24, be), + /* .phentsize */ GetU16(data + kIdentSize + 26, be), + /* .phnum */ GetU16(data + kIdentSize + 28, be), + /* .shentsize */ GetU16(data + kIdentSize + 30, be), + /* .shnum */ GetU16(data + kIdentSize + 32, be), + /* .shstrndx */ GetU16(data + kIdentSize + 34, be), }; } }; @@ -315,15 +295,16 @@ struct ProgramHeader32 { uint32_t align; static constexpr inline auto FromBytes(const uint8_t *data, const DataEncoding e) { + const bool be = DataEncoding::k2MSB == e; return ProgramHeader32{ - /* .type = */ ParseU32(data + 0, e), - /* .offset = */ ParseU32(data + 4, e), - /* .vaddr = */ ParseU32(data + 8, e), - /* .paddr = */ ParseU32(data + 12, e), - /* .filesz = */ ParseU32(data + 16, e), - /* .memsz = */ ParseU32(data + 20, e), - /* .flags = */ ParseU32(data + 24, e), - /* .align = */ ParseU32(data + 28, e), + /* .type = */ GetU32(data + 0, be), + /* .offset = */ GetU32(data + 4, be), + /* .vaddr = */ GetU32(data + 8, be), + /* .paddr = */ GetU32(data + 12, be), + /* .filesz = */ GetU32(data + 16, be), + /* .memsz = */ GetU32(data + 20, be), + /* .flags = */ GetU32(data + 24, be), + /* .align = */ GetU32(data + 28, be), }; } }; @@ -360,17 +341,18 @@ struct SectionHeader32 { uint32_t entsize{}; ///< Size of a single entry (every entry has same size) static constexpr inline auto FromBytes(const uint8_t *data, const DataEncoding e) { + const bool be = DataEncoding::k2MSB == e; return SectionHeader32{ - /* .name = */ ParseU32(data + 0, e), - /* .type = */ ParseU32(data + 4, e), - /* .flags = */ ParseU32(data + 8, e), - /* .addr = */ ParseU32(data + 12, e), - /* .offset = */ ParseU32(data + 16, e), - /* .size = */ ParseU32(data + 20, e), - /* .link = */ ParseU32(data + 24, e), - /* .info = */ ParseU32(data + 28, e), - /* .addralign = */ ParseU32(data + 32, e), - /* .entsize = */ ParseU32(data + 36, e), + /* .name = */ GetU32(data + 0, be), + /* .type = */ GetU32(data + 4, be), + /* .flags = */ GetU32(data + 8, be), + /* .addr = */ GetU32(data + 12, be), + /* .offset = */ GetU32(data + 16, be), + /* .size = */ GetU32(data + 20, be), + /* .link = */ GetU32(data + 24, be), + /* .info = */ GetU32(data + 28, be), + /* .addralign = */ GetU32(data + 32, be), + /* .entsize = */ GetU32(data + 36, be), }; } constexpr bool IsValid(void) const { return name != 0; } @@ -417,8 +399,8 @@ enum class Symbol32Type: unsigned char { struct Symbol32 { const char *name{}; uint32_t namendx{}; - Address value{}; ///< Value or address, e.g address of a variable in RAM - uint32_t size{}; ///< Size of a symbol, e.g length of a function, etc. + Address value{}; ///< Value or address, be.g address of a variable in RAM + uint32_t size{}; ///< Size of a symbol, be.g length of a function, etc. unsigned char info{}; unsigned char other{}; uint16_t shndx{}; ///< Index of a section the symbol belongs to @@ -432,14 +414,15 @@ struct Symbol32 { } static constexpr inline auto FromBytes(const uint8_t *data, const DataEncoding e) { + const bool be = DataEncoding::k2MSB == e; return Symbol32{ /* .name = */ nullptr, - /* .namendx = */ ParseU32(data + 0, e), - /* .value = */ ParseU32(data + 4, e), - /* .size = */ ParseU32(data + 8, e), - /* .info = */ ParseU8(data + 12, e), - /* .other = */ ParseU8(data + 13, e), - /* .shndx = */ ParseU16(data + 14, e), + /* .namendx = */ GetU32(data + 0, be), + /* .value = */ GetU32(data + 4, be), + /* .size = */ GetU32(data + 8, be), + /* .info = */ GetU8(data + 12, be), + /* .other = */ GetU8(data + 13, be), + /* .shndx = */ GetU16(data + 14, be), }; } }; diff --git a/src/elf_image.h b/src/elf_image.h index b753008..20627d3 100644 --- a/src/elf_image.h +++ b/src/elf_image.h @@ -3,7 +3,7 @@ /* SPDX-License-Identifier: Unlicense */ -#include "elf_format.h" +#include "elf.h" #include "data_buffer.h" #include <cstdlib> diff --git a/src/main.cpp b/src/main.cpp index 4e49fde..1125df0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -639,34 +639,6 @@ static void ParseTraceData(DisasmMap &disasm_map, const DataView &trace_data) } } -static size_t ReadFromStream(DataBuffer &db, FILE *stream) -{ - assert(db.buffer && db.buffer_size >= db.kInitialSize); - while (1) { - const size_t read_size = db.buffer_size - db.occupied_size; - const size_t fread_ret = fread( - db.buffer + db.occupied_size, sizeof(*db.buffer), read_size, stream); - db.occupied_size += fread_ret; - if (fread_ret >= db.buffer_size) { - assert(fread_ret == db.buffer_size); - db.Expand(db.buffer_size * 2); - } else { - const int err = errno; - if (feof(stream)) { - break; - } else if (ferror(stream)) { - fprintf(stderr, "ReadFromStream: fread(%zu): Error (%d): \"%s\"\n", read_size, err, strerror(err)); - return EXIT_FAILURE; - } else if (db.buffer_size == db.occupied_size) { - db.Expand(db.buffer_size * 2); - } else { - assert(false); - } - } - } - return db.occupied_size; -} - static DisasmMap *NewDisasmMap(FILE *trace_stream) { if (trace_stream == nullptr) { @@ -675,10 +647,11 @@ static DisasmMap *NewDisasmMap(FILE *trace_stream) return disasm_map; } // Read trace file into buffer - DataBuffer trace_data{}; - const size_t trace_size = ReadFromStream(trace_data, trace_stream); + auto trace_data = DataBuffer::FromStream(trace_stream); + const size_t trace_size = trace_data.occupied_size; if (trace_size == 0) { - fprintf(stderr, "ReadFromStream(trace_data, trace_stream): Error: No data has been read\n"); + fprintf(stderr, "DataBuffer::FromStream(trace_data, trace_stream): " + "Error: No data has been read\n"); return nullptr; } // Parse trace file into map @@ -692,10 +665,11 @@ static int M68kDisasm( FILE *input_stream, FILE *output_stream, FILE *trace_stream, const Settings &s) { // Read input file into buffer - DataBuffer input{}; - const size_t input_size = ReadFromStream(input, input_stream); + auto input = DataBuffer::FromStream(input_stream); + const size_t input_size = input.occupied_size; if (input_size == 0) { - fprintf(stderr, "ReadFromStream(input, input_stream): Error: No data has been read\n"); + fprintf(stderr, "DataBuffer::FromStream(input, input_stream): " + "Error: No data has been read\n"); return EXIT_FAILURE; } const ELF::Image elf(static_cast<DataBuffer&&>(input)); @@ -803,6 +777,7 @@ static void PrintUsage(FILE *s, const char *argv0) " automatically if not set. Only `auto`, `binary` and\n" " `elf` are currently supported.\n" " <input_file_name> Binary or elf file with the machine code to disassemble\n" + " ('-' means stdin).\n" "Feature flags:\n" " rdc Print raw data comment for traced locations.\n" " rdc-all Print raw data comment for every location (requires\n" diff --git a/src/readcoff.cpp b/src/readcoff.cpp new file mode 100644 index 0000000..f9398c4 --- /dev/null +++ b/src/readcoff.cpp @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: Unlicense + */ + +#include "coff_image.h" +#include "data_buffer.h" + +#define OPTPARSE_IMPLEMENTATION +#define OPTPARSE_API static +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wshadow" +#endif +#include "optparse/optparse.h" +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#include <cassert> +#include <cerrno> +#include <cstdio> +#include <cstdlib> +#include <cstring> + +static bool g_print_header = false; +static bool g_print_optional_header = false; +static bool g_print_section_headers = false; + +static void PrintCoffHeader(FILE *output_stream, const COFF::FileHeader &header) +{ + fprintf(output_stream, "COFF Header:\n"); + fprintf(output_stream, " Magic: %02x %02x (0x%x as big endian)\n", + (header.magic >> 8) & 0xff, header.magic & 0xff, header.magic); + fprintf(output_stream, " Number of section headers: %u\n", header.nsections); + // TODO proper time with year, month, day, hour, minute and second + fprintf(output_stream, " Time and date: %u\n", header.timedate); + fprintf(output_stream, " Symbol table file offset: %u (0x%x)\n", + header.symtable_offset, header.symtable_offset); + fprintf(output_stream, " Number of symbols: %u (0x%x)\n", + header.nsymbols, header.nsymbols); + fprintf(output_stream, " Optional header size, bytes: %u (0x%x)\n", + header.optional_header_nbytes, header.optional_header_nbytes); + // TODO Print detailed flags information + fprintf(output_stream, " Flags: 0x%x\n", header.flags); +} + +static void PrintOptionalHeader(FILE *output_stream, const COFF::OptionalHeader &header) +{ + fprintf(output_stream, "Optional Header:\n"); + fprintf(output_stream, " Magic: %02x %02x (0x%x as big endian)\n", + (header.magic >> 8) & 0xff, header.magic & 0xff, header.magic); + fprintf(output_stream, " Version: 0x%04x\n", header.version); + fprintf(output_stream, " Size of text, bytes: %u\n", header.tsize); + fprintf(output_stream, " Size of initialized data, bytes: %u\n", header.dsize); + fprintf(output_stream, " Size of uninitialized data, bytes: %u\n", header.bsize); + fprintf(output_stream, " Program entry point: %u (0x%x)\n", + header.entry, header.entry); + fprintf(output_stream, " Base address of text: %u (0x%x)\n", + header.text_start, header.text_start); + fprintf(output_stream, " Base address of daata: %u (0x%x)\n", + header.data_start, header.data_start); +} + +static const char *SectionTypeStr(COFF::SectionType type) +{ + switch (type) { + case COFF::SectionType::Regular: + return "Regular ARL"; + case COFF::SectionType::Dummy: + return "Dubby R"; + case COFF::SectionType::NoLoad: + return "NoLoad AR"; + case COFF::SectionType::Grouped: + return "Grouped"; + case COFF::SectionType::Padding: + return "Padding L"; + case COFF::SectionType::Copy: + return "Cppy A L"; + case COFF::SectionType::Text: + return "Text ARL"; + case COFF::SectionType::Data: + return "Data ARL"; + case COFF::SectionType::Bss: + return "BSS ARL"; + case COFF::SectionType::Org: + return "Org ARL"; + case COFF::SectionType::Info: + return "Info"; + case COFF::SectionType::Overlay: + return "Ovsrlay R"; + case COFF::SectionType::Lib: + return "Lib ARL"; + } + return "Unknwon"; +} + +static void PrintSectionHeader(FILE *output_stream, size_t index, const COFF::SectionHeader &header) +{ + fprintf(output_stream, + " [%2zu] %-8s ""%08x " "%08x " "%08x " "%08x " "%08x " "\n", + index, + header.name, + header.paddr, + header.vaddr, + header.size, + header.section_offset, + header.reloc_offset); + fprintf(output_stream, + " %08x " " %04x %04x %08x " "%s\n", + header.lineno_offset, + header.nreloc, + header.nlineno, + header.flags, + SectionTypeStr(static_cast<COFF::SectionType>(header.flags & 0x8ff))); +} + +static int ReadCoff(FILE *input_stream, FILE *output_stream) +{ + auto input = DataBuffer::FromStream(input_stream); + const size_t input_size = input.occupied_size; + if (input_size == 0) { + fprintf(stderr, "DataBuffer::FromStream(input, input_stream): " + "Error: No data has been read\n"); + return EXIT_FAILURE; + } + const COFF::Image coff(static_cast<DataBuffer&&>(input)); + if (!coff.IsValid()) { + fprintf(stderr, "Error: COFF image is not valid: %s\n", coff.Error()); + return EXIT_FAILURE; + } + const auto &file_header = coff.FileHeader(); + if (g_print_header) { + PrintCoffHeader(output_stream, file_header); + } + if (g_print_optional_header && coff.HasOptionalHeader()) { + PrintOptionalHeader(output_stream, coff.OptionalHeader()); + } + if (g_print_section_headers && file_header.nsections) { + fprintf(output_stream, "Section headers:\n"); + fprintf(output_stream, + " [Nr] Name PhysAddr VirtAddr Size DatOffst RelOffst\n" + " LnNoOfft NReloc NLineNo Flags Type Alloc/Reloc/Load\n"); + for (size_t i = 0; i < file_header.nsections; i++) { + PrintSectionHeader(output_stream, i, coff.GetSectionHeader(i)); + } + } + return EXIT_SUCCESS; +} + +static void PrintUsage(FILE *s, const char *argv0) +{ + // Please, keep all lines in 80 columns range when printed. + fprintf(s, + "Usage: %s [options] <input-file-name>\n" + "Options:\n" + " -H, --help Show this message.\n" + " -a --all Equivalent to: -h -o -S\n" + " -h --file-header Display the COFF file header\n" + " -o --optional-header Display the optional COFF file header\n" + " -S --section-headers Display the sections' header\n" + " --sections An alias for --section-headers\n" + " -e --headers Equivalent to: -h -o -S\n" + " <input_file_name> COFF file with the machine code to extract\n" + " ('-' means stdin).\n" + , argv0); +} + +int main(int, char* argv[]) +{ + struct optparse_long longopts[] = { + {"help", 'H', OPTPARSE_NONE}, + {"all", 'a', OPTPARSE_NONE}, + {"file-header", 'h', OPTPARSE_NONE}, + {"optional-header", 'o', OPTPARSE_NONE}, + {"section-headers", 'S', OPTPARSE_NONE}, + {"sections", 0x80, OPTPARSE_NONE}, + {"headers", 'e', OPTPARSE_NONE}, + {}, + }; + const char *input_file_name = nullptr; + struct optparse options; + optparse_init(&options, argv); + // Parse opts + int option; + while ((option = optparse_long(&options, longopts, NULL)) != -1) { + switch (option) { + case 'H': + PrintUsage(stdout, argv[0]); + return EXIT_SUCCESS; + break; + case 'h': + g_print_header = true; + break; + case 'o': + g_print_optional_header = true; + break; + case 0x80: + case 'S': + g_print_section_headers = true; + break; + case 'a': + case 'e': + g_print_header = true; + g_print_optional_header = true; + g_print_section_headers = true; + break; + case '?': + fprintf(stderr, "main: optparse_long: Error: \"%s\"\n", options.errmsg); + return EXIT_FAILURE; + } + } + // Parse input file name + char *arg; + while ((arg = optparse_arg(&options))) { + if (input_file_name == nullptr) { + input_file_name = arg; + } else { + fprintf(stderr, "error: too many free arguments provided\n"); + return EXIT_FAILURE; + } + } + // Open the files + FILE *input_stream = nullptr; + FILE *output_stream = stdout; + if (input_file_name) { + if (0 == strcmp(input_file_name, "-")) { + input_stream = stdin; + } else { + input_stream = fopen(input_file_name, "r"); + } + if (input_stream == nullptr) { + const int err = errno; + fprintf(stderr, "main: fopen(\"%s\", \"r\"): Error (%d): \"%s\"\n", input_file_name, err, strerror(err)); + return EXIT_FAILURE; + } + } else { + fprintf(stderr, "main: Error: no input file name specified, see usage below.\n"); + PrintUsage(stderr, argv[0]); + return EXIT_FAILURE; + } + if (!g_print_header && !g_print_optional_header && !g_print_section_headers) { + fprintf(stderr, "main: Error: no display options specified, see usage below.\n"); + PrintUsage(stdout, argv[0]); + return EXIT_FAILURE; + } + // Run the program + const int ret = ReadCoff(input_stream, output_stream); + fclose(output_stream); + fclose(input_stream); + return ret; +} |