Windows 下如何調試 PowerShell

背景

最近在用 PowerShell 的時候,發現一些地方特別有意思。於是就萌生了看源代碼的想法,單看肯定不過癮,調試起來才有意思。於是就有了這個,記錄下。

調試 PowerShell 主要分為兩種方式:通過 VS 直接編譯運行源代碼和通過 WinDbg 來調試。

由於 PowerShell 跨平台的特性,由於我目前只有 Windows 的訴求,所以以下內容將圍繞 Windows 來進行。其他平台可以參照官方文檔,可以在這裡看到:

PowerShell/docs/building at master · PowerShell/PowerShell

準備工作

拉代碼

第一步自然就是去 github 把代碼 clone 下來了。地址是:

//github.com/PowerShell/PowerShell

截至到2022年7月16日,建議不要直接直接用 master 分支,根據文檔介紹,master 分支為最新版本,並非穩定版本。也就是我們平常看到的 preview 的版本。而且,這裡強烈建議不用是因為目前 preview 的版本已經切換到 .net 7 了,而 .net 7 的 sos 我沒找到(如果知道怎麼找到的小夥伴可以告訴我,感謝~)。所以我這裡是採用最新的穩定版,也就是 7.2.5 版本的源代碼進行。

準備環境

第二步就是準備好編譯環境。因為 PowerShell 全部是 C# 實現的,自然就是依賴 dotnet 環境的了。

官方給了我們一個非常好的腳本,非常便利,在源代碼的根目錄下執行:

Import-Module ./build.psm1
Start-PSBootstrap

或者直接:

Install-Dotnet

就可以完成安裝了。

如果只是這樣,就結束了,是不是過於順利了,就沒有這篇文章的必要了。

由於我安裝了 VS2022 preview 的版本,它順帶幫我安裝了 .net 6.0.400-preview.22330.6 的版本,而且還不讓卸載。因此我最新的版本始終是 6.0.400-preview,後續的編譯,它就死命安裝不了要的版本。也就是 6.0.203。

於是就只能從 dot.net 手動下載安裝了。

方式一:通過 WinDbg 調試

這種方式適用於不想安裝 VS,但需要詳細調試的同學。(不要問我為啥不用 VSC,看代碼 VSC 確實不錯,但調試,還得 WinDbg 來)

編譯代碼

這裡我們直接採用官方推薦的方式:

Import-Module ./build.psm1
Start-PSBuild

然後就出現下面的錯誤:

VERBOSE: In Find-DotNet
WARNING: The 'dotnet' in the current path can't find SDK version 6.0.203, prepending C:\Users\frend\AppData\Local\Microsoft\dotnet to PATH.
WARNING: The currently installed .NET Command Line Tools is not the required version.

Installed version: 6.0.400-preview.22330
Required version: 6.0.203

Fix steps:

1. Remove the installed version from:
    - on windows '$env:LOCALAPPDATA\Microsoft\dotnet'
    - on macOS and linux '$env:HOME/.dotnet'
2. Run Start-PSBootstrap or Install-Dotnet
3. Start-PSBuild -Clean

可是我明明安裝了的呀:

- dotnet --list-sdks
6.0.203 [C:\Program Files\dotnet\sdk]
6.0.302 [C:\Program Files\dotnet\sdk]
6.0.400-preview.22330.6 [C:\Program Files\dotnet\sdk]

於是,就去找了下對應的腳本。在 [ps code root]/build.psm1 文件中的 Start-PSBuild → Find-Dotnet → Get-LatestInstalledSDK

function Get-LatestInstalledSDK {
    Start-NativeExecution -sb {
        dotnet --list-sdks | Select-String -Pattern '\d*.\d*.\d*(-\w*\.\d*)?' | ForEach-Object { [System.Management.Automation.SemanticVersion]::new($_.matches.value) } | Sort-Object -Descending | Select-Object -First 1
    } -IgnoreExitcode 2> $null
}

好傢夥,直接就是拿最新的作為我安裝的版本,所以導致匹配不上,無法開始編譯。由於我這裡安裝了 6.0.203 的,所以我就修改了下。根據我的安裝情況,改成讓他返回我想要讓他返回的版本了(如下加粗版本)。這裡需要根據自己實際情況修改!!!

dotnet --list-sdks | Select-String -Pattern '\d*.\d*.\d*(-\w*\.\d*)?' | ForEach-Object { [System.Management.Automation.SemanticVersion]::new($_.matches.value) } | Sort-Object -Descending | Select-Object -First **3 | Sort-Object | Select-Object -First 1**

於是我們重來一遍導入,編譯。就能成功啦!

然後就能找到啦 ./src/powershell-win-core/bin/Debug/net6.0/win7-x64/publish/pwsh.exe

調試代碼

打開 WinDbg preview → Launch executable(advanced)

Executable 中填入上一步編譯出來的地址,我的是這樣的:C:\Users\frend\source\repos\dotnet\PowerShell\src\powershell-win-core\bin\Debug\net6.0\win7-x64\pwsh.exe

Arguments,填入你想調試的命令就好啦。我的是:ex bypass -nop -Command Invoke-webRequest www.baidu.com

其他就可以不管了。來,開始調試~

由於我們要調試的是託管代碼,在啟動點我們沒辦法打斷點,得先等 sos 和 clr 加載好了之後才行。

這裡我先打了一個clr上的斷點:

bp coreclr!CallDescrWorkerInternal

待斷點斷到了,再加上 PowerShell 啟動代碼的斷點。注意,託管代碼需要用 sos 的 !bpmd 來打斷點:

0:000> !bpmd pwsh Microsoft.PowerShell.ManagedPSEntry.Main
Adding pending breakpoints...
0:000> bd 0
0:000> g
ModLoad: 00007ff8`d7460000 00007ff8`d7478000   C:\WINDOWS\SYSTEM32\kernel.appcore.dll
ModLoad: 00000288`fa860000 00000288`fa884000   C:\Users\frend\source\repos\dotnet\PowerShell\src\powershell-win-core\bin\Debug\net6.0\win7-x64\pwsh.dll
ModLoad: 00000288`fa860000 00000288`fa884000   C:\Users\frend\source\repos\dotnet\PowerShell\src\powershell-win-core\bin\Debug\net6.0\win7-x64\pwsh.dll
(45e4.6834): CLR notification exception - code e0444143 (first chance)
ModLoad: 00000288`fa8a0000 00000288`fa8ae000   C:\Users\frend\source\repos\dotnet\PowerShell\src\powershell-win-core\bin\Debug\net6.0\win7-x64\System.Runtime.dll
(45e4.6834): CLR notification exception - code e0444143 (first chance)
ModLoad: 00000288`fa8b0000 00000288`fa90e000   C:\Users\frend\source\repos\dotnet\PowerShell\src\powershell-win-core\bin\Debug\net6.0\win7-x64\Microsoft.PowerShell.ConsoleHost.dll
(45e4.6834): CLR notification exception - code e0444143 (first chance)
(45e4.6834): CLR notification exception - code e0444143 (first chance)
JITTED pwsh!Microsoft.PowerShell.ManagedPSEntry.Main(System.String[])
Setting breakpoint: bp 00007FF7E6F82530 [Microsoft.PowerShell.ManagedPSEntry.Main(System.String[])]
Breakpoint 1 hit
*** WARNING: Unable to verify checksum for C:\Users\frend\source\repos\dotnet\PowerShell\src\powershell-win-core\bin\Debug\net6.0\win7-x64\pwsh.dll
pwsh!Microsoft.PowerShell.ManagedPSEntry.Main:
00007ff7`e6f82530 55              push    rbp

然後,就可以命中斷點了。

這裡可能不會自動打開並且跳轉到源代碼中,再 t 幾下,就會自動打開的。最終就會變成這樣。

於是,我們就可以開始順利的調試了。

方式二:通過 VS 調試

這種方式相對就比較簡單了,這裡用 VS2022 來做演示。

記得一定要安裝對應的 dotnet sdk。具體的版本可以在源代碼根目錄的 global.json 中查看。

直接在源代碼中打開 PowerShell.sln 文件即可打開 PowerShell 項目。然後 build 一下,記得,Windows 下啟動目錄應該是:powershell-win-core

然後就可以配置調試的參數了:

在項目 powershell-win-core 上鼠標右擊,選擇屬性 → 調試 → Open debug launch profiles UI

然後就是跟 WinDbg 調試類似的參數了。如下圖:

然後,再你想觀察的代碼處打上斷點,點擊運行就好啦。

總結

其實官方文檔已經比較完善了。基本都比較簡單。特別是通過 VS,簡直方便的不要不要的。

對於基於 dotnet 的 PowerShell,肯定還會遇到 dotnet 上的問題,那怎麼調試 dotnet 呢?

看這裡:

//github.com/dotnet/runtime