mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-24 22:21:03 +00:00
Prepare web debugger (#247)
This commit is contained in:
1
src/.clang-format-ignore
Normal file
1
src/.clang-format-ignore
Normal file
@@ -0,0 +1 @@
|
||||
**/*.hxx
|
||||
@@ -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)
|
||||
|
||||
@@ -16,6 +16,7 @@ endif()
|
||||
|
||||
target_link_libraries(analyzer PRIVATE
|
||||
reflect
|
||||
debugger
|
||||
windows-emulator
|
||||
windows-gdb-stub
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
25
src/debugger/CMakeLists.txt
Normal file
25
src/debugger/CMakeLists.txt
Normal 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"
|
||||
)
|
||||
233
src/debugger/event_handler.cpp
Normal file
233
src/debugger/event_handler.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/debugger/event_handler.hpp
Normal file
21
src/debugger/event_handler.hpp
Normal 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
81
src/debugger/events.fbs
Normal 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;
|
||||
2070
src/debugger/events_generated.hxx
Normal file
2070
src/debugger/events_generated.hxx
Normal file
File diff suppressed because it is too large
Load Diff
76
src/debugger/message_transmitter.cpp
Normal file
76
src/debugger/message_transmitter.cpp
Normal 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
|
||||
}
|
||||
}
|
||||
11
src/debugger/message_transmitter.hpp
Normal file
11
src/debugger/message_transmitter.hpp
Normal 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();
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -169,6 +169,7 @@ namespace
|
||||
}
|
||||
|
||||
thread.apc_alertable = false;
|
||||
win_emu.callbacks.on_thread_switch();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user