early-access version 3118
This commit is contained in:
		| @@ -1,7 +1,7 @@ | ||||
| yuzu emulator early access | ||||
| ============= | ||||
|  | ||||
| This is the source code for early-access 3117. | ||||
| This is the source code for early-access 3118. | ||||
|  | ||||
| ## Legal Notice | ||||
|  | ||||
|   | ||||
| @@ -27,12 +27,21 @@ static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) { | ||||
|                 const u8* buffer_start = reinterpret_cast<const u8*>(&buffer); | ||||
|                 std::span<const u8> received_data{buffer_start, buffer_start + bytes_read}; | ||||
|                 c(received_data); | ||||
|                 AsyncReceiveInto(r, buffer, c); | ||||
|             } | ||||
|  | ||||
|             AsyncReceiveInto(r, buffer, c); | ||||
|         }); | ||||
| } | ||||
|  | ||||
| template <typename Callback> | ||||
| static void AsyncAccept(boost::asio::ip::tcp::acceptor& acceptor, Callback&& c) { | ||||
|     acceptor.async_accept([&, c](const boost::system::error_code& error, auto&& peer_socket) { | ||||
|         if (!error.failed()) { | ||||
|             c(peer_socket); | ||||
|             AsyncAccept(acceptor, c); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
| template <typename Readable, typename Buffer> | ||||
| static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) { | ||||
|     static_assert(std::is_trivial_v<Buffer>); | ||||
| @@ -59,9 +68,7 @@ namespace Core { | ||||
|  | ||||
| class DebuggerImpl : public DebuggerBackend { | ||||
| public: | ||||
|     explicit DebuggerImpl(Core::System& system_, u16 port) | ||||
|         : system{system_}, signal_pipe{io_context}, client_socket{io_context} { | ||||
|         frontend = std::make_unique<GDBStub>(*this, system); | ||||
|     explicit DebuggerImpl(Core::System& system_, u16 port) : system{system_} { | ||||
|         InitializeServer(port); | ||||
|     } | ||||
|  | ||||
| @@ -70,39 +77,42 @@ public: | ||||
|     } | ||||
|  | ||||
|     bool SignalDebugger(SignalInfo signal_info) { | ||||
|         { | ||||
|             std::scoped_lock lk{connection_lock}; | ||||
|         std::scoped_lock lk{connection_lock}; | ||||
|  | ||||
|             if (stopped) { | ||||
|                 // Do not notify the debugger about another event. | ||||
|                 // It should be ignored. | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             // Set up the state. | ||||
|             stopped = true; | ||||
|             info = signal_info; | ||||
|         if (stopped || !state) { | ||||
|             // Do not notify the debugger about another event. | ||||
|             // It should be ignored. | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // Set up the state. | ||||
|         stopped = true; | ||||
|         state->info = signal_info; | ||||
|  | ||||
|         // Write a single byte into the pipe to wake up the debug interface. | ||||
|         boost::asio::write(signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped))); | ||||
|         boost::asio::write(state->signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped))); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     // These functions are callbacks from the frontend, and the lock will be held. | ||||
|     // There is no need to relock it. | ||||
|  | ||||
|     std::span<const u8> ReadFromClient() override { | ||||
|         return ReceiveInto(client_socket, client_data); | ||||
|         return ReceiveInto(state->client_socket, state->client_data); | ||||
|     } | ||||
|  | ||||
|     void WriteToClient(std::span<const u8> data) override { | ||||
|         boost::asio::write(client_socket, boost::asio::buffer(data.data(), data.size_bytes())); | ||||
|         boost::asio::write(state->client_socket, | ||||
|                            boost::asio::buffer(data.data(), data.size_bytes())); | ||||
|     } | ||||
|  | ||||
|     void SetActiveThread(Kernel::KThread* thread) override { | ||||
|         active_thread = thread; | ||||
|         state->active_thread = thread; | ||||
|     } | ||||
|  | ||||
|     Kernel::KThread* GetActiveThread() override { | ||||
|         return active_thread; | ||||
|         return state->active_thread; | ||||
|     } | ||||
|  | ||||
| private: | ||||
| @@ -113,65 +123,78 @@ private: | ||||
|  | ||||
|         // Run the connection thread. | ||||
|         connection_thread = std::jthread([&, port](std::stop_token stop_token) { | ||||
|             Common::SetCurrentThreadName("Debugger"); | ||||
|  | ||||
|             try { | ||||
|                 // Initialize the listening socket and accept a new client. | ||||
|                 tcp::endpoint endpoint{boost::asio::ip::address_v4::any(), port}; | ||||
|                 tcp::acceptor acceptor{io_context, endpoint}; | ||||
|  | ||||
|                 acceptor.async_accept(client_socket, [](const auto&) {}); | ||||
|                 io_context.run_one(); | ||||
|                 io_context.restart(); | ||||
|                 AsyncAccept(acceptor, [&](auto&& peer) { AcceptConnection(std::move(peer)); }); | ||||
|  | ||||
|                 if (stop_token.stop_requested()) { | ||||
|                     return; | ||||
|                 while (!stop_token.stop_requested() && io_context.run()) { | ||||
|                 } | ||||
|  | ||||
|                 ThreadLoop(stop_token); | ||||
|             } catch (const std::exception& ex) { | ||||
|                 LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what()); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     void AcceptConnection(boost::asio::ip::tcp::socket&& peer) { | ||||
|         LOG_INFO(Debug_GDBStub, "Accepting new peer connection"); | ||||
|  | ||||
|         std::scoped_lock lk{connection_lock}; | ||||
|  | ||||
|         // Ensure everything is stopped. | ||||
|         PauseEmulation(); | ||||
|  | ||||
|         // Set up the new frontend. | ||||
|         frontend = std::make_unique<GDBStub>(*this, system); | ||||
|  | ||||
|         // Set the new state. This will tear down any existing state. | ||||
|         state = ConnectionState{ | ||||
|             .client_socket{std::move(peer)}, | ||||
|             .signal_pipe{io_context}, | ||||
|             .info{}, | ||||
|             .active_thread{}, | ||||
|             .client_data{}, | ||||
|             .pipe_data{}, | ||||
|         }; | ||||
|  | ||||
|         // Set up the client signals for new data. | ||||
|         AsyncReceiveInto(state->signal_pipe, state->pipe_data, [&](auto d) { PipeData(d); }); | ||||
|         AsyncReceiveInto(state->client_socket, state->client_data, [&](auto d) { ClientData(d); }); | ||||
|  | ||||
|         // Set the active thread. | ||||
|         UpdateActiveThread(); | ||||
|  | ||||
|         // Set up the frontend. | ||||
|         frontend->Connected(); | ||||
|     } | ||||
|  | ||||
|     void ShutdownServer() { | ||||
|         connection_thread.request_stop(); | ||||
|         io_context.stop(); | ||||
|         connection_thread.join(); | ||||
|     } | ||||
|  | ||||
|     void ThreadLoop(std::stop_token stop_token) { | ||||
|         Common::SetCurrentThreadName("Debugger"); | ||||
|  | ||||
|         // Set up the client signals for new data. | ||||
|         AsyncReceiveInto(signal_pipe, pipe_data, [&](auto d) { PipeData(d); }); | ||||
|         AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); }); | ||||
|  | ||||
|         // Set the active thread. | ||||
|         UpdateActiveThread(); | ||||
|  | ||||
|         // Set up the frontend. | ||||
|         frontend->Connected(); | ||||
|  | ||||
|         // Main event loop. | ||||
|         while (!stop_token.stop_requested() && io_context.run()) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void PipeData(std::span<const u8> data) { | ||||
|         switch (info.type) { | ||||
|         std::scoped_lock lk{connection_lock}; | ||||
|  | ||||
|         switch (state->info.type) { | ||||
|         case SignalType::Stopped: | ||||
|         case SignalType::Watchpoint: | ||||
|             // Stop emulation. | ||||
|             PauseEmulation(); | ||||
|  | ||||
|             // Notify the client. | ||||
|             active_thread = info.thread; | ||||
|             state->active_thread = state->info.thread; | ||||
|             UpdateActiveThread(); | ||||
|  | ||||
|             if (info.type == SignalType::Watchpoint) { | ||||
|                 frontend->Watchpoint(active_thread, *info.watchpoint); | ||||
|             if (state->info.type == SignalType::Watchpoint) { | ||||
|                 frontend->Watchpoint(state->active_thread, *state->info.watchpoint); | ||||
|             } else { | ||||
|                 frontend->Stopped(active_thread); | ||||
|                 frontend->Stopped(state->active_thread); | ||||
|             } | ||||
|  | ||||
|             break; | ||||
| @@ -179,8 +202,8 @@ private: | ||||
|             frontend->ShuttingDown(); | ||||
|  | ||||
|             // Wait for emulation to shut down gracefully now. | ||||
|             signal_pipe.close(); | ||||
|             client_socket.shutdown(boost::asio::socket_base::shutdown_both); | ||||
|             state->signal_pipe.close(); | ||||
|             state->client_socket.shutdown(boost::asio::socket_base::shutdown_both); | ||||
|             LOG_INFO(Debug_GDBStub, "Shut down server"); | ||||
|  | ||||
|             break; | ||||
| @@ -188,17 +211,16 @@ private: | ||||
|     } | ||||
|  | ||||
|     void ClientData(std::span<const u8> data) { | ||||
|         std::scoped_lock lk{connection_lock}; | ||||
|  | ||||
|         const auto actions{frontend->ClientData(data)}; | ||||
|         for (const auto action : actions) { | ||||
|             switch (action) { | ||||
|             case DebuggerAction::Interrupt: { | ||||
|                 { | ||||
|                     std::scoped_lock lk{connection_lock}; | ||||
|                     stopped = true; | ||||
|                 } | ||||
|                 stopped = true; | ||||
|                 PauseEmulation(); | ||||
|                 UpdateActiveThread(); | ||||
|                 frontend->Stopped(active_thread); | ||||
|                 frontend->Stopped(state->active_thread); | ||||
|                 break; | ||||
|             } | ||||
|             case DebuggerAction::Continue: | ||||
| @@ -206,15 +228,15 @@ private: | ||||
|                 break; | ||||
|             case DebuggerAction::StepThreadUnlocked: | ||||
|                 MarkResumed([&] { | ||||
|                     active_thread->SetStepState(Kernel::StepState::StepPending); | ||||
|                     active_thread->Resume(Kernel::SuspendType::Debug); | ||||
|                     ResumeEmulation(active_thread); | ||||
|                     state->active_thread->SetStepState(Kernel::StepState::StepPending); | ||||
|                     state->active_thread->Resume(Kernel::SuspendType::Debug); | ||||
|                     ResumeEmulation(state->active_thread); | ||||
|                 }); | ||||
|                 break; | ||||
|             case DebuggerAction::StepThreadLocked: { | ||||
|                 MarkResumed([&] { | ||||
|                     active_thread->SetStepState(Kernel::StepState::StepPending); | ||||
|                     active_thread->Resume(Kernel::SuspendType::Debug); | ||||
|                     state->active_thread->SetStepState(Kernel::StepState::StepPending); | ||||
|                     state->active_thread->Resume(Kernel::SuspendType::Debug); | ||||
|                 }); | ||||
|                 break; | ||||
|             } | ||||
| @@ -254,15 +276,14 @@ private: | ||||
|     template <typename Callback> | ||||
|     void MarkResumed(Callback&& cb) { | ||||
|         Kernel::KScopedSchedulerLock sl{system.Kernel()}; | ||||
|         std::scoped_lock cl{connection_lock}; | ||||
|         stopped = false; | ||||
|         cb(); | ||||
|     } | ||||
|  | ||||
|     void UpdateActiveThread() { | ||||
|         const auto& threads{ThreadList()}; | ||||
|         if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) { | ||||
|             active_thread = threads[0]; | ||||
|         if (std::find(threads.begin(), threads.end(), state->active_thread) == threads.end()) { | ||||
|             state->active_thread = threads[0]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -274,18 +295,22 @@ private: | ||||
|     System& system; | ||||
|     std::unique_ptr<DebuggerFrontend> frontend; | ||||
|  | ||||
|     boost::asio::io_context io_context; | ||||
|     std::jthread connection_thread; | ||||
|     std::mutex connection_lock; | ||||
|     boost::asio::io_context io_context; | ||||
|     boost::process::async_pipe signal_pipe; | ||||
|     boost::asio::ip::tcp::socket client_socket; | ||||
|  | ||||
|     SignalInfo info; | ||||
|     Kernel::KThread* active_thread; | ||||
|     bool pipe_data; | ||||
|     bool stopped; | ||||
|     struct ConnectionState { | ||||
|         boost::asio::ip::tcp::socket client_socket; | ||||
|         boost::process::async_pipe signal_pipe; | ||||
|  | ||||
|     std::array<u8, 4096> client_data; | ||||
|         SignalInfo info; | ||||
|         Kernel::KThread* active_thread; | ||||
|         std::array<u8, 4096> client_data; | ||||
|         bool pipe_data; | ||||
|     }; | ||||
|  | ||||
|     std::optional<ConnectionState> state{}; | ||||
|     bool stopped{}; | ||||
| }; | ||||
|  | ||||
| Debugger::Debugger(Core::System& system, u16 port) { | ||||
|   | ||||
| @@ -606,6 +606,8 @@ void GDBStub::HandleQuery(std::string_view command) { | ||||
|     } else if (command.starts_with("StartNoAckMode")) { | ||||
|         no_ack = true; | ||||
|         SendReply(GDB_STUB_REPLY_OK); | ||||
|     } else if (command.starts_with("Rcmd,")) { | ||||
|         HandleRcmd(Common::HexStringToVector(command.substr(5), false)); | ||||
|     } else { | ||||
|         SendReply(GDB_STUB_REPLY_EMPTY); | ||||
|     } | ||||
| @@ -645,6 +647,155 @@ void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>& | ||||
|     } | ||||
| } | ||||
|  | ||||
| constexpr std::array<std::pair<const char*, Kernel::Svc::MemoryState>, 22> MemoryStateNames{{ | ||||
|     {"----- Free -----", Kernel::Svc::MemoryState::Free}, | ||||
|     {"Io              ", Kernel::Svc::MemoryState::Io}, | ||||
|     {"Static          ", Kernel::Svc::MemoryState::Static}, | ||||
|     {"Code            ", Kernel::Svc::MemoryState::Code}, | ||||
|     {"CodeData        ", Kernel::Svc::MemoryState::CodeData}, | ||||
|     {"Normal          ", Kernel::Svc::MemoryState::Normal}, | ||||
|     {"Shared          ", Kernel::Svc::MemoryState::Shared}, | ||||
|     {"AliasCode       ", Kernel::Svc::MemoryState::AliasCode}, | ||||
|     {"AliasCodeData   ", Kernel::Svc::MemoryState::AliasCodeData}, | ||||
|     {"Ipc             ", Kernel::Svc::MemoryState::Ipc}, | ||||
|     {"Stack           ", Kernel::Svc::MemoryState::Stack}, | ||||
|     {"ThreadLocal     ", Kernel::Svc::MemoryState::ThreadLocal}, | ||||
|     {"Transfered      ", Kernel::Svc::MemoryState::Transfered}, | ||||
|     {"SharedTransfered", Kernel::Svc::MemoryState::SharedTransfered}, | ||||
|     {"SharedCode      ", Kernel::Svc::MemoryState::SharedCode}, | ||||
|     {"Inaccessible    ", Kernel::Svc::MemoryState::Inaccessible}, | ||||
|     {"NonSecureIpc    ", Kernel::Svc::MemoryState::NonSecureIpc}, | ||||
|     {"NonDeviceIpc    ", Kernel::Svc::MemoryState::NonDeviceIpc}, | ||||
|     {"Kernel          ", Kernel::Svc::MemoryState::Kernel}, | ||||
|     {"GeneratedCode   ", Kernel::Svc::MemoryState::GeneratedCode}, | ||||
|     {"CodeOut         ", Kernel::Svc::MemoryState::CodeOut}, | ||||
|     {"Coverage        ", Kernel::Svc::MemoryState::Coverage}, | ||||
| }}; | ||||
|  | ||||
| static constexpr const char* GetMemoryStateName(Kernel::Svc::MemoryState state) { | ||||
|     for (size_t i = 0; i < MemoryStateNames.size(); i++) { | ||||
|         if (std::get<1>(MemoryStateNames[i]) == state) { | ||||
|             return std::get<0>(MemoryStateNames[i]); | ||||
|         } | ||||
|     } | ||||
|     return "Unknown         "; | ||||
| } | ||||
|  | ||||
| static constexpr const char* GetMemoryPermissionString(const Kernel::Svc::MemoryInfo& info) { | ||||
|     if (info.state == Kernel::Svc::MemoryState::Free) { | ||||
|         return "   "; | ||||
|     } | ||||
|  | ||||
|     switch (info.permission) { | ||||
|     case Kernel::Svc::MemoryPermission::ReadExecute: | ||||
|         return "r-x"; | ||||
|     case Kernel::Svc::MemoryPermission::Read: | ||||
|         return "r--"; | ||||
|     case Kernel::Svc::MemoryPermission::ReadWrite: | ||||
|         return "rw-"; | ||||
|     default: | ||||
|         return "---"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static VAddr GetModuleEnd(Kernel::KPageTable& page_table, VAddr base) { | ||||
|     Kernel::Svc::MemoryInfo mem_info; | ||||
|     VAddr cur_addr{base}; | ||||
|  | ||||
|     // Expect: r-x Code (.text) | ||||
|     mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); | ||||
|     cur_addr = mem_info.base_address + mem_info.size; | ||||
|     if (mem_info.state != Kernel::Svc::MemoryState::Code || | ||||
|         mem_info.permission != Kernel::Svc::MemoryPermission::ReadExecute) { | ||||
|         return cur_addr - 1; | ||||
|     } | ||||
|  | ||||
|     // Expect: r-- Code (.rodata) | ||||
|     mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); | ||||
|     cur_addr = mem_info.base_address + mem_info.size; | ||||
|     if (mem_info.state != Kernel::Svc::MemoryState::Code || | ||||
|         mem_info.permission != Kernel::Svc::MemoryPermission::Read) { | ||||
|         return cur_addr - 1; | ||||
|     } | ||||
|  | ||||
|     // Expect: rw- CodeData (.data) | ||||
|     mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); | ||||
|     cur_addr = mem_info.base_address + mem_info.size; | ||||
|     return cur_addr - 1; | ||||
| } | ||||
|  | ||||
| void GDBStub::HandleRcmd(const std::vector<u8>& command) { | ||||
|     std::string_view command_str{reinterpret_cast<const char*>(&command[0]), command.size()}; | ||||
|     std::string reply; | ||||
|  | ||||
|     auto* process = system.CurrentProcess(); | ||||
|     auto& page_table = process->PageTable(); | ||||
|  | ||||
|     if (command_str == "get info") { | ||||
|         Loader::AppLoader::Modules modules; | ||||
|         system.GetAppLoader().ReadNSOModules(modules); | ||||
|  | ||||
|         reply = fmt::format("Process:     {:#x} ({})\n" | ||||
|                             "Program Id:  {:#018x}\n", | ||||
|                             process->GetProcessID(), process->GetName(), process->GetProgramID()); | ||||
|         reply += | ||||
|             fmt::format("Layout:\n" | ||||
|                         "  Alias: {:#012x} - {:#012x}\n" | ||||
|                         "  Heap:  {:#012x} - {:#012x}\n" | ||||
|                         "  Aslr:  {:#012x} - {:#012x}\n" | ||||
|                         "  Stack: {:#012x} - {:#012x}\n" | ||||
|                         "Modules:\n", | ||||
|                         page_table.GetAliasRegionStart(), page_table.GetAliasRegionEnd(), | ||||
|                         page_table.GetHeapRegionStart(), page_table.GetHeapRegionEnd(), | ||||
|                         page_table.GetAliasCodeRegionStart(), page_table.GetAliasCodeRegionEnd(), | ||||
|                         page_table.GetStackRegionStart(), page_table.GetStackRegionEnd()); | ||||
|  | ||||
|         for (const auto& [vaddr, name] : modules) { | ||||
|             reply += fmt::format("  {:#012x} - {:#012x} {}\n", vaddr, | ||||
|                                  GetModuleEnd(page_table, vaddr), name); | ||||
|         } | ||||
|     } else if (command_str == "get mappings") { | ||||
|         reply = "Mappings:\n"; | ||||
|         VAddr cur_addr = 0; | ||||
|  | ||||
|         while (true) { | ||||
|             using MemoryAttribute = Kernel::Svc::MemoryAttribute; | ||||
|  | ||||
|             auto mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); | ||||
|  | ||||
|             if (mem_info.state != Kernel::Svc::MemoryState::Inaccessible || | ||||
|                 mem_info.base_address + mem_info.size - 1 != std::numeric_limits<u64>::max()) { | ||||
|                 const char* state = GetMemoryStateName(mem_info.state); | ||||
|                 const char* perm = GetMemoryPermissionString(mem_info); | ||||
|  | ||||
|                 const char l = True(mem_info.attribute & MemoryAttribute::Locked) ? 'L' : '-'; | ||||
|                 const char i = True(mem_info.attribute & MemoryAttribute::IpcLocked) ? 'I' : '-'; | ||||
|                 const char d = True(mem_info.attribute & MemoryAttribute::DeviceShared) ? 'D' : '-'; | ||||
|                 const char u = True(mem_info.attribute & MemoryAttribute::Uncached) ? 'U' : '-'; | ||||
|  | ||||
|                 reply += | ||||
|                     fmt::format("  {:#012x} - {:#012x} {} {} {}{}{}{} [{}, {}]\n", | ||||
|                                 mem_info.base_address, mem_info.base_address + mem_info.size - 1, | ||||
|                                 perm, state, l, i, d, u, mem_info.ipc_count, mem_info.device_count); | ||||
|             } | ||||
|  | ||||
|             const uintptr_t next_address = mem_info.base_address + mem_info.size; | ||||
|             if (next_address <= cur_addr) { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             cur_addr = next_address; | ||||
|         } | ||||
|     } else if (command_str == "help") { | ||||
|         reply = "Commands:\n  get info\n  get mappings\n"; | ||||
|     } else { | ||||
|         reply = "Unknown command.\nCommands:\n  get info\n  get mappings\n"; | ||||
|     } | ||||
|  | ||||
|     std::span<const u8> reply_span{reinterpret_cast<u8*>(&reply.front()), reply.size()}; | ||||
|     SendReply(Common::HexToString(reply_span, false)); | ||||
| } | ||||
|  | ||||
| Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { | ||||
|     const auto& threads{system.GlobalSchedulerContext().GetThreadList()}; | ||||
|     for (auto* thread : threads) { | ||||
|   | ||||
| @@ -32,6 +32,7 @@ private: | ||||
|     void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions); | ||||
|     void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions); | ||||
|     void HandleQuery(std::string_view command); | ||||
|     void HandleRcmd(const std::vector<u8>& command); | ||||
|     void HandleBreakpointInsert(std::string_view command); | ||||
|     void HandleBreakpointRemove(std::string_view command); | ||||
|     std::vector<char>::const_iterator CommandEnd() const; | ||||
|   | ||||
| @@ -320,6 +320,9 @@ public: | ||||
|     constexpr VAddr GetAliasCodeRegionStart() const { | ||||
|         return m_alias_code_region_start; | ||||
|     } | ||||
|     constexpr VAddr GetAliasCodeRegionEnd() const { | ||||
|         return m_alias_code_region_end; | ||||
|     } | ||||
|     constexpr VAddr GetAliasCodeRegionSize() const { | ||||
|         return m_alias_code_region_end - m_alias_code_region_start; | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user