Выбрать главу

Сейбел: Когда вы говорите, что не было нормальной документации, это значит, что документация была неточной или что ее не было вовсе?

Завински: Нет, документация была, но зачастую она не отвечала действительности. Возможно, ошибка вкралась несколькими версиями раньше — кто знает? Но в определенный момент ты изменяешь этот бит, и машина уже не воспринимает твою программу как исполняемый модуль, и тебе приходится выяснять, что же произошло.

Сейбел: Ну, такое случается сплошь и рядом, начиная от низкоуровневого системного программирования и заканчивая высокоуровневым API, когда всё начинает работать совсем не так, как ты ожидаешь, или не так, как написано в документации. Как вы справлялись с этим?

Завински: Да просто начинаешь ожидать этого. Чем раньше поймешь, что сбился с пути, тем раньше сможешь выяснить, где именно. Лично я пытался создать исполняемый файл. Я знал, что компилятор Си может создавать исполняемые файлы. Поэтому алгоритм работы был такой: берешь хороший исполняемый файл и начинаешь его ковырять, пока он не превратится в плохой. Это основной механизм обратной разработки (reverse engineering).

Думаю, именно в компании Lucid я исправил самый сложный компьютерный баг. Я дошел до момента выполнения исполняемого файла, когда тот пытался загрузить интерпретатор Лиспа, но после выполнения 500 инструкций процесс загрузки падал. Тогда я начал выполнять процесс загрузки пошагово, чтобы выяснить, где же он падает. Хотя это было бессмысленно, создавалось впечатление, что процесс падал каждый раз в другом месте. Я стал исследовать ассемблерный код компьютерной архитектуры, о которой имел лишь смутное представление. Наконец до меня дошло. «Господи, при пошаговом выполнении он делает что-то не то. Возможно проблема связана с временными задержками». В итоге я понял, что происходило: дело в том, что это была одна из первых машин с упреждающим исполнением команд[6]. В этом случае выполнялись обе ветви кода[7]. Но GDB[8] при пошаговой отладке выполнял только одну из ветвей. Так что баг был в GDB.

Сейбел: Здорово.

Завински: Точно. Но это меня подкосило. «Господи! Мне придется отлаживать GDB, который я первый раз вижу». Чтобы обойти ошибку отладчика, нужно остановить выполнение процесса перед инструкцией ветвления, задать точки останова в обеих ветвях и продолжить выполнение. Именно таким способом мне удалось воспроизвести ситуацию и понять, что же происходит на самом деле. Затем я потратил около недели на исправление GDB, но так и не смог понять, в чем же дело. Я предполагал, что из-за проблем с одним из регистров отладчик считал, что всегда выполняется одна из ветвей условия или что-то в этом роде. Поэтому я изменил команду пошагового выполнения инструкций, чтобы определить, когда оно дойдет до инструкции ветвления, и там сказать: «Стоп, это не делай». Теперь я мог просто пошагово выполнять программу. Выполнение в конце концов останавливалось, я вручную задавал точку останова и продолжал выполнение. Когда что-то отлаживаешь, понимая, что не только путь выбран неверный, так еще и инструмент никуда не годится, — что может быть хуже.

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

Сейбел: А потом у вас появился отладчик Лиспа, и вы оказались во всеоружии.

Завински: Ага.

Сейбел: И примерно в то же время компания Lucid сменила курс, решив создавать интегрированную среду разработки для C++.

Завински: Это началось еще до меня: когда я пришел туда, интегрированная среда разработки уже создавалась. Люди начали переходить с Лиспа на Energize — так называлась эта среда разработки. Это был отличный продукт, но он появился на два-три года раньше, чем нужно. Никто — по крайней мере, никто среди пользователей UNIX — не думал, что им вообще это нужно. Сейчас все используют такие возможности, но тогда мы тратили кучу времени, объясняя, почему эта штука лучше, чем vi[9] и GCC. Еще я делал кое-что для Emacs. Кажется, тогда я уже переписал компилятор байт-кода[10]. Зачем мне это было нужно? Правильно, потому что я делал что-то вроде адресной книги.

вернуться

6

Упреждающее исполнение команд (Speculative Execution), или исполнение команд по предположению, — это совокупность методов, позволяющая ЦП с конвейерной архитектурой обрабатывать команды без уверенности в том, что они реально будут исполняться в программе (например, в случае условного перехода). Если предположение оказывается верным, то исполнение команд продолжается и выигрывается время, а если нет (misspeculation), результаты упреждающего исполнения аннулируются. — Прим. науч.ред.

вернуться

7

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

вернуться

8

GDB (GNU Project Debugger) — переносимый отладчик проекта GNU, который работает на многих UNIX-подобных системах и умеет выполнять отладку многих языков программирования, включая Си, C++ и Фортран. -Прим. науч. ред.

вернуться

9

vi (сокр. от visual) — серия текстовых редакторов операционных систем семейства UNIX, которые применялись совместно с компиляторами GCC для разработки ПО. — Прим. науч. ред.

вернуться

10

В зависимости от реализации некоторые компиляторы могут преобразовывать исходный текст на языке ЛИСП сразу же в машинный код (native code), а некоторые вначале компилируют исходный код в промежуточный (байт-код), который уже затем интерпретируется в машинный код конкретной машины. Для Emacs были реализованы компилятор и интерпретатор. — Прим. науч. ред.