Воробьёвы

(-:

Вы нас неоднократно спрашивали:
- КОГДА "НАЧНЕТСЯ" АСМ ПОД WINDOWS?
За что боролись, на то и напоролись...
- АСМ ПОД WINDOWS УЖЕ НАЧАЛСЯ!!
Вы нас неоднократно спрашивали:
- Когда "НАЧНЕТСЯ" SOFTICE?
За что боролись, на то и напоролись...
- SOFTICE УЖЕ НАЧАЛСЯ!!
- ГДЕ? КОГДА?
- СЕГОДНЯ!!!
- A-A-A-A-A-A-A-A-A-A-A-A-A-A-A-A!!!

10 июля 2001 года на Горкоте появилась новая рассылка © HI-TECH с хорошим дZенским названием "Программирование на Ассемблере под Win32"!! Спешите подписаться - скоро будет Самый Первый Выпуск! Программировать мы будем на MASM32, а копаться во внутренностях - SoftIce'ом :) Крепите дух, братья и сестры! Вас ожидают страшные вещи...

"Говорят, что ассемблер мертв. Но нет ничего более далекого от истины, чем это утверждение. Во-первых, потому, что он не может умереть, пока существуют компьютеры. Ведь ассемблер является "родным" языком микропроцессора. Во-вторых, потому, что еще не перевелись люди, которые могут подписаться под словами Б. Пастернака "Во всем мне хочется дойти до самой сути". Такие люди, например, из любопытства разбирают будильники до последнего винтика. Или программируют на ассемблере. Многие считают, что ассемблер чрезвычайно трудный язык - для написания, для чтения и для отладки. Я же считаю, что ассемблер прост во всех отношениях. Кроме того, ассемблер - это красивый и элегантный язык, хотя на первый взгляд он состоит из нечитабельных слов-уродов: jz, movsz, lodsw, xchg, cdq и т.д. Уверяю вас, ассемблер стоит того, чтобы его по-настоящему полюбить. Я постараюсь писать достаточно подробно и понятно, но желательно, чтобы вы уже знали какой-нибудь язык программирования высокого уровня: C/C++, Pascal, Perl, Visual Basic. Рассылка будет выходить раз в две недели. По крайней мере, постараюсь придерживаться этого графика."

Всемирная История, XXI век, © vkim, © команда HI-TECH

TURBO DEBUGGER

Первая любовь, первый поцелуй, первый аборт...

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

Развивать я эту мысль не буду, ладно? Как там в песне поется: "все пройдет, как с белых яблонь дым". Короче: вот вам 22-й выпуск нашей рассылки, читайте и самосовершенствуйтесь...

Итак, как и было обещано, сегодня мы попрощаемся с морально устаревшим отладчиком dzebug. Я уверен - вы уже успели его полюбить всеми фибрами своей программерской души :). Так вот: пора прощаться, dZebug мертв, да здравствует Turbo DZebugger!

- Где ж взять энтот Turbo Debugger?" - спросите вы.
- А там же, где и TASM с TLINK-ом взяли! В пакете TASM 5.0. Файл называется TD.EXE. А продукт - Turbo Debugger v5.0. На всякий случай вот вам с сылка вместе со ссылкой на соответствующий подраздел нашего сайта.

Скачали? Тогда поехали дальше...

После трудностей общения с dzebug'ом, вы без труда разберетесь с Turbo Debugger'om. Они очень похожи, только если в первом мы использовали буковки-команды, то второй имеет более дружелюбный интерфейс: всякие там выпадающие менюшки, полосы прокрутки, окошки и пр. муть.

В качестве "первой брачной ночи" попробуем поиметь под отладчиком программу которая "печатает десятичные циферки" (мы ее в прошлом номере писали).

Вот ее "главная процедура":

    testing proc
      mov  dx,12345d
      call write_decimal
      call exit_com
    testing endp

Естественно: ассемблируем, линкуем... а потом получившийся com-файл загружаем в отладчик одним из двух способов (тот, что первый - более дZенский):

Способ первый: командной строкой TD <вашапрога.com> [Enter]

Способ второй: запускаем TD, лезем в меню File > Open, пишем имя файла, и жмем на [ОК] (флажок "Execute startup code" нам пока пофиг).

В красивом таком окошечке наш TD предупредит: "Program has no symbol table!". Что это за table такой, то ли стол, то ли таблица, мы пока не знаем, а по сему жмем на [OK] и с ликованием смотрим, чего это у нас красивого получилось :).

А получилось у нас то же самое, что и в ответ на команду "U" в dZebuge :)

В самом большой части окошка мы видим мнемокод. Перед адресом каждой строчки стоит CS - это чтоб вы не забыли, что имеете дело с КОДОМ :).

Самая первая строчка помечена "треугольником вправо". Именно с этой инструкции (адрес 0100, hex-код BA3930, мнемоника dx,3039) начнется выполнение нашей проги.

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

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

А еще обращаю ваше внимание вот на что (так сказать, повторение пройденного):

1. COM-программа выполняется начиная со смещения 100.
2. Строчки ВЫШЕ строки, помеченной треугольником, ПРЯМОГО отношения к нашей программе не имеют (потому что в программе эти строки изначально не "закладывались"); зато имеют КОСВЕННОЕ: по адресам CS:0-100 лежит PSP, имеющий непосредственное отношение к программе (что такое PSP, вы пока еще не знаете, с ним мы попозже разберемся.)
3. Конец нашей программы ничем не помечен ;).

Так, посмотрим дальше чего там у нас. Ниже "окошка с кодом" в TD располагается "окошко с данными", каждая строчка которого помечена как DS. На него мы пока что тоже не будем обращать внимания :). Точно так, как и на окошко в правом нижнем углу - с регистрами стека мы еще близко не знакомились.

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

Ну что? Начнем "обращать внимание"? Программу в отладчик загрузили?

Видите первую строчку - mov DX,3039. А теперь посмотрим на наш исходник. Там у нас mov DX,12345d. Ненавязчиво напоминаю, что 3039h=12345d.

Жмем на F7 и эта строчка у нас выполняется...

Смотрим на значения регисров в столбце справа. DX=3039, да еще и другим цветом выделено - ИЗМЕНИЛСЯ, значит. А еще регистр IP стал равен 103 :).

Для тех, кто забыл что это за регистр - вернитесь и перечитайте номер 14, п.7.

Следующая команда call 019E. Жмем на F7. Прыжок на соотвествующую метку. На регистр SP пока что можно не обращать внимания...

Народ! Если вы думате, что мы и дальше трассировать программу за вас продолжим, то вы глубоко ошибаетесь! Трассировать вы уже в дZebuge должны били научиться! Здесь - тоже самое, только вместо команды T[race] можно просто жать на F7.

Трассируйте, как вы уже неоднократно это делали!

Выше мы вам неоднократно говорили: "не обращайте внимание на то", "не обращайте внимания на это"... А теперь говорим: при трассировке ОБРАТИТЕ ВНИМАНИЕ на то, как ИЗМЕНЯЮТСЯ ФЛАГИ!

Почему именно на это? Да потому, что сейчас мы познакомимся с ними поближе!

КРУТАЯ РАЗБОРКА С ФЛАГАМИ

Машеньке Луневой,
умнице и программистке,
посвящается ;;;)

[1] В хорошем справочнике по инструкциям процессора часто можно встретить загадочную табличку "состояние флагов после выполнения команды".

Например под хорошо нам известной командой ADD распологается вот такая табличка:

    OF SF ZF AF PF CF
    r  r  r  r  r  r

Под командой DIV:

    OF SF ZF AF PF CF
    ?  ?  ?  ?  ?  ?

А вот совсем неизвестная нам команда DAA, которая выполняет загадочную "десятичную коррекцию после сложения" имеет целых две таблички:

    OF SF ZF AF PF CF   и   OF SF ZF AF PF CF
    r  r  r  1  r  1        r  r  r  0  r  0

(левая - это если были переносы, правая - если не было).

- Что за фигня такая! - скажете вы. - Ничерта не понятно!
- Сами едва расколупали! - ответим мы. - А теперь вас грузить будем!

Для начала - расшифруем эти непонятные таблички. В первой строчке - "имена собственные" флагов, с которыми "взаимодействует" данная команда.

Во второй строчке - результат выполнения команды. Расшифровываем:

1 - после выполнения команды флаг устанавливается.
0 - после выполнения команды флаг устанавливается.
r - значение флага зависит от результата работы команды.
? - после выполнения команды флаг неопределен.

Дело в том, что инструкции могут по разному обращаться с флагами. Флаги могут игнорироваться (не меняться), устанавливаться, сбрасываться, задаваться в соответствие с результатом, либо ПОЛУЧАТЬ НЕОПРЕДЕЛЁННОЕ ЗНАЧЕНИЕ (т.е. то или иное значение флага после инструкции никем не гарантируется)...

Медитируйте!

[2] И сказалъ Хемуль Советикус:

- ... глаголю истинно: да не получите вы программу галимую, коль точное влияние инструкций на флаги знать будете! Ибо инструкция MOV ни на один из флагов влияния не оказывает, инструкции INC и DEC флаг переноса не трогают, а инструкция умножения оставляет флаг нуля в неопределённом состоянии... В общем, RTFM, RTFM и еще раз RTFM!

И сказал VKim:

- Пофиг мне все эти флаги, потому что творю я на масме, который позволяет приводить слова и двойные слова к знаковому или беззнаковому типу с помощью директив sword и sdword. И кроме того, я не выпендриваюсь, а пользуюсь высокоуровневыми конструкциями условных переходов: .if, .else, .endif. Например:

     .if sdword eax == -1
        ;some code
     .else
       ;another code
     .endif

или

     .if zero?
        ;some code
     .endif

И сказала Маша Лунева:

- Екарный бабай, нихрена я в эти флаги врубиться не могу! Наверное, не дождусь 22-го выпуска рассылки, где это популярно расжевано и в рот покладено будет... Уйду в монастырь! В мужской!! В дZенский!!! Просветляться, просветляться и еще раз просветляться, чтоб вам всем пусто было!!!!

И сказал Serrgio:

- Вот блин!

И заточил напитка дZенского - чифиря термоядерного...

И погрузился в медитацию трансцедентальную, летальным исходом e2 не закончившуюся...

И было ему видение: тьма тварей с глазами как окнами, вокруг геена огненная; стоят количеством 666 у алтаря каменного и кровью невинно убиенных младенцев выводят тайные символы каббалистические:

    ;------------------------------------------------
    ;           +++  PF SF AF CF ZF OF  +++
    ;------------------------------------------------
     .model tiny
     .code
    org 100h

    start:

    ;------------------------------------------------
    ;           +++   PF DEMO BLOCK   +++
    ;------------------------------------------------
    mov AL,0
    add AL,1         ;(1) AL=01h=00000001b, PF=0
    add AL,1         ;(2) AL=02h=00000010b, PF=0
    add AL,1         ;(3) AL=03h=00000011b, PF=1
    add AL,1         ;(4) AL=04h=00000100b, PF=0
    add AL,1         ;(5) AL=05h=00000101b, PF=1
    add AL,1         ;(6) AL=06h=00000110b, PF=1
    add AL,1         ;(7) AL=07h=00000111b, PF=0
    nop

    ;------------------------------------------------
    ;           +++   SF DEMO BLOCK   +++
    ;------------------------------------------------
    mov AL,01111110b
    add AL,1         ;(8) AL=7Fh=01111111b SF=0
    add AL,1         ;(9) AL=80h=10000000b SF=1
    add AL,1         ;(10) AL=81h=10000001b SF=1
    nop

    ;------------------------------------------------
    ;           +++   AF DEMO BLOCK   +++
    ;------------------------------------------------
    mov AL,1111b     ;(11) AL=0Fh=00001111b AF=0
    add AL,1         ;(12) AL=10h=00010000b AF=1
    add AL,1         ;(13) AL=11h=00010001b AF=0
    nop

    ;------------------------------------------------
    ;           +++   CF DEMO BLOCK   +++
    ;------------------------------------------------
    mov AL,11111110b ;(14) AL=FEh=11111110b CF=0
    add AL,1         ;(15) AL=FFh=11111111b CF=0
    add AL,1         ;(16) AL=00h=00000000b CF=1
    add AL,1         ;(17) AL=01h=00000001b CF=0
    add AL,1         ;(18) AL=02h=00000010b CF=0
    nop

    ;------------------------------------------------
    ;           +++   ZF DEMO BLOCK   +++
    ;------------------------------------------------
    sub AL,10b       ;(19) AL=0 ZF=1
    nop

    ;------------------------------------------------
    ;           +++   OF DEMO BLOCK   +++
    ;------------------------------------------------
    mov AX,7FFFh     ;(20)
    add AX,1         ;(21) AX=8000h=-32768, OF=1
    add AX,1         ;(22) AX=8001h=-32767, OF=0
    nop

    int 20h

    end start

    ;------------------------------------------------
    ;              +   A M E N O   +
    ;------------------------------------------------

- ЧТО ЭТО ЗА БРЕД?! - возмутитесь вы. - БЕССМЫСЛЕННОЕ НАГРОМОЖДЕНИЕ ИЗ ПРОСТЕЙШИХ КОМАНД!!
- Угу, - скажем мы. - "Стереокартинки" на первый взгляд тоже бессмысленное нагромождение текстур - пока вы не научитесь смотреть СКВОЗЬ них. И только тогда появляется НАСТОЯЩАЯ картина...

[3] Флаг паритета PF равен:

1 - если 8 МЛАДШИХ разрядов результата соддержат четное число единиц.
0 - если 8 МЛАДШИХ разрядов результата соддержат НЕ-четное число единиц.

"8 МЛАДШИХ" - потому что этот флаг только для 8 младших разрядов операнда любого размера.

Смотрим на PF и "считаем число единичек"! Трассируем! Помните, мы говорили о том, что делать перевод чисел HEX<->BIN вы должны уметь делать в уме и быстро? Не научились? Не вызубрили наизусть "таблицу соответствия"? Теперь трахайтесь с виндозовским калькулятором!!

Смотрим на наш кабаллистический сырец!

Бряк 1. В регистре AL только 1 "ВКЛЮЧЕННЫЙ" БИТ. 1 - число НЕ ЧЕТНОЕ => PF=0.
Бряк 2. В регистре AL только 1 "ВКЛЮЧЕННЫЙ" БИТ. 1 - число НЕ ЧЕТНОЕ => PF=0.
Бряк 3. В регистре AL 2 "ВКЛЮЧЕННЫХ" БИТА. 2 - число ЧЕТНОЕ => PF=1.
Бряк 4. В регистре AL только 1 "ВКЛЮЧЕННЫЙ" БИТ. 1 - число НЕ ЧЕТНОЕ => PF=0.
Бряк 5. В регистре AL 2 "ВКЛЮЧЕННЫХ" БИТА. 2 - число ЧЕТНОЕ => PF=1.
Бряк 6. В регистре AL 2 "ВКЛЮЧЕННЫХ" БИТА. 2 - число ЧЕТНОЕ => PF=1.
Бряк 7. В регистре AL 3 "ВКЛЮЧЕННЫХ" БИТА. 3 - число НЕ ЧЕТНОЕ => PF=0.

Да не введет вас в заблуждение то, что мы говорим о "включенных" битах регистра! Если бы мы использовали, например, не AL, а AX, то говорили бы не про AX, а про младшие 8 бит регистра AX.

Для тех, то не понял:

    mov AX,100000000b
    add AX,1

После выполнения второй команды флаг PF установлен НЕ БУДЕТ!

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

    test    al,00001010b ;биты 1 и 3
    jpe     same         ;перейти, если оба бита имеют одинаковое
                         ;значение (оба ноль или оба единицы)
    xor     al,00001010b ;инвертировать соответствующие биты
    same:

Всем медитировать! И продвинутым, и чайникам!

[4] Флаг знака SF равен:

1 - если старший разряд результата равен 1.
0 - если старший разряд результата равен 0.

Смотрим на SF и смотрим на "самый левый" (© Маша Лунева), т. е. _старший_ бит.

На бряке 8 значением старшего бита был 0, следовательно SF не устанавливался. На бряке 9 мы добавили единицу и СТАРШИЙ БИТ СТАЛ РАВЕН ЕДИНИЦЕ! Следовательно и SF=1. Добавили еще единицу (бряк 10). И что? А ничего! Старший бит по-прежнему равен единице, а флаг SF по-прежнему установлен :)

Для наиболее продвинутых напоминаем, что отрицательные числа в большинстве современных процессоров кодируются в дополнительном коде и для отрицательных чисел установлен старший разряд. Соответственно, флаг SF показывает, является ли результат знаковым. Отсюда и название.

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

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

    mov AL,-2 ;al=-2=0FEh=11111110b
    add AL,1  ;al=-1=0FFh=11111111b SF=1
    add AL,1  ;al=0=00000000b SF=0
    add AL,1  ;al=1=00000001b SF=0

Тех, кого не устраивает пример с командой ADD, вот вам вторая альтернатива:

    mov AX,1
    sub AX,1 ;AX=0, SF=0
    sub AX,1 ;AX=FFFF, SF=1
    sub AX,1 ;AX=FFFE, SF=1

Все удовлетворены? Тогда медитируем!

[5] Вспомогательный флаг переноса AF равен:

1 - если в результате сложения был произведен перенос из третьего разряда в четвертый (или в результате вычитания был произведен заем в третий разряд младшей тетрады из значения в старшей тетраде).
0 - если переносов/заемов в/из 3 разряд(а) не было.

Смотрим на AF и на третий бит...

(Блин, ну почему вы постоянно об нумерацию битов спотыкаетесь?? Сколько можно??!

В следующем "байте":

    1111X111

НА МЕСТЕ 3-го бита поставлен КРЕСТИК!

То есть справа он - четвертый!

Слева - пятый.

Но НОМЕР ЕГО - ТРЕТИЙ!! ПОТОМУ ЧТО БАЙТЫ НУМИРУЮТСЯ СПРАВА НАЛЕВО И НАЧИНАЯ С НУЛЯ!)

На бряке 11 мы внесли в регистр "пограничное" число.
На бряке 12 к этому числу добавляется единица, что увеличивает разряд этого числа. В одну тетраду число "не лезет", соответственно осуществляется перенос единички из старшего разряда младшей тетрады в младший разряд старшей тетрады. Иными словами: единичка из третьего разряда "перескакивает" в четвертый и из-за этого устанавливается флаг AF, в простонародье называемый "флагом полупереноса".
Бряк 13... А что бряк 13? Еще единичку добавляем. Перенос из третьего разряда осуществился? Нет! Смотрим на AF. AF=0.

Вопросы есть? Со всеми вопросами - сюда!

Самостоятельно:

1. Посмотрите под отладчиком как устанавливается флаг полупереноса на вычитании.
2. Сравните поведение "флага полупереноса" с "флагом переноса".
3. Не вздумайте замыливать нам результаты выполнения этого домашнего задания.

Кому этот флаг нужен? Да командам, работающим с BCD-числами.

Что такое BCD-числа? А хрен его знает. Как-нибудь спрошу об этом в RTFM_Helpers 8-)

Медитируем!!

[6] Флаг переноса CF равен:

1 - если операция произвела перенос из старшего бита результата (7-го, 15-го или 31-го, в зависимости от размерности операнда).
0 - если нет переноса старшего бита.

Смотрим на СF и на старший бит.

Что подразумевается под "переносом старшего бита"? А вот что (внимательно смотрим на бряк 15).

Размерность регистра AX, как известно 16.

На бряке 15 у нас в регистр заносится вот такая "последовательность" бит: 1111'1111'1111'1111. Верно? А строчкой ниже у нас к этому числу еще и единица прибавляется, т. е. 1111'1111'1111'1111 + 1 = 1'0000'0000'0000'0000. Таким образом появляется еще и 17-й разряд, который не влезает в 16-разрядный AX. В результате вот что получается: в регистре AX остаются 16 младших битов, а 17-й "лишний" (который не влезает в регистр) - отправляется в "флаг переноса". Результат: AX = 0000'0000'0000'0000 и установленый флаг CF.

Самостоятельно посмотрите под отладчиком, как работают следующие три команды, имеющие непосредственное отношение к флагу переноса:

CLC (CLear Carry flag) - Сброс флага переноса.
STC (Set Carry Flag) - Установка флага переноса.
CMC (CoMplement Carry flag) - Инвертирование флага переноса.

Медитируйте...

[7] Флаг нуля ZF равен:

0 - если результат арифметической операции не равен 0.
1 - если результат арифметической операции равен 0.

На бряке 18 мы "поместили" в регистр AL число 2 (оно же - 00000010b). Если от двух отнять 2, получится сколько? Правильно - 0 ;). А еще - флаг ZF установится :) - потому что ноль получися (бряк 19).

А вот и еще один примерчик:

     mov  BX,10
    LOOP:
     call FUNC
     dec  BX   ;BX станет равным 0 (ZF=1) после 10-й итерации
     jnz  LOOP

Кто не знает, что такое итерация - замените ее словом "повторение".

И помедитируйте часок!

[8] Флаг переполнения OF равен:

1 - если произошел перенос(заем) из(в) старшего знакового разряда, а перенос(заем) между старшим и предыдущим НЕ призошел; или наоборот.
0 - во всех остальных ;) случаях.

А вот и страшные и непонятные подробности:

Флаг переполнения OF равен 1 если при выполнении АРИФМЕТИЧЕСКИХ инструкций над ЧИСЛАМИ СО ЗНАКОМ происходит переполнение разрядной сетки.

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

    OF=C[n] xor C[n-1]

где C[n] - перенос/заём из/в старшего разряда (т.е. то же самое, что и флаг CF), а C[n-1] - перенос/заём из/в предыдущего разряда.

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

Медитировать здесь опасно!

Но все равно - медитируем!

[9] А теперь проведем границы:

1. Флаги мы рассматривали с точки зрения ИЗ-ЗА ЧЕГО они устанавливаются, то есть какая СИТУАЦИЯ приводит к УСТАНОВКЕ того или иного флага. Вопрос ДЛЯ ЧЕГО они устанавливаются (т. е. ЗАЧЕМ нужны) мы пока что не рассматиривали, хотя и затрагивали кое-где вскользь.
2. Очень важно: результат установки того или иного флага - это прежде всего результат выполнения КОМАНДЫ, и только во вторую очередь - результат установления того или иного "сочетания" битов регистра.

Другими словами: значение, которое получает флаг после выполнения инструкции, диктуется правилами, по которым работает инструкция - и не всегда эти правила прямо зависит от сочетания значений битов результата.

За сим флагов хватит (флаг направления мы в прошлом номере рассматривали). Будем считать, что более-менее познакомились. Остальные регистры либо нужны только операционной системе или подобным специализированным программам (например, RF - флаг маскирования ошибок отладки), либо будут рассматриваться в соответствующий момент, когда они понадобятся (например, IF - флаг разрешения прерывания). Большинству же программ вполне достаточно тех флагов, которые уже перечислены.

И да пребудет с вами сила, DZ братья и сестры!