Windows 下如何除錯 PowerShell

frendguo發表於2022-07-17

背景

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

除錯 PowerShell 主要分為兩種方式:通過 VS 直接編譯執行原始碼和通過 WinDbg 來除錯。

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

PowerShell/docs/building at master · PowerShell/PowerShell

準備工作

拉程式碼

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

https://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 呢?

看這裡:

https://github.com/dotnet/runtime

相關文章