summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt40
-rw-r--r--src/coff.h156
-rw-r--r--src/coff2bin.cpp146
-rw-r--r--src/coff_image.cpp118
-rw-r--r--src/coff_image.h45
-rw-r--r--src/common.h87
-rw-r--r--src/data_buffer.cpp36
-rw-r--r--src/data_buffer.h2
-rw-r--r--src/elf.h (renamed from src/elf_format.h)109
-rw-r--r--src/elf_image.h2
-rw-r--r--src/main.cpp43
-rw-r--r--src/readcoff.cpp251
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;
+}