mi

Author / Автор: Сергей Сацкий
NCBI
Publication date / Опубликовано: 07.02.2010
Version / Версия текста: 1.0

Что такое mi

mi – это утилита командной строки для Linux, предназначенная для выявления потенциально возможных взаимных блокировок в многопоточных программах. mi также умеет собирать статистическую информацию, такую как количество успешных и неуспешных операций с мьютексами, время потраченное на получение блокировки мьютексов. mi может показать стек вызовов функций, соответствующий операциям с мьютексами.

mi позволяет выявить следующие ситуации:

  • порядок блокирования двух мьютексов в одном потоке противоположен порядку блокирования этих же мьютексов в другом потоке
  • разблокирование мьютекса, который не был блокирован ранее
  • оставшийся заблокированным мьютекс после завершения программы
  • порядок разблокирования мьютексов не соответствует обратному порядку блокирования
  • разблокирование мьютекса, заблокированного ранее в другом потоке

mi написана на python и С++ и распространяется как public domain программное обеспечение.

mi работает с обычными исполняемыми файлами, с демонами и с программами, запущенными через скрипты. При этом перекомпиляция проверяемой программы не требуется.

Как работает mi

mi состоит из разделяемой библиотеки, написанной на C++ и двух python скриптов. Первый скрипт загружает разделяемую библиотеку с помощью механизма LD_PRELOAD, а библиотека, в свою очередь, перехватывает вызовы pthread для работы с мьютексами. После этого запускается проверяемая программа. Таким образом, не требуется перекомпиляция проверяемой программы, а сама она может быть написана на любом языке программирования. В процессе работы проверяемой программы собираются данные. После завершения работы программы, с помощью второго скрипта, производится анализ собранных данных, а результаты работы выводятся на консоль.

Сеанс работы mi

Перехват вызовов библиотеки pthread

Чего mi не может

  • mi анализирует только вызовы времени выполнения программы. Поэтому, если какие – либо потенциально опасные вызовы не делаются во время тестового запуска, mi не сможет их проанализировать. Лекарство от этой проблемы – это тестовые запуски на таком наборе данных, которые покрывают все возможные сценарии работы проверяемой программы.
  • mi требуется, чтобы проверяемая программа успешно завершилась. В момент завершения проверяемой программы закрывается файл протокола с информацией о вызовах функций работы с мьютексами. Запускать анализируещий скрипт имеет смысл после завершения сбора данных.
  • Если проверяемая программа использует пулы мьютексов, то mi, скорее всего, будет бессильна помочь. Для идентификации мьютексов mi использует их адрес, а в системах пулами мьютексов один и тот же мьютекс может использоваться для совершенно разных целей в разные моменты жизненного цикла программы. mi, скорее всего, будет выдавать неверную информацию о потенциально опасных цепочках захвата мьютексов.
  • Если проверяемая программа использует вызов dlopen() для загрузки библиотеки pthread, затем серию вызовов dlsym() для получения указателей на функции работы с мьютексами и далее пользуется этими указателями, то такие вызовы mi не сможет перехватить, а значит они не будут проанализированы.

Пример сеанса работы с mi

Запуск mi с тестовым примером:

satsky.homelinux.com:~> ./mi.py ./test/test-bad-lock-order-elf
Test (improper lock order, elf) t0: m1.lock -> m2.lock -> m3.lock -> m3.unlock -> m2.unlock -> m1.unlock
                                t1: m1.lock -> m3.lock -> m2.lock -> m2.unlock -> m3.unlock -> m1.unlock

В результате работы mi создается файл протокола:

satsky.homelinux.com:~> ll mi.log
-rw-rw-r-- 1 guest guest 4411 2010-01-31 20:54 mi.log

Теперь можно запустить скрипт, анализирующий протокол работы:

swift@satsky.homelinux.com:~> ./statmi.py
Execution environment:
    application: /home/guest/test/test-bad-lock-order-elf
    log file: /home/guest/mi.log
    libpthread.so path: /lib64/libpthread.so.0
    do not print stack trace
Number of threads: 3
Number of mutexes: 4
Successfull operations: 56
Failed operations: 0
Collecting chains and statistics...
Threads legend:
    t0 -> 140209742624528
    t1 -> 140209742620944
    t2 -> 140209732131088
Mutexes legend:
    m0 -> 0x310baf7040
    m1 -> 0x6013c0
    m2 -> 0x601400
    m3 -> 0x601440
--- E000 -- t2: m3 -> m2 -- t1: m2 -> m3
ERROR: potential dead lock detected
Thread t2 lock stack:
    Operation: lock Object: 0x601400(m2) Thread: 140209732131088(t2) Return code: 0 Clocks: 0
    Operation: lock Object: 0x601440(m3) Thread: 140209732131088(t2) Return code: 0 Clocks: 0
    Operation: lock Object: 0x6013c0(m1) Thread: 140209732131088(t2) Return code: 0 Clocks: 0
Thread t1 lock stack:
    Operation: lock Object: 0x601440(m3) Thread: 140209742620944(t1) Return code: 0 Clocks: 0
    Operation: lock Object: 0x601400(m2) Thread: 140209742620944(t1) Return code: 0 Clocks: 0
    Operation: lock Object: 0x6013c0(m1) Thread: 140209742620944(t1) Return code: 0 Clocks: 0
--- E000

Каждое сообщение об ошибке обрамляется выводом строк, начинающихся с трех тире:

--- E000 -- t2: m3 -> m2 -- t1: m2 -> m3
. . .
--- E000

Символ E означает, что это ошибка. Символ W используется для предупреждений. Далее следует трехзначный номер ошибки.

В заголовке ошибки кратко описывается смысл ошибки. Запись:

t2: m3 -> m2 -- t1: m2 -> m3

следует читать так – поток t2 выполнил захват мьютекса m3, а затем мьютекса m2; поток t1, в свою очередь, выполнил захват мьютекса m2, а затем мьютекса m3.

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

Более подробную информацию, включающую стек вызова функций для каждой операции захвата мьютексов, можно получить, если утилита mi запущена с опцией --option stack, например:

satsky.homelinux.com:~mi> ./mi.py --option stack ./test/test-bad-lock-order-elf
Test (improper lock order, elf) t0: m1.lock -> m2.lock -> m3.lock -> m3.unlock -> m2.unlock -> m1.unlock
                                t1: m1.lock -> m3.lock -> m2.lock -> m2.unlock -> m3.unlock -> m1.unlock

И затем:

satsky.homelinux.com:~> ./statmi.py
Execution environment:
    application: /home/guest/test/test-bad-lock-order-elf
    log file: /home/guest/mi.log
    libpthread.so path: /lib64/libpthread.so.0
    print stack trace
Number of threads: 3
Number of mutexes: 4
Successfull operations: 56
Failed operations: 0
Collecting chains and statistics...
Threads legend:
    t0 -> 140490701883152
    t1 -> 140490701879568
    t2 -> 140490691389712
Mutexes legend:
    m0 -> 0x310baf7040
    m1 -> 0x6013c0
    m2 -> 0x601400
    m3 -> 0x601440
--- E000 -- t2: m3 -> m2 -- t1: m2 -> m3
ERROR: potential dead lock detected
Thread t2 lock stack:
    Operation: lock Object: 0x601400(m2) Thread: 140490691389712(t2) Return code: 0 Clocks: 0
    Backtrace:
        /home/guest/test/test-bad-lock-order-elf [0x400a57]
        /lib64/libpthread.so.0 [0x310640685a]
        /lib64/libc.so.6 : clone()+0x6d
    Operation: lock Object: 0x601440(m3) Thread: 140490691389712(t2) Return code: 0 Clocks: 0
    Backtrace:
        /home/guest/test/test-bad-lock-order-elf [0x400a4d]
        /lib64/libpthread.so.0 [0x310640685a]
        /lib64/libc.so.6 : clone()+0x6d
    Operation: lock Object: 0x6013c0(m1) Thread: 140490691389712(t2) Return code: 0 Clocks: 0
    Backtrace:
        /home/guest/test/test-bad-lock-order-elf [0x400a43]
        /lib64/libpthread.so.0 [0x310640685a]
        /lib64/libc.so.6 : clone()+0x6d
Thread t1 lock stack:
    Operation: lock Object: 0x601440(m3) Thread: 140490701879568(t1) Return code: 0 Clocks: 0
    Backtrace:
        /home/guest/test/test-bad-lock-order-elf [0x4009fe]
        /lib64/libpthread.so.0 [0x310640685a]
        /lib64/libc.so.6 : clone()+0x6d
    Operation: lock Object: 0x601400(m2) Thread: 140490701879568(t1) Return code: 0 Clocks: 0
    Backtrace:
        /home/guest/test/test-bad-lock-order-elf [0x4009f4]
        /lib64/libpthread.so.0 [0x310640685a]
        /lib64/libc.so.6 : clone()+0x6d
    Operation: lock Object: 0x6013c0(m1) Thread: 140490701879568(t1) Return code: 0 Clocks: 0
    Backtrace:
        /home/guest/test/test-bad-lock-order-elf [0x4009ea]
        /lib64/libpthread.so.0 [0x310640685a]
        /lib64/libc.so.6 : clone()+0x6d
--- E000

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

Зависимости

mi использует следующие пакеты:

  • g++ (для компиляции разделяемой библиотеки, перехватывающей вызовы pthread)
  • python 2.5.2 или 2.6 (http://www.python.org) для скриптов запуска и анализа собранной информации
  • утилита ldd должна быть доступна через переменную PATH, если вы не указываете полный путь к библиотеке pthread в переменной окружения MI_LIBPTHREAD или через ключ --pthread утилиты mi.

Установка

Для сборки библиотеки, перехватывающей вызовы pthread_mutex_zzz(...) запустите:

make love

mi не требует никакой специальной установки. Просто скопируйте файлы mi (libmi.so, mi.py, statmi.py) в то место, где вы хотите их хранить.

Может быть имеет смысл сделать символические ссылки и подправить переменную PATH соответствующим образом.

mi тестировалась на Fedora 11.

Чтобы получить справку, наберите:

./mi.py –help

Скачать

Версия 0.0.1

mi-0.0.1.tar.bz2 (13610 байт)

Changelog (07-Feb-2010):

  • first public release

Вопросы и ответы

Вопрос: как запустить mi, если проверяемая программа запускается из скрипта?

Ответ: необходимо указать путь к libpthread.so с помощью ключа --pthread в момент запуска mi.py, например:

satsky.homelinux.com:~> ./mi.py --pthread  /lib/libpthread-2.10.2.so  my_prog.sh

Вопрос: нужны ли дополнительные шаги для проверки программы-демона?

Ответ: нет. Запуск демона осуществляется так же, как и обычной программы. Однако до запуска statmi.py необходимо дождаться завершения работы демона.

Вопрос: какие коды возврата у statmi.py?

Ответ: 0 – в случае отсутствия ошибок и предупреждений. 1 – в случае, если есть хотя бы одно предупреждение и ни одной ошибки в проверяемой программе. 2 – в случае, если есть по крайней мере одна ошибка в проверяемой программе. 3 – в случае ошибок выполнения statmi.py.

Вопрос: statmi.py выводит информацию о мьютексах, которых нет в моей программе. Это ошибка mi?

Ответ: нет. Окружение времени выполнения может использовать мьютексы для внутренних целей и mi регистрирует активность с этими мьютексами. Обычно эти мьютексы отличаются диапазоном адресов (см. мьютекс m0 в примере сеанса работы с mi) и действия с ними заканчиваются до начала действий с мьютексами проверяемой программы.

Послесловие

mi разрабатывалась для развлечения, хотя преследовались и другие цели:

  • предоставить бесплатную альтернативу небольшой части функциональности продукта Intel Thread Checker for Linux
  • поэксперементировать с перехватом вызова функций из динамических библиотек и, возможно, использовать наработанный опыт для других проектов

Утилита mi не закончена. Если она вам понравилась и вы хотите помочь ее улучшить – добро пожаловать!


Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved.

Разрешается копирование и распространение этой статьи любым способом без внесения изменений, при условии, что это разрешение сохраняется.
Last Updated: February 07, 2010