29.12.2024
| Главная | Поиск | Регистрация | Профиль | Вход | Выход |
Реклама
Реклама
Интересное
Главная » Статьи » Программирование на Ассемблере » Программирование на Ассемблере

Введение в машинный код_1 часть
Вы читали "Хроники Амбера" Роджера Желязны? Там есть такой эпизод:
  Главный герой находится в заточении. В абсолютной тьме. У него были выколоты глаза, но за год они регенерировали, и зрение постепенно к нему возвращается...
  И однажды каким-то чудом в одной камере с ним оказывается загадочный Дворкин — создатель Лабиринта. Именно "чудом" — он просто появился неизвестно откуда. Он тоже находится "в заключении", но, в отличие от Корвина (главного героя), может спокойно ходить через каменные стены.
  Удивленный Корвин спрашивает его:
  — Как ты оказался в моей камере? Ведь здесь нет дверей.
  Дворкин отвечает:
  — Двери есть везде. Просто нужно знать, как в них войти.
  Будем считать это эпиграфом...

1.1. Система счисления

  #1. Наверняка среди ваших знакомых есть "крутые" программисты, или люди, таковыми себя считающие ;). Попробуйте как-нибудь проверить их "на вшивость". Предложите им в уме перевести число 12 из шестнадцатеричной в двоичную систему счисления. Если над подобным вопросом "крутой программист" будет думать дольше 10 секунд - значит он вовсе не так крут, как говорит...

  #2. Система счисления (сие не подвластное человеческой логике определение взято из математической энциклопедии) - это совокупность приемов представления обозначения натуральных чисел. Этих "совокупностей приемов представления" существует очень много, но самая совершенная из всех - та, которая подчиняется позиционному принципу. А согласно этому принципу один и тот же цифровой знак имеет различные значения в зависимости от того места, где он расположен. Такая система счисления основывается на том, что некоторое число n единиц (radix) объединяются в единицу второго разряда, n единиц второго разряда объединяются в единицу третьего разряда и т. д.

  #3. "Разрядам" нас учили еще в начальных классах школы. Например, у числа 35672 цифра "2" имеет первый разряд, "7" - второй, "6" - третий, "5" - четвертый и "3" - пятый. А "различные значения" цифрового знака "в зависимости от того места, где он расположен" и "объединение в единицу старшего разряда" на тех же уроках арифметики "объяснялось" следующим образом:

35672 = 30000 + 5000 + 600 + 70 + 2
35672 = 3*10000 + 5*1000 + 6*100 + 7*10 + 2*1
35672 = 3*104 + 5*103 + 6*102 + 7*101 + 2*100 (1)

  #4. Очень наглядно это отображают обыкновенные счеты. Набранное на них число 35672 будет выглядеть... см. рисунок слева в общем...

  Чтобы набрать число 35672 мы должны передвинуть влево две "костяшки" на первом "прутике", 7 на втором, 6 на третьем, 5 на четвертом и 3 на пятом. (У нас ведь 1 "костяшка" на втором - это то же самое, что и 10 "костяшек" на первом, а одна на третьем равна десяти на втором, и так далее...) Пронумеруем наши "прутики" снизу вверх - да так, чтобы номером первого был "0"... И снова посмотрим на наши выражения:

35672 = 3*104 + 5*103 + 6*102 + 7*101 + 2*100

  Это (если сверху вниз считать) сколько на каждом "прутике" "костяшек" влево отодвинуто.

35672 = 3*104 + 5*103 + 6*102 + 7*101 + 2*100

  Это номер прутика (самый нижний - 0), на котором отодвинуто определенное число костяшек.

35672 = 3*104 + 5*103 + 6*102 + 7*101 + 2*100

  Это на каждом прутике - по 10 костяшек нанизано, не все влево отодвинуты, но всего-то их - 10!
  Кстати, красненькое 10 в последнем выражении соответствует основанию (radix) системы счисления (number system).

  #5. Пальцев на руках у человека 10, поэтому и считать мы привыкли в системе счисления с основанием 10, то есть в десятичной. Если вы хорошо представляете себе счеты и немного поупражнялись в разложении чисел аналогично выражению 1, то перейти на систему счисления с основанием, отличным от привычной, особого труда для вас не составит. Нужно всего лишь представить себе счеты, на каждый прут которых нанизано не привычные 10 костяшек, а... скажем, 9 или 8, или 16, или 32, или 2 и... попробовать мысленно считать на них.

  #6. Для обозначения десятичных чисел мы используем цифры от 0 до 9, для обозначения чисел в системах счисления с основанием менее 10 мы используем те же цифры:

radix 9 - 0, 1, 2, 3, 4, 5, 6, 7, 8;
radix 8 - 0, 1, 2, 3, 4, 5, 6, 7;
radix 2 - 0, 1 и т. д.

  Если же основание системы счисления больше десяти, то есть больше, чем десять привычных нам чисел, то начинают использоваться буквы английского алфавита. Например, для обозначения чисел в системе счисления с основанием 11 "как цифра" будет использоваться буква А:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A

  В системе счисления с основанием 16 - буквы от A до F:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F

  И так далее...

  Правда, при определенном основании (при каком?) буквы аглицкого алфавита закончатся...
  Но нам это, пока что, глубоко фиолетово, так как работать мы будем только с тремя radix-ами: 10 (ну естественно), 16 и 2. Правда, если кто на ДВК поизучать это дело собирается, тому еще и radix 8 понадобится.

  #7. Числа в любой системе счисления строятся аналогично десятичной. Только на "счетах" не с 10, а с другим количеством костяшек.
  Например, когда мы пишем десятичное число 123, то имеем в виду следующее:

1 раз 100 (10 раз по 10)
+ 2 раза 10
+ 3 раза 1

  Если же мы используем символы 123 для представления, например, шестнадцатеричного числа, то подразумеваем следующее:

1 раз 256 (16 раз по 16)
+ 2 раза 16
+ 3 раза 1

  Короче - полный беспредел. Говорим одно, а подразумеваем другое. И последнее не для красного словца сказано. А потому, что так оно и есть...

  Истина где-то рядом...

  #8. Трудность у вас может возникнуть при использовании символов A, B, C и т. д. Чтобы решить эту проблему раз и навсегда, необходимо назубок вызубрить ма-а-аленькую табличку "соответствия" между употребляемыми в "компьютерном деле" систем счисления:

radix 10   0123456789101112131415
radix 16   0123456789ABCDEF
radix 2    0 1 10 11 100 101 110 111 1000 1001 1010 1011 1100 1101 1110 1111

  Следуя этой таблице, число 5BC в шестнадцатеричном формате "строится" так:

5 раз 256 (16 раз по 16)
+ 11 раз 16 (10 - потому что по таблице B как бы равно 11)
+ 12 раз 1

  А теперь, если пораскинуть мозгами, с легкостью переведем 5BC из шестнадцатеричной в десятичную систему счисления:

5*256 + 11*16 + 12 = 1468

  Вот и объединили цифры с буквами. Пространство со временем поучимся объединять немного позже - если не испугаетесь сложностей низкоуровневого программирования.
  В общем-то решать вам. В Delphi тоже много чего объединять можно.

  #9. Двоичная система по-компьютерному обзывается "bin", "родная" десятичная - "dec", а шестнадцатеричная - "hex". Это так компьютерщики обозвали те системы счисления, с которыми имеют дело... А обозвали потому, что у них ведь полный бардак в голове, оказывается!
  Например, 10 - что это за число? Да это вообще не число! Палка и барабан - и только... А вот 10d или же 10_10 - уже понятно, что это - число, соответствующее количеству пальцев на обеих руках. И именно на обеих, а не на двух. Почему не на двух? - А потому что на двух в какой системе? Ежели в двоичной, так это на десяти! То бишь 100, если в десятичной...
  Вот и придумали программисты после числа буковку писать - b, d или h. А самые ленивые еще и директиву специальную придумали: напишут в самом начале программы какой-нибудь .radix 16 и будут автоматически все числа, которые без этих букв, за шестнадцатеричные приниматься.

  #10. Еще немного про перевод между "радиксами". (Вообще-то это плевое дело, конечно, если представляешь себе, что такое "совокупность приемов представления обозначения натуральных чисел").
  Например, преобразование числа 42936 из десятичного в шестнадцатеричный формат проводится следующим образом (в скобках - остаток):

42936/16 = 2683(8) 8 - младшая цифра
2683/16 = 167(11) B (11d=Bh по таблице)
167/16 = 10(7) 7
10/16 = 0(10) A - старшая цифра
--------------------------------------
42936d=A7B8h

  А вот и обратный процесс - перевод из HEX в DEC числа A7B8h:

10*16=160 160+7=167 (10 - потому что Ah=10d)
167*16=2672 2672+11=2683
2683*16=42928 42928+8=42936
--------------------------------------------
A7B8h=42936d

  Преобразования чисел в системы счисления с другим основанием проводятся аналогично... Счеты! Обыкновенные счеты, только с "плавающим" числом "костяшек" на каждом "прутике"...

  #11. Если честно, то конкретный "рисунок" цифр - единица там палкой обозначается, двойка - лебедем - это все лишь историческая случайность. Мы запросто можем считать в "троичной" системе счисления с цифрами %, *, _ (где запятая - это знак препинания, а вовсе не число):

%, *, _, *%, **, *_, _%, _*, __, *%%, *%*, *%_, **%...

  Или использовать родные цифры в десятичной системе счисления, но по другому "вектору упорядоченных цифр" - 1324890576:

1, 3, 2, 4, 8, 9, 0, 5, 7, 6, 31, 33,34, 34,38, 39, 30, 35, 37...

  Правда, этим немножко затрудняется понимание происходящего? А ведь тоже десятичная система! И рисунок цифр как бы знакомый :-)))
  Или вообще считать в 256-ричной системе счисления, используя в качестве "рисунка цифр" таблицу ASCII-символов! (По сравнению с вами, извращенцами, любой Биллгейтс будет девственником казаться!!).

  #12. Теперь самая интересная часть Марлезонского балета.
  Компьютер, как известно, считает только в двоичной системе счисления. Человеку привычна десятичная. Так нахрена еще и шестнадцатеричную какую-то знать нужно?
  Все очень просто. В умных книжках пишут, что "шестнадцатеричная нотация является удобной формой представления двоичных чисел". Что это значит?
  Переведите число A23F из шестнадцатеричной "нотации" в двоичную. (Один из возможных алгоритм приведен в п.10.). В результате длительных манипуляций у вас должно получиться 1010001000111111.
  А теперь еще раз посмотрите на таблицу в п. 8. (которую вы как бы уже и выучили) и попробуйте то же самое сделать в уме :

Ah=1010b
2h=0010b
3h=0011b
Fh=1111b
A23Fh = 1010 0010 0011 1111b

  Каждой шестнадцатеричной цифре соответствует тетрада (4 штуки) ноликов и единичек. Все, что потом нужно сделать - "состыковать" эти тетрады. Круто? Вас еще не то ждет!

  #13. Кстати (наверняка вы это уже знаете):

00000123 = 123, но!!
123 <> 12300000

  ... но это так... кстати...

  #14. И, напоследок, еще несколько слов про HEX и BIN :). Зайдите в Norton Commander, наведите указатель на какой-нить файл и нажмите там F3. А когда он откроется - на F4. Там какие-то нездоровые циферки попарно сгруппированы. Это и есть "нолики и единички" (которыми в компьютере все-все-все описывается), но в шестнадцатеричном формате...

  Следует основательно разобраться с системой счисления. Минимум, что должен вынести из этой главы юзвер, вступивший на скользкий путь низкоуровневого программирования - это научиться переводить числа между DEC, HEX и BIN... хе-хе... В УМЕ!

1.2. Регистры

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

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

  О специализации нам пока что говорить рано, описание наподобие "регистр-указатель базы кадра стека" вам вряд ли о чем-то скажет. Поэтому для начала познакомимся только с так называемыми регистрами общего назначения (РОН), и то не со всеми, а только с четырьмя основными, которые являются своего рода "рабочими лошадками" микропроцессора.

  Вот их "имена собственные" - AX, CX, DX, BX (именно в такой последовательности они "упорядочены" в Intel'овских микропроцессорах).

  А сейчас мы поближе посмотрим на эти "рабочие лошадки" микропроцессора.

  1. Запустите программу DEBUG.EXE1.

  2. Когда появится приглашение в виде "минусика", введите букву "R" (можно и "r" - регистр символов значения не имеет) и нажмите на "Enter".

  Не правда ли, весьма похоже на то, что показывают в художественных фильмах про хакеров?

AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=18B2 ES=18B2 SS=18B2 CS=18B2 IP=0100 NV UP EI PL NZ NA PO NC
18B2:0100 6A DB 6A

  - Ну и что это такое? - скептически спросите вы.
  - А черт его знает! Будем разбираться!

  #2. То, что у вас должно появиться - это список доступных регистров и текущее значение каждого из них. Как видите, значения регистров AX, BX, CX, DX равны 0. Не правда ли, создается впечатление, что они просто-напросто ждут того, чтобы в них внесли какое-либо значение?

  Природа не терпит пустоты. Писателей приводит в ужас чистый лист бумаги...

  Весьма скоро и вы при виде "пустых" регистров будете испытывать непреодолимое наркотическое желание чем-нибудь их заполнить...

  Однако прежде чем мы сделаем это в первый раз, давайте уточним тип этих "переменных".

  А он очень простой, этот тип - шестнадцатеричное число в диапазоне 0...FFFF. Или, если в BIN, то - от 0 до 1111 1111 1111 1111.

  Маловато будет? А вот создатели первых "IBM-совместимых" :) компьютеров посчитали, что и этого много2! 16-битная переменная еще и на две части дробится - для совместимости с языками ассемблера для предыдущих моделей процессора Intel, работавших только с 8-битными регистрами; да и просто ради удобства...

  В общем, в умных книжках рисуют вот такую вот "нездоровую" схемку3:

AX
AHAH

  А означает она следующее.

  Физически существует один регистр - AX, а вот логически он делится на два - на старшую (AH) и младшую (AL) части (от английского - high и low).

  Очевидно, что присвоить AX значение, например, 72F9h, мы можем следующими способами:

1. AX = 72F9h (одной командой);
2. AH = 72h; AL = F9h (двумя командами).

  Точно так же присвоить значение 78h регистру AH можно двумя способами:

1. AH = 78h;
2. AX = 7800h.

  То же самое, но для регистра AL:

1. AL = 78h;
2. AX = 0078h .

  Тех, кого смущают числа с буквами, мы со зловредной ухмылкой отсылаем к 1.1. Система счисления :-]

  #3. Если рассматривать регистр "целиком", то каждый из них имеет "длину" 16 бит, которые принято нумеровать справа налево4 . Так, для числа 2F4Dh, внесенного, например, в регистр AX, мы можем нарисовать такую вот "навороченную" табличку:

AX 2F4D
AH AL 2F 4D
Значение бита 0 0 1 0 1 1 1 1 0 1 0 0 1 1 0 1
Номер бита 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Тетрады Старшая AH Младшая AH Старшая AL Младшая AL

  Внимательно смотрим на таблицу: одной шестнадцатеричной цифре соответствует тетрада двоичных цифр (4 шт., они же - 4 бита). "Емкость" регистров AH и AL - две тетрады, т. е. 8 бит. Точно такую "длину" имеют: коды символов, скан-коды клавиш, номера функций прерываний и куча всего прочего, чего вы пока еще не знаете.

  Емкость AX (состоящего из двух половинок) - 4 тетрады, т. е. 16 бит; они же (эти 16 бит) иначе еще называются "словом"...

  #4. "Принудительно" присвоить регистру значение можно при помощи той же команды "R", только с параметром "имя собственное регистра".

  Например, команда

- R AX [Enter]

  выбросит вам на монитор

:

  Введите после двоеточия, например, число 123 и снова нажмите на Enter:

:123 [Enter]

  На дисплее опять появится приглашение "-", на которое мы отвечаем командой "R" без параметров и таким образом вновь просматриваем значения наших регистров:

AX=0123 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=18B2 ES=18B2 SS=18B2 CS=18B2 IP=0100 NV UP EI PL NZ NA PO NC
18B2:0100 6A DB 6A

  Смотрим внимательно - AX=0123, что и требовалось доказать...

Примечания

  1). В W9X она находится в папке WINDOWS\COMMAND\. В Y2K и XP - WINDOWS\SYSTEM32\. В обоих случаях достаточно набрать в командной строке "debug", чтобы она запустилась.

  2). Не забудьте, что при тогдашней технологической базе и это было большим прорывом. А экстенсивное расширение, например, разрядности, во-первых, нужно правильно предвидеть (вспомните, сколько в том же MS-DOS закладок на будущее, которые никуда не пошли за ненадобностью), а во-вторых, правильно оценить (в буквальном смысле). Неужели вы думаете, что, например, производители памяти не могут легким мановением руки увеличить ширину шины, соединяющую память с процессором? Могут, но во-первых - это резко повысит стоимость памяти, а во-вторых - не гарантирует повышения производительности.

  3). Впоследствии мы немного усложним эту схемку - регистры современных процессоров 32-разрядные и называются немного иначе ;)

  4). "Первый справа" бит мы будем называть "нулевым". Однако нам попадались руководства, в которых это же бит обозван как "первый". Можно долго обсуждать тонкости русского языка (которые, к сожалению, не всегда понимает переводчик), однако это выходит за рамки данной книги. Просто имейте это ввиду, что можете с этим столкнуться, и будьте бдительнее, читая документацию.

1.3. Память

  #1. Первым видом памяти, с которым мы войдем (придется!) в тесный физический контакт, будет оперативная, она же - RAM (от английского - Random Access Memory). Оперативная память - это своего рода "рабочая площадка", по которой суетится этакий шустрый многорукий дядька-процессор - чего-то там собирает, от кучи к куче бегает, всех ругает... :)

  Оперативная память - это ряд пронумерованых ячеек размером в байт. Мы можем получить доступ к первому байту памяти, ко второму, к третьему и т.д.

  Короче - пришло время испробовать еще одну команду из скромного арсенала DEBUG'a! Запустите debug и введите команду D (от английского - DUMP).

  "Картинка", которую вы увидели, называется "дамп памяти" (что в переводе с английского означает "свалка") и она насыщена не только важной информацией, но и специальной низкоуровневой энергетикой. Да чего уж там греха таить - каждый ассемблерщик знает, что рассматривание дампа памяти поднимает настроение, жизненный тонус и другие, не менее важные вещи ;)

18B2:0100 6A 00 68 4B 01 66 83 7E-E0 00 74 05 B8 4C 01 EB j.hK.f.~..t..L..
18B2:0110 03 B8 4A 01 2B D2 52 50-57 FF 36 C4 34 00 A1 18 ..J.+.RPW.6.4...
18B2:0120 F7 7F 83 C4 12 56 9A 16-44 F7 7F FF 76 FE 9A 59 .....V..D...v..Y
18B2:0130 04 8F 17 B8 FE FF 1F 5E-5F C9 CA 06 00 90 C8 54 .......^_......T
18B2:0140 04 00 57 56 8B 76 04 33-C0 89 46 D6 B9 0B 00 8D ..WV.v.3..F.....
18B2:0150 7E D8 16 07 F3 AB 89 46-BC B9 0C 00 8D 7E BE F3 ~......F.....~..
18B2:0160 AB 9A 21 9C 8F 17 89 46-FA A1 08 01 8E 46 06 26 ..!....F.....F.&
18B2:0170 39 44 02 0F 84 55 01 C7-46 BC 1A 00 C4 5E 0C 26 9D...U..F....^.&

  Слева - это адрес памяти. В центре - 16 столбцов из спаренных цифр...

  А здесь и повториться лишний раз не грех. Каждая пара шестнадцатеричных цифр - это байт. Смотрите внимательно на дамп! Байт по адресу 100 имеет значение 6A, байт по адресу 101 - 00, байт по адресу 102 - 68... Эти "сладкие парочки" - и есть неделимая "единица адресации" оперативной памяти.

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

  И, наконец, столбец справа - это символы, соответствующие шестнадцатеричным кодам центрального столбца (например, коду 6A соответствует символ J). Большинству кодов не соответствует никакой из "печатных символов" - таким в колонке справа соответствуют точки.

  #2. А теперь потренируем наши пальчики дампировать память - пройдемся по некоторым "историческим местам" нашей оперативной памяти... Для этого мы будем вводить команду D с параметром.

  Например, команда (параметр L8 означает "вывести 8 байтов"):

- D FFFF:5 L8 [Enter]

  покажет вам системную дату в правом столбце дампа.

  Короче, искателям приключений выдаем "простыню" самых интересных адресов (большинство слов в описании вам пока должны быть непонятны, но вы не пугайтесь - понимание придет!).

  • 0:417 - два байта разрядов состояния клавиатуры. Они активно используются ROM-BIOS для управления интерпретаций действий клавиатуры. Изменение этих байтов изменяет значение нажатых клавиш (например, верхний или нижний регистр).
  • 0:41A - cлово по этому адресу указывает на начало буфера BIOS для ввода с клавиатуры, расположенного начиная с адреса 41E. В этом буфере хранятся и ждут обработки результаты нажатия на клавиши. Конец буфера - слово по адресу 41C.
  • 0:43E - байт указывает, необходима ли проверка дискеты перед подводом головки на дорожку. Разряды 0...3 соответствуют дисководам 0...3. Если разряд установлен в 0, то необходима проверка дискеты. Как правило, вы можете обнаружить, что разряд установлен в 0, если при предыдущем обращении к дисководу имели место какие-либо проблемы. Например, разряд проверки будет равен 0, если вы попытаетесь запросить каталог на дисководе, на котором нет дискеты, и затем на запрос, появившийся на экране дисплея: "Not ready reading drive B: Abort, Retry, Ignore?" вы ответите: "A".
  • 0:44C (2 байта) - длина регенерации экрана. Это число байтов, используемых для страницы экрана. Зависит от режима.
  • 0:44E (2 байта) - смещение для адреса начала текущей страницы в памяти дисплея. Этот адрес указывает, какая страница в данный момент используется (маленькая, но неприятная подробность - это смещение внутри текущего сегмента видеопамяти, без учета самого сегмента. Например, для нулевой страницы смещение всегда будет равно нулю.)
  • 0:460 (2 байта) - размер курсора, представленный в виде диапазона строк развертки. Первый байт задает конечную, а второй - начальную строку развертки.
  • 0:449 - значение этого байта определяет текущий видеорежим. Для расшифровки требуется целая таблица. Например, 3 - 80-колонный текст, 16 цветов; 13h (19) - 256-цветный графический режим 320x200 и т. д.

  Ну и хватит для первого раза. Кому мало - ищите дополнительную документацию :-p "

1.4. Программа

  #1. Любая программа выполняется последовательно (мы ведь пока обсуждаем "простой" IBM PC, а не какой-нибудь крутой векторный параллельный суперкомпьютер). То есть пока не выполнилась текущая "строка" (инструкция) программы, следующая не выполнится. Совсем другой вопрос, какая "строка" будет выполнена после "текущей" (здесь мы имеем дело со всевозможными логическими "ветвлениями", "циклами" и т. д.), или же строчку из какой программы процессор выполнит следующей, а какая - будет ждать своей очереди (так называемая "многозадачность", которую пока трогать не будем - в большинстве случаев мы можем прекрасно прожить и без нее, поскольку все заботы об этом все равно берут на себя операционные системы).

  Итак, у нас есть оперативная память, в которую загружается программа перед ее выполнением (сразу же по нажатию на Enter из Norton Commander). Операционная система, которая, собственно, и загружает программу, сообщает процессору, что надо начать обрабатывать команды, которые в памяти начинаются с такого-то адреса. И здесь первый подводный камень, вернее скала, которую трудно не заметить.

  Начало программы в памяти процессор различает легко - ему указывает на это командный интерпретатор, а вот конец программы программист должен указывать сам!

  Каким образом? А очень легко! Компьютер "распознает" как выход из программы специальную последовательность байтов. Например, для исполнимых файлов типа com (именно с этим типом файлов мы будем работать на начальном этапе) достаточно последовательности CD и 20.

  Пробуем-проверяем? Ну конечно же! Только для этого вам понадобится какой-нибудь шестнадцатеричный редактор, например, HexWorkshop.

  Все очень просто - создаем новый файл, единственным содержимым которого является последовательность CD 20, и сохраняем его как, например, myprg_1.com. Если вы позаботились о том, чтобы после CD 20 не было никаких прочих символов, то исполнимая программа будет "весить" только 2 байта.

  Запускать это ваше первое творение лучше из Norton или Volcov Commander (все же это пока что DOS'овская программулька).

  Что же она делает, эта 2-байтовая малышка? А ничего, просто этот файл обладает двумя важными свойствами:

  • это - программа;
  • эта программа - программа с корректным выходом.

  Последнее и является единственным, что она пока что может делать (корректно выгружаться из памяти)...

  Еще к вопросу о выгружаемости - если после CD 20 вы напишите еще что-нибудь (чепуху), она все равно будет проигнорирована. Дело до ее выполнения просто-напросто не дойдет. Другое дело - если вы напишите чепуху до...

  #2. Честно говоря, опасно при низкоуровневом программировании чепуху писать. Можно невзначай и винт отформатировать :))). Поэтому лабуду писать не будем, вернее - будем, но не лабуду...

  Итак, продолжим наше извращение. Познакомимся еще с некоторыми "машинными командами" (в нашем случае - последовательностями шестнадцатеричных циферек).

  • B82301 - внести значение 0123h в AX;
  • 052500 - прибавить значение 0025h к AX;
  • 8BD8 - переслать содержимое AX в BX;
  • 03D8 - прибавить содержимое AX к BX;
  • 8BCB - переслать содержимое BX в CX;
  • 31C0 - очистка AX;
  • CD20 - конец программы. Передача управления операционной системе.

  Вот и давайте создадим еще одну программу типа com со следующим "шестнадцатеричным содержимым":

B8-23-01-05-25-00-8B-D8-03-D8-8B-CB-31-C0-CD-20

  Если вы все ввели правильно, то прога у вас без проблем запустится, а операционная система не будет ругаться... Правда, визуально (в смысле на мониторе) вы ее работу так и не заметите, но поверьте на слово - она работает! В этом вы еще убедитесь, когда посмотрите на ее работу изнутри - не различающими цветов глазами компьютера... Только сначала еще немного теории...

  #3. Теперь поговорим о втором подводном камне :). Один из принципов фон Неймана звучит приблизительно так: машине безразлично целевое назначение данных... Одна и та же цепочка битов может быть и машинными командами, и данными (например, символами, выраженными в виде кодов - есть такая "таблица символов ASCII", наверняка вы ее знаете).

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

  На высоком уровне это делает операционная система. Например, она не пытается загрузить в память для выполнения файлы с расширениями, отличными от COM, EXE и BAT (последний вообще не из этой оперы, но принцип сохраняется).

  Хотя..., вы всегда можете поэкспериментировать! Смените, например, у какого-нибудь текстового файла тип с TXT на COM и попробуйте его запустить на выполнение (хотя мы это делать настоятельно не рекомендуем!). В большинстве случаев ваш компьютер зависнет! Потому что:

  1. Он пытается интерпретировать данные как код. Соответственно, в процессор "попадает" всякая ерунда.
  2. Вряд ли он натолкнется на последовательность CD 20 вашем тексте :). Даже в том случае, если этот код выполнится "успешно" - ваша программа не возвратит управление операционной системе, а пойдет выполняться хлам, содержащийся в оперативной памяти. Как-то - остатки ранее выполненных программ, куски чьих-то данных, интерпретированные как код... и прочая многочисленная ерунда...

  Почти такой же эффект, но с потенциально большей разрушительной силой может получиться, если управление получит ИСПОРЧЕННЫЙ код, который вроде бы "в основном" правильный, но часть его вместо инициализации переменных и прочих подготовительных действий в лучшем случае ничего не делает, а в худшем портит другой код и данные...

  Как вам тяжело "въехать" в смысл повествования, состоящего из кусков различных книг, так и компьютеру тяжело понять подобную "мешанину". С той лишь разницей, что любую "неинтересную книгу" вы можете использовать в качестве туалетной бумаги, а вот "компутер" подобного права выбора лишен - он должен в это "въезжать", его процессор начинает перегреваться, а мозги кипят и вытекают через низкоуровневые порты ввода-вывода (командами IN и OUT соответственно).

  #4. Еще немного идеологии. О программе, которая выполняется в памяти...

  Сколько бы ни было "мозгов" в вашей навороченной тачке, любая программа выполняется в 640 килобайтах "нижней" (или основной) памяти. Если отнять от этой цифры "резидентную часть" операционной системы, многочисленные драйвера и т.д., то оставшееся и есть объем памяти, в котором выполняется ваша программа. А остальные мегабайты - это место для кэширования диска, хранения промежуточных данных и т.п.

  Страшно? Медитируйте!

  #5. Как уже говорилось в #3, одна и та же последовательность битов в памяти может быть:

  • кодом (т.е. что компьютеру нужно делать) - последовательностью инструкций;
  • данными (т.е. с чем компьютеру нужно выполнять ту или иную работу). Именно данные являются исходной "задачей" и конечным результатом работы процессора.
  • стеком - это область памяти, позволяющая писать реентерабельный/рекурсивный код и служащая для хранения адресов возврата и локальных данных и передачи параметров.

  Соответственно, и программа состоит из трех частей (сегментов): сегмента данных (data), сегмента кода (code) и сегмента стека (stack)...

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

  #6. Помните, как в конце фильма "Matrix" Нео в конце концов увидел ее - черно-зеленую "матрицу"? Сейчас с вами произойдет нечто подобное! ;)

  Посмотрите на машинные коды, и "что они делают" в #2. Немножко дополним эту "простыню". Например, командой "внести значение" 1234 последовательно в каждый из "регистров общего пользования":

B83412 - AX=1234
BB3412 - BX=1234
B93412 - CX=1234
BA3412 - DX=1234

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

  Однако никто не пишет программы в шестнадцатеричных редакторах! Никто! Это большая глупость! Единственное, зачем мы вам про это рассказываем - это чтобы вы поняли, что могут означать загадочные пары шестнадцатеричных цифр в дампе...

  Нет необходимости заучивать, что B8 - это "переместить в регистр AX", BB - "переместить в регистр BX" и так далее... Когда-нибудь это может пригодиться тому, кто будет писать компилятор, умеющий генерировать исполняемый код, упаковщики исполняемых файлов, самомодифицирующийся код или, на худой конец, конструкторы полиморфных самошифрующихся вирусов. Но это мы оставим на будущее...

  Все намного проще!

  В этом вы можете убедиться, загрузив вашу программу myprg_1.com в debug (например, командной строкой

debug myprg_1.com

  и введя команду "u".

  А вот дальше начинается самое интересное :)))

  #7. Вот что вы должны увидеть:

11B7:0100 B82301 MOV AX,0123 ; Внести значение 0123h в AX
11B7:0103 052500 ADD AX,0025 ; Прибавить значение 0025h к AX
11B7:0106 8BD8 MOV BX,AX ; Переслать содержимое AX в BX
11B7:0108 03D8 ADD BX,AX ; Прибавить содержимое AX к BX
11B7:010A 8BCB MOV CX,BX ; Переслать содержимое BX в CX
11B7:010C 31C0 XOR AX,AX ; Очистка AX
11B7:010E CD20 INT 20 ; Конец программы

  Возвратившись к #2, перенесем сюда "описание" машинных команд.

  Эти mov, add, xor, int - так называемые "мнемонические команды" (более или менее понятные человеку), на основе которых формируется (это debug делает) "машинный код". Не правда ли, так намного легче?

  Соответственно, вместо шестнадцатеричных кодов мы легко могли вводить эти команды при помощи команды "A" (однако этим мы займемся позже).

Категория: Программирование на Ассемблере | Добавил: MalCer (30.07.2009)
Просмотров: 1440 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Реклама
Кабинет

Читать ЛС ()

Гость, мы рады вас видеть. Пожалуйста зарегистрируйтесь или авторизуйтесь!



Фраза дня:
Кто с нами

Сегодня были:
Статистика
Партнеры
Graffiti Decorations(R) Studio (TM) Site Promoter Mnogo-softa.net.ru-Софт,срипты,шаблоны и др. MEGA-ToP-ТОП раскрутка раскрутка сайтов. Рейтинг лучших сайтов
описание