initial commit

This commit is contained in:
zer0condition
2026-08-19 01:23:15 +05:30
commit 00a3adf890
89 changed files with 21608 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
name: build
on:
push:
branches: [ main, master ]
pull_request:
workflow_dispatch:
jobs:
driver:
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
- name: install WDK
run: |
choco install --no-progress windowsdriverkit10
shell: pwsh
- name: install LLVM (for wasm32)
run: |
choco install --no-progress llvm --version=18.1.8
shell: pwsh
- name: locate MSBuild
uses: microsoft/setup-msbuild@v2
- name: build driver + cli + guest (Debug)
run: |
$env:LLVM_HOME = "C:\Program Files\LLVM"
msbuild Goodmans.sln /p:Configuration=Debug /p:Platform=x64 /m /nologo
shell: pwsh
- name: build driver + cli + guest (Release)
run: |
$env:LLVM_HOME = "C:\Program Files\LLVM"
msbuild Goodmans.sln /p:Configuration=Release /p:Platform=x64 /m /nologo
shell: pwsh
- name: upload artifacts
uses: actions/upload-artifact@v4
with:
name: goodmans-x64-release
path: |
driver/x64/Release/Goodmans.sys
cli/x64/Release/goodmans.exe
sample_guest/sample_guest.wasm
if-no-files-found: warn
+48
View File
@@ -0,0 +1,48 @@
# build outputs
x64/
Debug/
Release/
*.obj
*.o
*.pdb
*.ipdb
*.iobj
*.ilk
*.exp
*.lib
*.tlog
*.log
*.recipe
*.suo
*.user
.vs/
# driver artifacts
*.sys
*.inf.bak
*.cat
# built wasm
sample_guest/*.wasm
feature_guests/*.wasm
# CLI
cli/*.exe
!goodmans.exe.manifest
# generated certs (users make their own via deploy/gen_cert.cmd)
*.cer
*.pfx
# staged deploy binaries (users copy after build)
deploy/*.sys
deploy/*.exe
deploy/*.wasm
# VS
*.sdf
*.opendb
*.VC.db
# misc
*.zip
+42
View File
@@ -0,0 +1,42 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.0.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Goodmans", "driver\Goodmans.vcxproj", "{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0001}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "goodmans-cli", "cli\cli.vcxproj", "{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0002}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample-guest", "sample_guest\sample_guest.vcxproj", "{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0003}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "feature-guests", "feature_guests\feature_guests.vcxproj", "{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0007}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gvm-windbg", "windbg\gvm_ext.vcxproj", "{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0004}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0001}.Debug|x64.ActiveCfg = Debug|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0001}.Debug|x64.Build.0 = Debug|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0001}.Release|x64.ActiveCfg = Release|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0001}.Release|x64.Build.0 = Release|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0002}.Debug|x64.ActiveCfg = Debug|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0002}.Debug|x64.Build.0 = Debug|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0002}.Release|x64.ActiveCfg = Release|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0002}.Release|x64.Build.0 = Release|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0003}.Debug|x64.ActiveCfg = Debug|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0003}.Debug|x64.Build.0 = Debug|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0003}.Release|x64.ActiveCfg = Release|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0003}.Release|x64.Build.0 = Release|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0004}.Debug|x64.ActiveCfg = Debug|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0004}.Debug|x64.Build.0 = Debug|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0004}.Release|x64.ActiveCfg = Release|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0004}.Release|x64.Build.0 = Release|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0007}.Debug|x64.ActiveCfg = Debug|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0007}.Debug|x64.Build.0 = Debug|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0007}.Release|x64.ActiveCfg = Release|x64
{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0007}.Release|x64.Build.0 = Release|x64
EndGlobalSection
EndGlobal
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 zer0condition
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+107
View File
@@ -0,0 +1,107 @@
# Goodmans
Signed WDM driver embedding wasm3. Loads unsigned `.wasm` modules, gives
them nt/hal FFI. Write kernel logic in C/Rust, compile to `wasm32`, load,
iterate. HVCI-compliant.
## Features
- wasm3 in the kernel, 64KB expanded stack, no JIT
- Direct FFI to any nt/hal export by name
- Per-module capability manifest
- Per-slot KMUTEX, per-slot pool budget
- SEH-guarded kernel VA read/write
- InfinityHook trampoline
- Process/image notify callbacks
- Watchdog + trace/log rings
- Qt6 GUI
## Build
Needs VS 2022 + WDK 10.0.26100.0, Qt 6.9, clang wasm32.
```
build.cmd Release
```
## Install
```
cd deploy
gen_cert.cmd
install.cmd
```
Needs `bcdedit /set testsigning on` if not signed.
## IOCTLs
| Code | Purpose |
|------|---------|
| `LOAD_MODULE` | load .wasm, link imports |
| `CALL_EXPORT` | invoke export, up to 8 args |
| `UNLOAD_MODULE` | tear down runtime |
| `LIST_MODULES` / `MODULE_INFO` | enumerate |
| `UNLOAD_ALL` / `FORCE_UNLOAD` | bulk teardown |
| `TAIL_LOG` / `TAIL_TRACE` | poll rings |
| `TRACE_CTL` | toggle trace |
| `READ_GUEST` | read guest linear memory |
| `NOTIFY_STOP` | deregister callbacks |
## Host Imports
| Import | Sig |
|--------|-----|
| `host_dbg_print` | `v(ii)` |
| `host_alloc` / `host_free` | `I(i)` / `v(I)` |
| `host_read_u8/32/64` | `i/I(I)` |
| `host_write_u64` | `v(II)` |
| `host_read_bytes` / `host_write_bytes` | `i(Iii)` |
| `host_current_irql` | `i()` |
| `host_process_id` / `host_thread_id` | `i()` |
| `host_current_process` | `I()` |
| `host_cpuid` | `v(iii)` |
| `host_rdtsc` | `I()` |
| `host_readmsr` / `host_writemsr` | `I(i)` / `i(iI)` |
| `host_phys_read` / `host_phys_write` | `i(Iii)` |
| `host_call` | `I(iiiIIIIIIII)` |
| `host_ih_trampoline` / `host_ih_configure` / `host_ih_quiesce` | |
| `host_notify_enable` / `host_notify_poll` / `host_dispatch_start` / `host_dispatch_stop` | |
| `host_mem_base` / `host_mem_size` | `I()` / `i()` |
| `host_make_unistr` / `host_free_unistr` | |
Guest bindings in `guest_sdk/gvm.h`.
## Capabilities
| Bit | Name | Grants |
|-----|------|--------|
| 0 | `ALLOC` | host_alloc, host_free |
| 1 | `READ_KMEM` | host_read_* |
| 2 | `WRITE_KMEM` | host_write_* |
| 3 | `MSR_READ` | host_readmsr |
| 4 | `MSR_WRITE` | host_writemsr |
| 5 | `PHYSMEM` | host_phys_* |
| 6 | `CPUID_TSC` | host_cpuid, host_rdtsc |
| 7 | `CALLBACKS` | process/image notify |
| 8 | `HOSTCALL` | host_call |
| 9 | `INTROSPECT` | pid/tid/irql/current_process |
## Limits
- no f32/f64 in guests
- wasm memory bounds checks compiled out
- one caller per module
- 16MB ioctl payload
- 32 module slots
## Credits
- wasm3 by Volodymyr Shymanskyy and Steven Massey (MIT). https://github.com/wasm3/wasm3
- InfinityHook by everdox (MIT). https://github.com/everdox/InfinityHook
- Qt 6 (LGPL v3)
## License
MIT.
+78
View File
@@ -0,0 +1,78 @@
@echo off
setlocal EnableDelayedExpansion
rem goodmans - one-shot build: driver + samples + gui, all staged into deploy\
rem usage: build.cmd [Debug|Release]
set "CFG=%~1"
if "%CFG%"=="" set "CFG=Debug"
set "ROOT=%~dp0"
if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
set "MSBUILD=C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"
if not exist "%MSBUILD%" (
for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe"`) do set "MSBUILD=%%i"
)
if not exist "%MSBUILD%" (
echo [X] MSBuild not found. install Visual Studio 2022.
exit /b 1
)
set "QT_DIR=C:\Qt\6.9.3\msvc2022_64"
if not exist "%QT_DIR%\bin\Qt6Core.dll" (
echo [!] Qt not at %QT_DIR% - gui step will be skipped
set "SKIP_GUI=1"
)
rem stop driver silently so deploy\Goodmans.sys isn't locked during copy
sc stop Goodmans >nul 2>&1
echo.
echo [1/4] driver + samples + cli + windbg ext
"%MSBUILD%" "%ROOT%\Goodmans.sln" /p:Configuration=%CFG% /p:Platform=x64 /m /nologo /v:m
if errorlevel 1 (
echo [X] sln build failed
exit /b 1
)
if defined SKIP_GUI goto :after_gui
echo.
echo [2/4] gui-qt configure
if not exist "%ROOT%\gui-qt\build\CMakeCache.txt" (
cmake -S "%ROOT%\gui-qt" -B "%ROOT%\gui-qt\build" -G "Visual Studio 17 2022" -A x64 -DCMAKE_PREFIX_PATH="%QT_DIR%" >nul
if errorlevel 1 (
echo [X] cmake configure failed
exit /b 1
)
)
echo.
echo [3/4] gui-qt build
cmake --build "%ROOT%\gui-qt\build" --config Release --parallel
if errorlevel 1 (
echo [X] gui-qt build failed
exit /b 1
)
:after_gui
echo.
echo [4/4] summary
echo.
echo deploy tree:
echo %ROOT%\deploy\Goodmans.sys
echo %ROOT%\deploy\Goodmans.inf
echo %ROOT%\deploy\GoodmansTest.cer / .pfx
echo %ROOT%\deploy\install.cmd / uninstall.cmd / gen_cert.cmd
echo %ROOT%\deploy\gui\goodmans-gui.exe + Qt DLLs + toolkit.wasm
echo %ROOT%\deploy\features\toolkit.wasm + process_tracer.wasm
echo %ROOT%\deploy\samples\*.wasm (demo guests)
echo.
echo to install:
echo 1. cd deploy
echo 2. gen_cert.cmd (first time only, then run as Admin)
echo 3. install.cmd (as Admin)
echo 4. gui\goodmans-gui.exe
endlocal
+48
View File
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0002}</ProjectGuid>
<RootNamespace>goodmans</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<Keyword>Win32Proj</Keyword>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<UseDebugLibraries Condition="'$(Configuration)'=='Debug'">true</UseDebugLibraries>
<UseDebugLibraries Condition="'$(Configuration)'=='Release'">false</UseDebugLibraries>
<TargetName>goodmans</TargetName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>false</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\shared\goodmans_ioctl.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
+303
View File
@@ -0,0 +1,303 @@
/* main.c - user-mode client */
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../shared/goodmans_ioctl.h"
static HANDLE open_dev(void)
{
HANDLE h = CreateFileA(GVM_USER_PATH, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, 0, NULL);
if (h == INVALID_HANDLE_VALUE)
fprintf(stderr, "CreateFile(%s) failed: %lu\n", GVM_USER_PATH, GetLastError());
return h;
}
static int cmd_load(int argc, char** argv)
{
if (argc < 3) { fprintf(stderr, "load <wasm-path>\n"); return 1; }
const char* path = argv[2];
HANDLE f = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, NULL);
if (f == INVALID_HANDLE_VALUE) {
fprintf(stderr, "open %s: %lu\n", path, GetLastError());
return 1;
}
LARGE_INTEGER sz;
GetFileSizeEx(f, &sz);
if (sz.QuadPart <= 0 || sz.QuadPart > (16LL << 20)) {
fprintf(stderr, "bad size %lld\n", sz.QuadPart);
CloseHandle(f);
return 1;
}
size_t buf_sz = sizeof(gvm_load_in) + (size_t)sz.QuadPart;
unsigned char* buf = (unsigned char*)malloc(buf_sz);
if (!buf) { CloseHandle(f); return 1; }
gvm_load_in* in = (gvm_load_in*)buf;
memset(in, 0, sizeof(*in));
in->wasm_size = (unsigned int)sz.QuadPart;
in->stack_bytes = 0;
strncpy_s(in->name, GVM_MAX_MODULE_NAME, path, _TRUNCATE);
DWORD rd = 0;
ReadFile(f, buf + sizeof(gvm_load_in), (DWORD)sz.QuadPart, &rd, NULL);
CloseHandle(f);
HANDLE dev = open_dev();
if (dev == INVALID_HANDLE_VALUE) { free(buf); return 1; }
gvm_load_out out = { 0 };
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_LOAD_MODULE,
buf, (DWORD)buf_sz,
&out, sizeof(out), &ret, NULL);
CloseHandle(dev);
free(buf);
if (!ok) { fprintf(stderr, "ioctl failed: %lu\n", GetLastError()); return 1; }
if (out.status != 0) { fprintf(stderr, "load status=%d: %s\n", out.status, out.err_msg); return 1; }
printf("module_id=%u (%s)\n", out.module_id, out.err_msg);
return 0;
}
static int cmd_call(int argc, char** argv)
{
if (argc < 3) {
fprintf(stderr, "call [--timeout=ms] <module-id> <export> [arg1..arg8]\n");
return 1;
}
unsigned int timeout_ms = 0;
int base = 2;
if (argc > base && strncmp(argv[base], "--timeout=", 10) == 0) {
timeout_ms = (unsigned int)strtoul(argv[base] + 10, NULL, 0);
base++;
}
if (argc <= base) { fprintf(stderr, "missing module id\n"); return 1; }
gvm_call_in in = { 0 };
in.module_id = (unsigned int)strtoul(argv[base], NULL, 0);
in.timeout_ms = timeout_ms;
if (argc > base + 1) strncpy_s(in.export_name, GVM_MAX_EXPORT_NAME, argv[base + 1], _TRUNCATE);
in.argc = 0;
for (int i = base + 2; i < argc && in.argc < GVM_MAX_ARGS; i++)
in.argv[in.argc++] = _strtoui64(argv[i], NULL, 0);
HANDLE dev = open_dev();
if (dev == INVALID_HANDLE_VALUE) return 1;
gvm_call_out out = { 0 };
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_CALL_EXPORT,
&in, sizeof(in),
&out, sizeof(out), &ret, NULL);
CloseHandle(dev);
if (!ok) { fprintf(stderr, "ioctl failed: %lu\n", GetLastError()); return 1; }
if (out.status != 0) { fprintf(stderr, "call status=%d: %s\n", out.status, out.err_msg); return 1; }
printf("rv=0x%llx (%llu)\n", out.rv, out.rv);
return 0;
}
static int cmd_unload(int argc, char** argv)
{
if (argc < 3) { fprintf(stderr, "unload <module-id>\n"); return 1; }
gvm_unload_in in = { 0 };
in.module_id = (unsigned int)strtoul(argv[2], NULL, 0);
HANDLE dev = open_dev();
if (dev == INVALID_HANDLE_VALUE) return 1;
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_UNLOAD_MODULE,
&in, sizeof(in), NULL, 0, &ret, NULL);
CloseHandle(dev);
if (!ok) { fprintf(stderr, "ioctl failed: %lu\n", GetLastError()); return 1; }
printf("unloaded\n");
return 0;
}
static int cmd_modules(void)
{
HANDLE dev = open_dev();
if (dev == INVALID_HANDLE_VALUE) return 1;
gvm_list_out* out = (gvm_list_out*)malloc(sizeof(gvm_list_out));
if (!out) { CloseHandle(dev); return 1; }
memset(out, 0, sizeof(*out));
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_LIST_MODULES,
NULL, 0, out, sizeof(*out), &ret, NULL);
CloseHandle(dev);
if (!ok) { fprintf(stderr, "ioctl failed: %lu\n", GetLastError()); free(out); return 1; }
printf("%-4s %-8s %-8s %-8s %-16s %s\n",
"id", "wasm", "mem", "pool", "hash", "name");
for (unsigned int i = 0; i < out->count; i++) {
gvm_module_entry* e = &out->entries[i];
printf("%-4u %-6uB %-4uKB %-6lluB %016llx %s\n",
e->id, e->wasm_size, e->mem_pages * 64,
e->pool_bytes, e->hash, e->name);
}
printf("(%u module%s)\n", out->count, out->count == 1 ? "" : "s");
free(out);
return 0;
}
static int cmd_info(int argc, char** argv)
{
if (argc < 3) { fprintf(stderr, "info <module-id>\n"); return 1; }
gvm_info_in in = { 0 };
in.module_id = (unsigned int)strtoul(argv[2], NULL, 0);
HANDLE dev = open_dev();
if (dev == INVALID_HANDLE_VALUE) return 1;
gvm_info_out* out = (gvm_info_out*)malloc(sizeof(gvm_info_out));
if (!out) { CloseHandle(dev); return 1; }
memset(out, 0, sizeof(*out));
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_MODULE_INFO,
&in, sizeof(in),
out, sizeof(*out), &ret, NULL);
CloseHandle(dev);
if (!ok) { fprintf(stderr, "ioctl failed: %lu\n", GetLastError()); free(out); return 1; }
if (out->status != 0) { fprintf(stderr, "info status=%d: %s\n", out->status, out->err_msg); free(out); return 1; }
printf("id: %u\n", out->base.id);
printf("name: %s\n", out->base.name);
printf("wasm size: %u bytes\n", out->base.wasm_size);
printf("mem pages: %u (%u KB)\n", out->base.mem_pages, out->base.mem_pages * 64);
printf("hash: %016llx\n", out->base.hash);
free(out);
return 0;
}
static int cmd_unload_all(void)
{
HANDLE dev = open_dev();
if (dev == INVALID_HANDLE_VALUE) return 1;
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_UNLOAD_ALL,
NULL, 0, NULL, 0, &ret, NULL);
CloseHandle(dev);
if (!ok) { fprintf(stderr, "ioctl failed: %lu\n", GetLastError()); return 1; }
printf("all modules unloaded\n");
return 0;
}
static int write_file(const char* path, const char* contents)
{
HANDLE h = CreateFileA(path, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) { fprintf(stderr, "cannot create %s: %lu\n", path, GetLastError()); return 1; }
DWORD w = 0; WriteFile(h, contents, (DWORD)strlen(contents), &w, NULL);
CloseHandle(h);
return 0;
}
static int cmd_new(int argc, char** argv)
{
if (argc < 3) { fprintf(stderr, "new <module-name>\n"); return 1; }
const char* name = argv[2];
if (!CreateDirectoryA(name, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
fprintf(stderr, "cannot create dir %s: %lu\n", name, GetLastError());
return 1;
}
char cpath[MAX_PATH], bpath[MAX_PATH];
_snprintf_s(cpath, sizeof(cpath), _TRUNCATE, "%s\\%s.c", name, name);
_snprintf_s(bpath, sizeof(bpath), _TRUNCATE, "%s\\build.cmd", name);
char src[2048];
_snprintf_s(src, sizeof(src), _TRUNCATE,
"/* %s.c - goodmans wasm guest generated by `goodmans new`\n"
" * declare capabilities in GVM_MANIFEST, use gvm_* helpers from the SDK.\n"
" */\n"
"\n"
"#include \"gvm.h\"\n"
"\n"
"GVM_MANIFEST(GVM_CAP_ALLOC | GVM_CAP_READ_KMEM | GVM_CAP_INTROSPECT);\n"
"\n"
"GVM_EXPORT(hello)\n"
"u32 hello(u32 x)\n"
"{\n"
" // write to the driver debug ring - shows up in the GUI Log tab\n"
" static const char msg[] = \"hello from guest\";\n"
" gvm_dbg_print_raw(msg, sizeof(msg) - 1);\n"
" return x * 2 + 1;\n"
"}\n"
"\n"
"GVM_EXPORT(current_pid)\n"
"u32 current_pid(void) { return gvm_process_id(); }\n"
"\n"
"GVM_EXPORT(run_all)\n"
"u32 run_all(void)\n"
"{\n"
" (void)hello(21);\n"
" return current_pid();\n"
"}\n",
name);
char build[1024];
_snprintf_s(build, sizeof(build), _TRUNCATE,
"@echo off\r\n"
"setlocal\r\n"
"cd /d \"%%~dp0\"\r\n"
"set CLANG=%%LLVM_HOME%%\\bin\\clang.exe\r\n"
"if not exist \"%%CLANG%%\" set CLANG=C:\\Program Files\\LLVM\\bin\\clang.exe\r\n"
"if not exist \"%%CLANG%%\" ( echo [X] clang not found ^& exit /b 1 )\r\n"
"set SDK=..\\guest_sdk\r\n"
"\"%%CLANG%%\" -O2 --target=wasm32 -nostdlib -fno-builtin -I \"%%SDK%%\" ^\r\n"
" -Wl,--no-entry -Wl,--export-dynamic -Wl,--allow-undefined -Wl,--strip-all ^\r\n"
" -o %s.wasm %s.c\r\n"
"if errorlevel 1 exit /b 1\r\n"
"echo built %s.wasm\r\n",
name, name, name);
// put a copy of gvm.h next to the source for offline builds; if user has
// the repo checked out, they can point SDK= to guest_sdk instead.
if (write_file(cpath, src)) return 1;
if (write_file(bpath, build)) return 1;
printf("created %s\\%s.c\n", name, name);
printf("created %s\\build.cmd\n", name);
printf("\nnext:\n cd %s\n build.cmd\n goodmans load %s.wasm\n", name, name);
return 0;
}
int main(int argc, char** argv)
{
if (argc < 2) {
fprintf(stderr,
"goodmans - Goodmans kernel VM CLI\n\n"
" goodmans load <wasm-path>\n"
" goodmans call [--timeout=ms] <module-id> <export> [arg1..arg8]\n"
" goodmans unload <module-id>\n"
" goodmans modules\n"
" goodmans info <module-id>\n"
" goodmans unload-all\n"
" goodmans new <module-name> scaffold a new guest\n");
return 1;
}
if (!strcmp(argv[1], "load")) return cmd_load(argc, argv);
if (!strcmp(argv[1], "call")) return cmd_call(argc, argv);
if (!strcmp(argv[1], "unload")) return cmd_unload(argc, argv);
if (!strcmp(argv[1], "modules")) return cmd_modules();
if (!strcmp(argv[1], "info")) return cmd_info(argc, argv);
if (!strcmp(argv[1], "unload-all")) return cmd_unload_all();
if (!strcmp(argv[1], "new")) return cmd_new(argc, argv);
fprintf(stderr, "unknown cmd: %s\n", argv[1]);
return 1;
}
+48
View File
@@ -0,0 +1,48 @@
; Goodmans.inf - minimal WDM install
[Version]
Signature = "$WINDOWS NT$"
Class = System
ClassGuid = {4d36e97d-e325-11ce-bfc1-08002be10318}
Provider = %Provider%
DriverVer =
CatalogFile = Goodmans.cat
PnpLockdown = 1
[DestinationDirs]
DefaultDestDir = 12
[SourceDisksNames]
1 = %DiskName%
[SourceDisksFiles]
Goodmans.sys = 1
[Manufacturer]
%Provider% = Standard,NT$ARCH$
[Standard.NT$ARCH$]
%DeviceDesc% = Goodmans_Install,Root\Goodmans
[Goodmans_Install.NT]
CopyFiles = Goodmans.CopyFiles
[Goodmans_Install.NT.Services]
AddService = Goodmans,%SPSVCINST_ASSOCSERVICE%,Goodmans_Service
[Goodmans.CopyFiles]
Goodmans.sys
[Goodmans_Service]
DisplayName = %ServiceDesc%
ServiceType = 1
StartType = 3
ErrorControl = 1
ServiceBinary = %12%\Goodmans.sys
[Strings]
SPSVCINST_ASSOCSERVICE = 0x00000002
Provider = "Goodmans"
DeviceDesc = "Goodmans WASM Kernel VM"
ServiceDesc = "Goodmans"
DiskName = "Goodmans Install Disk"
+60
View File
@@ -0,0 +1,60 @@
Goodmans deploy folder
After running build.cmd from the repo root, this directory contains
everything needed to install and run Goodmans on a test VM.
layout after build:
deploy\
Goodmans.sys signed driver (test-cert)
Goodmans.inf driver inf
GoodmansTest.cer test code-signing cert
GoodmansTest.pfx pfx used for signing (password: goodmans)
gen_cert.cmd creates cer+pfx (run once, elevated)
install.cmd imports cert + sc create + sc start
uninstall.cmd sc stop + sc delete
README.txt this file
gui\
goodmans-gui.exe debug GUI (Qt6)
Qt6*.dll + platform + tls + imageformats plugins
toolkit.wasm toolkit guest for the Explorer tab
samples\
sample_guest.wasm demo host-import roundtrips
ffi_demo.wasm direct nt/hal export calls via host_call
pslist_dumper.wasm walks PsLoadedModuleList
handle_stripper.wasm ObDereferenceObject demo
infinity_hook.wasm full IH port in wasm
features\
toolkit.wasm
process_tracer.wasm
workflow on the target VM
1. enable test signing (once, then reboot):
bcdedit /set testsigning on
2. copy the whole `deploy\` folder to the VM. from an elevated cmd here:
gen_cert.cmd (first-time setup, creates the cer+pfx)
install.cmd (imports cert + sc create/start Goodmans)
3. run the GUI:
gui\goodmans-gui.exe
the toolkit.wasm loads automatically; the Explorer tab wakes up.
load any sample from Workbench, Browse, samples\*.wasm
4. teardown:
uninstall.cmd
common errors
sc start returns 577 driver isnt signed with a trusted cert.
re-run install.cmd. verify cert landed in
Cert:\LocalMachine\Root and TrustedPublisher.
sc start returns 1275 testsigning off, or HVCI blocking. verify:
bcdedit /enum {current}
testsigning should say Yes.
CreateFile fails 2 driver started but DriverEntry bailed. open the
GUI Log tab or DbgView filtered on [goodmans].
bugcheck on start paste the bugcheck code + parameters into an issue.
+44
View File
@@ -0,0 +1,44 @@
@echo off
setlocal
net session >nul 2>&1
if errorlevel 1 (
echo [X] must be run as Administrator.
pause
exit /b 1
)
set "HERE=%~dp0"
if "%HERE:~-1%"=="\" set "HERE=%HERE:~0,-1%"
set "CER=%HERE%\GoodmansTest.cer"
set "PFX=%HERE%\GoodmansTest.pfx"
if exist "%CER%" if exist "%PFX%" (
echo [*] cert already exists: %CER%
echo delete both files first if you want to regenerate.
exit /b 0
)
echo [*] generating self-signed code-signing cert
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
"$c = New-SelfSignedCertificate -Type CodeSigningCert -Subject 'CN=GoodmansTest' -CertStoreLocation Cert:\CurrentUser\My -KeyUsage DigitalSignature -KeyAlgorithm RSA -KeyLength 2048 -NotAfter (Get-Date).AddYears(5);" ^
"Export-Certificate -Cert $c -FilePath '%CER%' | Out-Null;" ^
"$pw = ConvertTo-SecureString -String 'goodmans' -Force -AsPlainText;" ^
"Export-PfxCertificate -Cert $c -FilePath '%PFX%' -Password $pw | Out-Null;" ^
"Write-Host ('thumbprint: ' + $c.Thumbprint)"
if errorlevel 1 (
echo [X] cert generation failed
exit /b 1
)
echo.
echo [+] generated:
echo %CER%
echo %PFX% (password: goodmans)
echo.
echo [*] to sign a driver:
echo signtool sign /fd sha256 /f "%PFX%" /p goodmans Goodmans.sys
endlocal
+57
View File
@@ -0,0 +1,57 @@
@echo off
setlocal
net session >nul 2>&1
if errorlevel 1 (
echo [X] must be run as Administrator.
pause
exit /b 1
)
set "HERE=%~dp0"
if "%HERE:~-1%"=="\" set "HERE=%HERE:~0,-1%"
set "SYS=%HERE%\Goodmans.sys"
set "CER=%HERE%\GoodmansTest.cer"
if not exist "%SYS%" (
echo [X] missing: %SYS%
echo build the driver and copy Goodmans.sys into this folder.
exit /b 1
)
if not exist "%CER%" (
echo [X] missing: %CER%
echo run gen_cert.cmd first to create the test cert.
exit /b 1
)
echo [*] Debug Print Filter mask
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter" /v DEFAULT /t REG_DWORD /d 0xFFFFFFFF /f >nul
echo [*] importing cert to Root + TrustedPublisher
certutil -addstore Root "%CER%" >nul 2>&1
certutil -addstore TrustedPublisher "%CER%" >nul 2>&1
echo [*] removing any prior Goodmans service
sc query Goodmans >nul 2>&1
if not errorlevel 1 (
sc stop Goodmans >nul 2>&1
sc delete Goodmans >nul 2>&1
timeout /t 1 /nobreak >nul
)
echo [*] sc create + start
sc create Goodmans type= kernel binPath= "%SYS%"
if errorlevel 1 ( echo [X] sc create failed & exit /b 1 )
sc start Goodmans
if errorlevel 1 ( echo [X] sc start failed. check testsigning + cert import. & exit /b 1 )
echo.
echo [+] driver running. open DbgView as admin, filter on [goodmans]
echo then:
echo goodmans.exe load sample_guest.wasm
echo goodmans.exe call 1 run_all
echo goodmans.exe unload 1
endlocal
+23
View File
@@ -0,0 +1,23 @@
@echo off
setlocal
net session >nul 2>&1
if errorlevel 1 (
echo [X] must be run as Administrator.
pause
exit /b 1
)
sc query Goodmans >nul 2>&1
if errorlevel 1 (
echo [*] service not present
exit /b 0
)
echo [*] sc stop Goodmans
sc stop Goodmans >nul 2>&1
echo [*] sc delete Goodmans
sc delete Goodmans
endlocal
+48
View File
@@ -0,0 +1,48 @@
; Goodmans.inf - minimal WDM install
[Version]
Signature = "$WINDOWS NT$"
Class = System
ClassGuid = {4d36e97d-e325-11ce-bfc1-08002be10318}
Provider = %Provider%
DriverVer =
CatalogFile = Goodmans.cat
PnpLockdown = 1
[DestinationDirs]
DefaultDestDir = 12
[SourceDisksNames]
1 = %DiskName%
[SourceDisksFiles]
Goodmans.sys = 1
[Manufacturer]
%Provider% = Standard,NT$ARCH$
[Standard.NT$ARCH$]
%DeviceDesc% = Goodmans_Install,Root\Goodmans
[Goodmans_Install.NT]
CopyFiles = Goodmans.CopyFiles
[Goodmans_Install.NT.Services]
AddService = Goodmans,%SPSVCINST_ASSOCSERVICE%,Goodmans_Service
[Goodmans.CopyFiles]
Goodmans.sys
[Goodmans_Service]
DisplayName = %ServiceDesc%
ServiceType = 1
StartType = 3
ErrorControl = 1
ServiceBinary = %12%\Goodmans.sys
[Strings]
SPSVCINST_ASSOCSERVICE = 0x00000002
Provider = "Goodmans"
DeviceDesc = "Goodmans WASM Kernel VM"
ServiceDesc = "Goodmans"
DiskName = "Goodmans Install Disk"
+124
View File
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0001}</ProjectGuid>
<TemplateGuid>{497e31cb-056b-4f31-abb8-447fd55ee5a5}</TemplateGuid>
<TemplateVersion>10.0.26100.0</TemplateVersion>
<MinimumVisualStudioVersion>17.0</MinimumVisualStudioVersion>
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<Platform Condition="'$(Platform)' == ''">x64</Platform>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<RootNamespace>Goodmans</RootNamespace>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries Condition="'$(Configuration)'=='Debug'">true</UseDebugLibraries>
<UseDebugLibraries Condition="'$(Configuration)'=='Release'">false</UseDebugLibraries>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
<ConfigurationType>Driver</ConfigurationType>
<DriverType>WDM</DriverType>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<SpectreMitigation>false</SpectreMitigation>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<TreatWarningAsError>false</TreatWarningAsError>
<WarningLevel>Level3</WarningLevel>
<AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)kshim;$(ProjectDir)wasm3;$(ProjectDir)inc;$(ProjectDir)..\shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_KERNEL_MODE;d_m3HasFloat=0;d_m3RecordBacktraces=0;d_m3LogTimestamps=0;d_m3VerboseErrorMessages=0;d_m3EnableValidation=0;d_m3SkipStackCheck=1;d_m3SkipMemoryBoundsCheck=1;d_m3CascadedOpcodes=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<BufferSecurityCheck>false</BufferSecurityCheck>
<DisableSpecificWarnings>4100;4127;4152;4189;4201;4204;4214;4245;4267;4310;4459;4505;4706;4996;4055;4131;4132;4244;4324;4456;4457;4090</DisableSpecificWarnings>
<ForcedIncludeFiles>
</ForcedIncludeFiles>
</ClCompile>
<Link>
<SubSystem>Native</SubSystem>
<!-- /INTEGRITYCHECK sets IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY.
required for PsSetCreateProcessNotifyRoutineEx to succeed. -->
<AdditionalOptions>/INTEGRITYCHECK %(AdditionalOptions)</AdditionalOptions>
</Link>
<Inf>
<TimeStamp>*</TimeStamp>
</Inf>
<DriverSign>
<FileDigestAlgorithm>sha256</FileDigestAlgorithm>
</DriverSign>
</ItemDefinitionGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<EnableInf2cat>false</EnableInf2cat>
<SignMode>Off</SignMode>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<EnableInf2cat>false</EnableInf2cat>
<SignMode>Off</SignMode>
</PropertyGroup>
<ItemGroup>
<ClCompile Include="driver.c" />
<ClCompile Include="ioctl_handler.c" />
<ClCompile Include="module_table.c" />
<ClCompile Include="host_imports.c" />
<ClCompile Include="wasm_call.c" />
<ClCompile Include="log_ring.c" />
<ClCompile Include="trace_ring.c" />
<ClCompile Include="watchdog.c" />
<ClCompile Include="ih.c" />
<ClCompile Include="kshim\kshim.c" />
<ClCompile Include="wasm3\m3_bind.c" />
<ClCompile Include="wasm3\m3_code.c" />
<ClCompile Include="wasm3\m3_compile.c" />
<ClCompile Include="wasm3\m3_core.c" />
<ClCompile Include="wasm3\m3_env.c" />
<ClCompile Include="wasm3\m3_exec.c" />
<ClCompile Include="wasm3\m3_function.c" />
<ClCompile Include="wasm3\m3_info.c" />
<ClCompile Include="wasm3\m3_module.c" />
<ClCompile Include="wasm3\m3_parse.c" />
<ClCompile Include="wasm3\m3_validate.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="inc\gvm.h" />
<ClInclude Include="kshim\kshim.h" />
<ClInclude Include="kshim\stdio.h" />
<ClInclude Include="kshim\stdlib.h" />
<ClInclude Include="kshim\malloc.h" />
<ClInclude Include="kshim\corecrt.h" />
<ClInclude Include="..\shared\goodmans_ioctl.h" />
</ItemGroup>
<ItemGroup>
<FilesToPackage Include="$(TargetPath)" />
</ItemGroup>
<ItemGroup>
<Inf Include="Goodmans.inf" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
<Target Name="StageToDeploy" AfterTargets="Build">
<PropertyGroup>
<DeployDir>$(SolutionDir)deploy</DeployDir>
<SignedSys>$(OutDir)Goodmans.sys</SignedSys>
</PropertyGroup>
<Message Text="[deploy] staging driver -&gt; $(DeployDir)" Importance="high" />
<Exec Command="if exist &quot;$(DeployDir)\GoodmansTest.pfx&quot; for /f &quot;delims=&quot; %%s in ('where signtool 2^&gt;nul') do &quot;%%s&quot; sign /fd sha256 /f &quot;$(DeployDir)\GoodmansTest.pfx&quot; /p goodmans &quot;$(SignedSys)&quot;" IgnoreExitCode="true" ContinueOnError="true" />
<Copy SourceFiles="$(SignedSys)" DestinationFolder="$(DeployDir)" SkipUnchangedFiles="true" ContinueOnError="true" Retries="1" RetryDelayMilliseconds="200" />
<Copy SourceFiles="$(MSBuildProjectDirectory)\Goodmans.inf" DestinationFolder="$(DeployDir)" SkipUnchangedFiles="true" ContinueOnError="true" Retries="1" RetryDelayMilliseconds="200" />
</Target>
</Project>
+121
View File
@@ -0,0 +1,121 @@
/* driver.c - entry, device create, dispatch */
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
DRIVER_INITIALIZE DriverEntry;
static DRIVER_UNLOAD gvm_unload;
static DRIVER_DISPATCH gvm_create_close;
static DRIVER_DISPATCH gvm_device_control;
PDEVICE_OBJECT g_device;
static UNICODE_STRING g_symlink;
NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT drv, _In_ PUNICODE_STRING regpath)
{
UNREFERENCED_PARAMETER(regpath);
// filter mask so DbgView captures our lines without Verbose
DbgSetDebugFilterState(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, TRUE);
DbgSetDebugFilterState(DPFLTR_IHVDRIVER_ID, DPFLTR_WARNING_LEVEL, TRUE);
DbgSetDebugFilterState(DPFLTR_IHVDRIVER_ID, DPFLTR_TRACE_LEVEL, TRUE);
DbgSetDebugFilterState(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, TRUE);
gvm_log_init();
gvm_trace_init();
gvm_watchdog_start();
gvm_log("driver load");
UNICODE_STRING dev_name;
RtlInitUnicodeString(&dev_name, GVM_DEVICE_NAME_U);
NTSTATUS s = IoCreateDevice(drv, 0, &dev_name, GVM_DEVICE_TYPE,
FILE_DEVICE_SECURE_OPEN, FALSE, &g_device);
if (!NT_SUCCESS(s)) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] IoCreateDevice=%08x\n", s);
return s;
}
RtlInitUnicodeString(&g_symlink, GVM_SYMLINK_NAME_U);
s = IoCreateSymbolicLink(&g_symlink, &dev_name);
if (!NT_SUCCESS(s)) {
IoDeleteDevice(g_device);
g_device = NULL;
return s;
}
s = gvm_modtab_init();
if (!NT_SUCCESS(s)) {
IoDeleteSymbolicLink(&g_symlink);
IoDeleteDevice(g_device);
g_device = NULL;
return s;
}
drv->MajorFunction[IRP_MJ_CREATE] = gvm_create_close;
drv->MajorFunction[IRP_MJ_CLOSE] = gvm_create_close;
drv->MajorFunction[IRP_MJ_DEVICE_CONTROL] = gvm_device_control;
drv->DriverUnload = gvm_unload;
g_device->Flags |= DO_BUFFERED_IO;
g_device->Flags &= ~DO_DEVICE_INITIALIZING;
gvm_log("driver ready, device \\\\.\\Goodmans");
return STATUS_SUCCESS;
}
static VOID
gvm_unload(_In_ PDRIVER_OBJECT drv)
{
UNREFERENCED_PARAMETER(drv);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] unload\n");
gvm_ih_teardown();
gvm_watchdog_stop();
gvm_notify_teardown();
gvm_modtab_teardown();
if (g_symlink.Buffer)
IoDeleteSymbolicLink(&g_symlink);
if (g_device)
IoDeleteDevice(g_device);
}
static NTSTATUS
gvm_create_close(_In_ PDEVICE_OBJECT dev, _Inout_ PIRP irp)
{
UNREFERENCED_PARAMETER(dev);
irp->IoStatus.Status = STATUS_SUCCESS;
irp->IoStatus.Information = 0;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
static NTSTATUS
gvm_device_control(_In_ PDEVICE_OBJECT dev, _Inout_ PIRP irp)
{
UNREFERENCED_PARAMETER(dev);
PIO_STACK_LOCATION sp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS s = STATUS_INVALID_DEVICE_REQUEST;
switch (sp->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_GVM_LOAD_MODULE: s = gvm_ioctl_load(irp, sp); break;
case IOCTL_GVM_CALL_EXPORT: s = gvm_ioctl_call(irp, sp); break;
case IOCTL_GVM_UNLOAD_MODULE: s = gvm_ioctl_unload(irp, sp); break;
case IOCTL_GVM_LIST_MODULES: s = gvm_ioctl_list(irp, sp); break;
case IOCTL_GVM_MODULE_INFO: s = gvm_ioctl_info(irp, sp); break;
case IOCTL_GVM_UNLOAD_ALL: s = gvm_ioctl_unload_all(irp, sp); break;
case IOCTL_GVM_TAIL_LOG: s = gvm_ioctl_tail_log(irp, sp); break;
case IOCTL_GVM_READ_GUEST: s = gvm_ioctl_read_guest(irp, sp); break;
case IOCTL_GVM_TAIL_TRACE: s = gvm_ioctl_tail_trace(irp, sp); break;
case IOCTL_GVM_TRACE_CTL: s = gvm_ioctl_trace_ctl(irp, sp); break;
case IOCTL_GVM_FORCE_UNLOAD: s = gvm_ioctl_force_unload(irp, sp); break;
case IOCTL_GVM_NOTIFY_STOP: s = gvm_ioctl_notify_stop(irp, sp); break;
default: break;
}
irp->IoStatus.Status = s;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return s;
}
File diff suppressed because it is too large Load Diff
+94
View File
@@ -0,0 +1,94 @@
/* ih.c - the ONE piece of InfinityHook that cannot live in wasm.
*
* this file exposes a native trampoline that guests install into
* WMI_LOGGER_CONTEXT.GetCpuClock. everything else (nt base discovery,
* EtwpDebuggerData pattern scan, offset resolution, atomic pointer swap)
* lives in sample_guest/infinity_hook.c as a portable wasm demo.
*
* the trampoline runs at ETW callback IRQL (up to DISPATCH_LEVEL), samples
* calls, pushes a SYSCALL event onto the shared dispatch ring, and returns
* the real QPC. the ring is drained by the same dispatch worker that
* handles process/image notify events.
*/
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
extern void gvm_push_event_generic(unsigned int kind, uint32_t pid,
uint64_t a, uint64_t b, uint32_t c);
static volatile LONG64 g_ih_hits = 0;
static volatile LONG g_ih_inflight = 0;
static volatile LONG g_ih_rate = 1000;
// caller (guest) sets this before installing the hook so the trampoline
// can reject callers outside nt's image range (avoids false positives from
// unrelated ETW paths).
static volatile UINT64 g_ih_nt_lo = 0;
static volatile UINT64 g_ih_nt_hi = 0;
// signature matches WMI_LOGGER_CONTEXT.GetCpuClock: takes no args, returns
// a QPC value. must be __stdcall/default x64 ABI (matches).
static UINT64 gvm_ih_trampoline(void)
{
InterlockedIncrement(&g_ih_inflight);
LONG64 hits = InterlockedIncrement64(&g_ih_hits);
LONG rate = g_ih_rate;
if (rate <= 0) rate = 1;
if ((hits % rate) == 0) {
PVOID retaddr = _ReturnAddress();
UINT64 lo = g_ih_nt_lo, hi = g_ih_nt_hi;
BOOLEAN in_nt = (lo && (UINT64)(uintptr_t)retaddr >= lo &&
(UINT64)(uintptr_t)retaddr < hi);
if (in_nt || !lo) {
HANDLE tid = PsGetCurrentThreadId();
gvm_push_event_generic(3, // SYSCALL kind
(uint32_t)(uintptr_t)tid,
(UINT64)(uintptr_t)retaddr, // a1 in on_syscall
0,
(UINT32)hits); // a2 in on_syscall
}
}
LARGE_INTEGER qpc = KeQueryPerformanceCounter(NULL);
UINT64 rv = (UINT64)qpc.QuadPart;
InterlockedDecrement(&g_ih_inflight);
return rv;
}
// exposed as host imports: guests get the trampoline VA to plug into the
// GetCpuClock slot they located, and configure sampling / nt-range so the
// trampoline can skip non-syscall callers.
UINT64 gvm_ih_trampoline_addr(void)
{
return (UINT64)(uintptr_t)&gvm_ih_trampoline;
}
void gvm_ih_configure(UINT32 rate, UINT64 nt_base, UINT32 nt_size)
{
if (rate > 0) InterlockedExchange(&g_ih_rate, (LONG)rate);
g_ih_nt_lo = nt_base;
g_ih_nt_hi = nt_base + nt_size;
}
// spin until any in-flight trampoline calls drain. call after guest writes
// the original pointer back to the GetCpuClock slot but before it unloads.
void gvm_ih_wait_quiescent(void)
{
for (int i = 0; i < 1000 && g_ih_inflight > 0; i++) {
LARGE_INTEGER li; li.QuadPart = -10000; // 1ms
KeDelayExecutionThread(KernelMode, FALSE, &li);
}
}
// stats (guest asks for hit count so it can report accurately)
UINT64 gvm_ih_hit_count(void) { return (UINT64)g_ih_hits; }
// driver unload safety: nothing to tear down since we don't own the slot,
// but we can spin briefly in case guests left the hook installed.
void gvm_ih_teardown(void)
{
gvm_ih_wait_quiescent();
}
+103
View File
@@ -0,0 +1,103 @@
/* gvm.h - internal driver-wide declarations */
#pragma once
#include <ntddk.h>
#include "kshim/kshim.h"
#include "wasm3/wasm3.h"
#define GVM_TAG_MOD 'doMG'
#define GVM_TAG_WBUF 'BWMG'
#define GVM_STACK_DEFAULT (64u * 1024u)
typedef struct _gvm_module {
unsigned int id;
BOOLEAN used;
IM3Environment env;
IM3Runtime runtime;
IM3Module module;
unsigned char* wasm_bytes;
unsigned int wasm_size;
unsigned long long hash;
volatile LONG refcount; // touch only with Interlocked*
KMUTEX call_mutex; // held around all m3_* activity on this module
// pool budget for host_alloc/host_free
volatile LONG64 pool_used;
LONG64 pool_budget;
// capability bitmask parsed from the guest's __gvm_caps export.
// absent export defaults to GVM_CAP_ALL for backward compat.
unsigned int caps;
// cooperative abort deadline in QPC ticks. 0 = no limit
volatile ULONG64 exec_deadline_qpc;
// QPC tick at which call_mutex was acquired. watchdog polls this to
// find guests stuck past GVM_WATCHDOG_MAX_MS. 0 = not held
volatile ULONG64 mutex_hold_qpc;
// watchdog sets this to reject further calls into a wedged guest.
// subsequent force-unload skips waiting on call_mutex
volatile LONG poisoned;
char name[64];
} gvm_module;
// ioctl_handler.c
unsigned int gvm_read_module_caps(gvm_module* mod);
BOOLEAN gvm_deadline_exceeded(gvm_module* mod);
// wasm_call.c
M3Result gvm_call_locked(gvm_module* mod, IM3Function fn, unsigned int argc, const void** argp);
void gvm_set_deadline_ms(gvm_module* mod, unsigned int timeout_ms);
NTSTATUS gvm_modtab_init(void);
void gvm_modtab_teardown(void);
gvm_module* gvm_modtab_alloc(void);
gvm_module* gvm_modtab_get(unsigned int id);
gvm_module* gvm_modtab_find_by_hash(unsigned long long hash);
gvm_module* gvm_modtab_find_by_hash_incref(unsigned long long hash, unsigned int expected_size);
gvm_module* gvm_modtab_iter(unsigned int idx); // returns null past end
gvm_module* gvm_modtab_owner_of_runtime(IM3Runtime rt);
void gvm_modtab_free(gvm_module* m);
M3Result gvm_link_host_imports(IM3Module module);
M3Result gvm_link_kernel_fallback(IM3Module module);
void gvm_notify_init(void);
void gvm_notify_teardown(void);
// callback dispatch (host_imports.c)
void gvm_dispatch_start(void);
void gvm_dispatch_stop_signal(void); // just sets shutdown, returns
void gvm_dispatch_stop_wait(void); // signal + wait for worker exit (unload only)
NTSTATUS gvm_ioctl_load(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_call(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_unload(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_list(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_info(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_unload_all(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_tail_log(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_read_guest(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_tail_trace(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_trace_ctl(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_force_unload(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_notify_stop(PIRP irp, PIO_STACK_LOCATION sp);
// watchdog thread (watchdog.c)
void gvm_watchdog_start(void);
void gvm_watchdog_stop(void);
// infinity-hook (ih.c) - only the trampoline. install/uninstall happens in
// wasm via kernel FFI + gvm_write_u64.
unsigned long long gvm_ih_trampoline_addr(void);
void gvm_ih_configure(unsigned int rate, unsigned long long nt_base, unsigned int nt_size);
void gvm_ih_wait_quiescent(void);
unsigned long long gvm_ih_hit_count(void);
void gvm_ih_teardown(void);
// trace ring (trace_ring.c)
void gvm_trace_init(void);
void gvm_trace_push(unsigned int module_id, unsigned int kind, const char* name,
unsigned int argc, const unsigned long long* argv, unsigned long long rv);
BOOLEAN gvm_trace_enabled_for(unsigned int module_id);
// log ring (log_ring.c)
void gvm_log_init(void);
void gvm_log_push(const char* fmt, ...);
#define gvm_log(fmt, ...) gvm_log_push(fmt, ##__VA_ARGS__)
+457
View File
@@ -0,0 +1,457 @@
/* ioctl_handler.c - LOAD / CALL / UNLOAD / LIST / INFO / UNLOAD_ALL */
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
#include "wasm3/m3_env.h"
#include "wasm3/m3_function.h"
static void
copy_errmsg(char* dst, size_t dst_sz, const char* src)
{
if (!dst || dst_sz == 0) return;
dst[0] = 0;
if (src) RtlStringCbCopyA(dst, dst_sz, src);
}
// FNV-1a 64-bit hash for wasm-blob dedup
static unsigned long long fnv1a64(const unsigned char* p, size_t n)
{
unsigned long long h = 0xcbf29ce484222325ULL;
for (size_t i = 0; i < n; i++) {
h ^= p[i];
h *= 0x100000001b3ULL;
}
return h;
}
// reads the module's capability bitmask by invoking its exported __gvm_caps
// function. must run AFTER link so the guest can use hosts inside its manifest
// function if it needs to (typical case: returns a constant). if the export
// is absent, returns GVM_CAP_ALL (open policy, backward compat).
unsigned int gvm_read_module_caps(gvm_module* mod)
{
if (!mod || !mod->runtime) return GVM_CAP_ALL;
IM3Function fn = NULL;
M3Result r = m3_FindFunction(&fn, mod->runtime, "__gvm_caps");
if (r || !fn) return GVM_CAP_ALL;
// must be called under the mutex + big stack, but at this point no one
// else has a handle on this module yet. still, use the standard path.
// start with wide-open caps so the manifest call itself isn't gated.
mod->caps = GVM_CAP_ALL;
r = gvm_call_locked(mod, fn, 0, NULL);
if (r) return GVM_CAP_ALL;
unsigned int caps = 0;
void* rvp = &caps;
m3_GetResults(fn, 1, (const void**)&rvp);
return caps ? caps : GVM_CAP_ALL;
}
BOOLEAN gvm_deadline_exceeded(gvm_module* mod)
{
if (!mod || mod->exec_deadline_qpc == 0) return FALSE;
LARGE_INTEGER now = KeQueryPerformanceCounter(NULL);
return (ULONG64)now.QuadPart >= mod->exec_deadline_qpc;
}
NTSTATUS
gvm_ioctl_load(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_load_in) || out_len < sizeof(gvm_load_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_load_in in;
RtlCopyMemory(&in, buf, sizeof(in));
if (in.wasm_size == 0 || in.wasm_size > (16u * 1024u * 1024u)) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
if (in_len < sizeof(gvm_load_in) + in.wasm_size) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_load_out out = { 0 };
unsigned char* wasm_start = (unsigned char*)buf + sizeof(gvm_load_in);
// dedup: hash first, return existing module id if match. atomic incref
// via find_by_hash_incref so a concurrent unload can't win the race.
unsigned long long h = fnv1a64(wasm_start, in.wasm_size);
gvm_module* existing = gvm_modtab_find_by_hash_incref(h, in.wasm_size);
if (existing) {
out.module_id = existing->id;
out.status = 0;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "reused");
goto done;
}
gvm_module* mod = gvm_modtab_alloc();
if (!mod) {
out.status = -1;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "module table full");
goto done;
}
mod->wasm_bytes = (unsigned char*)ExAllocatePoolWithTag(NonPagedPoolNx, in.wasm_size, GVM_TAG_WBUF);
if (!mod->wasm_bytes) {
gvm_modtab_free(mod);
out.status = -2;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "wasm alloc failed");
goto done;
}
RtlCopyMemory(mod->wasm_bytes, wasm_start, in.wasm_size);
mod->wasm_size = in.wasm_size;
mod->hash = h;
mod->pool_budget = in.pool_budget ? (LONG64)in.pool_budget : (4LL * 1024 * 1024);
mod->pool_used = 0;
mod->caps = GVM_CAP_ALL; // set to real value after link+manifest call
mod->exec_deadline_qpc = 0;
RtlStringCbCopyA(mod->name, sizeof(mod->name), in.name[0] ? in.name : "guest");
mod->env = m3_NewEnvironment();
if (!mod->env) {
gvm_modtab_free(mod);
out.status = -3;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "NewEnvironment failed");
goto done;
}
unsigned int stack = in.stack_bytes ? in.stack_bytes : GVM_STACK_DEFAULT;
mod->runtime = m3_NewRuntime(mod->env, stack, NULL);
if (!mod->runtime) {
gvm_modtab_free(mod);
out.status = -4;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "NewRuntime failed");
goto done;
}
M3Result r = m3_ParseModule(mod->env, &mod->module, mod->wasm_bytes, mod->wasm_size);
if (r) {
gvm_modtab_free(mod);
out.status = -5;
copy_errmsg(out.err_msg, sizeof(out.err_msg), r);
goto done;
}
r = m3_LoadModule(mod->runtime, mod->module);
if (r) {
gvm_modtab_free(mod);
out.status = -6;
copy_errmsg(out.err_msg, sizeof(out.err_msg), r);
goto done;
}
r = gvm_link_host_imports(mod->module);
if (r && r != m3Err_functionLookupFailed) {
gvm_modtab_free(mod);
out.status = -7;
copy_errmsg(out.err_msg, sizeof(out.err_msg), r);
goto done;
}
// resolve any leftover env.* imports against ntoskrnl/hal export table
gvm_link_kernel_fallback(mod->module);
// read the guest's declared capability manifest (if any) and lock down.
mod->caps = gvm_read_module_caps(mod);
gvm_log("load module_id=%u name=%s wasm=%u bytes hash=%016llx caps=%08x",
mod->id, mod->name, mod->wasm_size, mod->hash, mod->caps);
out.module_id = mod->id;
out.status = 0;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "ok");
done:
RtlCopyMemory(buf, &out, sizeof(out));
irp->IoStatus.Information = sizeof(out);
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_call(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_call_in) || out_len < sizeof(gvm_call_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_call_in in;
RtlCopyMemory(&in, buf, sizeof(in));
in.export_name[sizeof(in.export_name) - 1] = 0;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] IOCTL_CALL mod=%u export=%s argc=%u\n",
in.module_id, in.export_name, in.argc);
gvm_call_out out = { 0 };
gvm_module* mod = gvm_modtab_get(in.module_id);
if (!mod) {
out.status = -1;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "no such module");
goto done;
}
IM3Function fn = NULL;
M3Result r = m3Err_none;
__try {
r = m3_FindFunction(&fn, mod->runtime, in.export_name);
} __except (EXCEPTION_EXECUTE_HANDLER) {
r = "m3_FindFunction raised kernel exception";
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] m3_FindFunction export=%s fn=%p r=%s\n",
in.export_name, fn, r ? r : "ok");
if (r || !fn) {
out.status = -2;
copy_errmsg(out.err_msg, sizeof(out.err_msg), r ? r : "export not found");
goto done;
}
if (in.argc > GVM_MAX_ARGS) in.argc = GVM_MAX_ARGS;
const void* argp[GVM_MAX_ARGS];
for (unsigned int i = 0; i < in.argc; i++)
argp[i] = &in.argv[i];
gvm_set_deadline_ms(mod, in.timeout_ms);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] pre-gvm_call_locked mod=%u fn=%p export=%s\n",
in.module_id, fn, in.export_name);
r = gvm_call_locked(mod, fn, in.argc, argp);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] post-gvm_call_locked r=%s\n", r ? r : "ok");
if (!r && gvm_deadline_exceeded(mod)) r = "execution deadline exceeded";
if (r) {
out.status = -3;
copy_errmsg(out.err_msg, sizeof(out.err_msg), r);
M3ErrorInfo einfo = { 0 };
m3_GetErrorInfo(mod->runtime, &einfo);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[goodmans] m3_Call fail: r=%s message=%s file=%s line=%u\n",
r, einfo.message ? einfo.message : "(none)",
einfo.file ? einfo.file : "(none)", einfo.line);
goto done;
}
unsigned long long rv = 0;
void* rvp = &rv;
r = m3_GetResults(fn, 1, (const void**)&rvp);
// r may be non-null when the export returns void
out.rv = rv;
out.status = 0;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "ok");
gvm_log("call mod=%u %s(argc=%u) rv=0x%llx", in.module_id, in.export_name, in.argc, rv);
done:
RtlCopyMemory(buf, &out, sizeof(out));
irp->IoStatus.Information = sizeof(out);
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_unload(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_unload_in) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_unload_in in;
RtlCopyMemory(&in, buf, sizeof(in));
gvm_module* mod = gvm_modtab_get(in.module_id);
if (!mod) {
irp->IoStatus.Information = 0;
return STATUS_NOT_FOUND;
}
gvm_modtab_free(mod);
irp->IoStatus.Information = 0;
return STATUS_SUCCESS;
}
static void fill_entry(gvm_module_entry* e, const gvm_module* m)
{
e->id = m->id;
e->wasm_size = m->wasm_size;
e->hash = m->hash;
e->exports = 0;
e->mem_pages = 0;
e->pool_bytes = (unsigned long long)m->pool_used;
if (m->runtime) {
uint32_t mem_sz = 0;
m3_GetMemory(m->runtime, &mem_sz, 0);
e->mem_pages = mem_sz / 65536;
}
RtlCopyMemory(e->name, m->name, sizeof(e->name));
}
NTSTATUS
gvm_ioctl_list(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (out_len < sizeof(gvm_list_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_list_out out = { 0 };
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
gvm_module* m = gvm_modtab_iter(i);
if (!m || !m->used) continue;
if (out.count >= GVM_MAX_MODULES) break;
fill_entry(&out.entries[out.count], m);
out.count++;
}
RtlCopyMemory(buf, &out, sizeof(out));
irp->IoStatus.Information = sizeof(out);
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_info(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_info_in) || out_len < sizeof(gvm_info_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_info_in in;
RtlCopyMemory(&in, buf, sizeof(in));
gvm_info_out out = { 0 };
gvm_module* mod = gvm_modtab_get(in.module_id);
if (!mod) {
out.status = -1;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "no such module");
goto done;
}
fill_entry(&out.base, mod);
// enumerate exports + imports from the wasm3 module chain
if (mod->runtime) {
IM3Module m = mod->runtime->modules;
while (m) {
for (u32 i = 0; i < m->numFunctions; i++) {
IM3Function fn = &m->functions[i];
if (i < m->numFuncImports && fn->import.fieldUtf8 && out.import_count < GVM_MAX_INFO_IMPORTS) {
RtlStringCbCopyA(out.imports[out.import_count], GVM_INFO_NAME_LEN, fn->import.fieldUtf8);
out.import_count++;
}
if (fn->export_name && out.export_count < GVM_MAX_INFO_EXPORTS) {
RtlStringCbCopyA(out.exports[out.export_count], GVM_INFO_NAME_LEN, fn->export_name);
out.export_count++;
}
}
m = m->next;
}
}
out.status = 0;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "ok");
done:
RtlCopyMemory(buf, &out, sizeof(out));
irp->IoStatus.Information = sizeof(out);
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_unload_all(PIRP irp, PIO_STACK_LOCATION sp)
{
UNREFERENCED_PARAMETER(sp);
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
gvm_module* m = gvm_modtab_iter(i);
if (!m || !m->used) continue;
// drain refcount atomically to 1, then free (which decrements to 0)
while (InterlockedCompareExchange(&m->refcount, 1, m->refcount) != 1) {
if (!m->used) break;
}
gvm_modtab_free(m);
}
irp->IoStatus.Information = 0;
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_read_guest(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_read_guest_in) || out_len < sizeof(gvm_read_guest_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_read_guest_in in;
RtlCopyMemory(&in, buf, sizeof(in));
gvm_read_guest_out out;
RtlZeroMemory(&out, sizeof(out));
if (in.length == 0 || in.length > GVM_MAX_READ_GUEST_BYTES) {
out.status = -1;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "bad length");
goto done;
}
gvm_module* mod = gvm_modtab_get(in.module_id);
if (!mod || !mod->runtime) {
out.status = -2;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "no such module");
goto done;
}
uint32_t mem_sz = 0;
uint8_t* mem = m3_GetMemory(mod->runtime, &mem_sz, 0);
if (!mem) {
out.status = -3;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "no memory");
goto done;
}
if ((uint64_t)in.offset + in.length > mem_sz) {
out.status = -4;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "out of range");
goto done;
}
RtlCopyMemory(out.data, mem + in.offset, in.length);
out.length = in.length;
out.status = 0;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "ok");
done:
RtlCopyMemory(buf, &out, sizeof(out));
irp->IoStatus.Information = sizeof(out);
return STATUS_SUCCESS;
}
+2
View File
@@ -0,0 +1,2 @@
/* corecrt.h - blocks ucrt/corecrt.h chain */
#pragma once
+28
View File
@@ -0,0 +1,28 @@
/* inttypes.h - printf macros wasm3 uses */
#pragma once
#include <stdint.h>
#define PRId8 "d"
#define PRIi8 "i"
#define PRIu8 "u"
#define PRIx8 "x"
#define PRIX8 "X"
#define PRId16 "d"
#define PRIi16 "i"
#define PRIu16 "u"
#define PRIx16 "x"
#define PRIX16 "X"
#define PRId32 "d"
#define PRIi32 "i"
#define PRIu32 "u"
#define PRIx32 "x"
#define PRIX32 "X"
#define PRId64 "lld"
#define PRIi64 "lli"
#define PRIu64 "llu"
#define PRIx64 "llx"
#define PRIX64 "llX"
+248
View File
@@ -0,0 +1,248 @@
/* kshim.c - stdlib/stdio bodies wasm3 links against */
#include "kshim.h"
#include "stdio.h"
#include "stdlib.h"
FILE* const stdout = (FILE*)(void*)1;
FILE* const stderr = (FILE*)(void*)2;
FILE* const stdin = (FILE*)(void*)3;
typedef struct {
size_t size;
unsigned char data[1];
} kshim_hdr;
#define HDR_OFF FIELD_OFFSET(kshim_hdr, data)
void* __cdecl malloc(size_t n)
{
if (n == 0)
return NULL;
kshim_hdr* h = (kshim_hdr*)ExAllocatePoolWithTag(NonPagedPoolNx, n + HDR_OFF, GVM_POOL_TAG);
if (!h)
return NULL;
h->size = n;
return h->data;
}
void* __cdecl calloc(size_t n, size_t sz)
{
size_t total = n * sz;
if (sz != 0 && total / sz != n)
return NULL;
void* p = malloc(total);
if (p)
RtlZeroMemory(p, total);
return p;
}
void __cdecl free(void* p)
{
if (!p)
return;
kshim_hdr* h = (kshim_hdr*)((unsigned char*)p - HDR_OFF);
ExFreePoolWithTag(h, GVM_POOL_TAG);
}
void* __cdecl realloc(void* p, size_t new_sz)
{
if (!p)
return malloc(new_sz);
if (new_sz == 0) {
free(p);
return NULL;
}
kshim_hdr* h = (kshim_hdr*)((unsigned char*)p - HDR_OFF);
size_t old_sz = h->size;
if (old_sz >= new_sz)
return p;
void* np = malloc(new_sz);
if (!np)
return NULL;
RtlCopyMemory(np, p, old_sz);
free(p);
return np;
}
void __cdecl abort(void)
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] abort\n");
__fastfail(0);
}
void __cdecl exit(int status)
{
UNREFERENCED_PARAMETER(status);
abort();
}
int __cdecl printf(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
ULONG r = vDbgPrintExWithPrefix("[goodmans] ", DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, fmt, ap);
va_end(ap);
return (int)r;
}
int __cdecl fprintf(FILE* stream, const char* fmt, ...)
{
UNREFERENCED_PARAMETER(stream);
va_list ap;
va_start(ap, fmt);
ULONG r = vDbgPrintExWithPrefix("[goodmans] ", DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, fmt, ap);
va_end(ap);
return (int)r;
}
int __cdecl snprintf(char* buf, size_t sz, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
NTSTATUS s = RtlStringCbVPrintfA(buf, sz, fmt, ap);
va_end(ap);
if (!NT_SUCCESS(s))
return -1;
size_t len = 0;
while (buf[len]) len++;
return (int)len;
}
int __cdecl vsnprintf(char* buf, size_t sz, const char* fmt, va_list ap)
{
NTSTATUS s = RtlStringCbVPrintfA(buf, sz, fmt, ap);
if (!NT_SUCCESS(s))
return -1;
size_t len = 0;
while (buf[len]) len++;
return (int)len;
}
int __cdecl puts(const char* s)
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] %s\n", s ? s : "");
return 0;
}
int __cdecl fputs(const char* s, FILE* stream)
{
UNREFERENCED_PARAMETER(stream);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] %s", s ? s : "");
return 0;
}
int __cdecl fputc(int c, FILE* stream)
{
UNREFERENCED_PARAMETER(stream);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "%c", c);
return c;
}
int __cdecl putchar(int c)
{
return fputc(c, NULL);
}
int __cdecl fflush(FILE* stream)
{
UNREFERENCED_PARAMETER(stream);
return 0;
}
// m3_CallArgv wants strtoul/strtoull, base-10 or 0x hex
static int _kshim_digit(int c, int base)
{
int v = -1;
if (c >= '0' && c <= '9') v = c - '0';
else if (c >= 'a' && c <= 'z') v = c - 'a' + 10;
else if (c >= 'A' && c <= 'Z') v = c - 'A' + 10;
if (v < 0 || v >= base) return -1;
return v;
}
unsigned long __cdecl strtoul(const char* nptr, char** endptr, int base)
{
const char* p = nptr;
while (*p == ' ' || *p == '\t') p++;
if (*p == '+') p++;
if (base == 0) {
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { base = 16; p += 2; }
else if (p[0] == '0') { base = 8; p++; }
else base = 10;
} else if (base == 16 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
p += 2;
}
unsigned long acc = 0;
int d;
while ((d = _kshim_digit((unsigned char)*p, base)) >= 0) {
acc = acc * (unsigned long)base + (unsigned long)d;
p++;
}
if (endptr) *endptr = (char*)p;
return acc;
}
unsigned long long __cdecl strtoull(const char* nptr, char** endptr, int base)
{
const char* p = nptr;
while (*p == ' ' || *p == '\t') p++;
if (*p == '+') p++;
if (base == 0) {
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { base = 16; p += 2; }
else if (p[0] == '0') { base = 8; p++; }
else base = 10;
} else if (base == 16 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
p += 2;
}
unsigned long long acc = 0;
int d;
while ((d = _kshim_digit((unsigned char)*p, base)) >= 0) {
acc = acc * (unsigned long long)base + (unsigned long long)d;
p++;
}
if (endptr) *endptr = (char*)p;
return acc;
}
// kshim_* aliases for direct kshim.h consumers
void* kshim_alloc(size_t n) { return malloc(n); }
void* kshim_calloc(size_t n, size_t sz) { return calloc(n, sz); }
void* kshim_realloc(void* p, size_t new_sz){ return realloc(p, new_sz); }
void kshim_free(void* p) { free(p); }
void kshim_abort(const char* msg) { UNREFERENCED_PARAMETER(msg); abort(); }
int kshim_printf(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
ULONG r = vDbgPrintExWithPrefix("[goodmans] ", DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, fmt, ap);
va_end(ap);
return (int)r;
}
int kshim_snprintf(char* buf, size_t sz, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
NTSTATUS s = RtlStringCbVPrintfA(buf, sz, fmt, ap);
va_end(ap);
if (!NT_SUCCESS(s)) return -1;
size_t len = 0; while (buf[len]) len++;
return (int)len;
}
int kshim_vsnprintf(char* buf, size_t sz, const char* fmt, va_list ap)
{
NTSTATUS s = RtlStringCbVPrintfA(buf, sz, fmt, ap);
if (!NT_SUCCESS(s)) return -1;
size_t len = 0; while (buf[len]) len++;
return (int)len;
}
+24
View File
@@ -0,0 +1,24 @@
/* kshim.h - interface for kshim.c and driver TUs */
#pragma once
#include <ntddk.h>
#include <ntstrsafe.h>
#define GVM_POOL_TAG 'MVoG'
#ifdef __cplusplus
extern "C" {
#endif
void* kshim_alloc(size_t n);
void* kshim_calloc(size_t n, size_t sz);
void* kshim_realloc(void* p, size_t new_sz);
void kshim_free(void* p);
void kshim_abort(const char* msg);
int kshim_printf(const char* fmt, ...);
int kshim_snprintf(char* buf, size_t sz, const char* fmt, ...);
int kshim_vsnprintf(char* buf, size_t sz, const char* fmt, va_list ap);
#ifdef __cplusplus
}
#endif
+2
View File
@@ -0,0 +1,2 @@
/* malloc.h - blocks km/crt/malloc.h, decls in stdlib.h */
#pragma once
+29
View File
@@ -0,0 +1,29 @@
/* stdio.h - kshim decls, bodies in kshim.c */
#pragma once
#include <stdarg.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void FILE;
extern FILE* const stdout;
extern FILE* const stderr;
extern FILE* const stdin;
int __cdecl printf(const char* fmt, ...);
int __cdecl fprintf(FILE* stream, const char* fmt, ...);
int __cdecl snprintf(char* buf, size_t sz, const char* fmt, ...);
int __cdecl vsnprintf(char* buf, size_t sz, const char* fmt, va_list ap);
int __cdecl puts(const char* s);
int __cdecl fputs(const char* s, FILE* stream);
int __cdecl fputc(int c, FILE* stream);
int __cdecl putchar(int c);
int __cdecl fflush(FILE* stream);
#ifdef __cplusplus
}
#endif
+25
View File
@@ -0,0 +1,25 @@
/* stdlib.h - kshim decls */
#pragma once
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef NULL
#define NULL ((void*)0)
#endif
void* __cdecl malloc(size_t n);
void* __cdecl calloc(size_t n, size_t sz);
void* __cdecl realloc(void* p, size_t new_sz);
void __cdecl free(void* p);
void __cdecl abort(void);
void __cdecl exit(int status);
#define _abs64(x) ((x) < 0 ? -(x) : (x))
#ifdef __cplusplus
}
#endif
+95
View File
@@ -0,0 +1,95 @@
/* log_ring.c - kernel-side ring of recent debug lines. GUI polls via ioctl. */
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
static gvm_log_entry g_ring[GVM_LOG_ENTRIES];
static ULONG64 g_seq = 0; // strictly increasing, monotonic
static ULONG g_head = 0; // next write slot
static KSPIN_LOCK g_lock;
static BOOLEAN g_init = FALSE;
void
gvm_log_init(void)
{
if (g_init) return;
RtlZeroMemory(g_ring, sizeof(g_ring));
KeInitializeSpinLock(&g_lock);
g_init = TRUE;
}
void
gvm_log_push(const char* fmt, ...)
{
if (!g_init) return;
char tmp[GVM_LOG_ENTRY_LEN];
va_list ap; va_start(ap, fmt);
RtlStringCbVPrintfA(tmp, sizeof(tmp), fmt, ap);
va_end(ap);
// also mirror to DbgPrint so DebugView keeps working
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] %s\n", tmp);
LARGE_INTEGER now;
KeQuerySystemTimePrecise(&now);
KIRQL irql;
KeAcquireSpinLock(&g_lock, &irql);
gvm_log_entry* e = &g_ring[g_head];
e->timestamp_100ns = (ULONG64)now.QuadPart;
RtlStringCbCopyA(e->line, sizeof(e->line), tmp);
g_seq++;
g_head = (g_head + 1) % GVM_LOG_ENTRIES;
KeReleaseSpinLock(&g_lock, irql);
}
NTSTATUS
gvm_ioctl_tail_log(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_tail_in) || out_len < sizeof(gvm_tail_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_tail_in in;
RtlCopyMemory(&in, buf, sizeof(in));
gvm_tail_out* out = (gvm_tail_out*)ExAllocatePoolWithTag(
NonPagedPoolNx, sizeof(gvm_tail_out), 'gLog');
if (!out) return STATUS_INSUFFICIENT_RESOURCES;
RtlZeroMemory(out, sizeof(*out));
KIRQL irql;
KeAcquireSpinLock(&g_lock, &irql);
ULONG64 seq_here = g_seq;
ULONG64 want_from = in.last_seq;
ULONG64 available = 0;
if (seq_here > want_from) {
available = seq_here - want_from;
if (available > GVM_LOG_ENTRIES) {
out->dropped = (unsigned int)(available - GVM_LOG_ENTRIES);
available = GVM_LOG_ENTRIES;
}
}
// walk backwards from head, taking `available` newest entries in order
ULONG head = g_head;
for (ULONG64 i = 0; i < available; i++) {
ULONG idx = (head + GVM_LOG_ENTRIES - (ULONG)available + (ULONG)i) % GVM_LOG_ENTRIES;
out->entries[i] = g_ring[idx];
}
out->count = (unsigned int)available;
out->next_seq = seq_here;
KeReleaseSpinLock(&g_lock, irql);
RtlCopyMemory(buf, out, sizeof(*out));
ExFreePoolWithTag(out, 'gLog');
irp->IoStatus.Information = sizeof(gvm_tail_out);
return STATUS_SUCCESS;
}
+150
View File
@@ -0,0 +1,150 @@
/* module_table.c - fixed-size slot allocator for loaded modules.
* refcount is atomic (LONG). teardown holds the module's call_mutex so
* any in-flight m3_Call has to finish before the runtime is freed. */
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
static gvm_module g_modules[GVM_MAX_MODULES];
static KSPIN_LOCK g_lock;
NTSTATUS
gvm_modtab_init(void)
{
RtlZeroMemory(g_modules, sizeof(g_modules));
KeInitializeSpinLock(&g_lock);
// init every mutex up front so iterators that wait on all slots (e.g. the
// callback dispatch worker) don't touch an uninitialized DISPATCHER_HEADER
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++)
KeInitializeMutex(&g_modules[i].call_mutex, 0);
return STATUS_SUCCESS;
}
void
gvm_modtab_teardown(void)
{
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
if (g_modules[i].used) {
g_modules[i].refcount = 1; // force teardown
gvm_modtab_free(&g_modules[i]);
}
}
}
gvm_module*
gvm_modtab_alloc(void)
{
KIRQL irql;
gvm_module* out = NULL;
KeAcquireSpinLock(&g_lock, &irql);
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
if (!g_modules[i].used) {
g_modules[i].used = TRUE;
g_modules[i].id = i + 1;
g_modules[i].refcount = 1;
KeInitializeMutex(&g_modules[i].call_mutex, 0);
out = &g_modules[i];
break;
}
}
KeReleaseSpinLock(&g_lock, irql);
return out;
}
gvm_module*
gvm_modtab_get(unsigned int id)
{
if (id == 0 || id > GVM_MAX_MODULES) return NULL;
gvm_module* m = &g_modules[id - 1];
return m->used ? m : NULL;
}
gvm_module*
gvm_modtab_find_by_hash(unsigned long long hash)
{
KIRQL irql;
gvm_module* out = NULL;
KeAcquireSpinLock(&g_lock, &irql);
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
if (g_modules[i].used && g_modules[i].hash == hash) {
out = &g_modules[i];
break;
}
}
KeReleaseSpinLock(&g_lock, irql);
return out;
}
// atomic find + increment under the table lock. safer than
// find_by_hash + separate refcount++ which races with concurrent unload.
gvm_module*
gvm_modtab_find_by_hash_incref(unsigned long long hash, unsigned int expected_size)
{
KIRQL irql;
gvm_module* out = NULL;
KeAcquireSpinLock(&g_lock, &irql);
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
if (g_modules[i].used
&& g_modules[i].hash == hash
&& g_modules[i].wasm_size == expected_size) {
InterlockedIncrement(&g_modules[i].refcount);
out = &g_modules[i];
break;
}
}
KeReleaseSpinLock(&g_lock, irql);
return out;
}
gvm_module*
gvm_modtab_iter(unsigned int idx)
{
if (idx >= GVM_MAX_MODULES) return NULL;
return &g_modules[idx];
}
gvm_module*
gvm_modtab_owner_of_runtime(IM3Runtime rt)
{
if (!rt) return NULL;
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
if (g_modules[i].used && g_modules[i].runtime == rt)
return &g_modules[i];
}
return NULL;
}
void
gvm_modtab_free(gvm_module* m)
{
if (!m || !m->used) return;
// only the last reference proceeds to teardown
if (InterlockedDecrement(&m->refcount) > 0) return;
// block until any in-flight m3_Call finishes so the runtime is quiescent
KeWaitForSingleObject(&m->call_mutex, Executive, KernelMode, FALSE, NULL);
if (m->runtime) {
m3_FreeRuntime(m->runtime);
m->runtime = NULL;
}
if (m->env) {
m3_FreeEnvironment(m->env);
m->env = NULL;
}
if (m->wasm_bytes) {
ExFreePoolWithTag(m->wasm_bytes, GVM_TAG_WBUF);
m->wasm_bytes = NULL;
}
m->module = NULL;
m->wasm_size = 0;
m->hash = 0;
// publish used=FALSE before releasing so waiters see it on retry
m->used = FALSE;
KeReleaseMutex(&m->call_mutex, FALSE);
}
+137
View File
@@ -0,0 +1,137 @@
/* trace_ring.c - per-import call trace for wasm guest debugging.
* ring buffer holds recent host-import invocations, export calls, and traps.
* GUI polls via ioctl for live view.
*/
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
static gvm_trace_entry g_ring[GVM_TRACE_ENTRIES];
static ULONG64 g_seq = 0;
static ULONG g_head = 0;
static KSPIN_LOCK g_lock;
static BOOLEAN g_init = FALSE;
static volatile LONG g_mode = GVM_TRACE_OFF;
static volatile LONG g_only = 0; // module_id filter when mode==ON_MODULE
void
gvm_trace_init(void)
{
if (g_init) return;
RtlZeroMemory(g_ring, sizeof(g_ring));
KeInitializeSpinLock(&g_lock);
g_init = TRUE;
}
BOOLEAN
gvm_trace_enabled_for(unsigned int module_id)
{
LONG m = g_mode;
if (m == GVM_TRACE_OFF) return FALSE;
if (m == GVM_TRACE_ON_ALL) return TRUE;
return (LONG)module_id == g_only;
}
void
gvm_trace_push(unsigned int module_id, unsigned int kind, const char* name,
unsigned int argc, const ULONG64* argv, ULONG64 rv)
{
if (!g_init) return;
if (!gvm_trace_enabled_for(module_id)) return;
LARGE_INTEGER now;
KeQuerySystemTimePrecise(&now);
// capture pre-lock so lock IRQL doesn't confuse the value
unsigned int cap_irql = (unsigned int)KeGetCurrentIrql();
unsigned int cap_tid = (unsigned int)(ULONG_PTR)PsGetCurrentThreadId();
KIRQL irql;
KeAcquireSpinLock(&g_lock, &irql);
gvm_trace_entry* e = &g_ring[g_head];
RtlZeroMemory(e, sizeof(*e));
e->timestamp_100ns = (ULONG64)now.QuadPart;
e->module_id = module_id;
e->kind = kind;
e->thread_id = cap_tid;
e->irql = cap_irql;
e->argc = argc > 4 ? 4 : argc;
for (unsigned int i = 0; i < e->argc; i++) e->argv[i] = argv[i];
e->rv = rv;
if (name) RtlStringCbCopyA(e->name, sizeof(e->name), name);
g_seq++;
g_head = (g_head + 1) % GVM_TRACE_ENTRIES;
KeReleaseSpinLock(&g_lock, irql);
}
NTSTATUS
gvm_ioctl_tail_trace(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_trace_in) || out_len < sizeof(gvm_trace_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_trace_in in;
RtlCopyMemory(&in, buf, sizeof(in));
gvm_trace_out* out = (gvm_trace_out*)ExAllocatePoolWithTag(
NonPagedPoolNx, sizeof(gvm_trace_out), 'gTrc');
if (!out) return STATUS_INSUFFICIENT_RESOURCES;
RtlZeroMemory(out, sizeof(*out));
KIRQL irql;
KeAcquireSpinLock(&g_lock, &irql);
ULONG64 seq_here = g_seq;
ULONG64 want_from = in.last_seq;
ULONG64 available = 0;
ULONG64 dropped = 0;
if (seq_here > want_from) {
available = seq_here - want_from;
if (available > GVM_TRACE_ENTRIES) {
dropped = available - GVM_TRACE_ENTRIES;
available = GVM_TRACE_ENTRIES;
}
ULONG start = (g_head + GVM_TRACE_ENTRIES - (ULONG)available) % GVM_TRACE_ENTRIES;
for (ULONG64 i = 0; i < available; i++) {
ULONG src = (start + (ULONG)i) % GVM_TRACE_ENTRIES;
out->entries[i] = g_ring[src];
}
}
out->next_seq = seq_here;
out->count = (unsigned int)available;
out->dropped = (unsigned int)dropped;
KeReleaseSpinLock(&g_lock, irql);
RtlCopyMemory(buf, out, sizeof(*out));
ExFreePoolWithTag(out, 'gTrc');
irp->IoStatus.Information = sizeof(gvm_trace_out);
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_trace_ctl(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_trace_ctl_in) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_trace_ctl_in in;
RtlCopyMemory(&in, buf, sizeof(in));
InterlockedExchange(&g_mode, (LONG)in.mode);
InterlockedExchange(&g_only, (LONG)in.module_id);
gvm_log_push("trace mode=%u module=%u", in.mode, in.module_id);
irp->IoStatus.Information = 0;
return STATUS_SUCCESS;
}
+175
View File
@@ -0,0 +1,175 @@
//
// m3_bind.c
//
// Created by Steven Massey on 4/29/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#include "m3_env.h"
#include "m3_exception.h"
#include "m3_info.h"
u8 ConvertTypeCharToTypeId (char i_code)
{
switch (i_code) {
case 'v': return c_m3Type_none;
case 'i': return c_m3Type_i32;
case 'I': return c_m3Type_i64;
case 'f': return c_m3Type_f32;
case 'F': return c_m3Type_f64;
case '*': return c_m3Type_i32;
}
return c_m3Type_unknown;
}
M3Result SignatureToFuncType (IM3FuncType * o_functionType, ccstr_t i_signature)
{
IM3FuncType funcType = NULL;
_try {
if (not o_functionType)
_throw ("null function type");
if (not i_signature)
_throw ("null function signature");
cstr_t sig = i_signature;
size_t maxNumTypes = strlen (i_signature);
// assume min signature is "()"
_throwif (m3Err_malformedFunctionSignature, maxNumTypes < 2);
maxNumTypes -= 2;
_throwif (m3Err_tooManyArgsRets, maxNumTypes > d_m3MaxSaneFunctionArgRetCount);
_ (AllocFuncType (& funcType, (u32) maxNumTypes));
u8 * typelist = funcType->types;
bool parsingRets = true;
while (* sig)
{
char typeChar = * sig++;
if (typeChar == '(')
{
parsingRets = false;
continue;
}
else if ( typeChar == ' ')
continue;
else if (typeChar == ')')
break;
u8 type = ConvertTypeCharToTypeId (typeChar);
_throwif ("unknown argument type char", c_m3Type_unknown == type);
if (type == c_m3Type_none)
continue;
if (parsingRets)
{
_throwif ("malformed signature; return count overflow", funcType->numRets >= maxNumTypes);
funcType->numRets++;
*typelist++ = type;
}
else
{
_throwif ("malformed signature; arg count overflow", (u32)(funcType->numRets) + funcType->numArgs >= maxNumTypes);
funcType->numArgs++;
*typelist++ = type;
}
}
} _catch:
if (result)
m3_Free (funcType);
* o_functionType = funcType;
return result;
}
static
M3Result ValidateSignature (IM3Function i_function, ccstr_t i_linkingSignature)
{
M3Result result = m3Err_none;
IM3FuncType ftype = NULL;
_ (SignatureToFuncType (& ftype, i_linkingSignature));
if (not AreFuncTypesEqual (ftype, i_function->funcType))
{
m3log (module, "expected: %s", SPrintFuncTypeSignature (ftype));
m3log (module, " found: %s", SPrintFuncTypeSignature (i_function->funcType));
_throw ("function signature mismatch");
}
_catch:
m3_Free (ftype);
return result;
}
M3Result FindAndLinkFunction (IM3Module io_module,
ccstr_t i_moduleName,
ccstr_t i_functionName,
ccstr_t i_signature,
voidptr_t i_function,
voidptr_t i_userdata)
{
_try {
_throwif(m3Err_moduleNotLinked, !io_module->runtime);
const bool wildcardModule = (strcmp (i_moduleName, "*") == 0);
result = m3Err_functionLookupFailed;
for (u32 i = 0; i < io_module->numFunctions; ++i)
{
const IM3Function f = & io_module->functions [i];
if (f->import.moduleUtf8 and f->import.fieldUtf8)
{
if (strcmp (f->import.fieldUtf8, i_functionName) == 0 and
(wildcardModule or strcmp (f->import.moduleUtf8, i_moduleName) == 0))
{
if (i_signature) {
_ (ValidateSignature (f, i_signature));
}
_ (CompileRawFunction (io_module, f, i_function, i_userdata));
}
}
}
} _catch:
return result;
}
M3Result m3_LinkRawFunctionEx (IM3Module io_module,
const char * const i_moduleName,
const char * const i_functionName,
const char * const i_signature,
M3RawCall i_function,
const void * i_userdata)
{
return FindAndLinkFunction (io_module, i_moduleName, i_functionName, i_signature, (voidptr_t)i_function, i_userdata);
}
M3Result m3_LinkRawFunction (IM3Module io_module,
const char * const i_moduleName,
const char * const i_functionName,
const char * const i_signature,
M3RawCall i_function)
{
return FindAndLinkFunction (io_module, i_moduleName, i_functionName, i_signature, (voidptr_t)i_function, NULL);
}
+20
View File
@@ -0,0 +1,20 @@
//
// m3_bind.h
//
// Created by Steven Massey on 2/27/20.
// Copyright © 2020 Steven Massey. All rights reserved.
//
#ifndef m3_bind_h
#define m3_bind_h
#include "m3_env.h"
d_m3BeginExternC
u8 ConvertTypeCharToTypeId (char i_code);
M3Result SignatureToFuncType (IM3FuncType * o_functionType, ccstr_t i_signature);
d_m3EndExternC
#endif /* m3_bind_h */
+246
View File
@@ -0,0 +1,246 @@
//
// m3_code.c
//
// Created by Steven Massey on 4/19/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#include <limits.h>
#include "m3_code.h"
#include "m3_env.h"
//---------------------------------------------------------------------------------------------------------------------------------
IM3CodePage NewCodePage (IM3Runtime i_runtime, u32 i_minNumLines)
{
IM3CodePage page;
// check multiplication overflow
if (i_minNumLines > UINT_MAX / sizeof (code_t)) {
return NULL;
}
u32 pageSize = sizeof (M3CodePageHeader) + sizeof (code_t) * i_minNumLines;
// check addition overflow
if (pageSize < sizeof (M3CodePageHeader)) {
return NULL;
}
pageSize = (pageSize + (d_m3CodePageAlignSize-1)) & ~(d_m3CodePageAlignSize-1); // align
// check alignment overflow
if (pageSize == 0) {
return NULL;
}
page = (IM3CodePage)m3_Malloc ("M3CodePage", pageSize);
if (page)
{
page->info.sequence = ++i_runtime->newCodePageSequence;
page->info.numLines = (pageSize - sizeof (M3CodePageHeader)) / sizeof (code_t);
#if d_m3RecordBacktraces
u32 pageSizeBt = sizeof (M3CodeMappingPage) + sizeof (M3CodeMapEntry) * page->info.numLines;
page->info.mapping = (M3CodeMappingPage *)m3_Malloc ("M3CodeMappingPage", pageSizeBt);
if (page->info.mapping)
{
page->info.mapping->size = 0;
page->info.mapping->capacity = page->info.numLines;
}
else
{
m3_Free (page);
return NULL;
}
page->info.mapping->basePC = GetPageStartPC(page);
#endif // d_m3RecordBacktraces
m3log (runtime, "new page: %p; seq: %d; bytes: %d; lines: %d", GetPagePC (page), page->info.sequence, pageSize, page->info.numLines);
}
return page;
}
void FreeCodePages (IM3CodePage * io_list)
{
IM3CodePage page = * io_list;
while (page)
{
m3log (code, "free page: %d; %p; util: %3.1f%%", page->info.sequence, page, 100. * page->info.lineIndex / page->info.numLines);
IM3CodePage next = page->info.next;
#if d_m3RecordBacktraces
m3_Free (page->info.mapping);
#endif // d_m3RecordBacktraces
m3_Free (page);
page = next;
}
* io_list = NULL;
}
u32 NumFreeLines (IM3CodePage i_page)
{
d_m3Assert (i_page->info.lineIndex <= i_page->info.numLines);
return i_page->info.numLines - i_page->info.lineIndex;
}
void EmitWord_impl (IM3CodePage i_page, void * i_word)
{ d_m3Assert (i_page->info.lineIndex+1 <= i_page->info.numLines);
i_page->code [i_page->info.lineIndex++] = i_word;
}
void EmitWord32 (IM3CodePage i_page, const u32 i_word)
{ d_m3Assert (i_page->info.lineIndex+1 <= i_page->info.numLines);
memcpy (& i_page->code[i_page->info.lineIndex++], & i_word, sizeof(i_word));
}
void EmitWord64 (IM3CodePage i_page, const u64 i_word)
{
#if M3_SIZEOF_PTR == 4
d_m3Assert (i_page->info.lineIndex+2 <= i_page->info.numLines);
memcpy (& i_page->code[i_page->info.lineIndex], & i_word, sizeof(i_word));
i_page->info.lineIndex += 2;
#else
d_m3Assert (i_page->info.lineIndex+1 <= i_page->info.numLines);
memcpy (& i_page->code[i_page->info.lineIndex], & i_word, sizeof(i_word));
i_page->info.lineIndex += 1;
#endif
}
#if d_m3RecordBacktraces
void EmitMappingEntry (IM3CodePage i_page, u32 i_moduleOffset)
{
M3CodeMappingPage * page = i_page->info.mapping;
d_m3Assert (page->size < page->capacity);
M3CodeMapEntry * entry = & page->entries[page->size++];
pc_t pc = GetPagePC (i_page);
entry->pcOffset = pc - page->basePC;
entry->moduleOffset = i_moduleOffset;
}
#endif // d_m3RecordBacktraces
pc_t GetPageStartPC (IM3CodePage i_page)
{
return & i_page->code [0];
}
pc_t GetPagePC (IM3CodePage i_page)
{
if (i_page)
return & i_page->code [i_page->info.lineIndex];
else
return NULL;
}
void PushCodePage (IM3CodePage * i_list, IM3CodePage i_codePage)
{
IM3CodePage next = * i_list;
i_codePage->info.next = next;
* i_list = i_codePage;
}
IM3CodePage PopCodePage (IM3CodePage * i_list)
{
IM3CodePage page = * i_list;
* i_list = page->info.next;
page->info.next = NULL;
return page;
}
u32 FindCodePageEnd (IM3CodePage i_list, IM3CodePage * o_end)
{
u32 numPages = 0;
* o_end = NULL;
while (i_list)
{
* o_end = i_list;
++numPages;
i_list = i_list->info.next;
}
return numPages;
}
u32 CountCodePages (IM3CodePage i_list)
{
IM3CodePage unused;
return FindCodePageEnd (i_list, & unused);
}
IM3CodePage GetEndCodePage (IM3CodePage i_list)
{
IM3CodePage end;
FindCodePageEnd (i_list, & end);
return end;
}
#if d_m3RecordBacktraces
bool ContainsPC (IM3CodePage i_page, pc_t i_pc)
{
return GetPageStartPC (i_page) <= i_pc && i_pc < GetPagePC (i_page);
}
bool MapPCToOffset (IM3CodePage i_page, pc_t i_pc, u32 * o_moduleOffset)
{
M3CodeMappingPage * mapping = i_page->info.mapping;
u32 pcOffset = i_pc - mapping->basePC;
u32 left = 0;
u32 right = mapping->size;
while (left < right)
{
u32 mid = left + (right - left) / 2;
if (mapping->entries[mid].pcOffset < pcOffset)
{
left = mid + 1;
}
else if (mapping->entries[mid].pcOffset > pcOffset)
{
right = mid;
}
else
{
*o_moduleOffset = mapping->entries[mid].moduleOffset;
return true;
}
}
// Getting here means left is now one more than the element we want.
if (left > 0)
{
left--;
*o_moduleOffset = mapping->entries[left].moduleOffset;
return true;
}
else return false;
}
#endif // d_m3RecordBacktraces
//---------------------------------------------------------------------------------------------------------------------------------
+80
View File
@@ -0,0 +1,80 @@
//
// m3_code.h
//
// Created by Steven Massey on 4/19/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_code_h
#define m3_code_h
#include "m3_core.h"
d_m3BeginExternC
typedef struct M3CodePage
{
M3CodePageHeader info;
code_t code [1];
}
M3CodePage;
typedef M3CodePage * IM3CodePage;
IM3CodePage NewCodePage (IM3Runtime i_runtime, u32 i_minNumLines);
void FreeCodePages (IM3CodePage * io_list);
u32 NumFreeLines (IM3CodePage i_page);
pc_t GetPageStartPC (IM3CodePage i_page);
pc_t GetPagePC (IM3CodePage i_page);
void EmitWord_impl (IM3CodePage i_page, void* i_word);
void EmitWord32 (IM3CodePage i_page, u32 i_word);
void EmitWord64 (IM3CodePage i_page, u64 i_word);
# if d_m3RecordBacktraces
void EmitMappingEntry (IM3CodePage i_page, u32 i_moduleOffset);
# endif // d_m3RecordBacktraces
void PushCodePage (IM3CodePage * io_list, IM3CodePage i_codePage);
IM3CodePage PopCodePage (IM3CodePage * io_list);
IM3CodePage GetEndCodePage (IM3CodePage i_list); // i_list = NULL is valid
u32 CountCodePages (IM3CodePage i_list); // i_list = NULL is valid
# if d_m3RecordBacktraces
bool ContainsPC (IM3CodePage i_page, pc_t i_pc);
bool MapPCToOffset (IM3CodePage i_page, pc_t i_pc, u32 * o_moduleOffset);
# endif // d_m3RecordBacktraces
# ifdef DEBUG
void dump_code_page (IM3CodePage i_codePage, pc_t i_startPC);
# endif
#define EmitWord(page, val) EmitWord_impl(page, (void*)(val))
//---------------------------------------------------------------------------------------------------------------------------------
# if d_m3RecordBacktraces
typedef struct M3CodeMapEntry
{
u32 pcOffset;
u32 moduleOffset;
}
M3CodeMapEntry;
typedef struct M3CodeMappingPage
{
pc_t basePC;
u32 size;
u32 capacity;
M3CodeMapEntry entries [];
}
M3CodeMappingPage;
# endif // d_m3RecordBacktraces
d_m3EndExternC
#endif // m3_code_h
File diff suppressed because it is too large Load Diff
+208
View File
@@ -0,0 +1,208 @@
//
// m3_compile.h
//
// Created by Steven Massey on 4/17/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_compile_h
#define m3_compile_h
#include "m3_code.h"
#include "m3_exec_defs.h"
#include "m3_function.h"
d_m3BeginExternC
enum
{
c_waOp_block = 0x02,
c_waOp_loop = 0x03,
c_waOp_if = 0x04,
c_waOp_else = 0x05,
c_waOp_end = 0x0b,
c_waOp_branch = 0x0c,
c_waOp_branchTable = 0x0e,
c_waOp_branchIf = 0x0d,
c_waOp_call = 0x10,
c_waOp_getLocal = 0x20,
c_waOp_setLocal = 0x21,
c_waOp_teeLocal = 0x22,
c_waOp_getGlobal = 0x23,
c_waOp_store_f32 = 0x38,
c_waOp_store_f64 = 0x39,
c_waOp_i32_const = 0x41,
c_waOp_i64_const = 0x42,
c_waOp_f32_const = 0x43,
c_waOp_f64_const = 0x44,
c_waOp_extended = 0xfc,
c_waOp_memoryCopy = 0xfc0a,
c_waOp_memoryFill = 0xfc0b
};
#define d_FuncRetType(ftype,i) ((ftype)->types[(i)])
#define d_FuncArgType(ftype,i) ((ftype)->types[(ftype)->numRets + (i)])
//-----------------------------------------------------------------------------------------------------------------------------------
typedef struct M3CompilationScope
{
struct M3CompilationScope * outer;
pc_t pc; // used by ContinueLoop's
pc_t patches;
i32 depth;
u16 exitStackIndex;
u16 blockStackIndex;
// u16 topSlot;
IM3FuncType type;
m3opcode_t opcode;
bool isPolymorphic;
}
M3CompilationScope;
typedef M3CompilationScope * IM3CompilationScope;
typedef struct
{
IM3Runtime runtime;
IM3Module module;
bytes_t wasm;
bytes_t wasmEnd;
bytes_t lastOpcodeStart;
M3CompilationScope block;
IM3Function function;
IM3CodePage page;
#ifdef DEBUG
u32 numEmits;
u32 numOpcodes;
#endif
u16 stackFirstDynamicIndex; // args and locals are pushed to the stack so that their slot locations can be tracked. the wasm model itself doesn't
// treat these values as being on the stack, so stackFirstDynamicIndex marks the start of the real Wasm stack
u16 stackIndex; // current stack top
u16 slotFirstConstIndex;
u16 slotMaxConstIndex; // as const's are encountered during compilation this tracks their location in the "real" stack
u16 slotFirstLocalIndex;
u16 slotFirstDynamicIndex; // numArgs + numLocals + numReservedConstants. the first mutable slot available to the compiler.
u16 maxStackSlots;
m3slot_t constants [d_m3MaxConstantTableSize];
// 'wasmStack' holds slot locations
u16 wasmStack [d_m3MaxFunctionStackHeight];
u8 typeStack [d_m3MaxFunctionStackHeight];
// 'm3Slots' contains allocation usage counts
u8 m3Slots [d_m3MaxFunctionSlots];
u16 slotMaxAllocatedIndexPlusOne;
u16 regStackIndexPlusOne [2];
m3opcode_t previousOpcode;
bool isInitExpr; // walking a constant expression, not a function body
}
M3Compilation;
typedef M3Compilation * IM3Compilation;
typedef M3Result (* M3Compiler) (IM3Compilation, m3opcode_t);
//-----------------------------------------------------------------------------------------------------------------------------------
typedef struct M3OpInfo
{
#ifdef DEBUG
const char * const name;
#endif
i8 stackOffset;
u8 type;
// for most operations:
// [0]= top operand in register, [1]= top operand in stack, [2]= both operands in stack
IM3Operation operations [4];
M3Compiler compiler;
}
M3OpInfo;
typedef const M3OpInfo * IM3OpInfo;
IM3OpInfo GetOpInfo (m3opcode_t opcode);
// TODO: This helper should be removed, when MultiValue is implemented
static inline
u8 GetSingleRetType(IM3FuncType ftype) {
return (ftype && ftype->numRets) ? ftype->types[0] : (u8)c_m3Type_none;
}
static const u16 c_m3RegisterUnallocated = 0;
static const u16 c_slotUnused = 0xffff;
static inline
bool IsRegisterAllocated (IM3Compilation o, u32 i_register)
{
return (o->regStackIndexPlusOne [i_register] != c_m3RegisterUnallocated);
}
static inline
bool IsStackPolymorphic (IM3Compilation o)
{
return o->block.isPolymorphic;
}
static inline bool IsRegisterSlotAlias (u16 i_slot) { return (i_slot >= d_m3Reg0SlotAlias and i_slot != c_slotUnused); }
static inline bool IsFpRegisterSlotAlias (u16 i_slot) { return (i_slot == d_m3Fp0SlotAlias); }
static inline bool IsIntRegisterSlotAlias (u16 i_slot) { return (i_slot == d_m3Reg0SlotAlias); }
#ifdef DEBUG
#define M3OP(...) { __VA_ARGS__ }
#define M3OP_RESERVED { "reserved" }
#else
// Strip-off name
#define M3OP(name, ...) { __VA_ARGS__ }
#define M3OP_RESERVED { 0 }
#endif
#if d_m3HasFloat
#define M3OP_F M3OP
#elif d_m3NoFloatDynamic
#define M3OP_F(n,o,t,op,...) M3OP(n, o, t, { op_Unsupported, op_Unsupported, op_Unsupported, op_Unsupported }, __VA_ARGS__)
#else
#define M3OP_F(...) { 0 }
#endif
//-----------------------------------------------------------------------------------------------------------------------------------
u16 GetMaxUsedSlotPlusOne (IM3Compilation o);
M3Result CompileBlock (IM3Compilation io, IM3FuncType i_blockType, m3opcode_t i_blockOpcode);
M3Result CompileBlockStatements (IM3Compilation io);
M3Result CompileFunction (IM3Function io_function);
M3Result CompileRawFunction (IM3Module io_module, IM3Function io_function, const void * i_function, const void * i_userdata);
d_m3EndExternC
#endif // m3_compile_h
+168
View File
@@ -0,0 +1,168 @@
//
// m3_config.h
//
// Created by Steven Massey on 5/4/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_config_h
#define m3_config_h
#include "m3_config_platforms.h"
// general --------------------------------------------------------------------
# ifndef d_m3CodePageAlignSize
# define d_m3CodePageAlignSize 32*1024
# endif
# ifndef d_m3MaxFunctionStackHeight
# define d_m3MaxFunctionStackHeight 2000 // max: 32768
# endif
# ifndef d_m3MaxLinearMemoryPages
# define d_m3MaxLinearMemoryPages 65536
# endif
# ifndef d_m3MaxFunctionSlots
# define d_m3MaxFunctionSlots ((d_m3MaxFunctionStackHeight)*2)
# endif
# ifndef d_m3ValStack // validator operand and local type stacks:
# define d_m3ValStack (d_m3MaxFunctionStackHeight) // the same operand stack the compiler bounds
# endif
# ifndef d_m3ValCtrlDepth // validator block nesting depth. Each frame is bigger than an
# define d_m3ValCtrlDepth ((d_m3MaxFunctionStackHeight)/8)// operand entry, so this dominates the validator's stack usage
# endif
# ifndef d_m3MaxConstantTableSize
# define d_m3MaxConstantTableSize 120
# endif
# ifndef d_m3MaxDuplicateFunctionImpl
# define d_m3MaxDuplicateFunctionImpl 3
# endif
# ifndef d_m3CascadedOpcodes // Cascaded opcodes are slightly faster at the expense of some memory
# define d_m3CascadedOpcodes 1 // Adds ~3Kb to operations table in m3_compile.c
# endif
# ifndef d_m3VerboseErrorMessages
# define d_m3VerboseErrorMessages 1
# endif
# ifndef d_m3FixedHeap
# define d_m3FixedHeap false
//# define d_m3FixedHeap (32*1024)
# endif
# ifndef d_m3FixedHeapAlign
# define d_m3FixedHeapAlign 16
# endif
# ifndef d_m3Use32BitSlots
# define d_m3Use32BitSlots 1
# endif
# ifndef d_m3ProfilerSlotMask
# define d_m3ProfilerSlotMask 0xFFFF
# endif
# ifndef d_m3RecordBacktraces
# define d_m3RecordBacktraces 0
# endif
# ifndef d_m3EnableExceptionBreakpoint
# define d_m3EnableExceptionBreakpoint 0 // see m3_exception.h
# endif
// profiling and tracing ------------------------------------------------------
# ifndef d_m3EnableOpProfiling
# define d_m3EnableOpProfiling 0 // opcode usage counters
# endif
# ifndef d_m3EnableOpTracing
# define d_m3EnableOpTracing 0 // only works with DEBUG
# endif
# ifndef d_m3EnableWasiTracing
# define d_m3EnableWasiTracing 0
# endif
# ifndef d_m3EnableStrace
# define d_m3EnableStrace 0 // 1 - trace exported function calls
// 2 - trace all calls (structured)
// 3 - all calls + loops + memory operations
# endif
// logging --------------------------------------------------------------------
# ifndef d_m3LogParse
# define d_m3LogParse 0 // .wasm binary decoding info
# endif
# ifndef d_m3LogModule
# define d_m3LogModule 0 // wasm module info
# endif
# ifndef d_m3LogCompile
# define d_m3LogCompile 0 // wasm -> metacode generation phase
# endif
# ifndef d_m3LogWasmStack
# define d_m3LogWasmStack 0 // dump the wasm stack when pushed or popped
# endif
# ifndef d_m3LogEmit
# define d_m3LogEmit 0 // metacode generation info
# endif
# ifndef d_m3LogCodePages
# define d_m3LogCodePages 0 // dump metacode pages when released
# endif
# ifndef d_m3LogRuntime
# define d_m3LogRuntime 0 // higher-level runtime information
# endif
# ifndef d_m3LogNativeStack
# define d_m3LogNativeStack 0 // track the memory usage of the C-stack
# endif
# ifndef d_m3LogHeapOps
# define d_m3LogHeapOps 0 // track heap usage
# endif
# ifndef d_m3LogTimestamps
# define d_m3LogTimestamps 0 // track timestamps on heap logs
# endif
// other ----------------------------------------------------------------------
# ifndef d_m3HasFloat
# define d_m3HasFloat 1 // implement floating point ops
# endif
#if !d_m3HasFloat && !defined(d_m3NoFloatDynamic)
# define d_m3NoFloatDynamic 1 // if no floats, do not fail until flops are actually executed
#endif
# ifndef d_m3EnableValidation
# define d_m3EnableValidation 1 // pre-pass bytecode type validation
# endif
# ifndef d_m3SkipStackCheck
# define d_m3SkipStackCheck 0 // skip stack overrun checks
# endif
# ifndef d_m3SkipMemoryBoundsCheck
# define d_m3SkipMemoryBoundsCheck 0 // skip memory bounds checks
# endif
#define d_m3EnableCodePageRefCounting 0 // not supported currently
#endif // m3_config_h
+216
View File
@@ -0,0 +1,216 @@
//
// m3_config_platforms.h
//
// Created by Volodymyr Shymanskyy on 11/20/19.
// Copyright © 2019 Volodymyr Shymanskyy. All rights reserved.
//
#ifndef m3_config_platforms_h
#define m3_config_platforms_h
#include "wasm3_defs.h"
/*
* Internal helpers
*/
# if !defined(__cplusplus) || defined(_MSC_VER)
# define not !
# define and &&
# define or ||
# endif
/*
* Detect/define features
*/
# if defined(M3_COMPILER_MSVC)
# include <stdint.h>
# if UINTPTR_MAX == 0xFFFFFFFF
# define M3_SIZEOF_PTR 4
# elif UINTPTR_MAX == 0xFFFFFFFFFFFFFFFFu
# define M3_SIZEOF_PTR 8
# else
# error "Pointer size not supported"
# endif
# elif defined(__SIZEOF_POINTER__)
# define M3_SIZEOF_PTR __SIZEOF_POINTER__
#else
# error "Pointer size not detected"
# endif
# if defined(M3_BIG_ENDIAN)
# define M3_BSWAP_u8(X) {}
# define M3_BSWAP_u16(X) { (X)=m3_bswap16((X)); }
# define M3_BSWAP_u32(X) { (X)=m3_bswap32((X)); }
# define M3_BSWAP_u64(X) { (X)=m3_bswap64((X)); }
# define M3_BSWAP_i8(X) {}
# define M3_BSWAP_i16(X) M3_BSWAP_u16(X)
# define M3_BSWAP_i32(X) M3_BSWAP_u32(X)
# define M3_BSWAP_i64(X) M3_BSWAP_u64(X)
# define M3_BSWAP_f32(X) { union { f32 f; u32 i; } u; u.f = (X); M3_BSWAP_u32(u.i); (X) = u.f; }
# define M3_BSWAP_f64(X) { union { f64 f; u64 i; } u; u.f = (X); M3_BSWAP_u64(u.i); (X) = u.f; }
# else
# define M3_BSWAP_u8(X) {}
# define M3_BSWAP_u16(x) {}
# define M3_BSWAP_u32(x) {}
# define M3_BSWAP_u64(x) {}
# define M3_BSWAP_i8(X) {}
# define M3_BSWAP_i16(X) {}
# define M3_BSWAP_i32(X) {}
# define M3_BSWAP_i64(X) {}
# define M3_BSWAP_f32(X) {}
# define M3_BSWAP_f64(X) {}
# endif
# if defined(M3_COMPILER_MSVC)
# define M3_WEAK //__declspec(selectany)
# define M3_NO_UBSAN
# define M3_NOINLINE
# elif defined(__MINGW32__) || defined(__CYGWIN__)
# define M3_WEAK //__attribute__((selectany))
# define M3_NO_UBSAN
# define M3_NOINLINE __attribute__((noinline))
# else
# define M3_WEAK __attribute__((weak))
# define M3_NO_UBSAN //__attribute__((no_sanitize("undefined")))
// Workaround for Cosmopolitan noinline conflict: https://github.com/jart/cosmopolitan/issues/310
# if defined(noinline)
# define M3_NOINLINE noinline
# else
# define M3_NOINLINE __attribute__((noinline))
# endif
# endif
# if !defined(M3_HAS_TAIL_CALL)
# if defined(__EMSCRIPTEN__)
# define M3_HAS_TAIL_CALL 0
# else
# define M3_HAS_TAIL_CALL 1
# endif
# endif
# if M3_HAS_TAIL_CALL && M3_COMPILER_HAS_ATTRIBUTE(musttail)
# define M3_MUSTTAIL __attribute__((musttail))
# else
# define M3_MUSTTAIL
# endif
# ifndef M3_MIN
# define M3_MIN(A,B) (((A) < (B)) ? (A) : (B))
# endif
# ifndef M3_MAX
# define M3_MAX(A,B) (((A) > (B)) ? (A) : (B))
# endif
#define M3_INIT(field) memset(&field, 0, sizeof(field))
#define M3_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
#if defined(__AVR__)
#include <inttypes.h>
# define PRIu64 "llu"
# define PRIi64 "lli"
# define d_m3ShortTypesDefined
typedef double f64;
typedef float f32;
typedef uint64_t u64;
typedef int64_t i64;
typedef uint32_t u32;
typedef int32_t i32;
typedef short unsigned u16;
typedef short i16;
typedef uint8_t u8;
typedef int8_t i8;
#endif
/*
* Apply settings
*/
# if defined (M3_COMPILER_MSVC)
# define vectorcall // For MSVC, better not to specify any call convention
# elif defined(__x86_64__)
# define vectorcall
//# elif defined(__riscv) && (__riscv_xlen == 64)
//# define vectorcall
# elif defined(__MINGW32__)
# define vectorcall
# elif defined(WIN32)
# define vectorcall __vectorcall
# elif defined (ESP8266)
# include <c_types.h>
# define vectorcall //ICACHE_FLASH_ATTR
# elif defined (ESP32)
# if defined(M3_IN_IRAM) // the interpreter is in IRAM, attribute not needed
# define vectorcall
# else
# include "esp_system.h"
# define vectorcall IRAM_ATTR
# endif
# elif defined (FOMU)
# define vectorcall __attribute__((section(".ramtext")))
# endif
#ifndef vectorcall
#define vectorcall
#endif
/*
* Device-specific defaults
*/
# ifndef d_m3MaxFunctionStackHeight
# if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_AMEBA) || defined(TEENSYDUINO)
# define d_m3MaxFunctionStackHeight 256
# endif
# endif
# ifndef d_m3FixedHeap
# if defined(ARDUINO_AMEBA)
# define d_m3FixedHeap (128*1024)
# elif defined(BLUE_PILL) || defined(FOMU)
# define d_m3FixedHeap (12*1024)
# elif defined(ARDUINO_ARCH_ARC32) // Arduino 101
# define d_m3FixedHeap (10*1024)
# endif
# endif
/*
* Platform-specific defaults
*/
# if defined(ARDUINO) || defined(PARTICLE) || defined(PLATFORMIO) || defined(__MBED__) || \
defined(ESP8266) || defined(ESP32) || defined(BLUE_PILL) || defined(WM_W600) || defined(FOMU)
# ifndef d_m3CascadedOpcodes
# define d_m3CascadedOpcodes 0
# endif
# ifndef d_m3VerboseErrorMessages
# define d_m3VerboseErrorMessages 0
# endif
# ifndef d_m3MaxConstantTableSize
# define d_m3MaxConstantTableSize 64
# endif
# ifndef d_m3MaxFunctionStackHeight
# define d_m3MaxFunctionStackHeight 128
# endif
# ifndef d_m3CodePageAlignSize
# define d_m3CodePageAlignSize 1024
# endif
# endif
/*
* Arch-specific defaults
*/
#if defined(__riscv) && (__riscv_xlen == 64)
# ifndef d_m3Use32BitSlots
# define d_m3Use32BitSlots 0
# endif
#endif
#endif // m3_config_platforms_h
+717
View File
@@ -0,0 +1,717 @@
//
// m3_core.c
//
// Created by Steven Massey on 4/15/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#define M3_IMPLEMENT_ERROR_STRINGS
#include "m3_config.h"
#include "wasm3.h"
#include "m3_core.h"
#include "m3_env.h"
void m3_Abort(const char* message) {
#ifdef DEBUG
fprintf(stderr, "Error: %s\n", message);
#endif
abort();
}
M3_WEAK
M3Result m3_Yield ()
{
return m3Err_none;
}
#if d_m3LogTimestamps
#include <time.h>
#define SEC_TO_US(sec) ((sec)*1000000)
#define NS_TO_US(ns) ((ns)/1000)
static uint64_t initial_ts = -1;
uint64_t m3_GetTimestamp()
{
if (initial_ts == -1) {
initial_ts = 0;
initial_ts = m3_GetTimestamp();
}
struct timespec ts;
timespec_get(&ts, TIME_UTC);
uint64_t us = SEC_TO_US((uint64_t)ts.tv_sec) + NS_TO_US((uint64_t)ts.tv_nsec);
return us - initial_ts;
}
#endif
#if d_m3FixedHeap
static u8 fixedHeap[d_m3FixedHeap];
static u8* fixedHeapPtr = fixedHeap;
static u8* const fixedHeapEnd = fixedHeap + d_m3FixedHeap;
static u8* fixedHeapLast = NULL;
#if d_m3FixedHeapAlign > 1
# define HEAP_ALIGN_PTR(P) P = (u8*)(((size_t)(P)+(d_m3FixedHeapAlign-1)) & ~ (d_m3FixedHeapAlign-1));
#else
# define HEAP_ALIGN_PTR(P)
#endif
void * m3_Malloc_Impl (size_t i_size)
{
u8 * ptr = fixedHeapPtr;
fixedHeapPtr += i_size;
HEAP_ALIGN_PTR(fixedHeapPtr);
if (fixedHeapPtr >= fixedHeapEnd)
{
return NULL;
}
memset (ptr, 0x0, i_size);
fixedHeapLast = ptr;
return ptr;
}
void m3_Free_Impl (void * i_ptr)
{
// Handle the last chunk
if (i_ptr && i_ptr == fixedHeapLast) {
fixedHeapPtr = fixedHeapLast;
fixedHeapLast = NULL;
} else {
//printf("== free %p [failed]\n", io_ptr);
}
}
void * m3_Realloc_Impl (void * i_ptr, size_t i_newSize, size_t i_oldSize)
{
if (M3_UNLIKELY(i_newSize == i_oldSize)) return i_ptr;
void * newPtr;
// Handle the last chunk
if (i_ptr && i_ptr == fixedHeapLast) {
fixedHeapPtr = fixedHeapLast + i_newSize;
HEAP_ALIGN_PTR(fixedHeapPtr);
if (fixedHeapPtr >= fixedHeapEnd)
{
return NULL;
}
newPtr = i_ptr;
} else {
newPtr = m3_Malloc_Impl(i_newSize);
if (!newPtr) {
return NULL;
}
if (i_ptr) {
memcpy(newPtr, i_ptr, i_oldSize);
}
}
if (i_newSize > i_oldSize) {
memset ((u8 *) newPtr + i_oldSize, 0x0, i_newSize - i_oldSize);
}
return newPtr;
}
#else
void * m3_Malloc_Impl (size_t i_size)
{
return calloc (i_size, 1);
}
void m3_Free_Impl (void * io_ptr)
{
free (io_ptr);
}
void * m3_Realloc_Impl (void * i_ptr, size_t i_newSize, size_t i_oldSize)
{
if (M3_UNLIKELY(i_newSize == i_oldSize)) return i_ptr;
void * newPtr = realloc (i_ptr, i_newSize);
if (M3_LIKELY(newPtr))
{
if (i_newSize > i_oldSize) {
memset ((u8 *) newPtr + i_oldSize, 0x0, i_newSize - i_oldSize);
}
return newPtr;
}
return NULL;
}
#endif
void * m3_CopyMem (const void * i_from, size_t i_size)
{
void * ptr = m3_Malloc("CopyMem", i_size);
if (ptr) {
memcpy (ptr, i_from, i_size);
}
return ptr;
}
//--------------------------------------------------------------------------------------------
#if d_m3LogNativeStack
static size_t stack_start;
static size_t stack_end;
void m3StackCheckInit ()
{
char stack;
stack_end = stack_start = (size_t)&stack;
}
void m3StackCheck ()
{
char stack;
size_t addr = (size_t)&stack;
size_t stackEnd = stack_end;
stack_end = M3_MIN (stack_end, addr);
// if (stackEnd != stack_end)
// printf ("maxStack: %ld\n", m3StackGetMax ());
}
int m3StackGetMax ()
{
return stack_start - stack_end;
}
#endif
//--------------------------------------------------------------------------------------------
M3Result NormalizeType (u8 * o_type, i8 i_convolutedWasmType)
{
M3Result result = m3Err_none;
u8 type = -i_convolutedWasmType;
if (type == 0x40)
type = c_m3Type_none;
// Accept v128 (wasm-encoded as 0x7b → -i_convolutedWasmType == 5)
// as an opaque slot so modules with v128 in signatures or local
// declarations parse. Actual v128 opcodes still hit
// m3Err_unknownOpcode at compile time - we just stop refusing
// unused SIMD slots that auto-vectorization emits.
else if (type < c_m3Type_i32 or type > c_m3Type_v128)
result = m3Err_invalidTypeId;
* o_type = type;
return result;
}
bool IsFpType (u8 i_m3Type)
{
return (i_m3Type == c_m3Type_f32 or i_m3Type == c_m3Type_f64);
}
bool IsIntType (u8 i_m3Type)
{
return (i_m3Type == c_m3Type_i32 or i_m3Type == c_m3Type_i64);
}
bool Is64BitType (u8 i_m3Type)
{
if (i_m3Type == c_m3Type_i64 or i_m3Type == c_m3Type_f64)
return true;
else if (i_m3Type == c_m3Type_i32 or i_m3Type == c_m3Type_f32 or i_m3Type == c_m3Type_none)
return false;
else
return (sizeof (voidptr_t) == 8); // all other cases are pointers
}
u32 SizeOfType (u8 i_m3Type)
{
if (i_m3Type == c_m3Type_i32 or i_m3Type == c_m3Type_f32)
return sizeof (i32);
return sizeof (i64);
}
//-- Binary Wasm parsing utils ------------------------------------------------------------------------------------------
M3Result Read_u64 (u64 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
ptr += sizeof (u64);
if (ptr <= i_end)
{
memcpy(o_value, * io_bytes, sizeof(u64));
M3_BSWAP_u64(*o_value);
* io_bytes = ptr;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
M3Result Read_u32 (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
ptr += sizeof (u32);
if (ptr <= i_end)
{
memcpy(o_value, * io_bytes, sizeof(u32));
M3_BSWAP_u32(*o_value);
* io_bytes = ptr;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
#if d_m3ImplementFloat
M3Result Read_f64 (f64 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
ptr += sizeof (f64);
if (ptr <= i_end)
{
memcpy(o_value, * io_bytes, sizeof(f64));
M3_BSWAP_f64(*o_value);
* io_bytes = ptr;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
M3Result Read_f32 (f32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
ptr += sizeof (f32);
if (ptr <= i_end)
{
memcpy(o_value, * io_bytes, sizeof(f32));
M3_BSWAP_f32(*o_value);
* io_bytes = ptr;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
#endif
M3Result Read_u8 (u8 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
if (ptr < i_end)
{
* o_value = * ptr;
* io_bytes = ptr + 1;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
M3Result Read_opcode (m3opcode_t * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
if (ptr < i_end)
{
m3opcode_t opcode = * ptr++;
#if d_m3CascadedOpcodes == 0
if (M3_UNLIKELY(opcode == c_waOp_extended))
{
if (ptr < i_end)
{
opcode = (opcode << 8) | (* ptr++);
}
else return m3Err_wasmUnderrun;
}
#endif
* o_value = opcode;
* io_bytes = ptr;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
M3Result ReadLebUnsigned (u64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end)
{
M3Result result = m3Err_wasmUnderrun;
u64 value = 0;
u32 shift = 0;
const u8 * ptr = * io_bytes;
while (ptr < i_end)
{
u64 byte = * (ptr++);
value |= ((byte & 0x7f) << shift);
shift += 7;
if ((byte & 0x80) == 0)
{
result = m3Err_none;
#if d_m3EnableValidation
// The last byte must not carry bits past i_maxNumBits
if (shift > i_maxNumBits)
{
u32 numUsedBits = i_maxNumBits + 7 - shift;
if (byte >> numUsedBits)
result = m3Err_lebOverflow;
}
#endif
break;
}
if (shift >= i_maxNumBits)
{
result = m3Err_lebOverflow;
break;
}
}
* o_value = value;
* io_bytes = ptr;
return result;
}
M3Result ReadLebSigned (i64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end)
{
M3Result result = m3Err_wasmUnderrun;
i64 value = 0;
u32 shift = 0;
const u8 * ptr = * io_bytes;
while (ptr < i_end)
{
u64 byte = * (ptr++);
value |= ((byte & 0x7f) << shift);
shift += 7;
if ((byte & 0x80) == 0)
{
result = m3Err_none;
#if d_m3EnableValidation
// The bits of the last byte past i_maxNumBits must all repeat the
// sign bit, otherwise the value doesn't fit
if (shift > i_maxNumBits)
{
u32 numUsedBits = i_maxNumBits + 7 - shift;
u8 signBits = (u8) ((0x7f << (numUsedBits - 1)) & 0x7f);
u8 bits = (u8) (byte & signBits);
if (bits != 0 and bits != signBits)
result = m3Err_lebOverflow;
}
#endif
if ((byte & 0x40) and (shift < 64)) // do sign extension
{
u64 extend = 0;
value |= (~extend << shift);
}
break;
}
if (shift >= i_maxNumBits)
{
result = m3Err_lebOverflow;
break;
}
}
* o_value = value;
* io_bytes = ptr;
return result;
}
M3Result ReadLEB_u32 (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
u64 value;
M3Result result = ReadLebUnsigned (& value, 32, io_bytes, i_end);
* o_value = (u32) value;
return result;
}
M3Result ReadLEB_u7 (u8 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
u64 value;
M3Result result = ReadLebUnsigned (& value, 7, io_bytes, i_end);
* o_value = (u8) value;
return result;
}
M3Result ReadLEB_i7 (i8 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
i64 value;
M3Result result = ReadLebSigned (& value, 7, io_bytes, i_end);
* o_value = (i8) value;
return result;
}
M3Result ReadLEB_i32 (i32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
i64 value;
M3Result result = ReadLebSigned (& value, 32, io_bytes, i_end);
* o_value = (i32) value;
return result;
}
M3Result ReadLEB_i64 (i64 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
i64 value;
M3Result result = ReadLebSigned (& value, 64, io_bytes, i_end);
* o_value = value;
return result;
}
#if d_m3EnableValidation
// Validate that a byte sequence is well-formed UTF-8 per the Unicode spec.
// Returns true if valid, false otherwise.
static bool IsValidUtf8 (const u8 * i_data, u32 i_length)
{
const u8 * ptr = i_data;
const u8 * end = i_data + i_length;
while (ptr < end)
{
u8 b0 = *ptr++;
if (b0 < 0x80)
{
// single-byte: 0xxxxxxx
continue;
}
else if ((b0 & 0xE0) == 0xC0)
{
// two-byte: 110xxxxx 10xxxxxx
if (b0 < 0xC2) return false; // overlong
if (ptr >= end) return false;
u8 b1 = *ptr++;
if ((b1 & 0xC0) != 0x80) return false;
}
else if ((b0 & 0xF0) == 0xE0)
{
// three-byte: 1110xxxx 10xxxxxx 10xxxxxx
if (ptr + 1 >= end) return false;
u8 b1 = *ptr++;
u8 b2 = *ptr++;
if ((b1 & 0xC0) != 0x80) return false;
if ((b2 & 0xC0) != 0x80) return false;
// reject overlong
if (b0 == 0xE0 && b1 < 0xA0) return false;
// reject surrogates U+D800..U+DFFF
if (b0 == 0xED && b1 >= 0xA0) return false;
}
else if ((b0 & 0xF8) == 0xF0)
{
// four-byte: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
if (b0 > 0xF4) return false; // above U+10FFFF
if (ptr + 2 >= end) return false;
u8 b1 = *ptr++;
u8 b2 = *ptr++;
u8 b3 = *ptr++;
if ((b1 & 0xC0) != 0x80) return false;
if ((b2 & 0xC0) != 0x80) return false;
if ((b3 & 0xC0) != 0x80) return false;
// reject overlong
if (b0 == 0xF0 && b1 < 0x90) return false;
// reject above U+10FFFF
if (b0 == 0xF4 && b1 > 0x8F) return false;
}
else
{
// invalid leading byte (0x80..0xBF or 0xF5..0xFF)
return false;
}
}
return true;
}
#endif // d_m3EnableValidation
M3Result Read_utf8 (cstr_t * o_utf8, bytes_t * io_bytes, cbytes_t i_end)
{
*o_utf8 = NULL;
u32 utf8Length;
M3Result result = ReadLEB_u32 (& utf8Length, io_bytes, i_end);
if (not result)
{
if (utf8Length <= d_m3MaxSaneUtf8Length)
{
const u8 * ptr = * io_bytes;
const u8 * end = ptr + utf8Length;
if (end <= i_end)
{
#if d_m3EnableValidation
if (not IsValidUtf8 (ptr, utf8Length))
{
* io_bytes = end;
return m3Err_wasmMalformed;
}
#endif // d_m3EnableValidation
char * utf8 = (char *)m3_Malloc ("UTF8", utf8Length + 1);
if (utf8)
{
memcpy (utf8, ptr, utf8Length);
utf8 [utf8Length] = 0;
* o_utf8 = utf8;
}
* io_bytes = end;
}
else result = m3Err_wasmUnderrun;
}
else result = m3Err_missingUTF8;
}
return result;
}
#if d_m3RecordBacktraces
u32 FindModuleOffset (IM3Runtime i_runtime, pc_t i_pc)
{
// walk the code pages
IM3CodePage curr = i_runtime->pagesOpen;
bool pageFound = false;
while (curr)
{
if (ContainsPC (curr, i_pc))
{
pageFound = true;
break;
}
curr = curr->info.next;
}
if (!pageFound)
{
curr = i_runtime->pagesFull;
while (curr)
{
if (ContainsPC (curr, i_pc))
{
pageFound = true;
break;
}
curr = curr->info.next;
}
}
if (pageFound)
{
u32 result = 0;
bool pcFound = MapPCToOffset (curr, i_pc, & result);
d_m3Assert (pcFound);
return result;
}
else return 0;
}
void PushBacktraceFrame (IM3Runtime io_runtime, pc_t i_pc)
{
// don't try to push any more frames if we've already had an alloc failure
if (M3_UNLIKELY (io_runtime->backtrace.lastFrame == M3_BACKTRACE_TRUNCATED))
return;
M3BacktraceFrame * newFrame = m3_AllocStruct(M3BacktraceFrame);
if (!newFrame)
{
io_runtime->backtrace.lastFrame = M3_BACKTRACE_TRUNCATED;
return;
}
newFrame->moduleOffset = FindModuleOffset (io_runtime, i_pc);
if (!io_runtime->backtrace.frames || !io_runtime->backtrace.lastFrame)
io_runtime->backtrace.frames = newFrame;
else
io_runtime->backtrace.lastFrame->next = newFrame;
io_runtime->backtrace.lastFrame = newFrame;
}
void FillBacktraceFunctionInfo (IM3Runtime io_runtime, IM3Function i_function)
{
// If we've had an alloc failure then the last frame doesn't refer to the
// frame we want to fill in the function info for.
if (M3_UNLIKELY (io_runtime->backtrace.lastFrame == M3_BACKTRACE_TRUNCATED))
return;
if (!io_runtime->backtrace.lastFrame)
return;
io_runtime->backtrace.lastFrame->function = i_function;
}
void ClearBacktrace (IM3Runtime io_runtime)
{
M3BacktraceFrame * currentFrame = io_runtime->backtrace.frames;
while (currentFrame)
{
M3BacktraceFrame * nextFrame = currentFrame->next;
m3_Free (currentFrame);
currentFrame = nextFrame;
}
io_runtime->backtrace.frames = NULL;
io_runtime->backtrace.lastFrame = NULL;
}
#endif // d_m3RecordBacktraces
+311
View File
@@ -0,0 +1,311 @@
//
// m3_core.h
//
// Created by Steven Massey on 4/15/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_core_h
#define m3_core_h
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include "wasm3.h"
#include "m3_config.h"
# if defined(__cplusplus)
# define d_m3BeginExternC extern "C" {
# define d_m3EndExternC }
# else
# define d_m3BeginExternC
# define d_m3EndExternC
# endif
d_m3BeginExternC
#define d_m3ImplementFloat (d_m3HasFloat || d_m3NoFloatDynamic)
#if !defined(d_m3ShortTypesDefined)
typedef uint64_t u64;
typedef int64_t i64;
typedef uint32_t u32;
typedef int32_t i32;
typedef uint16_t u16;
typedef int16_t i16;
typedef uint8_t u8;
typedef int8_t i8;
#if d_m3ImplementFloat
typedef double f64;
typedef float f32;
#endif
#endif // d_m3ShortTypesDefined
#define PRIf32 "f"
#define PRIf64 "lf"
typedef const void * m3ret_t;
typedef const void * voidptr_t;
typedef const char * cstr_t;
typedef const char * const ccstr_t;
typedef const u8 * bytes_t;
typedef const u8 * const cbytes_t;
typedef u16 m3opcode_t;
typedef i64 m3reg_t;
# if d_m3Use32BitSlots
typedef u32 m3slot_t;
# else
typedef u64 m3slot_t;
# endif
typedef m3slot_t * m3stack_t;
typedef
const void * const cvptr_t;
# if defined (DEBUG)
# define d_m3Log(CATEGORY, FMT, ...) printf (" %8s | " FMT, #CATEGORY, ##__VA_ARGS__);
# if d_m3LogParse
# define m3log_parse(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_parse(...) {}
# endif
# if d_m3LogCompile
# define m3log_compile(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_compile(...) {}
# endif
# if d_m3LogEmit
# define m3log_emit(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_emit(...) {}
# endif
# if d_m3LogCodePages
# define m3log_code(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_code(...) {}
# endif
# if d_m3LogModule
# define m3log_module(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_module(...) {}
# endif
# if d_m3LogRuntime
# define m3log_runtime(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_runtime(...) {}
# endif
# define m3log(CATEGORY, FMT, ...) m3log_##CATEGORY (CATEGORY, FMT "\n", ##__VA_ARGS__)
# else
# define d_m3Log(CATEGORY, FMT, ...) {}
# define m3log(CATEGORY, FMT, ...) {}
# endif
# if defined(ASSERTS) || (defined(DEBUG) && !defined(NASSERTS))
# define d_m3Assert(ASS) if (!(ASS)) { printf("Assertion failed at %s:%d : %s\n", __FILE__, __LINE__, #ASS); abort(); }
# else
# define d_m3Assert(ASS)
# endif
typedef void /*const*/ * code_t;
typedef code_t const * /*__restrict__*/ pc_t;
typedef struct M3MemoryHeader
{
IM3Runtime runtime;
void * maxStack;
size_t length;
}
M3MemoryHeader;
struct M3CodeMappingPage;
typedef struct M3CodePageHeader
{
struct M3CodePage * next;
u32 lineIndex;
u32 numLines;
u32 sequence; // this is just used for debugging; could be removed
u32 usageCount;
# if d_m3RecordBacktraces
struct M3CodeMappingPage * mapping;
# endif // d_m3RecordBacktraces
}
M3CodePageHeader;
#define d_m3CodePageFreeLinesThreshold 4+2 // max is: select _sss & CallIndirect + 2 for bridge
#define d_m3DefaultMemPageSize 65536
#define d_m3Reg0SlotAlias 60000
#define d_m3Fp0SlotAlias (d_m3Reg0SlotAlias + 2)
#define d_m3MaxSaneTypesCount 1000000
#define d_m3MaxSaneFunctionsCount 1000000
#define d_m3MaxSaneImportsCount 100000
#define d_m3MaxSaneExportsCount 100000
#define d_m3MaxSaneGlobalsCount 1000000
#define d_m3MaxSaneElementSegments 10000000
#define d_m3MaxSaneDataSegments 100000
#define d_m3MaxSaneTableSize 10000000
#define d_m3MaxSaneUtf8Length 10000
#define d_m3MaxSaneFunctionArgRetCount 1000 // still insane, but whatever
#define d_externalKind_function 0
#define d_externalKind_table 1
#define d_externalKind_memory 2
#define d_externalKind_global 3
static const char * const c_waTypes [] = { "nil", "i32", "i64", "f32", "f64", "unknown" };
static const char * const c_waCompactTypes [] = { "_", "i", "I", "f", "F", "?" };
# if d_m3VerboseErrorMessages
M3Result m3Error (M3Result i_result, IM3Runtime i_runtime, IM3Module i_module, IM3Function i_function,
const char * const i_file, u32 i_lineNum, const char * const i_errorMessage, ...);
# define _m3Error(RESULT, RT, MOD, FUN, FILE, LINE, FORMAT, ...) \
m3Error (RESULT, RT, MOD, FUN, FILE, LINE, FORMAT, ##__VA_ARGS__)
# else
# define _m3Error(RESULT, RT, MOD, FUN, FILE, LINE, FORMAT, ...) (RESULT)
# endif
#define ErrorRuntime(RESULT, RUNTIME, FORMAT, ...) _m3Error (RESULT, RUNTIME, NULL, NULL, __FILE__, __LINE__, FORMAT, ##__VA_ARGS__)
#define ErrorModule(RESULT, MOD, FORMAT, ...) _m3Error (RESULT, MOD->runtime, MOD, NULL, __FILE__, __LINE__, FORMAT, ##__VA_ARGS__)
#define ErrorCompile(RESULT, COMP, FORMAT, ...) _m3Error (RESULT, COMP->runtime, COMP->module, NULL, __FILE__, __LINE__, FORMAT, ##__VA_ARGS__)
#if d_m3LogNativeStack
void m3StackCheckInit ();
void m3StackCheck ();
int m3StackGetMax ();
#else
#define m3StackCheckInit()
#define m3StackCheck()
#define m3StackGetMax() 0
#endif
#if d_m3LogTimestamps
#define PRIts "%llu"
uint64_t m3_GetTimestamp ();
#else
#define PRIts "%s"
#define m3_GetTimestamp() ""
#endif
void m3_Abort (const char* message);
void * m3_Malloc_Impl (size_t i_size);
void * m3_Realloc_Impl (void * i_ptr, size_t i_newSize, size_t i_oldSize);
void m3_Free_Impl (void * i_ptr);
void * m3_CopyMem (const void * i_from, size_t i_size);
#if d_m3LogHeapOps
// Tracing format: timestamp;heap:OpCode;name;size(bytes);new items;new ptr;old items;old ptr
static inline void * m3_AllocStruct_Impl(ccstr_t name, size_t i_size) {
void * result = m3_Malloc_Impl(i_size);
fprintf(stderr, PRIts ";heap:AllocStruct;%s;%zu;;%p;;\n", m3_GetTimestamp(), name, i_size, result);
return result;
}
static inline void * m3_AllocArray_Impl(ccstr_t name, size_t i_num, size_t i_size) {
void * result = m3_Malloc_Impl(i_size * i_num);
fprintf(stderr, PRIts ";heap:AllocArr;%s;%zu;%zu;%p;;\n", m3_GetTimestamp(), name, i_size, i_num, result);
return result;
}
static inline void * m3_ReallocArray_Impl(ccstr_t name, void * i_ptr_old, size_t i_num_new, size_t i_num_old, size_t i_size) {
void * result = m3_Realloc_Impl (i_ptr_old, i_size * i_num_new, i_size * i_num_old);
fprintf(stderr, PRIts ";heap:ReallocArr;%s;%zu;%zu;%p;%zu;%p\n", m3_GetTimestamp(), name, i_size, i_num_new, result, i_num_old, i_ptr_old);
return result;
}
static inline void * m3_Malloc (ccstr_t name, size_t i_size) {
void * result = m3_Malloc_Impl (i_size);
fprintf(stderr, PRIts ";heap:AllocMem;%s;%zu;;%p;;\n", m3_GetTimestamp(), name, i_size, result);
return result;
}
static inline void * m3_Realloc (ccstr_t name, void * i_ptr, size_t i_newSize, size_t i_oldSize) {
void * result = m3_Realloc_Impl (i_ptr, i_newSize, i_oldSize);
fprintf(stderr, PRIts ";heap:ReallocMem;%s;;%zu;%p;%zu;%p\n", m3_GetTimestamp(), name, i_newSize, result, i_oldSize, i_ptr);
return result;
}
#define m3_AllocStruct(STRUCT) (STRUCT *)m3_AllocStruct_Impl (#STRUCT, sizeof (STRUCT))
#define m3_AllocArray(STRUCT, NUM) (STRUCT *)m3_AllocArray_Impl (#STRUCT, NUM, sizeof (STRUCT))
#define m3_ReallocArray(STRUCT, PTR, NEW, OLD) (STRUCT *)m3_ReallocArray_Impl (#STRUCT, (void *)(PTR), (NEW), (OLD), sizeof (STRUCT))
#define m3_Free(P) do { void* p = (void*)(P); \
if (p) { fprintf(stderr, PRIts ";heap:FreeMem;;;;%p;\n", m3_GetTimestamp(), p); } \
m3_Free_Impl (p); (P) = NULL; } while(0)
#else
#define m3_Malloc(NAME, SIZE) m3_Malloc_Impl(SIZE)
#define m3_Realloc(NAME, PTR, NEW, OLD) m3_Realloc_Impl(PTR, NEW, OLD)
#define m3_AllocStruct(STRUCT) (STRUCT *)m3_Malloc_Impl (sizeof (STRUCT))
#define m3_AllocArray(STRUCT, NUM) (STRUCT *)m3_Malloc_Impl (sizeof (STRUCT) * (NUM))
#define m3_ReallocArray(STRUCT, PTR, NEW, OLD) (STRUCT *)m3_Realloc_Impl ((void *)(PTR), sizeof (STRUCT) * (NEW), sizeof (STRUCT) * (OLD))
#define m3_Free(P) do { m3_Free_Impl ((void*)(P)); (P) = NULL; } while(0)
#endif
M3Result NormalizeType (u8 * o_type, i8 i_convolutedWasmType);
bool IsIntType (u8 i_wasmType);
bool IsFpType (u8 i_wasmType);
bool Is64BitType (u8 i_m3Type);
u32 SizeOfType (u8 i_m3Type);
M3Result Read_u64 (u64 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result Read_u32 (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
#if d_m3ImplementFloat
M3Result Read_f64 (f64 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result Read_f32 (f32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
#endif
M3Result Read_u8 (u8 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result Read_opcode (m3opcode_t * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLebUnsigned (u64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLebSigned (i64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLEB_u32 (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLEB_u7 (u8 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLEB_i7 (i8 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLEB_i32 (i32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLEB_i64 (i64 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result Read_utf8 (cstr_t * o_utf8, bytes_t * io_bytes, cbytes_t i_end);
cstr_t SPrintValue (void * i_value, u8 i_type);
size_t SPrintArg (char * o_string, size_t i_stringBufferSize, voidptr_t i_sp, u8 i_type);
void ReportError (IM3Runtime io_runtime, IM3Module i_module, IM3Function i_function, ccstr_t i_errorMessage, ccstr_t i_file, u32 i_lineNum);
# if d_m3RecordBacktraces
void PushBacktraceFrame (IM3Runtime io_runtime, pc_t i_pc);
void FillBacktraceFunctionInfo (IM3Runtime io_runtime, IM3Function i_function);
void ClearBacktrace (IM3Runtime io_runtime);
# endif
d_m3EndExternC
#endif // m3_core_h
File diff suppressed because it is too large Load Diff
+221
View File
@@ -0,0 +1,221 @@
//
// m3_env.h
//
// Created by Steven Massey on 4/19/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_env_h
#define m3_env_h
#include "wasm3.h"
#include "m3_code.h"
#include "m3_compile.h"
d_m3BeginExternC
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3MemoryInfo
{
u32 initPages;
u32 maxPages;
u32 pageSize;
}
M3MemoryInfo;
typedef struct M3Memory
{
M3MemoryHeader * mallocated;
u32 numPages;
u32 maxPages;
u32 pageSize;
}
M3Memory;
typedef M3Memory * IM3Memory;
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3DataSegment
{
const u8 * initExpr; // wasm code
const u8 * data;
u32 initExprSize;
u32 memoryRegion;
u32 size;
}
M3DataSegment;
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3Global
{
M3ImportInfo import;
union
{
i32 i32Value;
i64 i64Value;
#if d_m3HasFloat
f64 f64Value;
f32 f32Value;
#endif
};
cstr_t name;
bytes_t initExpr; // wasm code
u32 initExprSize;
u8 type;
bool imported;
bool isMutable;
}
M3Global;
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3Module
{
struct M3Runtime * runtime;
struct M3Environment * environment;
bytes_t wasmStart;
bytes_t wasmEnd;
cstr_t name;
u32 numFuncTypes;
IM3FuncType * funcTypes; // array of pointers to list of FuncTypes
u32 numFuncImports;
u32 numFunctions;
u32 allFunctions; // allocated functions count
M3Function * functions;
i32 startFunction;
u32 numDataSegments;
M3DataSegment * dataSegments;
//u32 importedGlobals;
u32 numGlobals;
M3Global * globals;
u32 numElementSegments;
bytes_t elementSection;
bytes_t elementSectionEnd;
IM3Function * table0;
u32 table0Size;
const char* table0ExportName;
bool hasTable;
M3MemoryInfo memoryInfo;
M3ImportInfo memoryImport;
bool memoryImported;
bool memoryDeclared; // has a memory section entry
const char* memoryExportName;
//bool hasWasmCodeCopy;
struct M3Module * next;
}
M3Module;
M3Result Module_AddGlobal (IM3Module io_module, IM3Global * o_global, u8 i_type, bool i_mutable, bool i_isImported);
M3Result Module_PreallocFunctions (IM3Module io_module, u32 i_totalFunctions);
M3Result Module_AddFunction (IM3Module io_module, u32 i_typeIndex, IM3ImportInfo i_importInfo /* can be null */);
IM3Function Module_GetFunction (IM3Module i_module, u32 i_functionIndex);
void Module_GenerateNames (IM3Module i_module);
void FreeImportInfo (M3ImportInfo * i_info);
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3Environment
{
// struct M3Runtime * runtimes;
IM3FuncType funcTypes; // linked list of unique M3FuncType structs that can be compared using pointer-equivalence
IM3FuncType retFuncTypes [c_m3Type_unknown]; // these 'point' to elements in the linked list above.
// the number of elements must match the basic types as per M3ValueType
M3CodePage * pagesReleased;
M3SectionHandler customSectionHandler;
}
M3Environment;
void Environment_Release (IM3Environment i_environment);
// takes ownership of io_funcType and returns a pointer to the persistent version (could be same or different)
void Environment_AddFuncType (IM3Environment i_environment, IM3FuncType * io_funcType);
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3Runtime
{
M3Compilation compilation;
IM3Environment environment;
M3CodePage * pagesOpen; // linked list of code pages with writable space on them
M3CodePage * pagesFull; // linked list of at-capacity pages
u32 numCodePages;
u32 numActiveCodePages;
IM3Module modules; // linked list of imported modules
void * stack;
void * originStack;
u32 stackSize;
u32 numStackSlots;
IM3Function lastCalled; // last function that successfully executed
void * userdata;
M3Memory memory;
u32 memoryLimit;
#if d_m3EnableStrace >= 2
u32 callDepth;
#endif
M3ErrorInfo error;
#if d_m3VerboseErrorMessages
char error_message[256]; // the actual buffer. M3ErrorInfo can point to this
#endif
#if d_m3RecordBacktraces
M3BacktraceInfo backtrace;
#endif
u32 newCodePageSequence;
}
M3Runtime;
void InitRuntime (IM3Runtime io_runtime, u32 i_stackSizeInBytes);
void Runtime_Release (IM3Runtime io_runtime);
M3Result ResizeMemory (IM3Runtime io_runtime, u32 i_numPages);
typedef void * (* ModuleVisitor) (IM3Module i_module, void * i_info);
void * ForEachModule (IM3Runtime i_runtime, ModuleVisitor i_visitor, void * i_info);
void * v_FindFunction (IM3Module i_module, const char * const i_name);
IM3CodePage AcquireCodePage (IM3Runtime io_runtime);
IM3CodePage AcquireCodePageWithCapacity (IM3Runtime io_runtime, u32 i_lineCount);
void ReleaseCodePage (IM3Runtime io_runtime, IM3CodePage i_codePage);
d_m3EndExternC
#endif // m3_env_h
+33
View File
@@ -0,0 +1,33 @@
//
// m3_exception.h
//
// Created by Steven Massey on 7/5/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
// some macros to emulate try/catch
#ifndef m3_exception_h
#define m3_exception_h
#include "m3_config.h"
# if d_m3EnableExceptionBreakpoint
// declared in m3_info.c
void ExceptionBreakpoint (cstr_t i_exception, cstr_t i_message);
# define EXCEPTION_PRINT(ERROR) ExceptionBreakpoint (ERROR, (__FILE__ ":" M3_STR(__LINE__)))
# else
# define EXCEPTION_PRINT(...)
# endif
#define _try M3Result result = m3Err_none;
#define _(TRY) { result = TRY; if (M3_UNLIKELY(result)) { EXCEPTION_PRINT (result); goto _catch; } }
#define _throw(ERROR) { result = ERROR; EXCEPTION_PRINT (result); goto _catch; }
#define _throwif(ERROR, COND) if (M3_UNLIKELY(COND)) { _throw(ERROR); }
#define _throwifnull(PTR) _throwif (m3Err_mallocFailed, !(PTR))
#endif // m3_exception_h
+8
View File
@@ -0,0 +1,8 @@
//
// m3_exec.c
//
// Created by Steven Massey on 4/17/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
// EMPTY FOR NOW
File diff suppressed because it is too large Load Diff
+76
View File
@@ -0,0 +1,76 @@
//
// m3_exec_defs.h
//
// Created by Steven Massey on 5/1/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_exec_defs_h
#define m3_exec_defs_h
#include "m3_core.h"
d_m3BeginExternC
# define m3MemData(mem) (u8*)(((M3MemoryHeader*)(mem))+1)
# define m3MemRuntime(mem) (((M3MemoryHeader*)(mem))->runtime)
# define m3MemInfo(mem) (&(((M3MemoryHeader*)(mem))->runtime->memory))
# define d_m3BaseOpSig pc_t _pc, m3stack_t _sp, M3MemoryHeader * _mem, m3reg_t _r0
# define d_m3BaseOpArgs _sp, _mem, _r0
# define d_m3BaseOpAllArgs _pc, _sp, _mem, _r0
# define d_m3BaseOpDefaultArgs 0
# define d_m3BaseClearRegisters _r0 = 0;
# define d_m3BaseCstr ""
# define d_m3ExpOpSig(...) d_m3BaseOpSig, __VA_ARGS__
# define d_m3ExpOpArgs(...) d_m3BaseOpArgs, __VA_ARGS__
# define d_m3ExpOpAllArgs(...) d_m3BaseOpAllArgs, __VA_ARGS__
# define d_m3ExpOpDefaultArgs(...) d_m3BaseOpDefaultArgs, __VA_ARGS__
# define d_m3ExpClearRegisters(...) d_m3BaseClearRegisters; __VA_ARGS__
# if d_m3HasFloat
# define d_m3OpSig d_m3ExpOpSig (f64 _fp0)
# define d_m3OpArgs d_m3ExpOpArgs (_fp0)
# define d_m3OpAllArgs d_m3ExpOpAllArgs (_fp0)
# define d_m3OpDefaultArgs d_m3ExpOpDefaultArgs (0.)
# define d_m3ClearRegisters d_m3ExpClearRegisters (_fp0 = 0.;)
# else
# define d_m3OpSig d_m3BaseOpSig
# define d_m3OpArgs d_m3BaseOpArgs
# define d_m3OpAllArgs d_m3BaseOpAllArgs
# define d_m3OpDefaultArgs d_m3BaseOpDefaultArgs
# define d_m3ClearRegisters d_m3BaseClearRegisters
# endif
#define d_m3RetSig static inline m3ret_t vectorcall
# if (d_m3EnableOpProfiling || d_m3EnableOpTracing)
typedef m3ret_t (vectorcall * IM3Operation) (d_m3OpSig, cstr_t i_operationName);
# define d_m3Op(NAME) M3_NO_UBSAN d_m3RetSig op_##NAME (d_m3OpSig, cstr_t i_operationName)
# define nextOpImpl() ((IM3Operation)(* _pc))(_pc + 1, d_m3OpArgs, __FUNCTION__)
# define jumpOpImpl(PC) ((IM3Operation)(* PC))( PC + 1, d_m3OpArgs, __FUNCTION__)
# else
typedef m3ret_t (vectorcall * IM3Operation) (d_m3OpSig);
# define d_m3Op(NAME) M3_NO_UBSAN d_m3RetSig op_##NAME (d_m3OpSig)
# define nextOpImpl() ((IM3Operation)(* _pc))(_pc + 1, d_m3OpArgs)
# define jumpOpImpl(PC) ((IM3Operation)(* PC))( PC + 1, d_m3OpArgs)
# endif
#define nextOpDirect() M3_MUSTTAIL return nextOpImpl()
#define jumpOpDirect(PC) M3_MUSTTAIL return jumpOpImpl((pc_t)(PC))
# if (d_m3EnableOpProfiling || d_m3EnableOpTracing)
d_m3RetSig RunCode (d_m3OpSig, cstr_t i_operationName)
# else
d_m3RetSig RunCode (d_m3OpSig)
# endif
{
nextOpDirect();
}
d_m3EndExternC
#endif // m3_exec_defs_h
+233
View File
@@ -0,0 +1,233 @@
//
// m3_function.c
//
// Created by Steven Massey on 4/7/21.
// Copyright © 2021 Steven Massey. All rights reserved.
//
#include "m3_function.h"
#include "m3_env.h"
M3Result AllocFuncType (IM3FuncType * o_functionType, u32 i_numTypes)
{
*o_functionType = (IM3FuncType) m3_Malloc ("M3FuncType", sizeof (M3FuncType) + i_numTypes);
return (*o_functionType) ? m3Err_none : m3Err_mallocFailed;
}
bool AreFuncTypesEqual (const IM3FuncType i_typeA, const IM3FuncType i_typeB)
{
if (i_typeA->numRets == i_typeB->numRets && i_typeA->numArgs == i_typeB->numArgs)
{
return (memcmp (i_typeA->types, i_typeB->types, i_typeA->numRets + i_typeA->numArgs) == 0);
}
return false;
}
u16 GetFuncTypeNumParams (const IM3FuncType i_funcType)
{
return i_funcType ? i_funcType->numArgs : 0;
}
u8 GetFuncTypeParamType (const IM3FuncType i_funcType, u16 i_index)
{
u8 type = c_m3Type_unknown;
if (i_funcType)
{
if (i_index < i_funcType->numArgs)
{
type = i_funcType->types [i_funcType->numRets + i_index];
}
}
return type;
}
u16 GetFuncTypeNumResults (const IM3FuncType i_funcType)
{
return i_funcType ? i_funcType->numRets : 0;
}
u8 GetFuncTypeResultType (const IM3FuncType i_funcType, u16 i_index)
{
u8 type = c_m3Type_unknown;
if (i_funcType)
{
if (i_index < i_funcType->numRets)
{
type = i_funcType->types [i_index];
}
}
return type;
}
//---------------------------------------------------------------------------------------------------------------
void FreeImportInfo (M3ImportInfo * i_info)
{
m3_Free (i_info->moduleUtf8);
m3_Free (i_info->fieldUtf8);
}
void Function_Release (IM3Function i_function)
{
m3_Free (i_function->constants);
for (int i = 0; i < i_function->numNames; i++)
{
// name can be an alias of fieldUtf8
if (i_function->names[i] != i_function->import.fieldUtf8)
{
m3_Free (i_function->names[i]);
}
}
FreeImportInfo (& i_function->import);
if (i_function->ownsWasmCode)
m3_Free (i_function->wasm);
// Function_FreeCompiledCode (func);
# if (d_m3EnableCodePageRefCounting)
{
m3_Free (i_function->codePageRefs);
i_function->numCodePageRefs = 0;
}
# endif
}
void Function_FreeCompiledCode (IM3Function i_function)
{
# if (d_m3EnableCodePageRefCounting)
{
i_function->compiled = NULL;
while (i_function->numCodePageRefs--)
{
IM3CodePage page = i_function->codePageRefs [i_function->numCodePageRefs];
if (--(page->info.usageCount) == 0)
{
// printf ("free %p\n", page);
}
}
m3_Free (i_function->codePageRefs);
Runtime_ReleaseCodePages (i_function->module->runtime);
}
# endif
}
cstr_t m3_GetFunctionName (IM3Function i_function)
{
u16 numNames = 0;
cstr_t *names = GetFunctionNames(i_function, &numNames);
if (numNames > 0)
return names[0];
else
return "<unnamed>";
}
IM3Module m3_GetFunctionModule (IM3Function i_function)
{
return i_function ? i_function->module : NULL;
}
cstr_t * GetFunctionNames (IM3Function i_function, u16 * o_numNames)
{
if (!i_function || !o_numNames)
return NULL;
if (i_function->import.fieldUtf8)
{
*o_numNames = 1;
return &i_function->import.fieldUtf8;
}
else
{
*o_numNames = i_function->numNames;
return i_function->names;
}
}
cstr_t GetFunctionImportModuleName (IM3Function i_function)
{
return (i_function->import.moduleUtf8) ? i_function->import.moduleUtf8 : "";
}
u16 GetFunctionNumArgs (IM3Function i_function)
{
u16 numArgs = 0;
if (i_function)
{
if (i_function->funcType)
numArgs = i_function->funcType->numArgs;
}
return numArgs;
}
u8 GetFunctionArgType (IM3Function i_function, u32 i_index)
{
u8 type = c_m3Type_none;
if (i_index < GetFunctionNumArgs (i_function))
{
u32 numReturns = i_function->funcType->numRets;
type = i_function->funcType->types [numReturns + i_index];
}
return type;
}
u16 GetFunctionNumReturns (IM3Function i_function)
{
u16 numReturns = 0;
if (i_function)
{
if (i_function->funcType)
numReturns = i_function->funcType->numRets;
}
return numReturns;
}
u8 GetFunctionReturnType (const IM3Function i_function, u16 i_index)
{
return i_function ? GetFuncTypeResultType (i_function->funcType, i_index) : c_m3Type_unknown;
}
u32 GetFunctionNumArgsAndLocals (IM3Function i_function)
{
if (i_function)
return i_function->numLocals + GetFunctionNumArgs (i_function);
else
return 0;
}
+103
View File
@@ -0,0 +1,103 @@
//
// m3_function.h
//
// Created by Steven Massey on 4/7/21.
// Copyright © 2021 Steven Massey. All rights reserved.
//
#ifndef m3_function_h
#define m3_function_h
#include "m3_core.h"
d_m3BeginExternC
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3FuncType
{
struct M3FuncType * next;
u16 numRets;
u16 numArgs;
u8 types []; // returns, then args
}
M3FuncType;
typedef M3FuncType * IM3FuncType;
M3Result AllocFuncType (IM3FuncType * o_functionType, u32 i_numTypes);
bool AreFuncTypesEqual (const IM3FuncType i_typeA, const IM3FuncType i_typeB);
u16 GetFuncTypeNumParams (const IM3FuncType i_funcType);
u8 GetFuncTypeParamType (const IM3FuncType i_funcType, u16 i_index);
u16 GetFuncTypeNumResults (const IM3FuncType i_funcType);
u8 GetFuncTypeResultType (const IM3FuncType i_funcType, u16 i_index);
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3Function
{
struct M3Module * module;
M3ImportInfo import;
bytes_t wasm;
bytes_t wasmEnd;
cstr_t names[d_m3MaxDuplicateFunctionImpl];
cstr_t export_name; // should be a part of "names"
u16 numNames; // maximum of d_m3MaxDuplicateFunctionImpl
IM3FuncType funcType;
pc_t compiled;
# if (d_m3EnableCodePageRefCounting)
IM3CodePage * codePageRefs; // array of all pages used
u32 numCodePageRefs;
# endif
# if defined (DEBUG)
u32 hits;
u32 index;
# endif
u16 maxStackSlots;
u16 numRetSlots;
u16 numRetAndArgSlots;
u16 numLocals; // not including args
u16 numLocalBytes;
bool ownsWasmCode;
u16 numConstantBytes;
void * constants;
}
M3Function;
void Function_Release (IM3Function i_function);
void Function_FreeCompiledCode (IM3Function i_function);
cstr_t GetFunctionImportModuleName (IM3Function i_function);
cstr_t * GetFunctionNames (IM3Function i_function, u16 * o_numNames);
u16 GetFunctionNumArgs (IM3Function i_function);
u8 GetFunctionArgType (IM3Function i_function, u32 i_index);
u16 GetFunctionNumReturns (IM3Function i_function);
u8 GetFunctionReturnType (const IM3Function i_function, u16 i_index);
u32 GetFunctionNumArgsAndLocals (IM3Function i_function);
cstr_t SPrintFunctionArgList (IM3Function i_function, m3stack_t i_sp);
//---------------------------------------------------------------------------------------------------------------------------------
d_m3EndExternC
#endif /* m3_function_h */
+564
View File
@@ -0,0 +1,564 @@
//
// m3_info.c
//
// Created by Steven Massey on 4/27/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#include "m3_env.h"
#include "m3_info.h"
#include "m3_compile.h"
#if defined(DEBUG) || (d_m3EnableStrace >= 2)
size_t SPrintArg (char * o_string, size_t i_stringBufferSize, voidptr_t i_sp, u8 i_type)
{
int len = 0;
* o_string = 0;
if (i_type == c_m3Type_i32)
len = snprintf (o_string, i_stringBufferSize, "%" PRIi32, * (i32 *) i_sp);
else if (i_type == c_m3Type_i64)
len = snprintf (o_string, i_stringBufferSize, "%" PRIi64, * (i64 *) i_sp);
#if d_m3HasFloat
else if (i_type == c_m3Type_f32)
len = snprintf (o_string, i_stringBufferSize, "%" PRIf32, * (f32 *) i_sp);
else if (i_type == c_m3Type_f64)
len = snprintf (o_string, i_stringBufferSize, "%" PRIf64, * (f64 *) i_sp);
#endif
len = M3_MAX (0, len);
return len;
}
cstr_t SPrintFunctionArgList (IM3Function i_function, m3stack_t i_sp)
{
int ret;
static char string [256];
char * s = string;
ccstr_t e = string + sizeof(string) - 1;
ret = snprintf (s, e-s, "(");
s += M3_MAX (0, ret);
u64 * argSp = (u64 *) i_sp;
IM3FuncType funcType = i_function->funcType;
if (funcType)
{
u32 numArgs = funcType->numArgs;
for (u32 i = 0; i < numArgs; ++i)
{
u8 type = d_FuncArgType(funcType, i);
ret = snprintf (s, e-s, "%s: ", c_waTypes [type]);
s += M3_MAX (0, ret);
s += SPrintArg (s, e-s, argSp + i, type);
if (i != numArgs - 1) {
ret = snprintf (s, e-s, ", ");
s += M3_MAX (0, ret);
}
}
}
else printf ("null signature");
ret = snprintf (s, e-s, ")");
s += M3_MAX (0, ret);
return string;
}
#endif
#ifdef DEBUG
// a central function you can be breakpoint:
void ExceptionBreakpoint (cstr_t i_exception, cstr_t i_message)
{
printf ("\nexception: '%s' @ %s\n", i_exception, i_message);
return;
}
typedef struct OpInfo
{
IM3OpInfo info;
m3opcode_t opcode;
}
OpInfo;
void m3_PrintM3Info ()
{
printf ("\n-- m3 configuration --------------------------------------------\n");
// printf (" sizeof M3CodePage : %zu bytes (%d slots) \n", sizeof (M3CodePage), c_m3CodePageNumSlots);
printf (" sizeof M3MemPage : %u bytes \n", d_m3DefaultMemPageSize);
printf (" sizeof M3Compilation : %zu bytes \n", sizeof (M3Compilation));
printf (" sizeof M3Function : %zu bytes \n", sizeof (M3Function));
printf ("----------------------------------------------------------------\n\n");
}
void * v_PrintEnvModuleInfo (IM3Module i_module, u32 * io_index)
{
printf (" module [%u] name: '%s'; funcs: %d \n", * io_index++, i_module->name, i_module->numFunctions);
return NULL;
}
void m3_PrintRuntimeInfo (IM3Runtime i_runtime)
{
printf ("\n-- m3 runtime -------------------------------------------------\n");
printf (" stack-size: %zu \n\n", i_runtime->numStackSlots * sizeof (m3slot_t));
u32 moduleIndex = 0;
ForEachModule (i_runtime, (ModuleVisitor) v_PrintEnvModuleInfo, & moduleIndex);
printf ("----------------------------------------------------------------\n\n");
}
cstr_t GetTypeName (u8 i_m3Type)
{
if (i_m3Type < 5)
return c_waTypes [i_m3Type];
else
return "?";
}
// TODO: these 'static char string []' aren't thread-friendly. though these functions are
// mainly for simple diagnostics during development, it'd be nice if they were fully reliable.
cstr_t SPrintFuncTypeSignature (IM3FuncType i_funcType)
{
static char string [256];
sprintf (string, "(");
for (u32 i = 0; i < i_funcType->numArgs; ++i)
{
if (i != 0)
strcat (string, ", ");
strcat (string, GetTypeName (d_FuncArgType(i_funcType, i)));
}
strcat (string, ") -> ");
for (u32 i = 0; i < i_funcType->numRets; ++i)
{
if (i != 0)
strcat (string, ", ");
strcat (string, GetTypeName (d_FuncRetType(i_funcType, i)));
}
return string;
}
cstr_t SPrintValue (void * i_value, u8 i_type)
{
static char string [100];
SPrintArg (string, 100, (m3stack_t) i_value, i_type);
return string;
}
static
OpInfo find_operation_info (IM3Operation i_operation)
{
OpInfo opInfo = { NULL, 0 };
if (!i_operation) return opInfo;
// TODO: find also extended opcodes
for (u32 i = 0; i <= 0xff; ++i)
{
IM3OpInfo oi = GetOpInfo (i);
if (oi->type != c_m3Type_unknown)
{
for (u32 o = 0; o < 4; ++o)
{
if (oi->operations [o] == i_operation)
{
opInfo.info = oi;
opInfo.opcode = i;
break;
}
}
}
else break;
}
return opInfo;
}
#undef fetch
#define fetch(TYPE) (* (TYPE *) ((*o_pc)++))
#define d_m3Decoder(FUNC) void Decode_##FUNC (char * o_string, u8 i_opcode, IM3Operation i_operation, IM3OpInfo i_opInfo, pc_t * o_pc)
d_m3Decoder (Call)
{
void * function = fetch (void *);
i32 stackOffset = fetch (i32);
sprintf (o_string, "%p; stack-offset: %d", function, stackOffset);
}
d_m3Decoder (Entry)
{
IM3Function function = fetch (IM3Function);
// only prints out the first registered name for the function
sprintf (o_string, "%s", m3_GetFunctionName(function));
}
d_m3Decoder (f64_Store)
{
if (i_operation == i_opInfo->operations [0])
{
u32 operand = fetch (u32);
u32 offset = fetch (u32);
sprintf (o_string, "offset= slot:%d + immediate:%d", operand, offset);
}
// sprintf (o_string, "%s", function->name);
}
d_m3Decoder (Branch)
{
void * target = fetch (void *);
sprintf (o_string, "%p", target);
}
d_m3Decoder (BranchTable)
{
u32 slot = fetch (u32);
o_string += sprintf (o_string, "slot: %" PRIu32 "; targets: ", slot);
// IM3Function function = fetch2 (IM3Function);
i32 targets = fetch (i32);
for (i32 i = 0; i < targets; ++i)
{
pc_t addr = fetch (pc_t);
o_string += sprintf (o_string, "%" PRIi32 "=%p, ", i, addr);
}
pc_t addr = fetch (pc_t);
sprintf (o_string, "def=%p ", addr);
}
d_m3Decoder (Const)
{
u64 value = fetch (u64); i32 offset = fetch (i32);
sprintf (o_string, " slot [%d] = %" PRIu64, offset, value);
}
#undef fetch
void DecodeOperation (char * o_string, u8 i_opcode, IM3Operation i_operation, IM3OpInfo i_opInfo, pc_t * o_pc)
{
#define d_m3Decode(OPCODE, FUNC) case OPCODE: Decode_##FUNC (o_string, i_opcode, i_operation, i_opInfo, o_pc); break;
switch (i_opcode)
{
// d_m3Decode (0xc0, Const)
d_m3Decode (0xc5, Entry)
d_m3Decode (c_waOp_call, Call)
d_m3Decode (c_waOp_branch, Branch)
d_m3Decode (c_waOp_branchTable, BranchTable)
d_m3Decode (0x39, f64_Store)
}
}
// WARNING/TODO: this isn't fully implemented. it blindly assumes each word is a Operation pointer
// and, if an operation happens to missing from the c_operations table it won't be recognized here
void dump_code_page (IM3CodePage i_codePage, pc_t i_startPC)
{
m3log (code, "code page seq: %d", i_codePage->info.sequence);
pc_t pc = i_startPC ? i_startPC : GetPageStartPC (i_codePage);
pc_t end = GetPagePC (i_codePage);
m3log (code, "---------------------------------------------------------------------------------------");
while (pc < end)
{
pc_t operationPC = pc;
IM3Operation op = (IM3Operation) (* pc++);
OpInfo i = find_operation_info (op);
if (i.info)
{
char infoString [8*1024] = { 0 };
DecodeOperation (infoString, i.opcode, op, i.info, & pc);
m3log (code, "%p | %20s %s", operationPC, i.info->name, infoString);
}
else
m3log (code, "%p | %p", operationPC, op);
}
m3log (code, "---------------------------------------------------------------------------------------");
m3log (code, "free-lines: %d", i_codePage->info.numLines - i_codePage->info.lineIndex);
}
void dump_type_stack (IM3Compilation o)
{
/* Reminders about how the stack works! :)
-- args & locals remain on the type stack for duration of the function. Denoted with a constant 'A' and 'L' in this dump.
-- the initial stack dumps originate from the CompileLocals () function, so these identifiers won't/can't be
applied until this compilation stage is finished
-- constants are not statically represented in the type stack (like args & constants) since they don't have/need
write counts
-- the number shown for static args and locals (value in wasmStack [i]) represents the write count for the variable
-- (does Wasm ever write to an arg? I dunno/don't remember.)
-- the number for the dynamic stack values represents the slot number.
-- if the slot index points to arg, local or constant it's denoted with a lowercase 'a', 'l' or 'c'
*/
// for the assert at end of dump:
i32 regAllocated [2] = { (i32) IsRegisterAllocated (o, 0), (i32) IsRegisterAllocated (o, 1) };
// display whether r0 or fp0 is allocated. these should then also be reflected somewhere in the stack too.
d_m3Log(stack, "\n");
d_m3Log(stack, " ");
printf ("%s %s ", regAllocated [0] ? "(r0)" : " ", regAllocated [1] ? "(fp0)" : " ");
printf("\n");
for (u32 p = 1; p <= 2; ++p)
{
d_m3Log(stack, " ");
for (u16 i = 0; i < o->stackIndex; ++i)
{
if (i > 0 and i == o->stackFirstDynamicIndex)
printf ("#");
if (i == o->block.blockStackIndex)
printf (">");
const char * type = c_waCompactTypes [o->typeStack [i]];
const char * location = "";
i32 slot = o->wasmStack [i];
if (IsRegisterSlotAlias (slot))
{
bool isFp = IsFpRegisterSlotAlias (slot);
location = isFp ? "/f" : "/r";
regAllocated [isFp]--;
slot = -1;
}
else
{
if (slot < o->slotFirstDynamicIndex)
{
if (slot >= o->slotFirstConstIndex)
location = "c";
else if (slot >= o->function->numRetAndArgSlots)
location = "L";
else
location = "a";
}
}
char item [100];
if (slot >= 0)
sprintf (item, "%s%s%d", type, location, slot);
else
sprintf (item, "%s%s", type, location);
if (p == 1)
{
size_t s = strlen (item);
sprintf (item, "%d", i);
while (strlen (item) < s)
strcat (item, " ");
}
printf ("|%s ", item);
}
printf ("\n");
}
// for (u32 r = 0; r < 2; ++r)
// d_m3Assert (regAllocated [r] == 0); // reg allocation & stack out of sync
u16 maxSlot = GetMaxUsedSlotPlusOne (o);
if (maxSlot > o->slotFirstDynamicIndex)
{
d_m3Log (stack, " -");
for (u16 i = o->slotFirstDynamicIndex; i < maxSlot; ++i)
printf ("----");
printf ("\n");
d_m3Log (stack, " slot |");
for (u16 i = o->slotFirstDynamicIndex; i < maxSlot; ++i)
printf ("%3d|", i);
printf ("\n");
d_m3Log (stack, " alloc |");
for (u16 i = o->slotFirstDynamicIndex; i < maxSlot; ++i)
{
printf ("%3d|", o->m3Slots [i]);
}
printf ("\n");
}
d_m3Log(stack, "\n");
}
static const char * GetOpcodeIndentionString (i32 blockDepth)
{
blockDepth += 1;
if (blockDepth < 0)
blockDepth = 0;
static const char * s_spaces = ".......................................................................................";
const char * indent = s_spaces + strlen (s_spaces);
indent -= (blockDepth * 2);
if (indent < s_spaces)
indent = s_spaces;
return indent;
}
const char * get_indention_string (IM3Compilation o)
{
return GetOpcodeIndentionString (o->block.depth+4);
}
void log_opcode (IM3Compilation o, m3opcode_t i_opcode)
{
i32 depth = o->block.depth;
if (i_opcode == c_waOp_end or i_opcode == c_waOp_else)
depth--;
m3log (compile, "%4d | 0x%02x %s %s", o->numOpcodes++, i_opcode, GetOpcodeIndentionString (depth), GetOpInfo(i_opcode)->name);
}
void log_emit (IM3Compilation o, IM3Operation i_operation)
{
OpInfo i = find_operation_info (i_operation);
d_m3Log(emit, "");
if (i.info)
{
printf ("%p: %s\n", GetPagePC (o->page), i.info->name);
}
else printf ("not found: %p\n", i_operation);
}
#endif // DEBUG
# if d_m3EnableOpProfiling
typedef struct M3ProfilerSlot
{
cstr_t opName;
u64 hitCount;
}
M3ProfilerSlot;
static M3ProfilerSlot s_opProfilerCounts [d_m3ProfilerSlotMask + 1] = {};
void ProfileHit (cstr_t i_operationName)
{
u64 ptr = (u64) i_operationName;
M3ProfilerSlot * slot = & s_opProfilerCounts [ptr & d_m3ProfilerSlotMask];
if (slot->opName)
{
if (slot->opName != i_operationName)
{
m3_Abort ("profiler slot collision; increase d_m3ProfilerSlotMask");
}
}
slot->opName = i_operationName;
slot->hitCount++;
}
void m3_PrintProfilerInfo ()
{
M3ProfilerSlot dummy;
M3ProfilerSlot * maxSlot = & dummy;
do
{
maxSlot->hitCount = 0;
for (u32 i = 0; i <= d_m3ProfilerSlotMask; ++i)
{
M3ProfilerSlot * slot = & s_opProfilerCounts [i];
if (slot->opName)
{
if (slot->hitCount > maxSlot->hitCount)
maxSlot = slot;
}
}
if (maxSlot->opName)
{
fprintf (stderr, "%13llu %s\n", maxSlot->hitCount, maxSlot->opName);
maxSlot->opName = NULL;
}
}
while (maxSlot->hitCount);
}
# else
void m3_PrintProfilerInfo () {}
# endif
+38
View File
@@ -0,0 +1,38 @@
//
// m3_info.h
//
// Created by Steven Massey on 12/6/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_info_h
#define m3_info_h
#include "m3_compile.h"
d_m3BeginExternC
void ProfileHit (cstr_t i_operationName);
#ifdef DEBUG
void dump_type_stack (IM3Compilation o);
void log_opcode (IM3Compilation o, m3opcode_t i_opcode);
const char * get_indention_string (IM3Compilation o);
void log_emit (IM3Compilation o, IM3Operation i_operation);
cstr_t SPrintFuncTypeSignature (IM3FuncType i_funcType);
#else // DEBUG
#define dump_type_stack(...) {}
#define log_opcode(...) {}
#define get_indention_string(...) ""
#define emit_stack_dump(...) {}
#define log_emit(...) {}
#endif // DEBUG
d_m3EndExternC
#endif // m3_info_h
+316
View File
@@ -0,0 +1,316 @@
//
// m3_math_utils.h
//
// Created by Volodymyr Shymanksyy on 8/10/19.
// Copyright © 2019 Volodymyr Shymanskyy. All rights reserved.
//
#ifndef m3_math_utils_h
#define m3_math_utils_h
#include "m3_core.h"
#include <limits.h>
#if defined(M3_COMPILER_MSVC)
#include <intrin.h>
#define __builtin_popcount __popcnt
static inline
int __builtin_ctz(uint32_t x) {
unsigned long ret;
_BitScanForward(&ret, x);
return (int)ret;
}
static inline
int __builtin_clz(uint32_t x) {
unsigned long ret;
_BitScanReverse(&ret, x);
return (int)(31 ^ ret);
}
#ifdef _WIN64
#define __builtin_popcountll __popcnt64
static inline
int __builtin_ctzll(uint64_t value) {
unsigned long ret;
_BitScanForward64(&ret, value);
return (int)ret;
}
static inline
int __builtin_clzll(uint64_t value) {
unsigned long ret;
_BitScanReverse64(&ret, value);
return (int)(63 ^ ret);
}
#else // _WIN64
#define __builtin_popcountll(x) (__popcnt((x) & 0xFFFFFFFF) + __popcnt((x) >> 32))
static inline
int __builtin_ctzll(uint64_t value) {
//if (value == 0) return 64; // Note: ctz(0) result is undefined anyway
uint32_t msh = (uint32_t)(value >> 32);
uint32_t lsh = (uint32_t)(value & 0xFFFFFFFF);
if (lsh != 0) return __builtin_ctz(lsh);
return 32 + __builtin_ctz(msh);
}
static inline
int __builtin_clzll(uint64_t value) {
//if (value == 0) return 64; // Note: clz(0) result is undefined anyway
uint32_t msh = (uint32_t)(value >> 32);
uint32_t lsh = (uint32_t)(value & 0xFFFFFFFF);
if (msh != 0) return __builtin_clz(msh);
return 32 + __builtin_clz(lsh);
}
#endif // _WIN64
#endif // defined(M3_COMPILER_MSVC)
// TODO: not sure why, signbit is actually defined in math.h
#if (defined(ESP8266) || defined(ESP32)) && !defined(signbit)
#define signbit(__x) \
((sizeof(__x) == sizeof(float)) ? __signbitf(__x) : __signbitd(__x))
#endif
#if defined(__AVR__)
static inline
float rintf( float arg ) {
union { float f; uint32_t i; } u;
u.f = arg;
uint32_t ux = u.i & 0x7FFFFFFF;
if (M3_UNLIKELY(ux == 0 || ux > 0x5A000000)) {
return arg;
}
return (float)lrint(arg);
}
static inline
double rint( double arg ) {
union { double f; uint32_t i[2]; } u;
u.f = arg;
uint32_t ux = u.i[1] & 0x7FFFFFFF;
if (M3_UNLIKELY((ux == 0 && u.i[0] == 0) || ux > 0x433FFFFF)) {
return arg;
}
return (double)lrint(arg);
}
static inline
uint64_t strtoull(const char* str, char** endptr, int base) {
uint64_t result = 0;
const char* p = str;
while (*p == ' ' || *p == '\t') p++;
if (base == 0) {
if (*p == '0' && (*(p+1) == 'x' || *(p+1) == 'X')) {
base = 16; p += 2;
} else if (*p == '0') {
base = 8; p++;
} else {
base = 10;
}
} else if (base == 16 && *p == '0' && (*(p+1) == 'x' || *(p+1) == 'X')) {
p += 2;
}
while (*p) {
int digit;
if (*p >= '0' && *p <= '9') digit = *p - '0';
else if (*p >= 'a' && *p <= 'f') digit = *p - 'a' + 10;
else if (*p >= 'A' && *p <= 'F') digit = *p - 'A' + 10;
else break;
if (digit >= base) break;
result = result * base + digit;
p++;
}
if (endptr) *endptr = (char*)p;
return result;
}
#endif
/*
* Rotr, Rotl
*/
static inline
u32 rotl32(u32 n, unsigned c) {
const unsigned mask = CHAR_BIT * sizeof(n) - 1;
c &= mask & 31;
return (n << c) | (n >> ((-c) & mask));
}
static inline
u32 rotr32(u32 n, unsigned c) {
const unsigned mask = CHAR_BIT * sizeof(n) - 1;
c &= mask & 31;
return (n >> c) | (n << ((-c) & mask));
}
static inline
u64 rotl64(u64 n, unsigned c) {
const unsigned mask = CHAR_BIT * sizeof(n) - 1;
c &= mask & 63;
return (n << c) | (n >> ((-c) & mask));
}
static inline
u64 rotr64(u64 n, unsigned c) {
const unsigned mask = CHAR_BIT * sizeof(n) - 1;
c &= mask & 63;
return (n >> c) | (n << ((-c) & mask));
}
/*
* Integer Div, Rem
*/
#define OP_DIV_U(RES, A, B) \
if (M3_UNLIKELY(B == 0)) newTrap (m3Err_trapDivisionByZero); \
RES = A / B;
#define OP_REM_U(RES, A, B) \
if (M3_UNLIKELY(B == 0)) newTrap (m3Err_trapDivisionByZero); \
RES = A % B;
// 2's complement detection
#if (INT_MIN != -INT_MAX)
#define OP_DIV_S(RES, A, B, TYPE_MIN) \
if (M3_UNLIKELY(B == 0)) newTrap (m3Err_trapDivisionByZero); \
if (M3_UNLIKELY(B == -1 and A == TYPE_MIN)) { \
newTrap (m3Err_trapIntegerOverflow); \
} \
RES = A / B;
#define OP_REM_S(RES, A, B, TYPE_MIN) \
if (M3_UNLIKELY(B == 0)) newTrap (m3Err_trapDivisionByZero); \
if (M3_UNLIKELY(B == -1 and A == TYPE_MIN)) RES = 0; \
else RES = A % B;
#else
#define OP_DIV_S(RES, A, B, TYPE_MIN) OP_DIV_U(RES, A, B)
#define OP_REM_S(RES, A, B, TYPE_MIN) OP_REM_U(RES, A, B)
#endif
/*
* Trunc
*/
#define OP_TRUNC(RES, A, TYPE, RMIN, RMAX) \
if (M3_UNLIKELY(isnan(A))) { \
newTrap (m3Err_trapIntegerConversion); \
} \
if (M3_UNLIKELY(A <= RMIN or A >= RMAX)) { \
newTrap (m3Err_trapIntegerOverflow); \
} \
RES = (TYPE)A;
#define OP_I32_TRUNC_F32(RES, A) OP_TRUNC(RES, A, i32, -2147483904.0f, 2147483648.0f)
#define OP_U32_TRUNC_F32(RES, A) OP_TRUNC(RES, A, u32, -1.0f, 4294967296.0f)
#define OP_I32_TRUNC_F64(RES, A) OP_TRUNC(RES, A, i32, -2147483649.0 , 2147483648.0 )
#define OP_U32_TRUNC_F64(RES, A) OP_TRUNC(RES, A, u32, -1.0 , 4294967296.0 )
#define OP_I64_TRUNC_F32(RES, A) OP_TRUNC(RES, A, i64, -9223373136366403584.0f, 9223372036854775808.0f)
#define OP_U64_TRUNC_F32(RES, A) OP_TRUNC(RES, A, u64, -1.0f, 18446744073709551616.0f)
#define OP_I64_TRUNC_F64(RES, A) OP_TRUNC(RES, A, i64, -9223372036854777856.0 , 9223372036854775808.0 )
#define OP_U64_TRUNC_F64(RES, A) OP_TRUNC(RES, A, u64, -1.0 , 18446744073709551616.0 )
#define OP_TRUNC_SAT(RES, A, TYPE, RMIN, RMAX, IMIN, IMAX) \
if (M3_UNLIKELY(isnan(A))) { \
RES = 0; \
} else if (M3_UNLIKELY(A <= RMIN)) { \
RES = IMIN; \
} else if (M3_UNLIKELY(A >= RMAX)) { \
RES = IMAX; \
} else { \
RES = (TYPE)A; \
}
#define OP_I32_TRUNC_SAT_F32(RES, A) OP_TRUNC_SAT(RES, A, i32, -2147483904.0f, 2147483648.0f, INT32_MIN, INT32_MAX)
#define OP_U32_TRUNC_SAT_F32(RES, A) OP_TRUNC_SAT(RES, A, u32, -1.0f, 4294967296.0f, 0UL, UINT32_MAX)
#define OP_I32_TRUNC_SAT_F64(RES, A) OP_TRUNC_SAT(RES, A, i32, -2147483649.0 , 2147483648.0, INT32_MIN, INT32_MAX)
#define OP_U32_TRUNC_SAT_F64(RES, A) OP_TRUNC_SAT(RES, A, u32, -1.0 , 4294967296.0, 0UL, UINT32_MAX)
#define OP_I64_TRUNC_SAT_F32(RES, A) OP_TRUNC_SAT(RES, A, i64, -9223373136366403584.0f, 9223372036854775808.0f, INT64_MIN, INT64_MAX)
#define OP_U64_TRUNC_SAT_F32(RES, A) OP_TRUNC_SAT(RES, A, u64, -1.0f, 18446744073709551616.0f, 0ULL, UINT64_MAX)
#define OP_I64_TRUNC_SAT_F64(RES, A) OP_TRUNC_SAT(RES, A, i64, -9223372036854777856.0 , 9223372036854775808.0, INT64_MIN, INT64_MAX)
#define OP_U64_TRUNC_SAT_F64(RES, A) OP_TRUNC_SAT(RES, A, u64, -1.0 , 18446744073709551616.0, 0ULL, UINT64_MAX)
/*
* Min, Max
*/
#if d_m3HasFloat
#include <math.h>
// Propagate a NaN operand the way the arithmetic ops do.
// keep its sign and payload, but force the quiet bit, since the spec requires
// min/max to produce an arithmetic NaN.
static inline
f32 quiet_nan_f32(f32 arg) {
union { f32 f; u32 i; } u;
u.f = arg;
u.i |= 0x00400000;
return u.f;
}
static inline
f64 quiet_nan_f64(f64 arg) {
union { f64 f; u64 i; } u;
u.f = arg;
u.i |= 0x0008000000000000ULL;
return u.f;
}
static inline
f32 min_f32(f32 a, f32 b) {
if (M3_UNLIKELY(isnan(a) or isnan(b))) return quiet_nan_f32(isnan(a) ? a : b);
if (M3_UNLIKELY(a == 0 and a == b)) return signbit(a) ? a : b;
return a > b ? b : a;
}
static inline
f32 max_f32(f32 a, f32 b) {
if (M3_UNLIKELY(isnan(a) or isnan(b))) return quiet_nan_f32(isnan(a) ? a : b);
if (M3_UNLIKELY(a == 0 and a == b)) return signbit(a) ? b : a;
return a > b ? a : b;
}
static inline
f64 min_f64(f64 a, f64 b) {
if (M3_UNLIKELY(isnan(a) or isnan(b))) return quiet_nan_f64(isnan(a) ? a : b);
if (M3_UNLIKELY(a == 0 and a == b)) return signbit(a) ? a : b;
return a > b ? b : a;
}
static inline
f64 max_f64(f64 a, f64 b) {
if (M3_UNLIKELY(isnan(a) or isnan(b))) return quiet_nan_f64(isnan(a) ? a : b);
if (M3_UNLIKELY(a == 0 and a == b)) return signbit(a) ? b : a;
return a > b ? a : b;
}
#endif
#endif // m3_math_utils_h
+175
View File
@@ -0,0 +1,175 @@
//
// m3_module.c
//
// Created by Steven Massey on 5/7/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#include "m3_env.h"
#include "m3_exception.h"
void Module_FreeFunctions (IM3Module i_module)
{
for (u32 i = 0; i < i_module->numFunctions; ++i)
{
IM3Function func = & i_module->functions [i];
Function_Release (func);
}
}
void m3_FreeModule (IM3Module i_module)
{
if (i_module)
{
m3log (module, "freeing module: %s (funcs: %d; segments: %d)",
i_module->name, i_module->numFunctions, i_module->numDataSegments);
Module_FreeFunctions (i_module);
m3_Free (i_module->functions);
//m3_Free (i_module->imports);
m3_Free (i_module->funcTypes);
m3_Free (i_module->dataSegments);
m3_Free (i_module->table0);
for (u32 i = 0; i < i_module->numGlobals; ++i)
{
m3_Free (i_module->globals[i].name);
FreeImportInfo(&(i_module->globals[i].import));
}
m3_Free (i_module->globals);
m3_Free (i_module->memoryExportName);
m3_Free (i_module->table0ExportName);
FreeImportInfo(&i_module->memoryImport);
m3_Free (i_module);
}
}
M3Result Module_AddGlobal (IM3Module io_module, IM3Global * o_global, u8 i_type, bool i_mutable, bool i_isImported)
{
_try {
u32 index = io_module->numGlobals++;
io_module->globals = m3_ReallocArray (M3Global, io_module->globals, io_module->numGlobals, index);
_throwifnull (io_module->globals);
M3Global * global = & io_module->globals [index];
global->type = i_type;
global->imported = i_isImported;
global->isMutable = i_mutable;
if (o_global)
* o_global = global;
} _catch:
return result;
}
M3Result Module_PreallocFunctions (IM3Module io_module, u32 i_totalFunctions)
{
_try {
if (i_totalFunctions > io_module->allFunctions) {
io_module->functions = m3_ReallocArray (M3Function, io_module->functions, i_totalFunctions, io_module->allFunctions);
io_module->allFunctions = i_totalFunctions;
_throwifnull (io_module->functions);
}
} _catch:
return result;
}
M3Result Module_AddFunction (IM3Module io_module, u32 i_typeIndex, IM3ImportInfo i_importInfo)
{
_try {
u32 index = io_module->numFunctions++;
_ (Module_PreallocFunctions(io_module, io_module->numFunctions));
_throwif ("type sig index out of bounds", i_typeIndex >= io_module->numFuncTypes);
IM3FuncType ft = io_module->funcTypes [i_typeIndex];
IM3Function func = Module_GetFunction (io_module, index);
func->funcType = ft;
# ifdef DEBUG
func->index = index;
# endif
if (i_importInfo and func->numNames == 0)
{
func->import = * i_importInfo;
func->names[0] = i_importInfo->fieldUtf8;
func->numNames = 1;
}
m3log (module, " added function: %3d; sig: %d", index, i_typeIndex);
} _catch:
return result;
}
#ifdef DEBUG
void Module_GenerateNames (IM3Module i_module)
{
for (u32 i = 0; i < i_module->numFunctions; ++i)
{
IM3Function func = & i_module->functions [i];
if (func->numNames == 0)
{
char* buff = m3_AllocArray(char, 16);
snprintf(buff, 16, "$func%d", i);
func->names[0] = buff;
func->numNames = 1;
}
}
for (u32 i = 0; i < i_module->numGlobals; ++i)
{
IM3Global global = & i_module->globals [i];
if (global->name == NULL)
{
char* buff = m3_AllocArray(char, 16);
snprintf(buff, 16, "$global%d", i);
global->name = buff;
}
}
}
#endif
IM3Function Module_GetFunction (IM3Module i_module, u32 i_functionIndex)
{
IM3Function func = NULL;
if (i_functionIndex < i_module->numFunctions)
{
func = & i_module->functions [i_functionIndex];
//func->module = i_module;
}
return func;
}
const char* m3_GetModuleName (IM3Module i_module)
{
if (!i_module || !i_module->name)
return ".unnamed";
return i_module->name;
}
void m3_SetModuleName (IM3Module i_module, const char* name)
{
if (i_module) i_module->name = name;
}
IM3Runtime m3_GetModuleRuntime (IM3Module i_module)
{
return i_module ? i_module->runtime : NULL;
}
+846
View File
@@ -0,0 +1,846 @@
//
// m3_parse.c
//
// Created by Steven Massey on 4/19/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#include "m3_env.h"
#include "m3_compile.h"
#include "m3_exception.h"
#include "m3_info.h"
M3Result ParseType_Table (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 numTables;
_ (ReadLEB_u32 (& numTables, & i_bytes, i_end)); m3log (parse, "** Table [%d]", numTables);
// MVP: at most one table, counting any that was already imported
_throwif (m3Err_wasmMalformed, numTables > 1);
_throwif (m3Err_wasmMalformed, numTables and io_module->hasTable);
for (u32 i = 0; i < numTables; ++i)
{
u8 elemType;
_ (Read_u8 (& elemType, & i_bytes, i_end));
// Spec: element type must be funcref (0x70)
_throwif (m3Err_wasmMalformed, elemType != 0x70);
u8 flag;
_ (ReadLEB_u7 (& flag, & i_bytes, i_end));
u32 initSize;
_ (ReadLEB_u32 (& initSize, & i_bytes, i_end));
if (flag & 1) {
u32 maxSize;
_ (ReadLEB_u32 (& maxSize, & i_bytes, i_end));
_throwif (m3Err_wasmMalformed, maxSize < initSize);
}
io_module->hasTable = true;
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch: return result;
}
M3Result ParseType_Memory (M3MemoryInfo * o_memory, bytes_t * io_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u8 flag;
_ (ReadLEB_u7 (& flag, io_bytes, i_end)); // really a u1
_ (ReadLEB_u32 (& o_memory->initPages, io_bytes, i_end));
o_memory->maxPages = 0;
if (flag & (1u << 0))
{
_ (ReadLEB_u32 (& o_memory->maxPages, io_bytes, i_end));
// Spec: memory limits validation - max must not be less than init
_throwif (m3Err_wasmMalformed, o_memory->maxPages < o_memory->initPages);
}
o_memory->pageSize = 0;
if (flag & (1u << 3)) {
u32 logPageSize;
_ (ReadLEB_u32 (& logPageSize, io_bytes, i_end));
o_memory->pageSize = 1u << logPageSize;
}
// Spec: memory limits must be valid within range 2^16 (65536 pages)
// Only enforce for standard page size (no custom page size flag)
if (!(flag & (1u << 3)))
{
_throwif (m3Err_wasmMalformed, o_memory->initPages > 65536);
if (flag & (1u << 0))
_throwif (m3Err_wasmMalformed, o_memory->maxPages > 65536);
}
_catch: return result;
}
M3Result ParseSection_Type (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
IM3FuncType ftype = NULL;
_try {
u32 numTypes;
_ (ReadLEB_u32 (& numTypes, & i_bytes, i_end)); m3log (parse, "** Type [%d]", numTypes);
_throwif("too many types", numTypes > d_m3MaxSaneTypesCount);
if (numTypes)
{
// table of IM3FuncType (that point to the actual M3FuncType struct in the Environment)
io_module->funcTypes = m3_AllocArray (IM3FuncType, numTypes);
_throwifnull (io_module->funcTypes);
io_module->numFuncTypes = numTypes;
for (u32 i = 0; i < numTypes; ++i)
{
i8 form;
_ (ReadLEB_i7 (& form, & i_bytes, i_end));
_throwif (m3Err_wasmMalformed, form != -32); // for Wasm MVP
u32 numArgs;
_ (ReadLEB_u32 (& numArgs, & i_bytes, i_end));
_throwif (m3Err_tooManyArgsRets, numArgs > d_m3MaxSaneFunctionArgRetCount);
#if defined(M3_COMPILER_MSVC)
u8 argTypes [d_m3MaxSaneFunctionArgRetCount];
#else
u8 argTypes[numArgs+1]; // make ubsan happy
#endif
for (u32 a = 0; a < numArgs; ++a)
{
i8 wasmType;
u8 argType;
_ (ReadLEB_i7 (& wasmType, & i_bytes, i_end));
_ (NormalizeType (& argType, wasmType));
argTypes[a] = argType;
}
u32 numRets;
_ (ReadLEB_u32 (& numRets, & i_bytes, i_end));
_throwif (m3Err_tooManyArgsRets, (u64)(numRets) + numArgs > d_m3MaxSaneFunctionArgRetCount);
_ (AllocFuncType (& ftype, numRets + numArgs));
ftype->numArgs = numArgs;
ftype->numRets = numRets;
for (u32 r = 0; r < numRets; ++r)
{
i8 wasmType;
u8 retType;
_ (ReadLEB_i7 (& wasmType, & i_bytes, i_end));
_ (NormalizeType (& retType, wasmType));
ftype->types[r] = retType;
}
memcpy (ftype->types + numRets, argTypes, numArgs); m3log (parse, " type %2d: %s", i, SPrintFuncTypeSignature (ftype));
Environment_AddFuncType (io_module->environment, & ftype);
io_module->funcTypes [i] = ftype;
ftype = NULL; // ownership transferred to environment
}
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
} _catch:
if (result)
{
m3_Free (ftype);
// FIX: M3FuncTypes in the table are leaked
m3_Free (io_module->funcTypes);
io_module->numFuncTypes = 0;
}
return result;
}
M3Result ParseSection_Function (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 numFunctions;
_ (ReadLEB_u32 (& numFunctions, & i_bytes, i_end)); m3log (parse, "** Function [%d]", numFunctions);
_throwif("too many functions", numFunctions > d_m3MaxSaneFunctionsCount);
_ (Module_PreallocFunctions(io_module, io_module->numFunctions + numFunctions));
for (u32 i = 0; i < numFunctions; ++i)
{
u32 funcTypeIndex;
_ (ReadLEB_u32 (& funcTypeIndex, & i_bytes, i_end));
_ (Module_AddFunction (io_module, funcTypeIndex, NULL /* import info */));
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch: return result;
}
M3Result ParseSection_Import (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
M3ImportInfo import = { NULL, NULL }, clearImport = { NULL, NULL };
u32 numImports;
_ (ReadLEB_u32 (& numImports, & i_bytes, i_end)); m3log (parse, "** Import [%d]", numImports);
_throwif("too many imports", numImports > d_m3MaxSaneImportsCount);
// Most imports are functions, so we won't waste much space anyway (if any)
_ (Module_PreallocFunctions(io_module, numImports));
for (u32 i = 0; i < numImports; ++i)
{
u8 importKind;
_ (Read_utf8 (& import.moduleUtf8, & i_bytes, i_end));
_ (Read_utf8 (& import.fieldUtf8, & i_bytes, i_end));
_ (Read_u8 (& importKind, & i_bytes, i_end)); m3log (parse, " kind: %d '%s.%s' ",
(u32) importKind, import.moduleUtf8, import.fieldUtf8);
switch (importKind)
{
case d_externalKind_function:
{
u32 typeIndex;
_ (ReadLEB_u32 (& typeIndex, & i_bytes, i_end))
_ (Module_AddFunction (io_module, typeIndex, & import))
import = clearImport;
io_module->numFuncImports++;
}
break;
case d_externalKind_table:
{
// Parse and validate table type (elem type + limits)
u8 elemType;
_ (Read_u8 (& elemType, & i_bytes, i_end));
_throwif (m3Err_wasmMalformed, elemType != 0x70); // must be funcref
u8 flag;
_ (ReadLEB_u7 (& flag, & i_bytes, i_end));
u32 initSize;
_ (ReadLEB_u32 (& initSize, & i_bytes, i_end));
if (flag & 1) {
u32 maxSize;
_ (ReadLEB_u32 (& maxSize, & i_bytes, i_end));
}
io_module->hasTable = true;
}
break;
case d_externalKind_memory:
{
_ (ParseType_Memory (& io_module->memoryInfo, & i_bytes, i_end));
io_module->memoryImported = true;
io_module->memoryImport = import;
import = clearImport;
}
break;
case d_externalKind_global:
{
i8 waType;
u8 type, isMutable;
_ (ReadLEB_i7 (& waType, & i_bytes, i_end));
_ (NormalizeType (& type, waType));
_ (ReadLEB_u7 (& isMutable, & i_bytes, i_end)); m3log (parse, " global: %s mutable=%d", c_waTypes [type], (u32) isMutable);
_throwif (m3Err_wasmMalformed, isMutable > 1);
IM3Global global;
_ (Module_AddGlobal (io_module, & global, type, isMutable, true /* isImport */));
global->import = import;
import = clearImport;
}
break;
default:
_throw (m3Err_wasmMalformed);
}
FreeImportInfo (& import);
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch:
FreeImportInfo (& import);
return result;
}
M3Result ParseSection_Export (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
const char * utf8 = NULL;
#if d_m3EnableValidation
// We store name pointers + lengths to handle embedded NUL bytes correctly
typedef struct { const u8 * ptr; u16 len; } ExportName;
ExportName * exportNames = NULL;
#endif
u32 numExports;
_ (ReadLEB_u32 (& numExports, & i_bytes, i_end)); m3log (parse, "** Export [%d]", numExports);
_throwif("too many exports", numExports > d_m3MaxSaneExportsCount);
#if d_m3EnableValidation
// Spec: all export names must be different
if (numExports > 1)
{
exportNames = (ExportName *) m3_Malloc ("exportNames", sizeof(ExportName) * numExports);
}
#endif
for (u32 i = 0; i < numExports; ++i)
{
u8 exportKind;
u32 index;
// Read name length and remember raw position for uniqueness check
#if d_m3EnableValidation
const u8 * nameStart = i_bytes;
u32 nameLen = 0;
{
bytes_t tmp = i_bytes;
M3Result rl = ReadLEB_u32 (& nameLen, & tmp, i_end);
if (rl) { m3_Free(exportNames); _throw(rl); }
nameStart = tmp; // points to the raw name bytes
}
#endif
_ (Read_utf8 (& utf8, & i_bytes, i_end));
_ (Read_u8 (& exportKind, & i_bytes, i_end));
_ (ReadLEB_u32 (& index, & i_bytes, i_end)); m3log (parse, " index: %3d; kind: %d; export: '%s'; ", index, (u32) exportKind, utf8);
#if d_m3EnableValidation
if (exportNames)
{
for (u32 j = 0; j < i; ++j)
{
if (exportNames[j].len == nameLen &&
memcmp (exportNames[j].ptr, nameStart, nameLen) == 0)
{
m3_Free (exportNames);
_throw (m3Err_wasmMalformed); // duplicate export name
}
}
exportNames[i].ptr = nameStart;
exportNames[i].len = (u16)nameLen;
}
#endif
if (exportKind == d_externalKind_function)
{
_throwif(m3Err_wasmMalformed, index >= io_module->numFunctions);
IM3Function func = &(io_module->functions [index]);
if (func->numNames < d_m3MaxDuplicateFunctionImpl)
{
func->names[func->numNames++] = utf8;
func->export_name = utf8;
utf8 = NULL; // ownership transferred to M3Function
}
}
else if (exportKind == d_externalKind_global)
{
_throwif(m3Err_wasmMalformed, index >= io_module->numGlobals);
IM3Global global = &(io_module->globals [index]);
m3_Free (global->name);
global->name = utf8;
utf8 = NULL; // ownership transferred to M3Global
}
else if (exportKind == d_externalKind_memory)
{
_throwif(m3Err_wasmMalformed, index != 0);
_throwif(m3Err_wasmMalformed, not (io_module->memoryImported or io_module->memoryDeclared));
m3_Free (io_module->memoryExportName);
io_module->memoryExportName = utf8;
utf8 = NULL; // ownership transferred to M3Module
}
else if (exportKind == d_externalKind_table)
{
_throwif(m3Err_wasmMalformed, index != 0);
_throwif(m3Err_wasmMalformed, not io_module->hasTable);
m3_Free (io_module->table0ExportName);
io_module->table0ExportName = utf8;
utf8 = NULL; // ownership transferred to M3Module
}
m3_Free (utf8);
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch:
m3_Free (utf8);
#if d_m3EnableValidation
m3_Free (exportNames);
#endif
return result;
}
M3Result ParseSection_Start (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 startFuncIndex;
_ (ReadLEB_u32 (& startFuncIndex, & i_bytes, i_end)); m3log (parse, "** Start Function: %d", startFuncIndex);
if (startFuncIndex < io_module->numFunctions)
{
// Spec: start function type must be [] -> []
IM3Function func = & io_module->functions [startFuncIndex];
if (func->funcType)
{
_throwif (m3Err_wasmMalformed,
func->funcType->numArgs != 0 || func->funcType->numRets != 0);
}
io_module->startFunction = startFuncIndex;
}
else result = "start function index out of bounds";
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch: return result;
}
M3Result Parse_InitExpr (M3Module * io_module, bytes_t * io_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
// this doesn't generate code pages. just walks the wasm bytecode to find the end
#if defined(d_m3PreferStaticAlloc)
static M3Compilation compilation;
#else
M3Compilation compilation;
#endif
compilation = (M3Compilation){ .runtime = NULL, .module = io_module, .wasm = * io_bytes, .wasmEnd = i_end, .isInitExpr = true };
result = CompileBlockStatements (& compilation);
* io_bytes = compilation.wasm;
return result;
}
M3Result ParseSection_Element (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 numSegments;
bytes_t pos;
_ (ReadLEB_u32 (& numSegments, & i_bytes, i_end)); m3log (parse, "** Element [%d]", numSegments);
_throwif ("too many element segments", numSegments > d_m3MaxSaneElementSegments);
// Element segments need a table to populate
_throwif (m3Err_wasmMalformed, numSegments and not io_module->hasTable);
io_module->elementSection = i_bytes;
io_module->elementSectionEnd = i_end;
io_module->numElementSegments = numSegments;
// Walk the section to validate structure and detect section size mismatch.
// The actual element initialization happens later in InitElements.
pos = i_bytes;
for (u32 i = 0; i < numSegments; ++i)
{
u32 tableIndex;
_ (ReadLEB_u32 (& tableIndex, & pos, i_end));
// Walk the init expression (offset) to find its end
_ (Parse_InitExpr (io_module, & pos, i_end));
u32 numElements;
_ (ReadLEB_u32 (& numElements, & pos, i_end));
for (u32 e = 0; e < numElements; ++e)
{
u32 funcIndex;
_ (ReadLEB_u32 (& funcIndex, & pos, i_end));
}
}
_throwif (m3Err_wasmMalformed, pos != i_end); // section size mismatch
_catch: return result;
}
M3Result ParseSection_Code (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result;
u32 numFunctions;
_ (ReadLEB_u32 (& numFunctions, & i_bytes, i_end)); m3log (parse, "** Code [%d]", numFunctions);
if (numFunctions != io_module->numFunctions - io_module->numFuncImports)
{
_throw ("mismatched function count in code section");
}
for (u32 f = 0; f < numFunctions; ++f)
{
const u8 * start = i_bytes;
u32 size;
_ (ReadLEB_u32 (& size, & i_bytes, i_end));
if (size)
{
const u8 * ptr = i_bytes;
i_bytes += size;
if (i_bytes <= i_end)
{
/*
u32 numLocalBlocks;
_ (ReadLEB_u32 (& numLocalBlocks, & ptr, i_end)); m3log (parse, " code size: %-4d", size);
u32 numLocals = 0;
for (u32 l = 0; l < numLocalBlocks; ++l)
{
u32 varCount;
i8 wasmType;
u8 normalType;
_ (ReadLEB_u32 (& varCount, & ptr, i_end));
_ (ReadLEB_i7 (& wasmType, & ptr, i_end));
_ (NormalizeType (& normalType, wasmType));
numLocals += varCount; m3log (parse, " %2d locals; type: '%s'", varCount, c_waTypes [normalType]);
}
*/
IM3Function func = Module_GetFunction (io_module, f + io_module->numFuncImports);
func->module = io_module;
func->wasm = start;
func->wasmEnd = i_bytes;
//func->ownsWasmCode = io_module->hasWasmCodeCopy;
// func->numLocals = numLocals;
}
else _throw (m3Err_wasmSectionOverrun);
}
}
_catch:
if (not result and i_bytes != i_end)
result = m3Err_wasmSectionUnderrun;
return result;
}
M3Result ParseSection_Data (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 numDataSegments;
_ (ReadLEB_u32 (& numDataSegments, & i_bytes, i_end)); m3log (parse, "** Data [%d]", numDataSegments);
_throwif("too many data segments", numDataSegments > d_m3MaxSaneDataSegments);
io_module->dataSegments = m3_AllocArray (M3DataSegment, numDataSegments);
_throwifnull(io_module->dataSegments);
io_module->numDataSegments = numDataSegments;
for (u32 i = 0; i < numDataSegments; ++i)
{
M3DataSegment * segment = & io_module->dataSegments [i];
_ (ReadLEB_u32 (& segment->memoryRegion, & i_bytes, i_end));
// Spec: MVP only supports memory index 0, and it has to exist
_throwif (m3Err_wasmMalformed, segment->memoryRegion != 0);
_throwif (m3Err_wasmMalformed, not (io_module->memoryImported or io_module->memoryDeclared));
segment->initExpr = i_bytes;
_ (Parse_InitExpr (io_module, & i_bytes, i_end));
segment->initExprSize = (u32) (i_bytes - segment->initExpr);
_throwif (m3Err_wasmMissingInitExpr, segment->initExprSize <= 1);
_ (ReadLEB_u32 (& segment->size, & i_bytes, i_end));
segment->data = i_bytes; m3log (parse, " segment [%u] memory: %u; expr-size: %d; size: %d",
i, segment->memoryRegion, segment->initExprSize, segment->size);
i_bytes += segment->size;
_throwif("data segment underflow", i_bytes > i_end);
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch:
return result;
}
M3Result ParseSection_Memory (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
// TODO: MVP; assert no memory imported
u32 numMemories;
_ (ReadLEB_u32 (& numMemories, & i_bytes, i_end)); m3log (parse, "** Memory [%d]", numMemories);
_throwif (m3Err_tooManyMemorySections, numMemories > 1);
if (numMemories)
{
_ (ParseType_Memory (& io_module->memoryInfo, & i_bytes, i_end));
io_module->memoryDeclared = true;
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch: return result;
}
M3Result ParseSection_Global (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 numGlobals;
_ (ReadLEB_u32 (& numGlobals, & i_bytes, i_end)); m3log (parse, "** Global [%d]", numGlobals);
_throwif("too many globals", numGlobals > d_m3MaxSaneGlobalsCount);
for (u32 i = 0; i < numGlobals; ++i)
{
i8 waType;
u8 type, isMutable;
_ (ReadLEB_i7 (& waType, & i_bytes, i_end));
_ (NormalizeType (& type, waType));
_ (ReadLEB_u7 (& isMutable, & i_bytes, i_end)); m3log (parse, " global: [%d] %s mutable: %d", i, c_waTypes [type], (u32) isMutable);
_throwif (m3Err_wasmMalformed, isMutable > 1);
IM3Global global;
_ (Module_AddGlobal (io_module, & global, type, isMutable, false /* isImport */));
global->initExpr = i_bytes;
_ (Parse_InitExpr (io_module, & i_bytes, i_end));
global->initExprSize = (u32) (i_bytes - global->initExpr);
_throwif (m3Err_wasmMissingInitExpr, global->initExprSize <= 1);
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch: return result;
}
M3Result ParseSection_Name (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
cstr_t name;
while (i_bytes < i_end)
{
u8 nameType;
u32 payloadLength;
_ (ReadLEB_u7 (& nameType, & i_bytes, i_end));
_ (ReadLEB_u32 (& payloadLength, & i_bytes, i_end));
bytes_t start = i_bytes;
if (nameType == 1)
{
u32 numNames;
_ (ReadLEB_u32 (& numNames, & i_bytes, i_end));
_throwif("too many names", numNames > d_m3MaxSaneFunctionsCount);
for (u32 i = 0; i < numNames; ++i)
{
u32 index;
_ (ReadLEB_u32 (& index, & i_bytes, i_end));
_ (Read_utf8 (& name, & i_bytes, i_end));
if (index < io_module->numFunctions)
{
IM3Function func = &(io_module->functions [index]);
if (func->numNames == 0)
{
func->names[0] = name; m3log (parse, " naming function%5d: %s", index, name);
func->numNames = 1;
name = NULL; // transfer ownership
}
// else m3log (parse, "prenamed: %s", io_module->functions [index].name);
}
m3_Free (name);
}
}
i_bytes = start + payloadLength;
}
_catch: return result;
}
M3Result ParseSection_Custom (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result;
cstr_t name;
_ (Read_utf8 (& name, & i_bytes, i_end));
m3log (parse, "** Custom: '%s'", name);
if (strcmp (name, "name") == 0) {
_ (ParseSection_Name(io_module, i_bytes, i_end));
} else if (io_module->environment->customSectionHandler) {
_ (io_module->environment->customSectionHandler(io_module, name, i_bytes, i_end));
}
m3_Free (name);
_catch: return result;
}
M3Result ParseModuleSection (M3Module * o_module, u8 i_sectionType, bytes_t i_bytes, u32 i_numBytes)
{
M3Result result = m3Err_none;
typedef M3Result (* M3Parser) (M3Module *, bytes_t, cbytes_t);
static M3Parser s_parsers [] =
{
ParseSection_Custom, // 0
ParseSection_Type, // 1
ParseSection_Import, // 2
ParseSection_Function, // 3
ParseType_Table, // 4
ParseSection_Memory, // 5
ParseSection_Global, // 6
ParseSection_Export, // 7
ParseSection_Start, // 8
ParseSection_Element, // 9
ParseSection_Code, // 10
ParseSection_Data, // 11
NULL, // 12: TODO DataCount
};
M3Parser parser = NULL;
if (i_sectionType <= 12)
parser = s_parsers [i_sectionType];
if (parser)
{
cbytes_t end = i_bytes + i_numBytes;
result = parser (o_module, i_bytes, end);
}
else
{
m3log (parse, " skipped section type: %d", (u32) i_sectionType);
}
return result;
}
M3Result m3_ParseModule (IM3Environment i_environment, IM3Module * o_module, cbytes_t i_bytes, u32 i_numBytes)
{
IM3Module module; m3log (parse, "load module: %d bytes", i_numBytes);
_try {
module = m3_AllocStruct (M3Module);
_throwifnull (module);
module->name = ".unnamed"; m3log (parse, "load module: %d bytes", i_numBytes);
module->startFunction = -1;
//module->hasWasmCodeCopy = false;
module->environment = i_environment;
const u8 * pos = i_bytes;
const u8 * end = pos + i_numBytes;
module->wasmStart = pos;
module->wasmEnd = end;
u32 magic, version;
_ (Read_u32 (& magic, & pos, end));
_ (Read_u32 (& version, & pos, end));
_throwif (m3Err_wasmMalformed, magic != 0x6d736100);
_throwif (m3Err_incompatibleWasmVersion, version != 1);
static const u8 sectionsOrder[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 10, 11, 0 }; // 0 is a placeholder
u8 expectedSection = 0;
while (pos < end)
{
u8 section;
_ (ReadLEB_u7 (& section, & pos, end));
if (section != 0) {
// Ensure sections appear only once and in order
while (sectionsOrder[expectedSection++] != section) {
_throwif(m3Err_misorderedWasmSection, expectedSection >= 12);
}
}
u32 sectionLength;
_ (ReadLEB_u32 (& sectionLength, & pos, end));
_throwif(m3Err_wasmMalformed, pos + sectionLength > end);
_ (ParseModuleSection (module, section, pos, sectionLength));
pos += sectionLength;
}
// Spec: if a function section exists, a code section must also exist with
// matching count (and vice versa). ParseSection_Code checks the other
// direction; this covers the case where the code section is missing entirely.
if (module->numFunctions > module->numFuncImports)
{
IM3Function firstNonImport = & module->functions [module->numFuncImports];
_throwif (m3Err_wasmMalformed, firstNonImport->wasm == NULL);
}
} _catch:
if (result)
{
m3_FreeModule (module);
module = NULL;
}
* o_module = module;
return result;
}
+881
View File
@@ -0,0 +1,881 @@
//
// m3_validate.c
//
// Pre-pass WebAssembly bytecode validator.
// Implements the spec's type-checking algorithm with operand/control stacks.
//
#include "m3_validate.h"
#include "m3_exception.h"
#include "m3_info.h"
#if d_m3EnableValidation
// Sentinel type for polymorphic (unknown) operands
#define c_valUnknown 0xFF
// ---------- Control frame ----------
typedef struct {
m3opcode_t opcode;
u16 height; // operand stack height at block entry
u16 param_count;
u16 result_count;
IM3FuncType type; // block type (for params/results)
bool is_unreachable;
} ValCtrlFrame;
// ---------- Validator context ----------
typedef struct {
bytes_t wasm;
bytes_t wasmEnd;
IM3Module module;
IM3Function function;
u8 opd [d_m3ValStack];
u16 opdTop;
ValCtrlFrame ctrl [d_m3ValCtrlDepth];
u16 ctrlTop;
u8 localTypes [d_m3ValStack];
u16 numLocals;
} ValCtx;
// A memory op is only valid if the module defines or imports one
static bool v_has_memory (ValCtx * v)
{
return v->module and (v->module->memoryImported or v->module->memoryDeclared);
}
// Spec: the alignment immediate of a memory access must not be larger than the
// natural alignment of the operation. Natural alignment: 8-bit=0, 16-bit=1,
// 32-bit=2, 64-bit=3.
static u32 v_max_align (m3opcode_t opcode)
{
switch (opcode) {
case 0x2c: case 0x2d: // i32.load8_s, i32.load8_u
case 0x30: case 0x31: // i64.load8_s, i64.load8_u
case 0x3a: // i32.store8
case 0x3c: // i64.store8
return 0;
case 0x2e: case 0x2f: // i32.load16_s, i32.load16_u
case 0x32: case 0x33: // i64.load16_s, i64.load16_u
case 0x3b: // i32.store16
case 0x3d: // i64.store16
return 1;
case 0x29: // i64.load
case 0x2b: // f64.load
case 0x37: // i64.store
case 0x39: // f64.store
return 3;
default: // 32-bit accesses, and a safe fallback
return 2;
}
}
// ---------- Operand stack ----------
static M3Result v_push (ValCtx * v, u8 type)
{
if (v->opdTop >= d_m3ValStack)
return m3Err_functionStackOverflow;
v->opd[v->opdTop++] = type;
return m3Err_none;
}
static M3Result v_pop (ValCtx * v, u8 * o_type)
{
ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
if (v->opdTop == f->height) {
if (f->is_unreachable) { *o_type = c_valUnknown; return m3Err_none; }
return m3Err_functionStackUnderrun;
}
*o_type = v->opd[--v->opdTop];
return m3Err_none;
}
static M3Result v_pop_expect (ValCtx * v, u8 expect, u8 * o_actual)
{
u8 actual;
M3Result r = v_pop(v, &actual);
if (r) return r;
if (expect != c_valUnknown && actual != c_valUnknown && actual != expect)
return m3Err_typeMismatch;
*o_actual = (actual == c_valUnknown) ? expect : actual;
return m3Err_none;
}
// ---------- Control stack ----------
static M3Result v_push_ctrl (ValCtx * v, m3opcode_t op, IM3FuncType type)
{
if (v->ctrlTop >= d_m3ValCtrlDepth)
return m3Err_functionStackOverflow;
ValCtrlFrame * f = &v->ctrl[v->ctrlTop++];
f->opcode = op;
f->type = type;
f->param_count = type ? type->numArgs : 0;
f->result_count = type ? type->numRets : 0;
f->height = v->opdTop;
f->is_unreachable = false;
return m3Err_none;
}
static M3Result v_pop_ctrl (ValCtx * v, ValCtrlFrame * o_frame)
{
if (v->ctrlTop == 0)
return m3Err_wasmMalformed;
ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
// pop result types
if (f->type) {
for (u16 i = f->result_count; i > 0; i--) {
u8 a;
M3Result r = v_pop_expect(v, f->type->types[i - 1], &a);
if (r) return r;
}
}
if (v->opdTop != f->height)
return m3Err_typeCountMismatch;
if (o_frame) *o_frame = *f;
v->ctrlTop--;
return m3Err_none;
}
static void v_unreachable (ValCtx * v)
{
ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
v->opdTop = f->height;
f->is_unreachable = true;
}
// Label types: loop -> params, block/if/else/func -> results
static u16 v_label_n (ValCtrlFrame * f)
{
return (f->opcode == 0x03) ? f->param_count : f->result_count;
}
static u8 v_label_t (ValCtrlFrame * f, u16 i)
{
if (!f->type) return c_m3Type_none;
if (f->opcode == 0x03)
return f->type->types[f->type->numRets + i]; // params
return f->type->types[i]; // results
}
// Pop label types for branch target
static M3Result v_pop_labels (ValCtx * v, ValCtrlFrame * tgt)
{
u16 n = v_label_n(tgt);
for (u16 i = n; i > 0; i--) {
u8 a;
M3Result r = v_pop_expect(v, v_label_t(tgt, i - 1), &a);
if (r) return r;
}
return m3Err_none;
}
// Push label types back
static M3Result v_push_labels (ValCtx * v, ValCtrlFrame * tgt)
{
u16 n = v_label_n(tgt);
for (u16 i = 0; i < n; i++) {
M3Result r = v_push(v, v_label_t(tgt, i));
if (r) return r;
}
return m3Err_none;
}
// ---------- Block type resolution ----------
static M3Result v_read_blocktype (ValCtx * v, IM3FuncType * o_type)
{
if (v->wasm >= v->wasmEnd)
return m3Err_wasmUnderrun;
i64 type;
M3Result r = ReadLebSigned(&type, 33, &v->wasm, v->wasmEnd);
if (r) return r;
if (type < 0) {
u8 valtype;
r = NormalizeType(&valtype, (i8)type);
if (r) return r;
IM3Environment env = v->module->environment;
*o_type = env->retFuncTypes[valtype];
} else {
if ((u32)type >= v->module->numFuncTypes) return m3Err_wasmMalformed;
*o_type = v->module->funcTypes[(u32)type];
}
return m3Err_none;
}
// ---------- Convenience ----------
static M3Result v_unop (ValCtx * v, u8 in, u8 out)
{
u8 a; M3Result r = v_pop_expect(v, in, &a);
if (r) return r;
return v_push(v, out);
}
static M3Result v_binop (ValCtx * v, u8 t)
{
u8 a; M3Result r;
r = v_pop_expect(v, t, &a); if (r) return r;
r = v_pop_expect(v, t, &a); if (r) return r;
return v_push(v, t);
}
static M3Result v_relop (ValCtx * v, u8 t)
{
u8 a; M3Result r;
r = v_pop_expect(v, t, &a); if (r) return r;
r = v_pop_expect(v, t, &a); if (r) return r;
return v_push(v, c_m3Type_i32);
}
static M3Result v_testop (ValCtx * v, u8 t)
{
return v_unop(v, t, c_m3Type_i32);
}
static M3Result v_cvtop (ValCtx * v, u8 in, u8 out)
{
return v_unop(v, in, out);
}
// ---------- Main validation loop ----------
static M3Result v_validate_body (ValCtx * v)
{
M3Result r = m3Err_none;
u8 a;
while (v->wasm < v->wasmEnd)
{
m3opcode_t opcode;
r = Read_opcode(&opcode, &v->wasm, v->wasmEnd);
if (r) return r;
switch (opcode)
{
// ---- Control ----
case 0x00: // unreachable
v_unreachable(v);
break;
case 0x01: // nop
break;
case 0x02: // block
case 0x03: // loop
case 0x04: // if
{
IM3FuncType bt;
r = v_read_blocktype(v, &bt);
if (r) return r;
if (opcode == 0x04) {
r = v_pop_expect(v, c_m3Type_i32, &a);
if (r) return r;
}
// Pop block params from caller stack
if (bt) {
for (u16 i = bt->numArgs; i > 0; i--) {
r = v_pop_expect(v, bt->types[bt->numRets + i - 1], &a);
if (r) return r;
}
}
r = v_push_ctrl(v, opcode, bt);
if (r) return r;
// Push params inside block
if (bt) {
for (u16 i = 0; i < bt->numArgs; i++) {
r = v_push(v, bt->types[bt->numRets + i]);
if (r) return r;
}
}
break;
}
case 0x05: // else
{
ValCtrlFrame frame;
r = v_pop_ctrl(v, &frame);
if (r) return r;
if (frame.opcode != 0x04)
return m3Err_wasmMalformed;
r = v_push_ctrl(v, 0x05, frame.type);
if (r) return r;
if (frame.type) {
for (u16 i = 0; i < frame.type->numArgs; i++) {
r = v_push(v, frame.type->types[frame.type->numRets + i]);
if (r) return r;
}
}
break;
}
case 0x0b: // end
{
ValCtrlFrame frame;
r = v_pop_ctrl(v, &frame);
if (r) return r;
// Push results
if (frame.type) {
for (u16 i = 0; i < frame.result_count; i++) {
r = v_push(v, frame.type->types[i]);
if (r) return r;
}
}
// If this was the outermost frame, we're done
if (v->ctrlTop == 0)
return m3Err_none;
break;
}
case 0x0c: // br
{
u32 depth;
r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd);
if (r) return r;
if (depth >= v->ctrlTop) return m3Err_wasmMalformed;
ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth];
r = v_pop_labels(v, tgt);
if (r) return r;
v_unreachable(v);
break;
}
case 0x0d: // br_if
{
u32 depth;
r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd);
if (r) return r;
if (depth >= v->ctrlTop) return m3Err_wasmMalformed;
r = v_pop_expect(v, c_m3Type_i32, &a);
if (r) return r;
ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth];
r = v_pop_labels(v, tgt);
if (r) return r;
r = v_push_labels(v, tgt);
if (r) return r;
break;
}
case 0x0e: // br_table
{
u32 count;
r = ReadLEB_u32(&count, &v->wasm, v->wasmEnd);
if (r) return r;
u32 defDepth = 0;
u16 arity = 0;
// First pass: read all depths and validate arity + types match default
bytes_t savedPos = v->wasm;
// Read all targets to find the default (last one)
for (u32 i = 0; i <= count; i++) {
u32 d;
r = ReadLEB_u32(&d, &v->wasm, v->wasmEnd);
if (r) return r;
if (d >= v->ctrlTop) return m3Err_wasmMalformed;
if (i == count) defDepth = d;
}
// Now validate all labels match the default's types
ValCtrlFrame * defTgt = &v->ctrl[v->ctrlTop - 1 - defDepth];
arity = v_label_n(defTgt);
v->wasm = savedPos;
for (u32 i = 0; i <= count; i++) {
u32 d;
r = ReadLEB_u32(&d, &v->wasm, v->wasmEnd);
if (r) return r;
ValCtrlFrame * t = &v->ctrl[v->ctrlTop - 1 - d];
u16 n = v_label_n(t);
if (n != arity) return m3Err_typeCountMismatch;
// Spec: label types must be identical, not just same arity
for (u16 j = 0; j < n; j++) {
if (v_label_t(t, j) != v_label_t(defTgt, j))
return m3Err_typeMismatch;
}
}
r = v_pop_expect(v, c_m3Type_i32, &a);
if (r) return r;
ValCtrlFrame * dt = &v->ctrl[v->ctrlTop - 1 - defDepth];
r = v_pop_labels(v, dt);
if (r) return r;
v_unreachable(v);
break;
}
case 0x0f: // return
{
IM3FuncType ft = v->function->funcType;
if (ft) {
for (u16 i = ft->numRets; i > 0; i--) {
r = v_pop_expect(v, ft->types[i - 1], &a);
if (r) return r;
}
}
v_unreachable(v);
break;
}
// ---- Call ----
case 0x10: // call
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->module->numFunctions) return m3Err_wasmMalformed;
IM3FuncType ft = v->module->functions[idx].funcType;
if (ft) {
for (u16 i = ft->numArgs; i > 0; i--) {
r = v_pop_expect(v, ft->types[ft->numRets + i - 1], &a);
if (r) return r;
}
for (u16 i = 0; i < ft->numRets; i++) {
r = v_push(v, ft->types[i]);
if (r) return r;
}
}
break;
}
case 0x11: // call_indirect
{
u32 typeIdx;
r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd);
if (r) return r;
u32 tableIdx;
r = ReadLEB_u32(&tableIdx, &v->wasm, v->wasmEnd);
if (r) return r;
if (typeIdx >= v->module->numFuncTypes) return m3Err_wasmMalformed;
// Spec: table must exist (MVP requires table index 0 and table must be defined)
if (tableIdx != 0) return m3Err_wasmMalformed;
if (!v->module->hasTable) return m3Err_wasmMalformed;
IM3FuncType ft = v->module->funcTypes[typeIdx];
r = v_pop_expect(v, c_m3Type_i32, &a); // table index operand
if (r) return r;
if (ft) {
for (u16 i = ft->numArgs; i > 0; i--) {
r = v_pop_expect(v, ft->types[ft->numRets + i - 1], &a);
if (r) return r;
}
for (u16 i = 0; i < ft->numRets; i++) {
r = v_push(v, ft->types[i]);
if (r) return r;
}
}
break;
}
// ---- Parametric ----
case 0x1a: // drop
r = v_pop(v, &a);
if (r) return r;
break;
case 0x1b: // select
{
r = v_pop_expect(v, c_m3Type_i32, &a);
if (r) return r;
u8 t2;
r = v_pop(v, &t2);
if (r) return r;
u8 t1;
r = v_pop_expect(v, t2, &t1);
if (r) return r;
r = v_push(v, (t2 == c_valUnknown) ? t1 : t2);
if (r) return r;
break;
}
// ---- Variable ----
case 0x20: // local.get
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->numLocals) return m3Err_wasmMalformed;
r = v_push(v, v->localTypes[idx]);
if (r) return r;
break;
}
case 0x21: // local.set
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->numLocals) return m3Err_wasmMalformed;
r = v_pop_expect(v, v->localTypes[idx], &a);
if (r) return r;
break;
}
case 0x22: // local.tee
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->numLocals) return m3Err_wasmMalformed;
r = v_pop_expect(v, v->localTypes[idx], &a);
if (r) return r;
r = v_push(v, v->localTypes[idx]);
if (r) return r;
break;
}
case 0x23: // global.get
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->module->numGlobals) return m3Err_wasmMalformed;
r = v_push(v, v->module->globals[idx].type);
if (r) return r;
break;
}
case 0x24: // global.set
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->module->numGlobals) return m3Err_wasmMalformed;
r = v_pop_expect(v, v->module->globals[idx].type, &a);
if (r) return r;
break;
}
// ---- Memory load ----
case 0x28: case 0x29: case 0x2a: case 0x2b: // i32/i64/f32/f64.load
case 0x2c: case 0x2d: case 0x2e: case 0x2f: // i32.load8/16 s/u
case 0x30: case 0x31: case 0x32: case 0x33: // i64.load8/16 s/u
case 0x34: case 0x35: // i64.load32 s/u
{
u32 align, offset;
r = ReadLEB_u32(&align, &v->wasm, v->wasmEnd); if (r) return r;
r = ReadLEB_u32(&offset, &v->wasm, v->wasmEnd); if (r) return r;
if (align > v_max_align(opcode)) return m3Err_wasmMalformed;
if (not v_has_memory(v)) return m3Err_wasmMalformed;
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;
u8 result;
if (opcode == 0x28) result = c_m3Type_i32;
else if (opcode == 0x29) result = c_m3Type_i64;
else if (opcode == 0x2a) result = c_m3Type_f32;
else if (opcode == 0x2b) result = c_m3Type_f64;
else if (opcode <= 0x2f) result = c_m3Type_i32;
else result = c_m3Type_i64;
r = v_push(v, result);
if (r) return r;
break;
}
// ---- Memory store ----
case 0x36: case 0x37: case 0x38: case 0x39: // i32/i64/f32/f64.store
case 0x3a: case 0x3b: // i32.store8/16
case 0x3c: case 0x3d: case 0x3e: // i64.store8/16/32
{
u32 align, offset;
r = ReadLEB_u32(&align, &v->wasm, v->wasmEnd); if (r) return r;
r = ReadLEB_u32(&offset, &v->wasm, v->wasmEnd); if (r) return r;
if (align > v_max_align(opcode)) return m3Err_wasmMalformed;
if (not v_has_memory(v)) return m3Err_wasmMalformed;
u8 valtype;
if (opcode == 0x36) valtype = c_m3Type_i32;
else if (opcode == 0x37) valtype = c_m3Type_i64;
else if (opcode == 0x38) valtype = c_m3Type_f32;
else if (opcode == 0x39) valtype = c_m3Type_f64;
else if (opcode <= 0x3b) valtype = c_m3Type_i32;
else valtype = c_m3Type_i64;
r = v_pop_expect(v, valtype, &a); if (r) return r;
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;
break;
}
// ---- Memory size/grow ----
case 0x3f: // memory.size
{
u32 memidx;
r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
if (memidx != 0 or not v_has_memory(v)) return m3Err_wasmMalformed;
r = v_push(v, c_m3Type_i32); if (r) return r;
break;
}
case 0x40: // memory.grow
{
u32 memidx;
r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
if (memidx != 0 or not v_has_memory(v)) return m3Err_wasmMalformed;
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;
r = v_push(v, c_m3Type_i32); if (r) return r;
break;
}
// ---- Constants ----
case 0x41: { // i32.const
i32 val;
r = ReadLEB_i32(&val, &v->wasm, v->wasmEnd); if (r) return r;
r = v_push(v, c_m3Type_i32); if (r) return r;
break;
}
case 0x42: { // i64.const
i64 val;
r = ReadLEB_i64(&val, &v->wasm, v->wasmEnd); if (r) return r;
r = v_push(v, c_m3Type_i64); if (r) return r;
break;
}
case 0x43: { // f32.const
if (v->wasm + 4 > v->wasmEnd) return m3Err_wasmUnderrun;
v->wasm += 4;
r = v_push(v, c_m3Type_f32); if (r) return r;
break;
}
case 0x44: { // f64.const
if (v->wasm + 8 > v->wasmEnd) return m3Err_wasmUnderrun;
v->wasm += 8;
r = v_push(v, c_m3Type_f64); if (r) return r;
break;
}
// ---- i32 comparison ----
case 0x45: r = v_testop(v, c_m3Type_i32); break; // i32.eqz
case 0x46: case 0x47: case 0x48: case 0x49: case 0x4a:
case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f:
r = v_relop(v, c_m3Type_i32); break;
// ---- i64 comparison ----
case 0x50: r = v_testop(v, c_m3Type_i64); break; // i64.eqz
case 0x51: case 0x52: case 0x53: case 0x54: case 0x55:
case 0x56: case 0x57: case 0x58: case 0x59: case 0x5a:
r = v_relop(v, c_m3Type_i64); break;
// ---- f32 comparison ----
case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: case 0x60:
r = v_relop(v, c_m3Type_f32); break;
// ---- f64 comparison ----
case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66:
r = v_relop(v, c_m3Type_f64); break;
// ---- i32 unary ----
case 0x67: case 0x68: case 0x69: // clz, ctz, popcnt
r = v_unop(v, c_m3Type_i32, c_m3Type_i32); break;
// ---- i32 binary ----
case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f:
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75:
case 0x76: case 0x77: case 0x78: // add..rotr
r = v_binop(v, c_m3Type_i32); break;
// ---- i64 unary ----
case 0x79: case 0x7a: case 0x7b: // clz, ctz, popcnt
r = v_unop(v, c_m3Type_i64, c_m3Type_i64); break;
// ---- i64 binary ----
case 0x7c: case 0x7d: case 0x7e: case 0x7f: case 0x80: case 0x81:
case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
case 0x88: case 0x89: case 0x8a: // add..rotr
r = v_binop(v, c_m3Type_i64); break;
// ---- f32 unary ----
case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
case 0x90: case 0x91: // abs, neg, ceil, floor, trunc, nearest, sqrt
r = v_unop(v, c_m3Type_f32, c_m3Type_f32); break;
// ---- f32 binary ----
case 0x92: case 0x93: case 0x94: case 0x95: case 0x96:
case 0x97: case 0x98: // add, sub, mul, div, min, max, copysign
r = v_binop(v, c_m3Type_f32); break;
// ---- f64 unary ----
case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d:
case 0x9e: case 0x9f: // abs, neg, ceil, floor, trunc, nearest, sqrt
r = v_unop(v, c_m3Type_f64, c_m3Type_f64); break;
// ---- f64 binary ----
case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4:
case 0xa5: case 0xa6: // add, sub, mul, div, min, max, copysign
r = v_binop(v, c_m3Type_f64); break;
// ---- Conversions ----
case 0xa7: r = v_cvtop(v, c_m3Type_i64, c_m3Type_i32); break; // i32.wrap/i64
case 0xa8: case 0xa9: // i32.trunc_s/f32, i32.trunc_u/f32
r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
case 0xaa: case 0xab: // i32.trunc_s/f64, i32.trunc_u/f64
r = v_cvtop(v, c_m3Type_f64, c_m3Type_i32); break;
case 0xac: case 0xad: // i64.extend_s/i32, i64.extend_u/i32
r = v_cvtop(v, c_m3Type_i32, c_m3Type_i64); break;
case 0xae: case 0xaf: // i64.trunc_s/f32, i64.trunc_u/f32
r = v_cvtop(v, c_m3Type_f32, c_m3Type_i64); break;
case 0xb0: case 0xb1: // i64.trunc_s/f64, i64.trunc_u/f64
r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
case 0xb2: case 0xb3: // f32.convert_s/i32, f32.convert_u/i32
r = v_cvtop(v, c_m3Type_i32, c_m3Type_f32); break;
case 0xb4: case 0xb5: // f32.convert_s/i64, f32.convert_u/i64
r = v_cvtop(v, c_m3Type_i64, c_m3Type_f32); break;
case 0xb6: // f32.demote/f64
r = v_cvtop(v, c_m3Type_f64, c_m3Type_f32); break;
case 0xb7: case 0xb8: // f64.convert_s/i32, f64.convert_u/i32
r = v_cvtop(v, c_m3Type_i32, c_m3Type_f64); break;
case 0xb9: case 0xba: // f64.convert_s/i64, f64.convert_u/i64
r = v_cvtop(v, c_m3Type_i64, c_m3Type_f64); break;
case 0xbb: // f64.promote/f32
r = v_cvtop(v, c_m3Type_f32, c_m3Type_f64); break;
case 0xbc: // i32.reinterpret/f32
r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
case 0xbd: // i64.reinterpret/f64
r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
case 0xbe: // f32.reinterpret/i32
r = v_cvtop(v, c_m3Type_i32, c_m3Type_f32); break;
case 0xbf: // f64.reinterpret/i64
r = v_cvtop(v, c_m3Type_i64, c_m3Type_f64); break;
// ---- Sign-extension (MVP post) ----
case 0xc0: case 0xc1: // i32.extend8_s, i32.extend16_s
r = v_unop(v, c_m3Type_i32, c_m3Type_i32); break;
case 0xc2: case 0xc3: case 0xc4: // i64.extend8/16/32_s
r = v_unop(v, c_m3Type_i64, c_m3Type_i64); break;
// ---- 0xFC prefix (saturating truncations + bulk memory) ----
case 0xfc:
{
u32 sub;
r = ReadLEB_u32(&sub, &v->wasm, v->wasmEnd);
if (r) return r;
switch (sub) {
case 0x00: case 0x01: // i32.trunc_sat_f32_s/u
r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
case 0x02: case 0x03: // i32.trunc_sat_f64_s/u
r = v_cvtop(v, c_m3Type_f64, c_m3Type_i32); break;
case 0x04: case 0x05: // i64.trunc_sat_f32_s/u
r = v_cvtop(v, c_m3Type_f32, c_m3Type_i64); break;
case 0x06: case 0x07: // i64.trunc_sat_f64_s/u
r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
case 0x0a: // memory.copy
{
u32 dst, src;
r = ReadLEB_u32(&dst, &v->wasm, v->wasmEnd); if (r) return r;
r = ReadLEB_u32(&src, &v->wasm, v->wasmEnd); if (r) return r;
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // n
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // src
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // dst
break;
}
case 0x0b: // memory.fill
{
u32 memidx;
r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // n
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // val
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // dst
break;
}
default:
// Unknown FC sub-opcode: skip validation (allow forward compat)
break;
}
break;
}
default:
// Unknown opcode - skip rather than fail for forward compat
// (the compiler will reject truly unsupported ops later)
break;
} // switch
if (r) return r;
} // while
// If we ran out of bytes without hitting the final end
return m3Err_wasmMalformed;
}
// ---------- Public entry point ----------
M3Result ValidateFunction (IM3Function i_function)
{
if (!i_function->wasm) return m3Err_none;
IM3FuncType funcType = i_function->funcType;
IM3Module module = i_function->module;
// Set up context on stack
ValCtx v;
memset(&v, 0, sizeof(v));
v.module = module;
v.function = i_function;
v.wasm = i_function->wasm;
v.wasmEnd = i_function->wasmEnd;
// Skip code size LEB
u32 size;
M3Result r = ReadLEB_u32(&size, &v.wasm, v.wasmEnd);
if (r) return r;
// Parse locals
u32 numLocalBlocks;
r = ReadLEB_u32(&numLocalBlocks, &v.wasm, v.wasmEnd);
if (r) return r;
// First: params. Running out of room has to be an error, not a truncation:
// a short localTypes would make later local.get indices read as unknown
u16 numParams = funcType ? funcType->numArgs : 0;
if (numParams > d_m3ValStack) return m3Err_functionStackOverflow;
for (u16 i = 0; i < numParams; i++) {
v.localTypes[v.numLocals++] = funcType->types[funcType->numRets + i];
}
// Then: declared locals
for (u32 b = 0; b < numLocalBlocks; b++) {
u32 count;
r = ReadLEB_u32(&count, &v.wasm, v.wasmEnd);
if (r) return r;
i8 waType;
r = ReadLEB_i7(&waType, &v.wasm, v.wasmEnd);
if (r) return r;
u8 normalized;
r = NormalizeType(&normalized, waType);
if (r) return r;
if (count > (u32) (d_m3ValStack - v.numLocals)) return m3Err_functionStackOverflow;
for (u32 c = 0; c < count; c++) {
v.localTypes[v.numLocals++] = normalized;
}
}
// Push the function-level control frame
r = v_push_ctrl(&v, 0x00, funcType); // opcode 0x00 marks function frame
if (r) return r;
// Push params onto operand stack (they're part of the function body's initial stack)
// Actually per the spec, locals are indexed but not on the operand stack.
// The function frame's params are NOT pushed to the operand stack.
// Only block params would be pushed (and for the function frame there are no block params
// since the function body's "block type" has results = function returns, params = 0).
// The function frame's label_types = results (since it's not a loop).
// Validate the body
r = v_validate_body(&v);
if (r) return r;
// After validation, control stack should be empty
if (v.ctrlTop != 0)
return m3Err_wasmMalformed;
return m3Err_none;
}
#else // !d_m3EnableValidation
M3Result ValidateFunction (IM3Function i_function)
{
(void)i_function;
return m3Err_none;
}
#endif // d_m3EnableValidation
+25
View File
@@ -0,0 +1,25 @@
//
// m3_validate.h
//
// Pre-pass WebAssembly bytecode validator using the spec's type-checking algorithm.
// Runs before compilation to catch type errors early.
//
#ifndef m3_validate_h
#define m3_validate_h
#include "m3_core.h"
#include "m3_compile.h"
#include "m3_env.h"
d_m3BeginExternC
// Validate a function's bytecode before compilation.
// Performs full type-checking per the WebAssembly spec algorithm:
// operand type stack + control stack with polymorphic handling.
// Returns m3Err_none on success or a validation error.
M3Result ValidateFunction (IM3Function i_function);
d_m3EndExternC
#endif // m3_validate_h
+391
View File
@@ -0,0 +1,391 @@
//
// Wasm3, high performance WebAssembly interpreter
//
// Copyright © 2019 Steven Massey, Volodymyr Shymanskyy.
// All rights reserved.
//
#ifndef wasm3_h
#define wasm3_h
#define M3_VERSION_MAJOR 0
#define M3_VERSION_MINOR 5
#define M3_VERSION_REV 2
#define M3_VERSION "0.5.2"
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stddef.h>
#include "wasm3_defs.h"
// Constants
#define M3_BACKTRACE_TRUNCATED (IM3BacktraceFrame)(SIZE_MAX)
#if defined(__cplusplus)
extern "C" {
#endif
typedef const char * M3Result;
struct M3Environment; typedef struct M3Environment * IM3Environment;
struct M3Runtime; typedef struct M3Runtime * IM3Runtime;
struct M3Module; typedef struct M3Module * IM3Module;
struct M3Function; typedef struct M3Function * IM3Function;
struct M3Global; typedef struct M3Global * IM3Global;
typedef struct M3ErrorInfo
{
M3Result result;
IM3Runtime runtime;
IM3Module module;
IM3Function function;
const char * file;
uint32_t line;
const char * message;
} M3ErrorInfo;
typedef struct M3BacktraceFrame
{
uint32_t moduleOffset;
IM3Function function;
struct M3BacktraceFrame * next;
}
M3BacktraceFrame, * IM3BacktraceFrame;
typedef struct M3BacktraceInfo
{
IM3BacktraceFrame frames;
IM3BacktraceFrame lastFrame; // can be M3_BACKTRACE_TRUNCATED
}
M3BacktraceInfo, * IM3BacktraceInfo;
typedef enum M3ValueType
{
c_m3Type_none = 0,
c_m3Type_i32 = 1,
c_m3Type_i64 = 2,
c_m3Type_f32 = 3,
c_m3Type_f64 = 4,
// Opaque 16-byte slot used purely so wasm3 can PARSE modules
// whose function signatures or local-variable declarations
// mention v128 (the SIMD value type, wasm-encoded as 0x7B).
// Actual v128 OPCODES still error at compile-time with
// m3Err_unknownOpcode - we only avoid the parse-time
// m3Err_invalidTypeId rejection. LLVM's auto-vectorizer emits
// unused v128 locals into many `+simd128` modules even when no
// SIMD op executes; without this slot wasm3 rejects every such
// module before it ever sees a function body.
c_m3Type_v128 = 5,
c_m3Type_unknown
} M3ValueType;
typedef struct M3TaggedValue
{
M3ValueType type;
union M3ValueUnion
{
uint32_t i32;
uint64_t i64;
float f32;
double f64;
} value;
}
M3TaggedValue, * IM3TaggedValue;
typedef struct M3ImportInfo
{
const char * moduleUtf8;
const char * fieldUtf8;
}
M3ImportInfo, * IM3ImportInfo;
typedef struct M3ImportContext
{
void * userdata;
IM3Function function;
}
M3ImportContext, * IM3ImportContext;
// -------------------------------------------------------------------------------------------------------------------------------
// error codes
// -------------------------------------------------------------------------------------------------------------------------------
# if defined(M3_IMPLEMENT_ERROR_STRINGS)
# if defined(__cplusplus)
# define d_m3ErrorConst(LABEL, STRING) extern const M3Result m3Err_##LABEL = { STRING };
# else
# define d_m3ErrorConst(LABEL, STRING) const M3Result m3Err_##LABEL = { STRING };
# endif
# else
# define d_m3ErrorConst(LABEL, STRING) extern const M3Result m3Err_##LABEL;
# endif
// -------------------------------------------------------------------------------------------------------------------------------
d_m3ErrorConst (none, NULL)
// general errors
d_m3ErrorConst (mallocFailed, "memory allocation failed")
// parse errors
d_m3ErrorConst (incompatibleWasmVersion, "incompatible Wasm binary version")
d_m3ErrorConst (wasmMalformed, "malformed Wasm binary")
d_m3ErrorConst (misorderedWasmSection, "out of order Wasm section")
d_m3ErrorConst (wasmUnderrun, "underrun while parsing Wasm binary")
d_m3ErrorConst (wasmOverrun, "overrun while parsing Wasm binary")
d_m3ErrorConst (wasmMissingInitExpr, "missing init_expr in Wasm binary")
d_m3ErrorConst (lebOverflow, "LEB encoded value overflow")
d_m3ErrorConst (missingUTF8, "invalid length UTF-8 string")
d_m3ErrorConst (wasmSectionUnderrun, "section underrun while parsing Wasm binary")
d_m3ErrorConst (wasmSectionOverrun, "section overrun while parsing Wasm binary")
d_m3ErrorConst (invalidTypeId, "unknown value_type")
d_m3ErrorConst (tooManyMemorySections, "only one memory per module is supported")
d_m3ErrorConst (tooManyArgsRets, "too many arguments or return values")
// link errors
d_m3ErrorConst (moduleNotLinked, "attempting to use module that is not loaded")
d_m3ErrorConst (moduleAlreadyLinked, "attempting to bind module to multiple runtimes")
d_m3ErrorConst (functionLookupFailed, "function lookup failed")
d_m3ErrorConst (functionImportMissing, "missing imported function")
d_m3ErrorConst (malformedFunctionSignature, "malformed function signature")
// compilation errors
d_m3ErrorConst (noCompiler, "no compiler found for opcode")
d_m3ErrorConst (unknownOpcode, "unknown opcode")
d_m3ErrorConst (restrictedOpcode, "restricted opcode")
d_m3ErrorConst (functionStackOverflow, "compiling function overran its stack height limit")
d_m3ErrorConst (functionStackUnderrun, "compiling function underran the stack")
d_m3ErrorConst (mallocFailedCodePage, "memory allocation failed when acquiring a new M3 code page")
d_m3ErrorConst (settingImmutableGlobal, "attempting to set an immutable global")
d_m3ErrorConst (typeMismatch, "incorrect type on stack")
d_m3ErrorConst (typeCountMismatch, "incorrect value count on stack")
// runtime errors
d_m3ErrorConst (missingCompiledCode, "function is missing compiled m3 code")
d_m3ErrorConst (wasmMemoryOverflow, "runtime ran out of memory")
d_m3ErrorConst (globalMemoryNotAllocated, "global memory is missing from a module")
d_m3ErrorConst (globaIndexOutOfBounds, "global index is too large")
d_m3ErrorConst (argumentCountMismatch, "argument count mismatch")
d_m3ErrorConst (argumentTypeMismatch, "argument type mismatch")
d_m3ErrorConst (globalLookupFailed, "global lookup failed")
d_m3ErrorConst (globalTypeMismatch, "global type mismatch")
d_m3ErrorConst (globalNotMutable, "global is not mutable")
// traps
d_m3ErrorConst (trapOutOfBoundsMemoryAccess, "[trap] out of bounds memory access")
d_m3ErrorConst (trapDivisionByZero, "[trap] integer divide by zero")
d_m3ErrorConst (trapIntegerOverflow, "[trap] integer overflow")
d_m3ErrorConst (trapIntegerConversion, "[trap] invalid conversion to integer")
d_m3ErrorConst (trapIndirectCallTypeMismatch, "[trap] indirect call type mismatch")
d_m3ErrorConst (trapTableIndexOutOfRange, "[trap] undefined element")
d_m3ErrorConst (trapTableElementIsNull, "[trap] null table element")
d_m3ErrorConst (trapExit, "[trap] program called exit")
d_m3ErrorConst (trapAbort, "[trap] program called abort")
d_m3ErrorConst (trapUnreachable, "[trap] unreachable executed")
d_m3ErrorConst (trapStackOverflow, "[trap] stack overflow")
//-------------------------------------------------------------------------------------------------------------------------------
// configuration, can be found in m3_config.h, m3_config_platforms.h, m3_core.h)
//-------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------
// global environment than can host multiple runtimes
//-------------------------------------------------------------------------------------------------------------------------------
IM3Environment m3_NewEnvironment (void);
void m3_FreeEnvironment (IM3Environment i_environment);
typedef M3Result (* M3SectionHandler) (IM3Module i_module, const char* name, const uint8_t * start, const uint8_t * end);
void m3_SetCustomSectionHandler (IM3Environment i_environment, M3SectionHandler i_handler);
//-------------------------------------------------------------------------------------------------------------------------------
// execution context
//-------------------------------------------------------------------------------------------------------------------------------
IM3Runtime m3_NewRuntime (IM3Environment io_environment,
uint32_t i_stackSizeInBytes,
void * i_userdata);
void m3_FreeRuntime (IM3Runtime i_runtime);
// Wasm currently only supports one memory region. i_memoryIndex should be zero.
uint8_t * m3_GetMemory (IM3Runtime i_runtime,
uint32_t * o_memorySizeInBytes,
uint32_t i_memoryIndex);
// This is used internally by Raw Function helpers
uint32_t m3_GetMemorySize (IM3Runtime i_runtime);
void * m3_GetUserData (IM3Runtime i_runtime);
//-------------------------------------------------------------------------------------------------------------------------------
// modules
//-------------------------------------------------------------------------------------------------------------------------------
// i_wasmBytes data must be persistent during the lifetime of the module
M3Result m3_ParseModule (IM3Environment i_environment,
IM3Module * o_module,
const uint8_t * const i_wasmBytes,
uint32_t i_numWasmBytes);
// Only modules not loaded into a M3Runtime need to be freed. A module is considered unloaded if
// a. m3_LoadModule has not yet been called on that module. Or,
// b. m3_LoadModule returned a result.
void m3_FreeModule (IM3Module i_module);
// LoadModule transfers ownership of a module to the runtime. Do not free modules once successfully loaded into the runtime
M3Result m3_LoadModule (IM3Runtime io_runtime, IM3Module io_module);
// Optional, compiles all functions in the module
M3Result m3_CompileModule (IM3Module io_module);
// Calling m3_RunStart is optional
M3Result m3_RunStart (IM3Module i_module);
// Arguments and return values are passed in and out through the stack pointer _sp.
// Placeholder return value slots are first and arguments after. So, the first argument is at _sp [numReturns]
// Return values should be written into _sp [0] to _sp [num_returns - 1]
typedef const void * (* M3RawCall) (IM3Runtime runtime, IM3ImportContext _ctx, uint64_t * _sp, void * _mem);
M3Result m3_LinkRawFunction (IM3Module io_module,
const char * const i_moduleName,
const char * const i_functionName,
const char * const i_signature,
M3RawCall i_function);
M3Result m3_LinkRawFunctionEx (IM3Module io_module,
const char * const i_moduleName,
const char * const i_functionName,
const char * const i_signature,
M3RawCall i_function,
const void * i_userdata);
const char* m3_GetModuleName (IM3Module i_module);
void m3_SetModuleName (IM3Module i_module, const char* name);
IM3Runtime m3_GetModuleRuntime (IM3Module i_module);
//-------------------------------------------------------------------------------------------------------------------------------
// globals
//-------------------------------------------------------------------------------------------------------------------------------
IM3Global m3_FindGlobal (IM3Module io_module,
const char * const i_globalName);
M3Result m3_GetGlobal (IM3Global i_global,
IM3TaggedValue o_value);
M3Result m3_SetGlobal (IM3Global i_global,
const IM3TaggedValue i_value);
M3ValueType m3_GetGlobalType (IM3Global i_global);
//-------------------------------------------------------------------------------------------------------------------------------
// functions
//-------------------------------------------------------------------------------------------------------------------------------
M3Result m3_Yield (void);
// o_function is valid during the lifetime of the originating runtime
M3Result m3_FindFunction (IM3Function * o_function,
IM3Runtime i_runtime,
const char * const i_functionName);
M3Result m3_GetTableFunction (IM3Function * o_function,
IM3Module i_module,
uint32_t i_index);
uint32_t m3_GetArgCount (IM3Function i_function);
uint32_t m3_GetRetCount (IM3Function i_function);
M3ValueType m3_GetArgType (IM3Function i_function, uint32_t i_index);
M3ValueType m3_GetRetType (IM3Function i_function, uint32_t i_index);
M3Result m3_CallV (IM3Function i_function, ...);
M3Result m3_CallVL (IM3Function i_function, va_list i_args);
M3Result m3_Call (IM3Function i_function, uint32_t i_argc, const void * i_argptrs[]);
M3Result m3_CallArgv (IM3Function i_function, uint32_t i_argc, const char * i_argv[]);
M3Result m3_GetResultsV (IM3Function i_function, ...);
M3Result m3_GetResultsVL (IM3Function i_function, va_list o_rets);
M3Result m3_GetResults (IM3Function i_function, uint32_t i_retc, const void * o_retptrs[]);
void m3_GetErrorInfo (IM3Runtime i_runtime, M3ErrorInfo* o_info);
void m3_ResetErrorInfo (IM3Runtime i_runtime);
const char* m3_GetFunctionName (IM3Function i_function);
IM3Module m3_GetFunctionModule (IM3Function i_function);
//-------------------------------------------------------------------------------------------------------------------------------
// debug info
//-------------------------------------------------------------------------------------------------------------------------------
void m3_PrintRuntimeInfo (IM3Runtime i_runtime);
void m3_PrintM3Info (void);
void m3_PrintProfilerInfo (void);
// The runtime owns the backtrace, do not free the backtrace you obtain. Returns NULL if there's no backtrace.
IM3BacktraceInfo m3_GetBacktrace (IM3Runtime i_runtime);
//-------------------------------------------------------------------------------------------------------------------------------
// raw function definition helpers
//-------------------------------------------------------------------------------------------------------------------------------
# define m3ApiOffsetToPtr(offset) (void*)((uint8_t*)_mem + (uint32_t)(offset))
# define m3ApiPtrToOffset(ptr) (uint32_t)((uint8_t*)ptr - (uint8_t*)_mem)
# define m3ApiReturnType(TYPE) TYPE* raw_return = ((TYPE*) (_sp++));
# define m3ApiMultiValueReturnType(TYPE, NAME) TYPE* NAME = ((TYPE*) (_sp++));
# define m3ApiGetArg(TYPE, NAME) TYPE NAME = \
(sizeof(TYPE) >= sizeof(uint32_t)) ? \
(*((TYPE *)(_sp++))) : \
((TYPE)(*((uint32_t *)(_sp++))));
# define m3ApiGetArgMem(TYPE, NAME) TYPE NAME = (TYPE)m3ApiOffsetToPtr(* ((uint32_t *) (_sp++)));
# define m3ApiIsNullPtr(addr) ((void*)(addr) <= _mem)
# define m3ApiCheckMem(addr, len) { if (M3_UNLIKELY(((void*)(addr) < _mem) || ((uint64_t)(uintptr_t)(addr) + (len)) > ((uint64_t)(uintptr_t)(_mem)+m3_GetMemorySize(runtime)))) m3ApiTrap(m3Err_trapOutOfBoundsMemoryAccess); }
# define m3ApiRawFunction(NAME) const void * NAME (IM3Runtime runtime, IM3ImportContext _ctx, uint64_t * _sp, void * _mem)
# define m3ApiReturn(VALUE) { *raw_return = (VALUE); return m3Err_none;}
# define m3ApiMultiValueReturn(NAME, VALUE) { *NAME = (VALUE); }
# define m3ApiTrap(VALUE) { return VALUE; }
# define m3ApiSuccess() { return m3Err_none; }
# if defined(M3_BIG_ENDIAN)
# define m3ApiReadMem8(ptr) (* (uint8_t *)(ptr))
# define m3ApiReadMem16(ptr) m3_bswap16((* (uint16_t *)(ptr)))
# define m3ApiReadMem32(ptr) m3_bswap32((* (uint32_t *)(ptr)))
# define m3ApiReadMem64(ptr) m3_bswap64((* (uint64_t *)(ptr)))
# define m3ApiWriteMem8(ptr, val) { * (uint8_t *)(ptr) = (val); }
# define m3ApiWriteMem16(ptr, val) { * (uint16_t *)(ptr) = m3_bswap16((val)); }
# define m3ApiWriteMem32(ptr, val) { * (uint32_t *)(ptr) = m3_bswap32((val)); }
# define m3ApiWriteMem64(ptr, val) { * (uint64_t *)(ptr) = m3_bswap64((val)); }
# else
# define m3ApiReadMem8(ptr) (* (uint8_t *)(ptr))
# define m3ApiReadMem16(ptr) (* (uint16_t *)(ptr))
# define m3ApiReadMem32(ptr) (* (uint32_t *)(ptr))
# define m3ApiReadMem64(ptr) (* (uint64_t *)(ptr))
# define m3ApiWriteMem8(ptr, val) { * (uint8_t *)(ptr) = (val); }
# define m3ApiWriteMem16(ptr, val) { * (uint16_t *)(ptr) = (val); }
# define m3ApiWriteMem32(ptr, val) { * (uint32_t *)(ptr) = (val); }
# define m3ApiWriteMem64(ptr, val) { * (uint64_t *)(ptr) = (val); }
# endif
#if defined(__cplusplus)
}
#endif
#endif // wasm3_h
+293
View File
@@ -0,0 +1,293 @@
//
// wasm3_defs.h
//
// Created by Volodymyr Shymanskyy on 11/20/19.
// Copyright © 2019 Volodymyr Shymanskyy. All rights reserved.
//
#ifndef wasm3_defs_h
#define wasm3_defs_h
#define M3_STR__(x) #x
#define M3_STR(x) M3_STR__(x)
#define M3_CONCAT__(a,b) a##b
#define M3_CONCAT(a,b) M3_CONCAT__(a,b)
/*
* Detect compiler
*/
# if defined(__clang__)
# define M3_COMPILER_CLANG 1
# elif defined(__INTEL_COMPILER)
# define M3_COMPILER_ICC 1
# elif defined(__GNUC__) || defined(__GNUG__)
# define M3_COMPILER_GCC 1
# elif defined(_MSC_VER)
# define M3_COMPILER_MSVC 1
# else
# warning "Compiler not detected"
# endif
# if defined(M3_COMPILER_CLANG)
# if defined(WIN32)
# define M3_COMPILER_VER __VERSION__ " for Windows"
# else
# define M3_COMPILER_VER __VERSION__
# endif
# elif defined(M3_COMPILER_GCC)
# define M3_COMPILER_VER "GCC " __VERSION__
# elif defined(M3_COMPILER_ICC)
# define M3_COMPILER_VER __VERSION__
# elif defined(M3_COMPILER_MSVC)
# define M3_COMPILER_VER "MSVC " M3_STR(_MSC_VER)
# else
# define M3_COMPILER_VER "unknown"
# endif
# ifdef __has_feature
# define M3_COMPILER_HAS_FEATURE(x) __has_feature(x)
# else
# define M3_COMPILER_HAS_FEATURE(x) 0
# endif
# ifdef __has_builtin
# define M3_COMPILER_HAS_BUILTIN(x) __has_builtin(x)
# else
# define M3_COMPILER_HAS_BUILTIN(x) 0
# endif
# ifdef __has_attribute
# define M3_COMPILER_HAS_ATTRIBUTE(x) __has_attribute(x)
# else
# define M3_COMPILER_HAS_ATTRIBUTE(x) 0
# endif
/*
* Detect endianness
*/
# if defined(M3_COMPILER_MSVC)
# define M3_LITTLE_ENDIAN
# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define M3_LITTLE_ENDIAN
# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define M3_BIG_ENDIAN
# else
# error "Byte order not detected"
# endif
/*
* Detect platform
*/
# if defined(M3_COMPILER_CLANG) || defined(M3_COMPILER_GCC) || defined(M3_COMPILER_ICC)
# if defined(__wasm__)
# define M3_ARCH "wasm"
# elif defined(__x86_64__)
# define M3_ARCH "x86_64"
# elif defined(__i386__)
# define M3_ARCH "i386"
# elif defined(__aarch64__)
# define M3_ARCH "arm64-v8a"
# elif defined(__arm__)
# if defined(__ARM_ARCH_7A__)
# if defined(__ARM_NEON__)
# if defined(__ARM_PCS_VFP)
# define M3_ARCH "arm-v7a/NEON hard-float"
# else
# define M3_ARCH "arm-v7a/NEON"
# endif
# else
# if defined(__ARM_PCS_VFP)
# define M3_ARCH "arm-v7a hard-float"
# else
# define M3_ARCH "arm-v7a"
# endif
# endif
# else
# define M3_ARCH "arm"
# endif
# elif defined(__riscv)
# if defined(__riscv_32e)
# define _M3_ARCH_RV "rv32e"
# elif __riscv_xlen == 128
# define _M3_ARCH_RV "rv128i"
# elif __riscv_xlen == 64
# define _M3_ARCH_RV "rv64i"
# elif __riscv_xlen == 32
# define _M3_ARCH_RV "rv32i"
# endif
# if defined(__riscv_muldiv)
# define _M3_ARCH_RV_M _M3_ARCH_RV "m"
# else
# define _M3_ARCH_RV_M _M3_ARCH_RV
# endif
# if defined(__riscv_atomic)
# define _M3_ARCH_RV_A _M3_ARCH_RV_M "a"
# else
# define _M3_ARCH_RV_A _M3_ARCH_RV_M
# endif
# if defined(__riscv_flen)
# define _M3_ARCH_RV_F _M3_ARCH_RV_A "f"
# else
# define _M3_ARCH_RV_F _M3_ARCH_RV_A
# endif
# if defined(__riscv_flen) && __riscv_flen >= 64
# define _M3_ARCH_RV_D _M3_ARCH_RV_F "d"
# else
# define _M3_ARCH_RV_D _M3_ARCH_RV_F
# endif
# if defined(__riscv_compressed)
# define _M3_ARCH_RV_C _M3_ARCH_RV_D "c"
# else
# define _M3_ARCH_RV_C _M3_ARCH_RV_D
# endif
# define M3_ARCH _M3_ARCH_RV_C
# elif defined(__mips__)
# if defined(__MIPSEB__) && defined(__mips64)
# define M3_ARCH "mips64 " _MIPS_ARCH
# elif defined(__MIPSEL__) && defined(__mips64)
# define M3_ARCH "mips64el " _MIPS_ARCH
# elif defined(__MIPSEB__)
# define M3_ARCH "mips " _MIPS_ARCH
# elif defined(__MIPSEL__)
# define M3_ARCH "mipsel " _MIPS_ARCH
# endif
# elif defined(__PPC__)
# if defined(__PPC64__) && defined(__LITTLE_ENDIAN__)
# define M3_ARCH "ppc64le"
# elif defined(__PPC64__)
# define M3_ARCH "ppc64"
# else
# define M3_ARCH "ppc"
# endif
# elif defined(__sparc__)
# if defined(__arch64__)
# define M3_ARCH "sparc64"
# else
# define M3_ARCH "sparc"
# endif
# elif defined(__s390x__)
# define M3_ARCH "s390x"
# elif defined(__alpha__)
# define M3_ARCH "alpha"
# elif defined(__m68k__)
# define M3_ARCH "m68k"
# elif defined(__xtensa__)
# define M3_ARCH "xtensa"
# elif defined(__arc__)
# define M3_ARCH "arc32"
# elif defined(__AVR__)
# define M3_ARCH "avr"
# endif
# endif
# if defined(M3_COMPILER_MSVC)
# if defined(_M_X64)
# define M3_ARCH "x86_64"
# elif defined(_M_IX86)
# define M3_ARCH "i386"
# elif defined(_M_ARM64)
# define M3_ARCH "arm64"
# elif defined(_M_ARM)
# define M3_ARCH "arm"
# endif
# endif
# if !defined(M3_ARCH)
# warning "Architecture not detected"
# define M3_ARCH "unknown"
# endif
/*
* Byte swapping (for Big-Endian systems only)
*/
# if defined(M3_COMPILER_MSVC)
# define m3_bswap16(x) _byteswap_ushort((x))
# define m3_bswap32(x) _byteswap_ulong((x))
# define m3_bswap64(x) _byteswap_uint64((x))
# elif defined(M3_COMPILER_GCC) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))
// __builtin_bswap32/64 added in gcc 4.3, __builtin_bswap16 added in gcc 4.8
# define m3_bswap16(x) __builtin_bswap16((x))
# define m3_bswap32(x) __builtin_bswap32((x))
# define m3_bswap64(x) __builtin_bswap64((x))
# elif defined(M3_COMPILER_CLANG) && M3_COMPILER_HAS_BUILTIN(__builtin_bswap16)
# define m3_bswap16(x) __builtin_bswap16((x))
# define m3_bswap32(x) __builtin_bswap32((x))
# define m3_bswap64(x) __builtin_bswap64((x))
# elif defined(M3_COMPILER_ICC)
# define m3_bswap16(x) __builtin_bswap16((x))
# define m3_bswap32(x) __builtin_bswap32((x))
# define m3_bswap64(x) __builtin_bswap64((x))
# else
# ifdef __linux__
# include <endian.h>
# else
# include <stdint.h>
# endif
# if defined(__bswap_16)
# define m3_bswap16(x) __bswap_16((x))
# define m3_bswap32(x) __bswap_32((x))
# define m3_bswap64(x) __bswap_64((x))
# else
# warning "Using naive (probably slow) bswap operations"
static inline
uint16_t m3_bswap16(uint16_t x) {
return ((( x >> 8 ) & 0xffu ) | (( x & 0xffu ) << 8 ));
}
static inline
uint32_t m3_bswap32(uint32_t x) {
return ((( x & 0xff000000u ) >> 24 ) |
(( x & 0x00ff0000u ) >> 8 ) |
(( x & 0x0000ff00u ) << 8 ) |
(( x & 0x000000ffu ) << 24 ));
}
static inline
uint64_t m3_bswap64(uint64_t x) {
return ((( x & 0xff00000000000000ull ) >> 56 ) |
(( x & 0x00ff000000000000ull ) >> 40 ) |
(( x & 0x0000ff0000000000ull ) >> 24 ) |
(( x & 0x000000ff00000000ull ) >> 8 ) |
(( x & 0x00000000ff000000ull ) << 8 ) |
(( x & 0x0000000000ff0000ull ) << 24 ) |
(( x & 0x000000000000ff00ull ) << 40 ) |
(( x & 0x00000000000000ffull ) << 56 ));
}
# endif
# endif
/*
* Bit ops
*/
#define m3_isBitSet(val, pos) ((val & (1 << pos)) != 0)
/*
* Other
*/
# if defined(M3_COMPILER_GCC) || defined(M3_COMPILER_CLANG) || defined(M3_COMPILER_ICC)
# define M3_UNLIKELY(x) __builtin_expect(!!(x), 0)
# define M3_LIKELY(x) __builtin_expect(!!(x), 1)
# else
# define M3_UNLIKELY(x) (x)
# define M3_LIKELY(x) (x)
# endif
#endif // wasm3_defs_h
+103
View File
@@ -0,0 +1,103 @@
/* wasm_call.c - serialized wasm3 call under a per-module mutex on a fresh 64KB
* kernel stack. one entry point everyone in the driver uses to invoke guests. */
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
#include "wasm3/m3_function.h"
#include "wasm3/wasm3.h"
typedef struct {
IM3Function fn;
uint32_t argc;
const void** argp;
M3Result result;
} gvm_call_ctx;
static VOID gvm_call_callout(_In_ PVOID p)
{
gvm_call_ctx* c = (gvm_call_ctx*)p;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] pre-m3Call fn=%p name=%s\n",
c->fn, (c->fn && c->fn->export_name) ? c->fn->export_name : "?");
__try {
c->result = m3_Call(c->fn, c->argc, c->argp);
} __except (EXCEPTION_EXECUTE_HANDLER) {
c->result = "wasm3 dispatch raised kernel exception";
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] post-m3Call result=%s\n", c->result ? c->result : "ok");
}
M3Result
gvm_call_locked(gvm_module* mod, IM3Function fn, unsigned int argc, const void** argp)
{
if (!mod || !fn) return "invalid module or function";
if (mod->poisoned) return "module poisoned by watchdog";
KeWaitForSingleObject(&mod->call_mutex, Executive, KernelMode, FALSE, NULL);
if (mod->poisoned) { KeReleaseMutex(&mod->call_mutex, FALSE); return "module poisoned"; }
// stamp mutex acquire for watchdog visibility
LARGE_INTEGER _f;
mod->mutex_hold_qpc = (ULONG64)KeQueryPerformanceCounter(&_f).QuadPart;
// trace: entering export
if (gvm_trace_enabled_for(mod->id)) {
unsigned long long targv[4] = {0,0,0,0};
unsigned int tac = argc > 4 ? 4 : argc;
for (unsigned int i = 0; i < tac; i++) targv[i] = argp[i] ? *(uint64_t*)argp[i] : 0;
gvm_trace_push(mod->id, GVM_TRK_CALL, fn->export_name ? fn->export_name : "?", tac, targv, 0);
}
gvm_call_ctx c = { fn, argc, argp, m3Err_none };
NTSTATUS s = KeExpandKernelStackAndCalloutEx(
gvm_call_callout, &c,
64 * 1024,
FALSE,
NULL);
// clear deadline after every call so it doesn't leak across invocations
mod->exec_deadline_qpc = 0;
// trace: return or trap
if (gvm_trace_enabled_for(mod->id)) {
if (!NT_SUCCESS(s)) {
unsigned long long argv[1] = { (unsigned long long)s };
gvm_trace_push(mod->id, GVM_TRK_TRAP, "kstack_callout_failed", 1, argv, 0);
} else if (c.result) {
// push the trap reason itself
gvm_trace_push(mod->id, GVM_TRK_TRAP, c.result, 0, NULL, 0);
// and each frame of the wasm backtrace as its own entry
IM3BacktraceInfo bt = m3_GetBacktrace(mod->runtime);
if (bt) {
unsigned int depth = 0;
for (IM3BacktraceFrame f = bt->frames; f && f != M3_BACKTRACE_TRUNCATED && depth < 16;
f = f->next, depth++) {
unsigned long long a[2] = { (unsigned long long)f->moduleOffset, depth };
const char* fname = (f->function && f->function->export_name)
? f->function->export_name : "<anon>";
gvm_trace_push(mod->id, GVM_TRK_TRAP, fname, 2, a, 0);
}
}
} else {
gvm_trace_push(mod->id, GVM_TRK_RETURN, fn->export_name ? fn->export_name : "?", 0, NULL, 0);
}
}
mod->mutex_hold_qpc = 0;
KeReleaseMutex(&mod->call_mutex, FALSE);
if (!NT_SUCCESS(s))
return "KeExpandKernelStackAndCalloutEx failed";
return c.result;
}
// caller sets timeout_ms > 0 before invoking gvm_call_locked to arm the deadline
void gvm_set_deadline_ms(gvm_module* mod, unsigned int timeout_ms)
{
if (!mod || timeout_ms == 0) { if (mod) mod->exec_deadline_qpc = 0; return; }
LARGE_INTEGER freq;
LARGE_INTEGER now = KeQueryPerformanceCounter(&freq);
ULONG64 ticks = ((ULONG64)freq.QuadPart * timeout_ms) / 1000ULL;
mod->exec_deadline_qpc = (ULONG64)now.QuadPart + ticks;
}
+103
View File
@@ -0,0 +1,103 @@
/* watchdog.c - system thread that patrols loaded modules for stuck guests.
* a module holding call_mutex longer than GVM_WATCHDOG_MAX_MS is flagged
* "poisoned" so no more calls dispatch, and the incident is logged.
* force-unload IOCTL bypasses the standard mutex wait for these cases.
*/
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
#define GVM_WATCHDOG_MAX_MS 5000 // max wall-clock a single call may hold
#define GVM_WATCHDOG_PERIOD_MS 500 // scan cadence
static PETHREAD g_thread = NULL;
static KEVENT g_shutdown;
static BOOLEAN g_running = FALSE;
static VOID gvm_watchdog_body(PVOID ctx)
{
UNREFERENCED_PARAMETER(ctx);
LARGE_INTEGER wait; wait.QuadPart = -((LONGLONG)GVM_WATCHDOG_PERIOD_MS * 10 * 1000);
LARGE_INTEGER freq;
ULONG64 max_ticks = 0;
(void)KeQueryPerformanceCounter(&freq);
if (freq.QuadPart) max_ticks = ((ULONG64)freq.QuadPart * GVM_WATCHDOG_MAX_MS) / 1000ULL;
for (;;) {
NTSTATUS s = KeWaitForSingleObject(&g_shutdown, Executive, KernelMode, FALSE, &wait);
if (s == STATUS_SUCCESS) break; // shutdown signaled
if (!max_ticks) continue;
ULONG64 now = (ULONG64)KeQueryPerformanceCounter(NULL).QuadPart;
for (unsigned int i = 0; i < 32; i++) {
gvm_module* m = gvm_modtab_iter(i);
if (!m || !m->used) continue;
ULONG64 h = m->mutex_hold_qpc;
if (!h) continue;
if (now <= h) continue;
if ((now - h) < max_ticks) continue;
if (m->poisoned) continue;
InterlockedExchange(&m->poisoned, 1);
gvm_log("watchdog: module %u '%s' stuck >%u ms - marked poisoned",
m->id, m->name, GVM_WATCHDOG_MAX_MS);
}
}
PsTerminateSystemThread(STATUS_SUCCESS);
}
void gvm_watchdog_start(void)
{
if (g_running) return;
KeInitializeEvent(&g_shutdown, NotificationEvent, FALSE);
HANDLE h;
NTSTATUS s = PsCreateSystemThread(&h, THREAD_ALL_ACCESS, NULL, NULL, NULL, gvm_watchdog_body, NULL);
if (!NT_SUCCESS(s)) { gvm_log("watchdog: thread create failed 0x%x", s); return; }
ObReferenceObjectByHandle(h, THREAD_ALL_ACCESS, *PsThreadType, KernelMode, (PVOID*)&g_thread, NULL);
ZwClose(h);
g_running = TRUE;
gvm_log("watchdog: started (period=%ums, max_hold=%ums)", GVM_WATCHDOG_PERIOD_MS, GVM_WATCHDOG_MAX_MS);
}
void gvm_watchdog_stop(void)
{
if (!g_running) return;
KeSetEvent(&g_shutdown, IO_NO_INCREMENT, FALSE);
if (g_thread) {
KeWaitForSingleObject(g_thread, Executive, KernelMode, FALSE, NULL);
ObDereferenceObject(g_thread);
g_thread = NULL;
}
g_running = FALSE;
}
// IOCTL handler: bypass the normal unload path's mutex wait when a module is
// poisoned. safe only because poisoned modules can no longer make host calls.
NTSTATUS
gvm_ioctl_force_unload(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_unload_in) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_unload_in in; RtlCopyMemory(&in, buf, sizeof(in));
gvm_module* m = gvm_modtab_get(in.module_id);
if (!m) { irp->IoStatus.Information = 0; return STATUS_NOT_FOUND; }
InterlockedExchange(&m->poisoned, 1);
gvm_log("force_unload: module %u '%s'", m->id, m->name);
// drain refcount to 1 then free (which decrements to 0). same pattern
// as unload_all; safe even on a wedged guest because poison stops new calls.
while (InterlockedCompareExchange(&m->refcount, 1, m->refcount) != 1) {
if (!m->used) break;
}
gvm_modtab_free(m);
irp->IoStatus.Information = 0;
return STATUS_SUCCESS;
}
+22
View File
@@ -0,0 +1,22 @@
@echo off
setlocal
cd /d "%~dp0"
set CLANG=%LLVM_HOME%\bin\clang.exe
if not exist "%CLANG%" set CLANG=C:\Program Files\LLVM\bin\clang.exe
if not exist "%CLANG%" (
echo [X] clang not found. install LLVM or set LLVM_HOME.
exit /b 1
)
set CFLAGS=-O2 --target=wasm32 -nostdlib -fno-builtin -I..\guest_sdk
set LFLAGS=-Wl,--no-entry -Wl,--export-dynamic -Wl,--allow-undefined -Wl,--strip-all
call :one toolkit
call :one process_tracer
exit /b 0
:one
echo [*] %~1
"%CLANG%" %CFLAGS% %LFLAGS% -o %~1.wasm %~1.c
exit /b 0
+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0007}</ProjectGuid>
<RootNamespace>feature_guests</RootNamespace>
<Keyword>MakeFileProj</Keyword>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<NMakeBuildCommandLine>call "$(MSBuildProjectDirectory)\build_all.cmd"</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>call "$(MSBuildProjectDirectory)\build_all.cmd"</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>del /Q "$(MSBuildProjectDirectory)\*.wasm" 2&gt;nul</NMakeCleanCommandLine>
<NMakeOutput>toolkit.wasm;process_tracer.wasm</NMakeOutput>
<NMakeIncludeSearchPath>..\guest_sdk;$(NMakeIncludeSearchPath)</NMakeIncludeSearchPath>
</PropertyGroup>
<ItemGroup>
<ClCompile Include="toolkit.c" />
<ClCompile Include="process_tracer.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\guest_sdk\gvm.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Target Name="StageToDeploy" AfterTargets="Build">
<PropertyGroup>
<DeployDir>$(SolutionDir)deploy</DeployDir>
</PropertyGroup>
<Message Text="[deploy] staging feature guests -&gt; $(DeployDir)\features" Importance="high" />
<MakeDir Directories="$(DeployDir)\features" />
<MakeDir Directories="$(DeployDir)\gui" />
<ItemGroup>
<FeatureWasm Include="$(MSBuildProjectDirectory)\toolkit.wasm;$(MSBuildProjectDirectory)\process_tracer.wasm" />
</ItemGroup>
<Copy SourceFiles="@(FeatureWasm)" DestinationFolder="$(DeployDir)\features" SkipUnchangedFiles="true" ContinueOnError="true" />
<Copy SourceFiles="$(MSBuildProjectDirectory)\toolkit.wasm" DestinationFolder="$(DeployDir)\gui" SkipUnchangedFiles="true" ContinueOnError="true" />
</Target>
</Project>
+119
View File
@@ -0,0 +1,119 @@
/* process_tracer.c - live process create/exit + image load stream.
*
* TWO invocation models are demonstrated here:
*
* 1) polled: call start() once, then call poll() repeatedly. poll() drains
* the ring buffer and prints every event.
*
* 2) reactive: call start_reactive(). the driver's dispatch worker will
* invoke on_process_create() / on_process_exit() / on_image_load() below
* directly from the kernel notify callback. no polling.
*/
#include "gvm.h"
GVM_MANIFEST(GVM_CAP_ALLOC | GVM_CAP_CALLBACKS | GVM_CAP_INTROSPECT);
static void print_evt(const gvm_event* e)
{
char line[160]; char t[32]; u32 n = 0;
switch (e->kind) {
case 0:
n += gvm_strcpy(line + n, "PROC_CREATE pid=");
gvm_dec(e->pid, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " ppid=");
gvm_dec(e->ppid, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " ");
n += gvm_strcpy(line + n, e->name);
break;
case 1:
n += gvm_strcpy(line + n, "PROC_EXIT pid=");
gvm_dec(e->pid, t); n += gvm_strcpy(line + n, t);
break;
case 2:
n += gvm_strcpy(line + n, "IMAGE_LOAD pid=");
gvm_dec(e->pid, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " base=0x");
gvm_hex64(e->image_base, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " ");
n += gvm_strcpy(line + n, e->name);
break;
default:
n += gvm_strcpy(line + n, "unknown event");
break;
}
gvm_print(line);
}
GVM_EXPORT(start)
u64 start(void)
{
u32 a = gvm_notify_enable(0);
u32 b = gvm_notify_enable(1);
gvm_print(a == 0 ? "process notify: on" : "process notify: FAILED");
gvm_print(b == 0 ? "image notify: on" : "image notify: FAILED");
return (u64)a | ((u64)b << 32);
}
GVM_EXPORT(start_reactive)
u64 start_reactive(void)
{
u32 a = gvm_notify_enable(0);
u32 b = gvm_notify_enable(1);
u32 d = gvm_dispatch_start();
gvm_print(d == 0 ? "reactive dispatch: on" : "reactive dispatch: FAILED");
return ((u64)a) | ((u64)b << 8) | ((u64)d << 16);
}
GVM_EXPORT(poll)
u64 poll(void)
{
gvm_event e;
u32 count = 0;
while (gvm_notify_poll((u32)(u64)&e, sizeof(e)) == sizeof(e)) {
print_evt(&e);
count++;
}
return count;
}
// reactive callbacks: driver-side dispatch worker invokes these directly.
// signature is fixed: (pid, aux1, aux2) where aux1 is ppid or image_base,
// aux2 is 0 or image_size depending on event.
GVM_EXPORT(on_process_create)
u64 on_process_create(u64 pid, u64 ppid, u64 _unused)
{
(void)_unused;
char line[80]; char t[32]; u32 n = 0;
n += gvm_strcpy(line + n, "[reactive] PROC_CREATE pid=");
gvm_dec((u32)pid, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " ppid=");
gvm_dec((u32)ppid, t); n += gvm_strcpy(line + n, t);
gvm_print(line);
return 0;
}
GVM_EXPORT(on_process_exit)
u64 on_process_exit(u64 pid, u64 _a1, u64 _a2)
{
(void)_a1; (void)_a2;
char line[80]; char t[32]; u32 n = 0;
n += gvm_strcpy(line + n, "[reactive] PROC_EXIT pid=");
gvm_dec((u32)pid, t); n += gvm_strcpy(line + n, t);
gvm_print(line);
return 0;
}
GVM_EXPORT(on_image_load)
u64 on_image_load(u64 pid, u64 base, u64 size)
{
char line[128]; char t[32]; u32 n = 0;
n += gvm_strcpy(line + n, "[reactive] IMAGE_LOAD pid=");
gvm_dec((u32)pid, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " base=0x");
gvm_hex64(base, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " size=");
gvm_dec((u32)size, t); n += gvm_strcpy(line + n, t);
gvm_print(line);
return 0;
}
+138
View File
@@ -0,0 +1,138 @@
/* toolkit.c - frida-style primitives for the GUI to drive
*
* exposes a set of exports that the GUI calls to read/write kernel state.
* results that don't fit in a u64 are written into the guest's own linear
* memory at a caller-provided offset, and the GUI pulls them back with the
* new IOCTL_GVM_READ_GUEST ioctl.
*/
#include "../guest_sdk/gvm.h"
// declare all caps we might exercise
GVM_MANIFEST(GVM_CAP_ALLOC | GVM_CAP_READ_KMEM | GVM_CAP_WRITE_KMEM |
GVM_CAP_MSR_READ | GVM_CAP_MSR_WRITE | GVM_CAP_PHYSMEM |
GVM_CAP_CPUID_TSC | GVM_CAP_HOSTCALL | GVM_CAP_INTROSPECT);
// scratch buffer accessible from linear memory; GUI reads bytes out of here
static u8 g_scratch[16384];
// forces the scratch buffer to appear in linear memory so its address is
// stable across calls. return its offset so the GUI knows where to read from.
GVM_EXPORT(toolkit_scratch_off)
u32 toolkit_scratch_off(void)
{
return (u32)(unsigned long long)&g_scratch[0];
}
GVM_EXPORT(toolkit_scratch_size)
u32 toolkit_scratch_size(void) { return sizeof(g_scratch); }
// KERNEL VIRTUAL MEMORY
GVM_EXPORT(toolkit_read_kmem)
u32 toolkit_read_kmem(u64 va, u32 guest_off, u32 len)
{
return gvm_read_bytes(va, guest_off, len);
}
GVM_EXPORT(toolkit_read_u8)
u32 toolkit_read_u8(u64 va) { return gvm_read_u8(va); }
GVM_EXPORT(toolkit_read_u32)
u32 toolkit_read_u32(u64 va) { return gvm_read_u32(va); }
GVM_EXPORT(toolkit_read_u64)
u64 toolkit_read_u64(u64 va) { return gvm_read_u64(va); }
// PHYSICAL MEMORY
GVM_EXPORT(toolkit_read_phys)
u32 toolkit_read_phys(u64 pa, u32 guest_off, u32 len)
{
return gvm_phys_read(pa, guest_off, len);
}
// MSR
GVM_EXPORT(toolkit_readmsr)
u64 toolkit_readmsr(u32 idx) { return gvm_readmsr(idx); }
// CPUID - writes eax,ebx,ecx,edx (4 x u32) at guest_off
GVM_EXPORT(toolkit_cpuid)
void toolkit_cpuid(u32 leaf, u32 sub, u32 guest_off)
{
gvm_cpuid(leaf, sub, guest_off);
}
// RDTSC
GVM_EXPORT(toolkit_rdtsc)
u64 toolkit_rdtsc(void) { return gvm_rdtsc(); }
// EXPORT LOOKUP - returns kernel VA of an ntoskrnl/hal export by name
// name_off in linear memory, name_len bytes (nul terminator NOT required)
GVM_EXPORT(toolkit_resolve)
u64 toolkit_resolve(u32 name_off, u32 name_len)
{
if (name_len == 0 || name_len > 128) return 0;
// scratch a copy so we can nul-terminate cleanly
static char buf[144];
for (u32 i = 0; i < name_len && i < sizeof(buf)-1; i++) {
buf[i] = ((char*)0)[name_off + i];
}
buf[name_len < sizeof(buf) ? name_len : sizeof(buf)-1] = 0;
// use host_call with resolve helper: convention is fn_name = "MmGetSystemRoutineAddress",
// arg0 = pointer to UNICODE_STRING. Building UNICODE_STRING is fragile from wasm.
// Simpler: we do it host-side. Return 0 here for now, GUI can compare with a
// dedicated api if needed. This is a placeholder for future extension.
return 0;
}
// INTROSPECTION
GVM_EXPORT(toolkit_current_process)
u64 toolkit_current_process(void) { return gvm_current_process(); }
GVM_EXPORT(toolkit_current_pid)
u32 toolkit_current_pid(void) { return gvm_process_id(); }
GVM_EXPORT(toolkit_current_tid)
u32 toolkit_current_tid(void) { return gvm_thread_id(); }
GVM_EXPORT(toolkit_current_irql)
u32 toolkit_current_irql(void) { return gvm_current_irql(); }
// SIMPLE PROCESS ENUM - walks ActiveProcessLinks from PsGetCurrentProcess()
// writes packed records: [u64 pid][u64 eprocess][char name[16]] each
// returns count of records written. cap = max records the buffer can hold.
//
// offsets are Win10/11 x64. hard-coded here for demo. real toolkit would
// use signatures to derive them at load.
#define EPROC_ACTIVE_LINKS 0x448 // _EPROCESS.ActiveProcessLinks
#define EPROC_UNIQUE_PID 0x440 // _EPROCESS.UniqueProcessId
#define EPROC_IMAGE_NAME 0x5a8 // _EPROCESS.ImageFileName
GVM_EXPORT(toolkit_enum_procs)
u32 toolkit_enum_procs(u32 guest_off, u32 cap)
{
if (cap == 0) return 0;
u64 self = gvm_current_process();
if (!self) return 0;
u64 head = self + EPROC_ACTIVE_LINKS;
u64 cur = head;
u32 count = 0;
for (u32 iter = 0; iter < 4096 && count < cap; iter++) {
cur = gvm_read_u64(cur); // follow Flink
if (!cur || cur == head) break;
u64 eproc = cur - EPROC_ACTIVE_LINKS;
u64 pid = gvm_read_u64(eproc + EPROC_UNIQUE_PID);
// read record into scratch at guest_off + count*32
u32 rec_off = guest_off + count * 32;
*(u64*)((char*)0 + rec_off) = pid;
*(u64*)((char*)0 + rec_off + 8) = eproc;
// 16 bytes of image name
gvm_read_bytes(eproc + EPROC_IMAGE_NAME, rec_off + 16, 15);
((char*)0)[rec_off + 31] = 0;
count++;
}
return count;
}
+249
View File
@@ -0,0 +1,249 @@
/* gvm.h - Goodmans guest SDK */
#pragma once
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef signed char i8;
typedef signed short i16;
typedef signed int i32;
typedef signed long long i64;
typedef u32 size_t;
typedef u8 BOOLEAN;
typedef u8 UCHAR;
typedef u16 USHORT;
typedef u32 ULONG;
typedef u64 ULONGLONG;
typedef i32 LONG;
typedef i64 LONGLONG;
typedef u64 SIZE_T;
typedef i32 NTSTATUS;
typedef u64 HANDLE;
typedef u64 PVOID;
#define TRUE 1
#define FALSE 0
#define NULL ((PVOID)0)
#define NonPagedPool 0
#define PagedPool 1
#define NonPagedPoolNx 512
#define PASSIVE_LEVEL 0
#define APC_LEVEL 1
#define DISPATCH_LEVEL 2
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
#define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002L)
#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000DL)
#define STATUS_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC000009AL)
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
// import from the driver's env namespace: custom host_* helpers, plus any
// nt/hal export the driver auto-resolves via MmGetSystemRoutineAddress.
#define GVM_IMPORT(name) __attribute__((import_module("env"), import_name(#name)))
// alias for readability when importing a native kernel export directly
#define GVM_IMPORT_KERNEL(name) GVM_IMPORT(name)
// import from another loaded driver's export table. usage:
// GVM_IMPORT_DRV("vgk", SomeExport) u64 SomeExport(u64 a);
#define GVM_IMPORT_DRV(mod, name) __attribute__((import_module("drv$" mod), import_name(#name)))
#define GVM_EXPORT(name) __attribute__((export_name(#name)))
// buffer / memory / debug primitives (need wasm-linear-memory access)
GVM_IMPORT(host_dbg_print) void gvm_dbg_print_raw(const char* msg, u32 len);
GVM_IMPORT(host_read_bytes) u32 gvm_read_bytes(u64 kaddr, u32 guest_off, u32 len);
GVM_IMPORT(host_write_bytes) u32 gvm_write_bytes(u64 kaddr, u32 guest_off, u32 len);
GVM_IMPORT(host_cpuid) void gvm_cpuid(u32 leaf, u32 subleaf, u32 out_off);
GVM_IMPORT(host_phys_read) u32 gvm_phys_read(u64 pa, u32 guest_off, u32 len);
GVM_IMPORT(host_phys_write) u32 gvm_phys_write(u64 pa, u32 guest_off, u32 len);
// scalar primitives (thin one-line wrappers exposed via direct FFI)
GVM_IMPORT(host_current_irql) u32 gvm_current_irql(void);
GVM_IMPORT(host_process_id) u32 gvm_process_id(void);
GVM_IMPORT(host_thread_id) u32 gvm_thread_id(void);
GVM_IMPORT(host_current_process) u64 gvm_current_process(void);
GVM_IMPORT(host_read_u8) u32 gvm_read_u8(u64 kaddr);
GVM_IMPORT(host_read_u32) u32 gvm_read_u32(u64 kaddr);
GVM_IMPORT(host_read_u64) u64 gvm_read_u64(u64 kaddr);
GVM_IMPORT(host_write_u64) void gvm_write_u64(u64 kaddr, u64 val);
GVM_IMPORT(host_readmsr) u64 gvm_readmsr(u32 idx);
GVM_IMPORT(host_writemsr) u32 gvm_writemsr(u32 idx, u64 val);
GVM_IMPORT(host_rdtsc) u64 gvm_rdtsc(void);
// callback dispatch
GVM_IMPORT(host_notify_enable) u32 gvm_notify_enable(u32 kind);
GVM_IMPORT(host_notify_poll) u32 gvm_notify_poll(u32 out_off, u32 out_len);
GVM_IMPORT(host_dispatch_start) u32 gvm_dispatch_start(void);
GVM_IMPORT(host_dispatch_stop) u32 gvm_dispatch_stop(void);
// Native trampoline for InfinityHook. guest resolves the WMI_LOGGER_CONTEXT
// GetCpuClock slot itself (via kernel FFI) and writes this address into it.
// see sample_guest/infinity_hook.c for the full port.
GVM_IMPORT(host_ih_trampoline) u64 gvm_ih_trampoline(void);
GVM_IMPORT(host_ih_configure) void gvm_ih_configure(u32 rate, u64 nt_base, u32 nt_size);
GVM_IMPORT(host_ih_quiesce) u64 gvm_ih_quiesce(void);
// wasm-linear-memory <-> real-VA bridge for FFI pointer args
//
// host_mem_base returns the kernel VA of the guest's linear memory base.
// build real pointers into your own buffers with gvm_kva(p):
//
// char buf[64];
// RtlZeroMemory(gvm_kva(buf), sizeof(buf));
GVM_IMPORT(host_mem_base) u64 host_mem_base(void);
GVM_IMPORT(host_mem_size) u32 host_mem_size(void);
static inline u64 gvm_kva(const void* p)
{
return host_mem_base() + (u64)(u32)(unsigned long)p;
}
// build a real UNICODE_STRING in non-paged pool from a UTF-16 string in guest
// memory. returns kernel VA usable directly as PUNICODE_STRING. free after.
GVM_IMPORT(host_make_unistr) u64 host_make_unistr(u32 str_off, u32 byte_len);
GVM_IMPORT(host_free_unistr) void host_free_unistr(u64 kva);
// dynamic export dispatcher. use only when the target isn't known at compile
// time; direct GVM_IMPORT_KERNEL declarations are faster and traced by name.
GVM_IMPORT(host_call) u64 __gvm_host_call(
const char* name, u32 name_len, u32 argc,
u64 a0, u64 a1, u64 a2, u64 a3,
u64 a4, u64 a5, u64 a6, u64 a7);
#define GVM_CALL0(name) __gvm_host_call(#name, sizeof(#name)-1, 0, 0,0,0,0,0,0,0,0)
#define GVM_CALL1(name, a) __gvm_host_call(#name, sizeof(#name)-1, 1, (u64)(a),0,0,0,0,0,0,0)
#define GVM_CALL2(name, a,b) __gvm_host_call(#name, sizeof(#name)-1, 2, (u64)(a),(u64)(b),0,0,0,0,0,0)
#define GVM_CALL3(name, a,b,c) __gvm_host_call(#name, sizeof(#name)-1, 3, (u64)(a),(u64)(b),(u64)(c),0,0,0,0,0)
#define GVM_CALL4(name, a,b,c,d) __gvm_host_call(#name, sizeof(#name)-1, 4, (u64)(a),(u64)(b),(u64)(c),(u64)(d),0,0,0,0)
#define GVM_CALL5(name, a,b,c,d,e) __gvm_host_call(#name, sizeof(#name)-1, 5, (u64)(a),(u64)(b),(u64)(c),(u64)(d),(u64)(e),0,0,0)
#define GVM_CALL6(name, a,b,c,d,e,f) __gvm_host_call(#name, sizeof(#name)-1, 6, (u64)(a),(u64)(b),(u64)(c),(u64)(d),(u64)(e),(u64)(f),0,0)
#define GVM_CALL7(name, a,b,c,d,e,f,g) __gvm_host_call(#name, sizeof(#name)-1, 7, (u64)(a),(u64)(b),(u64)(c),(u64)(d),(u64)(e),(u64)(f),(u64)(g),0)
#define GVM_CALL8(name, a,b,c,d,e,f,g,h) __gvm_host_call(#name, sizeof(#name)-1, 8, (u64)(a),(u64)(b),(u64)(c),(u64)(d),(u64)(e),(u64)(f),(u64)(g),(u64)(h))
// direct kernel imports. these go through the FFI trampoline: no name
// lookup at call time, args pass straight into the kernel via x64 ABI.
GVM_IMPORT_KERNEL(ExAllocatePool2) PVOID ExAllocatePool2(u64 flags, SIZE_T size, u32 tag);
GVM_IMPORT_KERNEL(ExAllocatePoolWithTag) PVOID ExAllocatePoolWithTag(u32 type, SIZE_T size, u32 tag);
GVM_IMPORT_KERNEL(ExFreePool) void ExFreePool(PVOID p);
GVM_IMPORT_KERNEL(ExFreePoolWithTag) void ExFreePoolWithTag(PVOID p, u32 tag);
GVM_IMPORT_KERNEL(PsGetCurrentProcessId) HANDLE PsGetCurrentProcessId(void);
GVM_IMPORT_KERNEL(PsGetCurrentThreadId) HANDLE PsGetCurrentThreadId(void);
GVM_IMPORT_KERNEL(IoGetCurrentProcess) PVOID IoGetCurrentProcess(void);
GVM_IMPORT_KERNEL(KeGetCurrentThread) PVOID KeGetCurrentThread(void);
GVM_IMPORT_KERNEL(KeGetCurrentIrql) u8 KeGetCurrentIrql(void);
GVM_IMPORT_KERNEL(KeGetCurrentProcessorNumber) u32 KeGetCurrentProcessorNumber(void);
GVM_IMPORT_KERNEL(MmIsAddressValid) BOOLEAN MmIsAddressValid(PVOID p);
GVM_IMPORT_KERNEL(MmGetSystemRoutineAddress) PVOID MmGetSystemRoutineAddress(PVOID us);
GVM_IMPORT_KERNEL(PsGetProcessId) HANDLE PsGetProcessId(PVOID eproc);
GVM_IMPORT_KERNEL(PsGetProcessImageFileName) PVOID PsGetProcessImageFileName(PVOID eproc);
GVM_IMPORT_KERNEL(PsGetProcessInheritedFromUniqueProcessId) HANDLE PsGetProcessInheritedFromUniqueProcessId(PVOID eproc);
GVM_IMPORT_KERNEL(PsLookupProcessByProcessId) NTSTATUS PsLookupProcessByProcessId(HANDLE pid, PVOID* out_eproc);
GVM_IMPORT_KERNEL(ObDereferenceObject) void ObDereferenceObject(PVOID obj);
GVM_IMPORT_KERNEL(ZwClose) NTSTATUS ZwClose(HANDLE h);
GVM_IMPORT_KERNEL(ZwQuerySystemInformation) NTSTATUS ZwQuerySystemInformation(u32 cls, PVOID buf, u32 len, u32* got);
GVM_IMPORT_KERNEL(RtlZeroMemory) void RtlZeroMemory(PVOID dst, SIZE_T len);
GVM_IMPORT_KERNEL(RtlCopyMemory) void RtlCopyMemory(PVOID dst, PVOID src, SIZE_T len);
GVM_IMPORT_KERNEL(RtlCompareMemory) SIZE_T RtlCompareMemory(PVOID a, PVOID b, SIZE_T len);
GVM_IMPORT_KERNEL(KeStallExecutionProcessor) void KeStallExecutionProcessor(u32 us);
GVM_IMPORT_KERNEL(KeQueryPerformanceCounter) u64 KeQueryPerformanceCounter(u64* freq_out);
// gvm_event layout must match driver's gvm_event struct in host_imports.c
typedef struct {
u32 kind; // 0=proc create, 1=proc exit, 2=image load
u32 pid;
u32 ppid;
u32 flags;
u64 eprocess;
u64 image_base;
u32 image_size;
u32 _pad;
char name[80];
} gvm_event;
// tiny string helpers
static inline u32 gvm_strlen(const char* s)
{
u32 n = 0;
while (s[n]) n++;
return n;
}
static inline void gvm_print(const char* s)
{
gvm_dbg_print_raw(s, gvm_strlen(s));
}
static inline void gvm_hex64(u64 v, char* out17)
{
static const char h[] = "0123456789ABCDEF";
for (int i = 15; i >= 0; i--) { out17[i] = h[v & 0xF]; v >>= 4; }
out17[16] = 0;
}
static inline void gvm_dec(u32 v, char* out)
{
if (v == 0) { out[0] = '0'; out[1] = 0; return; }
char buf[16]; int n = 0;
while (v) { buf[n++] = (char)('0' + (v % 10)); v /= 10; }
for (int i = 0; i < n; i++) out[i] = buf[n - 1 - i];
out[n] = 0;
}
static inline int gvm_streq(const char* a, const char* b)
{
while (*a && *a == *b) { a++; b++; }
return *a == *b;
}
static inline int gvm_stricmp(const char* a, const char* b)
{
while (*a && *b) {
char ca = *a, cb = *b;
if (ca >= 'A' && ca <= 'Z') ca += 32;
if (cb >= 'A' && cb <= 'Z') cb += 32;
if (ca != cb) return ca - cb;
a++; b++;
}
return *a - *b;
}
static inline u32 gvm_strcpy(char* dst, const char* src)
{
u32 n = 0;
while ((dst[n] = src[n]) != 0) n++;
return n;
}
// capability manifest. embed in the guest to lock down which host imports
// the driver will let you call. see GVM_CAP_* below and shared/goodmans_ioctl.h.
// example: GVM_MANIFEST(GVM_CAP_ALLOC | GVM_CAP_READ_KMEM | GVM_CAP_HOSTCALL);
#define GVM_CAP_ALLOC (1u << 0)
#define GVM_CAP_READ_KMEM (1u << 1)
#define GVM_CAP_WRITE_KMEM (1u << 2)
#define GVM_CAP_MSR_READ (1u << 3)
#define GVM_CAP_MSR_WRITE (1u << 4)
#define GVM_CAP_PHYSMEM (1u << 5)
#define GVM_CAP_CPUID_TSC (1u << 6)
#define GVM_CAP_CALLBACKS (1u << 7)
#define GVM_CAP_HOSTCALL (1u << 8)
#define GVM_CAP_INTROSPECT (1u << 9)
#define GVM_CAP_ALL 0xFFFFFFFFu
#define GVM_MANIFEST(caps) \
GVM_EXPORT(__gvm_caps) \
u32 __gvm_caps(void) { return (u32)(caps); }
+47
View File
@@ -0,0 +1,47 @@
cmake_minimum_required(VERSION 3.20)
project(goodmans_gui LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_PREFIX_PATH "C:/Qt/6.9.3/msvc2022_64")
find_package(Qt6 REQUIRED COMPONENTS Widgets Gui Core)
add_executable(goodmans-gui WIN32
main.cpp
mainwindow.cpp
mainwindow.h
driver.cpp
driver.h
pages.cpp
pages.h
command_palette.cpp
command_palette.h
resources.qrc
)
target_include_directories(goodmans-gui PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../shared"
)
target_link_libraries(goodmans-gui PRIVATE Qt6::Widgets Qt6::Gui Qt6::Core)
if (WIN32)
get_target_property(_qmake_exe Qt6::qmake IMPORTED_LOCATION)
get_filename_component(_qt_bin "${_qmake_exe}" DIRECTORY)
set(DEPLOY_GUI "${CMAKE_CURRENT_SOURCE_DIR}/../deploy/gui")
# windeployqt straight into deploy/gui so we deploy once, not twice
add_custom_command(TARGET goodmans-gui POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${DEPLOY_GUI}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:goodmans-gui> "${DEPLOY_GUI}/"
COMMAND "${_qt_bin}/windeployqt.exe" --no-translations --no-opengl-sw --no-system-d3d-compiler "${DEPLOY_GUI}/goodmans-gui.exe"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_SOURCE_DIR}/../feature_guests/toolkit.wasm"
"${DEPLOY_GUI}/toolkit.wasm"
COMMENT "[deploy] staging gui -> deploy/gui"
)
endif()
+144
View File
@@ -0,0 +1,144 @@
#include "command_palette.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QStyledItemDelegate>
// Fuzzy sort proxy: match if all chars in query appear in order in target.
// Scoring: fewer gaps + earlier match wins. Used to power the "type anywhere" flow.
class FuzzyProxy : public QSortFilterProxyModel {
public:
explicit FuzzyProxy(QObject* p = nullptr) : QSortFilterProxyModel(p) { setDynamicSortFilter(true); }
void setQuery(const QString& q) { query = q.toLower(); invalidateFilter(); invalidate(); }
protected:
bool filterAcceptsRow(int row, const QModelIndex&) const override {
if (query.isEmpty()) return true;
QString s = sourceModel()->data(sourceModel()->index(row, 0)).toString().toLower();
int qi = 0;
for (int i = 0; i < s.size() && qi < query.size(); i++) if (s[i] == query[qi]) qi++;
return qi == query.size();
}
bool lessThan(const QModelIndex& l, const QModelIndex& r) const override {
if (query.isEmpty()) return l.row() < r.row();
auto score = [&](const QString& s)->int {
int qi = 0, first = -1, gaps = 0, prev = -100;
QString low = s.toLower();
for (int i = 0; i < low.size() && qi < query.size(); i++) {
if (low[i] == query[qi]) {
if (first == -1) first = i;
if (prev != i - 1) gaps++;
prev = i; qi++;
}
}
return first * 4 + gaps * 8 + s.size();
};
int sl = score(sourceModel()->data(l).toString());
int sr = score(sourceModel()->data(r).toString());
return sl < sr;
}
private:
QString query;
};
class PaletteDelegate : public QStyledItemDelegate {
public:
using QStyledItemDelegate::QStyledItemDelegate;
QSize sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const override { return {0, 34}; }
void paint(QPainter* p, const QStyleOptionViewItem& o, const QModelIndex& idx) const override {
p->save();
if (o.state & QStyle::State_Selected) p->fillRect(o.rect, QColor(0x2a, 0x21, 0x17));
auto full = idx.data().toString();
auto hint = idx.data(Qt::UserRole + 1).toString();
p->setPen((o.state & QStyle::State_Selected) ? QColor(0xf0, 0xa3, 0x40) : QColor(0xd4, 0xd7, 0xdd));
QFont f = p->font(); f.setPointSizeF(11.5); p->setFont(f);
p->drawText(QRect(o.rect.left() + 16, o.rect.top(), o.rect.width() - 200, o.rect.height()),
Qt::AlignVCenter | Qt::AlignLeft, full);
if (!hint.isEmpty()) {
p->setPen(QColor(0x6a, 0x72, 0x80));
QFont hf = f; hf.setPointSizeF(10.5); hf.setFamily("JetBrainsMono NF"); p->setFont(hf);
p->drawText(QRect(o.rect.right() - 180, o.rect.top(), 164, o.rect.height()),
Qt::AlignVCenter | Qt::AlignRight, hint);
}
p->restore();
}
};
CommandPalette::CommandPalette(QWidget* parent, const QVector<CommandAction>& actions)
: QDialog(parent), all_actions(actions)
{
setWindowFlags(Qt::Popup | Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground, false);
setStyleSheet(
"QDialog { background: #14171c; border: 1px solid #363c47; border-radius: 10px; }"
"QLineEdit { background: transparent; border: none; border-bottom: 1px solid #262a33;"
" padding: 14px 18px; font-size: 15px; color: #f0f2f7; }"
"QListView { background: transparent; border: none; outline: 0; }"
);
resize(640, 420);
auto* l = new QVBoxLayout(this);
l->setContentsMargins(0, 0, 0, 0);
l->setSpacing(0);
search = new QLineEdit;
search->setPlaceholderText("Type a command…");
l->addWidget(search);
model = new QStandardItemModel(this);
for (const auto& a : all_actions) {
auto* it = new QStandardItem(a.label);
it->setData(a.hint, Qt::UserRole + 1);
model->appendRow(it);
}
proxy = new FuzzyProxy(this);
proxy->setSourceModel(model);
list = new QListView;
list->setModel(proxy);
list->setItemDelegate(new PaletteDelegate(this));
list->setUniformItemSizes(true);
list->setSelectionMode(QAbstractItemView::SingleSelection);
l->addWidget(list, 1);
if (proxy->rowCount() > 0)
list->setCurrentIndex(proxy->index(0, 0));
connect(search, &QLineEdit::textChanged, this, [this](const QString& t) {
static_cast<FuzzyProxy*>(proxy)->setQuery(t);
proxy->sort(0);
if (proxy->rowCount() > 0) list->setCurrentIndex(proxy->index(0, 0));
});
connect(list, &QListView::activated, this, [this](const QModelIndex&) { runSelected(); });
search->setFocus();
}
void CommandPalette::runSelected()
{
auto idx = list->currentIndex();
if (!idx.isValid()) return;
auto src = proxy->mapToSource(idx);
int i = src.row();
if (i < 0 || i >= all_actions.size()) return;
auto fn = all_actions[i].run;
accept();
if (fn) fn();
}
void CommandPalette::keyPressEvent(QKeyEvent* e)
{
if (e->key() == Qt::Key_Escape) { reject(); return; }
if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { runSelected(); return; }
if (e->key() == Qt::Key_Up) {
auto cur = list->currentIndex().row();
if (cur > 0) list->setCurrentIndex(proxy->index(cur - 1, 0));
return;
}
if (e->key() == Qt::Key_Down) {
auto cur = list->currentIndex().row();
if (cur < proxy->rowCount() - 1) list->setCurrentIndex(proxy->index(cur + 1, 0));
return;
}
QDialog::keyPressEvent(e);
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <QDialog>
#include <QLineEdit>
#include <QListWidget>
#include <QVBoxLayout>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QListView>
#include <QKeyEvent>
#include <functional>
struct CommandAction {
QString label;
QString hint; // shortcut or context
std::function<void()> run;
};
class CommandPalette : public QDialog {
Q_OBJECT
public:
CommandPalette(QWidget* parent, const QVector<CommandAction>& actions);
protected:
void keyPressEvent(QKeyEvent* e) override;
private:
QLineEdit* search;
QListView* list;
QStandardItemModel* model;
QSortFilterProxyModel* proxy;
QVector<CommandAction> all_actions;
void runSelected();
};
+295
View File
@@ -0,0 +1,295 @@
#include "driver.h"
#include <windows.h>
#include <winsvc.h>
#include <shellapi.h>
#include <QFile>
#include <QFileInfo>
#include <QDateTime>
#include "goodmans_ioctl.h"
DriverClient::DriverClient(QObject* p) : QObject(p) {}
static HANDLE dev_open()
{
return CreateFileA(GVM_USER_PATH, GENERIC_READ|GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
}
bool DriverClient::device_open() const
{
HANDLE h = dev_open();
if (h == INVALID_HANDLE_VALUE) return false;
CloseHandle(h);
return true;
}
bool DriverClient::refresh_modules(QVector<ModuleRow>& out)
{
out.clear();
HANDLE dev = dev_open();
if (dev == INVALID_HANDLE_VALUE) return false;
gvm_list_out r{};
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_LIST_MODULES, nullptr, 0, &r, sizeof(r), &ret, nullptr);
CloseHandle(dev);
if (!ok) return false;
for (unsigned int i = 0; i < r.count; i++) {
ModuleRow m;
m.id = r.entries[i].id;
m.wasm_size = r.entries[i].wasm_size;
m.mem_pages = r.entries[i].mem_pages;
m.pool_bytes = r.entries[i].pool_bytes;
m.hash = r.entries[i].hash;
m.name = QString::fromUtf8(r.entries[i].name);
out.push_back(m);
}
return true;
}
bool DriverClient::module_info(quint32 id, ModuleInfo& info)
{
info = {};
HANDLE dev = dev_open();
if (dev == INVALID_HANDLE_VALUE) return false;
gvm_info_in in{ id };
auto* out = (gvm_info_out*)calloc(1, sizeof(gvm_info_out));
if (!out) { CloseHandle(dev); return false; }
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_MODULE_INFO, &in, sizeof(in), out, sizeof(*out), &ret, nullptr);
CloseHandle(dev);
if (!ok || out->status != 0) { free(out); return false; }
info.base.id = out->base.id;
info.base.name = QString::fromUtf8(out->base.name);
info.base.wasm_size = out->base.wasm_size;
info.base.mem_pages = out->base.mem_pages;
info.base.pool_bytes = out->base.pool_bytes;
info.base.hash = out->base.hash;
info.caps = 0;
for (unsigned int i = 0; i < out->export_count && i < GVM_MAX_INFO_EXPORTS; i++)
info.exports.push_back(QString::fromUtf8(out->exports[i]));
for (unsigned int i = 0; i < out->import_count && i < GVM_MAX_INFO_IMPORTS; i++)
info.imports.push_back(QString::fromUtf8(out->imports[i]));
info.valid = true;
free(out);
return true;
}
bool DriverClient::load_module(const QString& path, quint64 budget, quint32& out_id, QString& err)
{
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) { err = "cannot open file"; return false; }
QByteArray data = f.readAll();
f.close();
if (data.isEmpty() || data.size() > (16 << 20)) { err = "bad file size"; return false; }
QByteArray buf; buf.resize(sizeof(gvm_load_in) + data.size());
auto* in = (gvm_load_in*)buf.data();
memset(in, 0, sizeof(*in));
in->wasm_size = (unsigned int)data.size();
in->pool_budget = budget;
QByteArray nameU8 = QFileInfo(path).fileName().toUtf8();
strncpy_s(in->name, GVM_MAX_MODULE_NAME, nameU8.constData(), _TRUNCATE);
memcpy(buf.data() + sizeof(gvm_load_in), data.constData(), data.size());
HANDLE dev = dev_open();
if (dev == INVALID_HANDLE_VALUE) { err = "device closed"; return false; }
gvm_load_out out{}; DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_LOAD_MODULE, buf.data(), (DWORD)buf.size(), &out, sizeof(out), &ret, nullptr);
CloseHandle(dev);
if (!ok) { err = "ioctl failed"; return false; }
if (out.status) { err = QString::fromUtf8(out.err_msg); return false; }
out_id = out.module_id;
return true;
}
bool DriverClient::call_export(quint32 id, const QString& exp, quint32 timeout, const QVector<quint64>& argv, quint64& rv, QString& err)
{
HANDLE dev = dev_open();
if (dev == INVALID_HANDLE_VALUE) { err = "device closed"; return false; }
gvm_call_in in{}; in.module_id = id; in.timeout_ms = timeout;
in.argc = (unsigned int)argv.size(); if (in.argc > GVM_MAX_ARGS) in.argc = GVM_MAX_ARGS;
for (unsigned int i = 0; i < in.argc; i++) in.argv[i] = argv[i];
QByteArray e = exp.toUtf8();
strncpy_s(in.export_name, e.constData(), _TRUNCATE);
gvm_call_out out{}; DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_CALL_EXPORT, &in, sizeof(in), &out, sizeof(out), &ret, nullptr);
CloseHandle(dev);
if (!ok) { err = "ioctl failed"; return false; }
if (out.status) { err = QString::fromUtf8(out.err_msg); return false; }
rv = out.rv;
return true;
}
bool DriverClient::unload(quint32 id)
{
HANDLE dev = dev_open();
if (dev == INVALID_HANDLE_VALUE) return false;
gvm_unload_in in{ id }; DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_UNLOAD_MODULE, &in, sizeof(in), nullptr, 0, &ret, nullptr);
CloseHandle(dev);
return ok;
}
bool DriverClient::unload_all()
{
HANDLE dev = dev_open();
if (dev == INVALID_HANDLE_VALUE) return false;
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_UNLOAD_ALL, nullptr, 0, nullptr, 0, &ret, nullptr);
CloseHandle(dev);
return ok;
}
static int classify_line(const QString& line)
{
if (line.contains("load module_id")) return EVT_LOAD;
if (line.contains("unload module")) return EVT_UNLOAD;
if (line.contains("call mod=")) return EVT_CALL;
if (line.contains("PROC_CREATE") || line.contains("proc create")) return EVT_PROC;
if (line.contains("PROC_EXIT") || line.contains("proc exit")) return EVT_EXIT;
if (line.contains("IMAGE_LOAD") || line.contains("image load")) return EVT_IMAGE;
if (line.contains("error", Qt::CaseInsensitive) || line.contains("ERR")) return EVT_ERROR;
return EVT_MSG;
}
bool DriverClient::tail_log(quint64& last_seq, QVector<LogEntry>& out_entries, quint32& dropped)
{
HANDLE dev = dev_open();
if (dev == INVALID_HANDLE_VALUE) return false;
gvm_tail_in in{ last_seq };
auto* out = (gvm_tail_out*)malloc(sizeof(gvm_tail_out));
if (!out) { CloseHandle(dev); return false; }
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_TAIL_LOG, &in, sizeof(in), out, sizeof(*out), &ret, nullptr);
CloseHandle(dev);
if (!ok) { free(out); return false; }
last_seq = out->next_seq;
dropped = out->dropped;
for (unsigned int i = 0; i < out->count; i++) {
SYSTEMTIME sys; FILETIME ft, lft;
ft.dwLowDateTime = (DWORD) out->entries[i].timestamp_100ns;
ft.dwHighDateTime = (DWORD)(out->entries[i].timestamp_100ns >> 32);
FileTimeToLocalFileTime(&ft, &lft);
FileTimeToSystemTime(&lft, &sys);
LogEntry e;
e.ts = QString::asprintf("%02u:%02u:%02u.%03u", sys.wHour, sys.wMinute, sys.wSecond, sys.wMilliseconds);
e.line = QString::fromUtf8(out->entries[i].line);
e.kind = classify_line(e.line);
out_entries.push_back(e);
}
free(out);
return true;
}
bool DriverClient::service_present() const
{
bool present = false;
SC_HANDLE scm = OpenSCManagerA(nullptr, nullptr, SC_MANAGER_CONNECT);
if (!scm) return false;
SC_HANDLE svc = OpenServiceA(scm, "Goodmans", SERVICE_QUERY_STATUS);
if (svc) { present = true; CloseServiceHandle(svc); }
CloseServiceHandle(scm);
return present;
}
bool DriverClient::service_running() const
{
bool running = false;
SC_HANDLE scm = OpenSCManagerA(nullptr, nullptr, SC_MANAGER_CONNECT);
if (!scm) return false;
SC_HANDLE svc = OpenServiceA(scm, "Goodmans", SERVICE_QUERY_STATUS);
if (svc) {
SERVICE_STATUS_PROCESS ss{}; DWORD needed = 0;
if (QueryServiceStatusEx(svc, SC_STATUS_PROCESS_INFO, (LPBYTE)&ss, sizeof(ss), &needed))
running = (ss.dwCurrentState == SERVICE_RUNNING);
CloseServiceHandle(svc);
}
CloseServiceHandle(scm);
return running;
}
void DriverClient::run_sc(const QString& args)
{
QByteArray a = args.toLocal8Bit();
SHELLEXECUTEINFOA si{ sizeof(si) };
si.lpVerb = "runas"; si.lpFile = "sc.exe"; si.lpParameters = a.constData();
si.nShow = SW_HIDE; si.fMask = SEE_MASK_NOCLOSEPROCESS;
if (ShellExecuteExA(&si) && si.hProcess) {
WaitForSingleObject(si.hProcess, 8000);
CloseHandle(si.hProcess);
}
}
bool DriverClient::read_guest(quint32 module_id, quint32 offset, quint32 len, QByteArray& out, QString& err)
{
HANDLE dev = dev_open();
if (dev == INVALID_HANDLE_VALUE) { err = "device closed"; return false; }
gvm_read_guest_in in{ module_id, offset, len };
auto* r = (gvm_read_guest_out*)calloc(1, sizeof(gvm_read_guest_out));
if (!r) { CloseHandle(dev); err = "oom"; return false; }
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_READ_GUEST, &in, sizeof(in), r, sizeof(*r), &ret, nullptr);
CloseHandle(dev);
if (!ok) { err = "ioctl failed"; free(r); return false; }
if (r->status) { err = QString::fromUtf8(r->err_msg); free(r); return false; }
out = QByteArray((const char*)r->data, (int)r->length);
free(r);
return true;
}
bool DriverClient::tail_trace(quint64& last_seq, QVector<TraceEvent>& out, quint32& dropped)
{
HANDLE dev = dev_open();
if (dev == INVALID_HANDLE_VALUE) return false;
gvm_trace_in in{ last_seq };
auto* r = (gvm_trace_out*)malloc(sizeof(gvm_trace_out));
if (!r) { CloseHandle(dev); return false; }
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_TAIL_TRACE, &in, sizeof(in), r, sizeof(*r), &ret, nullptr);
CloseHandle(dev);
if (!ok) { free(r); return false; }
last_seq = r->next_seq;
dropped = r->dropped;
for (unsigned int i = 0; i < r->count; i++) {
SYSTEMTIME sys; FILETIME ft, lft;
ft.dwLowDateTime = (DWORD) r->entries[i].timestamp_100ns;
ft.dwHighDateTime = (DWORD)(r->entries[i].timestamp_100ns >> 32);
FileTimeToLocalFileTime(&ft, &lft);
FileTimeToSystemTime(&lft, &sys);
TraceEvent e;
e.timestamp_100ns = r->entries[i].timestamp_100ns;
e.ts = QString::asprintf("%02u:%02u:%02u.%03u", sys.wHour, sys.wMinute, sys.wSecond, sys.wMilliseconds);
e.module_id = r->entries[i].module_id;
e.kind = r->entries[i].kind;
e.thread_id = r->entries[i].thread_id;
e.irql = r->entries[i].irql;
e.name = QString::fromUtf8(r->entries[i].name);
for (unsigned int a = 0; a < r->entries[i].argc && a < 4; a++)
e.argv.push_back(r->entries[i].argv[a]);
e.rv = r->entries[i].rv;
out.push_back(e);
}
free(r);
return true;
}
bool DriverClient::trace_ctl(int mode, quint32 module_id)
{
HANDLE dev = dev_open();
if (dev == INVALID_HANDLE_VALUE) return false;
gvm_trace_ctl_in in{ (unsigned int)mode, module_id };
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_TRACE_CTL, &in, sizeof(in), nullptr, 0, &ret, nullptr);
CloseHandle(dev);
return ok;
}
bool DriverClient::notify_stop()
{
HANDLE dev = dev_open();
if (dev == INVALID_HANDLE_VALUE) return false;
DWORD ret = 0;
BOOL ok = DeviceIoControl(dev, IOCTL_GVM_NOTIFY_STOP, nullptr, 0, nullptr, 0, &ret, nullptr);
CloseHandle(dev);
return ok;
}
+72
View File
@@ -0,0 +1,72 @@
#pragma once
#include <QObject>
#include <QString>
#include <QVector>
#include <QByteArray>
#include <cstdint>
struct ModuleRow {
quint32 id{};
QString name;
quint32 wasm_size{};
quint32 mem_pages{};
quint64 pool_bytes{};
quint64 hash{};
};
struct ModuleInfo {
ModuleRow base;
quint32 caps{0};
QVector<QString> exports;
QVector<QString> imports;
bool valid{false};
};
struct LogEntry {
QString ts;
QString line;
int kind{0};
};
enum LogKind { EVT_MSG, EVT_LOAD, EVT_CALL, EVT_UNLOAD, EVT_PROC, EVT_EXIT, EVT_IMAGE, EVT_ERROR };
// trace kinds mirror driver-side GVM_TRK_*
enum TraceKind { TRK_IMPORT = 1, TRK_CALL = 2, TRK_RETURN = 3, TRK_TRAP = 4 };
struct TraceEvent {
quint64 timestamp_100ns; // raw for delta-time math
QString ts; // human-readable
quint32 module_id;
quint32 kind;
quint32 thread_id;
quint32 irql;
QString name;
QVector<quint64> argv;
quint64 rv;
};
enum TraceMode { TRACE_OFF = 0, TRACE_ON_ALL = 1, TRACE_ON_MODULE = 2 };
class DriverClient : public QObject {
Q_OBJECT
public:
explicit DriverClient(QObject* parent = nullptr);
bool device_open() const;
bool refresh_modules(QVector<ModuleRow>& out);
bool module_info(quint32 id, ModuleInfo& out);
bool load_module(const QString& path, quint64 budget, quint32& out_id, QString& err);
bool call_export(quint32 id, const QString& exp, quint32 timeout_ms, const QVector<quint64>& argv, quint64& rv, QString& err);
bool unload(quint32 id);
bool unload_all();
bool tail_log(quint64& last_seq, QVector<LogEntry>& out, quint32& dropped);
bool service_present() const;
bool service_running() const;
void run_sc(const QString& args);
bool read_guest(quint32 module_id, quint32 offset, quint32 len, QByteArray& out, QString& err);
bool tail_trace(quint64& last_seq, QVector<TraceEvent>& out, quint32& dropped);
bool trace_ctl(int mode, quint32 module_id = 0);
bool notify_stop();
};
+25
View File
@@ -0,0 +1,25 @@
#include <QApplication>
#include <QFile>
#include <QFontDatabase>
#include <QStyleFactory>
#include "mainwindow.h"
int main(int argc, char** argv)
{
QApplication::setStyle("Fusion");
QApplication app(argc, argv);
app.setOrganizationName("goodmans");
app.setApplicationName("The Goodmans Kernel");
QFontDatabase::addApplicationFont(":/fonts/segoeui");
QFile qss(":/style.qss");
if (qss.open(QIODevice::ReadOnly | QIODevice::Text)) {
app.setStyleSheet(QString::fromUtf8(qss.readAll()));
qss.close();
}
MainWindow w;
w.show();
return app.exec();
}
+522
View File
@@ -0,0 +1,522 @@
#include "mainwindow.h"
#include "command_palette.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QFrame>
#include <QToolBar>
#include <QStatusBar>
#include <QAction>
#include <QMenuBar>
#include <QApplication>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QCoreApplication>
#include <QShortcut>
#include <QKeySequence>
#include <QCloseEvent>
#include <QDir>
#include <QDialog>
#include <QPushButton>
MainWindow::MainWindow()
{
drv = new DriverClient(this);
setWindowTitle("The Goodmans Kernel");
resize(1500, 940);
setMinimumSize(1100, 720);
// menu bar
auto* mb = menuBar();
auto* fileMenu = mb->addMenu("&File");
auto* actLoad = fileMenu->addAction("&Load module…");
actLoad->setShortcut(QKeySequence("Ctrl+O"));
connect(actLoad, &QAction::triggered, this, &MainWindow::load_wasm_from_dialog);
recentMenu = fileMenu->addMenu("Open &Recent");
fileMenu->addSeparator();
auto* actPal = fileMenu->addAction("Command &Palette…");
actPal->setShortcut(QKeySequence("Ctrl+K"));
connect(actPal, &QAction::triggered, this, &MainWindow::open_command_palette);
fileMenu->addSeparator();
auto* actQuit = fileMenu->addAction("E&xit");
actQuit->setShortcut(QKeySequence("Ctrl+Q"));
connect(actQuit, &QAction::triggered, qApp, &QApplication::quit);
auto* viewMenu = mb->addMenu("&View");
QStringList page_labels = { "Modules", "Explorer", "Trace", "Profiler", "Events", "Log", "Memory", "Deploy" };
for (int i = 0; i < page_labels.size(); i++) {
auto* a = viewMenu->addAction(QString("&%1 %2").arg(i + 1).arg(page_labels[i]));
a->setShortcut(QKeySequence(QString("Ctrl+%1").arg(i + 1)));
connect(a, &QAction::triggered, this, [this, i]{ go_to_page(i); });
}
viewMenu->addSeparator();
auto* actRefresh = viewMenu->addAction("&Refresh");
actRefresh->setShortcut(QKeySequence("F5"));
connect(actRefresh, &QAction::triggered, this, &MainWindow::tick_slow);
auto* driverMenu = mb->addMenu("&Driver");
auto* actStart = driverMenu->addAction("Start service");
connect(actStart, &QAction::triggered, this, [this]{ drv->run_sc("start Goodmans"); tick_slow(); });
auto* actStop = driverMenu->addAction("Stop service");
connect(actStop, &QAction::triggered, this, [this]{ drv->run_sc("stop Goodmans"); tick_slow(); });
auto* actRestart = driverMenu->addAction("Restart service");
connect(actRestart, &QAction::triggered, this, [this]{
drv->run_sc("stop Goodmans"); drv->run_sc("start Goodmans"); tick_slow();
});
auto* toolsMenu = mb->addMenu("&Tools");
auto* actPalT = toolsMenu->addAction("Command palette");
actPalT->setShortcut(QKeySequence("Ctrl+K"));
connect(actPalT, &QAction::triggered, this, &MainWindow::open_command_palette);
auto* helpMenu = mb->addMenu("&Help");
auto* actAbout = helpMenu->addAction("&About The Goodmans Kernel");
connect(actAbout, &QAction::triggered, this, &MainWindow::open_about);
// toolbar (x64dbg / IDA style row of textual actions)
auto* tbar = addToolBar("Main");
tbar->setObjectName("mainToolbar");
tbar->setMovable(false);
tbar->setFloatable(false);
tbar->addAction(actLoad);
auto* actUnloadAll = tbar->addAction("Unload all");
connect(actUnloadAll, &QAction::triggered, this, [this]{ drv->unload_all(); tick_slow(); });
tbar->addSeparator();
tbar->addAction(actStart);
tbar->addAction(actStop);
tbar->addAction(actRestart);
tbar->addSeparator();
tbar->addAction(actRefresh);
tbar->addSeparator();
tbar->addAction(actPalT);
// tab bar as main navigation
tabs = new QTabWidget;
tabs->setObjectName("mainTabs");
tabs->setDocumentMode(true);
tabs->setUsesScrollButtons(false);
setCentralWidget(tabs);
workbench = new WorkbenchPage(drv);
explorerp = new ExplorerPage(drv);
tracep = new TracePage(drv);
profilerp = new ProfilerPage;
events = new EventsPage(drv);
connect(events, &EventsPage::toast, this, &MainWindow::show_toast);
logp = new LogPage;
memory = new MemoryPage(drv);
deploy = new DeployPage(drv);
tabs->addTab(workbench, "Modules");
tabs->addTab(explorerp, "Explorer");
tabs->addTab(tracep, "Trace");
tabs->addTab(profilerp, "Profiler");
tabs->addTab(events, "Events");
tabs->addTab(logp, "Log");
tabs->addTab(memory, "Memory");
tabs->addTab(deploy, "Deploy");
tabs->setCurrentIndex(0);
// status bar (permanent widgets on the right, temporary toasts on the left)
statDevice = new QLabel;
statSvc = new QLabel;
statCount = new QLabel;
for (auto* l : { statDevice, statSvc, statCount }) l->setStyleSheet("font-size:11.5px;");
set_stat(statDevice, "driver ?", "#808080", "#666666");
set_stat(statSvc, "service ?", "#808080", "#666666");
set_stat(statCount, "0 modules", "#808080", "");
auto* sep1 = new QLabel(" | "); sep1->setStyleSheet("color:#444444;");
auto* sep2 = new QLabel(" | "); sep2->setStyleSheet("color:#444444;");
statusBar()->addPermanentWidget(statDevice);
statusBar()->addPermanentWidget(sep1);
statusBar()->addPermanentWidget(statSvc);
statusBar()->addPermanentWidget(sep2);
statusBar()->addPermanentWidget(statCount);
// wiring
connect(tabs, &QTabWidget::currentChanged, this, &MainWindow::on_nav_changed);
connect(workbench, &WorkbenchPage::moduleSelected, this, &MainWindow::on_module_selected);
connect(workbench, &WorkbenchPage::moduleLoaded, this, &MainWindow::on_module_loaded);
connect(workbench, &WorkbenchPage::toast, this, &MainWindow::show_toast);
connect(deploy, &DeployPage::stateChanged, this, &MainWindow::on_state_changed);
connect(deploy, &DeployPage::toast, this, &MainWindow::show_toast);
connect(memory, &MemoryPage::toast, this, &MainWindow::show_toast);
connect(tracep, &TracePage::toast, this, &MainWindow::show_toast);
connect(explorerp, &ExplorerPage::toast, this, &MainWindow::show_toast);
connect(&fast_timer, &QTimer::timeout, this, &MainWindow::tick_fast);
connect(&slow_timer, &QTimer::timeout, this, &MainWindow::tick_slow);
fast_timer.start(300);
slow_timer.start(2000);
// hot reload watcher (guest .wasm and driver .sys)
hot_watcher = new QFileSystemWatcher(this);
connect(hot_watcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::on_file_changed);
// auto-watch the driver binary too; on change: stop/start service
QString here_dir = QCoreApplication::applicationDirPath();
QStringList driver_candidates = {
here_dir + "/../Goodmans.sys", // same layout as deploy/
here_dir + "/../../Goodmans.sys",
};
for (const auto& p : driver_candidates) {
QString abs = QFileInfo(p).absoluteFilePath();
if (QFile::exists(abs)) { hot_watcher->addPath(abs); break; }
}
// global shortcuts
auto* palSc = new QShortcut(QKeySequence("Ctrl+K"), this);
connect(palSc, &QShortcut::activated, this, &MainWindow::open_command_palette);
auto* findSc = new QShortcut(QKeySequence("Ctrl+F"), this);
connect(findSc, &QShortcut::activated, this, [this]{
if (tabs->currentIndex() == 2) tracep->focusFilter();
});
load_settings();
rebuild_recent_menu();
tick_slow();
// always land on Modules on launch, regardless of saved page
tabs->setCurrentIndex(0);
}
void MainWindow::closeEvent(QCloseEvent* e)
{
save_settings();
QMainWindow::closeEvent(e);
}
void MainWindow::set_stat(QLabel* lbl, const QString& txt, const QString& color, const QString& dot_color)
{
if (!dot_color.isEmpty()) {
lbl->setText(QString("<span style='color:%1;'>●</span> <span style='color:%2;'>%3</span>")
.arg(dot_color, color, txt.toHtmlEscaped()));
} else {
lbl->setText(QString("<span style='color:%1;'>%2</span>").arg(color, txt.toHtmlEscaped()));
}
}
void MainWindow::on_nav_changed(int row)
{
if (row >= 0) tabs->setCurrentIndex(row);
}
void MainWindow::go_to_page(int i)
{
if (i >= 0 && i < tabs->count()) tabs->setCurrentIndex(i);
}
void MainWindow::on_module_selected(quint32 id)
{
ModuleInfo info; drv->module_info(id, info);
workbench->setSelected(id, info);
}
void MainWindow::on_module_loaded(quint32 id)
{
QVector<ModuleRow> mods; drv->refresh_modules(mods);
workbench->refreshModules(mods);
ModuleInfo info; drv->module_info(id, info);
workbench->setSelected(id, info);
tabs->setCurrentIndex(0);
for (const auto& m : mods) {
if (m.id == id) {
add_recent(m.name);
if (QFile::exists(m.name)) {
path_to_module.insert(m.name, id);
hot_watcher->addPath(m.name);
}
break;
}
}
}
void MainWindow::on_state_changed() { tick_slow(); }
void MainWindow::show_toast(const QString& msg, int kind)
{
statusBar()->showMessage(msg, 4000);
statusBar()->setStyleSheet(
QString("QStatusBar { color: %1; font-size:12px; padding-left:8px; }")
.arg(kind == 1 ? "#cd5c5c" : (kind == 2 ? "#8fbf5c" : "#d4d4d4")));
}
void MainWindow::tick_fast()
{
QVector<LogEntry> entries; quint32 dropped = 0;
if (drv->tail_log(log_seq, entries, dropped) && !entries.isEmpty()) {
logp->append(entries, log_seq, dropped);
events->append(entries);
}
QVector<TraceEvent> tr; quint32 tdrop = 0;
if (drv->tail_trace(trace_seq, tr, tdrop) && !tr.isEmpty()) {
tracep->append(tr);
profilerp->observe(tr);
}
}
void MainWindow::tick_slow()
{
bool dev = drv->device_open();
bool pres = drv->service_present();
bool run = drv->service_running();
set_stat(statDevice, dev ? "driver loaded" : "driver not loaded",
"#cccccc", dev ? "#8fbf5c" : "#cd5c5c");
set_stat(statSvc, run ? "service running" : (pres ? "service stopped" : "service not installed"),
"#cccccc", run ? "#8fbf5c" : (pres ? "#e5b055" : "#cd5c5c"));
QVector<ModuleRow> mods; drv->refresh_modules(mods);
workbench->refreshModules(mods);
int user_count = 0;
for (const auto& m : mods)
if (QFileInfo(m.name).fileName().compare("toolkit.wasm", Qt::CaseInsensitive) != 0)
user_count++;
set_stat(statCount, QString("%1 modules").arg(user_count), "#999999", "");
deploy->refresh(dev, pres, run);
}
void MainWindow::load_wasm_from_dialog()
{
QString path = QFileDialog::getOpenFileName(this, "Load module", QString(), "WebAssembly (*.wasm)");
if (path.isEmpty()) return;
quint32 id = 0; QString err;
if (drv->load_module(path, 0, id, err)) {
show_toast(QString("loaded module %1").arg(id), 2);
on_module_loaded(id);
} else {
show_toast("load failed: " + err, 1);
}
}
void MainWindow::reload_current_selection()
{
// reload every tracked wasm from disk
for (auto it = path_to_module.begin(); it != path_to_module.end(); ++it) {
drv->unload(it.value());
}
QHash<QString, quint32> newmap;
for (auto it = path_to_module.begin(); it != path_to_module.end(); ++it) {
quint32 id = 0; QString err;
if (drv->load_module(it.key(), 0, id, err)) {
newmap.insert(it.key(), id);
}
}
path_to_module = newmap;
tick_slow();
show_toast(QString("reloaded %1 modules").arg(newmap.size()), 2);
}
void MainWindow::on_file_changed(const QString& path)
{
// some editors atomically-replace on write; watcher stops after that. re-add.
if (QFile::exists(path)) hot_watcher->addPath(path);
// driver .sys changed: restart service
if (path.endsWith("Goodmans.sys", Qt::CaseInsensitive)) {
show_toast("driver changed - restarting service", 0);
drv->run_sc("stop Goodmans");
drv->run_sc("start Goodmans");
// reload every previously-loaded wasm since the driver forgot them
QHash<QString, quint32> old = path_to_module;
path_to_module.clear();
for (auto it = old.begin(); it != old.end(); ++it) {
quint32 id = 0; QString err;
if (drv->load_module(it.key(), 0, id, err)) {
path_to_module.insert(it.key(), id);
if (it.key().endsWith("toolkit.wasm", Qt::CaseInsensitive))
explorerp->setToolkitId((int)id);
}
}
tick_slow();
show_toast(QString("driver restarted, %1 modules reloaded").arg(path_to_module.size()), 2);
return;
}
if (!path_to_module.contains(path)) return;
quint32 old_id = path_to_module.value(path);
drv->unload(old_id);
quint32 new_id = 0; QString err;
if (drv->load_module(path, 0, new_id, err)) {
path_to_module.insert(path, new_id);
if (path.endsWith("toolkit.wasm", Qt::CaseInsensitive))
explorerp->setToolkitId((int)new_id);
show_toast(QString("hot reload: %1: module %2").arg(QFileInfo(path).fileName()).arg(new_id), 2);
tick_slow();
} else {
show_toast("hot reload failed: " + err, 1);
}
}
void MainWindow::add_recent(const QString& path)
{
recent_files.removeAll(path);
recent_files.prepend(path);
while (recent_files.size() > 10) recent_files.removeLast();
rebuild_recent_menu();
}
void MainWindow::rebuild_recent_menu()
{
if (!recentMenu) return;
recentMenu->clear();
if (recent_files.isEmpty()) {
auto* a = recentMenu->addAction("(no recent files)");
a->setEnabled(false);
return;
}
for (const auto& p : recent_files) {
QString label = QFileInfo(p).fileName();
auto* a = recentMenu->addAction(label);
a->setToolTip(p);
connect(a, &QAction::triggered, this, [this, p]{
if (!QFile::exists(p)) { show_toast("file no longer exists", 1); return; }
quint32 id = 0; QString err;
if (drv->load_module(p, 0, id, err)) { show_toast(QString("loaded %1").arg(id), 2); on_module_loaded(id); }
else show_toast(err, 1);
});
}
recentMenu->addSeparator();
auto* clr = recentMenu->addAction("Clear recent");
connect(clr, &QAction::triggered, this, [this]{ recent_files.clear(); rebuild_recent_menu(); });
}
void MainWindow::save_settings()
{
QSettings s("goodmans", "gui");
s.setValue("geometry", saveGeometry());
s.setValue("state", saveState());
s.setValue("page", tabs->currentIndex());
s.setValue("recent", recent_files);
}
void MainWindow::load_settings()
{
QSettings s("goodmans", "gui");
if (s.contains("geometry")) restoreGeometry(s.value("geometry").toByteArray());
if (s.contains("state")) restoreState(s.value("state").toByteArray());
recent_files = s.value("recent").toStringList();
}
void MainWindow::open_about()
{
QDialog dlg(this);
dlg.setWindowTitle("About The Goodmans Kernel");
dlg.setFixedSize(560, 520);
dlg.setStyleSheet("QDialog { background: #1c1c1c; }");
auto* v = new QVBoxLayout(&dlg);
v->setContentsMargins(28, 24, 28, 20);
v->setSpacing(4);
auto* title = new QLabel("The Goodmans Kernel");
title->setStyleSheet("color:#ffffff; font-size:22px; font-weight:600;");
v->addWidget(title);
auto* tag = new QLabel("Signed WDM driver embedding wasm3.");
tag->setStyleSheet("color:#8b8b8b; font-size:12.5px;");
v->addWidget(tag);
auto* sep = new QFrame; sep->setFrameShape(QFrame::HLine);
sep->setStyleSheet("background:#2c2c2c; max-height:1px; border:none; margin-top:14px; margin-bottom:14px;");
v->addWidget(sep);
auto* project = new QLabel(
"<p style='color:#dcdcdc; font-size:12.5px; line-height:1.55;'>"
"Loads unsigned <b>.wasm</b> modules into a signed kernel driver."
"</p>"
);
project->setWordWrap(true);
project->setTextFormat(Qt::RichText);
v->addWidget(project);
v->addSpacing(6);
auto* authorLbl = new QLabel("Author");
authorLbl->setStyleSheet("color:#8b8b8b; font-size:11px; font-weight:600;");
v->addWidget(authorLbl);
auto* author = new QLabel(
"<p style='color:#dcdcdc; font-size:12.5px; line-height:1.55;'>"
"<b>zer0condition</b>"
"</p>"
);
author->setWordWrap(true);
author->setTextFormat(Qt::RichText);
v->addWidget(author);
v->addSpacing(6);
auto* stackLbl = new QLabel("Built with");
stackLbl->setStyleSheet("color:#8b8b8b; font-size:11px; font-weight:600;");
v->addWidget(stackLbl);
auto* stack = new QLabel(
"<p style='color:#c8c8c8; font-size:12px; line-height:1.55;'>"
"&bull; wasm3 , MIT, Volodymyr Shymanskyy / Steven Massey<br>"
"&bull; Qt 6 , LGPLv3, The Qt Company<br>"
"&bull; JetBrainsMono , OFL, JetBrains"
"</p>"
);
stack->setTextFormat(Qt::RichText);
v->addWidget(stack);
v->addStretch();
auto* btnRow = new QHBoxLayout;
btnRow->addStretch();
auto* close = new QPushButton("Close");
close->setObjectName("btnPrimary");
close->setDefault(true);
close->setFixedWidth(96);
btnRow->addWidget(close);
v->addLayout(btnRow);
connect(close, &QPushButton::clicked, &dlg, &QDialog::accept);
dlg.exec();
}
void MainWindow::open_command_palette()
{
QVector<CommandAction> actions;
// navigation
QStringList pages = { "Modules", "Explorer", "Trace", "Profiler", "Events", "Log", "Memory", "Deploy" };
for (int i = 0; i < pages.size(); i++)
actions.push_back({ "Go: " + pages[i], QString("Ctrl+%1").arg(i + 1), [this, i]{ go_to_page(i); }});
actions.push_back({ "Load module…", "Ctrl+O", [this]{ load_wasm_from_dialog(); }});
actions.push_back({ "Reload all modules", "", [this]{ reload_current_selection(); }});
actions.push_back({ "Unload all modules", "", [this]{ drv->unload_all(); tick_slow(); show_toast("unloaded all", 2); }});
actions.push_back({ "Refresh state", "F5", [this]{ tick_slow(); }});
actions.push_back({ "Trace: Start / Stop", "F5 in trace", [this]{ go_to_page(2) /* trace */; tracep->toggleTracing(); }});
actions.push_back({ "Trace: Focus filter", "Ctrl+F", [this]{ go_to_page(2) /* trace */; tracep->focusFilter(); }});
actions.push_back({ "Trace: Save…", "Ctrl+S", [this]{
go_to_page(2) /* trace */;
QString p = QFileDialog::getSaveFileName(this, "Save trace", "trace.gtrace",
"Goodmans trace (*.gtrace);;CSV (*.csv)");
if (!p.isEmpty()) tracep->saveTraceFile(p);
}});
actions.push_back({ "Trace: Open .gtrace…", "", [this]{
go_to_page(2) /* trace */;
QString p = QFileDialog::getOpenFileName(this, "Open trace", QString(), "Goodmans trace (*.gtrace)");
if (!p.isEmpty()) tracep->loadTraceFile(p);
}});
actions.push_back({ "Trace: Toggle bookmark", "Ctrl+B", [this]{ go_to_page(2) /* trace */; tracep->toggleBookmark(); }});
actions.push_back({ "Driver: Start service", "", [this]{ drv->run_sc("start Goodmans"); tick_slow(); }});
actions.push_back({ "Driver: Stop service", "", [this]{ drv->run_sc("stop Goodmans"); tick_slow(); }});
actions.push_back({ "Help: About", "", [this]{ open_about(); }});
for (const auto& p : recent_files) {
QString label = "Open recent: " + QFileInfo(p).fileName();
actions.push_back({ label, "", [this, p]{
if (!QFile::exists(p)) { show_toast("file gone", 1); return; }
quint32 id = 0; QString err;
if (drv->load_module(p, 0, id, err)) { on_module_loaded(id); show_toast(QString("loaded %1").arg(id), 2); }
else show_toast(err, 1);
}});
}
CommandPalette p(this, actions);
QRect g = geometry();
p.move(g.center().x() - p.width() / 2, g.top() + 80);
p.exec();
}
+76
View File
@@ -0,0 +1,76 @@
#pragma once
#include <QMainWindow>
#include <QListWidget>
#include <QStackedWidget>
#include <QTabWidget>
#include <QLabel>
#include <QTimer>
#include <QMenu>
#include <QFileSystemWatcher>
#include <QHash>
#include <QSettings>
#include "driver.h"
#include "pages.h"
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow();
private slots:
void tick_fast();
void tick_slow();
void on_module_selected(quint32 id);
void on_module_loaded(quint32 id);
void on_state_changed();
void on_nav_changed(int row);
void show_toast(const QString& msg, int kind);
private:
DriverClient* drv;
QTabWidget* tabs;
WorkbenchPage* workbench;
MemoryPage* memory;
EventsPage* events;
LogPage* logp;
TracePage* tracep;
ExplorerPage* explorerp;
ProfilerPage* profilerp;
DeployPage* deploy;
QTimer fast_timer;
QTimer slow_timer;
// status
QLabel* statDevice;
QLabel* statSvc;
QLabel* statCount;
// log state
quint64 log_seq = 0;
quint64 trace_seq = 0;
void set_stat(QLabel* lbl, const QString& txt, const QString& color, const QString& dot_color);
// shortcuts + palette
void open_command_palette();
void open_about();
void load_wasm_from_dialog();
void reload_current_selection();
void go_to_page(int i);
// hot reload
QFileSystemWatcher* hot_watcher = nullptr;
QHash<QString, quint32> path_to_module;
void on_file_changed(const QString& path);
// recent files
QMenu* recentMenu = nullptr;
QStringList recent_files;
void add_recent(const QString& path);
void rebuild_recent_menu();
// session persistence
void save_settings();
void load_settings();
protected:
void closeEvent(QCloseEvent* e) override;
};
+1746
View File
File diff suppressed because it is too large Load Diff
+208
View File
@@ -0,0 +1,208 @@
#pragma once
#include <QWidget>
#include <QTableWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
#include <QListWidget>
#include <QPlainTextEdit>
#include <QCheckBox>
#include <QSpinBox>
#include <QSet>
#include "driver.h"
class WorkbenchPage : public QWidget {
Q_OBJECT
public:
WorkbenchPage(DriverClient* d, QWidget* p = nullptr);
void refreshModules(const QVector<ModuleRow>& mods);
void setSelected(quint32 id, const ModuleInfo& info);
void clearSelection();
signals:
void moduleLoaded(quint32 id);
void moduleSelected(quint32 id);
void toast(const QString& msg, int kind);
private:
DriverClient* drv;
QLineEdit* loadPath;
QSpinBox* loadBudget;
QListWidget* moduleList;
QLabel* moduleCount;
QLabel* modTitle;
QLabel* modMeta;
QWidget* capsBar;
QTableWidget* expTable;
QTableWidget* impTable;
QLineEdit* callExport;
QLineEdit* callArgs;
QSpinBox* callTimeout;
QLabel* callResult;
QTableWidget* callHist;
ModuleInfo current;
QWidget* infoPanel;
QWidget* emptyPanel;
quint32 pending_select = 0;
void refreshCaps(quint32 caps);
};
class MemoryPage : public QWidget {
Q_OBJECT
public:
MemoryPage(DriverClient* d, QWidget* p = nullptr);
signals:
void toast(const QString& msg, int kind);
private:
DriverClient* drv;
QSpinBox* modId;
QLineEdit* exp;
QLineEdit* addr;
QSpinBox* length;
QLabel* status;
QPlainTextEdit* hex;
};
class EventsPage : public QWidget {
Q_OBJECT
public:
EventsPage(DriverClient* d, QWidget* p = nullptr);
void append(const QVector<LogEntry>& entries);
void clear();
signals:
void toast(const QString& msg, int kind);
private slots:
void on_load_tracer();
void on_start(bool reactive);
void on_stop();
private:
DriverClient* drv;
QTableWidget* table;
QLineEdit* filter;
QCheckBox* cbProc;
QCheckBox* cbExit;
QCheckBox* cbImage;
QCheckBox* cbCall;
QCheckBox* cbError;
QPushButton* loadBtn;
QPushButton* startBtn;
QPushButton* reactiveBtn;
QPushButton* stopBtn;
QLabel* statusLbl;
int tracer_id = -1;
bool running = false;
void set_status(const QString& s, const QString& color);
};
class LogPage : public QWidget {
Q_OBJECT
public:
LogPage(QWidget* p = nullptr);
void append(const QVector<LogEntry>& entries, quint64 seq, quint32 dropped);
void clear();
private:
QPlainTextEdit* view;
QLabel* statusLbl;
QCheckBox* autoscroll;
};
class TracePage : public QWidget {
Q_OBJECT
public:
TracePage(DriverClient* d, QWidget* p = nullptr);
void append(const QVector<TraceEvent>& events);
void clear();
void focusFilter() { filter->setFocus(); filter->selectAll(); }
void saveTraceFile(const QString& path);
void loadTraceFile(const QString& path);
void toggleTracing();
void toggleBookmark();
signals:
void toast(const QString& msg, int kind);
private:
DriverClient* drv;
QTableWidget* table;
QPlainTextEdit* details;
QPushButton* toggleBtn;
QPushButton* pauseBtn;
QPushButton* clearBtn;
QPushButton* exportBtn;
QPushButton* openBtn;
QSpinBox* moduleFilter;
QCheckBox* onlyModule;
QCheckBox* regexMode;
QLineEdit* filter;
QLabel* modeLbl;
QLabel* statsLbl;
bool tracing_on = false;
bool paused = false;
quint64 total_events = 0;
quint64 shown_events = 0;
quint64 first_ts = 0;
QHash<QString, quint64> kind_counts;
QVector<TraceEvent> backing;
QSet<int> bookmarks; // indices into backing
void render_details(const TraceEvent& e);
void refresh_stats();
void rebuild_from_backing();
bool filter_match(const TraceEvent& e) const;
void insert_row(int row, const TraceEvent& e, int back_idx);
};
class ExplorerPage : public QWidget {
Q_OBJECT
public:
ExplorerPage(DriverClient* d, QWidget* p = nullptr);
void setToolkitId(int id);
signals:
void toast(const QString& msg, int kind);
private slots:
void refresh_procs();
void read_memory();
void read_msrs();
void read_cpuid();
private:
DriverClient* drv;
int toolkit_id = -1;
QLabel* toolkitLbl;
QPushButton* loadToolkitBtn;
// procs
QTableWidget* procTable;
// memory
QLineEdit* memAddr;
QSpinBox* memLen;
QPlainTextEdit* memHex;
// msr
QTableWidget* msrTable;
QLineEdit* msrCustom;
// cpuid
QTableWidget* cpuidTable;
QSpinBox* cpuidMax;
};
class ProfilerPage : public QWidget {
Q_OBJECT
public:
ProfilerPage(QWidget* p = nullptr);
void observe(const QVector<TraceEvent>& events);
void clear();
private:
struct Stat { quint64 count = 0; quint64 total_ns = 0; quint64 max_ns = 0; quint64 last_call_ts = 0; };
QHash<QString, Stat> stats;
QTableWidget* table;
QLabel* total;
void refresh();
};
class DeployPage : public QWidget {
Q_OBJECT
public:
DeployPage(DriverClient* d, QWidget* p = nullptr);
void refresh(bool devUp, bool present, bool running);
signals:
void toast(const QString& msg, int kind);
void stateChanged();
private:
DriverClient* drv;
QLabel* svcState;
QLabel* runState;
QLabel* devState;
};
+5
View File
@@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>style.qss</file>
</qresource>
</RCC>
+355
View File
@@ -0,0 +1,355 @@
/* professional grayscale. no brand accent, colors only carry meaning.
* sharp rectangular geometry. references: Sublime Merge, IDA Pro, Xcode.
* palette is warm-neutral gray with white for active states, semantic
* colors reserved for ok/warn/err. */
* {
font-family: "Segoe UI Variable", "Segoe UI", "Inter", sans-serif;
font-size: 12.5px;
}
QMainWindow {
background: #1c1c1c;
color: #dcdcdc;
}
QWidget { color: #dcdcdc; }
QLabel { background: transparent; color: #dcdcdc; }
/* menubar */
QMenuBar {
background: #1c1c1c;
color: #dcdcdc;
border-bottom: 1px solid #2c2c2c;
padding: 1px 2px;
}
QMenuBar::item {
padding: 4px 11px;
background: transparent;
border-radius: 2px;
}
QMenuBar::item:selected { background: #333333; color: #ffffff; }
QMenuBar::item:pressed { background: #404040; }
QMenu {
background: #242424;
color: #dcdcdc;
border: 1px solid #404040;
padding: 3px;
border-radius: 2px;
}
QMenu::item { padding: 5px 24px 5px 22px; border-radius: 2px; }
QMenu::item:selected { background: #3a3a3a; color: #ffffff; }
QMenu::separator { height: 1px; background: #333333; margin: 3px 4px; }
/* toolbar */
QToolBar {
background: #232323;
border: none;
border-bottom: 1px solid #2c2c2c;
padding: 3px 4px;
spacing: 2px;
}
QToolBar::separator {
background: #3a3a3a;
width: 1px;
margin: 4px 5px;
}
QToolButton {
background: transparent;
color: #c8c8c8;
border: 1px solid transparent;
border-radius: 2px;
padding: 4px 11px;
font-size: 12.5px;
}
QToolButton:hover { background: #333333; color: #ffffff; }
QToolButton:pressed { background: #2a2a2a; }
QToolButton:disabled{ color: #5f5f5f; }
/* tab bar (top-level) */
QTabWidget::pane {
border: none;
background: #1c1c1c;
top: -1px;
}
QTabWidget#mainTabs::pane { border-top: 1px solid #2c2c2c; }
QTabBar { qproperty-drawBase: 0; }
QTabBar::tab {
background: #1c1c1c;
color: #8b8b8b;
padding: 7px 18px;
border: none;
min-width: 70px;
font-size: 12.5px;
}
QTabBar::tab:hover { color: #dcdcdc; }
QTabBar::tab:selected { color: #ffffff; }
QTabBar#mainTabs::tab:selected { border-bottom: 2px solid #dcdcdc; }
/* status bar */
QStatusBar {
background: #1c1c1c;
color: #8b8b8b;
border-top: 1px solid #2c2c2c;
padding: 1px 8px;
font-size: 11.5px;
}
QStatusBar QLabel { color: #8b8b8b; padding: 0 6px; }
QStatusBar::item { border: none; }
/* typography */
QLabel#h2 { font-size: 13.5px; font-weight: 600; color: #ffffff; }
QLabel#subtitle { color: #8b8b8b; font-size: 12px; }
QLabel#sectionLabel { color: #8b8b8b; font-size: 11px; font-weight: 600; }
QLabel#dim { color: #8b8b8b; font-size: 11.5px; }
QLabel#emptyState { color: #5f5f5f; font-size: 12.5px; padding: 30px; }
QLabel#moduleTitle{ font-size: 14.5px; font-weight: 600; color: #ffffff; }
QLabel#mono { font-family: "JetBrainsMono NF", "Consolas", monospace; font-size: 11.5px; color: #8b8b8b; }
QLabel#resultLabel{ font-family: "JetBrainsMono NF", "Consolas", monospace; color: #8b8b8b; font-size: 12px; }
/* panel */
QFrame#card {
background: #242424;
border: 1px solid #2c2c2c;
border-radius: 2px;
}
QFrame#toolbar {
background: #242424;
border: 1px solid #2c2c2c;
border-radius: 2px;
}
QFrame#tbsep { background: #3a3a3a; max-width: 1px; }
QFrame#sep,
QFrame#vsep { background: #2c2c2c; max-height: 1px; max-width: 1px; border: none; }
/* pill */
QLabel#pill {
padding: 2px 9px;
border-radius: 2px;
font-family: "JetBrainsMono NF", monospace;
font-size: 10.5px;
color: #5f5f5f;
background: transparent;
border: 1px solid #333333;
}
QLabel#pill[kind="on"] {
color: #ffffff;
background: #333333;
border: 1px solid #505050;
}
QLabel#pill[kind="off"] {
color: #5f5f5f;
background: transparent;
border: 1px solid #2c2c2c;
}
/* buttons */
QPushButton {
background: #2f2f2f;
color: #dcdcdc;
border: 1px solid #404040;
border-radius: 2px;
padding: 5px 14px;
font-size: 12.5px;
min-height: 22px;
}
QPushButton:hover { background: #3a3a3a; border-color: #505050; color: #ffffff; }
QPushButton:pressed { background: #262626; }
QPushButton:disabled{ color: #5f5f5f; background: #232323; border-color: #2c2c2c; }
QPushButton#btnPrimary {
background: #e6e6e6;
color: #1c1c1c;
border: 1px solid #e6e6e6;
font-weight: 600;
}
QPushButton#btnPrimary:hover { background: #ffffff; border-color: #ffffff; }
QPushButton#btnPrimary:pressed{ background: #c8c8c8; }
QPushButton#btnPrimary:disabled { background: #333333; color: #5f5f5f; border-color: #333333; }
QPushButton#btnDanger {
color: #e06c6c;
background: #2f2f2f;
border: 1px solid #4a2626;
}
QPushButton#btnDanger:hover { background: #3a2626; border-color: #6a2f2f; }
/* inputs */
QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox {
background: #1a1a1a;
color: #e6e6e6;
border: 1px solid #333333;
border-radius: 2px;
padding: 5px 8px;
selection-background-color: #4a4a4a;
selection-color: #ffffff;
min-height: 20px;
font-size: 12px;
}
QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QComboBox:focus {
border: 1px solid #6a6a6a;
background: #1e1e1e;
}
QSpinBox::up-button, QSpinBox::down-button,
QDoubleSpinBox::up-button, QDoubleSpinBox::down-button { width: 0; border: none; }
/* tables */
QTableWidget#dataTable, QTableView {
background: #1c1c1c;
color: #dcdcdc;
border: 1px solid #2c2c2c;
border-radius: 2px;
gridline-color: transparent;
selection-background-color: #3a3a3a;
selection-color: #ffffff;
outline: 0;
alternate-background-color: #1f1f1f;
}
QTableWidget#dataTable::item, QTableView::item {
padding: 4px 10px;
border: none;
}
QTableWidget#dataTable::item:selected, QTableView::item:selected {
background: #3a3a3a;
color: #ffffff;
}
QHeaderView::section {
background: #232323;
color: #8b8b8b;
padding: 6px 10px;
border: none;
border-right: 1px solid #2c2c2c;
border-bottom: 1px solid #2c2c2c;
font-size: 11px;
font-weight: 600;
}
QHeaderView::section:hover { color: #dcdcdc; }
/* inner tabs (Exports/Imports) */
QTabWidget#wbTabs::pane { border: none; background: transparent; top: -1px; }
QTabWidget#wbTabs QTabBar::tab {
background: transparent;
color: #8b8b8b;
padding: 6px 16px;
border: none;
border-bottom: 2px solid transparent;
font-weight: 500;
font-size: 12.5px;
}
QTabWidget#wbTabs QTabBar::tab:selected { color: #ffffff; border-bottom: 2px solid #dcdcdc; }
QTabWidget#wbTabs QTabBar::tab:hover { color: #dcdcdc; }
/* text panels */
QPlainTextEdit#logView, QPlainTextEdit#hexView, QPlainTextEdit#detailsPane {
background: #1a1a1a;
color: #dcdcdc;
border: 1px solid #2c2c2c;
border-radius: 2px;
padding: 8px 10px;
selection-background-color: #4a4a4a;
font-family: "JetBrainsMono NF", "Consolas", monospace;
font-size: 11.5px;
}
/* checkbox */
QCheckBox {
color: #dcdcdc;
spacing: 6px;
padding: 2px 0;
font-size: 12px;
}
QCheckBox::indicator {
width: 13px; height: 13px;
border: 1px solid #4a4a4a;
border-radius: 2px;
background: #1a1a1a;
}
QCheckBox::indicator:hover { border-color: #6a6a6a; }
QCheckBox::indicator:checked { background: #dcdcdc; border-color: #dcdcdc; }
/* list widget */
QListWidget {
background: #1c1c1c;
color: #c8c8c8;
border: 1px solid #2c2c2c;
border-radius: 2px;
outline: 0;
padding: 1px;
}
QListWidget::item {
padding: 5px 10px;
border: none;
color: #c8c8c8;
}
QListWidget::item:hover { background: #242424; color: #ffffff; }
QListWidget::item:selected { background: #3a3a3a; color: #ffffff; }
/* splitter */
QSplitter::handle { background: #2c2c2c; }
QSplitter::handle:hover { background: #505050; }
QSplitter::handle:horizontal { width: 1px; }
QSplitter::handle:vertical { height: 1px; }
/* scrollbars */
QScrollBar:vertical {
background: #1c1c1c;
width: 11px;
margin: 0;
border-left: 1px solid #2c2c2c;
}
QScrollBar::handle:vertical {
background: #3a3a3a;
min-height: 24px;
border-radius: 0;
margin: 1px;
}
QScrollBar::handle:vertical:hover { background: #505050; }
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical,
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: transparent; height: 0;
}
QScrollBar:horizontal {
background: #1c1c1c;
height: 11px;
margin: 0;
border-top: 1px solid #2c2c2c;
}
QScrollBar::handle:horizontal {
background: #3a3a3a;
min-width: 24px;
margin: 1px;
}
QScrollBar::handle:horizontal:hover { background: #505050; }
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal,
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
background: transparent; width: 0;
}
/* tooltip */
QToolTip {
background: #242424;
color: #dcdcdc;
border: 1px solid #404040;
padding: 3px 7px;
border-radius: 0;
font-size: 11.5px;
}
+25
View File
@@ -0,0 +1,25 @@
@echo off
setlocal
cd /d "%~dp0"
set CLANG=%LLVM_HOME%\bin\clang.exe
if not exist "%CLANG%" set CLANG=C:\Program Files\LLVM\bin\clang.exe
if not exist "%CLANG%" (
echo [X] clang not found. install LLVM or set LLVM_HOME.
exit /b 1
)
set CFLAGS=-O2 --target=wasm32 -nostdlib -fno-builtin -I..\guest_sdk
set LFLAGS=-Wl,--no-entry -Wl,--export-dynamic -Wl,--allow-undefined -Wl,--strip-all
call :one sample_guest
call :one ffi_demo
call :one pslist_dumper
call :one handle_stripper
call :one infinity_hook
exit /b 0
:one
echo [*] %~1
"%CLANG%" %CFLAGS% %LFLAGS% -o %~1.wasm %~1.c
exit /b 0
+87
View File
@@ -0,0 +1,87 @@
/* ffi_demo.c - shows the direct FFI patterns exposed by the driver.
* every kernel API call here goes through the trampoline: no name lookup
* at call time, standard x64 ABI, traced with the real symbol name.
*/
#include "../guest_sdk/gvm.h"
GVM_MANIFEST(GVM_CAP_ALLOC | GVM_CAP_READ_KMEM | GVM_CAP_INTROSPECT | GVM_CAP_HOSTCALL);
// direct kernel imports beyond what's in the SDK. any exported nt/hal
// symbol works this way.
GVM_IMPORT_KERNEL(KeQueryTimeIncrement) u32 KeQueryTimeIncrement(void);
GVM_IMPORT_KERNEL(MmGetPhysicalAddress) u64 MmGetPhysicalAddress(PVOID va);
GVM_IMPORT_KERNEL(PsIsSystemThread) BOOLEAN PsIsSystemThread(PVOID thread);
static char g_scratch[512];
GVM_EXPORT(demo_scalar_apis)
u32 demo_scalar_apis(void)
{
// pure scalar-in / scalar-out FFI. no buffer marshalling needed.
u32 pid = (u32)(u64)PsGetCurrentProcessId();
u32 tid = (u32)(u64)PsGetCurrentThreadId();
u8 irql = KeGetCurrentIrql();
u32 tinc = KeQueryTimeIncrement();
u8 isys = PsIsSystemThread(KeGetCurrentThread());
char buf[128];
u32 n = 0;
n += gvm_strcpy(buf + n, "pid="); gvm_dec(pid, buf + n); n += gvm_strlen(buf + n);
n += gvm_strcpy(buf + n, " tid="); gvm_dec(tid, buf + n); n += gvm_strlen(buf + n);
n += gvm_strcpy(buf + n, " irql="); gvm_dec(irql, buf + n); n += gvm_strlen(buf + n);
n += gvm_strcpy(buf + n, " tinc="); gvm_dec(tinc, buf + n); n += gvm_strlen(buf + n);
n += gvm_strcpy(buf + n, " sys="); gvm_dec(isys, buf + n); n += gvm_strlen(buf + n);
gvm_print(buf);
return pid;
}
// showing a POINTER out-param pattern. own-buffer VA computed via gvm_kva.
GVM_EXPORT(demo_pointer_out)
u64 demo_pointer_out(u64 va)
{
// MmGetPhysicalAddress returns a PHYSICAL_ADDRESS struct (union of u64).
// on x64 with FFI it comes back in RAX as a u64.
u64 pa = MmGetPhysicalAddress((PVOID)va);
return pa;
}
// buffer read INTO a wasm-side struct via a real kernel VA computed from
// our own linear memory. no host_read_bytes needed.
GVM_EXPORT(demo_buffer)
u64 demo_buffer(u64 kernel_src, u32 len)
{
if (len > sizeof(g_scratch)) len = sizeof(g_scratch);
// build a real kernel VA of &g_scratch[0] inside our wasm memory
u64 dst = gvm_kva(g_scratch);
RtlCopyMemory((PVOID)dst, (PVOID)kernel_src, len);
// return the first 8 bytes as a sanity check
u64 v = 0;
for (u32 i = 0; i < 8 && i < len; i++)
v |= ((u64)(u8)g_scratch[i]) << (i * 8);
return v;
}
// UNICODE_STRING roundtrip. build one, resolve a symbol with it, free it.
GVM_EXPORT(demo_unistr)
u64 demo_unistr(void)
{
// "ExAllocatePool2" as UTF-16LE
static const u16 wname[] = {
'E','x','A','l','l','o','c','a','t','e','P','o','o','l','2'
};
u64 us = host_make_unistr((u32)(unsigned long)wname, sizeof(wname));
if (!us) return 0;
u64 addr = (u64)MmGetSystemRoutineAddress((PVOID)us);
host_free_unistr(us);
return addr;
}
// canonical entry point exercised by the GUI's Call button
GVM_EXPORT(run_all)
u64 run_all(void)
{
demo_scalar_apis();
(void)demo_unistr();
return 0;
}
+82
View File
@@ -0,0 +1,82 @@
/* handle_stripper.c - enumerate handles held by a target process using
* the documented ExEnumHandleTable API. reports each entry's ObjectHeader
* and object type index. useful for auditing what a suspicious process
* is holding open (LSASS handles, process handles with debug rights, etc).
*
* this is a READ-ONLY tool. it doesn't actually strip anything. writing
* to HANDLE_TABLE_ENTRY needs raw kernel writes that are per-build-fragile.
* see the strip_by_object_pattern function below for a scaffold if you
* want to extend.
*/
#include "gvm.h"
GVM_MANIFEST(GVM_CAP_ALLOC | GVM_CAP_READ_KMEM | GVM_CAP_INTROSPECT | GVM_CAP_HOSTCALL);
// callback context passed to ExEnumHandleTable
typedef struct {
u32 count;
u32 target_pid;
} enum_ctx;
// scan EPROCESS for ObjectTable pointer. On modern Windows this is at
// a stable-ish offset but changes by build. Dynamic search: find the field
// whose value is a valid kernel pointer to something that looks like a
// HANDLE_TABLE (starts with a small integer HandleCount at offset ~0).
static u64 find_object_table(u64 eproc)
{
for (u32 off = 0x400; off < 0x800; off += 8) {
u64 v = gvm_read_u64(eproc + off);
if ((v >> 48) != 0xFFFF) continue;
u32 first = gvm_read_u32(v);
if (first < 0x100000 && first > 0) return v; // HandleCount-ish
}
return 0;
}
GVM_EXPORT(list_handles)
u64 list_handles(u64 target_pid)
{
u64 target_eproc = 0;
u64 status = GVM_CALL2(PsLookupProcessByProcessId,
target_pid, (u64)(u32)(u64)&target_eproc);
if (status != 0 || target_eproc == 0) {
gvm_print("PsLookupProcessByProcessId failed");
return 0;
}
u64 table = find_object_table(target_eproc);
if (!table) {
gvm_print("could not locate ObjectTable in EPROCESS");
GVM_CALL1(ObfDereferenceObject, target_eproc);
return 0;
}
u32 handle_count = gvm_read_u32(table);
char hex[17];
gvm_print("target EPROCESS ="); gvm_hex64(target_eproc, hex); gvm_print(hex);
gvm_print("ObjectTable ="); gvm_hex64(table, hex); gvm_print(hex);
gvm_print("HandleCount ="); gvm_hex64(handle_count, hex); gvm_print(hex);
// full HANDLE_TABLE walk requires TableCode (3-level tree) which is per-build.
// scaffold: use ExEnumHandleTable(HANDLE_TABLE*, callback, ctx, HANDLE*)
//. signature is (HANDLE_TABLE*, ProcedurePtr, void*, HANDLE*) returning BOOLEAN.
// callback signature is BOOLEAN(HANDLE_TABLE_ENTRY* entry, HANDLE h, void* ctx).
// guests can't easily provide a callback (would need JIT'd wasm trampoline).
// for now just report the count and leave detailed walking to future work.
gvm_print("(detailed walk requires ExEnumHandleTable callback. see comments)");
GVM_CALL1(ObfDereferenceObject, target_eproc);
return (u64)handle_count;
}
// scaffold: when we can provide a wasm:native trampoline, this is where
// filtering + close would happen.
GVM_EXPORT(strip_by_object_pattern)
u64 strip_by_object_pattern(u64 target_pid, u64 object_type_index)
{
(void)target_pid; (void)object_type_index;
gvm_print("strip: not implemented. needs wasm:native callback trampoline");
gvm_print(" (guest cannot pass a function pointer to ExEnumHandleTable)");
return 0;
}
+168
View File
@@ -0,0 +1,168 @@
/* infinity_hook.c - InfinityHook as a wasm guest.
* original technique: github.com/everdox/InfinityHook (MIT). */
#include "../guest_sdk/gvm.h"
GVM_MANIFEST(GVM_CAP_READ_KMEM | GVM_CAP_WRITE_KMEM | GVM_CAP_INTROSPECT |
GVM_CAP_CALLBACKS | GVM_CAP_HOSTCALL);
static u64 g_nt_base = 0;
static u32 g_nt_size = 0;
static u64 g_getclock_slot = 0;
static u64 g_orig_getclock = 0;
// walk pages backwards from any VA inside nt.exe until we find the MZ+PE
// header pair. classic module-base recovery.
static u64 find_nt_base(u64 va_in_nt)
{
u64 p = va_in_nt & ~0xFFFULL;
for (u32 i = 0; i < 4096 && p >= 0x1000; i++, p -= 0x1000) {
if (!MmIsAddressValid((PVOID)p)) continue;
if ((gvm_read_u32(p) & 0xFFFF) != 0x5A4D) continue;
u32 pe_off = gvm_read_u32(p + 0x3C);
if (!pe_off || pe_off > 0x1000) continue;
if (gvm_read_u32(p + pe_off) != 0x00004550) continue;
return p;
}
return 0;
}
// scan nt's .data section for the classic InfinityHook signature that
// marks the start of EtwpDebuggerData: 2C 08 04 38
static u64 find_etwp_debugger_data(u64 nt)
{
u32 pe_off = gvm_read_u32(nt + 0x3C);
u16 nsec = (u16)(gvm_read_u32(nt + pe_off + 6) & 0xFFFF);
u16 opt_sz = (u16)(gvm_read_u32(nt + pe_off + 20) & 0xFFFF);
u64 sec = nt + pe_off + 24 + opt_sz;
for (u16 i = 0; i < nsec; i++, sec += 40) {
if (gvm_read_u32(sec) != 0x61746164) continue; // '.dat'
u32 v_sz = gvm_read_u32(sec + 8);
u32 v_rva = gvm_read_u32(sec + 12);
u64 base = nt + v_rva;
u64 end = base + v_sz;
for (u64 q = base; q + 8 < end; q += 4) {
if (!MmIsAddressValid((PVOID)q)) continue;
if (gvm_read_u8(q + 0) == 0x2C &&
gvm_read_u8(q + 1) == 0x08 &&
gvm_read_u8(q + 2) == 0x04 &&
gvm_read_u8(q + 3) == 0x38)
return q;
}
return 0;
}
return 0;
}
// EtwpDebuggerData -> silo -> kernel WMI_LOGGER_CONTEXT -> GetCpuClock slot.
// GetCpuClock offset inside the context varies by Windows build (0x28 on
// most modern Win10/11, 0x18/0x30 on older). we try each and accept the
// one whose current value points into nt (unhooked baseline).
static u64 locate_getclock_slot(u64 nt, u32 nt_size, u64* out_orig)
{
u64 etwp = find_etwp_debugger_data(nt);
if (!etwp) return 0;
u64 silo = gvm_read_u64(etwp + 0x10);
if (!silo || !MmIsAddressValid((PVOID)silo)) return 0;
u64 ctx = gvm_read_u64(silo + 2 * 8); // silo[2] = kernel logger context
if (!ctx || !MmIsAddressValid((PVOID)ctx)) return 0;
static const u32 candidates[] = { 0x28, 0x18, 0x30 };
for (u32 i = 0; i < 3; i++) {
u64 slot = ctx + candidates[i];
if (!MmIsAddressValid((PVOID)slot)) continue;
u64 v = gvm_read_u64(slot);
if (v >= nt && v < nt + nt_size) {
*out_orig = v;
return slot;
}
}
return 0;
}
// install: locate everything, atomically swap in the driver's trampoline.
GVM_EXPORT(start)
u64 start(u64 sample_rate)
{
if (g_getclock_slot) { gvm_print("[ih] already installed"); return 0; }
PVOID any = MmGetSystemRoutineAddress((PVOID)0); // arg unused for demo
// real resolve: build a UNICODE_STRING for a known nt export, pass it in
u64 ustr = host_make_unistr((u32)(unsigned long)L"KeBugCheckEx",
sizeof(L"KeBugCheckEx") - sizeof(u16));
any = MmGetSystemRoutineAddress((PVOID)ustr);
host_free_unistr(ustr);
if (!any) { gvm_print("[ih] MmGetSystemRoutineAddress failed"); return 1; }
g_nt_base = find_nt_base((u64)any);
if (!g_nt_base) { gvm_print("[ih] nt base not found"); return 2; }
u32 pe_off = gvm_read_u32(g_nt_base + 0x3C);
g_nt_size = gvm_read_u32(g_nt_base + pe_off + 24 + 56); // SizeOfImage
g_getclock_slot = locate_getclock_slot(g_nt_base, g_nt_size, &g_orig_getclock);
if (!g_getclock_slot) {
gvm_print("[ih] GetCpuClock slot not located (build offsets may differ)");
return 3;
}
// hand the trampoline our nt bounds so it filters non-syscall callers
gvm_ih_configure((u32)sample_rate, g_nt_base, g_nt_size);
// atomic pointer swap. aligned 8-byte write on x64 is single-copy atomic
u64 tramp = gvm_ih_trampoline();
if (!tramp) { gvm_print("[ih] trampoline addr = 0"); return 4; }
gvm_write_u64(g_getclock_slot, tramp);
char line[128], t[24]; u32 n = 0;
n += gvm_strcpy(line + n, "[ih] hook installed. nt=0x");
gvm_hex64(g_nt_base, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " slot=0x");
gvm_hex64(g_getclock_slot, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " orig=0x");
gvm_hex64(g_orig_getclock, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " tramp=0x");
gvm_hex64(tramp, t); n += gvm_strcpy(line + n, t);
gvm_print(line);
return 0;
}
// uninstall: atomically restore original, then wait for in-flight trampoline
// calls to drain before we let the module be unloaded.
GVM_EXPORT(stop)
u64 stop(void)
{
if (!g_getclock_slot) { gvm_print("[ih] not installed"); return 0; }
gvm_write_u64(g_getclock_slot, g_orig_getclock);
u64 total = gvm_ih_quiesce();
g_getclock_slot = 0;
g_orig_getclock = 0;
char line[80], t[24]; u32 n = 0;
n += gvm_strcpy(line + n, "[ih] hook removed. trampoline hits=");
gvm_dec((u32)total, t); n += gvm_strcpy(line + n, t);
gvm_print(line);
return total;
}
// dispatched by the driver's worker on every sampled syscall event.
static volatile u32 g_seen = 0;
GVM_EXPORT(on_syscall)
u64 on_syscall(u64 tid, u64 retaddr, u64 counter)
{
g_seen++;
if ((g_seen & 63) != 0) return 0; // print 1 in 64 to keep log readable
char line[128], t[24]; u32 n = 0;
n += gvm_strcpy(line + n, "[ih] tid=");
gvm_dec((u32)tid, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " ret=0x");
gvm_hex64(retaddr, t); n += gvm_strcpy(line + n, t);
n += gvm_strcpy(line + n, " hit=");
gvm_dec((u32)counter, t); n += gvm_strcpy(line + n, t);
gvm_print(line);
return 0;
}
+111
View File
@@ -0,0 +1,111 @@
/* pslist_dumper.c - walk SYSTEM_PROCESS_INFORMATION via ZwQuerySystemInformation.
* safer than raw EPROCESS.ActiveProcessLinks traversal because we use the
* documented API surface. no offsets, no PatchGuard exposure. */
#include "gvm.h"
#define SystemProcessInformation 5
// SYSTEM_PROCESS_INFORMATION layout (x64). offsets verified against public winternl.
typedef struct {
u32 next_off; // 0x000 ULONG NextEntryOffset
u32 thread_count; // 0x004 ULONG NumberOfThreads
u64 pad1[3]; // 0x008 reserved + times
u64 create_time; // 0x020 LARGE_INTEGER CreateTime
u64 user_time;
u64 kernel_time;
u16 img_name_len; // 0x038 USHORT Length of ImageName
u16 img_name_max; // 0x03A
u32 pad2; // 0x03C
u64 img_name_ptr; // 0x040 PWSTR Buffer
u32 base_priority; // 0x048
u32 pad3; // 0x04C
u64 unique_pid; // 0x050 HANDLE UniqueProcessId
u64 inherited_ppid; // 0x058 HANDLE InheritedFromUniqueProcessId
// ... more fields we don't need
} spi_hdr;
static void wide_to_ascii(u64 wsrc, u16 wlen_bytes, char* dst, u32 dst_max)
{
u16 chars = wlen_bytes / 2;
if (chars > dst_max - 1) chars = (u16)(dst_max - 1);
for (u16 i = 0; i < chars; i++)
dst[i] = (char)gvm_read_u8(wsrc + i * 2);
dst[chars] = 0;
}
#define POOL_FLAG_NON_PAGED 0x0000000000000040ull
GVM_EXPORT(dump)
u64 dump(void)
{
gvm_print("dump: enter");
u32 sz = 64 * 1024;
PVOID buf = 0;
NTSTATUS s = 0;
for (int tries = 0; tries < 8; tries++) {
if (buf) ExFreePool(buf);
buf = ExAllocatePool2(POOL_FLAG_NON_PAGED, sz, 'psLD');
if (!buf) { gvm_print("dump: alloc failed"); return 0; }
gvm_print("dump: alloc ok, calling ZwQSI");
u32 got_off = 0;
s = ZwQuerySystemInformation(SystemProcessInformation, buf, sz, (u32*)gvm_kva(&got_off));
gvm_print("dump: ZwQSI returned");
if (s == 0) break;
if ((u32)s != 0xC0000004) break;
sz *= 2;
}
if (!NT_SUCCESS(s)) {
char hex[17];
gvm_print("ZwQuerySystemInformation failed:");
gvm_hex64((u64)(u32)s, hex); gvm_print(hex);
if (buf) ExFreePool(buf);
return 0;
}
u32 count = 0;
u64 cur = (u64)buf;
for (;;) {
// read the header via host_read_bytes into a local, then parse
spi_hdr h;
u32 n = gvm_read_bytes(cur, (u32)(u64)&h, sizeof(h));
if (n != sizeof(h)) break;
char name[64] = { 0 };
if (h.img_name_ptr && h.img_name_len)
wide_to_ascii(h.img_name_ptr, h.img_name_len, name, sizeof(name));
else
gvm_strcpy(name, "<System Idle Process>");
char line[160]; char t[32]; u32 lp = 0;
lp += gvm_strcpy(line + lp, "pid=");
gvm_dec((u32)h.unique_pid, t); lp += gvm_strcpy(line + lp, t);
lp += gvm_strcpy(line + lp, " ppid=");
gvm_dec((u32)h.inherited_ppid, t); lp += gvm_strcpy(line + lp, t);
lp += gvm_strcpy(line + lp, " thr=");
gvm_dec(h.thread_count, t); lp += gvm_strcpy(line + lp, t);
lp += gvm_strcpy(line + lp, " ");
lp += gvm_strcpy(line + lp, name);
gvm_print(line);
count++;
if (h.next_off == 0) break;
cur += h.next_off;
}
ExFreePool(buf);
char sum[64]; char t[32]; u32 lp = 0;
lp += gvm_strcpy(sum + lp, "total: ");
gvm_dec(count, t); lp += gvm_strcpy(sum + lp, t);
lp += gvm_strcpy(sum + lp, " processes");
gvm_print(sum);
return count;
}
+66
View File
@@ -0,0 +1,66 @@
/* sample_guest.c - reference guest exercising the Goodmans SDK */
#include "gvm.h"
/* print pid/tid/irql via SDK wrappers over host_call */
GVM_EXPORT(show_context)
u64 show_context(void)
{
HANDLE pid = PsGetCurrentProcessId();
HANDLE tid = PsGetCurrentThreadId();
u8 irql = KeGetCurrentIrql();
char hex[17];
gvm_print("context:");
gvm_hex64((u64)pid, hex); gvm_print(hex);
gvm_hex64((u64)tid, hex); gvm_print(hex);
gvm_hex64((u64)irql, hex); gvm_print(hex);
return ((u64)(u32)(u64)pid << 32) | (u32)(u64)tid;
}
/* alloc a kernel pool, write a pattern, read it back, free */
GVM_EXPORT(pool_roundtrip)
u64 pool_roundtrip(void)
{
const SIZE_T sz = 256;
PVOID p = ExAllocatePoolWithTag(NonPagedPoolNx, sz, 'GsmP');
if (!p) { gvm_print("alloc failed"); return 0; }
for (u32 i = 0; i < sz / 8; i++)
gvm_write_u64((u64)p + i * 8, 0xAABBCCDD00000000ULL | i);
u64 v = gvm_read_u64((u64)p);
char hex[17];
gvm_print("first slot:");
gvm_hex64(v, hex); gvm_print(hex);
ExFreePoolWithTag(p, 'GsmP');
return (u64)p;
}
/* resolve arbitrary ntoskrnl exports via host_call */
GVM_EXPORT(resolve_export)
u64 resolve_export(void)
{
u64 irql = GVM_CALL0(KeGetCurrentIrql);
u64 tick = GVM_CALL0(KeQueryTimeIncrement);
char hex[17];
gvm_print("KeGetCurrentIrql:");
gvm_hex64(irql, hex); gvm_print(hex);
gvm_print("KeQueryTimeIncrement:");
gvm_hex64(tick, hex); gvm_print(hex);
return tick;
}
GVM_EXPORT(run_all)
u64 run_all(void)
{
show_context();
pool_roundtrip();
resolve_export();
gvm_print("run_all done");
return 0;
}
+57
View File
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0003}</ProjectGuid>
<RootNamespace>sample_guest</RootNamespace>
<Keyword>MakeFileProj</Keyword>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<NMakeBuildCommandLine>call "$(MSBuildProjectDirectory)\build_all.cmd"</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>call "$(MSBuildProjectDirectory)\build_all.cmd"</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>del /Q "$(MSBuildProjectDirectory)\*.wasm" 2&gt;nul</NMakeCleanCommandLine>
<NMakeOutput>sample_guest.wasm;ffi_demo.wasm;pslist_dumper.wasm;handle_stripper.wasm;infinity_hook.wasm</NMakeOutput>
<NMakeIncludeSearchPath>..\guest_sdk;$(NMakeIncludeSearchPath)</NMakeIncludeSearchPath>
</PropertyGroup>
<ItemGroup>
<ClCompile Include="sample_guest.c" />
<ClCompile Include="ffi_demo.c" />
<ClCompile Include="pslist_dumper.c" />
<ClCompile Include="handle_stripper.c" />
<ClCompile Include="infinity_hook.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\guest_sdk\gvm.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Target Name="StageToDeploy" AfterTargets="Build">
<PropertyGroup>
<DeployDir>$(SolutionDir)deploy</DeployDir>
</PropertyGroup>
<Message Text="[deploy] staging demo wasm -&gt; $(DeployDir)\samples" Importance="high" />
<MakeDir Directories="$(DeployDir)\samples" />
<ItemGroup>
<SampleWasm Include="$(MSBuildProjectDirectory)\sample_guest.wasm;$(MSBuildProjectDirectory)\ffi_demo.wasm;$(MSBuildProjectDirectory)\pslist_dumper.wasm;$(MSBuildProjectDirectory)\handle_stripper.wasm;$(MSBuildProjectDirectory)\infinity_hook.wasm" />
</ItemGroup>
<Copy SourceFiles="@(SampleWasm)" DestinationFolder="$(DeployDir)\samples" SkipUnchangedFiles="true" ContinueOnError="true" />
</Target>
</Project>
+195
View File
@@ -0,0 +1,195 @@
/* goodmans_ioctl.h - shared UM/KM ioctl interface */
#pragma once
#ifdef _KERNEL_MODE
#include <ntddk.h>
#else
#include <windows.h>
#endif
#define GVM_DEVICE_NAME_U L"\\Device\\Goodmans"
#define GVM_SYMLINK_NAME_U L"\\DosDevices\\Goodmans"
#define GVM_USER_PATH "\\\\.\\Goodmans"
#define GVM_DEVICE_TYPE 0x8000
#define GVM_IOCTL(fn) CTL_CODE(GVM_DEVICE_TYPE, (fn), METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_GVM_LOAD_MODULE GVM_IOCTL(0x800)
#define IOCTL_GVM_CALL_EXPORT GVM_IOCTL(0x801)
#define IOCTL_GVM_UNLOAD_MODULE GVM_IOCTL(0x802)
#define IOCTL_GVM_LIST_MODULES GVM_IOCTL(0x803)
#define IOCTL_GVM_MODULE_INFO GVM_IOCTL(0x804)
#define IOCTL_GVM_UNLOAD_ALL GVM_IOCTL(0x805)
#define IOCTL_GVM_TAIL_LOG GVM_IOCTL(0x806)
#define IOCTL_GVM_READ_GUEST GVM_IOCTL(0x807)
#define IOCTL_GVM_TAIL_TRACE GVM_IOCTL(0x808)
#define IOCTL_GVM_TRACE_CTL GVM_IOCTL(0x809)
#define IOCTL_GVM_FORCE_UNLOAD GVM_IOCTL(0x80a)
#define IOCTL_GVM_NOTIFY_STOP GVM_IOCTL(0x80b)
#define GVM_MAX_MODULE_NAME 64
#define GVM_MAX_EXPORT_NAME 64
#define GVM_MAX_ARGS 8
#define GVM_MAX_MODULES 32
// capability bits, packed into gvm_module.caps
#define GVM_CAP_ALLOC (1u << 0) // host_alloc / host_free
#define GVM_CAP_READ_KMEM (1u << 1) // host_read_u8/u32/u64/bytes
#define GVM_CAP_WRITE_KMEM (1u << 2) // host_write_u64/bytes
#define GVM_CAP_MSR_READ (1u << 3) // host_readmsr
#define GVM_CAP_MSR_WRITE (1u << 4) // host_writemsr
#define GVM_CAP_PHYSMEM (1u << 5) // host_phys_read / host_phys_write
#define GVM_CAP_CPUID_TSC (1u << 6) // host_cpuid, host_rdtsc
#define GVM_CAP_CALLBACKS (1u << 7) // host_notify_enable / dispatch
#define GVM_CAP_HOSTCALL (1u << 8) // host_call (arbitrary export resolve)
#define GVM_CAP_INTROSPECT (1u << 9) // pid/tid/irql/current_process
#define GVM_CAP_ALL 0xFFFFFFFFu
typedef struct _gvm_load_in {
unsigned int wasm_size;
unsigned int stack_bytes; // 0 = 64KB default
unsigned long long pool_budget; // 0 = 4MB default
char name[GVM_MAX_MODULE_NAME];
} gvm_load_in;
typedef struct _gvm_load_out {
unsigned int module_id;
int status;
char err_msg[128];
} gvm_load_out;
typedef struct _gvm_call_in {
unsigned int module_id;
unsigned int argc;
unsigned int timeout_ms; // 0 = no limit
unsigned int _pad;
char export_name[GVM_MAX_EXPORT_NAME];
unsigned long long argv[GVM_MAX_ARGS];
} gvm_call_in;
typedef struct _gvm_call_out {
unsigned long long rv;
int status;
char err_msg[128];
} gvm_call_out;
typedef struct _gvm_unload_in {
unsigned int module_id;
} gvm_unload_in;
typedef struct _gvm_module_entry {
unsigned int id;
unsigned int wasm_size;
unsigned long long hash;
unsigned int exports;
unsigned int mem_pages;
unsigned long long pool_bytes;
char name[GVM_MAX_MODULE_NAME];
} gvm_module_entry;
typedef struct _gvm_list_out {
unsigned int count;
unsigned int pad;
gvm_module_entry entries[GVM_MAX_MODULES];
} gvm_list_out;
typedef struct _gvm_info_in {
unsigned int module_id;
} gvm_info_in;
#define GVM_MAX_INFO_EXPORTS 64
#define GVM_MAX_INFO_IMPORTS 64
#define GVM_INFO_NAME_LEN 48
typedef struct _gvm_info_out {
gvm_module_entry base;
unsigned int export_count;
unsigned int import_count;
char exports[GVM_MAX_INFO_EXPORTS][GVM_INFO_NAME_LEN];
char imports[GVM_MAX_INFO_IMPORTS][GVM_INFO_NAME_LEN];
int status;
char err_msg[128];
} gvm_info_out;
// TAIL_LOG: driver keeps a ring of recent DbgPrint lines. GUI polls this to
// show live driver output without needing DebugView/WinDbg.
#define GVM_LOG_ENTRY_LEN 200
#define GVM_LOG_ENTRIES 256
typedef struct _gvm_log_entry {
unsigned long long timestamp_100ns;
char line[GVM_LOG_ENTRY_LEN];
} gvm_log_entry;
typedef struct _gvm_tail_in {
unsigned long long last_seq; // returns entries with seq > last_seq
} gvm_tail_in;
#define GVM_MAX_READ_GUEST_BYTES (16 * 1024)
typedef struct _gvm_read_guest_in {
unsigned int module_id;
unsigned int offset;
unsigned int length;
} gvm_read_guest_in;
typedef struct _gvm_read_guest_out {
int status;
unsigned int length;
unsigned char data[GVM_MAX_READ_GUEST_BYTES];
char err_msg[128];
} gvm_read_guest_out;
typedef struct _gvm_tail_out {
unsigned long long next_seq; // pass this back as last_seq next time
unsigned int count;
unsigned int dropped; // events dropped since last poll (ring wrap)
gvm_log_entry entries[GVM_LOG_ENTRIES];
} gvm_tail_out;
// trace ring: records every wasm->host import invocation when tracing is
// enabled, plus trap/error captures. lets developers watch their guest
// modules run in real time and see exactly what triggered a failure.
#define GVM_TRACE_ENTRIES 512
#define GVM_TRACE_NAME_LEN 48
// kind field values
#define GVM_TRK_IMPORT 1 // host import invoked
#define GVM_TRK_CALL 2 // export call started
#define GVM_TRK_RETURN 3 // export call returned
#define GVM_TRK_TRAP 4 // guest trapped (OOB, div0, unreachable, timeout)
typedef struct _gvm_trace_entry {
unsigned long long timestamp_100ns;
unsigned int module_id;
unsigned int kind;
unsigned int thread_id; // OS TID of the thread that made the call
unsigned int irql; // KIRQL at the time
unsigned int argc;
unsigned int _pad;
unsigned long long argv[4]; // up to 4 args captured
unsigned long long rv; // return value (or 0 for void)
char name[GVM_TRACE_NAME_LEN];
} gvm_trace_entry;
typedef struct _gvm_trace_in {
unsigned long long last_seq;
} gvm_trace_in;
typedef struct _gvm_trace_out {
unsigned long long next_seq;
unsigned int count;
unsigned int dropped;
gvm_trace_entry entries[GVM_TRACE_ENTRIES];
} gvm_trace_out;
// trace control
#define GVM_TRACE_OFF 0
#define GVM_TRACE_ON_ALL 1 // trace every module
#define GVM_TRACE_ON_MODULE 2 // trace only module_id
typedef struct _gvm_trace_ctl_in {
unsigned int mode;
unsigned int module_id; // used when mode == GVM_TRACE_ON_MODULE
} gvm_trace_ctl_in;
+29
View File
@@ -0,0 +1,29 @@
@echo off
setlocal
set CLANG=%LLVM_HOME%\bin\clang.exe
if not exist "%CLANG%" set CLANG=C:\Program Files\LLVM\bin\clang.exe
if not exist "%CLANG%" (
echo [X] clang not found. install LLVM or set LLVM_HOME.
exit /b 1
)
set W3=..\..\driver\wasm3
"%CLANG%" -O2 -g -fsanitize=fuzzer,address ^
-DM3_IMPLEMENT_ERROR_STRINGS ^
-Dd_m3HasFloat=0 ^
-I"%W3%" ^
fuzz_parse.c ^
"%W3%\m3_bind.c" "%W3%\m3_code.c" "%W3%\m3_compile.c" ^
"%W3%\m3_core.c" "%W3%\m3_env.c" "%W3%\m3_exec.c" ^
"%W3%\m3_function.c" "%W3%\m3_info.c" "%W3%\m3_module.c" ^
"%W3%\m3_parse.c" "%W3%\m3_validate.c" ^
-o fuzz_parse.exe
if not exist corpus mkdir corpus
if exist ..\..\sample_guest\sample_guest.wasm copy /Y ..\..\sample_guest\sample_guest.wasm corpus\ >nul
echo [+] built fuzz_parse.exe. run with:
echo fuzz_parse.exe corpus -max_len=65536 -jobs=4
endlocal
+46
View File
@@ -0,0 +1,46 @@
/* fuzz_parse.c - libfuzzer target for m3_ParseModule.
*
* exercises the wasm3 parser with attacker-controlled bytes.
* kernel driver embeds the same parser; parser bugs here = potential
* bugcheck when someone loads a crafted .wasm via IOCTL_GVM_LOAD_MODULE.
*
* build (clang):
* clang -O2 -g -fsanitize=fuzzer,address \
* -DM3_IMPLEMENT_ERROR_STRINGS \
* -I../../driver/wasm3 \
* fuzz_parse.c ../../driver/wasm3/m3_*.c \
* -o fuzz_parse.exe
*
* run:
* fuzz_parse.exe corpus/ -max_len=65536
*
* seed corpus:
* mkdir corpus && cp ../../sample_guest/sample_guest.wasm corpus/
*/
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "wasm3.h"
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
if (size < 8 || size > (16u * 1024u * 1024u)) return 0;
IM3Environment env = m3_NewEnvironment();
if (!env) return 0;
IM3Runtime rt = m3_NewRuntime(env, 64 * 1024, NULL);
if (!rt) { m3_FreeEnvironment(env); return 0; }
IM3Module mod = NULL;
M3Result r = m3_ParseModule(env, &mod, data, (uint32_t)size);
if (!r && mod) {
m3_LoadModule(rt, mod);
// no m3_Call here. parse+load exercises the surface we care about.
// if wasm3 signals load failure, module is not owned by us to free.
}
m3_FreeRuntime(rt);
m3_FreeEnvironment(env);
return 0;
}
+233
View File
@@ -0,0 +1,233 @@
/* gvm_ext.cpp - WinDbg extension for inspecting Goodmans state.
*
* bang commands:
* !gvm.modules enumerate loaded wasm modules from the driver
* !gvm.info <id> detailed info on one module (name/size/hash/caps)
* !gvm.mem <id> <off> <len> dump guest linear memory
* !gvm.help
*
* load with:
* .load path\to\gvm_ext.dll
*
* requires:
* - Goodmans.sys loaded with symbols (Goodmans.pdb) in .sympath
* - live kernel debugging OR full/kernel minidump
*/
#include <windows.h>
#include <dbgeng.h>
#include <stdio.h>
#include <string.h>
extern "C" {
IDebugClient* g_client = nullptr;
IDebugControl* g_control = nullptr;
IDebugSymbols* g_symbols = nullptr;
IDebugDataSpaces* g_data = nullptr;
}
// mirror of gvm_module layout. must stay in sync with driver/inc/gvm.h
#pragma pack(push, 8)
struct gvm_module_dbg {
ULONG id;
UCHAR used;
UCHAR _pad0[7];
ULONG64 env;
ULONG64 runtime;
ULONG64 module;
ULONG64 wasm_bytes;
ULONG wasm_size;
ULONG _pad1;
ULONG64 hash;
volatile LONG refcount;
ULONG _pad2;
ULONG64 call_mutex[6]; // KMUTEX opaque
volatile LONG64 pool_used;
LONG64 pool_budget;
ULONG caps;
ULONG _pad3;
volatile ULONG64 exec_deadline_qpc;
CHAR name[64];
};
#pragma pack(pop)
#define GVM_MAX_MODULES 32
static HRESULT read_kmem(ULONG64 addr, void* buf, ULONG size)
{
if (!g_data) return E_FAIL;
ULONG got = 0;
HRESULT hr = g_data->ReadVirtual(addr, buf, size, &got);
if (SUCCEEDED(hr) && got != size) return E_FAIL;
return hr;
}
static HRESULT resolve_symbol(const char* name, ULONG64* out)
{
if (!g_symbols) return E_FAIL;
return g_symbols->GetOffsetByName(name, out);
}
extern "C" __declspec(dllexport) HRESULT CALLBACK
DebugExtensionInitialize(PULONG version, PULONG flags)
{
*version = DEBUG_EXTENSION_VERSION(1, 0);
*flags = 0;
if (DebugCreate(__uuidof(IDebugClient), (void**)&g_client) != S_OK)
return E_FAIL;
g_client->QueryInterface(__uuidof(IDebugControl), (void**)&g_control);
g_client->QueryInterface(__uuidof(IDebugSymbols), (void**)&g_symbols);
g_client->QueryInterface(__uuidof(IDebugDataSpaces), (void**)&g_data);
return S_OK;
}
extern "C" __declspec(dllexport) void CALLBACK
DebugExtensionUninitialize(void)
{
if (g_data) g_data->Release();
if (g_symbols) g_symbols->Release();
if (g_control) g_control->Release();
if (g_client) g_client->Release();
g_data = nullptr; g_symbols = nullptr; g_control = nullptr; g_client = nullptr;
}
static void printf_ext(const char* fmt, ...)
{
va_list ap; va_start(ap, fmt);
char buf[1024];
_vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, ap);
va_end(ap);
if (g_control) g_control->Output(DEBUG_OUTPUT_NORMAL, "%s", buf);
}
static void print_cap_string(char* out, size_t out_sz, ULONG caps)
{
static const char* names[] = {
"ALLOC","READ","WRITE","MSR_R","MSR_W","PHYS","CPUID","CB","HCALL","INTRO"
};
out[0] = 0;
if (caps == 0xFFFFFFFFu) { strncpy_s(out, out_sz, "ALL", _TRUNCATE); return; }
for (int i = 0; i < 10; i++) {
if (caps & (1u << i)) {
if (out[0]) strncat_s(out, out_sz, "|", _TRUNCATE);
strncat_s(out, out_sz, names[i], _TRUNCATE);
}
}
}
extern "C" __declspec(dllexport) HRESULT CALLBACK
modules(IDebugClient* client, PCSTR args)
{
UNREFERENCED_PARAMETER(client);
UNREFERENCED_PARAMETER(args);
ULONG64 base = 0;
if (FAILED(resolve_symbol("Goodmans!g_modules", &base))) {
printf_ext("[!] cannot resolve Goodmans!g_modules. is the .pdb loaded?\n");
return E_FAIL;
}
printf_ext("%-4s %-6s %-10s %-10s %-16s %-24s %s\n",
"id","wasm","pool","budget","hash","caps","name");
printf_ext("---- ------ ---------- ---------- ---------------- ------------------------ ----\n");
unsigned int active = 0;
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
gvm_module_dbg m = {};
if (FAILED(read_kmem(base + i * sizeof(m), &m, sizeof(m)))) break;
if (!m.used) continue;
active++;
char capstr[128] = {};
print_cap_string(capstr, sizeof(capstr), m.caps);
printf_ext("%-4u %-6u %-10lld %-10lld %016llx %-24s %s\n",
m.id, m.wasm_size, m.pool_used, m.pool_budget, m.hash, capstr, m.name);
}
printf_ext("\n(%u active)\n", active);
return S_OK;
}
extern "C" __declspec(dllexport) HRESULT CALLBACK
info(IDebugClient* client, PCSTR args)
{
UNREFERENCED_PARAMETER(client);
if (!args || !*args) { printf_ext("usage: !gvm.info <id>\n"); return E_INVALIDARG; }
unsigned int id = (unsigned int)strtoul(args, nullptr, 0);
if (id == 0 || id > GVM_MAX_MODULES) { printf_ext("bad id\n"); return E_INVALIDARG; }
ULONG64 base = 0;
if (FAILED(resolve_symbol("Goodmans!g_modules", &base))) return E_FAIL;
gvm_module_dbg m = {};
ULONG64 slot = base + (id - 1) * sizeof(m);
if (FAILED(read_kmem(slot, &m, sizeof(m)))) return E_FAIL;
if (!m.used) { printf_ext("slot %u is free\n", id); return S_OK; }
char capstr[128] = {};
print_cap_string(capstr, sizeof(capstr), m.caps);
printf_ext("id: %u\n", m.id);
printf_ext("name: %s\n", m.name);
printf_ext("wasm size: %u bytes\n", m.wasm_size);
printf_ext("wasm bytes ka: 0x%016llx\n", m.wasm_bytes);
printf_ext("hash: %016llx\n", m.hash);
printf_ext("refcount: %d\n", m.refcount);
printf_ext("pool used: %lld / %lld\n", m.pool_used, m.pool_budget);
printf_ext("caps: %08x (%s)\n", m.caps, capstr);
printf_ext("env ka: 0x%016llx\n", m.env);
printf_ext("runtime ka: 0x%016llx\n", m.runtime);
printf_ext("module ka: 0x%016llx\n", m.module);
return S_OK;
}
extern "C" __declspec(dllexport) HRESULT CALLBACK
mem(IDebugClient* client, PCSTR args)
{
UNREFERENCED_PARAMETER(client);
if (!args || !*args) {
printf_ext("usage: !gvm.mem <id> <off> <len>\n");
return E_INVALIDARG;
}
unsigned int id, off, len;
if (sscanf_s(args, "%u %x %x", &id, &off, &len) != 3) {
if (sscanf_s(args, "%u %u %u", &id, &off, &len) != 3) {
printf_ext("bad args\n"); return E_INVALIDARG;
}
}
if (id == 0 || id > GVM_MAX_MODULES || len == 0 || len > 4096) {
printf_ext("bad args (len max 4096)\n"); return E_INVALIDARG;
}
ULONG64 base = 0;
if (FAILED(resolve_symbol("Goodmans!g_modules", &base))) return E_FAIL;
gvm_module_dbg m = {};
if (FAILED(read_kmem(base + (id - 1) * sizeof(m), &m, sizeof(m))) || !m.used || !m.runtime) {
printf_ext("slot not loaded\n"); return E_FAIL;
}
// wasm3 stores linear memory at runtime->memory.mallocated + offset.
// walking the M3Runtime struct requires matching wasm3's layout exactly.
// for portability, emit a KD-side memory command using a symbolic offset
// once the exact struct is resolved via .lookup_field.
printf_ext("[!] guest linear memory walk requires wasm3 pdb. read runtime@0x%016llx manually with dt or !address\n",
m.runtime);
printf_ext(" len=%u off=%u\n", len, off);
return S_OK;
}
extern "C" __declspec(dllexport) HRESULT CALLBACK
help(IDebugClient* client, PCSTR args)
{
UNREFERENCED_PARAMETER(client);
UNREFERENCED_PARAMETER(args);
printf_ext("Goodmans WinDbg extension\n");
printf_ext(" !gvm.modules enumerate loaded modules\n");
printf_ext(" !gvm.info <id> detailed info for module id\n");
printf_ext(" !gvm.mem <id> <off> <len> dump guest linear memory (needs wasm3 pdb)\n");
printf_ext(" !gvm.help this help\n");
printf_ext("\nrequires Goodmans.pdb in .sympath.\n");
return S_OK;
}
+8
View File
@@ -0,0 +1,8 @@
LIBRARY gvm_ext
EXPORTS
DebugExtensionInitialize
DebugExtensionUninitialize
modules
info
mem
help
+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0004}</ProjectGuid>
<RootNamespace>gvm_ext</RootNamespace>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<UseDebugLibraries Condition="'$(Configuration)'=='Debug'">true</UseDebugLibraries>
<UseDebugLibraries Condition="'$(Configuration)'=='Release'">false</UseDebugLibraries>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<LanguageStandard>stdcpp17</LanguageStandard>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<ModuleDefinitionFile>gvm_ext.def</ModuleDefinitionFile>
<AdditionalDependencies>dbgeng.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="gvm_ext.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>