Почему-то не нашёл с первой попытки здесь на Хабре какого-нибудь демо или инструкции по использованию этой старинной, но милой тулы из стандартной поставки DOS. Давайте быстренько это исправим. Как легко догадаться из названия - DEBUG.EXE предполагается использовать для отладки. Мы же напишем в ней несколько коротких ассемблерных программ "с нуля" - это не займет много времени, а притом даст лёгкое ощущение магии!
Экспериментировать можно в DosBox или DosEmu - правда если вы возьмёте версию DEBUG.EXE например из MS-DOS 6.22 то скорее всего обнаружите что она не заработает в эмуляторе. Несложно отыскать в интернете рабочую версию (из Windows XP или FreeDOS например - иногда также под именем DEBUG.COM) - либо установить полноценный ДОС в виртуалку вроде VirtualBox.
Внимание! Эта статья имеет скорее развлекательно-познавательное назначение - её автор вовсе не думает что кому-то (кроме студентов со старыми лабами по "архитектуре микро-ЭВМ") в наше время это понадобится в практических целях. Мне же эта мини-инструкция пригодится как вспомогательная для будущей статьи про "самокопирующуюся программу".
Историческое замечание
В старые времена операционная система DOS пользователю доставалась на нескольких дискетах, одна из которых была загрузочной и позволяла сделать загрузочным и жёсткий диск - а остальные 2-3 содержали в слегка сжатом виде кучку полезных вспомогательных программ. Среди них были аж две посвящённые программированию - QBasic и вот Debug.
Трудно сказать для чего она там была - большинству обычных пользователей не пригодится. Отлаживать собственные программы написанные на языках высокого уровня в ней вряд ли добно т.к. она дизассемблирует программу в машинные инструкции и не поддерживает (вроде) отладочную информацию. Но потенциально гуру мог с её помощью творить чудеса. Хотя у гуру наверняка были под рукой инструменты и посерьёзнее.
Мы не гуру, но небольшие чудеса доступны и нам. Чем мы теперь и займёмся.
Дальнейшие манипуляции подразумевают поверхностное знакомство с ассемблером (или некоторое желание познкомиться) но в глубокие подробности мы уходить не будем. Если чувствуете что интересно, почитайте или посмотрите какой-нибудь тьюториал или хотя бы эту мою ознакомительную статью.
Создание исполнимого COM-файла
Популярным форматом исполнимых файлов в DOS/Windows были EXE
- они позволяли загружать код в несколько сегментов и могли быть довольно большими.
В то же время мы помним что существовали и COM-файлы - они в отличие от EXE не содержали никакого заголовка, а состояли непосредственно из машинных инструкций. ОС загружала их в один сегмент памяти, в котором они и работали. Размер сегмента 64кб - как раз определял максимальный размер COM-файла. Впрочем ОС выясняла тип файла по заголовку, а не расширению, так что существовала некая путаница.
Мы будем писать маленькие программы и сохранять их в COM-файлы.
Запустим эмулятор и в нем запустим DEBUG.EXE - появится "приглашение" к вводу команд в виде одиночного дефиса:

Создадим самую простую программу которая делает одно действие - выходит обратно в ОС. Действительно, из ассемблерной программы надо уметь выйти...
Здесь есть краткая справка-напоминалка по командам, вызываемая командой "?", но подробной инструкции нет. UPD: обратите внимание на комментарий коллеги RCgoff и предложенную им ссылку на руководство к данной программе.
Ну а мы используем сначала команду "a" (ассемблирование, ввод инструкций) - для того чтобы ввести единственную команду нашей программы RET
(после этого ввод на пустрой строке возвращает к командному режиму). Можно заметить что команды располагаются с адреса 0x100 в текущем сегменте памяти - такое соглашение для COM-файлов.
Затем команда "n" позволяет задать имя файла, а команда "w" записывает его. Правда ей нужно указать сколько байт писать - довольно странным образом - занести нужное число в регистр CX - это мы делаем с помощью команды "r cx".
Выйдем с помощью команды "q" и полюбуемся что файл действительно создался и его размер 1 байт.

Если запустить эту программу (командой test.com
) то покажется что она ничего не делает. Это неправда - она делает важное дело - выходит обратно в ОС. Если вам понравится экспериментировать с ассемблером, вы ещё не раз столкнётесь с программами которые "не выходят" из-за какой-нибудь ошибки.
Как работает однобайтовый RET
- тут тоже замешана некая магия - при загрузке COM-файла память в сегменте инициализирована таким образом что в стеке лежит одно число, 0000 - а по соответствующему адресу расположены байты CD 20
(то есть инструкция int 0x20
) - вызов досовской системной функции для выхода в ОС.
Усложним нашу программу, пусть научится выводить на экран.
Печать на экран и пошаговое выполнение
Запустим debug
снова и введём программу посложнее
mov ah,2
mov dl,2a
int 21
ret
Здесь мы используем функцию 2 из "досовских прерываний" - она печатает символ код которого записан в регистр DL. Номер функции указывается в регистре AH, команда INT 21
это вызов прерывания с заданным номером "вручную" - так мы вызываем системные функции ДОС. Ну и RET мы уже видели - он выталкивает адрес из стека и переходит по нему (используется для возврата из подпрограмм).

Программа работает - печатает звёздочку! Попробуем теперь загрузить её снова и выполнить по шагам. Для этого вызовем debug.exe указав имя нашей программы, для отладки. После этого сразу выполните команду "u" (unassemble) - дизассемблировать код.

Вы увидите что дизассемблируются сразу больше десятка инструкций - оно и понятно, отладчик не умеет "угадывать" после какой инструкции ваш код заканчивается.
Из интересного - мы видим левее команд их "машинные коды" - и в частности, какая команда сколько байт занимает.
Посмотрим на состояние регистров - для этого служит команда "r" без параметра:

отладчик печатает содержимое всех регистров флагов - а также команду на которую указывает регистр IP
(instruction pointer) - и которая будет выполнена следующей. Чтобы сделать это в пошаговом режиме - используем команду "t" (trace). Я проделаю это пару раз для наглядности:

Мы выполнили команды MOV AH,02
и MOV DL,2A
- и действительно можно видеть что в старшей (H) половине регистра AX и в младшей (L) половине DX появились нужные значения! Команду прерывания мы выполним с помощью инструкции "g 106" - от слова "go" - ей можно указать адрес до которого нужно выполнить программу. С помощью инструкции "t" мы бы зашли внутрь подпрограммы системного вызова и вскоре там бы заблудились.

Здесь мы выполнили команду "g" дважды - видно что в первый раз после ввода "g 106" напечаталась робкая звёздочка (перед именем регистра AX) - а во второй раз отладчик корректно определил что сработала системная процедура завершения - и выдал соответствующее сообщение. Адрес останова для команды "g" я беру из текущего адреса добавляя столько байт, сколько (как вижу) занимает команда.
А как же Hello World?
Можно напечатать не только звёздочку, но и вывести целую текстовую строку. Маленькая загвоздка в том, что нам нужно занести текст в память программы, а в DEBUG.EXE по-моему память можно редактировать только в шестнадцатеричных кодах, это не очень удобно, поэтому схитрим - запишем нужную нам строчку в файл заранее - а заодно испытаем ещё одну полезную команду.
Начнём с того что (выйдя из debug) используем команду "echo" для того чтобы записать нужную нам строчку в новый файл. После чего загрузим его в дебаггер как и в прошлый раз, и выполним команду "d" - дамп памяти.

Можно видеть что не только все интересующие нас видимые символы строки удобно разместились в файле - но тут также есть и символы возврата каретки (0x0D) и перевода строки (0x0A). Это всё нам пригодится, но есть нюанс - куда же писать инструкции, ведь на их месте теперь данные?
Давайте сдвинем данные на 3 байта чтобы перед ними можно было разместить хотя бы одну инструкцию, с которой начнётся программа. Для этого есть команда "m" с достаточно задумчивым форматом аргументов - нужно указать адрес, включая сегмент, с какого (и до какого) взять данные - и куда двигать. В нашем случае мы двигаем начиная с адреса CS:100
до адреса 111
- и целевым адресом будет 103
(на 3 байта). Этот трюк удобен (в отладчике) и тогда когда нужно сдвнуть часть программы чтобы добавить не влезшую инструкцию (но в общем для больших программ лучше использовать полноценный компилятор ассемблера, с метками).

Здесь видно, что сдвинув нужный фрагмент данных (из-за чего получилось заикание "PrePreved...") я дампнул память с адреса 100, чтобы посмотреть что вышло (если не указать адрес то повторная команда "d" дампает все дальше и дальше) - а потом вызвал команду редактирования памяти "e" (edit) и добавил символ доллара по адресу 114, он будет означать конец выводимой строки. Как и говорилось выше, для этого приходится использовать код нужного символа, что не очень удобно.
Отлично, теперь основной код нашей программы можно будет разместить с адреса 115. В начале же нужна инструкция безусловного перехода на этот адрес. Добавим её

Как видим, я немножко ошибся, думая что инструкция перехода займёт 3 байта. Для близких переходов есть и двухбайтовая версия и дебаггер услужливо использовал короткий вариант.
Что же, дальше пишем инструкции с адреса 115 - мы воспользуемся функцией 9 того же "досовского прерывания" - она выводит строку, адрес которой загружен в регистр DX - и которая завершается символом доллара.

Адрес строки, после того как мы её подвинули, стал 103 - его мы и загрузили в DX. Дальше всё по аналогии с предыдущей программой (про звёздочку). После ввода последней инструкции мы замечаем следующий адрес (011D) из чего делаем вывод что размер программы 1D байт - даже не нужно думать сколько это в десятичной системе - просто помещаем это число в CX и апдейтим файл. Запускаем - убеждаемся что всё в порядке.
Программа посложнее - ввод имени пользователя
Эта часть не столько про использование программы DEBUG - с её базовыми возможностями мы уже достаточно познакомились - сколько для того чтобы дать с её помощью чуть более подробное представление о программировании на ассемблере. Инструкции будут не очень подробными - в основном в виде скриншотов "что должно получиться" - так что вы сами сможете поэкспериментировать с тем как достичь нужного результата.
По предыдущей паре примеровы выглядит так, что мы лишь подготавливаем данные для системных подпрограмм (досовских прерываний) - и не так-то много программируем сами! Чтобы устранить это ощущение, попробуем расширить функционал последней программы - пусть она спрашивает как пользователя зовут (может, он не Medved) - и приветствует его персонифицированно. Для этого нам потребуется:
завернуть функционал вывода строки в подпрограмму, т.к. он будет использован 4 раза
написать подпрограмму ввода строки
Программа будет выполнять это в такой последовательности:
печатаем фразу "как вас зовут?"
вызываем ввод имени и запись его в отдельном месте в памяти
печатаем по очереди "Preved, ", введённое имя и ещё коротенькую строчку из восклицательного знака с переводом строки.
Первым делом сделаем по аналогии файл, в котором содержатся три вышеперечисленные константные строки (заканчивающиеся символом доллара) а перед ними идёт двухбайтовая инструкция JMP. Придумайте как зарезервировать место для этой инструкции при создании файла, чтобы не двигать данные командой "m". Нам нужно получить нечто вроде:

Байты EB 20 в начале - это код команды JMP 122 в чём несложно убедиться дизассемблировав с этого адреса. Однако в дальнейшем мы этот адрес изменим, поскольку с адреса 122 собираемся разместить ещё вышеупомянутые подпрограммы - перескакивать на них нам не нужно. Перескакивать мы будем на главную часть программы следующую далее (непринципиально размещать их в таком порядке, но нам так удобнее их написать и протестировать).
Подпрограмма для печати строки очень проста - она напоминает вышеприведённый код, только регистр DX заполнять не нужно (его мы будем заполнять до вызова подпрограммы, разными значениями каждый раз).
Вторая подпрограмма похитрее - она будет получать адрес некой пустой области, куда можно записать строку - а внутри содержит цикл, в котором вызывается системная подпрограмма ввода символа - эти символы с помощью цикла мы запишем последовательно в указанную область памяти а когда пользователь введет возврат каретки, запишем вместо него символ доллара и завершим цикл.

Как видите, в скомпилированном коде инструкциями никак не выделено начало подпрограмм - мы просто переходим на нужное место и все дела. Конец можно угадать по инструкции ret
- хотя понятно что из подпрограммы может быть несколько выходов. Здесь у нас две подпрограммы - одна из 3х инструкций с адреса 122 (печать строки по адресу в DX) - вторая из 9 инструкций, с адреса 127 (ввод строки в область по адресу в BX).
Первую комментировать не будем, а по второй пройдёмся подробнее. Мы видим что первыми двумя инструкциями вызывается 1-я функция досовского прерывания int 21. По справочнику легко найти что эт функция ввода символа с клавиатуры - при этом символ отображается на экране - а в результате оказывается записан в регистр AL. Есть и версия без отображения (функция 8) - актуально для паролей.
Дальше интересная инструкция mov [bx], al
- квадратные скобки означают что мы записываем не в регистр BX, а в адрес памяти который в этом регистре хранится. Почему не DX? Архитектура 8086 изначально умела использовать для такой косвенной адресации только один из 4 регистров (bx, si, di, sp).
Адрес в регистре мы дальше хотим увеличить - это инструкция inc bx
- от слова "инкремент" - и теперь хорошо бы понять был ли введён видимый символ, либо какой-то управляющий (в частности перевод строки). И в зависимости от этого либо повторить операцию, либо завершать подпрограмму. В ассемблерах сравнение и переход по условию обычно это две отдельных инструкции.
Инструкция сравнения cmp al,20
- сравнивает введённый символ с пробелом (все видимые символы в ASCII начинаются с пробела имеющего код 0x20) - мнемоника от слова "compare" - сравнить. Мнемоника следующей инструкции "jge" расшифровывается как "jump if greater or equal" - перейти если больше или равно.
Причем это "больше или равно" относится к результату предыдущей инструкции. Как этот результат передаётся? Есть специальный набор однобитовых "флагов". В нём и выставляется нужная комбинация инструкцией cmp
- а потом проверяется инструкцией jge
. Мы это вскоре увидим в действии.
Дальнейшие инструкции в общем понятны - если мы вывалились из цикла, то записываем в AL код для символа доллара (0x24) и записываем AL в память по адресу BX-1. Тут может создаться ошибочное впечатление что ассемблер поддерживает математические выражения - но нет - это просто инструкции записи в [bx]
можно задать таким образом дополнительное смещение для адреса. Иногда удобно.
Если вы уже ввели эту абракадабру, попробуйте потестировать её. Сначала выполним до инструкции jge
- вы увидите что отладчик как бы зависнет ничего не выдавая - это он ждёт от вас нажатия клавиши. Мы сможем поанализировать состояние флажков в этой точке. Потом же запустим выполнение до инструкции ret
- куда мы попадём только по окончании цикла.

Как видите, чтобы отладить подпрограмму, мы выставляем адрес её начала в регистр IP а также задаём некий свободный адрес (0x200) в регистре BX который мы назначили параметром этой подпрограммы. После этого команда g 130
запускает выполнение одной итерации. Как видите после неё была введена буква "P" (она появилась перед названием регистра AX в следующей строке). После чего мы запустили выполнение до адреса 137.
После обоих этих запусков нам распечатали две строчки регистров - заметьте что они заканчиваются двухбуквенными именами флагов - в частности последний из них различается. После первой итерации он был NC (no carry - нет переноса) - а после последней там CY (carry - случился перенос).
Смысл в том что операция cmp
это просто вычитание второго операнда из первого, но без записи результата куда-либо - только флаги проставляются. И когда мы вычитаем из меньшего числа большее - случается "заём", как учат ещё в школе при вычитании в столбик. Этот заём и отображается во флаге "займа/переноса". Поскольку инструкция JGE проверяет было ли первое число больше или равно второму - она ориентируется именно на флаг переноса (её другое имя JNC - jump if no carry).
Как видите, по окончании мы дампнули область куда должна была записаться строка - и мы её там видим!
Что же, теперь дело за малым - добавим главную часть программы, вызывающую эти подпрограммы:

инструкции добавленные с адреса 138 пояснять наверное не нужно - здесь пять вызовов подпрограмм добавленных выше, просто скармливаем им нужные адреса.
Что произошло дальше - это поучительно, т.к. случается при недостатке внимания довольно часто. Я записал 0x57 байт в файл, но вместо того чтобы выйти и запустить этот файл на выполнение, решил проверить программу прямо в отладчике.
Для этого установил IP на начало (0x100) и попросил выполнить до адреса 156 (последний ret в главной части).
Как видим, напечаталась некая абракадабра и программа завершилась. Почему?
Ах да, я ведь забыл что инструкция перехода в начале до сих пор указывает на адрес 122 - так что вместо главной части начала выполняться инструкция печати, в DX был какой-то непредсказуемый адрес (вероятно 0000) - поэтому напечаталось много лишнего - а потом RET из подпрограммы оказался поневоле "завершением программы" т.к. мы ни в какую подпрограмму не входили и на стеке лежал адрес 0000, по которому нашлась инструкция int 20h.
Конечно это легко исправить, благодаря тому что я сохранил код в файл (он очищается в отладчике когда программа завершается силами ДОСа). Вот правка и тестирование - как в отладчике, так и вне его, вызовом готовой программы из командной строки:

Как вообще работает отладка?
Запустите отладчик снова и с чистого листа введите три простые инструкции:

Мы пытаемся записать число 0x55aa в память по адресу 0x109. Для этого записали 0x100 в регистр BX, а само число в AX - и пишем со смещением +9 (для красоты).
Выполнив эти инструкции пошагово (потрассировав) мы видим что запись вроде бы произошла - в следующей инструкции (как раз имеющей дрес 0109) появилось AA - младший байт записанного числа (он определен как однобайтовая инструкция).
Однако теперь попробуем вернуть IP на начало программы и выполнить все три инструкции одним махом, с помощью команды "g 109" - т.е. попросив остановиться когда выполнение дойдёт до нужного адреса.
После того как я нажал Enter введя эту команду, отладчик завис и через несколько секунд вообще эмулятор был убит системой (это поведение недетерминировано).
Почему так вышло? Сделаем ещё эксперимент - снова запустим с чистого листа и введём похожую простую программу:

Здесь мы не пишем в память а наоборот читаем - из адреса 106, следующего аккурат за первыми двумя инструкциями. Третьей инструкцией идёт NOP (нет операции) с кодом 90 - просто "пропуск хода" процессором. Что мы ожидаем увидеть в AX после такого чтения?

Мы ожидали увидеть 0090 - именно такие коды были в памяти в байтах по адресам 107 и 106 (с учётом порядка LSB) - но ничего подобного мы не видим! Как будто вместо инструкции NOP прочлась какая-то другая, с таинственным кодом 0xCC. Что же это за инструкция? Легко выяснить введя её код в память и вызвав дизассемблер:

Это код инструкции INT 3 - причём заметим что в отличие от других прерываний используется особая однобайтовая инструкция а не обычное двухбайтовое CD 03
как мы могли бы ожидать.
Справочник подскажет что это специальное прерывание для отладки. С его помощью отладчик устраивает "брейкпойнты" - вставляет его в код в требуемом месте (до которого велели выполниться) - и запускает программу. Когда прерывание срабатывает остаётся только восстановить байт по этому адресу. И никто ничего не заметит.
Кроме случая программы активно модифицирующей или инспектирующей собственный код.
Поэтому-то в первом случае дело закончилось крашем - отладчик вставил "брейкпойнт" - а мы его перезатёрли по ходу выполнения предыдущих инструкций. Останов не случился и процессор пошёл выполнять дальше неведомые и рандомные инструкции.
Заключение
Надеюсь, несмотря на откровенный архаизм инструментария, эта небольшая инструкция покажется кому-то полезной. Кроме того многие версии DEBUG.EXE которые вы найдёте имеют команду для переключения на 32-разрядный режим, в котором отображаются всё-таки "более современные" регистры (EAX, EBX...) и доступны команды появившиеся в 80386.
Мы же, надеюсь, ещё вернёмся к этим экспериментам чуть позднее, в статье которую я хочу посвятить небольшому упражнению именно над "самомодифицирующимся" кодом. До скорого!
Примечание (и упражнение)
В подпрограмме ввода имени мы определяли конец ввода по признаку "код меньше чем у пробела" - может возникнуть вопрос, а почему не просто "равно коду перевода строки"?
Если вы уже потестировали эту программу то наверное обнаружили что такая проверка имеет явный недостаток - бэкспейс тоже завершает ввод вместо того чтобы стирать символы! Ведь он выдаёт код 8 - явно меньше 0x20...
Дело в том что полноценная подпрограмма для ввода строки будет значительно сложнее, даже если мы захотим обработать дополнительно только пробел. Для демонстрации посмотрим, что получится если просто заменить две команды на точное сравнение с кодом клавиши Enter:

Здесь мы загрузили программу, изменили прицельно две инструкции на "сравнить с 13" и "перейти к началу если не равно", после чего, как и раньше, настроили регистры IP и BX чтобы протестить только саму подпрограмму ввода - и запустили её.
По скриншоту кажется что дальше мы просто ввели имя "Sauronf" - но когда мы дампаем область памяти с введённой строкой, оказывается что всё было сложнее:
ввели имя Gandalf (7 букв)
нажали 7 раз бэкспейс
ввели имя Sauron (6 букв)
Выдача бэкспейса на печать в DOS-е работает так что курсор сдвигается назад, но знакоместо не очищается. Поэтому последняя буква от Гэндальфа осталась висеть и у Саурона (имя которого оказалось немного короче). А во введённой строке, в памяти, сохранились и Гэндальф, и Саурон, и семь бэкспейсов между ними.
В качестве упражнения можете попробовать модифицировать эту подпрограмму ввода строки, так чтобы она правильно реагировала на бэкспейс - то есть уменьшала адрес в BX и всё-таки стирала символ на экране. Если не знаете, как стирать - попробуйте придумать. Того что вы узнали вполне достаточно, чтобы догадаться! Желаю успеха!
Упражнение "со звёздочкой"
Займёмся хакерством! Возьмите какой-нибудь COM-файл, не самодельный, а готовый. Хотя бы DEBUG.COM если он у вас не в EXE формате. А ещё лучше - установите в эмуляторе Windows 3.1 и наложите руки на WIN.COM
- файл который запускает сам Windows!
Что требуется сделать:
загрузите файл в отладчике, т.е.
debug win.com
просмотрите первые команды - почти наверняка он начинается с перехода
jmp
- запомните (или запишите) адреспоменяйте адрес перехода так, чтобы он попадал за конец (последний байт) файла
припишите в этом месте (за концом файла) немного кода, который будет запрашивать у пользователя пароль - если он соответствует хранящейся в коде секретной фразе, нужно сделать переход на тот адрес который вы записали ранее - в противном случае выйти из программы (или заставить пользователя повторять ввод до опупения)
в идеале нужно хранить секретную фразу в коде в слегка зашифрованном виде (XOR-ом или сдвигом на единицу) - чтобы при дампе (просмотре файла) она не была видна
занесите увеличенный размер файла в CX и перезапишите файл
проверьте, работает ли ваш "хак"
В зависимости от выбранной сложности подпрограммы ввода строки (и длинны секретной фразы), этот "довесок" к файлу должен занять около сотни байт - так что справиться будет несложно, даже новичку - но надеюсь что когда у Вас все получится "как надо" Вы почувствуете определённый восторг :)
Комментарии (8)
pfemidi
18.08.2025 04:55symdeb.exe наше всё! По интерфейсу как debug.exe, но чуть-чуть пофункциональнее.
RodionGork Автор
18.08.2025 04:55любопытно, не слыхал но взгляну, спасибо :) хотя тут ясно интерес был в основном из того что эта тула исторически валялась во всех дистрибутивах доса и виндовса не знаю с каких пор...
pfemidi
18.08.2025 04:55symdeb в отличии от debug не входил в DOS, но входил в поставку MASM и в Windows SDK для Windows 3.x Умел делать абсолютно то же самое что и debug но чуть больше и чуть дружелюбнее в некоторых командах (если подобный интерфейс вообще можно назвать дружелюбным). Плюс AFAIR умел показывать символы в одном из первых форматов CodeView.
https://www.pcjs.org/blog/2018/02/25/
RCgoff
18.08.2025 04:55а в DEBUG.EXE по-моему память можно редактировать только в шестнадцатеричных кодах
Неверно. Вы вполне можете писать что-то вроде E CS:1B0 "Hello world".
Посмотрите доку на DEBUG здесь:
https://www.pcjs.org/documents/books/mspl13/msdos/encyclopedia/section4/
(в главе больше материала, нужно поиском по странице перейти к фразе "Program debugger").Вообще-то DEBUG умеет и побольше. Точки останова, пропуск циклов и подпрограмм (т.е. step over).
Заносить в IP 100h для пуска не обязательно, можно печатать g=100 и все. И точку останова ("выполнить до адреса") тоже не обязательно.
LeonidPr
18.08.2025 04:55Начинал с debug. Так уж вышло, что на первом моем IBM PC компе на 386м кто-то в целях оптимизации места выкосил все .hlp файлы и qbasic. Был только debug.exe
А потом мне попала в руки книжка Джордейна. С листингами и реализацией всякого на низком уровне. Оказалось, что в debug можно было кодить, ну и пошел процесс...
IisNuINu
ретро, как говориться, песни нашей молодости, более 25 лет к нему не обращался. Желаю автору с такой же любовью и подробностью написать, о чём нибудь по современнее, например про gdb.
RodionGork Автор
спасибо, правда как-то не кажется что особо захватывающе будет :) gdb приходится пользоваться более-менее регулярно но в основном для отладки на С и Go т.к. он-то отладочную информацию умеет :) надо глянуть, о нём-то уж наверное на хабре не раз писали