Калашников.ru - Ассемблер? Это просто!.. (Выпуск № 006)
InterReklama Advertising
Здравствуйте, уважаемые любители Ассемблера!
Выпуск N 006
I like to move it, move it
I like to move it, move it
I like to... move it!
Напел я старенькую песню и
загрузил в AX число 20h
Сегодня в нашей рассылке:
Ваши письма;
Результаты голосования;
Стек/стэк (stack);
Программка для практики.
Ваши письма
На прошлой неделе я получил еще порцию писем от вас. Вот что мне хотелось бы отметить:
Дорогие мои! Не спешите! Я, к сожалению, не успеваю отвечать на ваши вопросы. Давайте все будем рассматривать по порядку. Я, конечно, понимаю, что вам не терпится попробовать ВСЕ. Я сам когда-то учился программировать. Руки так чесались, что по ночам лез на стенку. В то время (1993 год) у меня не было компьютера, и приходилось довольствоваться услугами компьютерных центров, в которых стояли тогда отечественные ЕС-1840 (жалкое подобие IBM PC/XT, только без винчестера)...
Пробуйте, экспериментируйте. Поверьте, самому гораздо интереснее докапываться до истины. У вас есть все необходимое (tasm, tlink, afd/CV). Если кто-то не имеет этих программ - возьмите здесь:
http://asm.kalashnikoff.ru/prog/
Если уж вы зашли в тупик, то пишите ( oleg77@online.ru?Subject=Я в тупике! ). Постараемся разобраться.
Результаты голосования.
Спасибо всем, кто принял участие в голосовании. Вот его окончательные результаты (прямо хит-парад какой-то получился):
Команды и инструкции 486-Pentium 17 Viewer для просмотра графики 9 Безобидный вирус 31 Графическая игра 12 Дизассемблер 7 Графическая заставка с музыкой 15 Локальная сеть 18 Оболочка типа Нортон Коммандер 29 Резидент 24 Остальные варианты, к сожалению, набрали менее 5 голосов. За предварительными результатами можно было наблюдать на моей странице (я периодически их обновлял).
Как видите, первая тройка выглядит так:
1) Безобидный вирус;
2) Оболочка;
3) Резидент.
Что тут можно сказать? Очень сложно всем угодить, к сожалению. У меня есть два варианта:
1) Можно сделать рассылку чаще (2 раза в неделю), но меньшего объема. При этом, например, в понедельник рассматриваем Оболочку и команды 486, а в четверг Вирус и резидент.
2) Можно сделать так: в рассылке будет три-четыре раздела, посвященные той или иной теме (вирус, оболочка и пр.), но сама рассылка будет выходить один раз в неделю.
Первый вариант, я думаю, предпочтительней. Пишите мне, если Вы не согласны:
oleg77@online.ru?Subject=Не согласен!
В письме, пожалуйста, укажите ваше мнение.
Когда мы будем писать Оболочку, то затронем Графическую заставку, возможно Viewer и прочее.
Сейчас у меня небольшие проблемы с переездом в новый офис. Поэтому не так много свободного времени. Но, надеюсь, что скоро (как обживусь) я смогу уделять больше внимания рассылке. Потерпите пока немного, пожалуйста!
Стек/стэк (stack)
Немного теории.
Друзья мои! Я настоятельно рекомендую Вам выполнять все, что я вас прошу сделать в этом разделе (набирать программы, запускать их под отладчиком). Так вам будет гораздо легче разобраться со стеком.
У кого до сих пор нет отладчика, возьмите его, пожалуйста, здесь:
http://asm.kalashnikoff.ru/prog/
Итак, что же такое стек и для чего он нужен?
Стек - это специально отведенная область памяти для хранения промежуточных данных.
Давайте немного договоримся об определениях.
Пусть будет так, что сегмент "растет" сверху вниз:
0000
0001
0002
...
FFFE
FFFF
Т.е. мы как бы погружаемся под землю. Таким образом (сверху вниз) выполняется программа (если, конечно, не встречаются инструкции (команды) типа jmp, call и т.п.).
Стек же наоборот пополняется снизу вверх. Вершина стека - 0FFFFh, а низ (дно) - 0000h.
Когда мы вызываем какую-нибудь подпрограмму командой call, процессор кладет в стек адрес следующей за командой call инструкции. Следить за стеком позволяет пара регистров SS:SP. Многие уже, наверное, заметили, что при запуске какой-нибудь com-программы регистр SP равен 0FFFEh, а сегментный регистр SS, как уже упоминалось в предыдущих выпусках, равен нашему единственному сегменту (CSEG) (как и CS, DS, ES).
Теперь вам необходимо вооружиться отладчиком. Давайте рассмотрим вышесказанное на примере:
Напечатайте такую программу в редакторе (обязательно!):
CSEG segment
assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG
org 100h
begin:
call Our_proc
int 20h
Our_proc proc
ret
Our_proc endp
CSEG ends
end Begin
Ничего сложного... Запускаем отладчик.
Итак, смотрим на пару регистров SS:SP. SS=CS=DS=ES (это понятно). SP=0FFFEh (т.е. указывает на вершину стека).
Теперь заходим в процедуру. Для CV нажимаем F8, для AFD F1.
Опа! SP изменился. Он уменьшился на 2. Компьютер поместил в стек адрес возврата из процедуры (на инструкцию int 20h) (помните прошлые выпуски?). Проще говоря, call перешел на метку Our_proc, поместив в стек адрес возврата из этой подпрограммы.
Нажимаем еще раз F8/F1. Что получилось? SP опять изменился! Но теперь он увеличился на 2 (стал равным 0FFFEh). Т.е. команда ret взяла из стека адрес возврата и перешла по нему. Как раз на int 20h.
В данном случае говорят, что мы выровнили стек. Он был изначально равен 0FFFEh и остался равен перед выходом 0FFFEh.
Вот один из способов использования стека. Но на этом мы не остановимся и рассмотрим несколько операторов, которые позволяют работать со стеком.
Оператор Перевод Применение Процессор PUSH приемник push - втолкнуть Поместить в стек число 8086
Оператор Перевод Применение Процессор POP приемник pop - вытолкнуть Достать из стека число 8086 Допустим, нам нужно временно сохранит какое-нибудь число. Например, перед вызовом процедуры, прерывания или циклом.
Вот как это запишется:
...
(1) mov ax,345h
(2) push ax
(3) mov ah,10h
(4) int 16h
(5) pop ax
...
Здесь мы загружаем в AX число 345h, сохраняем его, ждем нажатия клавиши (при этом сама клавиша будет в AX, т.е. AX изменится) и восстанавливаем AX. В итоге AX будет содержать число 345h, что, как говорится, и требовалось доказать.
Однако, стоит заметить такой момент. Допустим, мы помещаем в стек следующие регистры: AX, BX, CX:
push ax
push bx
push cx
Обратите внимание, что восстанавливать со стека нужно в обратном порядке:
pop cx
pop bx
pop ax
Если вы поменяете местами регистры при восстановлении, то ничего страшного не произойдет, только содержать они будут другие числа. Например:
mov ax,1234h
mov bx,5678h
push ax
push bx
pop ax
pop bx
В итоге AX будет равен 5678h, а BX - 1234h.
Но в процедурах необходимо очень тщательно следить за стеком. Вот пример:
...
call Our_proc
int 20h
...
Our_proc proc
mov ax,15
push ax
mov ah,9
mov dx,offset Str
int 21h
ret
Our_proc endp
...
Обратите внимание, что мы "забыли" восстановить из стека AX в нашей процедуре (Our_proc). Что произойдет? Компьютер, дойдя до оператора ret, вытащит из стека не адрес возврата, а число 15 и перейдет на этот адрес. Что находится по адресу 15 - не известно. Машина, скорее всего, "зависнет". Надеюсь, что это понятно.
Следить за стеком (как уже говорилось) позволяет пара регистров SS (сегмент):SP (смещение).
Программист может менять как сегмент, так и смещение, но при этом следует иметь ввиду, что перед сменой регистров SS и SP необходимо запретить все прерывания, а после изменения разрешить (запрет прерываний вешает компьютер! Восстановление же - приводит к нормальной работе. После команды cli всегда должна идти sti, иначе компьютер виснет!). Это позволяют сделать следующие операторы:
Оператор Перевод Применение Процессор CLI CLear Interrupt - запретить Запретить прерывания 8086
Оператор Перевод Применение Процессор STI reSTore Interrupt - восстановить Разрешить прерывания 8086 Для чего это нужно? Давайте рассмотрим это в двух словах, т.к. механизму работы прерываний необходимо посвятить целый выпуск. Если что-то не понятно - просто опустите, не обращайте внимания.
Когда вы создали программу и запустили ее на выполнение, то работает не только она одна. Простейший пример - таймер, который вызывается примерно 18 раз в секунду для обновления. Компьютер всегда что-то делает! Даже тогда, когда ждет от вас нажатия клавиши. Что происходит, когда вызывается прерывание от таймера? Примерно тоже, что и при вызове процедуры. Компьютер запоминает в стеке адрес текущей команды, а также все регистры и переходит на адрес прерывания, по которому находится процедура обработки этого прерывания (например, таймера, которая обновит показания часов/минут/секунд). Затем, как процедура отработала, компьютер восстановит из стека адрес возврата и все регистры, и наша программа пойдет работать дальше.
Вот пример, в котором мы изменим регистры SS:SP, не запрещая прерывания:
(1) mov ax,100h
(2) mov ss,ax
(3) mov sp,200h
Допустим, прерывание таймера сработало после строки (2). Что у нас получилось? SS равен 100h, а SP еще не успел измениться. Получается, что сегмент стека верный, а смещение - осталось прежним (допустим, SP=0FFFEh). В итоге, SS=100h, а SP=0FFFEh. Компьютер и сохранит по этому адресу данные. Какой при этом код программы затрется - не известно. Мы ведь хотели сделать SS=100h, а SP=200h! Хорошо, если две строки успели выполниться перед вызовом прерывания. Хорошо также, если по адресу 100h:0FFFEh ничего нет (память свободна). А если есть? Тогда компьютер, скорее всего, "зависнет".
Отсюда вытекает два правила:
1) Прежде чем менять регистры SS:SP, необходимо запретить все прерывания командой cli, а затем разрешить командой sti.
2) SS:SP нужно устанавливать на свободную область памяти. При этом следует убедиться, что код не утратил работоспособности.
Мы знаем, что после загрузки com-программы в память, SS равен сегменту, куда загрузилась программа, а SP - 0FFFEh. Код программы начинается с адреса 100h (org 100h). Вершина стека - конец нашего сегмента. Если наша программа занимает, скажем 2000h байт, то можем установить SP в 2200h. В этом случае мы отводим 100h (именно сто) байт для стека (т.к. программа загружается по адресу 100h, то 2000h нужно прибавить 100h). Стек, как вы помните, растет снизу вверх. Если мы переполним стек (например, поместим более 100h данных), то затрется часть нашей программы снизу. Имейте это ввиду!
Друзья мои! Это понятно? oleg77@online.ru?Subject=Ничего не понял в стеке
Вот пример изменения регистров стека:
cli
mov ax,0B900h
mov ss,ax
mov sp,100h
sti
Программка для практики.
Прежде чем перейти к программе, рассмотрим новый оператор:
Оператор Перевод Применение Процессор NOP No OPerand - нет операнда Ничего не делает 8086 Этот оператор делает то, что ничего не делает, но занимает один байт. Его обычно используют для резервирования места либо для того, чтобы "забить" ненужный код, когда исходник на Ассемблере отсутствует. Например, программа перед стартом проверяет версию MS-DOS. Версия, которая установлена на вашем компьютере, не соответствует требуемой программой. Для этого данным оператором "забивают" участок кода, который проверяет версию ОС.
Все это позволяет сделать Hacker's View, который можно взять на сайте:
http://asm.kalashnikoff.ru/prog/
Запомните машинный код данной команды: 90h.
Даю голову на отсечение, что ни один язык высоко уровня не позволяет сделать того, что может наша программа:
(1) CSEG segment
(2) assume cs:CSEG, es:CSEG, ds:CSEG, ss:CSEG
(3) org 100h
(4) Begin:
(5) mov sp,offset Lab_1
(6) mov ax,9090h
(7) push ax
(8) int 20h
(9) Lab_1:
(10) mov ah,9
(11) mov dx,offset Mess
(12) int 21h
(13) int 20h
(14) Mess db 'А все-таки она выводится!$'
(15) CSEG ends
(16) end Begin
То, что вы видите - обман зрения. На первый взгляд, программа что-то делает с регистром SP, а затем выходит. Строки (9) - (12) вообще не будут работать. Но это глубокое заблуждение!
Попробуйте запустить ее под отладчиком. Вы увидите, что CodeView, TurboDebuger, AFD будут сообщать какую-то ерунду (непонятные операторы, сообщения типа "программа завершилась", хотя строка не выведена и пр.). Но, если запустить ее просто из ДОС, то строка появится на экране, т.е. программа будет работать корректно!
Данный пример - типичный случай "заламывания рук" отладчикам (но не SoftIce!). И вы уже можете это делать!
Вывод один: указанные выше отладчики используют стек пользовательской программы (что это значит - думаю, вы разберетесь).
Ваша задача: разобрать "по полочкам" программу. Почему так происходит? Почему строка выводится? Почему отладчик работает неверно? И пр. и пр. Вопросов море. И вам предстоит дать ответ на них.
Разобравшись с программой самостоятельно, вы почувствуете Силу и Неограниченные Возможности Ассемблера. И это правда! Не спешите писать мне письмо с просьбой помочь. Будет не интересно! Пробуйте разобраться сами! Не бойтесь экспериментировать! Компьютер будет часто зависать, но это не главное! Ваши мучения приведут вас к Истине! Я сам проходил когда-то через это...
Впечатлениями можно делиться со мной:
oleg77@online.ru?Subject=Впечатления
Давайте попробуем встретиться в чате в понедельник (07.08.2000) в 09:30 утра по московскому времени:
http://www.chats.ru/users_chats/oleg77/
Жду вас всех (если удастся пробиться)!
Нескучной Вам недели!
С уважением,
Калашников Олег ( oleg77@online.ru?Subject=Ассемблер: )
[Следующий выпуск] [На главную страницу]
u="u496.71.spylog.com";d=document;nv=navigator;na=nv.appName;p=1; bv=Math.round(parseFloat(nv.appVersion)*100); n=(na.substring(0,2)=="Mi")?0:1;rn=Math.random();z="p="+p+"&rn="+rn;y=""; y+=""; y+=""; y+=""; d.write(y);if(!n) { d.write("