| // Copyright 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cheriot/debug_command_shell.h" |
| |
| #include <cstdint> |
| #include <cstring> |
| #include <fstream> |
| #include <functional> |
| #include <istream> |
| #include <ostream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/functional/any_invocable.h" |
| #include "absl/functional/bind_front.h" |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/numbers.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "cheriot/cheriot_debug_interface.h" |
| #include "cheriot/cheriot_register.h" |
| #include "cheriot/cheriot_state.h" |
| #include "cheriot/cheriot_top.h" |
| #include "cheriot/riscv_cheriot_enums.h" |
| #include "cheriot/riscv_cheriot_register_aliases.h" |
| #include "mpact/sim/generic/core_debug_interface.h" |
| #include "mpact/sim/generic/data_buffer.h" |
| #include "mpact/sim/generic/instruction.h" |
| #include "mpact/sim/generic/type_helpers.h" |
| #include "re2/re2.h" |
| #include "riscv//stoull_wrapper.h" |
| |
| namespace mpact { |
| namespace sim { |
| namespace cheriot { |
| |
| using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits; |
| |
| using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason; |
| using ::mpact::sim::generic::operator*; // NOLINT: used below (clang error). |
| |
| DebugCommandShell::InterruptListener::InterruptListener(CoreAccess *core_access) |
| : core_access_(core_access), |
| state_(static_cast<CheriotState *>(core_access_->state)), |
| dbg_if_(static_cast<CheriotTop *>(core_access->debug_interface)), |
| taken_listener_( |
| absl::bind_front(&InterruptListener::SetTakenValue, this)), |
| return_listener_( |
| absl::bind_front(&InterruptListener::SetReturnValue, this)) { |
| state_->counter_interrupts_taken()->AddListener(&taken_listener_); |
| state_->counter_interrupt_returns()->AddListener(&return_listener_); |
| } |
| |
| void DebugCommandShell::InterruptListener::SetReturnValue(int64_t value) { |
| auto &interrupt_info_list = state_->interrupt_info_list(); |
| if (interrupt_info_list.empty()) { |
| LOG(ERROR) << "Interrupt stack is empty"; |
| return; |
| } |
| auto info = interrupt_info_list.back(); |
| // If breakpoints are enabled, then request a halt of the appropriate type. |
| if (info.is_interrupt && interrupts_enabled_) |
| (void)dbg_if_->Halt(kInterruptReturn); |
| if (!info.is_interrupt && exceptions_enabled_) |
| (void)dbg_if_->Halt(kExceptionReturn); |
| } |
| |
| void DebugCommandShell::InterruptListener::SetTakenValue(int64_t value) { |
| InterruptInfo info = state_->interrupt_info_list().back(); |
| // If breakpoints are enabled, the request a halt of the appropriate type. |
| if (info.is_interrupt && interrupts_enabled_) |
| (void)dbg_if_->Halt(kInterruptTaken); |
| if (!info.is_interrupt && exceptions_enabled_) |
| (void)dbg_if_->Halt(kExceptionTaken); |
| } |
| |
| // The constructor initializes all the regular expressions and the help string. |
| DebugCommandShell::DebugCommandShell() |
| : quit_re_{R"(\s*quit\s*)"}, |
| core_re_{R"(\s*core\s+(\d+)\s*)"}, |
| run_re_{R"(\s*(?:run|c)\s*)"}, |
| run_free_re_{R"(\s*run\s+free\s*)"}, |
| wait_re_{R"(\s*wait\s*)"}, |
| step_1_re_{R"(\s*step\s*)"}, |
| step_n_re_{R"(\s*step\s+(\d+)\s*)"}, |
| halt_re_{R"(\s*halt\s*)"}, |
| next_re_{R"(\s*next\s*)"}, |
| read_reg_re_{R"(\s*reg\s+get\s+(\$?(\w|\.)+)(\s+[foduxX]\d+)?\s*)"}, |
| read_reg2_re_{R"(\s*reg\s+(\$?(\w|\.)+)(\s+[foduxX]\d+)?\s*)"}, |
| write_reg_re_{R"(\s*reg\s+set\s+(\$?\w+)\s+(\w+)\s*)"}, |
| rd_vreg_re_{R"(\s*vreg(?:\s+get)?\s+(\$?\w+)(?:(?:\s*\:(\d+))?)" |
| R"((?:\s+(?:([oduxX])(8|16|32|64))?)?)?\s*)"}, |
| read_mem_re_{R"(\s*mem\s+get\s+(\w+)(?:\s+([foduxX]\d+|i)?)?\s*)"}, |
| read_mem2_re_{R"(\s*mem\s+(\w+)(?:\s+([foduxX]\d+|i)?)?\s*)"}, |
| write_mem_re_{R"(\s*mem\s+set\s+(\w+)\s+([oduxX]\d+)?\s+(\w+)\s*)"}, |
| set_break_re_{R"(\s*break\s+set\s+(\$?\w+)\s*)"}, |
| set_break2_re_{R"(\s*break\s+(\$?\w+)\s*)"}, |
| set_break_n_re_{R"(\s*break\s+(set\s+)?\#(\d+)\s*)"}, |
| list_break_re_{R"(\s*break\s*)"}, |
| clear_break_n_re_{R"(\s*break\s+clear\s+\#(\d+)\s*)"}, |
| clear_break_re_{R"(\s*break\s+clear\s+(\$?\w+)\s*)"}, |
| clear_all_break_re_{R"(\s*break\s+clear-all\s*)"}, |
| set_watch_re_{R"(\s*watch\s+set\s+(\w+)\s+(\w+)(\s+r|\s+w|\s+rw)?\s*)"}, |
| set_watch2_re_{R"(\s*watch\s+(\w+)\s+(\w+)(\s+r|\s+w|\s+rw)?\s*)"}, |
| set_watch_n_re_{R"(\s*watch\s+(set\s+)?\#(\d+)\s*)"}, |
| list_watch_re_{R"(\s*watch\s*)"}, |
| clear_watch_re_{R"(\s*watch\s+clear\s+(\w+)(\s+r|\s+w|\s+rw)?\s*)"}, |
| clear_watch_n_re_{R"(\s*watch\s+clear\s+\#(\d+)\s*)"}, |
| clear_all_watch_re_{R"(\s*watch\s+clear-all\s*)"}, |
| list_action_re_{R"(\s*action\s*)"}, |
| enable_action_n_re_{R"(\s*action\s+enable\s+\*(\d+)\s*)"}, |
| disable_action_n_re_{R"(\s*action\s+disable\s+\*(\d+)\s*)"}, |
| clear_action_n_re_{R"(\s*action\s+clear\s+\*(\d+)\s*)"}, |
| clear_all_action_re_{R"(\s*action\s+clear-all\s*)"}, |
| branch_trace_re_{R"(\s*branch-trace\s*)"}, |
| exec_re_{R"(\s*exec\s+(.+)\s*)"}, |
| empty_re_{R"(\s*(?:\#.*)?)"}, |
| help_re_{R"(\s*help\s*)"} { |
| help_message_ = |
| R"raw( quit - exit command shell. |
| core [N] - direct subsequent commands to core N |
| (default: 0). |
| run - run program from current pcc until a |
| breakpoint or exit. Wait until halted. |
| run free - run program in background from current pcc |
| until breakpoint or exit. |
| wait - wait for any free run to complete. |
| step [N] - step [N] instructions (default: 1). |
| next - step 1 instruction (stepping over calls). |
| halt - halt a running program. |
| reg get NAME [FORMAT] - get the value or register NAME (see below |
| for special names and display formats). |
| reg NAME [FORMAT] - get the value of register NAME (see below). |
| reg set NAME VALUE - set register NAME to VALUE. |
| reg set NAME SYMBOL - set register NAME to value of SYMBOL. |
| mem get VALUE [FORMAT] - get memory from location VALUE according to |
| FORMAT (see below for display formats). |
| Default format is x32. |
| mem get SYMBOL [FORMAT] - get memory from location SYMBOL and format |
| according to FORMAT (see below). |
| mem SYMBOL [FORMAT] - get memory from location SYMBOL and format |
| according to FORMAT (see below). |
| mem set VALUE [FORMAT] VALUE - set memory at location VALUE(1) to VALUE(2) |
| according to FORMAT (see below). Default |
| format is x32. |
| mem set SYMBOL [FORMAT] VALUE - set memory at location SYMBOL to VALUE |
| according to FORMAT (see below). Default |
| format is x32. |
| break [set] VALUE - set breakpoint at address VALUE. |
| break [set] SYMBOL - set breakpoint at value of SYMBOL (see below |
| for special symbol names). |
| break set #<N> - reactivate breakpoint index N. |
| break #<N> - reactivate breakpoint index N. |
| break clear VALUE - clear breakpoint at address VALUE. |
| break clear SYMBOL - clear breakpoint at value of SYMBOL (see |
| below for pseudo-symbols). |
| break clear #<N> - clear breakpoint index N. |
| break clear-all - remove all breakpoints. |
| break - list breakpoints. |
| watch [set] VALUE len [r|w|rw] - set watchpoint at value (read, write, or |
| read+write) - default is write. |
| watch [set] SYMBOL len [r|w|rw] - set watchpoint at value (read, write, or |
| read+write) - default is write. |
| watch set #<N> - reactivate watchpoint index N. |
| watch clear VALUE [r|w|rw] - clear watchpoint at value (read, write, or |
| readwrite) - default is write. |
| watch clear SYMBOL [r|w|rw] - clear watchpoint at symbol (read, write or |
| readwrite) - default is write. |
| watch clear #<N> - clear watchpoint index N. |
| watch clear-all - remove all watchpoints. |
| watch - list watchpoints. |
| action enable #<N> - enable action point with index N. |
| action disable #<N> - disable action point with index N. |
| action clear #<N> - clear action point with index N. |
| action clear-all - clear all action points. |
| action - list action points. |
| branch-trace - list the control flow change (includes |
| interrupts) w/out repetitions due to loops. |
| exec NAME - load commands from file 'NAME' and execute |
| each line as a command. Lines starting with |
| a '#' are treated as comments. |
| status - display current status. |
| help - display this message. |
| |
| Special names: |
| $all - core set of registers (e.g., reg $all). |
| $exception - pseudo-symbol for exception handler entry/exit |
| (e.g., break set $exception). |
| $interrupt - pseudo-symbol for interrupt handler entry/exit |
| (e.g., break clear $interrupt). |
| |
| Display formats: |
| o{8|16|32|64} - octal (base 8) of bit-width data |
| d{8|16|32|64} - signed decimal (base 10) of bit-width data |
| u{8|16|32|64} - unsigned decimal (base 10) of bit-width data |
| x{8|16|32|64} - lower-case hexdecimal (base 16) of bit-width data |
| X{8|16|32|64} - upper-case hexdecimal (base 16) of bit-width data |
| i - decode as instruction (only use with memory get) |
| )raw"; |
| // Insert known capability registers |
| for (int i = 0; i < 16; i++) { |
| reg_vector_.push_back(kCRegisterAliases[i]); |
| capability_registers_.insert(absl::StrCat("c", i)); |
| capability_registers_.insert(kCRegisterAliases[i]); |
| } |
| capability_registers_.insert("pcc"); |
| capability_registers_.insert("mtcc"); |
| capability_registers_.insert("mtdc"); |
| capability_registers_.insert("mscratchc"); |
| capability_registers_.insert("mepcc"); |
| reg_vector_.push_back("pcc"); |
| reg_vector_.push_back("mepcc"); |
| reg_vector_.push_back("mtcc"); |
| reg_vector_.push_back("mtdc"); |
| reg_vector_.push_back("mscratchc"); |
| } |
| |
| DebugCommandShell::~DebugCommandShell() { |
| for (auto *listener : interrupt_listeners_) { |
| delete listener; |
| } |
| } |
| |
| void DebugCommandShell::AddCore(const CoreAccess &core_access) { |
| core_access_.push_back(core_access); |
| interrupt_listeners_.push_back(new InterruptListener(&core_access_.back())); |
| core_action_point_id_.push_back(0); |
| core_action_point_info_.emplace_back(); |
| } |
| |
| void DebugCommandShell::AddCores(const std::vector<CoreAccess> &core_access) { |
| for (const auto &core_access : core_access) { |
| AddCore(core_access); |
| } |
| } |
| |
| // NOLINTBEGIN(readability/fn_size) |
| void DebugCommandShell::Run(std::istream &is, std::ostream &os) { |
| // TODO(torerik): refactor this function. |
| // Assumes the max linesize is 512. |
| command_streams_.push_back(&is); |
| constexpr int kLineSize = 512; |
| char line[kLineSize]; |
| std::string previous_line; |
| current_core_ = 0; |
| absl::string_view line_view; |
| bool halt_reason = false; |
| int last_info_list_size = 0; |
| while (true) { |
| // Prompt and read in the next command. |
| auto pcc_result = |
| core_access_[current_core_].debug_interface->ReadRegister("pcc"); |
| std::string prompt; |
| if (halt_reason) { |
| halt_reason = false; |
| auto result = |
| core_access_[current_core_].debug_interface->GetLastHaltReason(); |
| if (result.ok()) { |
| switch (result.value()) { |
| case *HaltReason::kSoftwareBreakpoint: |
| absl::StrAppend(&prompt, "Stopped at software breakpoint\n"); |
| break; |
| case *HaltReason::kUserRequest: |
| absl::StrAppend(&prompt, "Stopped at user request\n"); |
| break; |
| case *HaltReason::kDataWatchPoint: |
| absl::StrAppend(&prompt, "Stopped at data watchpoint\n"); |
| break; |
| case InterruptListener::kInterruptTaken: |
| absl::StrAppend(&prompt, "Stopped at taken interrupt\n"); |
| break; |
| case InterruptListener::kInterruptReturn: |
| absl::StrAppend(&prompt, |
| "Stopped at return from interrupt handler\n"); |
| break; |
| case InterruptListener::kExceptionTaken: |
| absl::StrAppend(&prompt, "Stopped at exception\n"); |
| break; |
| case InterruptListener::kExceptionReturn: |
| absl::StrAppend(&prompt, "Stopped at return exception handler\n"); |
| break; |
| case *HaltReason::kProgramDone: |
| absl::StrAppend(&prompt, "Program done\n"); |
| break; |
| default: |
| if ((result.value() >= *HaltReason::kUserSpecifiedMin) && |
| (result.value() <= *HaltReason::kUserSpecifiedMax)) { |
| absl::StrAppend(&prompt, "Stopped for custom halt reason\n"); |
| } |
| break; |
| } |
| } |
| } |
| if (pcc_result.ok()) { |
| auto *loader = core_access_[current_core_].loader_getter(); |
| if (loader != nullptr) { |
| auto fcn_result = loader->GetFunctionName(pcc_result.value()); |
| if (fcn_result.ok()) { |
| absl::StrAppend(&prompt, "[", fcn_result.value(), "]:\n"); |
| } |
| auto symbol_result = loader->GetFcnSymbolName(pcc_result.value()); |
| if (symbol_result.ok()) { |
| absl::StrAppend(&prompt, symbol_result.value(), ":\n"); |
| } |
| } |
| absl::StrAppend(&prompt, |
| absl::Hex(pcc_result.value(), absl::PadSpec::kZeroPad8)); |
| auto disasm_result = |
| core_access_[current_core_].debug_interface->GetDisassembly( |
| pcc_result.value()); |
| if (disasm_result.ok()) { |
| absl::StrAppend(&prompt, " ", disasm_result.value()); |
| } |
| absl::StrAppend(&prompt, "\n"); |
| } |
| auto cheriot_state = |
| static_cast<CheriotState *>(core_access_[current_core_].state); |
| auto &info_list = cheriot_state->interrupt_info_list(); |
| // Check if there is a new interrupt, if so print the info. |
| if (info_list.size() > last_info_list_size) { |
| auto const &info = *info_list.rbegin(); |
| absl::StrAppend(&prompt, info.is_interrupt ? "interrupt " : "exception ", |
| info.is_interrupt ? GetInterruptDescription(info) |
| : GetExceptionDescription(info)); |
| } |
| last_info_list_size = info_list.size(); |
| absl::StrAppend(&prompt, "[", current_core_, "] > "); |
| while (!command_streams_.empty()) { |
| auto ¤t_is = *command_streams_.back(); |
| // Ignore comments or empty lines. |
| bool is_file = command_streams_.size() > 1; |
| // Read a command from the input stream. If it's from a file, then ignore |
| // empty lines and comments. |
| do { |
| if (command_streams_.size() == 1) os << prompt; |
| current_is.getline(line, kLineSize); |
| } while ((is_file && RE2::FullMatch(line, *empty_re_)) && |
| !current_is.bad() && !current_is.eof()); |
| |
| if (command_streams_.empty()) return; |
| |
| // If the current is at eof or gone bad, pop the stream and try the next. |
| if (current_is.bad() || current_is.eof()) { |
| // If it's not the only stream, delete the stream since it was allocated |
| // for an exec command. |
| if (is_file) { |
| delete command_streams_.back(); |
| } |
| command_streams_.pop_back(); |
| previous_line = previous_commands_.back(); |
| previous_commands_.pop_back(); |
| continue; |
| } |
| // We have a valid command. |
| break; |
| } |
| |
| if (command_streams_.empty()) { |
| os << "Error: input end of file or bad stream state\n" << std::endl; |
| os.flush(); |
| return; |
| } |
| |
| if (line[0] != '\0') { |
| previous_line = line; |
| } |
| line_view = absl::string_view(previous_line); |
| |
| // Start matching commands. |
| |
| // First try any added custom commands. |
| bool executed = false; |
| for (auto &fcn : command_functions_) { |
| std::string output; |
| executed = fcn(line_view, core_access_[current_core_], output); |
| if (executed) { |
| os << output << std::endl; |
| break; |
| } |
| } |
| if (executed) continue; |
| |
| // quit |
| if (RE2::FullMatch(line_view, *quit_re_)) return; |
| |
| // core N |
| if (int new_core; |
| RE2::FullMatch(line_view, *core_re_, RE2::CRadix(&new_core))) { |
| if (new_core >= core_access_.size()) { |
| os << absl::StrCat("Error: core number must be less than ", |
| core_access_.size()) |
| << std::endl; |
| os.flush(); |
| continue; |
| } |
| current_core_ = new_core; |
| continue; |
| } |
| |
| // run |
| if (RE2::FullMatch(line_view, *run_re_)) { |
| auto run_result = core_access_[current_core_].debug_interface->Run(); |
| if (!run_result.ok()) { |
| os << "Error: " << run_result.message() << std::endl; |
| os.flush(); |
| } |
| auto wait_result = core_access_[current_core_].debug_interface->Wait(); |
| if (!wait_result.ok()) { |
| os << "Error: " << wait_result.message() << std::endl; |
| os.flush(); |
| } |
| halt_reason = true; |
| continue; |
| } |
| |
| // run free |
| if (RE2::FullMatch(line_view, *run_free_re_)) { |
| auto result = core_access_[current_core_].debug_interface->Run(); |
| if (!result.ok()) { |
| os << "Error: " << result.message() << std::endl; |
| os.flush(); |
| } |
| halt_reason = true; |
| continue; |
| } |
| |
| // wait |
| if (RE2::FullMatch(line_view, *wait_re_)) { |
| auto result = core_access_[current_core_].debug_interface->Wait(); |
| if (!result.ok()) { |
| os << "Error: " << result.message() << std::endl; |
| os.flush(); |
| } |
| continue; |
| } |
| |
| // step |
| if (RE2::FullMatch(line_view, *step_1_re_)) { |
| auto result = core_access_[current_core_].debug_interface->Step(1); |
| if (!result.status().ok()) { |
| os << "Error: " << result.status().message() << std::endl; |
| os.flush(); |
| continue; |
| } |
| if (result.value() != 1) { |
| os << result.value() << " instructions executed" << std::endl; |
| os.flush(); |
| } |
| continue; |
| } |
| |
| // step N |
| if (int count; |
| RE2::FullMatch(line_view, *step_n_re_, RE2::CRadix(&count))) { |
| auto result = core_access_[current_core_].debug_interface->Step(count); |
| if (!result.status().ok()) { |
| os << "Error: " << result.status().message() << std::endl; |
| os.flush(); |
| continue; |
| } |
| if (result.value() != count) { |
| os << result.value() << " instructions executed" << std::endl; |
| os.flush(); |
| halt_reason = true; |
| } |
| continue; |
| } |
| |
| // halt |
| if (RE2::FullMatch(line_view, *halt_re_)) { |
| auto result = core_access_[current_core_].debug_interface->Halt(); |
| if (!result.ok()) { |
| os << "Error: " << result.message() << std::endl; |
| os.flush(); |
| } |
| halt_reason = true; |
| continue; |
| } |
| |
| // reg read NAME |
| if (std::string name, format; |
| RE2::FullMatch(line_view, *read_reg_re_, &name, &format)) { |
| // If it is the simulator 'register' $all, print all registers. |
| if (name == "$all") { |
| os << FormatAllRegisters(current_core_); |
| continue; |
| } |
| os << FormatRegister(current_core_, name) << std::endl; |
| os.flush(); |
| continue; |
| } |
| |
| // reg write NAME = VALUE |
| if (std::string name, value; |
| RE2::FullMatch(line_view, *write_reg_re_, &name, &value)) { |
| auto result = GetValueFromString(current_core_, value, /*radix=*/0); |
| if (!result.ok()) { |
| os << absl::StrCat("Error: '", value, "' ", result.status().message()) |
| << std::endl; |
| os.flush(); |
| continue; |
| } |
| auto write_result = |
| core_access_[current_core_].debug_interface->WriteRegister( |
| name, result.value()); |
| if (!write_result.ok()) { |
| os << "Error: " << write_result.message() << std::endl; |
| os.flush(); |
| } |
| continue; |
| } |
| |
| // mem get VALUE | SYMBOL [FORMAT] |
| |
| if (std::string str_value, format; |
| RE2::FullMatch(line_view, *read_mem_re_, &str_value, &format)) { |
| os << ReadMemory(current_core_, str_value, format) << std::endl; |
| continue; |
| } |
| |
| if (std::string str_value1, format, str_value2; RE2::FullMatch( |
| line_view, *write_mem_re_, &str_value1, &format, &str_value2)) { |
| os << WriteMemory(current_core_, str_value1, format, str_value2) |
| << std::endl; |
| continue; |
| } |
| |
| // break set VALUE | SYMBOL |
| if (std::string str_value; |
| RE2::FullMatch(line_view, *set_break_re_, &str_value) || |
| RE2::FullMatch(line_view, *set_break2_re_, &str_value)) { |
| if (str_value == "$branch") { |
| auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>( |
| core_access_[current_core_].debug_interface); |
| cheriot_interface->SetBreakOnControlFlowChange(true); |
| continue; |
| } else if (str_value == "$exception") { |
| if (interrupt_listeners_[current_core_]->AreExceptionsEnabled()) { |
| os << "Break on exceptions are already enabled\n"; |
| continue; |
| } |
| interrupt_listeners_[current_core_]->SetEnableExceptions(true); |
| continue; |
| } else if (str_value == "$interrupt") { |
| if (interrupt_listeners_[current_core_]->AreInterruptsEnabled()) { |
| os << "Break on interrupts are already enabled\n"; |
| continue; |
| } |
| interrupt_listeners_[current_core_]->SetEnableInterrupts(true); |
| continue; |
| } |
| auto result = GetValueFromString(current_core_, str_value, /*radix=*/0); |
| if (!result.ok()) { |
| os << absl::StrCat("Error: ?????? '", str_value, "' ", |
| result.status().message()) |
| << std::endl; |
| os.flush(); |
| continue; |
| } |
| auto cmd_result = |
| core_access_[current_core_].debug_interface->SetSwBreakpoint( |
| result.value()); |
| if (!cmd_result.ok()) { |
| os << "Error: " << cmd_result.message() << std::endl; |
| os.flush(); |
| continue; |
| } |
| core_access_[current_core_] |
| .breakpoint_map[core_access_[current_core_].breakpoint_index++] = |
| result.value(); |
| os << absl::StrCat("Breakpoint set at 0x", |
| absl::Hex(result.value(), absl::PadSpec::kZeroPad8)) |
| << std::endl; |
| continue; |
| } |
| |
| // break set #<N> |
| if (std::string str_value, num_value; |
| RE2::FullMatch(line_view, *set_break_n_re_, &str_value, &num_value)) { |
| int index; |
| if (!absl::SimpleAtoi(num_value, &index)) { |
| os << absl::StrCat("Error: cannot parse '", str_value, |
| "' as a breakpoint index\n"); |
| continue; |
| } |
| auto iter = core_access_[current_core_].breakpoint_map.find(index); |
| if (iter == core_access_[current_core_].breakpoint_map.end()) { |
| os << absl::StrCat("Error: no breakpoint with index ", index, "\n"); |
| continue; |
| } |
| uint64_t address = iter->second; |
| if (!core_access_[current_core_].debug_interface->HasBreakpoint( |
| address)) { |
| auto status = |
| core_access_[current_core_].debug_interface->SetSwBreakpoint( |
| address); |
| if (!status.ok()) { |
| os << absl::StrCat("Error: ", status.message(), "\n"); |
| } |
| } else { |
| os << "Breakpoint already active\n"; |
| } |
| continue; |
| } |
| |
| // break clear #<N> |
| if (std::string str_value; |
| RE2::FullMatch(line_view, *clear_break_n_re_, &str_value)) { |
| int index; |
| if (!absl::SimpleAtoi(str_value, &index)) { |
| os << absl::StrCat("Error: cannot parse '", str_value, |
| "' as a breakpoint index\n"); |
| continue; |
| } |
| auto iter = core_access_[current_core_].breakpoint_map.find(index); |
| if (iter == core_access_[current_core_].breakpoint_map.end()) { |
| os << absl::StrCat("Error: no breakpoint with index ", index, "\n"); |
| continue; |
| } |
| uint64_t address = iter->second; |
| if (core_access_[current_core_].debug_interface->HasBreakpoint(address)) { |
| auto status = |
| core_access_[current_core_].debug_interface->ClearSwBreakpoint( |
| address); |
| if (!status.ok()) { |
| os << absl::StrCat("Error: ", status.message(), "\n"); |
| } |
| } else { |
| os << absl::StrCat("No such active breakpoint: #", index, "\n"); |
| } |
| continue; |
| } |
| |
| // break clear-all |
| if (RE2::FullMatch(line_view, *clear_all_break_re_)) { |
| auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>( |
| core_access_[current_core_].debug_interface); |
| cheriot_interface->SetBreakOnControlFlowChange(false); |
| auto result = |
| core_access_[current_core_].debug_interface->ClearAllSwBreakpoints(); |
| if (!result.ok()) { |
| os << absl::StrCat("Error: ", result.message()) << std::endl; |
| os.flush(); |
| continue; |
| } |
| os << "All breakpoints removed" << std::endl; |
| continue; |
| } |
| |
| // break clear VALUE | SYMBOL |
| if (std::string str_value; |
| RE2::FullMatch(line_view, *clear_break_re_, &str_value)) { |
| if (str_value == "$branch") { |
| auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>( |
| core_access_[current_core_].debug_interface); |
| cheriot_interface->SetBreakOnControlFlowChange(false); |
| continue; |
| } else if (str_value == "$exception") { |
| if (!interrupt_listeners_[current_core_]->AreExceptionsEnabled()) { |
| os << "Break on exceptions are already disabled\n"; |
| continue; |
| } |
| interrupt_listeners_[current_core_]->SetEnableExceptions(false); |
| continue; |
| } else if (str_value == "$interrupt") { |
| if (!interrupt_listeners_[current_core_]->AreInterruptsEnabled()) { |
| os << "Break on interrupts are already disabled\n"; |
| continue; |
| } |
| interrupt_listeners_[current_core_]->SetEnableInterrupts(false); |
| continue; |
| } |
| auto result = GetValueFromString(current_core_, str_value, /*radix=*/0); |
| if (!result.ok()) { |
| os << absl::StrCat("Error: '", str_value, "' ", |
| result.status().message()) |
| << std::endl; |
| os.flush(); |
| continue; |
| } |
| auto cmd_result = |
| core_access_[current_core_].debug_interface->ClearSwBreakpoint( |
| result.value()); |
| if (!cmd_result.ok()) { |
| os << "Error: " << cmd_result.message() << std::endl; |
| os.flush(); |
| continue; |
| } |
| os << absl::StrCat("Breakpoint removed from 0x", |
| absl::Hex(result.value(), absl::PadSpec::kZeroPad8)) |
| << std::endl; |
| continue; |
| } |
| |
| // break list |
| if (RE2::FullMatch(line_view, *list_break_re_)) { |
| std::string bp_list; |
| for (auto [index, address] : core_access_[current_core_].breakpoint_map) { |
| bool active = |
| core_access_[current_core_].debug_interface->HasBreakpoint(address); |
| std::string symbol; |
| auto *loader = core_access_[current_core_].loader_getter(); |
| if (loader != nullptr) { |
| auto res = loader->GetFcnSymbolName(address); |
| if (res.ok()) symbol = std::move(res.value()); |
| } |
| absl::StrAppend(&bp_list, |
| absl::StrFormat(" %3d %-8s 0x%08x %s\n", index, |
| active ? "active" : "inactive", address, |
| symbol.empty() ? "-" : symbol)); |
| } |
| os << absl::StrCat("Breakpoints:\n", bp_list, "\n"); |
| continue; |
| } |
| |
| // help |
| if (RE2::FullMatch(line_view, *help_re_)) { |
| for (auto const &usage : command_usage_) { |
| os << usage << std::endl; |
| } |
| os << help_message_; |
| os.flush(); |
| continue; |
| } |
| |
| // reg NAME |
| if (std::string name, format; |
| RE2::FullMatch(line_view, *read_reg2_re_, &name, &format)) { |
| if (name == "$all") { |
| os << FormatAllRegisters(current_core_) << std::endl; |
| continue; |
| } |
| os << FormatRegister(current_core_, name) << std::endl; |
| os.flush(); |
| continue; |
| } |
| |
| // watch set SYMBOL | VALUE <length> [r|w|rw] |
| if (std::string str_value, length_value, rw_value; RE2::FullMatch( |
| line_view, *set_watch_re_, &str_value, &length_value, &rw_value)) { |
| auto result = GetValueFromString(current_core_, str_value, /*radix=*/0); |
| if (!result.ok()) { |
| os << absl::StrCat("Error: '", str_value, "' ", |
| result.status().message()) |
| << std::endl; |
| os.flush(); |
| continue; |
| } |
| if (!rw_value.empty()) { |
| rw_value = rw_value.substr(rw_value.find_first_not_of(' ')); |
| } |
| AccessType access_type = AccessType::kStore; |
| if (rw_value == "r") { |
| access_type = AccessType::kLoad; |
| } else if (rw_value == "rw") { |
| access_type = AccessType::kStore; |
| } |
| |
| uint64_t address = result.value(); |
| result = GetValueFromString(current_core_, length_value, /*radix=*/0); |
| if (!result.ok()) { |
| os << absl::StrCat("Error: cannot parse '", length_value, |
| "' as a length\n"); |
| os.flush(); |
| continue; |
| } |
| size_t length = result.value(); |
| auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>( |
| core_access_[current_core_].debug_interface); |
| auto cmd_result = |
| cheriot_interface->SetDataWatchpoint(address, length, access_type); |
| if (!cmd_result.ok()) { |
| os << "Error: " << cmd_result.message() << std::endl; |
| os.flush(); |
| continue; |
| } |
| core_access_[current_core_] |
| .watchpoint_map[core_access_[current_core_].watchpoint_index++] = { |
| address, length, access_type, /*active=*/true}; |
| os << absl::StrCat("Watchpoint set at 0x", |
| absl::Hex(address, absl::PadSpec::kZeroPad8)) |
| << std::endl; |
| continue; |
| } |
| |
| // watch set #<N> |
| if (std::string str_value, num_value; |
| RE2::FullMatch(line_view, *set_watch_n_re_, &str_value, &num_value)) { |
| int index; |
| if (!absl::SimpleAtoi(num_value, &index)) { |
| os << absl::StrCat("Error: cannot parse '", str_value, |
| "' as a watchpoint index\n"); |
| continue; |
| } |
| auto iter = core_access_[current_core_].watchpoint_map.find(index); |
| if (iter == core_access_[current_core_].watchpoint_map.end()) { |
| os << absl::StrCat("Error: no watchpoint with index ", index, "\n"); |
| continue; |
| } |
| if (iter->second.active) { |
| os << "Watchpoint already active\n"; |
| continue; |
| } |
| uint64_t address = iter->second.address; |
| size_t length = iter->second.length; |
| AccessType access_type = iter->second.access_type; |
| auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>( |
| core_access_[current_core_].debug_interface); |
| auto status = |
| cheriot_interface->SetDataWatchpoint(address, length, access_type); |
| if (!status.ok()) { |
| os << absl::StrCat("Error: ", status.message(), "\n"); |
| continue; |
| } |
| iter->second.active = true; |
| continue; |
| } |
| |
| // watch clear #<N> |
| if (std::string str_value; |
| RE2::FullMatch(line_view, *clear_watch_n_re_, &str_value)) { |
| int index; |
| if (!absl::SimpleAtoi(str_value, &index)) { |
| os << absl::StrCat("Error: cannot parse '", str_value, |
| "' as a watchpoint index\n"); |
| continue; |
| } |
| auto iter = core_access_[current_core_].watchpoint_map.find(index); |
| if (iter == core_access_[current_core_].watchpoint_map.end()) { |
| os << absl::StrCat("Error: no watchpoint with index ", index, "\n"); |
| continue; |
| } |
| if (!iter->second.active) continue; |
| uint64_t address = iter->second.address; |
| auto access_type = iter->second.access_type; |
| auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>( |
| core_access_[current_core_].debug_interface); |
| auto status = |
| cheriot_interface->ClearDataWatchpoint(address, access_type); |
| if (!status.ok()) { |
| os << absl::StrCat("Error: ", status.message(), "\n"); |
| continue; |
| } |
| iter->second.active = false; |
| continue; |
| } |
| |
| // watch clear-all |
| if (RE2::FullMatch(line_view, *clear_all_watch_re_)) { |
| for (auto &[index, info] : core_access_[current_core_].watchpoint_map) { |
| if (!info.active) continue; |
| |
| uint64_t address = info.address; |
| auto access_type = info.access_type; |
| auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>( |
| core_access_[current_core_].debug_interface); |
| auto status = |
| cheriot_interface->ClearDataWatchpoint(address, access_type); |
| if (!status.ok()) { |
| os << absl::StrCat("Error: ", status.message(), "\n"); |
| continue; |
| } |
| info.active = false; |
| } |
| os << "All watchpoints removed" << std::endl; |
| continue; |
| } |
| |
| // watch clear VALUE | SYMBOL [r|w|rw] |
| if (std::string str_value, rw_value; |
| RE2::FullMatch(line_view, *clear_watch_re_, &str_value, &rw_value)) { |
| auto result = GetValueFromString(current_core_, str_value, /*radix=*/0); |
| if (!result.ok()) { |
| os << absl::StrCat("Error: '", str_value, "' ", |
| result.status().message()) |
| << std::endl; |
| os.flush(); |
| continue; |
| } |
| if (!rw_value.empty()) { |
| rw_value = rw_value.substr(rw_value.find_first_not_of(' ')); |
| } |
| auto access_type = AccessType::kStore; |
| if (rw_value == "r") { |
| access_type = AccessType::kLoad; |
| } else if (rw_value == "rw") { |
| access_type = AccessType::kLoadStore; |
| } |
| bool done = false; |
| for (auto &[index, info] : core_access_[current_core_].watchpoint_map) { |
| if ((info.address == result.value()) && |
| (info.access_type == access_type)) { |
| auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>( |
| core_access_[current_core_].debug_interface); |
| auto cmd_result = cheriot_interface->ClearDataWatchpoint( |
| result.value(), access_type); |
| if (!cmd_result.ok()) { |
| os << "Error: " << cmd_result.message() << std::endl; |
| os.flush(); |
| break; |
| } |
| info.active = false; |
| done = true; |
| break; |
| } |
| } |
| if (!done) { |
| continue; |
| } |
| os << absl::StrCat("Watchpoint removed from 0x", |
| absl::Hex(result.value(), absl::PadSpec::kZeroPad8)) |
| << std::endl; |
| continue; |
| } |
| |
| // watch SYMBOL | VALUE [r|w|rw] |
| if (std::string str_value, length_value, rw_value; RE2::FullMatch( |
| line_view, *set_watch2_re_, &str_value, &length_value, &rw_value)) { |
| auto result = GetValueFromString(current_core_, str_value, /*radix=*/0); |
| if (!result.ok()) { |
| os << absl::StrCat("Error: '", str_value, "' ", |
| result.status().message()) |
| << std::endl; |
| os.flush(); |
| continue; |
| } |
| AccessType access_type = AccessType::kStore; |
| if (!rw_value.empty()) { |
| rw_value = rw_value.substr(rw_value.find_first_not_of(' ')); |
| } |
| if (rw_value == "r") { |
| access_type = AccessType::kLoad; |
| } else if (rw_value == "rw") { |
| access_type = AccessType::kLoadStore; |
| } |
| uint64_t address = result.value(); |
| result = GetValueFromString(current_core_, length_value, /*radix=*/0); |
| if (!result.ok()) { |
| os << absl::StrCat("Error: cannot parse '", length_value, |
| "' as a length\n"); |
| os.flush(); |
| continue; |
| } |
| size_t length = result.value(); |
| auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>( |
| core_access_[current_core_].debug_interface); |
| auto cmd_result = |
| cheriot_interface->SetDataWatchpoint(address, length, access_type); |
| if (!cmd_result.ok()) { |
| os << "Error: " << cmd_result.message() << std::endl; |
| os.flush(); |
| continue; |
| } |
| core_access_[current_core_] |
| .watchpoint_map[core_access_[current_core_].watchpoint_index++] = { |
| address, length, access_type, /*active=*/true}; |
| os << absl::StrCat("Watchpoint set at 0x", |
| absl::Hex(address, absl::PadSpec::kZeroPad8)) |
| << std::endl; |
| continue; |
| } |
| |
| // watch list |
| if (RE2::FullMatch(line_view, *list_watch_re_)) { |
| std::string bp_list; |
| for (auto const &[index, info] : |
| core_access_[current_core_].watchpoint_map) { |
| std::string symbol; |
| auto *loader = core_access_[current_core_].loader_getter(); |
| if (loader != nullptr) { |
| auto res = loader->GetFcnSymbolName(info.address); |
| if (res.ok()) symbol = std::move(res.value()); |
| } |
| std::string access_type; |
| switch (info.access_type) { |
| case AccessType::kStore: |
| access_type = "w"; |
| break; |
| case AccessType::kLoad: |
| access_type = "r"; |
| break; |
| case AccessType::kLoadStore: |
| access_type = "rw"; |
| } |
| absl::StrAppend( |
| &bp_list, |
| absl::StrFormat(" %3d %-8s 0x%08x %3d %2s %s\n", index, |
| info.active ? "active" : "inactive", info.address, |
| info.length, access_type, |
| symbol.empty() ? "-" : symbol)); |
| } |
| os << absl::StrCat("Watchpoints:\n", bp_list, "\n"); |
| continue; |
| } |
| |
| // mem get VALUE | SYMBOL [FORMAT] |
| if (std::string str_value, format; |
| RE2::FullMatch(line_view, *read_mem2_re_, &str_value, &format)) { |
| os << ReadMemory(current_core_, str_value, format) << std::endl; |
| continue; |
| } |
| |
| // Action points. |
| if (RE2::FullMatch(line_view, *list_action_re_)) { |
| os << ListActionPoints(); |
| continue; |
| } |
| if (std::string str_value; |
| RE2::FullMatch(line_view, *enable_action_n_re_, &str_value)) { |
| os << EnableActionPointN(str_value); |
| continue; |
| } |
| if (std::string str_value; |
| RE2::FullMatch(line_view, *disable_action_n_re_, &str_value)) { |
| os << DisableActionPointN(str_value); |
| continue; |
| } |
| if (std::string str_value; |
| RE2::FullMatch(line_view, *clear_action_n_re_, &str_value)) { |
| os << ClearActionPointN(str_value); |
| continue; |
| } |
| if (RE2::FullMatch(line_view, *clear_all_action_re_)) { |
| os << ClearAllActionPoints(); |
| continue; |
| } |
| // branch-trace. |
| // This prints out a list of the last 16 pairs of <from, to> control flow |
| // changes (including interrupts), with no repetitions for loops. |
| if (RE2::FullMatch(line_view, *branch_trace_re_)) { |
| // Get the index of the head of the queue. |
| auto head_result = |
| core_access_[current_core_].debug_interface->ReadRegister( |
| "$branch_trace_head"); |
| if (!head_result.ok()) { |
| os << "Error: " << head_result.status().message() << "\n"; |
| continue; |
| } |
| // Adjust by one, as the head points to the most recent valid entry. |
| auto head = head_result.value() + 1; |
| // Get the branch trace data buffer. |
| auto result = |
| core_access_[current_core_].debug_interface->GetRegisterDataBuffer( |
| "$branch_trace"); |
| if (!result.ok()) { |
| os << "Error: " << result.status().message() << "\n"; |
| continue; |
| } |
| auto *db = result.value(); |
| // Check for null data buffer. |
| if (db == nullptr) { |
| os << "Error: register '$branch_trace' has no data buffer\n"; |
| os.flush(); |
| continue; |
| } |
| // Get a span for the branch trace. |
| auto trace_span = db->Get<BranchTraceEntry>(); |
| auto size = trace_span.size(); |
| os << absl::StrFormat(" %-8s %-8s %8s\n", "From", "To", |
| "Count"); |
| for (int i = 0; i < size; ++i) { |
| auto index = (head + i) % size; |
| auto [from, to, count] = trace_span[index]; |
| // Ignore 0 -> 0 entries. Those are the initial values. |
| if (count == 0) continue; |
| os << absl::StrFormat(" 0x%08x -> 0x%08x %8u\n", from, to, count); |
| } |
| os.flush(); |
| continue; |
| } |
| |
| // Step (over function call). |
| if (RE2::FullMatch(line_view, *next_re_)) { |
| auto res = StepOverCall(current_core_, os); |
| if (!res.ok()) { |
| os << "Error: " << res.message() << "\n"; |
| } |
| continue; |
| } |
| |
| if (std::string file_name; |
| RE2::FullMatch(line_view, *exec_re_, &file_name)) { |
| auto *ifile = new std::ifstream(file_name); |
| if (!ifile->is_open() || !ifile->good()) { |
| os << "Error: unable to open '" << file_name << "'\n"; |
| os.flush(); |
| continue; |
| } |
| previous_commands_.push_back(previous_line); |
| command_streams_.push_back(ifile); |
| continue; |
| } |
| |
| // At this point a valid command should have been matched, so assume an |
| // error. |
| os << absl::StrCat("Error: unrecognized command '", line, "'") << std::endl; |
| os.flush(); |
| } |
| } |
| // NOLINTEND(readability/fn_size) |
| |
| void DebugCommandShell::AddCommand(absl::string_view usage, |
| CommandFunction command_function) { |
| command_usage_.emplace_back(usage); |
| command_functions_.emplace_back(std::move(command_function)); |
| } |
| |
| namespace { |
| |
| constexpr absl::string_view kHexFormatNone = "%x"; |
| constexpr absl::string_view kHexFormatCapNone = "%X"; |
| constexpr absl::string_view kHexFormatUint8 = "%02x"; |
| constexpr absl::string_view kHexFormatCapUint8 = "%02X"; |
| constexpr absl::string_view kHexFormatUint16 = "%04x"; |
| constexpr absl::string_view kHexFormatCapUint16 = "%04X"; |
| constexpr absl::string_view kHexFormatUint32 = "%08x"; |
| constexpr absl::string_view kHexFormatCapUint32 = "%08X"; |
| constexpr absl::string_view kHexFormatUint64 = "%016x"; |
| constexpr absl::string_view kHexFormatCapUint64 = "%016X"; |
| |
| constexpr absl::string_view kHexFormat[] = { |
| kHexFormatNone, kHexFormatUint8, kHexFormatUint16, |
| kHexFormatNone, kHexFormatUint32, kHexFormatNone, |
| kHexFormatNone, kHexFormatNone, kHexFormatUint64}; |
| |
| constexpr absl::string_view kHexFormatCap[] = { |
| kHexFormatCapNone, kHexFormatCapUint8, kHexFormatCapUint16, |
| kHexFormatCapNone, kHexFormatCapUint32, kHexFormatCapNone, |
| kHexFormatCapNone, kHexFormatCapNone, kHexFormatCapUint64}; |
| |
| // Templated helper function to help format integer values of different widths. |
| template <typename T> |
| std::string FormatDbValue(generic::DataBuffer *db, absl::string_view format, |
| int index) { |
| std::string output; |
| if (index < 0 || index >= db->size<T>()) return "Error: index out of range"; |
| T value = db->Get<T>(index); |
| switch (format[0]) { |
| case 'd': |
| output += absl::StrFormat("%d", value); |
| break; |
| case 'o': |
| output += absl::StrFormat("%o", value); |
| break; |
| case 'u': |
| output += absl::StrFormat("%u", value); |
| break; |
| case 'x': |
| output += absl::StrFormat(kHexFormat[sizeof(T)], value); |
| break; |
| case 'X': |
| output += absl::StrFormat(kHexFormatCap[sizeof(T)], value); |
| break; |
| default: |
| output = absl::StrCat("Error: invalid '", format, "'"); |
| break; |
| } |
| return output; |
| } |
| |
| template <typename T> |
| absl::Status WriteDbValue(absl::string_view str_value, absl::string_view format, |
| int index, generic::DataBuffer *db) { |
| if (index < 0 || index >= db->size<T>()) |
| return absl::OutOfRangeError("Error: index out of range"); |
| if (format[0] == 'd') { |
| int64_t value; |
| if (!absl::SimpleAtoi(str_value, &value)) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Error: could not convert '", str_value, "' to number")); |
| } |
| db->Set<T>(index, static_cast<T>(value)); |
| return absl::OkStatus(); |
| } |
| if (format[0] == 'u') { |
| uint64_t value; |
| if (!absl::SimpleAtoi(str_value, &value)) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Error: could not convert '", str_value, "' to number")); |
| } |
| db->Set<T>(index, static_cast<T>(value)); |
| return absl::OkStatus(); |
| } |
| if (format[0] == 'x' || format[0] == 'X') { |
| uint64_t value; |
| if (!absl::SimpleHexAtoi(str_value, &value)) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Error: could not convert '", str_value, "' to number")); |
| } |
| db->Set<T>(index, static_cast<T>(value)); |
| return absl::OkStatus(); |
| } |
| return absl::InternalError(absl::StrCat("Unsupported format '", format, "'")); |
| } |
| |
| } // namespace |
| |
| std::string DebugCommandShell::FormatSingleDbValue(generic::DataBuffer *db, |
| const std::string &format, |
| int width, int index) const { |
| switch (width) { |
| case 8: |
| return FormatDbValue<uint8_t>(db, format, index); |
| case 16: |
| return FormatDbValue<uint16_t>(db, format, index); |
| case 32: |
| return FormatDbValue<uint32_t>(db, format, index); |
| case 64: |
| return FormatDbValue<uint64_t>(db, format, index); |
| default: |
| return absl::StrCat("Error: illegal width '", width, "'"); |
| } |
| } |
| |
| std::string DebugCommandShell::FormatAllDbValues(generic::DataBuffer *db, |
| const std::string &format, |
| int width) const { |
| std::string output; |
| std::string sep; |
| switch (width) { |
| case 8: |
| for (int i = 0; i < db->size<uint8_t>(); ++i) { |
| output += sep + FormatDbValue<uint8_t>(db, format, i); |
| sep = ":"; |
| } |
| break; |
| case 16: |
| for (int i = 0; i < db->size<uint16_t>(); ++i) { |
| output += sep + FormatDbValue<uint16_t>(db, format, i); |
| sep = ":"; |
| } |
| break; |
| case 32: |
| for (int i = 0; i < db->size<uint32_t>(); ++i) { |
| output += sep + FormatDbValue<uint32_t>(db, format, i); |
| sep = ":"; |
| } |
| break; |
| case 64: |
| for (int i = 0; i < db->size<uint64_t>(); ++i) { |
| output += sep + FormatDbValue<uint64_t>(db, format, i); |
| sep = ":"; |
| } |
| break; |
| default: |
| output = absl::StrCat("Error: illegal width '", width, "'"); |
| break; |
| } |
| return output; |
| } |
| |
| absl::Status DebugCommandShell::WriteSingleValueToDb( |
| absl::string_view str_value, generic::DataBuffer *db, std::string format, |
| int width, int index) const { |
| switch (width) { |
| case 8: |
| return WriteDbValue<uint8_t>(str_value, format, index, db); |
| case 16: |
| return WriteDbValue<uint16_t>(str_value, format, index, db); |
| case 32: |
| return WriteDbValue<uint32_t>(str_value, format, index, db); |
| case 64: |
| return WriteDbValue<uint64_t>(str_value, format, index, db); |
| default: |
| return absl::InternalError("Error"); |
| } |
| return absl::OkStatus(); |
| } |
| |
| std::string DebugCommandShell::ReadMemory(int core, |
| const std::string &str_value, |
| absl::string_view format) { |
| int size = 0; |
| char format_char = 'x'; |
| int bit_width = 32; |
| |
| // Get the bit width, but only if the format is not 'i'. |
| if (!format.empty()) { |
| if (format[0] == 'i') { |
| format_char = 'i'; |
| } else { |
| // Check the format specification. |
| auto pos = format.find_first_not_of(' '); |
| format_char = format[pos]; |
| auto status = absl::SimpleAtoi(format.substr(pos + 1), &bit_width); |
| if (!status) { |
| return absl::StrCat("Error '", format.substr(pos + 1), |
| "': ", "unable to convert to int"); |
| } |
| if ((bit_width != 8) && (bit_width != 16) && (bit_width != 32) && |
| (bit_width != 64)) { |
| return absl::StrCat("Illegal bit width specification: ", bit_width); |
| } |
| } |
| } |
| |
| // Get the address. |
| auto result = GetValueFromString(current_core_, str_value, /*radix=*/0); |
| if (!result.ok()) { |
| return absl::StrCat("Error: '", str_value, "' ", result.status().message()); |
| } |
| auto address = result.value(); |
| |
| // If format is 'i', then we are getting a disassembled instruction. Ignore |
| // bitwidth. |
| if (format_char == 'i') { |
| auto disasm_result = |
| core_access_[current_core_].debug_interface->GetDisassembly(address); |
| if (!disasm_result.ok()) { |
| return absl::StrCat("Error: ", disasm_result.status().message()); |
| } |
| return absl::StrCat(" ", disasm_result.value()); |
| } |
| |
| // Perform the memory access. |
| size = bit_width / 8; |
| if (size > kMemBufferSize) size = kMemBufferSize; |
| auto mem_result = core_access_[current_core_].debug_interface->ReadMemory( |
| address, mem_buffer_, size); |
| if (!mem_result.ok()) { |
| return absl::StrCat("Error: ", mem_result.status().message()); |
| } |
| // Perform tag memory access. |
| // There is a tag per each 8 bytes of data. So compute the number of tags |
| // that need to be loaded, taking into account misalignment. |
| auto tag_address = address & ~0x7ULL; |
| int tag_size = (address + size - tag_address + 7) / 8; |
| auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>( |
| core_access_[current_core_].debug_interface); |
| auto tag_result = |
| cheriot_interface->ReadTagMemory(tag_address, tag_buffer_, tag_size); |
| if (!tag_result.ok()) { |
| return absl::StrCat("Error: ", tag_result.status().message()); |
| } |
| std::string tag_string; |
| std::string sep = "["; |
| for (int i = 0; i < tag_size; ++i) { |
| absl::StrAppend(&tag_string, sep, tag_buffer_[i]); |
| sep = ", "; |
| } |
| absl::StrAppend(&tag_string, "]"); |
| // Get the result and format it. |
| void *void_buffer = mem_buffer_; |
| std::string output; |
| if ((format_char == 'f') && (bit_width >= 32)) { |
| switch (bit_width) { |
| case 32: |
| output = absl::StrCat(*reinterpret_cast<float *>(void_buffer)); |
| break; |
| case 64: |
| output = absl::StrCat(*reinterpret_cast<double *>(void_buffer)); |
| break; |
| default: |
| break; |
| } |
| } else if (format_char == 'd') { |
| switch (bit_width) { |
| case 8: |
| output = absl::StrCat(*static_cast<int8_t *>(void_buffer)); |
| break; |
| case 16: |
| output = absl::StrCat(*static_cast<int16_t *>(void_buffer)); |
| break; |
| case 32: |
| output = absl::StrCat(*static_cast<int32_t *>(void_buffer)); |
| break; |
| case 64: |
| output = absl::StrCat(*static_cast<int64_t *>(void_buffer)); |
| break; |
| default: |
| break; |
| } |
| } else { |
| uint64_t val = 0; |
| auto pad = absl::PadSpec::kNoPad; |
| switch (bit_width) { |
| case 8: |
| val = *static_cast<uint8_t *>(void_buffer); |
| pad = absl::PadSpec::kZeroPad2; |
| break; |
| case 16: |
| val = *static_cast<uint16_t *>(void_buffer); |
| pad = absl::PadSpec::kZeroPad4; |
| break; |
| case 32: |
| val = *static_cast<uint32_t *>(void_buffer); |
| pad = absl::PadSpec::kZeroPad8; |
| break; |
| case 64: |
| val = *static_cast<uint64_t *>(void_buffer); |
| pad = absl::PadSpec::kZeroPad16; |
| break; |
| } |
| std::string format_string; |
| if ((format_char == 'x') || (format_char == 'X')) { |
| output = absl::StrCat(absl::Hex(val, pad)); |
| } else if (format_char == 'u') { |
| output = absl::StrCat(val); |
| } else { |
| output = absl::StrFormat("%o", val); |
| } |
| } |
| return absl::StrCat("[", absl::Hex(address, absl::PadSpec::kZeroPad8), |
| "] = ", output, " ", tag_string); |
| } |
| |
| std::string DebugCommandShell::WriteMemory(int core, |
| const std::string &str_value1, |
| const std::string &format, |
| const std::string &str_value2) { |
| int size = 0; |
| char format_char = '\0'; |
| int radix = 0; |
| int bit_width = 32; |
| if (!format.empty()) { |
| // Check the format specification. |
| auto pos = format.find_first_not_of(' '); |
| format_char = format[pos]; |
| if ((format_char == 'x') || (format_char == 'X')) { |
| radix = 16; |
| } else if ((format_char == 'd') || (format_char == 'u')) { |
| radix = 10; |
| } else if (format_char == 'o') { |
| radix = 8; |
| } |
| auto status = riscv::internal::stoull(format.substr(pos + 1)); |
| bit_width = 0; |
| if (!status.ok()) { |
| return absl::StrCat("Error '", format.substr(pos + 1), |
| "': ", status.status().message()); |
| } |
| bit_width = status.value(); |
| } |
| |
| // Determine the zero padding for hex number. |
| auto pad = absl::kNoPad; |
| if (bit_width == 8) { |
| pad = absl::kZeroPad2; |
| } else if (bit_width == 16) { |
| pad = absl::kZeroPad4; |
| } else if (bit_width == 32) { |
| pad = absl::kZeroPad8; |
| } else if (bit_width == 64) { |
| pad = absl::kZeroPad16; |
| } else { |
| return absl::StrCat("Illegal bit width specification: ", bit_width); |
| } |
| |
| // Get the address. |
| auto result = GetValueFromString(current_core_, str_value1, /*radix=*/0); |
| if (!result.ok()) { |
| return absl::StrCat("Error: '", str_value1, "' ", |
| result.status().message()); |
| } |
| auto address = result.value(); |
| |
| // Get the value to be stored. |
| auto value_result = GetValueFromString(current_core_, str_value2, radix); |
| if (!value_result.ok()) { |
| return absl::StrCat("Error: '", str_value2, "' ", |
| result.status().message()); |
| } |
| int64_t mem_value = value_result.value(); |
| |
| // Perform the memory access. |
| size = bit_width / 8; |
| if (size > kMemBufferSize) size = kMemBufferSize; |
| std::memcpy(mem_buffer_, &mem_value, size); |
| auto mem_result = core_access_[current_core_].debug_interface->WriteMemory( |
| address, mem_buffer_, size); |
| if (!mem_result.ok()) { |
| return absl::StrCat("Error: ", mem_result.status().message()); |
| } |
| return absl::StrCat("[", absl::Hex(address, absl::kZeroPad8), |
| "] = ", absl::Hex(mem_value, pad)); |
| } |
| |
| absl::StatusOr<uint64_t> DebugCommandShell::GetValueFromString( |
| int core, const std::string &str_value, int radix) { |
| size_t index; |
| // Attempt to convert to a number. |
| auto convert_result = riscv::internal::stoull(str_value, &index, radix); |
| // If successful and the entire string was consumed, the number is good. |
| if (convert_result.ok() && (index >= str_value.size())) { |
| return convert_result.value(); |
| } |
| // If it's out of range, signal an error. |
| if (convert_result.status().code() == absl::StatusCode::kOutOfRange) { |
| return convert_result.status(); |
| } |
| // If all else fails, let's see if it's a symbol. |
| auto *loader = core_access_[core].loader_getter(); |
| if (loader == nullptr) |
| return absl::NotFoundError("No symbol table available"); |
| auto result = loader->GetSymbol(str_value); |
| if (!result.ok()) return result.status(); |
| return result.value().first; |
| } |
| |
| absl::Status DebugCommandShell::StepOverCall(int core, std::ostream &os) { |
| auto pcc_result = |
| core_access_[current_core_].debug_interface->ReadRegister("pcc"); |
| if (!pcc_result.ok()) { |
| return absl::UnavailableError(pcc_result.status().message()); |
| } |
| auto pcc = pcc_result.value(); |
| auto inst_res = |
| core_access_[current_core_].debug_interface->GetInstruction(pcc); |
| if (!inst_res.ok()) { |
| return absl::UnavailableError(inst_res.status().message()); |
| } |
| auto *inst = inst_res.value(); |
| auto opcode = inst->opcode(); |
| // If it's not a jump-and-link, it's a single step. |
| if ((opcode != *isa32::OpcodeEnum::kCheriotJal) && |
| (opcode != *isa32::OpcodeEnum::kCheriotJalr) && |
| (opcode != *isa32::OpcodeEnum::kCheriotJalrCra) && |
| (opcode != *isa32::OpcodeEnum::kCheriotCjal) && |
| (opcode != *isa32::OpcodeEnum::kCheriotCjalrCra)) { |
| inst->DecRef(); |
| return core_access_[current_core_].debug_interface->Step(1).status(); |
| } |
| // If it is a jump-and-link, we have to set a breakpoint on the instruction |
| // following the jump-and-link, then run until it halts, which may even be |
| // on another breakpoint. Either way, we then remove the breakpoint we |
| // inserted and return. |
| uint64_t bp_address = pcc + inst->size(); |
| inst->DecRef(); |
| // See if there is a bp on that address already, if so, don't try to set |
| // another one. |
| if (!core_access_[current_core_].debug_interface->HasBreakpoint(bp_address)) { |
| auto bp_set_res = |
| core_access_[current_core_].debug_interface->SetSwBreakpoint( |
| bp_address); |
| if (!bp_set_res.ok()) { |
| return bp_set_res; |
| } |
| } |
| auto run_res = core_access_[current_core_].debug_interface->Run(); |
| if (!run_res.ok()) { |
| // First remove the breakpoint, then return error. |
| (void)core_access_[current_core_].debug_interface->ClearSwBreakpoint( |
| bp_address); |
| return run_res; |
| } |
| (void)core_access_[current_core_].debug_interface->Wait(); |
| auto bp_clr_res = |
| core_access_[current_core_].debug_interface->ClearSwBreakpoint( |
| bp_address); |
| pcc_result = core_access_[current_core_].debug_interface->ReadRegister("pcc"); |
| pcc = pcc_result.value(); |
| if (pcc != bp_address) { |
| os << absl::StrCat( |
| "Warning: Stopped at instruction other than the expected: [", |
| absl::Hex(bp_address, absl::kZeroPad8), "]\n"); |
| } |
| return bp_clr_res; |
| } |
| |
| std::string DebugCommandShell::FormatCapabilityRegister( |
| int core, const std::string ®_name) const { |
| std::string output; |
| std::vector<uint64_t> values; |
| auto res = |
| core_access_[current_core_].debug_interface->ReadRegister(reg_name); |
| if (!res.ok()) { |
| return absl::StrCat("Error reading '", reg_name, |
| "': ", res.status().message()); |
| } |
| values.push_back(res.value()); |
| // Read the capability components. |
| for (auto const &suffix : |
| {"tag", "base", "top", "length", "object_type", "permissions"}) { |
| res = core_access_[current_core_].debug_interface->ReadRegister( |
| absl::StrCat(reg_name, ".", suffix)); |
| if (!res.ok()) { |
| return absl::StrCat("Error reading '", reg_name, ".", suffix, |
| "': ", res.status().message()); |
| } |
| values.push_back(res.value()); |
| } |
| // Format the capability register components into a readable format. |
| uint64_t obj_type = |
| values[5] | |
| ((values[5] && !(values[6] & PB::kPermitExecute)) ? 0x8 : 0x0); |
| std::string permissions = |
| absl::StrCat(values[6] & PB::kPermitGlobal ? "G " : "- ", |
| values[6] & PB::kPermitLoad ? "R" : "-", |
| values[6] & PB::kPermitStore ? "W" : "-", |
| values[6] & PB::kPermitLoadStoreCapability ? "c" : "-", |
| values[6] & PB::kPermitLoadMutable ? "m" : "-", |
| values[6] & PB::kPermitLoadGlobal ? "g" : "-", |
| values[6] & PB::kPermitStoreLocalCapability ? "l " : "- ", |
| values[6] & PB::kPermitExecute ? "X" : "-", |
| values[6] & PB::kPermitAccessSystemRegisters ? "a " : "- ", |
| values[6] & PB::kPermitSeal ? "S" : "-", |
| values[6] & PB::kPermitUnseal ? "U" : "-", |
| values[6] & PB::kUserPerm0 ? "0" : "-"); |
| absl::StrAppend( |
| &output, |
| absl::StrFormat( |
| "%-5s = 0x%08x (v: %1x 0x%08x-0x%09x l: 0x%09x o: 0x%x p: %s)", |
| reg_name, values[0], values[1], values[2], values[3], values[4], |
| obj_type, permissions)); |
| return output; |
| } |
| |
| bool DebugCommandShell::IsCapabilityRegister( |
| const std::string ®_name) const { |
| return capability_registers_.contains(reg_name); |
| } |
| |
| std::string DebugCommandShell::FormatRegister( |
| int core, const std::string ®_name) const { |
| if (IsCapabilityRegister(reg_name)) { |
| return FormatCapabilityRegister(current_core_, reg_name); |
| } |
| std::string output; |
| auto result = |
| core_access_[current_core_].debug_interface->ReadRegister(reg_name); |
| if (result.ok()) { |
| absl::StrAppend(&output, reg_name, " = ", absl::Hex(result.value())); |
| } else { |
| absl::StrAppend(&output, "Error reading '", reg_name, |
| "': ", result.status().message()); |
| } |
| return output; |
| } |
| |
| std::string DebugCommandShell::FormatAllRegisters(int core) const { |
| std::string output; |
| // Interrupt stack. |
| auto cheriot_state = |
| static_cast<CheriotState *>(core_access_[current_core_].state); |
| auto &info_list = cheriot_state->interrupt_info_list(); |
| int count = 0; |
| if (!info_list.empty()) { |
| absl::StrAppend(&output, "Interrupt stack:\n"); |
| for (auto iter = info_list.rbegin(); iter != info_list.rend(); ++iter) { |
| auto const &info = *iter; |
| absl::StrAppend(&output, "[", count++, "] ", |
| info.is_interrupt ? "interrupt" : "exception", " ", |
| info.is_interrupt ? GetInterruptDescription(info) |
| : GetExceptionDescription(info)); |
| } |
| absl::StrAppend(&output, "\n"); |
| } |
| // Registers. |
| for (auto const ®_name : reg_vector_) { |
| absl::StrAppend(&output, FormatRegister(current_core_, reg_name), "\n"); |
| } |
| return output; |
| } |
| |
| // Action point methods. |
| std::string DebugCommandShell::ListActionPoints() { |
| std::string output; |
| auto &action_map = core_action_point_info_[current_core_]; |
| for (auto const &[local_id, info] : action_map) { |
| absl::StrAppend( |
| &output, |
| absl::StrFormat("%02d [0x%08lx] %8s %s\n", local_id, info.address, |
| info.is_enabled ? "enabled" : "disabled", info.name)); |
| } |
| return output; |
| } |
| |
| std::string DebugCommandShell::EnableActionPointN( |
| const std::string &index_str) { |
| auto res = riscv::internal::stoull(index_str, nullptr, 10); |
| if (!res.ok()) { |
| return std::string(res.status().message()); |
| } |
| auto &action_map = core_action_point_info_[current_core_]; |
| int index = res.value(); |
| auto it = action_map.find(index); |
| if (it == action_map.end()) { |
| return absl::StrCat("Action point ", index, " not found"); |
| } |
| auto &info = it->second; |
| if (info.is_enabled) { |
| return absl::StrCat("Action point ", index, " is already enabled"); |
| } |
| info.is_enabled = true; |
| auto *dbg_if = core_access_[current_core_].debug_interface; |
| auto *cheriot_dbg_if = static_cast<CheriotDebugInterface *>(dbg_if); |
| auto status = cheriot_dbg_if->EnableAction(info.address, info.id); |
| if (!status.ok()) { |
| return absl::StrCat("Error: ", status.message()); |
| } |
| return ""; |
| } |
| |
| std::string DebugCommandShell::DisableActionPointN( |
| const std::string &index_str) { |
| auto res = riscv::internal::stoull(index_str, nullptr, 10); |
| if (!res.ok()) { |
| return std::string(res.status().message()); |
| } |
| auto &action_map = core_action_point_info_[current_core_]; |
| int index = res.value(); |
| auto it = action_map.find(index); |
| if (it == action_map.end()) { |
| return absl::StrCat("Action point ", index, " not found"); |
| } |
| auto &info = it->second; |
| if (!info.is_enabled) { |
| return absl::StrCat("Action point ", index, " is already disabled"); |
| } |
| info.is_enabled = false; |
| auto *dbg_if = core_access_[current_core_].debug_interface; |
| auto *cheriot_dbg_if = static_cast<CheriotDebugInterface *>(dbg_if); |
| auto status = cheriot_dbg_if->DisableAction(info.address, info.id); |
| if (!status.ok()) { |
| return absl::StrCat("Error: ", status.message()); |
| } |
| return ""; |
| } |
| |
| std::string DebugCommandShell::ClearActionPointN(const std::string &index_str) { |
| auto res = riscv::internal::stoull(index_str, nullptr, 10); |
| if (!res.ok()) { |
| return std::string(res.status().message()); |
| } |
| auto &action_map = core_action_point_info_[current_core_]; |
| int index = res.value(); |
| auto it = action_map.find(index); |
| if (it == action_map.end()) { |
| return absl::StrCat("Action point ", index, " not found"); |
| } |
| auto &info = it->second; |
| auto *dbg_if = core_access_[current_core_].debug_interface; |
| auto *cheriot_dbg_if = static_cast<CheriotDebugInterface *>(dbg_if); |
| auto status = cheriot_dbg_if->ClearActionPoint(info.address, info.id); |
| if (!status.ok()) { |
| return absl::StrCat("Error: ", status.message()); |
| } |
| action_map.erase(it); |
| return ""; |
| } |
| |
| std::string DebugCommandShell::ClearAllActionPoints() { |
| std::string output; |
| auto *dbg_if = core_access_[current_core_].debug_interface; |
| auto *cheriot_dbg_if = static_cast<CheriotDebugInterface *>(dbg_if); |
| for (auto &[local_id, info] : core_action_point_info_[current_core_]) { |
| auto status = cheriot_dbg_if->ClearActionPoint(info.address, info.id); |
| if (!status.ok()) { |
| absl::StrAppend(&output, "Error: ", status.message()); |
| } |
| } |
| return output; |
| } |
| |
| absl::Status DebugCommandShell::SetActionPoint( |
| uint64_t address, std::string name, |
| absl::AnyInvocable<void(uint64_t, int)> function) { |
| auto *dbg_if = core_access_[current_core_].debug_interface; |
| auto *cheriot_dbg_if = static_cast<CheriotDebugInterface *>(dbg_if); |
| auto result = cheriot_dbg_if->SetActionPoint(address, std::move(function)); |
| if (!result.ok()) { |
| return absl::InternalError(result.status().message()); |
| } |
| int id = result.value(); |
| int local_id = core_action_point_id_[current_core_]++; |
| auto &action_map = core_action_point_info_[current_core_]; |
| action_map.emplace(local_id, ActionPointInfo{address, id, name, true}); |
| return absl::OkStatus(); |
| } |
| |
| std::string DebugCommandShell::GetInterruptDescription( |
| const InterruptInfo &info) const { |
| std::string output; |
| if (!info.is_interrupt) return output; |
| switch (info.cause & 0x7fff'ffff) { |
| case 0: |
| absl::StrAppend(&output, "User software interrupt"); |
| break; |
| case 1: |
| absl::StrAppend(&output, "Supervisor software interrupt"); |
| break; |
| case 3: |
| absl::StrAppend(&output, "Machine software interrupt"); |
| break; |
| case 4: |
| absl::StrAppend(&output, "User timer interrupt"); |
| break; |
| case 5: |
| absl::StrAppend(&output, "Supervisor timer interrupt"); |
| break; |
| case 7: |
| absl::StrAppend(&output, "Machine timer interrupt"); |
| break; |
| case 8: |
| absl::StrAppend(&output, "User external interrupt"); |
| break; |
| case 9: |
| absl::StrAppend(&output, "Supervisor external interrupt"); |
| break; |
| case 11: |
| absl::StrAppend(&output, "Machine external interrupt"); |
| break; |
| default: |
| absl::StrAppend(&output, "Error - Unknown interrupt"); |
| break; |
| } |
| absl::StrAppend(&output, "\n"); |
| return output; |
| } |
| |
| std::string DebugCommandShell::GetExceptionDescription( |
| const InterruptInfo &info) const { |
| std::string output; |
| if (info.is_interrupt) return output; |
| absl::StrAppend(&output, " Exception taken at ", absl::Hex(info.epc), ": "); |
| switch (info.cause) { |
| case 0: |
| absl::StrAppend(&output, "Instruction address misaligned: "); |
| absl::StrAppend(&output, ": ", absl::Hex(info.tval)); |
| break; |
| case 1: |
| absl::StrAppend(&output, "Instruction access fault"); |
| break; |
| case 2: |
| absl::StrAppend(&output, "Illegal instruction"); |
| absl::StrAppend(&output, " opcode: ", absl::Hex(info.tval)); |
| break; |
| case 3: |
| absl::StrAppend(&output, "Breakpoint instruction"); |
| break; |
| case 4: |
| absl::StrAppend(&output, "Load address misaligned"); |
| absl::StrAppend(&output, ": ", absl::Hex(info.tval)); |
| break; |
| case 5: |
| absl::StrAppend(&output, "Load access fault"); |
| break; |
| case 6: |
| absl::StrAppend(&output, "Store/AMO address misaligned"); |
| absl::StrAppend(&output, ": ", absl::Hex(info.tval)); |
| break; |
| case 7: |
| absl::StrAppend(&output, "Store/AMO access fault"); |
| break; |
| case 8: |
| absl::StrAppend(&output, "Environment call from U-mode"); |
| break; |
| case 9: |
| absl::StrAppend(&output, "Environment call from S-mode"); |
| break; |
| case 11: |
| absl::StrAppend(&output, "Environment call from M-mode"); |
| break; |
| case 12: |
| absl::StrAppend(&output, "Instruction page fault"); |
| absl::StrAppend(&output, ": ", absl::Hex(info.tval)); |
| break; |
| case 13: |
| absl::StrAppend(&output, "Load page fault"); |
| absl::StrAppend(&output, ": ", absl::Hex(info.tval)); |
| break; |
| case 15: |
| absl::StrAppend(&output, "Store/AMO page fault"); |
| absl::StrAppend(&output, ": ", absl::Hex(info.tval)); |
| break; |
| case 0x1c: { |
| absl::StrAppend(&output, "CHERI exception"); |
| switch (info.tval & 0x1f) { |
| case 0: |
| absl::StrAppend(&output, ": none??"); |
| break; |
| case 1: |
| absl::StrAppend(&output, ": bounds violation"); |
| break; |
| case 2: |
| absl::StrAppend(&output, ": tag violation"); |
| break; |
| case 3: |
| absl::StrAppend(&output, ": seal violation"); |
| break; |
| case 0x11: |
| absl::StrAppend(&output, ": PERMIT_EXECUTION violation"); |
| break; |
| case 0x12: |
| absl::StrAppend(&output, ": PERMIT_LOAD violation"); |
| break; |
| case 0x13: |
| absl::StrAppend(&output, ": PERMIT_STORE violation"); |
| break; |
| case 0x15: |
| absl::StrAppend(&output, ": PERMIT_STORE_CAPABILITY violation"); |
| break; |
| case 0x18: |
| absl::StrAppend(&output, |
| ": PERMIT_ACCESS_SYSTEM_REGISTERS violation"); |
| break; |
| default: |
| absl::StrAppend(&output, ": unknown cause"); |
| break; |
| } |
| int cap_indx = (info.tval >> 5) & 0x1f; |
| if (cap_indx < 16) |
| absl::StrAppend(&output, " c", cap_indx); |
| else if (cap_indx == 28) |
| absl::StrAppend(&output, " mtcc"); |
| else if (cap_indx == 29) |
| absl::StrAppend(&output, " mtdc"); |
| else if (cap_indx == 30) |
| absl::StrAppend(&output, " mscratchc"); |
| else if (cap_indx == 31) |
| absl::StrAppend(&output, " mepcc"); |
| else |
| absl::StrAppend(&output, " unknown capability"); |
| break; |
| } |
| default: |
| absl::StrAppend(&output, "Error - Unknown trap"); |
| break; |
| } |
| absl::StrAppend(&output, "\n"); |
| return output; |
| } |
| |
| } // namespace cheriot |
| } // namespace sim |
| } // namespace mpact |