Skip to content

Commit f40926f

Browse files
matzke1rosecompiler
authored andcommitted
(Binary Analysis) New simple memory image format
* Created a new memory image format by extending ROSE's "vxcore" format to version 2. The new format uses a simple yet extensible, all binary, naturally aligned, fixed-endian, message header that contains only what's needed to restore memory to its original contents. It is intended to be easily produced on small devices. Issue #216
1 parent a69e1a9 commit f40926f

File tree

4 files changed

+154
-54
lines changed

4 files changed

+154
-54
lines changed

src/Rose/BinaryAnalysis/Partitioner2/EngineBinary.C

+5-5
Original file line numberDiff line numberDiff line change
@@ -949,11 +949,11 @@ EngineBinary::specimenNameDocumentation() {
949949

950950
"@bullet{If the name begins with the string \"vxcore:\" then it is treated as a special VxWorks core dump "
951951
"in a format defined by ROSE. The complete specification has the syntax \"vxcore:[@v{memory_attributes}]"
952-
":[@v{file_attributes}]:@v{file_name}\". The parts in square brackets are optional. The only memory attribute "
953-
"recognized at this time is an equal sign (\"=\") followed by zero of more of the letters \"r\" (read), "
954-
"\"w\" (write), and \"x\" (execute) to specify the mapping permissions. The default mapping permission if "
955-
"no equal sign is specified is read, write, and execute. The only file attribute recognized at this time is "
956-
"\"version=@v{v}\" where @v{v} is a version number, and ROSE currently supports only version 1.}"
952+
":[@v{file_attributes}]:@v{file_name}\". The parts in square brackets are optional. The only file attribute "
953+
"recognized at this time is \"version=@v{v}\" where @v{v} is a version number which must be 1 or 2, defaulting "
954+
"to 1. For version 1, the only memory attribute is an equal sign (\"=\") followed by zero of more of the letters "
955+
"\"r\" (read), \"w\" (write), and \"x\" (execute) to specify the mapping permissions, defaulting to read, write, and "
956+
"execute. Version 2 has no memory attributes.}"
957957

958958
"@bullet{If the name begins with the string \"meta:\" then it adjusts meta information about the memory "
959959
"map, such as permissions. " + MemoryMap::adjustMapDocumentation() + "}"

src/frontend/BinaryFormats/BinaryVxcoreParser.C

+99-28
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,13 @@ VxcoreParser::parseUrl(const std::string &spec) {
7070
throw Exception("URL", 0, "invalid file attribute for vxcore URL: \"" + StringUtility::cEscape(parts[1]) + "\"");
7171
}
7272
}
73-
if (settings_.version != 1)
73+
if (settings_.version != 1 && settings_.version != 2)
7474
throw Exception("URL", 0, "vxcore version " + boost::lexical_cast<std::string>(settings_.version) + " is not supported");
7575

76+
// Error checking
77+
if (2 == settings_.version && settings_.protOverride)
78+
throw Exception("URL", 0, "vxcore version 2 does not support memory protection override (\"=\" attribute)");
79+
7680
return parts[2];
7781
}
7882

@@ -97,18 +101,64 @@ VxcoreParser::parse(const boost::filesystem::path &fileName, const MemoryMap::Pt
97101
void
98102
VxcoreParser::parse(std::istream &input, const MemoryMap::Ptr &memory, const BaseSemantics::RegisterState::Ptr &registers,
99103
const BaseSemantics::RiscOperators::Ptr &ops, const std::string &inputName) {
100-
for (size_t segmentIdx = 0; input; ++segmentIdx) {
101-
size_t headerOffset = input.tellg();
102-
std::string header = rose_getline(input);
103-
if (header.empty())
104-
break; // EOF
105-
106-
std::string name = inputName + " segment #" + boost::lexical_cast<std::string>(segmentIdx);
107-
if (!parseMemory(header, input, memory, name, headerOffset) &&
108-
!parseRegisters(header, input, registers, ops, name, headerOffset)) {
109-
throw Exception(inputName, input.tellg(), "invalid header: \"" + StringUtility::cEscape(header.substr(0, 30)) + "\"" +
110-
(header.size() > 30 ? "..." : ""));
104+
Sawyer::Message::Stream debug(mlog[DEBUG]);
105+
if (1 == settings_.version) {
106+
for (size_t segmentIdx = 0; input; ++segmentIdx) {
107+
size_t headerOffset = input.tellg();
108+
std::string header = rose_getline(input);
109+
if (header.empty())
110+
break; // EOF
111+
112+
std::string name = inputName + " segment #" + boost::lexical_cast<std::string>(segmentIdx);
113+
if (!parseMemory(header, input, memory, name, headerOffset) &&
114+
!parseRegisters(header, input, registers, ops, name, headerOffset)) {
115+
throw Exception(inputName, input.tellg(), "invalid header: \"" + StringUtility::cEscape(header.substr(0, 30)) + "\"" +
116+
(header.size() > 30 ? "..." : ""));
117+
}
111118
}
119+
} else if (2 == settings_.version) {
120+
while (true) {
121+
// Read the message header
122+
HeaderVersion2 header;
123+
const size_t headerOffset = input.tellg();
124+
input.read((char*)&header, sizeof header);
125+
const size_t nHeader = input.gcount();
126+
header.payloadSize = BitOps::fromLittleEndian(header.payloadSize);
127+
header.addr = BitOps::fromLittleEndian(header.addr);
128+
129+
if (0 == nHeader) {
130+
break;
131+
} else if (nHeader != sizeof header) {
132+
throw Exception(inputName, headerOffset,
133+
(boost::format("short read (expected %1%, got only %2%) at %3%")
134+
% sizeof(header) % nHeader % headerOffset).str());
135+
} else if (2 != header.version) {
136+
throw Exception(inputName, headerOffset,
137+
(boost::format("invalid message version (expected %1%, got %2%) at %3%")
138+
% settings_.version % header.version % headerOffset).str());
139+
} else if (header.unused0 || header.unused1) {
140+
throw Exception(inputName, headerOffset, (boost::format("unused fields must be zero at %1%") % headerOffset).str());
141+
} else if (header.mapFlags & ~MemoryMap::READ_WRITE_EXECUTE) {
142+
throw Exception(inputName, headerOffset, (boost::format("invalid map flags at %1%") % headerOffset).str());
143+
} else if (header.payloadSize > 0) {
144+
std::vector<uint8_t> buf(header.payloadSize);
145+
input.read((char*)buf.data(), header.payloadSize);
146+
const size_t nPayload = input.gcount();
147+
if (nPayload != header.payloadSize) {
148+
throw Exception(inputName, headerOffset,
149+
(boost::format("short payload read (expected %1%, got only %2%) at %3%)")
150+
% header.payloadSize % nPayload % (headerOffset + sizeof header)).str());
151+
} else if (memory) {
152+
const auto where = AddressInterval::baseSize(header.addr, header.payloadSize);
153+
SAWYER_MESG(debug) <<"vxcore: addresses " <<StringUtility::addrToString(where) <<" at " <<headerOffset <<"\n";
154+
memory->insert(where, MemoryMap::Segment::anonymousInstance(header.payloadSize, header.mapFlags, inputName));
155+
const size_t nCopied = memory->at(header.addr).limit(header.payloadSize).write(buf.data()).size();
156+
ASSERT_always_require(nCopied == header.payloadSize);
157+
}
158+
}
159+
}
160+
} else {
161+
ASSERT_not_implemented("vxcore version " + boost::lexical_cast<std::string>(settings_.version));
112162
}
113163
}
114164

@@ -222,19 +272,34 @@ VxcoreParser::unparse(std::ostream &out, const MemoryMap::Ptr &memory, const Add
222272
const std::string &outputName) {
223273
if (memory && !memoryLimit.isEmpty()) {
224274
rose_addr_t va = memoryLimit.least();
225-
while (const AddressInterval selected = memory->atOrAfter(va).singleSegment().available() & memoryLimit) {
275+
const size_t maxPayload = 0xffffffff;
276+
while (const AddressInterval selected = memory->atOrAfter(va).limit(maxPayload).singleSegment().available() & memoryLimit) {
226277
MemoryMap::ConstNodeIterator inode = memory->at(selected.least()).nodes().begin();
227278
ASSERT_forbid(inode == memory->nodes().end()); // because of the while loop's condition
228279
ASSERT_require(inode->key().contains(selected));
229280
const MemoryMap::Segment &segment = inode->value();
230281

231282
// Header
232-
out <<StringUtility::addrToString(selected.least()).substr(2)
233-
<<" " <<StringUtility::addrToString(selected.size()).substr(2)
234-
<<" =" <<(0 != (segment.accessibility() & MemoryMap::READABLE) ? "R" : "-")
235-
<<(0 != (segment.accessibility() & MemoryMap::WRITABLE) ? "W" : "-")
236-
<<(0 != (segment.accessibility() & MemoryMap::EXECUTABLE) ? "X" : "-")
237-
<<"\n";
283+
if (1 == settings_.version) {
284+
out <<StringUtility::addrToString(selected.least()).substr(2)
285+
<<" " <<StringUtility::addrToString(selected.size()).substr(2)
286+
<<" =" <<(0 != (segment.accessibility() & MemoryMap::READABLE) ? "R" : "-")
287+
<<(0 != (segment.accessibility() & MemoryMap::WRITABLE) ? "W" : "-")
288+
<<(0 != (segment.accessibility() & MemoryMap::EXECUTABLE) ? "X" : "-")
289+
<<"\n";
290+
} else if (2 == settings_.version) {
291+
HeaderVersion2 header;
292+
memset(&header, 0, sizeof header);
293+
header.version = settings_.version;
294+
header.mapFlags = segment.accessibility() & MemoryMap::READ_WRITE_EXECUTE;
295+
header.payloadSize = BitOps::toLittleEndian(boost::numeric_cast<uint32_t>(selected.size()));
296+
header.addr = BitOps::toLittleEndian(boost::numeric_cast<uint64_t>(selected.least()));
297+
out.write((const char*)&header, sizeof header);
298+
if (!out.good())
299+
throw Exception(outputName, out.tellp(), "write failed");
300+
} else {
301+
ASSERT_not_implemented("vxcore version " + boost::lexical_cast<std::string>(settings_.version));
302+
}
238303

239304
// Data output one buffer-full at a time since the memory map' underlying buffer might not be storing the bytes
240305
// 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
265330
}
266331

267332
if (registers) {
268-
ASSERT_not_null(ops);
269-
out <<"registers " <<registers->registerDictionary()->name() <<"\n";
270-
RegisterDictionary::RegisterDescriptors regs = registers->registerDictionary()->getLargestRegisters();
271-
RegisterNames registerName(registers->registerDictionary());
272-
BOOST_FOREACH (RegisterDescriptor reg, regs) {
273-
BaseSemantics::SValue::Ptr val = registers->peekRegister(reg, ops->undefined_(reg.nBits()), ops.get());
274-
if (auto number = val->toUnsigned())
275-
out <<(boost::format("%s 0x%x\n") % registerName(reg) % *number);
333+
if (1 == settings_.version) {
334+
ASSERT_not_null(ops);
335+
out <<"registers " <<registers->registerDictionary()->name() <<"\n";
336+
RegisterDictionary::RegisterDescriptors regs = registers->registerDictionary()->getLargestRegisters();
337+
RegisterNames registerName(registers->registerDictionary());
338+
BOOST_FOREACH (RegisterDescriptor reg, regs) {
339+
BaseSemantics::SValue::Ptr val = registers->peekRegister(reg, ops->undefined_(reg.nBits()), ops.get());
340+
if (auto number = val->toUnsigned())
341+
out <<(boost::format("%s 0x%x\n") % registerName(reg) % *number);
342+
}
343+
out <<"end\n";
344+
} else if (2 == settings_.version) {
345+
// Registers are not stored for version 2
346+
} else {
347+
ASSERT_not_implemented("vxcore version " + boost::lexical_cast<std::string>(settings_.version));
276348
}
277-
out <<"end\n";
278349
}
279350
}
280351

src/frontend/BinaryFormats/BinaryVxcoreParser.h

+21-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,16 @@ namespace BinaryAnalysis {
3434
*
3535
* A register record (at most one per file) begins with the word "registers", a space, and the instruction set architecture name
3636
* recognized by ROSE. Following the header is one line per register, each line being a register name recognized by ROSE, a colon,
37-
* optional horizontal white space, and a hexadecimal value, this time with a leading "0x". */
37+
* optional horizontal white space, and a hexadecimal value, this time with a leading "0x".
38+
*
39+
* This format version was designed by Jim Leek.
40+
*
41+
* @seciton vxcore_v2 Version 2
42+
*
43+
* Version 2 of this format is a sequence of messages consisting of a binary header followed by a binary payload. Each header
44+
* contains naturally aligned fields: a one byte version number having the value 2; two bytes not currently used for any purpose;
45+
* one byte containing the memory access permission bits (see @ref MemoryMap); a four-byte little-endian payload size in bytes; an
46+
* eight-byte little-endian starting memory address. */
3847
class VxcoreParser {
3948
public:
4049
/** Settings that control the parser and unparser. */
@@ -77,6 +86,17 @@ class VxcoreParser {
7786
}
7887
};
7988

89+
private:
90+
// Message headers for version 2.
91+
struct HeaderVersion2 {
92+
uint8_t version; // must be 2
93+
uint8_t unused0;
94+
uint8_t unused1;
95+
uint8_t mapFlags; // MemoryMap::{READABLE,WRITABLE,EXECUTABLE}
96+
uint32_t payloadSize; // little-endian
97+
uint64_t addr; // little-endian
98+
};
99+
80100
private:
81101
Settings settings_;
82102
std::string isaName_; // Parsed instruction set architecture name

0 commit comments

Comments
 (0)