背景
最近在用 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 呢?
看這裡: