Блог пользователя RodionGork

Автор RodionGork, 13 лет назад, По-русски

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

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

Напишем программу "Хелло Ворлд", такого размера, что выводимый текст будет занимать в ней больше половины!

Как и вся магия, она будет плохо переносима, но под дос и винды работать будет. Что нам понадобится? Утилита debug.exe - в современных виндах она живёт в c:\windows\system32 - это примитивный дебаггер который остался там рудиментарно с самых старинных версий доса... Кое для чего он ещё может пригодиться! (хотя для серьёзной разработки на ассемблере нужен нормальный компилятор - ну а простенькие вещи и в дебаггере можно накидать)

Если вы работаете из под linux, как я, то вам понадобится либо виртуальная машина, либо dosbox.

Итак, запустите окошко с командной строкой (например, cmd) и выполните команду:

С:\>debug.exe

Здесь и далее договоримся что полужирным отмечено то, что пишем мы, а простым - то что нам отвечает система.

Вы должны увидеть приглашение в виде единичного тире (минуса, дефиса) в левой позиции экрана. Введите команду "a" и нажмите enter. Слева появится шестнадцатеричный адрес в виде сегмент:смещение. Сегмент может оказаться довольно произвольным, а смещение будет 100 - с этого адреса грузятся в память программы в формате COM. Вводите дальше инструкции, строка за строкой (всего 4)

-a
7234:0100 mov ah,9
7234:0102 mov dx,109
7234:0105 int 21
7234:0107 int 20
7234:0109

В пятой строке вы просто нажмёте enter и опять перейдете в режим приглашения.

Наша программа состоит из 4 команд - из них первые три посвящены вызову системной функции печати строки. Первая загружает номер подфункции (9) в регистр процессора ah, вторая загружает адрес выводимой строки (её смещение) в регистр dx а третья вызывает системную функцию (прерывание) номер 21h - это прерывание зарезервировано за большим количеством простейших полезных функций операционной системы ДОС. В частности 9-я подфункция печатает строку с заданного в dx адреса до символа доллар.
В четвёртой строке мы вызываем ещё одну системную функцию, номер 20h (она не требует каких-либо аргументов в регистрах процессора) - выход из программы (да, в таких маленьких программах выходом нужно заниматься специально!)

Но ведь у нас ещё не записана выводимая строка? Нет проблем. Правда debug.exe не умеет задавать текст, но будем действовать так: введите команду e 109 (ввод данных побайтово с такого то адреса) и потом набирайте, нажимая пробел после каждых двух символов (оно само будет переходить к следующему байту) шестнадцатеричные значения. Старые значения байтов будут отображаться перед точкой, новые после:

-e 109
7234:0109   45.48   24.65   55.6C   47.6C   00.6F   00.0D   00.0A
7234:0110   50.24

Введя последнее значение, мы опять нажали enter. Введите d 109 (дамп памяти с заданного адреса) и убедитесь что всё верно (там справа будет отображаться текстовое представление, что удобно). Вообще нужно понимать, что эту цепочку байтов я не высасывал из пальца, а просто набрал в блокноте а потом сохранённый файл дампировал тем же debug.exe

Теперь дело за малым - укажем размер файла, его имя - и сохраним его

-r cx
CX 0000
:11
-n hw.com
-w
Writing 00011 bytes

Здесь нужно пояснить только две вещи - размер программы - от адреса 100 до адреса 110 включительно - 17 байт (11h) - это число надо записать в регистр CX (но не в режиме работы программы, а в самом отладчике, т.е. как бы в текущее состояние процессора).
Имя файла должно иметь расширение COM - это самый старый формат исполнимых файлов в DOS, он не требует специальных таблиц загрузки как EXE (хотя там есть смешной нюанс, в который не будем углубляться - формат определяется не по расширению а по сигнатуре MZ в начале).

Теперь вводите команду "q" чтобы выйти из дебаггера обратно в командную строку.

Выполните созданную программу:
C:\>hw.com
Hello
C:\>

Проверьте её размер:

C:\>dir hw.com
HW          COM        17 ....
1 file    17 bytes

95% из читающих это, это знание никогда не понадобится. Но в качестве развлечения - тем кто не знает что такое ассемблер, процессор и его регистры, адреса памяти, функции операционной системы и т.п. - всё это может сгодиться, не так ли? В инете вы можете найти массу информации на эту тему - ну или спросить меня если что. ;-)

Добавлю, что до недавнего времени проводились соревнования Hugi Compo - кто напишет самую маленькую программу выполняющую требуемые действия (например, распечатывающую текущий каталог).

Актуальность ассемблера для x86 сейчас сильно снижается по понятным причинам, хотя ессно существует куча других процессоров, систем и ассемблеров. Если будете программировать стиральные машины или микроволновки - обязательно об этом узнаете. ;-)

  • Проголосовать: нравится
  • +46
  • Проголосовать: не нравится

13 лет назад, # |
  Проголосовать: нравится +1 Проголосовать: не нравится
Благодарю, за проведение разбора моего поста :)
13 лет назад, # |
  Проголосовать: нравится +1 Проголосовать: не нравится
Всегда не любил когда преподавали "16-битный" ассемблер.
32-битный с одним сегментом лучше:)
  • 13 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

    Это совершенно непринципиально. По работе я вообще с ассемблерами для PC не сталкивался. Ну а для контроллеров у которых до 64кб ROM и пара килобайт оперативки сами понимаете, сегментация редко встречается... ;-)

    В данном случае дебаггер работает в реальном режиме, поэтому и хрен с ним %)

    UPD: да и в 32-битном режиме (в защищённом) вы ведь тоже можете иметь целую кучу сегментов пересекающихся/нахлёстывающихся как угодно... Только что они в принципе побольше могут быть... Ну да, надо говорить "селектор/дескриптор", но какая разница... ;-)

    • 13 лет назад, # ^ |
        Проголосовать: нравится +1 Проголосовать: не нравится
      На самом деле тот дебагер понимает мало команд, меньше чем мне надо было (плюс 16-бит адресация), так что для получения нормального машинного кода быстро и качество, я начал использовать visual studio, который можно заставить в режиме дебага выводить дизасемб и машинные коды для каждой команды программы :)
      • 13 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        Про debug.exe я читал ещё в книжке Питера Нортона, выпущенной в незапамятные времена... И он конечно мало изменился с тех пор.

        По-настоящему крутой в этом отношении всё-таки компилятор от GNU - микрософт там и рядом не стоял - но пока разберёшься с его форматами вставок - есть шанс голову сломать... ;-)
13 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Могу добавить только, что принцип по которому С позволил творить сие чудо известен и используется уже давно: а именно, то что данные и код по сути не различимы и если направить исполнитель команд на данные он потопает "исполнять" данные как ни в чём ни бывало :)
В С++ указатели на функции очень упрощают переход от одного к другому, надо только немного каста ;)
  • 13 лет назад, # ^ |
      Проголосовать: нравится +1 Проголосовать: не нравится
    Кстати много где запрещены ассемблерные вставки, а таким способом можно обходить этот запрет?
    • 13 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Можно обратить внимание что MSVC например ругается на функции типа strcpy или strcat как на несекьюрные, и требует применять strcpy_s или strcat_s... Это одно из последствий возможности нечаянно (или по чужому злому умыслу) исполнять данные вместо кода.
    • 13 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Да, идея забавная.
      На сколько я знаю visual с++ присекает такую магию. Gnu позволяет, и задача будет только в том чтобы не прогадать с операционкой.
  • 13 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    К сожалению это так отнюдь не во всех компиляторах. Очень многие процы сделаны так что код и данные жёстко живут в разных адресных пространствах. Компиляторы C для таких процессоров (Keil для 8051 например или GNU AVR) имеют кучу забавных особенностей которые требуются чтобы обходить эту проблему.

    Представьте например что у вас строка - литерал. Она хранится в памяти кода. Но при работе она должна находиться в ОЗУ - т.е. при инициализации её надо туда скопировать, да ещё место зарезервировать. Это просто жуть.
13 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Боян "немного". Читал шесть лет назад.
  • 13 лет назад, # ^ |
      Проголосовать: нравится +3 Проголосовать: не нравится
    Думаешь, стоило рассказать о более магичных вещах (вот до чего ассемблерные вставки доводят):

    Про дифференциальный счётчик для линейного шагового двигателя


    Или хотя бы о простом радиофотоплетизмографе:
13 лет назад, # |
  Проголосовать: нравится +25 Проголосовать: не нравится
Боян немного. Писал 19 лет назад.
13 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
На самом деле настоящий фокусник просто так не раскрывает своих секретов, чтобы у зрителей осталось хоть немножечко веры в чудо :D
  • 13 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    Ну да, я думал, ты как честный человек asm compiler + самописную конвертилку + debugger для выяснения адресов использовал :)
    • 13 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      На самом деле всё руками. hview использовал очень активно. Куски кода писал на встроеном асме в visual и смотрел машинный код.