Ver Fonte

Merge pull request #9225 from liamwhite/debugger-instance

Debugger improvements
liamwhite há 3 anos atrás
pai
commit
040a01a5dd

+ 93 - 68
src/core/debugger/debugger.cpp

@@ -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 ShutdownServer() {
-        connection_thread.request_stop();
-        io_context.stop();
-        connection_thread.join();
-    }
+    void AcceptConnection(boost::asio::ip::tcp::socket&& peer) {
+        LOG_INFO(Debug_GDBStub, "Accepting new peer connection");
 
-    void ThreadLoop(std::stop_token stop_token) {
-        Common::SetCurrentThreadName("Debugger");
+        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(signal_pipe, pipe_data, [&](auto d) { PipeData(d); });
-        AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
+        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();
+    }
 
-        // Main event loop.
-        while (!stop_token.stop_requested() && io_context.run()) {
-        }
+    void ShutdownServer() {
+        connection_thread.request_stop();
+        io_context.stop();
+        connection_thread.join();
     }
 
     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;
+
+        SignalInfo info;
+        Kernel::KThread* active_thread;
+        std::array<u8, 4096> client_data;
+        bool pipe_data;
+    };
 
-    std::array<u8, 4096> client_data;
+    std::optional<ConnectionState> state{};
+    bool stopped{};
 };
 
 Debugger::Debugger(Core::System& system, u16 port) {

+ 151 - 0
src/core/debugger/gdbstub.cpp

@@ -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) {

+ 1 - 0
src/core/debugger/gdbstub.h

@@ -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;

+ 3 - 0
src/core/hle/kernel/k_page_table.h

@@ -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;
     }