Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
541 changes: 534 additions & 7 deletions .github/workflows/build-windows.yml

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ native/**/x86/
native/**/ARM64/
native/**/.vs/
scripts/TTDDownload/
packaging/dotnet-tool/bin/
packaging/dotnet-tool/obj/
artifacts/
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,23 @@ The built executable is:
target\debug\windbg-tool.exe
```

Release ZIP artifacts are built by the Windows packaging workflow. Run **Windows packages** manually with an unprefixed semantic version such as `0.1.0` to publish the `v0.1.0` GitHub Release. Select **dry_run** to build and validate the packages without creating a tag or release. The release contains `windbg-tool-x64.zip` and `windbg-tool-arm64.zip`.
Release ZIP artifacts are built by the Windows packaging workflow. Run **Windows packages** manually with an unprefixed semantic version such as `0.1.0` to publish the `v0.1.0` GitHub Release. Select **dry_run** to build, sign, and validate the packages without creating a tag or release. The release contains `windbg-tool-x64.zip`, `windbg-tool-arm64.zip`, the .NET tool packages, and `checksums.txt`.

The local equivalent uses `cargo xtask deps --arch <amd64|arm64>`, `cargo xtask native-build --arch <amd64|arm64> --static-crt`, a release build for the matching MSVC Rust target, and `cargo xtask package --profile release`. Both packages statically link the MSVC runtime and bundle their required TTD, DbgEng, symbol, and native-bridge DLLs.

For deeper setup, test commands, runtime details, and workspace notes, see [the development guide](docs/development.md).

## Install as a .NET tool

On Windows with the .NET 10 SDK or later, install the global tool containing signed Windows payloads from NuGet:

```powershell
dotnet tool install --global Devolutions.WinDbg.Tool
windbg-tool discover
```

The package includes `win-x64` and `win-arm64` payloads and exposes the same `windbg-tool` command as the release ZIPs.

## CLI quick start

Some commands work without loading a trace or starting the daemon:
Expand Down
33 changes: 31 additions & 2 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,41 @@ Cross-compiling the ARM64 package from an x64 machine requires the Visual Studio

### Publishing a GitHub Release

Run the **Windows packages** workflow with **Run workflow** and enter an unprefixed semantic version such as `0.1.0`. After both package matrix jobs complete, the workflow creates the `v0.1.0` tag at the commit selected for the dispatch and publishes a GitHub Release containing:
Run the **Windows packages** workflow with **Run workflow** and enter an unprefixed semantic version such as `0.1.0`. After both package matrix jobs complete, the workflow signs the project-owned Windows binaries, creates the .NET tool packages, and publishes a GitHub Release containing:

- `windbg-tool-x64.zip`
- `windbg-tool-arm64.zip`
- `Devolutions.WinDbg.Tool.win-x64.<version>.nupkg`
- `Devolutions.WinDbg.Tool.win-arm64.<version>.nupkg`
- `Devolutions.WinDbg.Tool.any.<version>.nupkg`
- `Devolutions.WinDbg.Tool.<version>.nupkg`
- `checksums.txt`

Select **dry_run** to build both ZIPs, validate their contents, validate the version, and confirm that the tag is available without creating a tag or GitHub Release. The workflow rejects existing tags and invalid versions rather than replacing a release. Each ZIP contains the statically linked Rust executable and native bridge plus the required dynamic TTD Replay, DbgEng, and symbol runtime DLLs.
Select **dry_run** to build both ZIPs, sign the Windows payloads, validate their contents, validate the version, and confirm that the tag is available without creating a tag or GitHub Release. The workflow rejects existing tags and invalid versions rather than replacing a release. Each ZIP contains the statically linked Rust executable and native bridge plus the required dynamic TTD Replay, DbgEng, and symbol runtime DLLs.

The workflow uses the `publish-prod` environment by default; choose `test` to use `publish-test`. Configure the Azure Trusted Signing secrets for every dispatch, including dry runs, and add `NUGET_BOT_USERNAME` when NuGet publishing is enabled:

- `AZURE_CLIENT_ID`
- `AZURE_TENANT_ID`
- `AZURE_SUBSCRIPTION_ID`
- `TRUSTED_SIGNING_ENDPOINT`
- `TRUSTED_SIGNING_ACCOUNT_NAME`
- `TRUSTED_SIGNING_PROFILE_NAME`
- `NUGET_BOT_USERNAME`

The optional `TRUSTED_SIGNING_TIMESTAMP_SERVER` environment variable defaults to `http://timestamp.acs.microsoft.com/`. The workflow obtains the Azure Artifact Signing access token with GitHub OIDC and publishes through `NuGet/login@v1`; no long-lived NuGet API key is stored. It signs `windbg-tool.exe`, `ttd_replay_bridge.dll`, and the generated .NET launcher assemblies while preserving the signatures on Microsoft-provided runtime DLLs. The ZIP and NuGet containers themselves are not Authenticode-signed; they contain the signed Windows payloads.

For a local package smoke test, place the architecture-specific release payloads under `target\dotnet-tool\win-x64` and `target\dotnet-tool\win-arm64`, then run:

```powershell
$stagingRoot = Resolve-Path target\dotnet-tool
dotnet pack packaging\dotnet-tool\Devolutions.WinDbg.Tool.csproj `
-c Release `
-p:PackageVersion=0.1.0 `
-p:WindbgToolStagingRoot=$stagingRoot `
-o artifacts\nuget
./scripts\Test-WindbgDotnetToolPackage.ps1 -Version 0.1.0 -PackageDir artifacts\nuget
```

To smoke-test the packaged MCP server:

Expand Down
51 changes: 51 additions & 0 deletions packaging/dotnet-tool/Devolutions.WinDbg.Tool.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>

<PackAsTool>true</PackAsTool>
<ToolCommandName>windbg-tool</ToolCommandName>
<PackageId>Devolutions.WinDbg.Tool</PackageId>

<Version Condition="'$(Version)' == ''">0.1.0</Version>
<Authors>Devolutions</Authors>
<Description>RID-specific dotnet tool wrapper around the signed windbg-tool Windows executable.</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/Devolutions/windbg-tool</PackageProjectUrl>
<PackageTags>windbg;debugging;ttd;time-travel-debugging;dotnet-tool</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/Devolutions/windbg-tool</RepositoryUrl>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>

<NoPackageAnalysis>true</NoPackageAnalysis>
<ToolPackageRuntimeIdentifiers>win-x64;win-arm64;any</ToolPackageRuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-arm64;any</RuntimeIdentifiers>
<RollForward>Major</RollForward>
</PropertyGroup>

<PropertyGroup>
<WindbgToolStagingRoot Condition="'$(WindbgToolStagingRoot)' == ''">$(MSBuildProjectDirectory)/../../target/dotnet-tool</WindbgToolStagingRoot>
<WindbgToolPayloadDirectory>$(WindbgToolStagingRoot)/$(RuntimeIdentifier)</WindbgToolPayloadDirectory>
</PropertyGroup>

<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="README.md" />

<None Include="$(WindbgToolPayloadDirectory)/**/*"
Pack="true"
PackagePath="tools/net10.0/$(RuntimeIdentifier)/%(RecursiveDir)%(Filename)%(Extension)"
Visible="false"
Condition="('$(RuntimeIdentifier)' == 'win-x64' or '$(RuntimeIdentifier)' == 'win-arm64') and Exists('$(WindbgToolPayloadDirectory)')" />
</ItemGroup>

<Target Name="ValidateWindbgToolStaging"
BeforeTargets="GenerateNuspec"
Condition="'$(RuntimeIdentifier)' == 'win-x64' or '$(RuntimeIdentifier)' == 'win-arm64'">
<Error Condition="!Exists('$(WindbgToolPayloadDirectory)/windbg-tool.exe')"
Text="Missing staged windbg-tool executable for RuntimeIdentifier '$(RuntimeIdentifier)': $(WindbgToolPayloadDirectory)/windbg-tool.exe" />
<Error Condition="!Exists('$(WindbgToolPayloadDirectory)/ttd_replay_bridge.dll')"
Text="Missing staged TTD replay bridge for RuntimeIdentifier '$(RuntimeIdentifier)': $(WindbgToolPayloadDirectory)/ttd_replay_bridge.dll" />
</Target>
</Project>
50 changes: 50 additions & 0 deletions packaging/dotnet-tool/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;

return Run(args);

static int Run(string[] args)
{
string nativeExecutablePath = Path.Combine(AppContext.BaseDirectory, "windbg-tool.exe");

if (!File.Exists(nativeExecutablePath))
{
Console.Error.WriteLine("No native windbg-tool executable is available for this runtime identifier in the installed package.");
return 1;
}

var processStartInfo = new ProcessStartInfo(nativeExecutablePath)
{
UseShellExecute = false,
};

foreach (string argument in args)
{
processStartInfo.ArgumentList.Add(argument);
}

try
{
using Process? process = Process.Start(processStartInfo);
if (process is null)
{
Console.Error.WriteLine("Unable to start the native windbg-tool executable.");
return 1;
}

process.WaitForExit();
return process.ExitCode;
}
catch (Win32Exception ex)
{
Console.Error.WriteLine($"Unable to start the native windbg-tool executable: {ex.Message}");
return 1;
}
catch (InvalidOperationException ex)
{
Console.Error.WriteLine($"Unable to start the native windbg-tool executable: {ex.Message}");
return 1;
}
}
12 changes: 12 additions & 0 deletions packaging/dotnet-tool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Devolutions.WinDbg.Tool

`Devolutions.WinDbg.Tool` installs the `windbg-tool` command as a .NET 10 global
or local tool on Windows.

```powershell
dotnet tool install --global Devolutions.WinDbg.Tool
windbg-tool discover
```

The package contains architecture-specific signed payloads for `win-x64` and
`win-arm64`. The installed .NET SDK/runtime must be version 10 or later.
56 changes: 56 additions & 0 deletions scripts/Test-WindbgDotnetToolPackage.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
param(
[Parameter(Mandatory = $true)]
[string]$Version,

[string]$PackageDir = (Join-Path $PSScriptRoot "..\artifacts\nuget"),
[string]$ToolPath = (Join-Path ([System.IO.Path]::GetTempPath()) "windbg-tool-package-smoke-$PID")
)

$ErrorActionPreference = "Stop"

$packageSource = (Resolve-Path -LiteralPath $PackageDir).Path
$nugetConfig = Join-Path ([System.IO.Path]::GetTempPath()) "windbg-tool-package-smoke-$PID.nuget.config"

if (Test-Path -LiteralPath $ToolPath) {
Remove-Item -LiteralPath $ToolPath -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $ToolPath | Out-Null

@"
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="local" value="$packageSource" />
</packageSources>
</configuration>
"@ | Set-Content -LiteralPath $nugetConfig -Encoding utf8

try {
dotnet tool install Devolutions.WinDbg.Tool `
--tool-path $ToolPath `
--configfile $nugetConfig `
--version $Version

if ($LASTEXITCODE -ne 0) {
throw "dotnet tool install failed with exit code $LASTEXITCODE"
}

$toolExe = @("windbg-tool", "windbg-tool.exe", "windbg-tool.cmd") |
ForEach-Object { Join-Path $ToolPath $_ } |
Where-Object { Test-Path -LiteralPath $_ } |
Select-Object -First 1

if (-not $toolExe) {
throw "Installed tool shim not found under: $ToolPath"
}

& $toolExe discover | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "windbg-tool discover failed with exit code $LASTEXITCODE"
}
}
finally {
Remove-Item -LiteralPath $nugetConfig -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $ToolPath -Recurse -Force -ErrorAction SilentlyContinue
}