mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-11 16:46:16 +00:00
Generate an output that the Tenet plugin can read
Generates a Tenet tracer output with the -t parameter. The original Tenet tracer plugin is outdated, so I made updates to support our emulator. The forked project below works well. https://github.com/maskelihileci/tenet/tree/master/plugins_sogen-support
This commit is contained in:
@@ -1,39 +1,39 @@
|
||||
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
|
||||
*.cpp
|
||||
*.hpp
|
||||
*.rc
|
||||
)
|
||||
|
||||
list(SORT SRC_FILES)
|
||||
|
||||
add_executable(analyzer ${SRC_FILES})
|
||||
|
||||
momo_assign_source_group(${SRC_FILES})
|
||||
|
||||
if(NOT MOMO_ENABLE_CLANG_TIDY)
|
||||
target_precompile_headers(analyzer PRIVATE std_include.hpp)
|
||||
endif()
|
||||
|
||||
target_link_libraries(analyzer PRIVATE
|
||||
reflect
|
||||
debugger
|
||||
windows-emulator
|
||||
windows-gdb-stub
|
||||
backend-selection
|
||||
)
|
||||
|
||||
set_property(GLOBAL PROPERTY VS_STARTUP_PROJECT analyzer)
|
||||
|
||||
momo_strip_target(analyzer)
|
||||
|
||||
set(ENV_PREFIX "$")
|
||||
set(ENV_SUFFIX "")
|
||||
|
||||
if(WIN)
|
||||
set(ENV_PREFIX "%")
|
||||
set(ENV_SUFFIX "%")
|
||||
endif()
|
||||
|
||||
add_test(NAME analyzer-test
|
||||
COMMAND "${PYTHON3_EXE}" "${CMAKE_CURRENT_LIST_DIR}/test.py"
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:analyzer>")
|
||||
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
|
||||
*.cpp
|
||||
*.hpp
|
||||
*.rc
|
||||
)
|
||||
|
||||
list(SORT SRC_FILES)
|
||||
|
||||
add_executable(analyzer ${SRC_FILES})
|
||||
|
||||
momo_assign_source_group(${SRC_FILES})
|
||||
|
||||
if(NOT MOMO_ENABLE_CLANG_TIDY)
|
||||
target_precompile_headers(analyzer PRIVATE std_include.hpp)
|
||||
endif()
|
||||
|
||||
target_link_libraries(analyzer PRIVATE
|
||||
reflect
|
||||
debugger
|
||||
windows-emulator
|
||||
windows-gdb-stub
|
||||
backend-selection
|
||||
)
|
||||
|
||||
set_property(GLOBAL PROPERTY VS_STARTUP_PROJECT analyzer)
|
||||
|
||||
momo_strip_target(analyzer)
|
||||
|
||||
set(ENV_PREFIX "$")
|
||||
set(ENV_SUFFIX "")
|
||||
|
||||
if(WIN)
|
||||
set(ENV_PREFIX "%")
|
||||
set(ENV_SUFFIX "%")
|
||||
endif()
|
||||
|
||||
add_test(NAME analyzer-test
|
||||
COMMAND "${PYTHON3_EXE}" "${CMAKE_CURRENT_LIST_DIR}/test.py"
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:analyzer>")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,43 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "reflect_type_info.hpp"
|
||||
#include <set>
|
||||
#include <cinttypes>
|
||||
|
||||
template <typename T>
|
||||
emulator_hook* watch_object(windows_emulator& emu, const std::set<std::string, std::less<>>& modules,
|
||||
emulator_object<T> object, const auto verbose)
|
||||
{
|
||||
const reflect_type_info<T> info{};
|
||||
|
||||
return emu.emu().hook_memory_read(
|
||||
object.value(), static_cast<size_t>(object.size()),
|
||||
[i = std::move(info), object, &emu, verbose, modules](const uint64_t address, const void*, size_t) {
|
||||
const auto rip = emu.emu().read_instruction_pointer();
|
||||
const auto* mod = emu.mod_manager.find_by_address(rip);
|
||||
const auto is_main_access = mod == emu.mod_manager.executable || modules.contains(mod->name);
|
||||
|
||||
if (!verbose && !is_main_access)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!verbose)
|
||||
{
|
||||
static std::unordered_set<uint64_t> logged_addresses{};
|
||||
if (is_main_access && !logged_addresses.insert(address).second)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto offset = address - object.value();
|
||||
const auto* mod_name = mod ? mod->name.c_str() : "<N/A>";
|
||||
const auto& type_name = i.get_type_name();
|
||||
const auto member_name = i.get_member_name(static_cast<size_t>(offset));
|
||||
|
||||
emu.log.print(is_main_access ? color::green : color::dark_gray,
|
||||
"Object access: %s - 0x%" PRIx64 " (%s) at 0x%" PRIx64 " (%s)\n", type_name.c_str(), offset,
|
||||
member_name.c_str(), rip, mod_name);
|
||||
});
|
||||
}
|
||||
#pragma once
|
||||
|
||||
#include "reflect_type_info.hpp"
|
||||
#include <set>
|
||||
#include <cinttypes>
|
||||
|
||||
template <typename T>
|
||||
emulator_hook* watch_object(windows_emulator& emu, const std::set<std::string, std::less<>>& modules,
|
||||
emulator_object<T> object, const auto verbose)
|
||||
{
|
||||
const reflect_type_info<T> info{};
|
||||
|
||||
return emu.emu().hook_memory_read(
|
||||
object.value(), static_cast<size_t>(object.size()),
|
||||
[i = std::move(info), object, &emu, verbose, modules](const uint64_t address, const void*, size_t) {
|
||||
const auto rip = emu.emu().read_instruction_pointer();
|
||||
const auto* mod = emu.mod_manager.find_by_address(rip);
|
||||
const auto is_main_access = mod == emu.mod_manager.executable || modules.contains(mod->name);
|
||||
|
||||
if (!verbose && !is_main_access)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!verbose)
|
||||
{
|
||||
static std::unordered_set<uint64_t> logged_addresses{};
|
||||
if (is_main_access && !logged_addresses.insert(address).second)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto offset = address - object.value();
|
||||
const auto* mod_name = mod ? mod->name.c_str() : "<N/A>";
|
||||
const auto& type_name = i.get_type_name();
|
||||
const auto member_name = i.get_member_name(static_cast<size_t>(offset));
|
||||
|
||||
emu.log.print(is_main_access ? color::green : color::dark_gray,
|
||||
"Object access: %s - 0x%" PRIx64 " (%s) at 0x%" PRIx64 " (%s)\n", type_name.c_str(), offset,
|
||||
member_name.c_str(), rip, mod_name);
|
||||
});
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,83 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wconversion"
|
||||
#pragma GCC diagnostic ignored "-Wtautological-compare"
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-private-field"
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4308)
|
||||
#endif
|
||||
|
||||
#include "reflect_extension.hpp"
|
||||
#include <reflect>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
class reflect_type_info
|
||||
{
|
||||
public:
|
||||
reflect_type_info()
|
||||
{
|
||||
this->type_name_ = reflect::type_name<T>();
|
||||
|
||||
reflect::for_each<T>([this](auto I) {
|
||||
const auto member_name = reflect::member_name<I, T>();
|
||||
const auto member_offset = reflect::offset_of<I, T>();
|
||||
|
||||
this->members_[member_offset] = member_name;
|
||||
});
|
||||
}
|
||||
|
||||
std::string get_member_name(const size_t offset) const
|
||||
{
|
||||
size_t last_offset{};
|
||||
std::string_view last_member{};
|
||||
|
||||
for (const auto& member : this->members_)
|
||||
{
|
||||
if (offset == member.first)
|
||||
{
|
||||
return member.second;
|
||||
}
|
||||
|
||||
if (offset < member.first)
|
||||
{
|
||||
const auto diff = offset - last_offset;
|
||||
return std::string(last_member) + "+" + std::to_string(diff);
|
||||
}
|
||||
|
||||
last_offset = member.first;
|
||||
last_member = member.second;
|
||||
}
|
||||
|
||||
return "<N/A>";
|
||||
}
|
||||
|
||||
const std::string& get_type_name() const
|
||||
{
|
||||
return this->type_name_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string type_name_{};
|
||||
std::map<size_t, std::string> members_{};
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wconversion"
|
||||
#pragma GCC diagnostic ignored "-Wtautological-compare"
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-private-field"
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4308)
|
||||
#endif
|
||||
|
||||
#include "reflect_extension.hpp"
|
||||
#include <reflect>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
class reflect_type_info
|
||||
{
|
||||
public:
|
||||
reflect_type_info()
|
||||
{
|
||||
this->type_name_ = reflect::type_name<T>();
|
||||
|
||||
reflect::for_each<T>([this](auto I) {
|
||||
const auto member_name = reflect::member_name<I, T>();
|
||||
const auto member_offset = reflect::offset_of<I, T>();
|
||||
|
||||
this->members_[member_offset] = member_name;
|
||||
});
|
||||
}
|
||||
|
||||
std::string get_member_name(const size_t offset) const
|
||||
{
|
||||
size_t last_offset{};
|
||||
std::string_view last_member{};
|
||||
|
||||
for (const auto& member : this->members_)
|
||||
{
|
||||
if (offset == member.first)
|
||||
{
|
||||
return member.second;
|
||||
}
|
||||
|
||||
if (offset < member.first)
|
||||
{
|
||||
const auto diff = offset - last_offset;
|
||||
return std::string(last_member) + "+" + std::to_string(diff);
|
||||
}
|
||||
|
||||
last_offset = member.first;
|
||||
last_member = member.second;
|
||||
}
|
||||
|
||||
return "<N/A>";
|
||||
}
|
||||
|
||||
const std::string& get_type_name() const
|
||||
{
|
||||
return this->type_name_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string type_name_{};
|
||||
std::map<size_t, std::string> members_{};
|
||||
};
|
||||
|
||||
@@ -1,101 +1,101 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#pragma code_page(65001)
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "windows.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (United States) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""windows.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,0,0,0
|
||||
PRODUCTVERSION 1,0,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x40004L
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "momo5502"
|
||||
VALUE "FileDescription", "Windows Emulator"
|
||||
VALUE "FileVersion", "1.0.0.0"
|
||||
VALUE "InternalName", "emulator"
|
||||
VALUE "LegalCopyright", "All rights reserved."
|
||||
VALUE "OriginalFilename", "emulator.exe"
|
||||
VALUE "ProductName", "emulator"
|
||||
VALUE "ProductVersion", "1.0.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Binary Data
|
||||
//
|
||||
|
||||
GLFW_ICON ICON "resources/icon.ico"
|
||||
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#pragma code_page(65001)
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "windows.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (United States) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""windows.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,0,0,0
|
||||
PRODUCTVERSION 1,0,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x40004L
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "momo5502"
|
||||
VALUE "FileDescription", "Windows Emulator"
|
||||
VALUE "FileVersion", "1.0.0.0"
|
||||
VALUE "InternalName", "emulator"
|
||||
VALUE "LegalCopyright", "All rights reserved."
|
||||
VALUE "OriginalFilename", "emulator.exe"
|
||||
VALUE "ProductName", "emulator"
|
||||
VALUE "ProductVersion", "1.0.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Binary Data
|
||||
//
|
||||
|
||||
GLFW_ICON ICON "resources/icon.ico"
|
||||
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
|
||||
|
||||
@@ -1,123 +1,123 @@
|
||||
#include "snapshot.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/compression.hpp>
|
||||
|
||||
namespace snapshot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct snapshot_header
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
char magic[4] = {'S', 'N', 'A', 'P'};
|
||||
uint32_t version{1};
|
||||
};
|
||||
|
||||
static_assert(sizeof(snapshot_header) == 8);
|
||||
|
||||
std::span<const std::byte> validate_header(const std::span<const std::byte> snapshot)
|
||||
{
|
||||
snapshot_header header{};
|
||||
constexpr snapshot_header default_header{};
|
||||
|
||||
if (snapshot.size() < sizeof(header))
|
||||
{
|
||||
throw std::runtime_error("Snapshot is too small");
|
||||
}
|
||||
|
||||
memcpy(&header, snapshot.data(), sizeof(header));
|
||||
|
||||
if (memcmp(default_header.magic, header.magic, sizeof(header.magic)) != 0)
|
||||
{
|
||||
throw std::runtime_error("Invalid snapshot");
|
||||
}
|
||||
|
||||
if (default_header.version != header.version)
|
||||
{
|
||||
throw std::runtime_error("Unsupported snapshot version: " + std::to_string(header.version) +
|
||||
"(needed: " + std::to_string(default_header.version) + ")");
|
||||
}
|
||||
|
||||
return snapshot.subspan(sizeof(header));
|
||||
}
|
||||
|
||||
std::vector<std::byte> get_compressed_emulator_state(const windows_emulator& win_emu)
|
||||
{
|
||||
utils::buffer_serializer serializer{};
|
||||
win_emu.serialize(serializer);
|
||||
|
||||
return utils::compression::zlib::compress(serializer.get_buffer());
|
||||
}
|
||||
|
||||
std::vector<std::byte> get_decompressed_emulator_state(const std::span<const std::byte> snapshot)
|
||||
{
|
||||
const auto data = validate_header(snapshot);
|
||||
return utils::compression::zlib::decompress(data);
|
||||
}
|
||||
|
||||
std::string get_main_executable_name(const windows_emulator& win_emu)
|
||||
{
|
||||
const auto* exe = win_emu.mod_manager.executable;
|
||||
if (exe)
|
||||
{
|
||||
return std::filesystem::path(exe->name).stem().string();
|
||||
}
|
||||
|
||||
return "process";
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::byte> create_emulator_snapshot(const windows_emulator& win_emu)
|
||||
{
|
||||
const auto state = get_compressed_emulator_state(win_emu);
|
||||
|
||||
snapshot_header header{};
|
||||
std::span header_span(reinterpret_cast<const std::byte*>(&header), sizeof(header));
|
||||
|
||||
std::vector<std::byte> snapshot{};
|
||||
snapshot.reserve(header_span.size() + state.size());
|
||||
snapshot.assign(header_span.begin(), header_span.end());
|
||||
snapshot.insert(snapshot.end(), state.begin(), state.end());
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::filesystem::path write_emulator_snapshot(const windows_emulator& win_emu, const bool log)
|
||||
{
|
||||
std::filesystem::path snapshot_file =
|
||||
get_main_executable_name(win_emu) + "-" + std::to_string(time(nullptr)) + ".snap";
|
||||
|
||||
if (log)
|
||||
{
|
||||
win_emu.log.log("Writing snapshot to %s...\n", snapshot_file.string().c_str());
|
||||
}
|
||||
|
||||
const auto snapshot = create_emulator_snapshot(win_emu);
|
||||
if (!utils::io::write_file(snapshot_file, snapshot))
|
||||
{
|
||||
throw std::runtime_error("Failed to write snapshot!");
|
||||
}
|
||||
|
||||
return snapshot_file;
|
||||
}
|
||||
|
||||
void load_emulator_snapshot(windows_emulator& win_emu, const std::span<const std::byte> snapshot)
|
||||
{
|
||||
const auto data = get_decompressed_emulator_state(snapshot);
|
||||
|
||||
utils::buffer_deserializer deserializer{data};
|
||||
win_emu.deserialize(deserializer);
|
||||
}
|
||||
|
||||
void load_emulator_snapshot(windows_emulator& win_emu, const std::filesystem::path& snapshot_file)
|
||||
{
|
||||
std::vector<std::byte> data{};
|
||||
if (!utils::io::read_file(snapshot_file, &data))
|
||||
{
|
||||
throw std::runtime_error("Failed to read snapshot file: " + snapshot_file.string());
|
||||
}
|
||||
|
||||
load_emulator_snapshot(win_emu, data);
|
||||
}
|
||||
}
|
||||
#include "snapshot.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/compression.hpp>
|
||||
|
||||
namespace snapshot
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct snapshot_header
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
char magic[4] = {'S', 'N', 'A', 'P'};
|
||||
uint32_t version{1};
|
||||
};
|
||||
|
||||
static_assert(sizeof(snapshot_header) == 8);
|
||||
|
||||
std::span<const std::byte> validate_header(const std::span<const std::byte> snapshot)
|
||||
{
|
||||
snapshot_header header{};
|
||||
constexpr snapshot_header default_header{};
|
||||
|
||||
if (snapshot.size() < sizeof(header))
|
||||
{
|
||||
throw std::runtime_error("Snapshot is too small");
|
||||
}
|
||||
|
||||
memcpy(&header, snapshot.data(), sizeof(header));
|
||||
|
||||
if (memcmp(default_header.magic, header.magic, sizeof(header.magic)) != 0)
|
||||
{
|
||||
throw std::runtime_error("Invalid snapshot");
|
||||
}
|
||||
|
||||
if (default_header.version != header.version)
|
||||
{
|
||||
throw std::runtime_error("Unsupported snapshot version: " + std::to_string(header.version) +
|
||||
"(needed: " + std::to_string(default_header.version) + ")");
|
||||
}
|
||||
|
||||
return snapshot.subspan(sizeof(header));
|
||||
}
|
||||
|
||||
std::vector<std::byte> get_compressed_emulator_state(const windows_emulator& win_emu)
|
||||
{
|
||||
utils::buffer_serializer serializer{};
|
||||
win_emu.serialize(serializer);
|
||||
|
||||
return utils::compression::zlib::compress(serializer.get_buffer());
|
||||
}
|
||||
|
||||
std::vector<std::byte> get_decompressed_emulator_state(const std::span<const std::byte> snapshot)
|
||||
{
|
||||
const auto data = validate_header(snapshot);
|
||||
return utils::compression::zlib::decompress(data);
|
||||
}
|
||||
|
||||
std::string get_main_executable_name(const windows_emulator& win_emu)
|
||||
{
|
||||
const auto* exe = win_emu.mod_manager.executable;
|
||||
if (exe)
|
||||
{
|
||||
return std::filesystem::path(exe->name).stem().string();
|
||||
}
|
||||
|
||||
return "process";
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::byte> create_emulator_snapshot(const windows_emulator& win_emu)
|
||||
{
|
||||
const auto state = get_compressed_emulator_state(win_emu);
|
||||
|
||||
snapshot_header header{};
|
||||
std::span header_span(reinterpret_cast<const std::byte*>(&header), sizeof(header));
|
||||
|
||||
std::vector<std::byte> snapshot{};
|
||||
snapshot.reserve(header_span.size() + state.size());
|
||||
snapshot.assign(header_span.begin(), header_span.end());
|
||||
snapshot.insert(snapshot.end(), state.begin(), state.end());
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::filesystem::path write_emulator_snapshot(const windows_emulator& win_emu, const bool log)
|
||||
{
|
||||
std::filesystem::path snapshot_file =
|
||||
get_main_executable_name(win_emu) + "-" + std::to_string(time(nullptr)) + ".snap";
|
||||
|
||||
if (log)
|
||||
{
|
||||
win_emu.log.log("Writing snapshot to %s...\n", snapshot_file.string().c_str());
|
||||
}
|
||||
|
||||
const auto snapshot = create_emulator_snapshot(win_emu);
|
||||
if (!utils::io::write_file(snapshot_file, snapshot))
|
||||
{
|
||||
throw std::runtime_error("Failed to write snapshot!");
|
||||
}
|
||||
|
||||
return snapshot_file;
|
||||
}
|
||||
|
||||
void load_emulator_snapshot(windows_emulator& win_emu, const std::span<const std::byte> snapshot)
|
||||
{
|
||||
const auto data = get_decompressed_emulator_state(snapshot);
|
||||
|
||||
utils::buffer_deserializer deserializer{data};
|
||||
win_emu.deserialize(deserializer);
|
||||
}
|
||||
|
||||
void load_emulator_snapshot(windows_emulator& win_emu, const std::filesystem::path& snapshot_file)
|
||||
{
|
||||
std::vector<std::byte> data{};
|
||||
if (!utils::io::read_file(snapshot_file, &data))
|
||||
{
|
||||
throw std::runtime_error("Failed to read snapshot file: " + snapshot_file.string());
|
||||
}
|
||||
|
||||
load_emulator_snapshot(win_emu, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows_emulator.hpp>
|
||||
|
||||
namespace snapshot
|
||||
{
|
||||
std::vector<std::byte> create_emulator_snapshot(const windows_emulator& win_emu);
|
||||
std::filesystem::path write_emulator_snapshot(const windows_emulator& win_emu, bool log = true);
|
||||
|
||||
void load_emulator_snapshot(windows_emulator& win_emu, std::span<const std::byte> snapshot);
|
||||
void load_emulator_snapshot(windows_emulator& win_emu, const std::filesystem::path& snapshot_file);
|
||||
}
|
||||
#pragma once
|
||||
|
||||
#include <windows_emulator.hpp>
|
||||
|
||||
namespace snapshot
|
||||
{
|
||||
std::vector<std::byte> create_emulator_snapshot(const windows_emulator& win_emu);
|
||||
std::filesystem::path write_emulator_snapshot(const windows_emulator& win_emu, bool log = true);
|
||||
|
||||
void load_emulator_snapshot(windows_emulator& win_emu, std::span<const std::byte> snapshot);
|
||||
void load_emulator_snapshot(windows_emulator& win_emu, const std::filesystem::path& snapshot_file);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <ranges>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <platform/platform.hpp>
|
||||
|
||||
// NOLINTNEXTLINE(google-global-names-in-headers)
|
||||
using namespace std::literals;
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <ranges>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <platform/platform.hpp>
|
||||
|
||||
// NOLINTNEXTLINE(google-global-names-in-headers)
|
||||
using namespace std::literals;
|
||||
|
||||
256
src/analyzer/tenet_tracer.cpp
Normal file
256
src/analyzer/tenet_tracer.cpp
Normal file
@@ -0,0 +1,256 @@
|
||||
#include "tenet_tracer.hpp"
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
|
||||
TenetTracer::TenetTracer(windows_emulator& win_emu, const std::string& log_filename)
|
||||
: m_win_emu(win_emu), m_log_file(log_filename)
|
||||
{
|
||||
if (!m_log_file.is_open())
|
||||
{
|
||||
throw std::runtime_error("TenetTracer: Failed to open log file -> " + log_filename);
|
||||
}
|
||||
// Set up memory hooks.
|
||||
auto& emu = m_win_emu.emu();
|
||||
m_read_hook = emu.hook_memory_read(0, 0xFFFFFFFFFFFFFFFF, [this](uint64_t a, const void* d, size_t s) {
|
||||
this->log_memory_read(a, d, s);
|
||||
});
|
||||
m_write_hook = emu.hook_memory_write(0, 0xFFFFFFFFFFFFFFFF, [this](uint64_t a, const void* d, size_t s) {
|
||||
this->log_memory_write(a, d, s);
|
||||
});
|
||||
}
|
||||
|
||||
TenetTracer::~TenetTracer()
|
||||
{
|
||||
auto& emu = m_win_emu.emu();
|
||||
if (m_read_hook) emu.delete_hook(m_read_hook);
|
||||
if (m_write_hook) emu.delete_hook(m_write_hook);
|
||||
|
||||
// Filter and write the buffer when the program ends.
|
||||
filter_and_write_buffer();
|
||||
|
||||
if (m_log_file.is_open())
|
||||
{
|
||||
m_log_file.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function: Parses a log line and adds register changes to the map.
|
||||
static void parse_and_accumulate_changes(const std::string& line, std::map<std::string, std::string>& changes)
|
||||
{
|
||||
size_t start = 0;
|
||||
while (start < line.length())
|
||||
{
|
||||
size_t end = line.find(',', start);
|
||||
if (end == std::string::npos)
|
||||
{
|
||||
end = line.length();
|
||||
}
|
||||
|
||||
std::string pair_str = line.substr(start, end - start);
|
||||
size_t equals_pos = pair_str.find('=');
|
||||
if (equals_pos != std::string::npos)
|
||||
{
|
||||
std::string key = pair_str.substr(0, equals_pos);
|
||||
std::string value = pair_str.substr(equals_pos + 1);
|
||||
changes[key] = value; // Updates existing or adds a new one.
|
||||
}
|
||||
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void TenetTracer::filter_and_write_buffer()
|
||||
{
|
||||
if (m_raw_log_buffer.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* exe_module = m_win_emu.mod_manager.executable;
|
||||
if (!exe_module)
|
||||
{
|
||||
// If there is no main module, write the raw data and exit.
|
||||
for (const auto& line : m_raw_log_buffer)
|
||||
{
|
||||
m_log_file << line << '\n';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Always write the first line (initial registers).
|
||||
if (!m_raw_log_buffer.empty())
|
||||
{
|
||||
m_log_file << m_raw_log_buffer.front() << '\n';
|
||||
}
|
||||
|
||||
bool currently_outside = false;
|
||||
std::map<std::string, std::string> accumulated_changes;
|
||||
|
||||
for (size_t i = 1; i < m_raw_log_buffer.size(); ++i)
|
||||
{
|
||||
const auto& line = m_raw_log_buffer[i];
|
||||
|
||||
size_t rip_pos = line.find("rip=0x");
|
||||
if (rip_pos == std::string::npos) continue;
|
||||
|
||||
char* end_ptr;
|
||||
uint64_t address = std::strtoull(line.c_str() + rip_pos + 6, &end_ptr, 16);
|
||||
|
||||
bool is_line_inside = exe_module->is_within(address);
|
||||
|
||||
if (is_line_inside)
|
||||
{
|
||||
// We are inside the main module.
|
||||
if (currently_outside)
|
||||
{
|
||||
// JUST ENTERED FROM OUTSIDE (moment of return from API)
|
||||
// 1. Create a synthetic log line from the accumulated changes.
|
||||
if (!accumulated_changes.empty())
|
||||
{
|
||||
std::stringstream summary_line;
|
||||
bool first = true;
|
||||
|
||||
// Separate rip from the map as it will be added at the end.
|
||||
auto rip_it = accumulated_changes.find("rip");
|
||||
std::string last_rip;
|
||||
if (rip_it != accumulated_changes.end())
|
||||
{
|
||||
last_rip = rip_it->second;
|
||||
accumulated_changes.erase(rip_it);
|
||||
}
|
||||
|
||||
for (const auto& pair : accumulated_changes)
|
||||
{
|
||||
if (!first) summary_line << ",";
|
||||
summary_line << pair.first << "=" << pair.second;
|
||||
first = false;
|
||||
}
|
||||
|
||||
// Add the last known rip at the end.
|
||||
if (!last_rip.empty())
|
||||
{
|
||||
if (!first) summary_line << ",";
|
||||
summary_line << "rip=" << last_rip;
|
||||
}
|
||||
|
||||
m_log_file << summary_line.str() << '\n';
|
||||
}
|
||||
accumulated_changes.clear();
|
||||
}
|
||||
|
||||
// 2. Write the current line within the main module.
|
||||
m_log_file << line << '\n';
|
||||
currently_outside = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are outside the main module.
|
||||
// 1. Accumulate the changes.
|
||||
parse_and_accumulate_changes(line, accumulated_changes);
|
||||
currently_outside = true;
|
||||
}
|
||||
}
|
||||
|
||||
m_raw_log_buffer.clear();
|
||||
}
|
||||
|
||||
|
||||
std::string TenetTracer::format_hex(uint64_t value)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "0x" << std::hex << value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string TenetTracer::format_byte_array(const uint8_t* data, size_t size)
|
||||
{
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(data[i]);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void TenetTracer::log_memory_read(uint64_t address, const void* data, size_t size)
|
||||
{
|
||||
if (!m_mem_read_log.str().empty())
|
||||
{
|
||||
m_mem_read_log << ";";
|
||||
}
|
||||
m_mem_read_log << format_hex(address) << ":" << format_byte_array(static_cast<const uint8_t*>(data), size);
|
||||
}
|
||||
|
||||
void TenetTracer::log_memory_write(uint64_t address, const void* data, size_t size)
|
||||
{
|
||||
if (!m_mem_write_log.str().empty())
|
||||
{
|
||||
m_mem_write_log << ";";
|
||||
}
|
||||
m_mem_write_log << format_hex(address) << ":" << format_byte_array(static_cast<const uint8_t*>(data), size);
|
||||
}
|
||||
|
||||
void TenetTracer::process_instruction(uint64_t address)
|
||||
{
|
||||
auto& emu = m_win_emu.emu();
|
||||
std::stringstream trace_line;
|
||||
|
||||
std::array<uint64_t, GPRs_TO_TRACE.size()> current_regs;
|
||||
for (size_t i = 0; i < GPRs_TO_TRACE.size(); ++i)
|
||||
{
|
||||
current_regs[i] = emu.reg<uint64_t>(GPRs_TO_TRACE[i].first);
|
||||
}
|
||||
|
||||
bool first_entry = true;
|
||||
auto append_separator = [&]() {
|
||||
if (!first_entry) {
|
||||
trace_line << ",";
|
||||
}
|
||||
first_entry = false;
|
||||
};
|
||||
|
||||
if (m_is_first_instruction)
|
||||
{
|
||||
for (size_t i = 0; i < GPRs_TO_TRACE.size(); ++i)
|
||||
{
|
||||
append_separator();
|
||||
trace_line << GPRs_TO_TRACE[i].second << "=" << format_hex(current_regs[i]);
|
||||
}
|
||||
m_is_first_instruction = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < GPRs_TO_TRACE.size(); ++i)
|
||||
{
|
||||
if (m_previous_regs[i] != current_regs[i])
|
||||
{
|
||||
append_separator();
|
||||
trace_line << GPRs_TO_TRACE[i].second << "=" << format_hex(current_regs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
append_separator();
|
||||
trace_line << "rip=" << format_hex(address);
|
||||
|
||||
std::string mem_reads = m_mem_read_log.str();
|
||||
if (!mem_reads.empty()) {
|
||||
append_separator();
|
||||
trace_line << "mr=" << mem_reads;
|
||||
}
|
||||
std::string mem_writes = m_mem_write_log.str();
|
||||
if (!mem_writes.empty()) {
|
||||
append_separator();
|
||||
trace_line << "mw=" << mem_writes;
|
||||
}
|
||||
|
||||
// Add the data to the buffer instead of writing directly to the file.
|
||||
m_raw_log_buffer.push_back(trace_line.str());
|
||||
|
||||
m_previous_regs = current_regs;
|
||||
|
||||
m_mem_read_log.str("");
|
||||
m_mem_read_log.clear();
|
||||
m_mem_write_log.str("");
|
||||
m_mem_write_log.clear();
|
||||
}
|
||||
60
src/analyzer/tenet_tracer.hpp
Normal file
60
src/analyzer/tenet_tracer.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows_emulator.hpp>
|
||||
#include <emulator/x86_register.hpp>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
|
||||
// List of registers to trace for the x64 architecture.
|
||||
constexpr std::array<std::pair<x86_register, std::string_view>, 16> GPRs_TO_TRACE = {{
|
||||
{x86_register::rax, "rax"}, {x86_register::rbx, "rbx"}, {x86_register::rcx, "rcx"},
|
||||
{x86_register::rdx, "rdx"}, {x86_register::rsi, "rsi"}, {x86_register::rdi, "rdi"},
|
||||
{x86_register::rbp, "rbp"}, {x86_register::rsp, "rsp"}, {x86_register::r8, "r8"},
|
||||
{x86_register::r9, "r9"}, {x86_register::r10, "r10"}, {x86_register::r11, "r11"},
|
||||
{x86_register::r12, "r12"}, {x86_register::r13, "r13"}, {x86_register::r14, "r14"},
|
||||
{x86_register::r15, "r15"}
|
||||
}};
|
||||
|
||||
class TenetTracer
|
||||
{
|
||||
public:
|
||||
TenetTracer(windows_emulator& win_emu, const std::string& log_filename);
|
||||
~TenetTracer();
|
||||
|
||||
// A new public method to call TenetTracer for each instruction.
|
||||
void process_instruction(uint64_t address);
|
||||
|
||||
// Disable copy and move operations.
|
||||
TenetTracer(const TenetTracer&) = delete;
|
||||
TenetTracer& operator=(const TenetTracer&) = delete;
|
||||
|
||||
private:
|
||||
void filter_and_write_buffer();
|
||||
void log_memory_read(uint64_t address, const void* data, size_t size);
|
||||
void log_memory_write(uint64_t address, const void* data, size_t size);
|
||||
|
||||
std::string format_hex(uint64_t value);
|
||||
std::string format_byte_array(const uint8_t* data, size_t size);
|
||||
|
||||
windows_emulator& m_win_emu;
|
||||
std::ofstream m_log_file;
|
||||
|
||||
// In-memory buffering for performance.
|
||||
std::vector<std::string> m_raw_log_buffer;
|
||||
|
||||
// Use an array instead of a map to store the register state of the previous instruction.
|
||||
std::array<uint64_t, GPRs_TO_TRACE.size()> m_previous_regs{};
|
||||
bool m_is_first_instruction = true;
|
||||
|
||||
// To temporarily store memory operations.
|
||||
std::stringstream m_mem_read_log;
|
||||
std::stringstream m_mem_write_log;
|
||||
|
||||
// To manage memory hooks.
|
||||
emulator_hook* m_read_hook = nullptr;
|
||||
emulator_hook* m_write_hook = nullptr;
|
||||
};
|
||||
@@ -1,26 +1,26 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
emulator_root = os.getenv('EMULATOR_ROOT')
|
||||
analysis_sample = os.getenv('ANALYSIS_SAMPLE')
|
||||
virtual_sample = 'C:/analysis-sample.exe'
|
||||
|
||||
application = 'analyzer'
|
||||
|
||||
def make_app(app):
|
||||
if os.name == 'nt':
|
||||
return app + ".exe"
|
||||
|
||||
return app
|
||||
|
||||
command = [
|
||||
os.path.join(os.getcwd(), make_app(application)),
|
||||
'-c',
|
||||
'-e', emulator_root,
|
||||
'-p', virtual_sample, analysis_sample,
|
||||
virtual_sample
|
||||
]
|
||||
|
||||
result = subprocess.run(command, cwd=os.getcwd())
|
||||
|
||||
exit(result.returncode)
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
emulator_root = os.getenv('EMULATOR_ROOT')
|
||||
analysis_sample = os.getenv('ANALYSIS_SAMPLE')
|
||||
virtual_sample = 'C:/analysis-sample.exe'
|
||||
|
||||
application = 'analyzer'
|
||||
|
||||
def make_app(app):
|
||||
if os.name == 'nt':
|
||||
return app + ".exe"
|
||||
|
||||
return app
|
||||
|
||||
command = [
|
||||
os.path.join(os.getcwd(), make_app(application)),
|
||||
'-c',
|
||||
'-e', emulator_root,
|
||||
'-p', virtual_sample, analysis_sample,
|
||||
virtual_sample
|
||||
]
|
||||
|
||||
result = subprocess.run(command, cwd=os.getcwd())
|
||||
|
||||
exit(result.returncode)
|
||||
|
||||
Reference in New Issue
Block a user