diff --git a/src/Rose/BinaryAnalysis/Partitioner2/EngineBinary.C b/src/Rose/BinaryAnalysis/Partitioner2/EngineBinary.C index afc10031fd..e79b771ab9 100644 --- a/src/Rose/BinaryAnalysis/Partitioner2/EngineBinary.C +++ b/src/Rose/BinaryAnalysis/Partitioner2/EngineBinary.C @@ -949,11 +949,11 @@ EngineBinary::specimenNameDocumentation() { "@bullet{If the name begins with the string \"vxcore:\" then it is treated as a special VxWorks core dump " "in a format defined by ROSE. The complete specification has the syntax \"vxcore:[@v{memory_attributes}]" - ":[@v{file_attributes}]:@v{file_name}\". The parts in square brackets are optional. The only memory attribute " - "recognized at this time is an equal sign (\"=\") followed by zero of more of the letters \"r\" (read), " - "\"w\" (write), and \"x\" (execute) to specify the mapping permissions. The default mapping permission if " - "no equal sign is specified is read, write, and execute. The only file attribute recognized at this time is " - "\"version=@v{v}\" where @v{v} is a version number, and ROSE currently supports only version 1.}" + ":[@v{file_attributes}]:@v{file_name}\". The parts in square brackets are optional. The only file attribute " + "recognized at this time is \"version=@v{v}\" where @v{v} is a version number which must be 1 or 2, defaulting " + "to 1. For version 1, the only memory attribute is an equal sign (\"=\") followed by zero of more of the letters " + "\"r\" (read), \"w\" (write), and \"x\" (execute) to specify the mapping permissions, defaulting to read, write, and " + "execute. Version 2 has no memory attributes.}" "@bullet{If the name begins with the string \"meta:\" then it adjusts meta information about the memory " "map, such as permissions. " + MemoryMap::adjustMapDocumentation() + "}" diff --git a/src/frontend/BinaryFormats/BinaryVxcoreParser.C b/src/frontend/BinaryFormats/BinaryVxcoreParser.C index 5d981cd20e..511a55e6af 100644 --- a/src/frontend/BinaryFormats/BinaryVxcoreParser.C +++ b/src/frontend/BinaryFormats/BinaryVxcoreParser.C @@ -70,9 +70,13 @@ VxcoreParser::parseUrl(const std::string &spec) { throw Exception("URL", 0, "invalid file attribute for vxcore URL: \"" + StringUtility::cEscape(parts[1]) + "\""); } } - if (settings_.version != 1) + if (settings_.version != 1 && settings_.version != 2) throw Exception("URL", 0, "vxcore version " + boost::lexical_cast(settings_.version) + " is not supported"); + // Error checking + if (2 == settings_.version && settings_.protOverride) + throw Exception("URL", 0, "vxcore version 2 does not support memory protection override (\"=\" attribute)"); + return parts[2]; } @@ -97,18 +101,64 @@ VxcoreParser::parse(const boost::filesystem::path &fileName, const MemoryMap::Pt void VxcoreParser::parse(std::istream &input, const MemoryMap::Ptr &memory, const BaseSemantics::RegisterState::Ptr ®isters, const BaseSemantics::RiscOperators::Ptr &ops, const std::string &inputName) { - for (size_t segmentIdx = 0; input; ++segmentIdx) { - size_t headerOffset = input.tellg(); - std::string header = rose_getline(input); - if (header.empty()) - break; // EOF - - std::string name = inputName + " segment #" + boost::lexical_cast(segmentIdx); - if (!parseMemory(header, input, memory, name, headerOffset) && - !parseRegisters(header, input, registers, ops, name, headerOffset)) { - throw Exception(inputName, input.tellg(), "invalid header: \"" + StringUtility::cEscape(header.substr(0, 30)) + "\"" + - (header.size() > 30 ? "..." : "")); + Sawyer::Message::Stream debug(mlog[DEBUG]); + if (1 == settings_.version) { + for (size_t segmentIdx = 0; input; ++segmentIdx) { + size_t headerOffset = input.tellg(); + std::string header = rose_getline(input); + if (header.empty()) + break; // EOF + + std::string name = inputName + " segment #" + boost::lexical_cast(segmentIdx); + if (!parseMemory(header, input, memory, name, headerOffset) && + !parseRegisters(header, input, registers, ops, name, headerOffset)) { + throw Exception(inputName, input.tellg(), "invalid header: \"" + StringUtility::cEscape(header.substr(0, 30)) + "\"" + + (header.size() > 30 ? "..." : "")); + } } + } else if (2 == settings_.version) { + while (true) { + // Read the message header + HeaderVersion2 header; + const size_t headerOffset = input.tellg(); + input.read((char*)&header, sizeof header); + const size_t nHeader = input.gcount(); + header.payloadSize = BitOps::fromLittleEndian(header.payloadSize); + header.addr = BitOps::fromLittleEndian(header.addr); + + if (0 == nHeader) { + break; + } else if (nHeader != sizeof header) { + throw Exception(inputName, headerOffset, + (boost::format("short read (expected %1%, got only %2%) at %3%") + % sizeof(header) % nHeader % headerOffset).str()); + } else if (2 != header.version) { + throw Exception(inputName, headerOffset, + (boost::format("invalid message version (expected %1%, got %2%) at %3%") + % settings_.version % header.version % headerOffset).str()); + } else if (header.unused0 || header.unused1) { + throw Exception(inputName, headerOffset, (boost::format("unused fields must be zero at %1%") % headerOffset).str()); + } else if (header.mapFlags & ~MemoryMap::READ_WRITE_EXECUTE) { + throw Exception(inputName, headerOffset, (boost::format("invalid map flags at %1%") % headerOffset).str()); + } else if (header.payloadSize > 0) { + std::vector buf(header.payloadSize); + input.read((char*)buf.data(), header.payloadSize); + const size_t nPayload = input.gcount(); + if (nPayload != header.payloadSize) { + throw Exception(inputName, headerOffset, + (boost::format("short payload read (expected %1%, got only %2%) at %3%)") + % header.payloadSize % nPayload % (headerOffset + sizeof header)).str()); + } else if (memory) { + const auto where = AddressInterval::baseSize(header.addr, header.payloadSize); + SAWYER_MESG(debug) <<"vxcore: addresses " <insert(where, MemoryMap::Segment::anonymousInstance(header.payloadSize, header.mapFlags, inputName)); + const size_t nCopied = memory->at(header.addr).limit(header.payloadSize).write(buf.data()).size(); + ASSERT_always_require(nCopied == header.payloadSize); + } + } + } + } else { + ASSERT_not_implemented("vxcore version " + boost::lexical_cast(settings_.version)); } } @@ -222,19 +272,34 @@ VxcoreParser::unparse(std::ostream &out, const MemoryMap::Ptr &memory, const Add const std::string &outputName) { if (memory && !memoryLimit.isEmpty()) { rose_addr_t va = memoryLimit.least(); - while (const AddressInterval selected = memory->atOrAfter(va).singleSegment().available() & memoryLimit) { + const size_t maxPayload = 0xffffffff; + while (const AddressInterval selected = memory->atOrAfter(va).limit(maxPayload).singleSegment().available() & memoryLimit) { MemoryMap::ConstNodeIterator inode = memory->at(selected.least()).nodes().begin(); ASSERT_forbid(inode == memory->nodes().end()); // because of the while loop's condition ASSERT_require(inode->key().contains(selected)); const MemoryMap::Segment &segment = inode->value(); // Header - out <(selected.size())); + header.addr = BitOps::toLittleEndian(boost::numeric_cast(selected.least())); + out.write((const char*)&header, sizeof header); + if (!out.good()) + throw Exception(outputName, out.tellp(), "write failed"); + } else { + ASSERT_not_implemented("vxcore version " + boost::lexical_cast(settings_.version)); + } // Data output one buffer-full at a time since the memory map' underlying buffer might not be storing the bytes // contiguously, but we need contiguous bytes for std::ostream::write. @@ -265,16 +330,22 @@ VxcoreParser::unparse(std::ostream &out, const MemoryMap::Ptr &memory, const Add } if (registers) { - ASSERT_not_null(ops); - out <<"registers " <registerDictionary()->name() <<"\n"; - RegisterDictionary::RegisterDescriptors regs = registers->registerDictionary()->getLargestRegisters(); - RegisterNames registerName(registers->registerDictionary()); - BOOST_FOREACH (RegisterDescriptor reg, regs) { - BaseSemantics::SValue::Ptr val = registers->peekRegister(reg, ops->undefined_(reg.nBits()), ops.get()); - if (auto number = val->toUnsigned()) - out <<(boost::format("%s 0x%x\n") % registerName(reg) % *number); + if (1 == settings_.version) { + ASSERT_not_null(ops); + out <<"registers " <registerDictionary()->name() <<"\n"; + RegisterDictionary::RegisterDescriptors regs = registers->registerDictionary()->getLargestRegisters(); + RegisterNames registerName(registers->registerDictionary()); + BOOST_FOREACH (RegisterDescriptor reg, regs) { + BaseSemantics::SValue::Ptr val = registers->peekRegister(reg, ops->undefined_(reg.nBits()), ops.get()); + if (auto number = val->toUnsigned()) + out <<(boost::format("%s 0x%x\n") % registerName(reg) % *number); + } + out <<"end\n"; + } else if (2 == settings_.version) { + // Registers are not stored for version 2 + } else { + ASSERT_not_implemented("vxcore version " + boost::lexical_cast(settings_.version)); } - out <<"end\n"; } } diff --git a/src/frontend/BinaryFormats/BinaryVxcoreParser.h b/src/frontend/BinaryFormats/BinaryVxcoreParser.h index 627a45c877..47edc67d06 100644 --- a/src/frontend/BinaryFormats/BinaryVxcoreParser.h +++ b/src/frontend/BinaryFormats/BinaryVxcoreParser.h @@ -34,7 +34,16 @@ namespace BinaryAnalysis { * * A register record (at most one per file) begins with the word "registers", a space, and the instruction set architecture name * recognized by ROSE. Following the header is one line per register, each line being a register name recognized by ROSE, a colon, - * optional horizontal white space, and a hexadecimal value, this time with a leading "0x". */ + * optional horizontal white space, and a hexadecimal value, this time with a leading "0x". + * + * This format version was designed by Jim Leek. + * + * @seciton vxcore_v2 Version 2 + * + * Version 2 of this format is a sequence of messages consisting of a binary header followed by a binary payload. Each header + * contains naturally aligned fields: a one byte version number having the value 2; two bytes not currently used for any purpose; + * one byte containing the memory access permission bits (see @ref MemoryMap); a four-byte little-endian payload size in bytes; an + * eight-byte little-endian starting memory address. */ class VxcoreParser { public: /** Settings that control the parser and unparser. */ @@ -77,6 +86,17 @@ class VxcoreParser { } }; +private: + // Message headers for version 2. + struct HeaderVersion2 { + uint8_t version; // must be 2 + uint8_t unused0; + uint8_t unused1; + uint8_t mapFlags; // MemoryMap::{READABLE,WRITABLE,EXECUTABLE} + uint32_t payloadSize; // little-endian + uint64_t addr; // little-endian + }; + private: Settings settings_; std::string isaName_; // Parsed instruction set architecture name diff --git a/tools/BinaryAnalysis/bat-mem.C b/tools/BinaryAnalysis/bat-mem.C index 343fc403f6..9659a7ef6e 100644 --- a/tools/BinaryAnalysis/bat-mem.C +++ b/tools/BinaryAnalysis/bat-mem.C @@ -27,12 +27,12 @@ namespace { Sawyer::Message::Facility mlog; -enum class OutputFormat { UNSPECIFIED, NONE, HEXDUMP, SRECORDS, BINARY, VXCORE }; +enum class OutputFormat { UNSPECIFIED, NONE, HEXDUMP, SRECORDS, INTEL_HEX, BINARY, VXCORE_1, VXCORE_2 }; struct Settings { SerialIo::Format stateFormat = SerialIo::BINARY; OutputFormat outputFormat = OutputFormat::UNSPECIFIED; - SRecord::Syntax srecordSyntax = SRecord::SREC_MOTOROLA; + Sawyer::Optional srecordSyntax; std::string outputPrefix; std::vector where; }; @@ -52,12 +52,16 @@ parseCommandLine(int argc, char *argv[], Settings &settings) { output.insert(Switch("format", 'F') .argument("fmt", enumParser(settings.outputFormat) - ->with("none", OutputFormat::NONE) - ->with("hexdump", OutputFormat::HEXDUMP) - ->with("srecord", OutputFormat::SRECORDS) - ->with("srecords", OutputFormat::SRECORDS) - ->with("binary", OutputFormat::BINARY) - ->with("vxcore", OutputFormat::VXCORE)) + ->with("none", OutputFormat::NONE) + ->with("hexdump", OutputFormat::HEXDUMP) + ->with("srec", OutputFormat::SRECORDS) + ->with("hex", OutputFormat::INTEL_HEX) + ->with("binary", OutputFormat::BINARY) + ->with("vxcore-1", OutputFormat::VXCORE_1) + ->with("vxcore-2", OutputFormat::VXCORE_2) + ->with("vxcore", OutputFormat::VXCORE_1) // backward compatibility, no longer documented + ->with("srecord", OutputFormat::SRECORDS) // backward compatibility, no longer documented + ->with("srecords", OutputFormat::SRECORDS)) // backward compatibility, no longer documented .doc("Style of output to use when dumping memory. The choices are:" "@named{none}{Don't dump any data; just show basic information about the memory map. This " @@ -66,7 +70,9 @@ parseCommandLine(int argc, char *argv[], Settings &settings) { "@named{hexdump}{Show the data in a format similar to the @man{hexdump}{1} command. This is " "the default if addresses are specified with @s{where}.}" - "@named{srecords}{Emit the data as Motorola S-Records.}" + "@named{srec}{Emit the data as Motorola S-Records.}" + + "@named{hex}{Emit the data in Intel-HEX format.}" "@named{binary}{Save the data to one or more binary files, one per region of memory. The file " "names are generated by appending the hexadecimal starting address (without leading \"0x\") and " @@ -74,19 +80,17 @@ parseCommandLine(int argc, char *argv[], Settings &settings) { "index file is emitted to standard output and contains the command-line arguments that describe " "how to load the binary files back into memory.}" - "@named{vxcore}{Save the data to a binary file whose format is defined by ROSE. The @s{prefix} " - "is used as the output file name.}")); + "@named{vxcore-1}{Save the data to a binary file whose format is defined by ROSE. Use the version 1 " + "variant of this format. The @s{prefix} is used as the output file name.}" + + "@named{vxcore-2}{Save the data to a binary file whose format is defined by ROSE. Use the version 2 " + "variant of this format. The @s{prefix} is used as the output file name.}")); output.insert(Switch("srecord-syntax") .argument("syntax", enumParser(settings.srecordSyntax) ->with("motorola", SRecord::SREC_MOTOROLA) ->with("intel", SRecord::SREC_INTEL)) - .doc("When generating S-Record output (@s{format}=srecords), this switch controls which syntax family " - "to use. The choices are:" - "@named{motorola}{Motorola S-Record syntax." + - std::string(SRecord::SREC_MOTOROLA == settings.srecordSyntax ? " This is the default." : "") + "}" - "@named{intel}{Intel HEX syntax." + - std::string(SRecord::SREC_INTEL == settings.srecordSyntax ? " This is the default." : "") + "}")); + .hidden(true)); // backward compatibility, no longer documented output.insert(Switch("output", 'o') .longName("prefix") @@ -243,7 +247,7 @@ main(int argc, char *argv[]) { } partitioner->memoryMap()->dump(std::cout); - } else if (OutputFormat::VXCORE == settings.outputFormat) { + } else if (OutputFormat::VXCORE_1 == settings.outputFormat || OutputFormat::VXCORE_2 == settings.outputFormat) { if (settings.outputPrefix.empty()) { mlog[FATAL] <<"an output file must be specified for binary output formats\n"; exit(1); @@ -253,6 +257,7 @@ main(int argc, char *argv[]) { if (!output.good()) throw std::runtime_error("cannot create \"" + outputName); Rose::BinaryAnalysis::VxcoreParser parser; + parser.settings().version = OutputFormat::VXCORE_1 == settings.outputFormat ? 1 : 2; for (AddressInterval where: settings.where) parser.unparse(output, map, where, "output"); exit(0); @@ -276,7 +281,10 @@ main(int argc, char *argv[]) { HexDumper()(settings, map, interval, std::cout); break; case OutputFormat::SRECORDS: - SRecordDumper(settings.srecordSyntax)(settings, map, interval, std::cout); + SRecordDumper(settings.srecordSyntax.orElse(SRecord::SREC_MOTOROLA))(settings, map, interval, std::cout); + break; + case OutputFormat::INTEL_HEX: + SRecordDumper(SRecord::SREC_INTEL)(settings, map, interval, std::cout); break; case OutputFormat::BINARY: { boost::filesystem::path outputName = settings.outputPrefix + @@ -297,7 +305,8 @@ main(int argc, char *argv[]) { <<"::" <