diff --git a/src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.cpp b/src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.cpp index ae42cd38..4e4ab5e4 100644 --- a/src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.cpp +++ b/src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.cpp @@ -12,7 +12,7 @@ namespace DebugServer::Gdb::RiscVGdb RiscVGdbTargetDescriptor::RiscVGdbTargetDescriptor(const Targets::TargetDescriptor& targetDescriptor) : systemAddressSpaceDescriptor(targetDescriptor.getAddressSpaceDescriptor("system")) , cpuAddressSpaceDescriptor(targetDescriptor.getAddressSpaceDescriptor("debug_module")) - , programMemorySegmentDescriptor(this->systemAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_program_memory")) + , programMemorySegmentDescriptor(this->systemAddressSpaceDescriptor.getMemorySegmentDescriptor("main_program")) , gpRegistersMemorySegmentDescriptor(this->cpuAddressSpaceDescriptor.getMemorySegmentDescriptor("gp_registers")) , cpuGpPeripheralDescriptor(targetDescriptor.getPeripheralDescriptor("cpu")) , cpuGpRegisterGroupDescriptor(this->cpuGpPeripheralDescriptor.getRegisterGroupDescriptor("gpr")) diff --git a/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.cpp b/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.cpp index 7a3561fe..11a03457 100644 --- a/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.cpp +++ b/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.cpp @@ -32,6 +32,7 @@ #include "src/Exceptions/InternalFatalErrorException.hpp" #include "src/TargetController/Exceptions/TargetFailure.hpp" #include "src/TargetController/Exceptions/TargetOperationFailure.hpp" +#include "src/Targets/RiscV/Exceptions/IllegalMemoryAccess.hpp" #include "src/Logger/Logger.hpp" @@ -963,7 +964,17 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec output.reserve(bytes); for (auto address = startAddress; address <= (startAddress + bytes - 1); address += 4) { - this->executeAbstractCommand(command); + const auto commandError = this->tryExecuteAbstractCommand(command); + if (commandError != AbstractCommandError::NONE) { + if (commandError == AbstractCommandError::EXCEPTION) { + throw Exceptions::IllegalMemoryAccess{}; + } + + throw Exceptions::TargetOperationFailure{ + "Failed to read memory via abstract command - error: 0x" + + Services::StringService::toHex(static_cast(commandError)) + }; + } const auto data = this->dtmInterface.readDebugModuleRegister(RegisterAddress::ABSTRACT_DATA_0); output.emplace_back(static_cast(data)); @@ -1005,7 +1016,17 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec ) ); - this->executeAbstractCommand(command); + const auto commandError = this->tryExecuteAbstractCommand(command); + if (commandError != AbstractCommandError::NONE) { + if (commandError == AbstractCommandError::EXCEPTION) { + throw Exceptions::IllegalMemoryAccess{}; + } + + throw Exceptions::TargetOperationFailure{ + "Failed to write memory via abstract command - error: 0x" + + Services::StringService::toHex(static_cast(commandError)) + }; + } } } @@ -1041,7 +1062,22 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec try { this->writeProgramBuffer(programOpcodes); - this->writeCpuRegister(CpuRegisterNumber::GPR_X8, startAddress, {.postExecute = true}); + + auto commandError = this->tryWriteCpuRegister( + CpuRegisterNumber::GPR_X8, + startAddress, + {.postExecute = true} + ); + if (commandError != AbstractCommandError::NONE) { + if (commandError == AbstractCommandError::EXCEPTION) { + throw Exceptions::IllegalMemoryAccess{}; + } + + throw Exceptions::TargetOperationFailure{ + "Program buffer execution failed - abstract command error: 0x" + + Services::StringService::toHex(commandError) + }; + } auto output = Targets::TargetMemoryBuffer{}; output.reserve(bytes); @@ -1101,13 +1137,15 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec output.emplace_back(static_cast(word >> 24)); } - const auto abstractStatusRegister = this->readDebugModuleAbstractControlStatusRegister(); - if (abstractStatusRegister.commandError != AbstractCommandError::NONE) { - this->clearAbstractCommandError(); + commandError = this->readAndClearAbstractCommandError(); + if (commandError != AbstractCommandError::NONE) { + if (commandError == AbstractCommandError::EXCEPTION) { + throw Exceptions::IllegalMemoryAccess{}; + } throw Exceptions::TargetOperationFailure{ "Program buffer execution failed - abstract command error: 0x" - + Services::StringService::toHex(abstractStatusRegister.commandError) + + Services::StringService::toHex(commandError) }; } @@ -1122,11 +1160,10 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec return output; - } catch (const Exceptions::Exception& exception) { + } catch (const Exceptions::Exception&) { preservedX8Register.restoreOnce(); preservedX9Register.restoreOnce(); - - throw exception; + throw; } } @@ -1201,24 +1238,25 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec AbstractCommandAutoExecuteRegister{}.value() ); - const auto abstractStatusRegister = this->readDebugModuleAbstractControlStatusRegister(); - if (abstractStatusRegister.commandError != AbstractCommandError::NONE) { - this->clearAbstractCommandError(); + const auto commandError = this->readAndClearAbstractCommandError(); + if (commandError != AbstractCommandError::NONE) { + if (commandError == AbstractCommandError::EXCEPTION) { + throw Exceptions::IllegalMemoryAccess{}; + } throw Exceptions::TargetOperationFailure{ "Program buffer execution failed - abstract command error: 0x" - + Services::StringService::toHex(abstractStatusRegister.commandError) + + Services::StringService::toHex(commandError) }; } preservedX8Register.restore(); preservedX9Register.restore(); - } catch (const Exceptions::Exception& exception) { + } catch (const Exceptions::Exception&) { preservedX8Register.restoreOnce(); preservedX9Register.restoreOnce(); - - throw exception; + throw; } } diff --git a/src/DebugToolDrivers/WCH/WchLinkDebugInterface.cpp b/src/DebugToolDrivers/WCH/WchLinkDebugInterface.cpp index ca9658df..d6eb2ea2 100644 --- a/src/DebugToolDrivers/WCH/WchLinkDebugInterface.cpp +++ b/src/DebugToolDrivers/WCH/WchLinkDebugInterface.cpp @@ -58,9 +58,7 @@ namespace DebugToolDrivers::Wch } ) , programSegmentDescriptor( - this->targetDescriptionFile.getSystemAddressSpaceDescriptor().getMemorySegmentDescriptor( - "internal_program_memory" - ) + this->targetDescriptionFile.getSystemAddressSpaceDescriptor().getMemorySegmentDescriptor("main_program") ) , flashProgramOpcodes( WchLinkDebugInterface::getFlashProgramOpcodes( @@ -105,6 +103,9 @@ namespace DebugToolDrivers::Wch * In addition to sending the post-attach command, we have to send another attach command, because the target * variant ID returned in the response of the first attach command may be invalid. Sending another attach * command will ensure that we have a valid target variant ID. + * + * TODO: Add a property to the target's TDF, to determine whether the post-attach is required, instead of + * hardcoding target IDs here. This can be done after v2.0.0. */ if (this->cachedTargetId == 0x09) { this->wchLinkInterface.sendCommandAndWaitForResponse(Commands::Control::PostAttach{}); @@ -236,9 +237,28 @@ namespace DebugToolDrivers::Wch * smaller than 64 bytes, such as when we're inserting software breakpoints. */ const auto bufferSize = static_cast(buffer.size()); + const auto alignmentSize = this->programmingBlockSize; + const auto alignedStartAddress = (startAddress / alignmentSize) * alignmentSize; + const auto alignedBufferSize = static_cast(std::ceil( + static_cast(bufferSize) / static_cast(alignmentSize) + ) * alignmentSize); + const auto alignmentRequired = alignedStartAddress != startAddress || alignedBufferSize != bufferSize; - if (bufferSize <= WchLinkInterface::MAX_PARTIAL_BLOCK_WRITE_SIZE) { + if ( + bufferSize <= WchLinkInterface::MAX_PARTIAL_BLOCK_WRITE_SIZE + || ( + alignmentRequired + && !memorySegmentDescriptor.addressRange.contains( + TargetMemoryAddressRange{ + alignedStartAddress, + alignedStartAddress + alignedBufferSize - 1 + } + ) + ) + ) { using namespace ::DebugToolDrivers::Protocols::RiscVDebugSpec; + Logger::debug("Using partial block write command"); + /* * WCH-Link tools seem to make use of the target's program buffer to service the partial block write * command. @@ -261,28 +281,7 @@ namespace DebugToolDrivers::Wch return; } - const auto alignmentSize = this->programmingBlockSize; - const auto alignedStartAddress = (startAddress / alignmentSize) * alignmentSize; - const auto alignedBufferSize = static_cast(std::ceil( - static_cast(bufferSize) / static_cast(alignmentSize) - ) * alignmentSize); - - if (alignedStartAddress != startAddress || alignedBufferSize != bufferSize) { - if ( - !memorySegmentDescriptor.addressRange.contains( - TargetMemoryAddressRange{ - alignedStartAddress, - alignedStartAddress + alignedBufferSize - 1 - } - ) - ) { - /* - * TODO: The aligned address range exceeds the bounds of the memory segment. I'm not sure what to - * do here. We could just ignore it...I don't think it will cause much of an issue, for now. - * Review (after v2.0.0, maybe?). - */ - } - + if (alignmentRequired) { auto alignedBuffer = (alignedStartAddress < startAddress) ? this->readMemory( addressSpaceDescriptor, @@ -322,6 +321,9 @@ namespace DebugToolDrivers::Wch ); } + Logger::debug( + "Using full block write command (block size: " + std::to_string(this->programmingBlockSize) + ")" + ); this->wchLinkInterface.writeFlashFullBlocks( startAddress, buffer, diff --git a/src/Targets/CMakeLists.txt b/src/Targets/CMakeLists.txt index b0239e85..a432955b 100755 --- a/src/Targets/CMakeLists.txt +++ b/src/Targets/CMakeLists.txt @@ -26,5 +26,6 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/RiscV/TargetDescriptionFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RiscV/IsaDescriptor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RiscV/Wch/WchRiscV.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/RiscV/Wch/WchRiscVTargetConfig.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RiscV/Wch/TargetDescriptionFile.cpp ) diff --git a/src/Targets/RiscV/Exceptions/IllegalMemoryAccess.hpp b/src/Targets/RiscV/Exceptions/IllegalMemoryAccess.hpp new file mode 100644 index 00000000..298a41a0 --- /dev/null +++ b/src/Targets/RiscV/Exceptions/IllegalMemoryAccess.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "src/TargetController/Exceptions/TargetOperationFailure.hpp" + +namespace Exceptions +{ + class IllegalMemoryAccess: public TargetOperationFailure + { + public: + explicit IllegalMemoryAccess() + : TargetOperationFailure("Illegal memory access") + {} + + explicit IllegalMemoryAccess(const std::string& message) + : TargetOperationFailure(message) + {} + }; +} diff --git a/src/Targets/RiscV/RiscV.cpp b/src/Targets/RiscV/RiscV.cpp index 2b356026..928433b6 100644 --- a/src/Targets/RiscV/RiscV.cpp +++ b/src/Targets/RiscV/RiscV.cpp @@ -9,6 +9,8 @@ #include "src/Services/StringService.hpp" #include "src/Logger/Logger.hpp" +#include "Exceptions/IllegalMemoryAccess.hpp" + #include "src/Exceptions/Exception.hpp" #include "src/Exceptions/InvalidConfig.hpp" #include "src/TargetController/Exceptions/TargetOperationFailure.hpp" @@ -307,6 +309,20 @@ namespace Targets::RiscV return this->programmingMode; } + bool RiscV::probeMemory( + const TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const TargetMemorySegmentDescriptor& memorySegmentDescriptor, + Targets::TargetMemoryAddress address + ) { + try { + this->riscVDebugInterface->readMemory(addressSpaceDescriptor, memorySegmentDescriptor, address, 4, {}); + return true; + + } catch (const Exceptions::IllegalMemoryAccess&) { + return false; + } + } + void RiscV::applyDebugInterfaceAccessRestrictions(TargetAddressSpaceDescriptor& addressSpaceDescriptor) { for (auto& [segmentKey, segmentDescriptor] : addressSpaceDescriptor.segmentDescriptorsByKey) { this->riscVDebugInterface->applyAccessRestrictions(segmentDescriptor); diff --git a/src/Targets/RiscV/RiscV.hpp b/src/Targets/RiscV/RiscV.hpp index c2487279..a85ff68d 100644 --- a/src/Targets/RiscV/RiscV.hpp +++ b/src/Targets/RiscV/RiscV.hpp @@ -122,6 +122,12 @@ namespace Targets::RiscV bool programmingMode = false; + bool probeMemory( + const TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const TargetMemorySegmentDescriptor& memorySegmentDescriptor, + TargetMemoryAddress address + ); + void applyDebugInterfaceAccessRestrictions(TargetAddressSpaceDescriptor& addressSpaceDescriptor); void applyDebugInterfaceAccessRestrictions(TargetRegisterGroupDescriptor& registerGroupDescriptor); diff --git a/src/Targets/RiscV/Wch/WchRiscV.cpp b/src/Targets/RiscV/Wch/WchRiscV.cpp index b3940b19..a13a4ca4 100644 --- a/src/Targets/RiscV/Wch/WchRiscV.cpp +++ b/src/Targets/RiscV/Wch/WchRiscV.cpp @@ -3,8 +3,10 @@ #include #include -#include "src/Logger/Logger.hpp" #include "src/Exceptions/InvalidConfig.hpp" +#include "src/Exceptions/Exception.hpp" + +#include "src/Logger/Logger.hpp" namespace Targets::RiscV::Wch { @@ -13,10 +15,24 @@ namespace Targets::RiscV::Wch TargetDescriptionFile&& targetDescriptionFile ) : RiscV(targetConfig, targetDescriptionFile) + , targetConfig(WchRiscVTargetConfig{RiscV::targetConfig}) , targetDescriptionFile(std::move(targetDescriptionFile)) - , programMemorySegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_program_memory")) - , mappedProgramMemorySegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("mapped_progmem")) - {} + , mappedSegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("mapped_program_memory")) + , mainProgramSegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("main_program")) + , bootProgramSegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("boot_program")) + , peripheralSegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("peripherals")) + , selectedProgramSegmentDescriptor( + this->targetConfig.programSegmentKey.has_value() + && *(this->targetConfig.programSegmentKey) == this->bootProgramSegmentDescriptor.key + ? this->bootProgramSegmentDescriptor + : this->mainProgramSegmentDescriptor + ) + { + Logger::info( + "Selected program memory segment: \"" + this->selectedProgramSegmentDescriptor.name + "\" (\"" + + this->selectedProgramSegmentDescriptor.key + "\")" + ); + } void WchRiscV::activate() { RiscV::activate(); @@ -89,7 +105,7 @@ namespace Targets::RiscV::Wch } auto& sysAddressSpaceDescriptor = descriptor.getAddressSpaceDescriptor("system"); - sysAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_program_memory").inspectionEnabled = true; + sysAddressSpaceDescriptor.getMemorySegmentDescriptor("main_program").inspectionEnabled = true; sysAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_ram").inspectionEnabled = true; /* @@ -103,7 +119,7 @@ namespace Targets::RiscV::Wch * See the overridden WchRiscV::writeMemory() member function below, for more. */ sysAddressSpaceDescriptor.getMemorySegmentDescriptor( - this->mappedProgramMemorySegmentDescriptor.key + this->mappedSegmentDescriptor.key ).programmingModeAccess.writeable = true; return descriptor; @@ -112,12 +128,12 @@ namespace Targets::RiscV::Wch void WchRiscV::setProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) { if ( breakpoint.type == TargetProgramBreakpoint::Type::SOFTWARE - && breakpoint.memorySegmentDescriptor == this->mappedProgramMemorySegmentDescriptor + && breakpoint.memorySegmentDescriptor == this->mappedSegmentDescriptor ) { this->riscVDebugInterface->setProgramBreakpoint(TargetProgramBreakpoint{ .addressSpaceDescriptor = this->sysAddressSpaceDescriptor, - .memorySegmentDescriptor = this->getDestinationProgramMemorySegmentDescriptor(), - .address = this->transformAliasedProgramMemoryAddress(breakpoint.address), + .memorySegmentDescriptor = this->selectedProgramSegmentDescriptor, + .address = this->transformMappedAddress(breakpoint.address, this->selectedProgramSegmentDescriptor), .size = breakpoint.size, .type = breakpoint.type }); @@ -131,12 +147,12 @@ namespace Targets::RiscV::Wch void WchRiscV::removeProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) { if ( breakpoint.type == TargetProgramBreakpoint::Type::SOFTWARE - && breakpoint.memorySegmentDescriptor == this->mappedProgramMemorySegmentDescriptor + && breakpoint.memorySegmentDescriptor == this->mappedSegmentDescriptor ) { this->riscVDebugInterface->removeProgramBreakpoint(TargetProgramBreakpoint{ .addressSpaceDescriptor = this->sysAddressSpaceDescriptor, - .memorySegmentDescriptor = this->getDestinationProgramMemorySegmentDescriptor(), - .address = this->transformAliasedProgramMemoryAddress(breakpoint.address), + .memorySegmentDescriptor = this->selectedProgramSegmentDescriptor, + .address = this->transformMappedAddress(breakpoint.address, this->selectedProgramSegmentDescriptor), .size = breakpoint.size, .type = breakpoint.type }); @@ -147,52 +163,183 @@ namespace Targets::RiscV::Wch this->riscVDebugInterface->removeProgramBreakpoint(breakpoint); } + TargetMemoryBuffer WchRiscV::readMemory( + const TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const TargetMemorySegmentDescriptor& memorySegmentDescriptor, + TargetMemoryAddress startAddress, + TargetMemorySize bytes, + const std::set& excludedAddressRanges + ) { + using Services::StringService; + + if (memorySegmentDescriptor == this->mappedSegmentDescriptor) { + const auto& aliasedSegment = this->selectedProgramSegmentDescriptor; + const auto transformedAddress = this->transformMappedAddress(startAddress, aliasedSegment); + + const auto addressRange = TargetMemoryAddressRange{ + transformedAddress, + static_cast(transformedAddress + bytes - 1) + }; + + if (!aliasedSegment.addressRange.contains(addressRange)) { + throw Exceptions::Exception{ + "Read access range (0x" + StringService::toHex(addressRange.startAddress) + " -> 0x" + + StringService::toHex(addressRange.endAddress) + ", " + std::to_string(addressRange.size()) + + " bytes) exceeds the boundary of the selected program segment \"" + aliasedSegment.key + + "\" (0x" + StringService::toHex(aliasedSegment.addressRange.startAddress) + " -> 0x" + + StringService::toHex(aliasedSegment.addressRange.endAddress) + ", " + + std::to_string(aliasedSegment.addressRange.size()) + " bytes)" + }; + } + + return RiscV::readMemory( + addressSpaceDescriptor, + aliasedSegment, + transformedAddress, + bytes, + excludedAddressRanges + ); + } + + return RiscV::readMemory( + addressSpaceDescriptor, + memorySegmentDescriptor, + startAddress, + bytes, + excludedAddressRanges + ); + } + void WchRiscV::writeMemory( const TargetAddressSpaceDescriptor& addressSpaceDescriptor, const TargetMemorySegmentDescriptor& memorySegmentDescriptor, TargetMemoryAddress startAddress, TargetMemoryBufferSpan buffer ) { - /* - * WCH targets have an alias segment that maps to either the program memory segment or the boot program - * memory segment. - * - * Reading directly from this memory segment is fine, but we cannot write to it - the operation just fails - * silently. We handle this by forwarding any write operations on that segment to the appropriate (aliased) - * segment. - * - * @TODO: Currently, this just assumes that the alias segment always maps to the program memory segment, but I - * believe it may map to the boot program memory segment in some cases. This needs to be revisited - * before v2.0.0. - */ - if (memorySegmentDescriptor == this->mappedProgramMemorySegmentDescriptor) { - const auto transformedAddress = this->transformAliasedProgramMemoryAddress(startAddress); - assert(this->programMemorySegmentDescriptor.addressRange.contains(transformedAddress)); + using Services::StringService; - return RiscV::writeMemory( - addressSpaceDescriptor, - this->programMemorySegmentDescriptor, + if (memorySegmentDescriptor == this->mappedSegmentDescriptor) { + const auto& aliasedSegment = this->selectedProgramSegmentDescriptor; + const auto transformedAddress = this->transformMappedAddress(startAddress, aliasedSegment); + + const auto addressRange = TargetMemoryAddressRange{ transformedAddress, - buffer - ); + static_cast(transformedAddress + buffer.size() - 1) + }; + + if (!aliasedSegment.addressRange.contains(addressRange)) { + throw Exceptions::Exception{ + "Write access range (0x" + StringService::toHex(addressRange.startAddress) + " -> 0x" + + StringService::toHex(addressRange.endAddress) + ", " + std::to_string(addressRange.size()) + + " bytes) exceeds the boundary of the selected program segment \"" + aliasedSegment.key + + "\" (0x" + StringService::toHex(aliasedSegment.addressRange.startAddress) + " -> 0x" + + StringService::toHex(aliasedSegment.addressRange.endAddress) + ", " + + std::to_string(aliasedSegment.addressRange.size()) + " bytes)" + }; + } + + return RiscV::writeMemory(addressSpaceDescriptor, aliasedSegment, transformedAddress, buffer); } return RiscV::writeMemory(addressSpaceDescriptor, memorySegmentDescriptor, startAddress, buffer); } - const TargetMemorySegmentDescriptor& WchRiscV::getDestinationProgramMemorySegmentDescriptor() { - return this->programMemorySegmentDescriptor; + void WchRiscV::eraseMemory( + const TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const TargetMemorySegmentDescriptor& memorySegmentDescriptor + ) { + if (memorySegmentDescriptor == this->mappedSegmentDescriptor) { + return RiscV::eraseMemory(addressSpaceDescriptor, this->selectedProgramSegmentDescriptor); + } + + RiscV::eraseMemory(addressSpaceDescriptor, memorySegmentDescriptor); } - TargetMemoryAddress WchRiscV::transformAliasedProgramMemoryAddress(TargetMemoryAddress address) const { + TargetMemoryAddress WchRiscV::getProgramCounter() { + const auto programCounter = RiscV::getProgramCounter(); + + if (this->mappedSegmentDescriptor.addressRange.contains(programCounter)) { + const auto& actualAliasedSegment = this->resolveAliasedMemorySegment(); + if (actualAliasedSegment != this->selectedProgramSegmentDescriptor) { + /* + * The target's mapped segment no longer aliases the selected program segment. + * + * Imagine starting a debug session with GDB, then replacing the entire program being debugged with a + * totally different program, whilst GDB is still running and the same debug session is still active. + * Understandably, GDB would become very confused by this, as it has no idea what just happened, or why + * the program it was observing just moments ago has suddenly disappeared and been replaced by another. + * + * This is essentially what has just happened. The mapped segment initially aliased one segment in + * program memory, but now, all of a sudden, it appears to be aliasing a different segment. This can + * happen when the target switches to a different mode of operation. When the target is in "user mode", + * the mapped segment aliases the main program segment. But when the target is in "boot mode", the + * mapped segment aliases the boot segment. The program running on the target can invoke a mode switch + * by writing to a register and performing a software reset. + * + * So, we have a program counter that's addressing a totally different program, but to most external + * entities, it will appear as if it's addressing the same program. + * + * In order to avoid causing havoc and potentially misleading the user, we transform the PC to its + * aliased address. That way, it will be clear to all external entities, that the target is currently + * executing code in a different memory segment to the one that was selected for debugging. + */ + Logger::warning( + "The mapped program memory segment is currently aliasing a foreign segment (\"" + + actualAliasedSegment.key + "\") - the program counter will be transformed to its aliased" + " address" + ); + return this->transformMappedAddress(programCounter, actualAliasedSegment); + } + } + + return programCounter; + } + + const TargetMemorySegmentDescriptor& WchRiscV::resolveAliasedMemorySegment() { + /* + * To determine the aliased segment, we probe the boundary of the boot segment via the mapped segment. + * + * Assumptions that must hold, for this to work: + * - The boot segment must be smaller than the main program memory segment + * - Breaching the boundary of the boot segment must always result in an exception (out-of-bounds error) + * + * If the mapped segment is aliasing the boot segment, the memory access will fail, due to an out-of-bounds + * error. If the access succeeds, we can be fairly certain the mapped segment is aliasing the main program + * memory segment. + * + * I did consider using the FLASH_STATR peripheral register to determine the aliased segment, but not all WCH + * targets have the required bit fields for that to work. And even the ones that do, do not behave in the way + * described by the documentation. + */ + const auto probeAddress = this->bootProgramSegmentDescriptor.addressRange.endAddress + - this->bootProgramSegmentDescriptor.addressRange.startAddress + + this->mappedSegmentDescriptor.addressRange.startAddress + 1; + + assert(this->sysAddressSpaceDescriptor.addressRange.contains(probeAddress)); + assert(this->mainProgramSegmentDescriptor.size() > this->bootProgramSegmentDescriptor.size()); + + const auto& segment = this->probeMemory( + this->sysAddressSpaceDescriptor, + this->mappedSegmentDescriptor, + probeAddress + ) ? this->mainProgramSegmentDescriptor : this->bootProgramSegmentDescriptor; + + Logger::debug("Aliased program memory segment: \"" + segment.key + "\""); + return segment; + } + + TargetMemoryAddress WchRiscV::transformMappedAddress( + TargetMemoryAddress address, + const TargetMemorySegmentDescriptor& segmentDescriptor + ) { using Services::StringService; - const auto transformedAddress = address - this->mappedProgramMemorySegmentDescriptor.addressRange.startAddress - + this->programMemorySegmentDescriptor.addressRange.startAddress; + const auto transformedAddress = address - this->mappedSegmentDescriptor.addressRange.startAddress + + segmentDescriptor.addressRange.startAddress; Logger::debug( "Transformed mapped program memory address 0x" + StringService::toHex(address) + " to 0x" - + StringService::toHex(transformedAddress) + + StringService::toHex(transformedAddress) + " (segment: \"" + segmentDescriptor.key + "\")" ); return transformedAddress; diff --git a/src/Targets/RiscV/Wch/WchRiscV.hpp b/src/Targets/RiscV/Wch/WchRiscV.hpp index 13bbcfe3..ede117ce 100644 --- a/src/Targets/RiscV/Wch/WchRiscV.hpp +++ b/src/Targets/RiscV/Wch/WchRiscV.hpp @@ -6,6 +6,7 @@ #include "src/Targets/RiscV/RiscV.hpp" +#include "WchRiscVTargetConfig.hpp" #include "TargetDescriptionFile.hpp" namespace Targets::RiscV::Wch @@ -22,22 +23,60 @@ namespace Targets::RiscV::Wch void setProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) override; void removeProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) override; + TargetMemoryBuffer readMemory( + const TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const TargetMemorySegmentDescriptor& memorySegmentDescriptor, + TargetMemoryAddress startAddress, + TargetMemorySize bytes, + const std::set& excludedAddressRanges + ) override; void writeMemory( const TargetAddressSpaceDescriptor& addressSpaceDescriptor, const TargetMemorySegmentDescriptor& memorySegmentDescriptor, TargetMemoryAddress startAddress, TargetMemoryBufferSpan buffer ) override; + void eraseMemory( + const TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const TargetMemorySegmentDescriptor& memorySegmentDescriptor + ) override; + + TargetMemoryAddress getProgramCounter() override; protected: + WchRiscVTargetConfig targetConfig; TargetDescriptionFile targetDescriptionFile; std::optional> variant = std::nullopt; - const TargetMemorySegmentDescriptor& programMemorySegmentDescriptor; - const TargetMemorySegmentDescriptor& bootProgramMemorySegmentDescriptor; - const TargetMemorySegmentDescriptor& mappedProgramMemorySegmentDescriptor; + const TargetMemorySegmentDescriptor& mappedSegmentDescriptor; + const TargetMemorySegmentDescriptor& mainProgramSegmentDescriptor; + const TargetMemorySegmentDescriptor& bootProgramSegmentDescriptor; + const TargetMemorySegmentDescriptor& peripheralSegmentDescriptor; - const TargetMemorySegmentDescriptor& getDestinationProgramMemorySegmentDescriptor(); - TargetMemoryAddress transformAliasedProgramMemoryAddress(TargetMemoryAddress address) const; + /* + * The selected program segment is the program memory segment the user has selected to be the subject of all + * memory accesses that are forwarded from the mapped program memory segment. + * + * In other words, whenever we service a memory access via the mapped program memory segment, we perform the + * operation on the selected program segment, instead. This prevents memory access requests from being + * misinterpreted in cases where the WCH target has switched to/from boot mode. + * + * The user can use the "program_segment_key" config param to specify the selected segment. If not provided, + * the selected segment defaults to the main program memory segment. This means, by default, we assume the user + * intends to debug the main user program, residing on the main program segment, as opposed to the boot program + * residing on the boot segment. If the user intends to debug their boot program, they must select the boot + * segment as the program segment, via the "program_segment_key" config param. + * + * I will explain this more clearly in the user documentation, on the Bloom website. + * + * For more, see the implementation of the memory access member functions. + */ + const TargetMemorySegmentDescriptor& selectedProgramSegmentDescriptor; + + const TargetMemorySegmentDescriptor& resolveAliasedMemorySegment(); + TargetMemoryAddress transformMappedAddress( + TargetMemoryAddress address, + const TargetMemorySegmentDescriptor& segmentDescriptor + ); }; } diff --git a/src/Targets/RiscV/Wch/WchRiscVTargetConfig.cpp b/src/Targets/RiscV/Wch/WchRiscVTargetConfig.cpp new file mode 100644 index 00000000..a6157280 --- /dev/null +++ b/src/Targets/RiscV/Wch/WchRiscVTargetConfig.cpp @@ -0,0 +1,14 @@ +#include "WchRiscVTargetConfig.hpp" + +namespace Targets::RiscV +{ + WchRiscVTargetConfig::WchRiscVTargetConfig(const RiscVTargetConfig& targetConfig) + : RiscVTargetConfig(targetConfig) + { + const auto& targetNode = targetConfig.targetNode; + + if (targetNode["program_segment_key"]) { + this->programSegmentKey = targetNode["program_segment_key"].as(""); + } + } +} diff --git a/src/Targets/RiscV/Wch/WchRiscVTargetConfig.hpp b/src/Targets/RiscV/Wch/WchRiscVTargetConfig.hpp new file mode 100644 index 00000000..6f850c25 --- /dev/null +++ b/src/Targets/RiscV/Wch/WchRiscVTargetConfig.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "src/Targets/RiscV/RiscVTargetConfig.hpp" + +namespace Targets::RiscV +{ + struct WchRiscVTargetConfig: public RiscVTargetConfig + { + public: + explicit WchRiscVTargetConfig(const RiscVTargetConfig& targetConfig); + + std::optional programSegmentKey; + }; +}