В блоге Microsoft недавно была опубликована статья, которую написал Билл Рэндольф, старший инженер-программист Blizzard, занимающийся разработкой Diablo IV. В этой статье раскрыты некоторые особенности работы над Diablo IV и, в частности, рассказано об использовании Visual Studio для отладки кода, рассчитанного на Linux. Сегодня мы предлагаем вашему вниманию перевод этого материала.
Введение
Мы, работая над Diablo IV, пишем весь код в Windows, а потом компилируем его для разных платформ. Это относится и к нашим серверам, которые работают под управлением Linux. (Код включает в себя директивы условной компиляции и, там, где это нужно, в нём есть фрагменты, написанные специально для конкретной платформы.) Наша работа организована именно так по многим причинам. Для начала — ключевые профессиональные навыки нашей команды относятся к Windows. Даже наши серверные программисты лучше всего знакомы именно с Windows-разработкой. Мы ценим возможность применения одних и тех же инструментов и одной и той же базы знаний всеми программистами нашей команды.
Ещё одна, самая важная причина того, что мы занимаемся разработкой на Windows, заключается в возможности пользоваться высокофункциональным и надёжным набором инструментов, который даёт нам Visual Studio. И даже если бы мы разрабатывали что-то в Linux, то я могу сказать, что в мире Linux нет ничего такого, что можно было бы хотя бы сравнить с Visual Studio.
Правда, из-за этого мы сталкиваемся с некоторыми сложностями, которые возникают тогда, когда сервер даёт сбой и нам надо отладить дамп памяти. У нас есть возможность удалённого входа в виртуальную машину (или, точнее — в контейнер), который дал сбой, мы можем запустить gdb для выяснения причин произошедшего. Но у такого подхода есть множество недостатков. Например — мы не разворачиваем бинарные файлы вместе с исходным кодом — в результате при работе с виртуальной машиной или с контейнером исходный код в gdb-сессии недоступен.
Ещё одна сложность кроется в самом gdb. Дело в том, что если не пользоваться этим инструментом постоянно, на регулярной основе, его нельзя освоить на таком уровне, который бы нас устраивал. Проще говоря, наши разработчики гораздо охотнее пользовались бы знакомыми инструментами для отладки кода. Так как только 2-3 из наших разработчиков очень хорошо знают gdb, то, когда что-то идёт не так, поисками проблемы занимаются именно они. А это нельзя назвать оптимальным распределением нагрузки на программистов.
Нам всегда хотелось найти такой способ отладки Linux-кода, который отличался бы интуитивной понятностью. Именно поэтому нас так восхищает возможность использования новой возможности Visual Studio, которая позволяет нам решать именно эту задачу в знакомой среде! И вовсе не преувеличением будет сказать, что благодаря этому наша мечта сбылась.
О применяемом нами процессе отладки кода
Отладка Linux-кода в Visual Studio возможна лишь в том случае, если в системе установлена подсистема Windows для Linux (WSL), или тогда, когда в менеджере подключений (Connection Manager) настроено подключение к Linux. Все наши серверные разработчики установили WSL, используя дистрибутив, на котором мы разворачиваем наш проект. Мы запускаем написанный мной скрипт, который устанавливает все инструменты разработки и вспомогательные библиотеки, необходимые для сборки нашего сервера в WSL.
(Ненадолго отвлекусь от нашей основной темы. Мне хотелось бы подчеркнуть, что мы пришли к выводу о том, что WSL — это лучшее из существующих окружений, позволяющих разработчикам тестировать изменения в Linux-сборках. Крайне удобно выглядит такая схема работы: переход в WSL, использование команды cd
для входа в общую директорию с кодом и сборка проекта прямо оттуда. Это — гораздо лучшее решение, чем использование виртуальной машины или даже контейнера. Если вы собираете проекты с использованием CMake, это значит, что вы, кроме того, можете задействовать встроенную в Visual Studio поддержку WSL.)
Немного расскажу о наших сборках. Мы разрабатываем код в Windows и у нас есть Windows-версия нашего сервера, рассчитанная на работу в этой ОС. Это приносит нам пользу при работе над обычными возможностями проекта. Но мы разворачиваем наш серверный код в среде Linux, что требует выполнения сборок на Linux. Linux-сборки создаются на сборочной ферме. Там используется система сборки, работающая на Linux-компьютере. С её помощью собирается наш серверный проект и соответствующий контейнер, который в дальнейшем и развёртывается. Исполняемые файлы, рассчитанные на Linux, разворачиваются только в контейнерах. Обычно у разработчиков нет доступа к этим контейнерам.
Когда один из серверов нашей инфраструктуры даёт сбой, нас об этом уведомляет специальный скрипт, после чего файлы дампа записываются в общую сетевую папку. Для отладки этих файлов, либо в Linux, либо в Visual Studio, необходима работающая программа. При отладке полезно использовать в точности те общие библиотеки, которые применялись в развёрнутом контейнере. Для получения этих файлов мы используем другой скрипт. Сначала мы копируем дамп на локальную машину, а потом запускаем скрипт и передаём ему сведения об этом дампе. Скрипт загружает контейнер Docker, который был собран для исследуемой версии кода, извлекает из него исполняемые файлы нашего сервера, а так же — определённые общие библиотеки времени выполнения. Всё это нужно для gdb. (Это, при работе с gdb, позволяет избежать проблем с совместимостью, которые могут возникнуть в том случае, если WSL-версия системы не является точно такой же, как её развёрнутая Linux-версия.) Скрипт, настраивая отладочную сессию, пишет данные в ~/.gdbinit
, указывая, что общие библиотеки являются системными библиотеками.
Потом мы переходим в Visual Studio, где и начинается самое интересное. Мы загружаем решение для сборки Windows-версии наших серверов. Затем мы открываем новое диалоговое окно отладки, воспользовавшись командой Debug -> Other Debug Targets -> Debug Linux Core Dump with Native Only
. Мы устанавливаем флажок Debug on WSL
и вводим пути к файлам дампа и к серверным бинарникам (предназначенным для WSL!). После этого достаточно нажать на кнопку Debug
и наблюдать за происходящим.
Запуск отладки в Visual Studio
Visual Studio самостоятельно запускает gdb в WSL. После того, как некоторое время система поработает с диском, выводится стек вызовов программы, давшей сбой, а указатель инструкций устанавливается на соответствующую строку кода. Это, воистину, дивный новый мир!
Дальше мы занимаемся идентификацией самого сбоя. У нас есть обработчик сбоев, который перехватывает соответствующее событие для выполнения некоторых служебных процедур. Поэтому сведения о самом сбое расположены, на однопоточном сервере, глубже в стеке вызовов. Но некоторые из наших серверов многопоточны. И сбой может произойти в любом из их потоков. Обработчик сбоев логирует сведения о коде сбойного файла и о номере строки. Поэтому исследование этих данных даёт нам первую подсказку. Мы ищем то место в стеке вызовов, которое соответствует выполнению этого кода.
В давние времена, а именно, несколько недель назад, мы применили бы gdb для обратной трассировки всех потоков, а после этого просмотрели бы получившийся список в поиске потока, в стеке вызовов которого, вероятнее всего, произошёл сбой. Например, если поток находился в спящем состоянии, то сбой в нём, скорее всего, не происходил. Нам нужен стек, в котором есть нечто большее, чем несколько кадров и сведения о том, что мы имеем дело со спящим потоком. Далее, нам надо исследовать код для того чтобы понять, что перед нами за проблема. Если это что-то простое — это можно увидеть прямо в коде. Если же перед нами проблема посложнее — придётся прибегать к возможностям gdb для исследования состояния процесса.
А вот Visual Studio даёт нам значительно более мощные возможности, чем имелись у нас раньше. В многопоточных средах можно открыть в отладочной сессии окно Threads
и, пощёлкав по потокам, посмотреть их стеки. Это, правда, очень похоже на подход, используемый в gdb. Поэтому, если надо изучить, скажем, 50 потоков, это может превратиться в довольно трудоёмкую и скучную задачу. К счастью, в Visual Studio есть инструмент, значительно упрощающий эту задачу. Речь идёт об окне Parallel Stacks.
Признаю: большинство из нас не знали о Parallel Stacks до тех пор, пока Эрика Свит и её команда не сказали нам об этом. Если во время работы отладочной сессии выполнить команду Debug -> Windows -> Parallel Stacks
— будет открыто новое окно, которое выводит сведения о стеке вызовов каждого потока в исследуемом процессе. Это нечто вроде вида с высоты птичьего полёта на всё пространство процесса. По любому кадру стека любого потока можно сделать двойной щелчок. После этого Visual Studio перейдёт в этот кадр и в окне исходного кода, и в окне стека вызовов. Это очень сильно помогает нам экономить время.
После того, как мы видим код, находящийся поблизости от места сбоя, мы можем исследовать переменные с использованием мыши, с помощью QuickWatch, или применив любой другой из множества инструментов Visual Studio. Конечно, в релизных сборках многие переменные подвергаются оптимизации, но, в то же время, многие не подвергаются! Мы, применяя интерфейс Visual Studio, можем выявить проблему гораздо быстрее, чем раньше, используя gdb.
Итоги
Наша команда просто счастлива возможностью отлаживать дампы из Linux в Visual Studio, в окружении, в котором мы занимаемся разработкой. Для нас это — серьёзное улучшение, так как это позволяет гораздо большему количеству разработчиков, чем раньше, диагностировать проблемы в естественных условиях их возникновения. Это позволяет всем нам пользоваться мощными отладочными инструментами Visual Studio. После того, как завершена предварительная подготовка рабочей среды, оказаться в отладочной сессии Visual Studio можно буквально за считанные минуты. Эта возможность значительно повышает скорость нахождения проблем и эффективность нашей работы. Благодарю Эрику и её команду за помощь.
Какие возможности Visual Studio вы считаете самыми полезными?
Автор: ru_vds