"Народ, я чего-то новой рассылки не узрел!
Где же периодичность, блин, а?
Только в мозгах проясняться началось и на тебе :{" (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
Школа по Второй Логике.
И вытрите ноги!
В следующем номере - вывод на монитор значений регистров в десятичной и двоичной системах счисления. ЕСЛИ...