Debugging Tools в помощь PowerShell разработчику

в 11:35, , рубрики: debug tools, powershell, разработка под windows

Идея возникла несколько лет назад по мере чтения документации 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 системы.

NtQuerySystemInformation

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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js