Time Travel Debugging в новом WinDbg

    Возможно, вы уже слышали о том, что Microsoft выпустила обновлённую версию своего известного отладчика WinDbg, который и раньше был хорош, но слишком уж отстал по интерфейсу от современных тенденций. Новая версия WinDbg, к счастью, не пошла настолько далеко, чтобы получить новомодный UWP-интерфейс, но вот классические риббон-бары в стиле Microsoft Office — ей очень идут. Приложение распространяется только через Microsoft Store и работают на Win10 как минимум с Anniversary Update. Microsoft говорит, что это сделано для удобства установки и обновления, но я как-то не помню, чтобы с классическим WinDbg были какие-то проблемы с установкой. Скорее это выглядит как ещё один способ приучения разработчиков и пользователей к привычке пользоваться только самой последней версией Windows. Ну ок, пусть так.

    WinDbg выглядит симпатично:

    image

    И вся его мощь в виде команд, отладки драйверов, удалённой отладки, скриптов и прочего — осталась при нём. Более того, 25 сентября было выпущено обновление, добавляющее в новый WinDbg важную фичу — отладку с возможностью двигаться по ходу работы программы в обратном направлении (Time Travel Debugging). Возможность интересная, поскольку попав в некоторое невалидное состояние программист часто задаётся вопросом «А как же так вышло?». Ранее получить на него ответ можно было либо проигрывая в уме команды в обратном порядке, либо перезапуская отладку снова и снова с добавлением логов и новых контрольных точек. Всё это занимало время. Давайте посмотрим, как это работает сейчас.

    Устанавливаем WinDbg

    Пишем каку-нибудь небольшую программу и компилируем её. Я взял первую попавшуюся в Интернете реализацию пузырьковой сортировки (да, потому, что я лентяй).

    Пузырьковая сортировка
    #include "stdafx.h"
    
    void swap(int *xp, int *yp)
    {
    	int temp = *xp;
    	*xp = *yp;
    	*yp = temp;
    }
    
    // An optimized version of Bubble Sort
    void bubbleSort(int arr[], int n)
    {
    	int i, j;
    	bool swapped;
    	for (i = 0; i < n - 1; i++)
    	{
    		swapped = false;
    		for (j = 0; j < n - i - 1; j++)
    		{
    			if (arr[j] > arr[j + 1])
    			{
    				swap(&arr[j], &arr[j + 1]);
    				swapped = true;
    			}
    		}
    
    		// IF no two elements were swapped by inner loop, then break
    		if (swapped == false)
    			break;
    	}
    }
    
    /* Function to print an array */
    void printArray(int arr[], int size)
    {
    	int i;
    	for (i = 0; i < size; i++)
    		printf("%d ", arr[i]);
    }
    
    // Driver program to test above functions
    int main()
    {
    	int arr[] = { 64, 34, 25, 12, 22, 11, 90 };
    	int n = sizeof(arr) / sizeof(arr[0]);
    	bubbleSort(arr, n);
    	printf("Sorted array: \n");
    	printArray(arr, n);
    	return 0;
    }


    Теперь у нас есть скомпилированный бинарник, символьный файл к нему и файл с исходником. Это всё понадобится для WinDbg.

    Запускаем WinDbg с привилигиями администратора (это важно!). Выбираем File → Start debugging → Launch executable (advanced):



    Задаём путь к отлаживаемому бинарнику, ставим галку «Record process with Time Travel Debugging», задаём путь для сохранения записанного трейса выполнения.



    Жмём ок, программа запускается, отрабатывает и закрывается. WinDbg сохраняет записанный трейс выполнения в указанную папку и сразу же загружает его (это экономит время отладки).



    Теперь открываем в WinDbg файл с кодом, ставим пару брейкпоинтов, запускаем отладку. На первый взгляд всё выглядит знакомо.



    Но вот оно — главное отличие:



    Нам доступен блок реверсивного управления направлением выполнения кода. Мы можем просто ступить на строку назад.



    Мы можем поставить новый брейкпоинт где-нибудь выше и нажать «Go back», чтобы обратное выполнение программы дошло до него.



    Обратите внимание — мы прыгнули назад во времени до входа программы в циклы for — и вот внизу в окне Locals мы уже видим, что переменные i и j в этот момент ещё имеют неопределённые значения.

    Мы можем ходить вперёд и назад сколько угодно, перезапускать отладку с самого начала. Можно коннектится к уже запущенным процессам для записи выполнения только определённых блоков кода. Очень удобно ловить баги, которые воспроизводятся только иногда или только в определённом окружении, а потом прокручивать их снова и снова.

    В общем, фича мне нравится.

    Материалы по теме:

    1. Анонс фичи в блоге WinDbg
    2. Документация
    3. Установить WinDbg
    Инфопульс Украина 166,77
    Creating Value, Delivering Excellence
    Поделиться публикацией
    Комментарии 27
    • +3

      На случай, если кто-то задумается: "Блин, хочу такое же, но я на Линуксе". Оно есть (во всяком случае, похожее). Есть упоминания о reverse debug в GDB (там записывается каждая выполненная инструкция). Также есть эпичный проект от Mozilla: rr — он записывает только недетерминированное поведение (системные вызовы, rdtsc, ...) и имеет некоторые ограничения (в частности, работает, насколько я помню, только под Linux и на относительно свежих процессорах от Intel; недавно, вроде, пытались добавить поддержку AMD Ryzen, интересно, чем закончилось) — заявляется оверхед на запись чуть ли не x1.5 и меньше. В QEMU тоже, вроде, какой-то record-replay добавляли...

      • 0
        Тоже хотел про rr написать, классная штука. Кроме этого есть CLion и еще несколько проприетарных отладчиков с этой функцией (они быстрее gdb). В свое время на винде мне этого не хватало, и вот наконец появилось.
        • 0
          Кроме этого есть CLion и еще несколько проприетарных отладчиков с этой функцией (они быстрее gdb).
          Осталось понять как CLion, в котором отладка выполнена через вызов gdb, смог работать быстрее, чем сам gdb.

          Удобнее — верю. Быстрее — нет.
          • 0
            Нет, те что быстрее это из «еще несколько проприетарных». Я не помню уже какие именно пробовал, но что то типа таких undo.io/products/undodb, если поискать можно еше несколько аналогичных найти. Вот из них на то время точно были быстрее стокового gdb. Сейчас уже обычно rr хватает, с ним никаких проблем по скорости.
        • 0
          Процессор должен быть начиная с Core 2 Duo.
          А вот в виртуалке будет работать, только если она поддерживает виртуализацию PMU.
        • 0
          Протокол отладки опять несовместим? Опять везде переставлять dbgsrv( удаленно кстати не работает
          • 0
            То, что каждой версии debugging tools был нужен свой dbgsrv — это не новость, так всегда было. Но касаемо фичи Time Travel Debugging — тут всё ещё хуже, она для remote debug не работает вовсе.
            • 0
              Я все таки надеялся что с приходом десятки протокол будет обратно совместимым. А из блога вроде говорят что пока не работает, возможно заработает к релизу на win10 au+
          • 0

            А IDA или radare уже совместимы?

            • 0
              прикольно.
              • 0
                Можно ли ожидать появления такой же функции в Visual Studio?
                • 0
                  в VS 2017 Update 5 будет)
                  • 0
                    В VS2017 вроде убрали такое понятие как update выпуски или я неправ? теперь она сама вроде предлагает обновиться
                • 0
                  Так вроде для шарпа есть уже пару версий что-то схожее… Или вопрос про С\С++?
                  • 0
                    Конечно вопрос про С++. Разве WinDbg имеет отношение к C#?
                    • +1
                      ну windbg умеет дебажить c# через sos plugin
                • 0
                  Жаль только, что в 7ке этого не получить. Они написали, что TTD выйдет в следующем SDK для 7ки, но когда это будет — не знаю.
                  • 0
                    Слушайте, а бывает в отладчиках такая функция (и если бывает, то как называется?):
                    выбираю два момента времени по мере работы программы → получаю в дизассемблерном листинге подсвеченными именно те инструкции, которые выполнялись между этими двумя моментами времени?
                    • 0
                      ну с TTD это можно будет сделать в трассе же указаны адреса функций и бранчи по которым ходили а дальше используя скриптоту можно будет узнать инструкции через uf
                    • 0
                      Ну, используя профайлер, можно получить названия вызываемых между двумя моментами времени функций и время их работы.
                    • 0
                      Смотрю я на эту новость… и ловлю себя на мысли: а почему, собственно, я должен воспринимать это как «вау, как крута?»… Откройте мануал к старому древнему Turbo Debugger'у 2.0 1990го года выпуска — и вы увидите эту фичу в разделе «Controlling Program Execution», подраздел «Back Trace».

                      Вот только системные требования за четверть века выросли с пресловутых 640K, которых «хватит всем» (хотя, конечно в TD.EXE back trace был очень ограниченный, для комфортной работы тогда требовались «огроменнейшие» 2-4MB) до 2-4GB, то есть более, чем в 1000 раз… и процессор требуется в 2000-4000 раз более мощный (номинальные частоты отличаются почти в 1000 раз, а ведь 8086 одну команду исполнять мог чуть не 10 тактов)… и… где, чёрт побери, программная индустрия слетела с катушек?

                      P.S. Я ни в коем случае не умаляю достижений команды WinDbg. Охотно верю в то, что у них была масса сложностей, которые они успешно преодолели. Вопрос скорее философский. Почему фича, которая не казалось чем-то «вау» тогда… сегодня вдруг — повод для статьи на хабре? Почему мы в качестве новинок получаем то, что уже имели четверть века назад — пусть и новыми, более красивыми кнопочками?
                      • 0
                        «Turbo Debugger can record about 400
                        instructions. If you have EMS, it can record approximately 3000
                        instructions. » вобщем не то это было. Оно не писало трассу всего приложения а просто хранило историю инструкций, эту историю мог 1 большой цикл сожрать за раз. А step back в пределах фрейма без сохранения истории для последующего анализа есть и сейчас в msvs
                        • 0
                          «Трасса всего приложения» всегда ограничена. Если я Хром запущу, который сейчас у меня по счётчику отработал 30 часов — думаете он трассу сможет куда-нибудь записать? И в те времена TD сохранить трассу для реального приложения не мог, и сейчас WinDbg не сможет.

                          А поскольку в те времена 100 слоёв обёрток не наворачивали, то 3000 инструкций хватало для очень и очень многого. Какой-нибудь memcpy — это пяток инструкций был. И strcmp тоже. И многое другое было резко короче, чем сегодня…
                          • 0
                            Если я Хром запущу, который сейчас у меня по счётчику отработал 30 часов — думаете он трассу сможет куда-нибудь записать?


                            rr, например, как раз и создавался для записи реального приложения — Mozilla Firefox. Записывать им трассу на 30 часов не практично, конечно. Записать 30 минут — час совершенно не проблема. Ограничение здесь даже не дисковое пространство — его-то как раз хватит, а время на перемотку. Вот если допилят снапшоты, то и 30 часов не будет большой проблемой.

                            TTD, если мне не изменяет память, по дисковым требованиям похож на rr.
                        • +1
                          Откройте мануал к старому древнему Turbo Debugger'у 2.0 1990го года выпуска — и вы увидите эту фичу в разделе «Controlling Program Execution», подраздел «Back Trace».


                          Вы сравниваете несравнимые вещи. В этом же мануале написано:

                          Some restrictions apply. See the section, «The Instructions pane (page 86).»

                          The execution history only keeps track of instructions that have
                          been executed with the Trace Into command (Fl) or the Instruction
                          Trace command (AIt-Fl). It also tracks for Step Over, as long as you
                          don't encounter one of the commands listed on page 84. As soon
                          as you use the Run command or execute an interrupt, the
                          execution history is deleted. (It starts being recorded again as
                          soon as you go back to tracing.)


                          Иными словами Trace Back в Turbo Debugger — это банальный undo для выполненных вручную шагов по коду. В случае однопоточной программы реализуется тривиально запоминанием контекста процессора на каждом шаге.

                          TTD — это полноценная записть состояния процесса для всех его потоков. Если делать такую запись в лоб — получаются жуткие тормоза: меденно (так как эмулируется каждая инструкция) и количество данных зашкаливает. Поэтому хорошие реализации хитрят (что TTD, что rr) — избегая необходимости сохранять более 99% состояния.

                          На самом деле, TTD — это технология 10-летней давности. Просто Microsoft решила наконец её выпустить в мир: www.usenix.org/legacy/events/vee06/full_papers/p154-bhansali.pdf.

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

                        Самое читаемое