DataHoarder
3868034b6b
All checks were successful
continuous-integration/drone/push Build is passing
328 lines
13 KiB
C++
328 lines
13 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2020, rrcSmall FM10K-Documentation Contributors
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of the copyright holder nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*****************************************************************************/
|
|
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
#include <queue>
|
|
#include "ImageFormat.h"
|
|
#include "Registers.h"
|
|
#include "instructions/Write.h"
|
|
|
|
std::unique_ptr<Instruction::Instruction> ImageFormat::NULL_INSTRUCTION(nullptr);
|
|
|
|
ImageFormat ImageFormat::fromBytes(const std::vector<uint8_t> &imageBytes) {
|
|
ImageFormat image;
|
|
|
|
image.baseImage = imageBytes;
|
|
|
|
uint32_t offset = 0;
|
|
|
|
uint8_t spiConfig = imageBytes[offset++];
|
|
image.header.reserved = (spiConfig) & 0b111;
|
|
image.header.speed = static_cast<HeaderSpeed>((spiConfig >> 3) & 0b111);
|
|
image.header.mode = static_cast<HeaderMode>((spiConfig >> 6) & 0b11);
|
|
|
|
image.header.baseAddress = imageBytes[offset++] << 16;
|
|
image.header.baseAddress |= imageBytes[offset++] << 8;
|
|
image.header.baseAddress |= imageBytes[offset++];
|
|
|
|
offset = CFG_SIGNATURE;
|
|
uint8_t currentCharacter;
|
|
while ((currentCharacter = imageBytes[offset++]) != 0xFF) {
|
|
image.imageSignature += currentCharacter;
|
|
}
|
|
|
|
offset = CFG_HEADER;
|
|
image.cfgHeader.length = imageBytes[offset++];
|
|
|
|
image.cfgHeader.base = imageBytes[offset++] << 16;
|
|
image.cfgHeader.base |= imageBytes[offset++] << 8;
|
|
image.cfgHeader.base |= imageBytes[offset++];
|
|
|
|
offset = image.cfgHeader.base;
|
|
|
|
image.cfg.fileFormat = imageBytes[offset++];
|
|
image.cfg.version = imageBytes[offset++];
|
|
|
|
image.cfg.length = imageBytes[offset++] << 8;
|
|
image.cfg.length |= imageBytes[offset++];
|
|
image.cfg.length <<= 4;
|
|
|
|
|
|
if (image.cfg.version == 0) {
|
|
if (image.cfg.fileFormat == 1) {
|
|
offset = image.cfgHeader.base + CFG_LENGTH;
|
|
uint32_t imageSize = image.cfg.length;
|
|
uint32_t bytesCnt = 0;
|
|
|
|
while (bytesCnt < imageSize) {
|
|
std::string currentEntry;
|
|
uint8_t character;
|
|
do {
|
|
character = imageBytes[offset++];
|
|
++bytesCnt;
|
|
if (character != 0xFF) {
|
|
currentEntry += character;
|
|
if (character == '\n') {
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
} while (true);
|
|
|
|
if (!currentEntry.empty()) {
|
|
if (currentEntry[currentEntry.size() - 1] == '\n') {
|
|
currentEntry.resize(currentEntry.size() - 1);
|
|
}
|
|
image.bootConfig.addEntry(currentEntry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
image.decodeAnalyzeInstructionsAt(image.header.baseAddress);
|
|
|
|
return image;
|
|
}
|
|
|
|
std::vector<uint8_t> ImageFormat::toBytes() const {
|
|
std::vector<uint8_t> bytes;
|
|
bytes.resize(getBaseImage().size() - 1, 0xFF);
|
|
|
|
uint32_t offset = 0;
|
|
|
|
|
|
bytes[offset++] = (header.reserved) | ((uint8_t) header.speed << 3) | ((uint8_t) header.mode << 6);
|
|
bytes[offset++] = (header.baseAddress >> 16) & 0xFF;
|
|
bytes[offset++] = (header.baseAddress >> 8) & 0xFF;
|
|
bytes[offset++] = header.baseAddress & 0xFF;
|
|
|
|
for (; offset < CFG_HEADER; ++offset) {
|
|
bytes[offset] = getBaseImage()[offset];
|
|
}
|
|
|
|
bytes[offset++] = cfgHeader.length;
|
|
bytes[offset++] = (cfgHeader.base >> 16) & 0xFF;
|
|
bytes[offset++] = (cfgHeader.base >> 8) & 0xFF;
|
|
bytes[offset++] = cfgHeader.base & 0xFF;
|
|
|
|
for (; offset < CFG_SIGNATURE; ++offset) {
|
|
bytes[offset] = getBaseImage()[offset];
|
|
}
|
|
|
|
std::copy(imageSignature.begin(), imageSignature.end(), bytes.begin() + offset);
|
|
|
|
offset += imageSignature.length();
|
|
|
|
for (; offset < cfgHeader.base; ++offset) {
|
|
bytes[offset] = getBaseImage()[offset];
|
|
}
|
|
|
|
bytes[offset++] = cfg.fileFormat;
|
|
bytes[offset++] = cfg.version;
|
|
|
|
bytes[offset++] = ((cfg.length >> 4) >> 8) & 0xFF;
|
|
bytes[offset++] = (cfg.length >> 4) & 0xFF;
|
|
|
|
for (; offset < cfgHeader.base + CFG_LENGTH; ++offset) {
|
|
bytes[offset] = getBaseImage()[offset];
|
|
}
|
|
|
|
if (cfg.version == 0) {
|
|
if (cfg.fileFormat == 1) {
|
|
for (const auto &entry : bootConfig.getAllEntries()) {
|
|
std::copy(entry.begin(), entry.end(), bytes.begin() + offset);
|
|
offset += entry.length();
|
|
bytes[offset++] = '\n';
|
|
}
|
|
bytes[offset++] = 0xFF;
|
|
}
|
|
}
|
|
|
|
for (; offset < 0x10000; ++offset) {
|
|
bytes[offset] = 0xFF;
|
|
}
|
|
|
|
std::copy(getBaseImage().begin() + offset, getBaseImage().end(), bytes.begin() + offset);
|
|
|
|
for (const auto &entry : instructions) {
|
|
auto data = entry.second->toBytes();
|
|
std::copy(data.begin(), data.end(), bytes.begin() + entry.second->getAddress());
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
|
|
void ImageFormat::decodeAnalyzeInstructionsAt(uint32_t offset) {
|
|
jumpTable.clear();
|
|
|
|
std::queue<AnalysisState> savedStates;
|
|
std::vector<AnalysisState> branchingStates;
|
|
|
|
std::unordered_map<uint32_t, bool> jumpsUsed;
|
|
|
|
AnalysisState baseState(offset, jumpTable);
|
|
|
|
savedStates.push(baseState);
|
|
|
|
uint32_t maxUnchangedExecutions = 1000;
|
|
uint32_t maxTotalExecutions = 20000;
|
|
uint32_t speculativeJumps = 0;
|
|
|
|
while (!savedStates.empty()) {
|
|
AnalysisState state = savedStates.front();
|
|
savedStates.pop();
|
|
|
|
uint32_t loopsSinceLastModification = 0;
|
|
uint32_t absoluteLoops = 0;
|
|
|
|
do {
|
|
|
|
if (jumpsUsed.find(state.current) != jumpsUsed.end() && !jumpsUsed[state.current]) {
|
|
speculativeJumps++;
|
|
}
|
|
|
|
jumpsUsed[state.current] = true;
|
|
|
|
if (state.current >= 0x100000 || (findInstructionByAddress(state.current) == nullptr &&
|
|
findInstructionByAddress(state.current, true) !=
|
|
nullptr)) { //Prevent arbitrary decoding in between decoded instructions
|
|
break;
|
|
} else if (findInstructionByAddress(state.current) == nullptr) {
|
|
auto decodedInstruction = Instruction::Instruction::decodeInstructionFromBytes(state.current,
|
|
getBaseImage());
|
|
instructions[decodedInstruction->getAddress()] = std::move(decodedInstruction);
|
|
}
|
|
|
|
if (loopsSinceLastModification > 800) {
|
|
//std::cout << "TOO UNCHANGED " << std::hex << state.previous << " -> " << std::hex << state.current << "\n";
|
|
|
|
}
|
|
|
|
state.previous = state.current;
|
|
auto &instruction = findInstructionByAddress(state.current);
|
|
|
|
if (instruction == nullptr) {
|
|
break;
|
|
}
|
|
|
|
state.setRegister((uint32_t)KnownRegisters::BSM_CTRL, (state.getRegister((uint32_t)KnownRegisters::BSM_CTRL) & 0xFF) | ((instruction->getEndAddress()) << 8)); // Set EepromAddr as next address
|
|
auto possibleBranches = instruction->execute(state);
|
|
|
|
if ((instruction->getCommand() == Instruction::Instruction::CommandOp::JUMP ||
|
|
instruction->getCommand() == Instruction::Instruction::CommandOp::RETURN) &&
|
|
jumpsUsed.find(instruction->getEndAddress()) == jumpsUsed.end()) {
|
|
jumpsUsed[instruction->getEndAddress()] = false; //TODO: remove this or make it opt-in by default
|
|
}
|
|
|
|
if (jumpsUsed.find(state.current) != jumpsUsed.end() && !jumpsUsed[state.current]) {
|
|
jumpsUsed.erase(state.current); //Clear to recognize a non-speculative jump
|
|
}
|
|
|
|
if (state.current == 0) {
|
|
//std::cout << "EXIT DUE TO END " << std::hex << state.previous << " -> " << std::hex << state.current << "\n";
|
|
break;
|
|
}
|
|
|
|
|
|
//Handle interrupts
|
|
uint32_t addr = state.getRegister((uint32_t) KnownRegisters::BSM_ARGS) & 0xFFFFFF;
|
|
if(addr != 0 && jumpsUsed.find(addr) == jumpsUsed.end()){
|
|
jumpsUsed[addr] = false;
|
|
}
|
|
|
|
if (instruction->getCommand() == Instruction::Instruction::CommandOp::JUMP) {
|
|
uint32_t nextAddress = instruction->getAddress() - 1;
|
|
while (true) {
|
|
const auto &previousInstruction = findInstructionByAddress(nextAddress, true);
|
|
if (previousInstruction != nullptr &&
|
|
previousInstruction->getCommand() == Instruction::Instruction::CommandOp::WRITE) {
|
|
const auto &writeInstruction = reinterpret_cast<const std::unique_ptr<Instruction::Write> &>(previousInstruction);
|
|
if (
|
|
(
|
|
writeInstruction->address.address == (uint32_t) KnownRegisters::MGMT_SCRATCH_1
|
|
|| (writeInstruction->address.address >= (uint32_t) KnownRegisters::BSM_SCRATCH_START &&
|
|
writeInstruction->address.address < (uint32_t) KnownRegisters::BSM_SCRATCH_END)
|
|
)
|
|
&& writeInstruction->data.size() == 1
|
|
) { //This is commonly used before jumps to mark return values or switch statements
|
|
if (jumpsUsed.find(writeInstruction->data[0]) == jumpsUsed.end()) {
|
|
jumpsUsed[writeInstruction->data[0]] = false;
|
|
}
|
|
}
|
|
|
|
nextAddress = previousInstruction->getAddress() - 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for (const auto &branch : possibleBranches) {
|
|
AnalysisState newState(state);
|
|
newState.current = branch.first;
|
|
for (const auto &entry : branch.second) {
|
|
newState.setRegister(entry.first, entry.second);
|
|
}
|
|
|
|
bool stateExists = false;
|
|
for (const auto &previousState : branchingStates) {
|
|
if (newState.previous == previousState.previous && newState.current == previousState.current) {
|
|
stateExists = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!stateExists) {
|
|
loopsSinceLastModification = 0;
|
|
savedStates.push(newState);
|
|
branchingStates.push_back(std::move(newState));
|
|
}
|
|
}
|
|
} while (loopsSinceLastModification++ < maxUnchangedExecutions && absoluteLoops++ < maxTotalExecutions);
|
|
|
|
if (savedStates.empty()) {
|
|
for (auto &visited : jumpsUsed) {
|
|
if (!visited.second && visited.first >= offset && visited.first <= 0x100000) {
|
|
baseState.current = visited.first;
|
|
baseState.addKnownJump(visited.first, 0, JumpKind::Speculative);
|
|
savedStates.push(baseState);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::cerr << "Next state, branched states left: " << std::dec << savedStates.size()
|
|
<< /*", executed states: " << std::dec << createdStates.size() <<*/ ", speculative jumps: "
|
|
<< std::dec << speculativeJumps << ", total instructions decoded: "
|
|
<< std::dec << instructions.size() << "\n";
|
|
}
|
|
}
|