Идея возникла несколько лет назад по мере чтения документации Debugging Tools — набора инструментов, должно отметить, в хозяйстве нужного и необходимого, — а ее суть основывается на возможности просматривать типы данных, описание которых в MSDN относительно, либо отсутствует вовсе. Конечно же то, о чем речь пойдет далее не заменит полностью LiveKd, но вполне имеет право на жизнь в виду того, что, во-первых, не требует прав адинистратора, во-вторых, позволяет ознакомиться с большинством различных типов, являющихся «строительными блоками» системы.
Слова об отсутсвии прав администратора ничуть не преувеличены: kd и WinDbg вполне допускают запускать себя для просмотра дампов из-под учетной записи с ограниченными правами, — собственно, это и есть основа идеи: создать дамп процесса, содержащего наибольшее количество системных модулей, и загрузить оный дамп в отладчик, а тот в свою очередь подгрузит нужные отладочные символы и выдаст интересующие данные. Но давате по-порядку.
Из доступных пользователю с ограниченными правами процессов наибольший интерес вызывает explorer, так как соответствует критериям обозначенным в преамбуле.
PS C:> (ps explorer).Modules | sort ModuleName | ft -a
Львиная доля данных, способных заинтересовать исследователя, содержится в ntdll.dll и в urlmon.dll, если речь об ХР (есть кто еще ею пользуется?) или ole32.dll в случае «семерки» (другими системами, увы, не располагаю, ибо та же «семерка» поднята в VirtualBox под Linux'ом). Но здесь мы явно забегаем вперед, так как еще нужно автоматизировать процесс запуска отладчика с дампом explorer'а. И здесь, пожалуй, лучше всего начать с настройки переменных окружения, а именно добавить в PATH путь до отладочных утилит и задать переменную _NT_SYMBOL_PATH, например, srv*C:symbols*http://msdl.microsoft.com/download/symbols, после чего можно переходить непосредственно к кодингу. Запускам Vim или свою любимую IDE и пишем:
function Invoke-Debugger {
param(
[Switch]$WinDbg
)
begin {
$MiniDumpWriteDump = [PSObject].Assembly.GetType(
'System.Management.Automation.WindowsErrorReporting+NativeMethods'
).GetMethod('MiniDumpWriteDump', [Reflection.BindingFlags]40)
$DumpFile = "$($env:tmp)crash.dmp"
$Debugger = "$(switch ($WinDbg) {$true {'windbg'} $false {'kd'}}).exe"
# на случай, если использется что-то вроде Sysinternals Desktops
$Process = if (($$ = @(Get-Process explorer)).Length -gt 1) {
($$ | Sort-Object StartTime)[0]
} else { $$ }
}
process {
try {
$fs = [IO.File]::Create($DumpFile)
if (!$MiniDumpWriteDump.Invoke($null, @(
$Process.Handle, $Process.Id, $fs.SafeFileHandle, [UInt32]0,
[IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero
))) {
throw New-Object ComponentModel.Win32Exception(
[Runtime.InteropServices.Marshal]::GetLastWin32Error()
)
}
}
catch { Write-Error $_.Exception }
finally {
if ($fs -ne $null) {
$fs.Dispose()
$fs.Close()
}
}
}
end {
# если не удалось создать дамп
if ((Get-Item $DumpFile).Length -eq 0) {
Remove-Item $DumpFile -Force -ErrorAction 0
return
}
# в противном случае запускаем отладчик
Start-Process $Debugger -ArgumentList "-z $DumpFile $(if (
$Debugger -eq 'kd.exe'
) {'-y ' + $env:_NT_SYMBOL_PATH} else {'-Q'})"
}
}
Приведенную выше функцию удобно разместить в профиле PowerShell, но на вкус и цвет, а фломастеры у всех разные, можно оформить ее и как модуль. Лично мне удобней, чтобы эта функция была именно в профиле, так как вызвать ее можно сразу после запуска хоста PowerShell:
PS C:> Invoke-Debugger -WinDbg
И снова таки, мне больше нравится консоль, поэтому:
PS C:> Invoke-Debugger
Ну и ради чего все затевалось.
0:000> dt ole32!_system* /t ... 0:000> dt ole32!_system_information_class ... SystemTimeOfDayInformation = 0n3 ... 0:000> dt ole32!_system_timeofday_information +0x000 BootTime : _LARGE_INTEGER +0x008 CurrentTime : _LARGE_INTEGER +0x010 TimeZoneBias : _LARGE_INTEGER +0x018 TimeZoneId : Uint4B +0x01c Reserved : Uint4B +0x020 BootTimeBias : Uint8B +0x028 SleepTimeBias : Uint8B 0:000> ?? sizeof(ole32!_system_timeofday_information) unsigned int 0x30
Зная это, а также то, что NtQuerySystemInformation, которой и следует скармливать данную структуру, точнее ее сигнатура, имеется в сборке System.dll, можно получить uptime системы.
try {
$ptr = [Runtime.InteropServices.Marshal]::AllocHGlobal(48)
if ([Regex].Assembly.GetType(
'Microsoft.Win32.NativeMethods'
).GetMethod(
'NtQuerySystemInformation'
).Invoke($null, @(3, $ptr, 48, 0)) -ne 0) {
throw New-Object InvalidOperationException('Could not retrieve system uptime.')
}
'{0}.{1:D2}:{2:D2}:{3:D2}' -f ($$ = [TimeSpan]::FromMilliseconds((
[Runtime.InteropServices.Marshal]::ReadInt64($ptr, 8) -
[Runtime.InteropServices.Marshal]::ReadInt64($ptr)
) / 10000)).Days, $$.Hours, $$.Minutes, $$.Seconds
}
catch {
$_.Exception
}
finally {
if ($ptr -ne $null) {
[Runtime.InteropServices.Marshal]::FreeHGlobal($ptr)
}
}
Само собой, что получением uptime дело не ограничивается, напротив, перспективы очень даже впечатляют. Единственное, что может напрягать PowerShell разработчика, так это неизбежное, казалось бы, использование Add-Type для вызова апишных функций, но альтернативный взгляд на решение данной проблемы составляет отдельную тему для разговора.
Автор: GrigoriZakharov