среда, 30 января 2013 г.

Работа со стеком в Kernel Mode

Просмотр стека, наверное, самая частая операция, которая выполняется при анализе аварийных дампов или отладке. И очень часто стека текущего потока нам мало: дедлоки, нарушение синхронизации при доступе к данным, "зависшие" операции ввода-вывода - все это приводит нас к необходимости вывести стеки всех потоков системы. Каким командами обычно мы пользуемся:

Вывод всех потоков для всех процессов:
!process 0 7
Поиск процесса и вывод стеков только для него
!process 0 0 proga.exe
!process addr 7

Еще вариант:
!stacks 2
Последний вариант интересен тем, что выводит также потоки для потока с ID = 0 ( IDLE ), команда !process "IDLE" за процесс не считает :).
Есть еще команда для вывода стеков активных потоков:
!running -i -t
Все варианты страдают двумя недостатками: проблемы с отображением юзермодных частей стеков ( чтобы они нормально отображались нужно переключаться в контекст конкретного процесса и получать список юзермодных модулей ); стеков много, а метод для фильтрации вывода по сути один - скопировать вывод и анализировать его какой нибудь внешней программой.

Поэтому мы решили немного упростить жизнь kernel-mode разработчиков и сделали скрипт stkwalk

Скрипт stkwalk

Если вы следите за нашим проектом, то, верно, знаете, что не давно мы выпустили новую версию pykd - 0.2.0.16. Если не знаете, то нужно поскорей узнать и обновить, потому что это необходимо для наших дальнейших упражнений. Если вы ленивы и воспользовались для установки pykd автоматический инсталлятор, то вы будете вознаграждены установкой га вашу машину целой толпы очень полезных скриптов. Среди них будет и stkwalk. Начнем знакомство:
!py stkwalk
Получим вывод стеков почти такой же, как выдает !process 0 0, но юзермодные части стеков выводятся более тщательно. Более того, если вы запустите скрипт на x64 системе, вы увидите также и WOW64 части стека. Польза +1.

Тут пора посмотреть, какие возможности предоставляет нам stkwalk. Спросим у него самого

3: kd> !py stkwalk --help
Stack walker. ver 1.0
Usage: stkwalk [options]

Options:
  -h, --help            show this help message and exit
  -p PROCESSFILTER, --process=PROCESSFILTER
                        process filter: boolean expression with python syntax
  -m MODULEFILTER, --module=MODULEFILTER
                        module filter: boolean expression with python syntax
  -f FUNCFILTER, --function=FUNCFILTER
                        function filter: boolean expression with python syntax
  -t THREADFILTER, --thread=THREADFILTER
                        thread filter: boolean expresion with python syntax
  -u, --unique          show only unique stacks
Мы тут видим возможность фильтрации по различным параметрам, с этим мы подробнее разберемся ниже. Последняя опция позволяет выводить только уникальные стеки. Пробуем:
3: kd> !py stkwalk -u
Stack walker. ver 1.0
Process ffffffff855a1c40
Name: System  Pid: 0x4

......

Thread ffffffff855f7d48, Process: System (ffffffff855a1c40)
ffffffff82c7de06 ffffffff8db6fc1c nt!KiSwapContext+26
ffffffff82c84fa5 ffffffff8db6fc30 nt!KiSwapThread+266
ffffffff82c838a3 ffffffff8db6fc68 nt!KiCommitThreadWait+1df
ffffffff82c84afd ffffffff8db6fc90 nt!KeRemoveQueueEx+4f8
ffffffff82c84393 ffffffff8db6fcf4 nt!ExpWorkerThread+e5
ffffffff82e25ad1 ffffffff8db6fd58 nt!PspSystemThreadStartup+9e
ffffffff82cd7539 ffffffff8db6fd98 nt!KiThreadStartup+19

.......

Если рассмотреть вывод без фильтрации мы обнаружим в нем многочисленные повторения выделенного стека. И не удивительно - это стек рабочего потока системы, ожидающего задания. Таких потоков может быть довольно много. С другой стороны, данные стеки как правило не интересны для анализа. И так, Польза +1.

Фильтрация по процессам

Рассмотрим сначала пример - вывод стеков только для процессов с определенным именем:

3: kd> !py stkwalk -p "name=='winlogon.exe'"
Stack walker. ver 1.0
Process ffffffff88395d40
Name: winlogon.exe  Pid: 0x30c

Thread ffffffff88393840, Process: winlogon.exe (ffffffff88395d40)
ffffffff82c7de06 ffffffff90ea3bd4 nt!KiSwapContext+26
ffffffff82c84fa5 ffffffff90ea3be8 nt!KiSwapThread+266
ffffffff82c838a3 ffffffff90ea3c20 nt!KiCommitThreadWait+1df
ffffffff82c7d76f ffffffff90ea3c48 nt!KeWaitForSingleObject+393
ffffffff82e48b45 ffffffff90ea3cc0 nt!NtWaitForSingleObject+c6
ffffffff82c596ea ffffffff90ea3d28 nt!KiFastCallEntry+12a
00000000771b6194 000000000006fad8 ntdll!KiFastSystemCallRet
00000000771b5b0c 000000000006fadc ntdll!NtWaitForSingleObject+c
00000000753b179c 000000000006fae0 KERNELBASE!WaitForSingleObjectEx+98
000000007699efe3 000000000006fb4c kernel32!WaitForSingleObjectExImplementation+75
000000007699ef92 000000000006fb64 kernel32!WaitForSingleObject+12
0000000000df1c9e 000000000006fb78 winlogon!SignalManagerWaitForSignal+e4
0000000000df1a50 000000000006fb98 winlogon!StateMachineRun+271
0000000000dfe343 000000000006fd54 winlogon!WlStateMachineRun+16
0000000000dfe199 000000000006fd68 winlogon!WinMain+af4
0000000000dfe479 000000000006fde8 winlogon!_initterm_e+1a1
00000000769a1154 000000000006fe78 kernel32!BaseThreadInitThunk+e
00000000771cb299 000000000006fe84 ntdll!__RtlUserThreadStart+70
00000000771cb26c 000000000006fec4 ntdll!_RtlUserThreadStart+1b

.....

Как видим, фильтр задается с помощью ключа -p ( --process ) и последующей строки, задающей сам фильтр. В примере мы закрыли строку, задающую фильтр, в кавычки. В принципе, внешние кавычки можно было бы опустить, но тогда мы должны гарантировать отсутствие пробелов в строке ( иначе, парсер командной строки просто разобьет строку на два параметра и все сломается ). Поэтому, для внутреннего спокойствия лучше поставит кавычки. Само выражение должно быть корректно с точки зрения python. Иначе мы увидим сообщение об ошибке:
3: kd> !py stkwalk -p name='winlogon.exe'
Stack walker. ver 1.0


Traceback (most recent call last):

  File "C:\proj\pykd-0.2\Snippets\stkwalk.py", line 238, in 
    main()

  File "C:\proj\pykd-0.2\Snippets\stkwalk.py", line 231, in main
    printProcess( process, processFilter, threadFilter, moduleFilter, funcFilter, printopt )

  File "C:\proj\pykd-0.2\Snippets\stkwalk.py", line 147, in printProcess
    if processFilter and not processFilter(process, process.UniqueProcessId, processName ):

  File "C:\proj\pykd-0.2\Snippets\stkwalk.py", line 212, in 
    processFilter = lambda process, pid, name: eval( options.processfilter )

  File "", line 1

    name='winlogon.exe'

        ^

SyntaxError: invalid syntax
Выражение должно вернуть булевский результат. В нем могут использоваться следующие переменные:
  • name - имя процесса
  • pid- PID процесса
  • process - объект типа typedVar( "nt!_EPROCESS", addr )
Кроме того, может задавать маски через fnmatch. Все это лучше показать на примерах:
3: kd> !py stkwalk -p fnmatch(name,'winlogon.*')
3: kd> !py stkwalk -p "pid>100 and pid<1000"
3: kd> !py stkwalk -p "process.Win32Process==0"

Фильтрация по модулям

Разобравшись с фильтрацией по процессам, перейдем к фильтрации по модулям. Фильтрация по модулю задается с помощью ключа --module или, сокращенно, -m. В качестве параметра выступает также логическое выражение, удовлетворяющее синтаксису Python. В выражении можно использовать:
  • name - имя модуля
  • module - объект типа module
Несколько примеров:
0: kd> !py stkwalk -m fnmatch(name,'HTTP*')
0: kd> !py stkwalk -m "module.checksum()==0x8d29e"
0: kd> !py stkwalk -m "name=='HTTP' or name=='tcpip'"

Фильтрация по функциям

Для задания фильтра по функциям служит ключ --function, -m. В выражении, задающем предикат фильтра, можно использовать переменную name - имя функции.

0: kd> !py stkwalk -f fnmatch(name,'*WaitFor*')

Фильтрация по потокам

Для задания фильтра по функциям служит ключ --thread, -t. В выражении, задающем предикат фильтра, можно использовать:

  • tid- идентификатор потока
  • thread- объект типа typedVar("nt!_KTHREAD", AddressOfThread )
0: kd> !py stkwalk -t tid<0xF

FAQ

Q: У меня нет никакого stkwalk.py. Где его, черт возьми, искать?
A: Попробуйте скачать скрипт вручную здесь
Q: Какого хрена, ничего не работает!
A: Убедитесь, что в находитесь в режиме отладки ядра.

Скрипт stkdelta

При разработке сложных драйверов, таких как фильтры файловых систем, разработчики порой сталкиваются с ситуацией нехватки стека режима ядра. Для анализа потребления стека мы разработали этот маленький скрипт. Он может выводить информацию о потреблении стека каждым вызовом функции или суммарное потребление:

0: kd> !py stkdelta

Stack Base: ffffffff9f387fd0 Limit: ffffffff9f385000 Current: ffffffff9f387754 Used: 87c Unused: 2754
Stack Delta: Function:
          56 nt!KiSwapThread
          40 nt!KiCommitThreadWait
         380 nt!KeWaitForMultipleObjects
         652 nt!ObpWaitForMultipleObjects
         336 nt!NtWaitForMultipleObjects
           0 nt!KiFastCallEntry
-18446744072066663752 ntdll!KiFastSystemCallRet
           4 ntdll!ZwWaitForMultipleObjects
         156 KERNELBASE!WaitForMultipleObjectsEx
          72 kernel32!WaitForMultipleObjectsExImplementation
          28 kernel32!WaitForMultipleObjects
          36 mpengine!FreeSigFiles
          76 mpengine!GetSigFiles
          12 mpengine!GetSigFiles
          56 msvcrt!_endthreadex
           8 msvcrt!_endthreadex
          12 kernel32!BaseThreadInitThunk
          64 ntdll!__RtlUserThreadStart
          24 ntdll!_RtlUserThreadStart

0: kd> !py stkdelta stat
Stack Base: ffffffff9f387fd0 Limit: ffffffff9f385000 Current: ffffffff9f387754 Used: 87c Unused: 2754

Stack usage: Module:
==============================
   1464(%11) nt
    156 (%1) KERNELBASE
    124 (%1) mpengine
    112 (%0) kernel32
     92 (%0) ntdll
     64 (%0) msvcrt

Stack usage: Function
==============================
    652 (%5) nt!ObpWaitForMultipleObjects
    380 (%3) nt!KeWaitForMultipleObjects
    336 (%2) nt!NtWaitForMultipleObjects
    156 (%1) KERNELBASE!WaitForMultipleObjectsEx
     88 (%0) mpengine!GetSigFiles
     72 (%0) kernel32!WaitForMultipleObjectsExImplementation
     64 (%0) ntdll!__RtlUserThreadStart
     64 (%0) msvcrt!_endthreadex
     56 (%0) nt!KiSwapThread
     40 (%0) nt!KiCommitThreadWait
     36 (%0) mpengine!FreeSigFiles
     28 (%0) kernel32!WaitForMultipleObjects
     24 (%0) ntdll!_RtlUserThreadStart
     12 (%0) kernel32!BaseThreadInitThunk
      4 (%0) ntdll!ZwWaitForMultipleObjects

Комментариев нет:

Отправить комментарий