Prepare web debugger (#247)

This commit is contained in:
Maurice Heumann
2025-04-29 09:51:15 +02:00
committed by GitHub
49 changed files with 4768 additions and 47 deletions

1
src/.clang-format-ignore Normal file
View File

@@ -0,0 +1 @@
**/*.hxx

View File

@@ -9,6 +9,7 @@ momo_targets_set_folder("backends" ${BACKEND_TARGETS})
if (NOT MOMO_BUILD_AS_LIBRARY)
add_subdirectory(analyzer)
add_subdirectory(debugger)
add_subdirectory(fuzzing-engine)
add_subdirectory(fuzzer)
add_subdirectory(windows-emulator-test)

View File

@@ -16,6 +16,7 @@ endif()
target_link_libraries(analyzer PRIVATE
reflect
debugger
windows-emulator
windows-gdb-stub
)

View File

@@ -6,6 +6,10 @@
#include "object_watching.hpp"
#include "snapshot.hpp"
#ifdef OS_EMSCRIPTEN
#include <event_handler.hpp>
#endif
#include <utils/interupt_handler.hpp>
#include <cstdio>
@@ -249,6 +253,14 @@ namespace
bool run(const analysis_options& options, const std::span<const std::string_view> args)
{
const auto win_emu = setup_emulator(options, args);
#ifdef OS_EMSCRIPTEN
win_emu->callbacks.on_thread_switch = [&] {
debugger::event_context c{.win_emu = *win_emu};
debugger::handle_events(c); //
};
#endif
win_emu->log.log("Using emulator: %s\n", win_emu->emu().get_name().c_str());
(void)&watch_system_objects;

View File

@@ -0,0 +1,25 @@
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
*.cpp
*.hpp
*.rc
)
list(SORT SRC_FILES)
add_library(debugger ${SRC_FILES})
momo_assign_source_group(${SRC_FILES})
target_link_libraries(debugger PRIVATE
windows-emulator
flatbuffers
base64
)
target_include_directories(debugger INTERFACE "${CMAKE_CURRENT_LIST_DIR}")
add_custom_target(generate-flatbuffer
DEPENDS flatc
COMMAND "$<TARGET_FILE:flatc>" --gen-mutable --gen-object-api --filename-ext hxx --cpp -o "${CMAKE_CURRENT_LIST_DIR}" "${CMAKE_CURRENT_LIST_DIR}/events.fbs"
COMMAND "$<TARGET_FILE:flatc>" --gen-mutable --gen-object-api --ts -o "${PROJECT_SOURCE_DIR}/page/src/fb" "${CMAKE_CURRENT_LIST_DIR}/events.fbs"
)

View File

@@ -0,0 +1,233 @@
#include "event_handler.hpp"
#include "message_transmitter.hpp"
#include <base64.hpp>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
#include "events_generated.hxx"
#ifdef _MSC_VER
#pragma warning(pop)
#endif
namespace debugger
{
namespace
{
std::optional<Debugger::DebugEventT> receive_event()
{
const auto message = receive_message();
if (message.empty())
{
return std::nullopt;
}
const auto data = base64::from_base64(message);
flatbuffers::Verifier verifier(reinterpret_cast<const uint8_t*>(data.data()), data.size());
if (!Debugger::VerifyDebugEventBuffer(verifier))
{
return std::nullopt;
}
Debugger::DebugEventT e{};
Debugger::GetDebugEvent(data.data())->UnPackTo(&e);
return {std::move(e)};
}
void send_event(const Debugger::DebugEventT& event)
{
flatbuffers::FlatBufferBuilder fbb{};
fbb.Finish(Debugger::DebugEvent::Pack(fbb, &event));
const std::string_view buffer(reinterpret_cast<const char*>(fbb.GetBufferPointer()), fbb.GetSize());
const auto message = base64::to_base64(buffer);
send_message(message);
}
template <typename T>
requires(!std::is_same_v<std::remove_cvref_t<T>, Debugger::DebugEventT>)
void send_event(T event)
{
Debugger::DebugEventT e{};
e.event.Set(std::move(event));
send_event(e);
}
Debugger::State translate_state(const emulation_state state)
{
switch (state)
{
case emulation_state::paused:
return Debugger::State_Paused;
case emulation_state::none:
case emulation_state::running:
return Debugger::State_Running;
default:
return Debugger::State_None;
}
}
void handle_get_state(const event_context& c)
{
Debugger::GetStateResponseT response{};
response.state = translate_state(c.state);
send_event(response);
}
void handle_read_memory(const event_context& c, const Debugger::ReadMemoryRequestT& request)
{
std::vector<uint8_t> buffer{};
buffer.resize(request.size);
const auto res = c.win_emu.memory.try_read_memory(request.address, buffer.data(), buffer.size());
Debugger::ReadMemoryResponseT response{};
response.address = request.address;
if (res)
{
response.data = std::move(buffer);
}
send_event(std::move(response));
}
void handle_write_memory(const event_context& c, const Debugger::WriteMemoryRequestT& request)
{
bool success{};
try
{
c.win_emu.memory.write_memory(request.address, request.data.data(), request.data.size());
success = true;
}
catch (...)
{
success = false;
}
Debugger::WriteMemoryResponseT response{};
response.address = request.address;
response.size = static_cast<uint32_t>(request.data.size());
response.success = success;
send_event(response);
}
void handle_read_register(const event_context& c, const Debugger::ReadRegisterRequestT& request)
{
std::array<uint8_t, 512> buffer{};
const auto res = c.win_emu.emu().read_register(static_cast<x86_register>(request.register_), buffer.data(),
buffer.size());
const auto size = std::min(buffer.size(), res);
Debugger::ReadRegisterResponseT response{};
response.register_ = request.register_;
response.data.assign(buffer.data(), buffer.data() + size);
send_event(std::move(response));
}
void handle_write_register(const event_context& c, const Debugger::WriteRegisterRequestT& request)
{
bool success{};
size_t size = request.data.size();
try
{
size = c.win_emu.emu().write_register(static_cast<x86_register>(request.register_), request.data.data(),
request.data.size());
success = true;
}
catch (...)
{
success = false;
}
Debugger::WriteRegisterResponseT response{};
response.register_ = request.register_;
response.size = static_cast<uint32_t>(size);
response.success = success;
send_event(response);
}
void handle_event(event_context& c, const Debugger::DebugEventT& e)
{
switch (e.event.type)
{
case Debugger::Event_PauseRequest:
c.state = emulation_state::paused;
break;
case Debugger::Event_RunRequest:
c.state = emulation_state::running;
break;
case Debugger::Event_GetStateRequest:
handle_get_state(c);
break;
case Debugger::Event_ReadMemoryRequest:
handle_read_memory(c, *e.event.AsReadMemoryRequest());
break;
case Debugger::Event_WriteMemoryRequest:
handle_write_memory(c, *e.event.AsWriteMemoryRequest());
break;
case Debugger::Event_ReadRegisterRequest:
handle_read_register(c, *e.event.AsReadRegisterRequest());
break;
case Debugger::Event_WriteRegisterRequest:
handle_write_register(c, *e.event.AsWriteRegisterRequest());
break;
default:
break;
}
}
}
void handle_events_once(event_context& c)
{
while (true)
{
suspend_execution(0ms);
const auto e = receive_event();
if (!e.has_value())
{
break;
}
handle_event(c, *e);
}
}
void handle_events(event_context& c)
{
while (true)
{
handle_events_once(c);
if (c.state != emulation_state::paused)
{
break;
}
suspend_execution(2ms);
}
}
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <windows_emulator.hpp>
namespace debugger
{
enum class emulation_state
{
none,
running,
paused,
};
struct event_context
{
windows_emulator& win_emu;
emulation_state state{emulation_state::none};
};
void handle_events(event_context& c);
}

81
src/debugger/events.fbs Normal file
View File

@@ -0,0 +1,81 @@
namespace Debugger;
table GetStateRequest {}
enum State : uint32 {
None = 0,
Running,
Paused,
}
table GetStateResponse {
state: State;
}
table PauseRequest {}
table RunRequest {
single_step: bool;
}
table WriteMemoryRequest {
address: uint64;
data: [ubyte];
}
table WriteMemoryResponse {
address: uint64;
size: uint32;
success: bool;
}
table ReadMemoryRequest {
address: uint64;
size: uint32;
}
table ReadMemoryResponse {
address: uint64;
data: [ubyte];
}
table WriteRegisterRequest {
register: uint32;
data: [ubyte];
}
table WriteRegisterResponse {
register: uint32;
size: uint32;
success: bool;
}
table ReadRegisterRequest {
register: uint32;
}
table ReadRegisterResponse {
register: uint32;
data: [ubyte];
}
union Event {
PauseRequest,
RunRequest,
GetStateRequest,
GetStateResponse,
WriteMemoryRequest,
WriteMemoryResponse,
ReadMemoryRequest,
ReadMemoryResponse,
WriteRegisterRequest,
WriteRegisterResponse,
ReadRegisterRequest,
ReadRegisterResponse,
}
table DebugEvent {
event: Event;
}
root_type DebugEvent;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
#include "message_transmitter.hpp"
#include <platform/compiler.hpp>
#include <thread>
#include <utils/finally.hpp>
#ifdef OS_EMSCRIPTEN
#include <emscripten.h>
#endif
using namespace std::literals;
namespace debugger
{
void send_message(const std::string& message)
{
#ifdef OS_EMSCRIPTEN
// clang-format off
EM_ASM_({
handleMessage(UTF8ToString($0));
}, message.c_str());
// clang-format on
#else
(void)message;
#endif
}
std::string receive_message()
{
#ifdef OS_EMSCRIPTEN
// clang-format off
auto* ptr = EM_ASM_PTR({
var message = getMessageFromQueue();
if (!message || message.length == 0)
{
return null;
}
const length = lengthBytesUTF8(message) + 1;
const buffer = _malloc(length);
stringToUTF8(message, buffer, length);
return buffer;
});
// clang-format on
if (!ptr)
{
return {};
}
const auto _ = utils::finally([&] {
free(ptr); //
});
return {reinterpret_cast<const char*>(ptr)};
#else
return {};
#endif
}
void suspend_execution(const std::chrono::milliseconds ms)
{
#ifdef OS_EMSCRIPTEN
emscripten_sleep(static_cast<uint32_t>(ms.count()));
#else
if (ms > 0ms)
{
std::this_thread::sleep_for(ms);
}
else
{
std::this_thread::yield();
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include <chrono>
#include <string>
namespace debugger
{
void suspend_execution(std::chrono::milliseconds ms = std::chrono::milliseconds(0));
void send_message(const std::string& message);
std::string receive_message();
}

View File

@@ -34,6 +34,7 @@ struct process_context
{
utils::optional_function<void(handle h, emulator_thread& thr)> on_create_thread{};
utils::optional_function<void(handle h, emulator_thread& thr)> on_thread_terminated{};
utils::optional_function<void()> on_thread_switch{};
};
struct atom_entry

View File

@@ -169,6 +169,7 @@ namespace
}
thread.apc_alertable = false;
win_emu.callbacks.on_thread_switch();
return true;
}