Возможно, вы уже слышали о том, что 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

Комментарии (27)


  1. atrosinenko
    23.10.2017 13:56

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


    1. lieff
      23.10.2017 14:24

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


      1. khim
        24.10.2017 06:08

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

        Удобнее — верю. Быстрее — нет.


        1. lieff
          24.10.2017 11:26

          Нет, те что быстрее это из «еще несколько проприетарных». Я не помню уже какие именно пробовал, но что то типа таких undo.io/products/undodb, если поискать можно еше несколько аналогичных найти. Вот из них на то время точно были быстрее стокового gdb. Сейчас уже обычно rr хватает, с ним никаких проблем по скорости.


    1. Gumanoid
      23.10.2017 21:26

      Процессор должен быть начиная с Core 2 Duo.
      А вот в виртуалке будет работать, только если она поддерживает виртуализацию PMU.


  1. Kobalt_x
    23.10.2017 15:03

    Протокол отладки опять несовместим? Опять везде переставлять dbgsrv( удаленно кстати не работает


    1. tangro Автор
      23.10.2017 15:28

      То, что каждой версии debugging tools был нужен свой dbgsrv — это не новость, так всегда было. Но касаемо фичи Time Travel Debugging — тут всё ещё хуже, она для remote debug не работает вовсе.


      1. Kobalt_x
        23.10.2017 16:27

        Я все таки надеялся что с приходом десятки протокол будет обратно совместимым. А из блога вроде говорят что пока не работает, возможно заработает к релизу на win10 au+


  1. Evengard
    23.10.2017 15:23

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


  1. Stochkas
    23.10.2017 16:55

    прикольно.


  1. VioletGiraffe
    23.10.2017 17:15

    Можно ли ожидать появления такой же функции в Visual Studio?


    1. denismaster
      23.10.2017 21:26

      в VS 2017 Update 5 будет)


      1. Kobalt_x
        23.10.2017 22:21

        В VS2017 вроде убрали такое понятие как update выпуски или я неправ? теперь она сама вроде предлагает обновиться


        1. denismaster
          23.10.2017 22:29

          Да, имелась в виду версия 15.5)


    1. molnij
      23.10.2017 22:21

      Так вроде для шарпа есть уже пару версий что-то схожее… Или вопрос про С\С++?


      1. VioletGiraffe
        23.10.2017 22:49

        Конечно вопрос про С++. Разве WinDbg имеет отношение к C#?


        1. Kobalt_x
          23.10.2017 23:35

          ну windbg умеет дебажить c# через sos plugin


  1. MacIn
    23.10.2017 17:38

    Жаль только, что в 7ке этого не получить. Они написали, что TTD выйдет в следующем SDK для 7ки, но когда это будет — не знаю.


  1. diversenok
    23.10.2017 20:03

    Слушайте, а бывает в отладчиках такая функция (и если бывает, то как называется?):
    выбираю два момента времени по мере работы программы > получаю в дизассемблерном листинге подсвеченными именно те инструкции, которые выполнялись между этими двумя моментами времени?


    1. Kobalt_x
      23.10.2017 22:24

      ну с TTD это можно будет сделать в трассе же указаны адреса функций и бранчи по которым ходили а дальше используя скриптоту можно будет узнать инструкции через uf


      1. MacIn
        24.10.2017 18:34

        Трассировка.


    1. tangro Автор
      24.10.2017 09:54

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


  1. khim
    24.10.2017 06:46

    Смотрю я на эту новость… и ловлю себя на мысли: а почему, собственно, я должен воспринимать это как «вау, как крута?»… Откройте мануал к старому древнему 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. Охотно верю в то, что у них была масса сложностей, которые они успешно преодолели. Вопрос скорее философский. Почему фича, которая не казалось чем-то «вау» тогда… сегодня вдруг — повод для статьи на хабре? Почему мы в качестве новинок получаем то, что уже имели четверть века назад — пусть и новыми, более красивыми кнопочками?


    1. Kobalt_x
      24.10.2017 08:17

      «Turbo Debugger can record about 400
      instructions. If you have EMS, it can record approximately 3000
      instructions. » вобщем не то это было. Оно не писало трассу всего приложения а просто хранило историю инструкций, эту историю мог 1 большой цикл сожрать за раз. А step back в пределах фрейма без сохранения истории для последующего анализа есть и сейчас в msvs


      1. khim
        24.10.2017 08:35

        «Трасса всего приложения» всегда ограничена. Если я Хром запущу, который сейчас у меня по счётчику отработал 30 часов — думаете он трассу сможет куда-нибудь записать? И в те времена TD сохранить трассу для реального приложения не мог, и сейчас WinDbg не сможет.

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


        1. alexeypa
          24.10.2017 08:48

          Если я Хром запущу, который сейчас у меня по счётчику отработал 30 часов — думаете он трассу сможет куда-нибудь записать?


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

          TTD, если мне не изменяет память, по дисковым требованиям похож на rr.


    1. alexeypa
      24.10.2017 08:27

      Откройте мануал к старому древнему 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.