Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Низкоуровневое программирование для дZeнствующих #20


Служба Рассылок Subscribe.Ru проекта Citycat.Ru
< /tr>
c а й т   и з о б р е т а т е  л е й   ф о н а р и к о в   н а    с о л н е ч н ы х   б а т а р е й к а х
  - Колонка вредактора
  - Печать шестнадцатеричных циферек :)
    [1], [2], [3], [4], [5], [6], [7], [8]
  - Примечание by DZ^2 Хемуль Советикус (для особо продвинутых)
  - Обзор дZенских сайтов - ZEN.RU
  - Анонс
"Народ, я чего-то новой рассылки не узрел!
Где же периодичность, блин, а?
Только в мозгах проясняться началось и на тебе :{" (C) Baltika
  Хм... получил я вчера почту, посмотрел... и точно - нема там новой рассылки. Абидна, да?
  Мне тож обидно. Ну никто не хочет эту гребаную рассылку делать!! Материала - куча, но вот чтобы подорваться и оформить эту дрянь более-менее красиво и связно...
  Ну да ладно, не буду кряхтеть. Народ там продуктивно взломщик rar-архивов под линуксовый кластер ваяет :). Так увлеклись, что даже за пивом некому в магазин сбегать... (Кофий пьют, придурки! Не понимают, что это почек у человека две штуки, а сердце - оно только одно!).
  Да черт с ними, в общем! Если зарелизим через месяц-другой первый в мире линуксовый/кластерный брутфорсный кракер rar-архивов - сам же потом балтикой и проставляться буду :).
  Ладно... проехали...

  Новостей у нас 10b. Одна хорошая, одна плохая.
  Та, которая хорошая, состоит в том, что мы начали редизайн нашего сайта делать :). Зацените главную страничку :) и логотипчик а-ля Микеланджело :). "Рука бога и рука Адама" :) Ааааа? Лично мне нравится :). Тем более, что HTML-код ручками писался :). Безо всяких визивингов :).
  18 килобайт эта страничка весит :)). А прошлый index.html - 35 :). Мне теперь на hi-tech.ph.ru ходить страшно! Зато как приятно на hi-tech.nsys.by :))). Ооооо...

  А теперь плохая новость: начать-то мы начали, а вот кончить никак не можем :(. На один Архив рассылки (который еще и на сервер-то даже не залит) времени ушло немеряно! (Ужас какой)... Так что нечего пока и на nsys'е делать :(((.
  Для тех кто не понял: МЫ НЕ ПОМЕРЛИ! МЫ ПРОСТО ОКУКЛИЛИСЬ!
  Превращение идет. Метаморфоза то бишь. Были червяком мерзким, а теперь типа бабочкой станем. Еще более мерзкой.
  Да черт с ней, с "мерзостью"! У бабочки вовсе не красивыЯ пятнышки главное. И вовсе не то, что она пыльцу, а не гадость всякую, жрет!   Главное у бабочки - крылья!

  Как говаривал Козьма прутков, "если у тебя есть фонтан - заткни его". Так вот: затыкаю свой и переходим к делу.
  [1] Мы уже неоднократно юзали хорошую мнемоническую (aka ассемблерную) команду ADD :). Напомню, что в результате выполнения команд
mov AX,2
mov BX,3
add AX,BX
  в регистр AX у нас помещалась сумма (AX=AX+BX).

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

  Как мы это сделаем? Вы уже неоднократно слышали, что "в ассемблере" все делается "ручками" :). Сейчас вы лишний раз убедитесь в том (некоторые замрут в ужасе), что это утверждение истинно. Для вывода значения регистра мы вовсе не "познакомимся с новым прерыванием". Даже такая простейшая операция, как "вывод на дисплей значения регистра (переменной)" - это целая процедура. И не одна, как вы скоро в этом убедитесь. Страшно?

  Поехали!!

  [2] Задача: вывести на монитор значение регистра DL.
  Народ! Давайте сразу расставим границы между КОДОМ СИМВОЛА и его НАЧЕРТАНИЕМ.
  Например, у нас F3h в DL. Как мы хотим это вывести? Как символы 'F3' или же как ASCII символ, соответствующий коду F3h? Определяемся. Если в DL у нас F3h - то надо чтоб именно 'F3' у нас на монитор и выводилась. Не 'э перевернутое', не '46 33', а именно 'F3'. Помедитируйте. Уловите разницу между 'э', '46 33' и 'F3'.

  Эту задачу мы немножко упростим :). Для начала напишем процедуру, которая выводит только младшую тетраду регистра DL (цифру "3" в нашем примере). Для этого мы обратимся к процедуре WRITE_CHAR из прошлого номера. Именно она печатает нам на монитор символ, ASCII-код которого находится в DL.
  Но тут загвоздка: в DL-код, а печатается-то символ :). А нам, собственно, именно две циферки шестнадцатеричного кода, как два символа, и нужно напечатать. Ну, или хотя бы младшую циферку этого кода...
  Решается эта задача элементарно :). Главное - это правильно ее сформулировать!
  Вот чего я тут нарисовал:
   ЕСТЬ         НУЖНО
код символ    символ код
-----------  ------------
00h  '?'       '0'   30h
01h  '?'       '1'   31h
02h  '?'       '2'   32h
03h  '?'       '3'   33h
04h  '?'       '4'   34h
05h  '?'       '5'   35h
06h  '?'       '6'   36h
07h  '?'       '7'   37h
08h  '?'       '8'   38h
09h  '?'       '9'   39h
0Ah  '?'       'A'   41h
0Bh  '?'       'B'   42h
OCh  '?'       'C'   43h
ODh  '?'       'D'   44h
OEh  '?'       'E'   45h
0Fh  '?'       'F'   46h
  Только во второй колонке вместо вопросительных знаков должны быть соответствующие всякие символы (посмотрите в ASCII-таблице, какие они на вид страшные!).

  Процедурка наша вот что должна делать:
  Всего-навсего перевести "переконвертировать" тетраду в код соответствующего ей символа...
  Завернуто??
  Если разобраться, то не очень-то и завернуто.
  Смотрите: в DL у нас 03h. Хотим мы эту '3' на монитор вывести. Если вызовем WRITE_CHAR, то у нас символ "сердечко" выплюнется. А надо, чтоб символ '3' вывелся, код которого 33h.
  Соответственно и для остальных смотри по табличке.

  А теперь обратите внимание, насколько "шестнадцатеричная циферка" (тетрада) отличается от ASCII-кода, этой "циферке" соответствующего. Сам скажу: на 30h для цифр от '1' до '9', и на 37h для цифр от 'A' до 'F'. То есть "переконвертацию" мы запросто можем сделать командами add DL,30h (если тетрада в диапазоне 0...9) и add DL,37h (если тетрада в диапазоне A...F).

  Короче, вот код (пропиваю!):
;-[WRITE_HEX_DIGIT, V1]----------------------------------
;Печатает одну шестнадцатеричную цифру (младшую тетраду DL)
;(старшая тетрада должна быть равна 0)
;На входе: DL - цифра
;На выходе: нихрена
;Прерывания: ан нэту
;Процедуры: WRITE_CHAR
;--------------------------------------------------------
WRITE_HEX_DIGIT proc
  push DX
  cmp  DL,0Ah
  jae  HEX_LETTER
  add  DL,30h
  JMP  WRITE_DIGIT
 HEX_LETTER:
  add  DL,37h
 WRITE_DIGIT:
  call WRITE_CHAR
  pop  DX
  ret
WRITE_HEX_DIGIT endp
  Сначала, ессно, изменяемые регистры сохраняем (мы ж их изменяем!). (Ну, и восстанавливаем в конце процедуры (PUSH и POP соответственно)).
  Потом у нас логическое ветвление организовано. Сравниваем значение DL с "общей границей" наших двух диапазонов (команда - CMP, "граница" - 0Ah). Если это значение больше или равно 0Ah, то прыжок на метку HEX_LETTER, прибавление к DL 37h и печать цифры (WRITE_CHAR). Иначе добавляем 30h и безо всяких условий перепрыгиваем на вызов WRITE_CHAR (минуя add DL,37h то бишь).
  Все. Тестируем.

  [3] Про тестирование - базар отдельный. В данном случае мы можем запросто проверить нашу процедуру на абсолютно всех возможных значениях этой тетрады (всего-то ничего 16 вариантов). Но намного правильнее, проанализировав алгоритм, установить своего рода "критические" значения, на которых целесообразно проводить проверку. Плюс, естественно, минимальное и максимальное значения.
TESTING proc
  mov DL,00h
  call WRITE_HEX_DIGIT
  mov DL,01h
  call WRITE_HEX_DIGIT
  mov DL,09h
  call WRITE_HEX_DIGIT
  mov DL,0Ah
  call WRITE_HEX_DIGIT
  mov DL,0fh
  call WRITE_HEX_DIGIT
  call EXIT_COM
TESTING endp
  Если сия "тестовая" (она же - главная) процедура выведет на монитор
019AF
  значит мы с высокой долей вероятности можем быть уверенными, что процедура WRITE_HEX_DIGIT работает правильно на всех значениях младшей тетрады DL.
  Кто не просто скопировал процедуру из буфера обмена, а действительно разобрался с тем, как она работает - сами знают, что значение старшей тетрады нашей процедуре НЕбезразлично. Оно должно быть равным 0.

  [4] Что мы имеем? Процедуру для вывода на дисплей одной шестнадцатеричной циферки - младшей тетрады (в DL). Но нам-то нужно две вывести! Сначала старшую циферку-тетраду, и только потом - младшую!
  Таким образом очередная задача разбивается на две части: печать старшей тетрады DL и печать младшей тетрады DL.
  Первая "подзадача" решается легко: нужно просто старшую тетраду переместить на место младшей и вызывать процедуру (основательно протестированную и 99,9%-но работающую) WRITE_HEX_DIGIT.
  А вторая подзадача - хм... заключается в восстановлении предыдущего (мы ж тетраду перенесли) значения DL и снова - вызове WRITE_HEX_DIGIT.
  (Хе! Вот теперь-то вы уж точно почувствуете всю прелесть "дробления кода на процедуры"!)

  Перенос тетрады мы осуществим при помощи команды SHR, которую в умных книжках обзывают как "логический сдвига разрядов операнда вправо". Объясню.
  Представьте себе деревянную доску, длинной в 8 бутылок пива и шириной в одну. (В принципе, доску эту можно и в два раза длиннее представить, но тогда на ней надо "DX" написать, мы же пока только "DL" напишем). А еще дурня, у которого на лбу SHR написано. Так вот, если этому придурку стукнуть по хребту, то он слева от доски поставит ПУСТУЮ бутылку, а остальные сдвинет на одну позицию вправо, в результате чего самая правая бутылка, ессно, с доски упадет.
  Бутылки, которые сразу стояли, могут быть пустыми или полными, а вот дурень SHR - только пустые ставит. И только слева.
"исходное" 11110011
   SHR     01111001
   SHR     00111100
   SHR     00011110
   SHR     00001111
  Ну тут и ежу все понятно. Четыре раза дурню по хребту надо дать, чтоб старшая тетрада на место младшей переместилась.
  (На самом деле самая правая бутылка перед тем как об землю разбиться, на некоторое время в воздухе зависает, но вы пока этим голову не забивайте).
  Реализовывается этот сдвиг вот как:
mov DL,11110011b
mov CL,4
shr DL,CL
  В DL - наша цепочка битов. (11110011b = F3h, естественно).
  В CL заносим "на сколько позиций" нам нашу цепочку сдвинуть.
  Ну и SHR - это дурень, который сдвигает вправо, а слева нули дописывает.

  [5] Думаете, это все?? Разогнались!! Не все так просто :).
  WRITE_HEX_DIGIT у нас требует, чтобы первой тетрадой были только одни нули. Я заострял на этом ваше внимание.
  При печати первой тетрады это условие соблюдается. SHR слева нули дописывает.
  А вот при печати второй цифры нужно вот что: ничего никуда не сдвигая, обнулить старшую тетраду, а младшую (которая, собственно, и есть "цифра") оставить в покое.
  Решим мы эту команду при помощи логической операции "и" (and по-аглицкому). Кто статьи Хемуля в прошлых выпусках читал, сами вспомнят, что это за "и" такое. А кто не читал, я напомню "таблицу истинности" для данной логической операции:
0 0 1 1
0 1 0 1
-------
0 0 0 1
  А теперь и для особо одаренных:
0 and 0 = 0
0 and 1 = 0
1 and 0 = 0
1 and 1 = 1
  Смотрите, интересно как получается:
  Если мы AND чего-либо (нуля или единички) с 0 делаем, то у нас в результате 0 и только 0 получается.
  А если AND с единичкой - то ЧТО БЫЛО, ТО И ОСТАЕТСЯ.
  (Это и есть потаенный дZенский смысл команды AND)

  Решение нашей проблемы (обнулить старшую тетраду, а младшую оставить без изменений) таким образом сводится к тому, что старшую тетраду нужно "AND 0", а младшую - "AND 1".
  То есть значению DL с 00001111b (оно же - 0Fh) "AND" сделать.

  На ассемблере это вот как выглядеть будет:
and  DL,00001111b
  Естественно, 00001111b = 0Fh

  Аминь!!

  [6] Уфф... Вот что получиться в итоге должно:
;-[WRITE_HEX, V1]----------------------------------------
;Печатает две шестнадцатеричные цифры
;На входе: DL - типа цифры две :))
;На выходе: нихрена
;Прерывания: ан нэту
;Процедуры: WRITE_HEX_DIGIT
;--------------------------------------------------------
WRITE_HEX proc
  push CX
  push DX
  mov  DH,DL
  mov  CL,4
  shr  DL,CL
  call WRITE_HEX_DIGIT
  mov  DL,DH
  and  DL,0Fh
  call WRITE_HEX_DIGIT
  pop  DX
  pop  CX
  ret
WRITE_HEX endp
  Ну че тут объяснять?? Я уже объяснил все!! Единственное, что могу добавить - это про mov DH,DL. Этой командой мы значение регистра копируем перед тем как биты "ему" сдвинуть. А потом статус-кво mov DL,DH восстанавливаем, чтоб и младшую цифру напечатать.
  Все. Тестируем. Вроде должно работать.

  [7] Так сказать "к вопросу о шаблонах мышления"...
  Мы тут доооолго трахались с тетрадами. Вроде успешно.
  Когда при тестировании понимаемости материала мы предложили пяти "подопытным" самостоятельно написать процедуру для вывода на монитор "большого" регистра (DX), они все как один начали сдвигать байты... :(
  Народ!! Это не есть правильно!!
  Не буду разжевывать и в рот класть, "почему".

  Вот первый "правильный" способ (для регистра DX):
call WRITE_HEX
mov DL,DH
call WRITE_HEX
  а вот второй:
call WRITE_HEX
xchg DL,DH
call WRITE_HEX
  xchg DL,DH и xchg DH,DL, кстати, работают абсолютно одинаково. Операнды просто меняются между собой значениями. В качестве одного из операндов может выступать память.

  [8] Ну, и напоследок, - информация к размышлению:

  Команду shr можно использовать для деления целочисленных операндов без знака на степени 2 :)).
mov     cl,4
shr     ax,cl
  Думаете эти "фокусники" который в уме офигенны уравнения считать умеют, шибко умные?? Нифига!! Они просто люди ЗНАЮЩИЕ.
  А делить на десять и вы умеете...
  Над этим я настоятельно рекомендую дооооолго помедитировать.
  А еще над тем, что девушки весьма и весьма любят, когда им фокусы показывают.

  Впрочем, это (вычисления в уме) - тема отдельная. Мы ее тоже когда-нить коснемся :).

MATRIX MUST DIE!!
; In:   AL (value)
; Out:  AX (ASCII string, 2 hex digits)
; Use:  none
; Modf: CL
; Call: none

hexbyte2a proc
  mov  ah,al
  and  al,0Fh
  cmp  al,10
  sbb  al,69h
  das
  xchg ah,al
  mov  cl,4
  shr  al,cl
  cmp  al,10
  sbb  al,69h
  das
  ret
hexbyte2a endp
  В данном случае конвертация отделена от печати, хотя никто не мешает их совместить. Примечание: в результате старшая тетрада размещается в AL - это для того, чтобы потом AX можно было сохранить в память как "строку". Я именно так и делают - сначала подготавливаю в памяти строковые буфера, а потом одним махом вывожу всё.
  Такие дела...
  Не поднимается моя грязная (окровавленная, с человеческими кишками, намотанными на пальцы) рука "обозревать" этот сайт. Поэтому скажу просто:

  Народ! Те, кто понял, в чем заключается программерский дзен - пора делать следующий шаг!!
  Прежде, чем воевать с Матрицей, нужно убить матрицу в себе.
  Прежде чем научиться летать, нужно научиться падать.
  Все падают в первый раз. Лишь немногие решаются на Вторую Попытку...
  www.zen.ru
  Школа по Второй Логике.

  И вытрите ноги!
  В следующем номере - вывод на монитор значений регистров в десятичной и двоичной системах счисления. ЕСЛИ...
Рассылки Subscribe.Ru
Низкоуровневое программирование для дZeнствующих
В помощь Дзенствующему. (ZenRu-экспресс)
Copyright 2001 HI-TECH. По всем вопросам мылить hi-tech@tut.by.


http://subscribe.ru/
E-mail: ask@subscribe.ru

В избранное