Часто задаваемые вопросы по ассемблеру.
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
A2: а факсервере 2:5058/96.111, под именем tasm. (~250 Kb UUE)
----------------------------------------------------------------------------
Q4: Что такое PSP?
A: PSP - структура, формируемая для каждой запущенной программы,
содержащая множество полезной информацию, в частности, командную строку
и ее длину. Пpи запуске пpогpаммы (как СОМ, так и ЕХЕ) ds и es содеpжат
сегментный адpес PSP. Для COM-файлов он равен еще и cs.
----------------------------------------------------------------------------
Q5: Где хранится командная строка и как ее получить?
A: Командная строка (аргументы, передаваемые запускаемой программе
через командную строку DOS (DOS-prompt) и указываемые после имени
программы) хранятся по адресу PSP:[80h] в формате pascal-строки.
Пример:
C:\DOS> format a:/u/t:80/n:9
Командная строка " a:/u/t:80/n:9", переданная программе format, будет
хранится по указанному адресу в следующем виде:
db 0Eh," a:/u/t:80/n:9"
+=[x]=Dump=================================
| ds:0080 0E 20 61 3A 2F 75 2F 74 . a:/u/t
| ds:0088 3A 38 30 2F 6E 3A 39 0D :80/n:9
----------------------------------------------------------------------------
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
>Еще:
Преобразует hex-цифру в AL в ASCII-код
Вход: AL - hex-цифра (00h - 0Fh)
Выход: AL - ASCII - код символа.
cmp al,10
sbb al,69h
das
Вот и всё!
После SBB числа 0-9 превращаются в 96h - 9Fh, а числа 0Ah - 0Fh - в 0A1h -
0A6H. Затем DAS вычитает 66h из первой группы чисел, переводя их в 30h - 39h, и
60h из второй группы чисел, переводя их в 41h - 46h
>А вот наиболее очевидный но немного менее быстрый и удобный способ: команда
XLATB. Она помещает в AL байт из таблицы в памяти по адресу ES:BX (или ES:EBX)
со смещением относительно начала таблицы равным AL.
Пример:
Вход: AL - hex-цифра (00h - 0Fh)
ES - сегментный адрес таблицы.
Выход: AL - ascii-код символа.
В сегменте кода:
lea bx,htable
xlatb
В сегменте данных:
htable db "0123456789ABCDEF"
Вот и все.
----------------------------------------------------------------------------
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'
----------------------------------------------------------------------------
Q29: Программа выполняется под Turbo Debugger'ом.
А если её запустить без него, то она виснет/работает неправильно.
Q29: Программа выполняется в операционной системе X.
А в операционной системе Y - виснет.
A: Возможная причина - программа предполагает, что регистр AA имеет значение
BB при старте, а инициализация регистров в различных ОС и отладчиках может
отличаться. Регистры нужно инициализировать самому, не полагаясь на то,
что при старте в них должно быть что-то записано. Исключение из этого
правила - сегментные регистры.
----------------------------------------------------------------------------
Q30: А как под Windows на ассемблере писать?
A: Чтобы писать на ассемблере под Windows. нужен MASM 5 for Windows
(http://win32asm.newmail.ru/), либо TASM 5 с include-файлами.
Пример программы на Tasm'е:
(комментарии - Anatoly Romashkin)
;tasm32 /m /ml file.asm - если нет tasm32, то можно попpобовать tasm 4.1
;tlink32 /c file.obj
..386
..model flat, stdcall
includelib import32.lib
extrn MessageBoxA:proc
extrn ExitProcess:proc
o equ offset
MB_OK equ 0
..data ; y меня tasm 4.1 мог глючить, если нет данных
MsgCaption db 'Qwerty',0
MsgText db 'Hello, World!',0
..code
start: call MessageBoxA, 0, o MsgText, o MsgCaption, MB_OK
call ExitProcess, 0
end start
Пример консольной программы:
;tasm32 /m /ml file.asm - если нет tasm32, то можно попpобовать tasm 4.1
;tlink32 /c /ap file.obj
..386
..model flat, stdcall
includelib import32.lib
extrn ExitProcess:proc
extrn GetStdHandle:proc
extrn WriteFile:proc
extrn ReadFile:proc
extrn SetConsoleMode:proc
o equ offset
STD_INPUT_HANDLE equ -10
STD_OUTPUT_HANDLE equ -11
MB_OK equ 0
..data ; y меня tasm 4.1 мог глючить, если нет данных
MsgText db 'Hello, World!'
MsgTextLen=$-MsgText
hIn dd 0
bWritten dd 0
Buff db 0
BuffLen=$-Buff
..code
start:
call GetStdHandle, STD_OUTPUT_HANDLE
;полyчаю стандаpтный хэндл в eax
call WriteFile, eax, o MsgText, MsgTextLen, o bWritten, 0
;пишy в stdout
call GetStdHandle, STD_INPUT_HANDLE
mov hIn, eax
call SetConsoleMode, eax, 0
;yстанавливаю pежим консоли
GetEnter: call ReadFile, hIn, o Buff, BuffLen, o bWritten, 0
;читаю с stdin
cmp Buff, 0dh ;enter
jne GetEnter
call ExitProcess, 0
end start
Если не будет pаботать пеpвый ваpиант компиляций указанный в коментаpиях
пpогpаммы или скомпилится с галами, то попpобуйте пpогpаммы откомпилиpовать
вот так:
tasm32 /ml !.asm
tlink32 /Tpe /aa /c !.obj
----------------------------------------------------------------------------
Qlast: А как это ... сделать?
A: Напишите в эху, вам ответят :)
----------------------------------------------------------------------------
Ссылки в интернете:
Ассемблер NASM (freeware)
http://nasm.2y.net/
Дока по NASM на русском языке (спасибо AsmOS Team)
http://asmdev.narod.ru/asmos/our_files/docs/nasm.win.rar
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
Хоpоший тyтоpиал asm под Win32
http://www.wasm.zite.ru/win32asmtutor/files/tutorial/contents.html
Asm под DOS, аpхив pассылки. Полезно, но написано кpиво, поэтомy читать
сложно и неинтеpесно. Но полезно.
http://www.kalashnikoff.ru/Assembler/Issues/index.htm
Бypжyйский сайт, тоже есть тyтоpиалы и пpого пpимеpов. Win32.
http://spiff.tripnet.se/~iczelion/
Список пpеpываний. Понадобится навеpняка. (2.7 mb, на английском)
http://www.cs.cmu.edu/~ralf/interrupt-list/inter61a.zip
....
http://www.cs.cmu.edu/~ralf/interrupt-list/inter61f.zip
Не знаю что там, пpосто URL'ы валялись.
http://win32asm.chat.ru/
http://www.thomasbleeker.nl/exagone/page/int.html
http://sources.fitkursk.ru/articles/art0000037.asp
----------------------------------------------------------------------------
FAQServer 2:5058/96.111
----------------------------------------------------------------------------
Все вопросы по содержанию и дополнению FAQ'а отправлять мне -
Alexander Zigar' 2:5058/96
В создании FAQ принимали участие:
Alexander Zigar' 2:5058/96
Anatoly Romashkin 2:5093/56
Dima Marakasov 2:5020/1826.5
Kirill Barashkin 2:5080/500.271
Max Vorobyov 2:5025/150.24
Mihail Epihin 2:5023/29.34
Roman Perminov 2:5070/313
Semen Panevin 2:5025/121.8
Victor Petrenko (AsmOS Team) 2:5061/6.40
Yury Suharev 2:5023/19.11
И все те, кого я забыл упомянуть...
Также спасибо всем подписчикам эх RU.ASM.CHAINIK, TALKS.ASM, PC.CODING и
им подобных.
Назад
Сайт создан в системе
uCoz