Назад в раздел
RU.ASM.CHAINIK FAQ.
eManual.ru - электронная документация
From: AutoPost <AutoPost@p8.f56.n5058.z2.fidonet.org>
Date: Mon, 19 Feb 2001 11:07:27 +0300
RU.ASM.CHAINIK FAQ
редакция от 13.2.2001
Данный FAQ постится раз в неделю в эхоконференцию RU.ASM.CHAINIK
Его всегда можно взять на faqserver'е по
адресу 2:5058/58.111, топик 'asmfaq'.
-------------------------------------------------------------------------------
Содержание:
1. Покажите маленькую программку типа 'Hello, world!'
2. А как ее запустить (слинковать, асссемблировать)?
3. А где можно взять tasm и tlink?
4. Что такое PSP?
5. Где хранится командная строка и как ее получить?
6. Как узнать полный путь к запущенной пpогpамме из нее самой?
7. Что такое прерывание и как оно работает?
8. Что такое вектор прерывания?
9. А как можно сгенерировать звук?
10. Что лучше - стандартные или упрощенные директивы определения сегментов?
11. Для чего нужна команда LEA. То же самое может и OFFSET, да и Tasm
заменяет LEA на MOV...OFFSET.
12. Откуда программа узнает адрес сегмента? После компиляции стоит mov ax,1.
13. Как сделать COM с отладочной информацией, понимаемой TD ?
14. Не получается! COM есть, TDS есть, а TD отладочную информацию не
видит: "Program has no symbol table"
15. Как расчитать количество памяти, необходимое для резидента?
16. Не могу запустить дочернюю задачу функцией 4Bh
17. Не выделяется память по функции 48h
18. Да вроде все есть, почему не выделяет-то?
19. Как сжать блок памяти, занимаемый программой?
20. А что за команда такая rdtsc?
21. И еще, расскажите русским языком, что такое рекурсия (никогда не
сталкивался!)?
22. Расскажите про сопроцессор, как его использовать?
23. Что делать, если "Relative jump out of range"?
24. А какие-нибудь ссылки в интернете?
25. А что такое CMOS и как с ней работать?
26. Что делать, если метки одинаковые?
27. Как вывести число в шестнадцатеричном виде?
28. Как слинковать драйвер устройства?
last. А как это ... сделать?
----------------------------------------------------------------------------
Q1: Покажите маленькую программку типа 'Hello, world!'
A: Вот пример: Слинковать в com файл (я бы вам пока вообще не рекомендовал
использовать EXE).
..model tiny ; модель памяти - делаем com-файл
..code ; сегмент кода или пpосто - код
..startup ; стаpтовая точка пpогpаммы
mov ah,09 ; фyнкция N9 - вывод текста на экpан
mov dx,offset msg ; в dx заносим адpес сообщения msg
int 21h ; вызов так называемого Сеpвиса Доса
; (в ah для него номеp фyнкции)
ret ; в СОМ-файле так можно завеpшать пpогpамму
; в ЕХЕ - немного сложнее...
msg db 'Hello, world! $' ; сообщение (должно оканчиваться на '$')
end ; конец файла
----------------------------------------------------------------------------
Q2: А как ее запустить (слинковать, асссемблировать)?
A: Вот так:
tasm hello.asm
tlink /t hello.obj
----------------------------------------------------------------------------
Q3: А где можно взять tasm и tlink?
A: Они вообще-то не freeware, но если очень надо :)
http://bsg.nm.ru/minimum.zip
----------------------------------------------------------------------------
Q4: Что такое PSP?
A: PSP - структура, формируемая для каждой запущенной программы,
содержащая множество полезной информацию, в частности, командную строку
и ее длину. Пpи запуске пpогpаммы (как СОМ, так и ЕХЕ) ds и es содеpжат
сегментный адpес PSP. Для COM-файлов он равен еще и cs.
----------------------------------------------------------------------------
Q5: Где хранится командная строка и как ее получить?
A: Она хранится в PSP:[80h] - 1 байт длина, затем 127 байт сама строка.
A2: Имхо нужно разделить понятия командная строка и параметры
запускаемой проги. В PSP:[80] лежат именно параметры, а сама командная
строка в другом месте. PSP:[2ch] - сегментный адрес командной строки.
----------------------------------------------------------------------------
Q6: Как узнать полный путь к запущенной пpогpамме из нее самой?
A:
mov ax,1203h
int 2Fh ;получим сегмент данных DOS
mov ax,ds
lds si,ds:[bp-1Ah] ;в ds:si - указатель на полный путь
----------------------------------------------------------------------------
Q7: Что такое прерывание и как оно работает?
A: Прерывание - это именно прерывание программы для выполнения
какой-либо другой работы.
Необходимо pазличать пpогpаммные и аппаpатные пpеpывания.
Аппаpатные генеpятся устpойствами, а пpогpаммные вызываются самой
пpогpаммой и являются фактически аналогами вызова подпpогpамм, вызовами
системных функций DOS, напpимеp. Аппаратные прерывания прерывают
программу в необходимый момент, например, по приходу байта от модема, по
движению мыши и т.п.
Смотрите первый пример 'Hello, world!', там используется int 21h -
прерывание номер 21h, которое отвечает за функции ДОС. В ah у нас было
09h - это функция вывода текста на экран, начиная с адреса ds:dx.
---------------------------------------------------------------------------
Q8: Что такое вектор прерывания?
A: Это адрес, по которому будет сделан переход в случае вызова
соотвествующего прерывания. Например, в случае, если в программе стоит
'INT 21h', адрес перехода берется из ячейки по адресу 0000:21h*4 (по 4
байта на один вектор прерывания).
----------------------------------------------------------------------------
Q9: А как можно сгенерировать звук?
A: Вот так:
;
; подпрограмма генерации звука
; Вход: АX= частота звука в Гц
;
Sound proc near
push ax ;сохранить регистры
push bx
push dx
mov bx,ax ;частота
mov ax,34DDh
mov dx,12h ;(dx,ax)=1193181
cmp dx,bx ;если bx < 18Гц, то выход
jnb Done ;чтобы избежать переполнения
div bx ;ax=(dx,ax)/bx
mov bx,ax ;счетчик таймера
in al,61h ;порт РВ
or al,3 ;установить биты 0-1
out 61h,al
mov al,00001011b ;управляющее слово таймера:
;канал 2, режим 3, двоичное слово
mov dx,43h
out dx,al ;вывод в регистр режима
dec dx
mov al,bl
out dx,al ;младший байт счетчика
mov al,bh
out dx,al ;старший байт счетчика
Done:
pop dx ;восстановить регистры
pop bx
pop ax
ret
Sound endp
Выключение звука:
No_Sound proc near
push ax
in al,61h ;порт РВ
and al,not 3 ;сброс битов 0-1
out 61h,al
pop ax
ret
No_Sound endp
----------------------------------------------------------------------------
Q10: Что лучше - стандартные или упрощенные директивы определения сегментов?
A: Однозначно проще - упрощенные. Что лучше - решается индивидуально.
Использование стандартных директив имеет смысл или в педагогических
целях, или при наличии причин, требующих использования именно стандартных
директив. Например, необходимость использования специальных имен
сегментов, специальных атрибутов и особой группировки. Последнее
опять-таки вовсе не означает, что упрощенные директивы не могут быть
использованы. Например:
.MODEL LARGE
MyGroup group MySpecialSeg,$LibTable
MySpecialSeg segment word public use16 'DATA'
...
ends
$LibTable segment para common use16 'DATA'
...
ends
.DATA
.CODE
end
Поэтому вполне разумным видится использование упрощенных директив,
совмещенное (при необходимости) с использованием стандартных.
---------------------------------------------------------------------------
Q11: Для чего нужна команда LEA. То же самое может и OFFSET, да и Tasm заменяет
LEA на MOV...OFFSET.
A: MOV...OFFSET короче LEA, поэтому в режиме SMART tasm заменяет LEA на MOV
для тех случаев, когда это возможно:
lea di,Array
mov di,offset Array
Но такая замена возможна не всегда:
lea di,Array[si+bx.FieldName]
Логика работы LEA в данном случае эквивалентна такому фрагменту:
mov di,offset Array
add di,si
add di,bx
add di,FieldName
Результат этого фрагмента не может быть вычислен на этапе компиляции
из-за неизвестных величин, а следовательно, LEA в данном случае не может
быть заменена командой MOV...OFFSET
---------------------------------------------------------------------------
Q12: mov ax,@data
mov ds,ax
Откуда программа узнает адрес сегмента? После компиляции стоит mov ax,1.
А в отладчике появляется сразу нужный адрес: mov ax,140Fh
Кто его туда прописывает?
A: Т.к. EXE может быть загружен по различным адресам, вместо явных значений
cегментов в EXE указаны номера 16-байтных параграфов [0...FFFF] этих
cегментов, начиная от начала образа EXE. Загрузчик, после считывания образа
EXE в память, используя информацию в заголовке EXE, находит ссылки на
явные значения сегментов и прибавляет к значению параграфа, указанное
непосредственно в команде, реальное значение сегмента, начиная с которого
загружен EXE.
Например, образ EXE считан в память, начиная с адреса 140Eh:0 После
корректировки значений сегментов вместо mov ax,1 получается mov ax,140F
---------------------------------------------------------------------------
Q13: Как сделать COM с отладочной информацией, понимаемой TD ?
A: comdbg.bat TEST
tasm /zi %1
tlink /v %1,%1,,,
tdstrip -s -c %1.exe
----------------------------------------------------------------------------
Q14: Не получается! COM есть, TDS есть, а TD отладочную информацию не видит:
"Program has no symbol table"
A: У TDS время меньше, чем у COM - такое бывает в Винде.
Воспользуйтесь утилитой touch из NWDOS (в MS DOS она похуже)
touch %1.tds
---------------------------------------------------------------------------
Q15: Как расчитать количество памяти, необходимое для резидента?
A: FirstFreeByteSeg - PspSeg + ((FirstFreeByteOffs+15) div 16)
Resident macro FirstFreeByteSeg,FirstFreeByteOffs
mov dx,FirstFreeByteSeg
sub dx,[PspSeg]
mov ax,FirstFreeByteOffs
dec ax
shr ax,4
inc ax
add dx,ax
mov ah,31h
mov al,[ErrorLevel]
int 21h
endm
Resident seg Install,<offset Install>
---------------------------------------------------------------------------
Q16: Не могу запустить дочернюю задачу функцией 4Bh
Q17: Не выделяется память по функции 48h
A: Нет свободной памяти.
---------------------------------------------------------------------------
Q18: Да вроде все есть, почему не выделяет-то?
A: Да потому что она уже тебе выделена, теперь ее осталось только сжать.
---------------------------------------------------------------------------
Q19: Как сжать блок памяти, занимаемый программой?
A: Сжимать ее следует через функцию DOS 4Ah. Алгоритм тот же, что и у
макроса Resident:
ShrinkMem macro FreeSeg,FreeOffs
mov bx,FreeSeg
sub bx,[PspSeg]
mov ax,FreeOffs
dec ax
shr ax,4
inc ax
add bx,ax
mov ah,4Ah
mov es,[PspSeg]
int 21h
endm
ShrinkMem <seg stack>,<(size stack)+1>
В случае полного/дополнительного ручного объявления сегментов и их особого
упорядочивания необходимо указать имя последнего сегмента.
----------------------------------------------------------------------------
Q20: А что за команда такая rdtsc?
A: read tsc - Read Time Stamp Counter. Читает регистр tsc, проще говоря
возвращает в edx:eax количество тактов с момента последнего сброса
процессора. Опкод - 0F 31, команда появилась на процессорах Pentium (и
то не на всех.)
---------------------------------------------------------------------------
Q21:. И еще, расскажите русским языком, что такое рекурсия (никогда не
сталкивался!)?
A: Вызов функцией самой себя.
Q: Примерчик приветствуется.
A: Классический пример - вычисление факториала:
..model farstack small, pascal
.386
locals @@
..stack 2048
..code
Factorial PROC ; function factorial(@@N:Word):DWord;
arg @@N:word ; begin
mov ax,@@N
cmp ax,1 ; if (@@N=1) or (@@N=0) then
ja @@calc
mov ax,1 ; factorial:=1
xor dx,dx
ret
@@calc:
dec ax ; else
push ax
call Factorial ; factorial := factorial(@@N-1)
mul @@N ; * @@N;
ret ; end;
endp
MAIN PROC
.startup
push 4
call Factorial
.exit
ENDP
end MAIN
----------------------------------------------------------------------------
Q22: Расскажите про сопроцессор, как его использовать?
A: Вот тебе пример программы с комментариями:
..model tiny
..code
..386 ; привычка :)
..387 ; использование сопроца
..startup
finit ; инициализация сопроца
fild data1 ; загрузка data1
fiadd data2 ; складывание с data2
fist _result ; сохранение результата в
; _result
ret
data1 dw 1
data2 dw 200
_result dw ?
end
Теперь немного теории.
Пример команды:
fild
^^^^
||++
|||
||+-- 'ld' - load, загрузка числа в стек сопроцессора
|+--- 'i' - integer, означает, что работаем с ЦЕЛЫМИ данными (
| еще варианты - '', то есть fld, например - загрузка вещественного
| числа в сопроцессор, 'b', т.е. fbld - загрузка BCD числа)
+---- 'f' - обозначает, что это команда сопроцессора
Примеры команд:
fld data1 ; загрузка вещественного числа из памяти
; по адресу data1 в сопроцессор
fist _result ; сохранение числа как целого в память по
; адресу _result (при необходимости оно
округляется -
; это делает сам сопроц)
fistp _result ; то же самое, но при сохранении числа оно
; выталкивается из стека сопроца
fsqrt ; вычисление квадратного корня из st0, то
; есть аргумент берется из стека, туда же и
; помещается значение корня
fcos, fsin ; вычисляет косинус и синус угла, заданного
; в стеке сопроца. Угол должен быть в
; _радианах_.
fsincos ; одновременно вычисляет и sin и cos, в
; st0 помещается sin, в st1 - cos.
Вообще для понимания механизма работы возьмите Turbo Debugger:
F10/View/Numeric processor:
Здесь видно, что в стеке сопроца находится число 300 :)
+=[x]=80486 IPTR=54CE3 OPCODE=706 OPTR=54CEE==2=[][]=+
|Valid ST(0) 300 | im=1 | ie=0 |
|Empty ST(1) | dm=1 | de=0 |
|Empty ST(2) | zm=1 | ze=0 |
|Empty ST(3) | om=1 | oe=0 |
|Empty ST(4) | um=1 | ue=0 |
|Empty ST(5) | pm=1 | pe=0 |
|Empty ST(6) |iem=0 | ir=0 |
|Empty ST(7) | pc=3 | cc=0 |
| | rc=0 | st=7 |
| | ic=0 | |
+xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX+======+=====-+
Выполняем:
cs:0107 D9FA fsqrt
+=[x]=80486 IPTR=54CE7 OPCODE=1FA OPTR=54BE0==2=[][]=+
|Valid ST(0) 17.320508075688773 | im=1 | ie=0 |
|Empty ST(1) | dm=1 | de=0 |
|Empty ST(2) | zm=1 | ze=0 |
|Empty ST(3) | om=1 | oe=0 |
|Empty ST(4) | um=1 | ue=0 |
|Empty ST(5) | pm=1 | pe=1 |
|Empty ST(6) |iem=0 | ir=0 |
|Empty ST(7) | pc=3 | cc=0 |
| | rc=0 | st=7 |
| | ic=0 | |
+xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX+======+=====-+
Получили вещественное число в стеке сопроцессора. Сохраним его в памяти
по адресу ds:[110] как целое:
cs:0109 DF1E1001 fistp word ptr[0110]
Смотрим содержимое:
ds:0110 11 00 FF 16 57 9A A5 1B
^^^^^ вон он наш результат :)
----------------------------------------------------------------------------
Q23: Народ, как обойти "Relative jump out of range"?
A: Поставь директиву .386 или даже больше - .586, напpимеp - и
наслаждайся... ;) А по умолчанию используется 8086. У него только jmp
short есть.
A2: А если все же пpиспичило писать под пpоцессоp менее 386, то можно
воспользоваться директивой 'jumps':
JUMPS
....
CMP чего надо
JZ куда надо
....
NOJUMPS
И это автоматом постpоит констpукцию, подобную этой:
CMP чего надо
JNZ @2
JMP куда_надо
@2:
Диpектива JUMPS заменяет все коpоткие пеpеходы на такую
констpукцию, в случае необходимости. Поэтому пользоваться ей можно
всегда.
----------------------------------------------------------------------------
Q25: А что такое CMOS и как с ней работать?
A: Сейчас под термином CMOS (в рамках компьютерщиков) понимают 64 (уже
говорят, что 128) байт энергонезависимой памяти.
PC класса AT имеют питаемые от батарейки часы реального времени
(RTC) и 64 байта постоянной CMOS-памяти.
Эта память содержит разнообразную информацию, включающую текущие
дату и время, сведения о конфигурации машины и байт статуса закрытия
системы (этот байт используется механизмом, позволяющим машине AT
рестартовать после выполнения сброса процессора, выводящего из
защищенного режима).
Работать с ней надо так:
Чтобы прочитать байт из CMOS, выполните команду OUT 70H, адрес;
затем выполните IN 71H. Чтобы записать байт в CMOS, выполните OUT 70H,
адрес; затем OUT 71H, значение.
Пример: ;------- прочитать тип установленного твердого диска
mov al,12H
out 70H,al ;выбрать адрес CMOS 12H
jmp $+2 ;требуется небольшая задержка
in al,71H ;теперь в AL тип устройства (0-15)
Адреса 10H..20H защищены контрольной суммой, что позволяет
обнаружить износ батарейки или порчу информации в записи конфигурации.
Контрольная сумма - это просто 16-битовая сумма защищаемых байт памяти.
----------------------------------------------------------------------------
Q26: Тут такое дело, в процедурах у меня часто метки одинаковые, или
вставляю из разных своих исходников куски, tasm ругается, мол, метки
одинаковые :( Приходится все иправлять... Что сделать-то можно? И вообще
как можно удобно сделать работу с метками?
A: Есть два способа, один простой, другой хитрый :)
Способ простой: ставим в начале исходника locals @@ и все метки,
начинающиеся с символов '@@' будут _локальными_, то есть существовать в
пределах одной процедуры и не вызывать конфликта с одинаковыми именами.
Способ хитрый: у tasm'а есть такой режим работы, при котором существуют
метки типа @@,@b,@f (@b и @f соответственно переходят на ближнюю метку
@@ назад, либо вперед), этот режим включается словами 'masm' и 'quirks'
(обязательны обе директивы, иначе работать не будет!). Работает так:
+------------+
@@:| |
+ nop |
nop |
jmp @b -+
nop
jmp @f -+
@@:+ nop |
+------------+
----------------------------------------------------------------------------
Q27: А как напечатать число в шестнадцатеричном виде?
A: Можно сделать так:
(результат помещяется в es:di)
byte2hex proc near
push cx
mov cx,2
@@L1: rol dl,4
mov ax,300fh
and al,dl
aaa
aad 11h
stosb
loop @@L1
pop cx
ret
byte2hex endp
word2hex proc near
push cx
mov cx,2
@@L1: rol dx,8
call byte2hex
loop @@L1
pop cx
ret
word2hex endp
dword2hex proc near
mov cx,2
@@L1: rol edx,16
call word2hex
loop @@L1
ret
dword2hex endp
----------------------------------------------------------------------------
Q28: А как слинковать драйвер устройства (sys, или просто сделать файл с
org0?)
A: Вот так:
tasm driver.asm /m4
tlink driver.obj, driver.sys /t
> ^ обратите внимание, это самое главное :)
p.s. Есть одна тонкость, на которую я напоролся и имел много проблем.
Тонкость вот в чем. Если у вас tlink вызывается через батник, то
слинковать драйвер вы не сможете, так как батник при разборе параметров
%1 %2 и т.п. Е передаст tlink'у символ запятой, которая здесь играет
решающую роль. Так что линкуйте без батника, либо в нем явно напишите
'tlink driver.obj, driver.bin /t'
----------------------------------------------------------------------------
Qlast: А как это ... сделать?
A: Напишите в эху, вам ответят :)
----------------------------------------------------------------------------
Ссылки в интернете:
Ассемблер NASM (freeware)
http://www.cryogen.com/nasm
Interrupt list (хорошее описание прерываний)
http://www.pobox.com/~ralf
Различные базы, дополнения, NG, TechHelp!.
http://www.whitetown.com/ru/ng/
http://www.shortway.to/posohov
Программирование на ASM'е под Windows:
http://win32asm.newmail.ru/
TechHelp! 4.0 RUS
http://bsg.nm.ru/techhelp.zip
----------------------------------------------------------------------------
FAQServer 2:5058/58.111
----------------------------------------------------------------------------
Все вопросы по содержанию и дополнению FAQ'а отправлять мне -
Alexander Zigar' 2:5058/56.8.
В создании FAQ принимали участие:
Alexander Zigar' 2:5058/56.8
Kirill Barashkin 2:5080/500.271
Max Vorobyov 2:5025/150.24
Roman Perminov 2:5070/313
Yury Suharev 2:5023/19.11
Mihail Epihin 2:5023/29.34
Также спасибо всем подписчикам эх RU.ASM.CHAINIK, TALKS.ASM, PC.CODING и
им подобных.
|
|
|
|