mirror of
https://github.com/zer0condition/GoodmansKernel/
synced 2026-08-19 01:14:35 +00:00
initial commit
This commit is contained in:
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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 -> $(DeployDir)" Importance="high" />
|
||||
<Exec Command="if exist "$(DeployDir)\GoodmansTest.pfx" for /f "delims=" %%s in ('where signtool 2^>nul') do "%%s" sign /fd sha256 /f "$(DeployDir)\GoodmansTest.pfx" /p goodmans "$(SignedSys)"" 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
@@ -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
@@ -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();
|
||||
}
|
||||
@@ -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__)
|
||||
@@ -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 = ∩︀
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/* corecrt.h - blocks ucrt/corecrt.h chain */
|
||||
#pragma once
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
/* malloc.h - blocks km/crt/malloc.h, decls in stdlib.h */
|
||||
#pragma once
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>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 -> $(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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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); }
|
||||
@@ -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()
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;'>"
|
||||
"• wasm3 , MIT, Volodymyr Shymanskyy / Steven Massey<br>"
|
||||
"• Qt 6 , LGPLv3, The Qt Company<br>"
|
||||
"• 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();
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+208
@@ -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;
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource prefix="/">
|
||||
<file>style.qss</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>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 -> $(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>
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
LIBRARY gvm_ext
|
||||
EXPORTS
|
||||
DebugExtensionInitialize
|
||||
DebugExtensionUninitialize
|
||||
modules
|
||||
info
|
||||
mem
|
||||
help
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user