АСЕМБЛЕР под WINDOWS за Начинаещи

0
864

От Автора:

Цитат:


Музата за тази статия ми дойде с ефекта, който предизвика една сентенция за Асемблер, която за мое съжеление не е моя, но аз изцяло подкрепям.


І. НЯКОЛКО ДУМИ ЗА УВОД

Първата Асемблерска програмка под Windows, с която се сблъсках бе прословутия Message Box.
А тя изглежда така (Flat Assembler):

Код:


include '%fasminc%/win32ax.inc' 
.code 
  start: 
        invoke  MessageBox,0,"Hi! I'm the example program!","Win32 Assembly",MB_OK 
        invoke  ExitProcess,0 
.end start

Сравнявайки я с подобни програмки написани на езици на високо ниво и вършещи същата работа останах с впечатление, че програмирането на Асемблер под Windows не се различава кой знай колко от програмирането на високо ниво. Това мое впечатление се превърна в убеждение, когато написах няколко други прости програмки ползващи основно WindowsAPI. Най-интересен ми бе крайния резултат – Асемблерските програмки бяха много по-малки по обем, зареждаха се и се изпълняваха много по-бързо (прозорците просто “летяха”). Всичко това така ме “запали”, че на бърза ръка си купих няколко книжки засягащи тематиката наречена Асемблер.
Когато почнах да ги прелиствам – се “хванах за главата” – то едни сегменти, адреси, флагове, прекъсвания, кодове и т.н, т.н. – нищо общо с програмирането на високо ниво и почти нищо за програмирането на Асемблер под Windows, а за да ги разбереш сигурно ще са ти необходими поне “два семестъра Информатика”.
А в действителност програмирането на Асемблер под Windows е елементарно, по-елементарно дори и от… Basic, а книжките разпространявани в търговската мрежа са написани от много добри програмисти и математици за програмисти, а и за съжеление “поовехтяли”… (понабутах се)

Но така или иначе да започваме…

Всички (предполагам) сме гледали “Матрицата” и знаем как започва. Всъщност именно тези нули и единици са езика на процесора (сигурен съм че ви е известно). Преди години – чувайки думата “Асемблер” си представях нещо подобно свързано с някъкви трибуквени съкращения и писане на много, ама много код за нищо. В действителност не съм бил много далеч от истината, но живота си тече и нещата се променят. Революцията дойде с процесора 386 DX, 32 битовата “плоска” памет и… Windows. А какво е Windows? Това е просто една операционна система – подобна на DOS, само че с много по-големи възможности, но най-вече въвеждаща РЕД и КОНТРОЛ. Така, че да програмираш на Асемблер под Windows е почти същото като да програмираш на други програмни езици под Windows с тази разлика, че програмата върши само това, което си написал. За пример ще се върна на Message Box-а. Разглейждайки компилираната програма в който и да е HEX editor в Асемберската програма ще откриете това което сте написали, за ралика от компилираната програма на който и да е език от “високо” ниво. А ако я “дебъгвате” ще се убедите в думите ми. Но стига хвалебствия и по същество.

ІІ. СТЪПКИ

1. КОМПИЛАТОР

Първата крачка, която трябва да направите е да си “харесате” компилатор.

FASM – Мой абсолютен фаворит! Това е съвременен асемблер с много опростен синтаксис, еднакво подходящ за начинаещи и напреднали. Синтаксисът и макро езика са внимателно обмисляни и въпреки че са изчистени, са много, много мощни. Определено се чувствува влиянието на TASM. Ако сте ползвали Delphi или TP, ще се чувствате много комфортно. Не изисква допълнителни инструменти, тъй като може да компилира директно до изпълним код (разбира се може да компилира и до обектен код, който да се свърже после с линкер) Има го в конзолен вариант за DOS, Windows, Linux и MenuetOS, а за Windows го има и във GUI вариант със вграден редактор – FASMW. В момента се разработва още един вариант – “Fresh”, който е всъщност “FASM със изградена около него среда за визуално програмиране” или пък “Визуална среда за програмиране с вграден FASM компилатор
FASM е напълно безплатен и с отворен код

MASM – Това е асемблерът на Майкрософт. Стар продукт и с традиции. Поддържа много конструкции, които са взаимствани от “C” така че програмите написани на него приличат повече на C отколкото на асемблер. Лицензът на Майкрософт допуска използването му (за версия 6.14) само за писане под Windows а по-новите версии – само за писане на драйвери под Windows. Към пакета, който може да си изтеглите от http://www.masm32.com е прикрепено нещо като IDE – с наименование Qeditor.exe, а и доста плъджини, с чиято помощ буквално за минута изграждате основен прозорец – частично обработващ почти всички съобщения изпращани от Windows с прикрепено основно, както и тулбар меню. Но ако разработвате сериозен проект се изисква писане на много допълнителен код – предпроцесни директиви и т.н.

NASM – Велик проект в миналото, сега е почти изоставен. Напоследък се чуват различни слухове, че развитието му ще продължи, но почти нищо реално. Поддържа идеята за много изчистен код, без почти никакви допълнителни допълнителни директиви. (т.н. Red tape) обаче в някои случаи такъв краен подход се отразява зле на четимостта на програмите.

TASM – Голям конкурент на MASM в миналото, сега е изоставен от Борланд/Инпрайз. Не поддържа съвременните процесори. Има два режима на работа – MASM съвместим и т.н. IDEAL – доста изчистен режим, който е повлиял на много от новите асемблери. Не го препоръчвам изобщо за писане под Windows. Още повече че в момента е невъзможно да се сдобиете с легално копие от него.

GOASM – Доста добър асемблер от ново поколение. За съжаление не е с отворен код. Безплатен е само за некомерсиално използване. Доста бърз, ако не използвате макроси. Макро системата му е доста зле написана и забавя компилацията много. Доколкото знам, авторът му активно работи върху този проблем.

2. IDE

Това са помощни програмни среди, които са създадени да ви спестяват времето при създаването на вашите програми. Най-известния продукт от този клас разбира се е Visual C++. Лично аз не съм им голям почитател и основанията за това са ми две – прекалено шарени са и за да разуча как се работи с него ми отива повече време отколкото да науча самия език, а програмата, която съм направил без да ги ползвам върши същата работа, само дето върви по-бързо. Но нямам желание да ви обременявам с моето мнение. Сравнението ще е просто:
Да не ползвате IDE е същото като пишете документ на Notepad вместо на Word. Така, че решете сами…
А ето и по-известните (разбира се и абсолютно безплатни):

RADAsmhttp://radasm.visualassembler.com
Всеобщото мнение е, че е най-добър. Може да се каже – можеш да го настроиш към всички познати компилатори дори и за “С”. Почти не се “дъни”, а и не е претрупан с менюта.

NAGOAhttp://www.visual-assembler.pt.vu
Според мен това е една блестяща идея. Но само толкова. Настройва се лесно. Поддържа всички познати компилатори но най-вече NASM. Към пакета са приложени много интересни plugins и “страхотен” help. Но само дотук – самата NAGOA забива понякога, а ако имате грешки в проекта линкера симулира вирус и компютъра забива. Но не се плашете – в действителност вирус няма, а Alink e добър линкер.

Negatory Assembly Studio 1.0 – Продукт на Nullsoft, Inc. И според мен най-доброто. Да работиш с него е елементарно. Позволява всякъкви настройки, но за съжеление е предназначен най-вече за MASM и TASM, което го поставя на трето място в моята класация.

WinAsm – Подобен е на RADAsm, но с по-малко заложени първоначални възможности. Разбира се можете да си го настроите по ваш избор.

Най-накрая ще спомена за проекта Fresh – IDE за Flat Assembler – все още недоработен.

ResHacker – Това не е IDE а Редактор на ресурси при компилирани програми. Страхотен продукт. Повече няма какво да кажа за него.

Определено това не са всичките IDE за Асемблер. Създадени са още – някои добри, други не чак толкова; някои платени други не, но на практика ползването им или не – зависи от вашия темперамент и проектите, които творите.

3. ДЕБЪГЕР, ДИЗАСЕМБЛЕР

Без тях наистина не можем. Колкото и да сме “перфектни” винаги се случва да сгрешим. Така, че се “воъражете” с дебъгер. А ги има много.
Лично аз ползвам – OllyDbg – един великолепен продукт: http://home.t-online.de/home/ollydbg
Ако все пак се чудите в каква последователност да напишете някой код може да се снабдите с Дизасемблер. Ще ви посоча два:
W32Dasm – страхотен 32 битов дизасемлер. За съжеление вече поостаряващ. Но дава почти пълна информация http://www.exetools.com/disassemblers.htm – там го има все още.
PEBrowse Pofesionalhttp://www.smidgeonsoft.com – Определено отговаря на името си. С негова помощ, а и с ваши познания можете да разкодирате почти всяка една програма. Добра визуализация, но липсват някои от възможностите на W32Dasm. Така, че двата се допълват идеално.

Ако се поровите из нета може да откриете и други програми, които могат да са ви от полза при създаването на ваши проекти. Но един съвет – не прекалявайте. Повечето създаден, уж помощен софтуер – дублира някои функции на гореспоменатите продукти – при това не така добре както при тях или не функционира добре.

4. РЕГИСТРИ

След като вече сте си избрали средствата, с които ще създавате и проверявате вашите програми можем да започваме да творим. Така, че да се запознаем накратко и със самия Асемблер.
Асемблера е символен език и е най-близък до този на процесора – т.е. всеки символ (оператор) отговаря на определен от производителите на процесори машинен код. А всеки процесор (засега) е разделен на регистри, които най-общо може да си ги представите като променливи с ограничено пространство. Променяйки тяхното състояние вие “заповядвате” (в зависимост от дадения от операционната система приоритет) – директно на процесора. Преди да ви запозная с тях ще ви спомена накратко с “байтовата” терминология:

byte – 1 байт, 8 бита
word – 2 байта, 16 бита
dword – 4 байта, 32 бита
fword – 6 байта, 48 бита
pword – 6 байта, 48 бита
qword – 8 байта, 64 бита
tword – 10 байта, 80 бита
dqword – 16 байта, 128 бита

Ето и техните наименования:

Генерални регистри – 32 битови (за данни):

EAX – Акумолатор. Основния I/O (входно изходен) регистър. Допълнително се използва при математически операции. Съдържа един 16 битов – AX регистър и два 8 битови – младши – AL и старши – AH регистри. Например ако
EAX = 1234 –> AX = 12, AL = 1, а AH = 2. Небходимо е да знаем, че
AL = byte [EAX] = byte [AX], AH = byte [EAX+1] = byte [AX+1], AX = word [EAX], откъдето е видно, че променяйки състоянието на AL променяме състоянието на EAX като цяло. Това е валидно за всички генерални регистри.
EBX – База. Разделя се на BX – 16 битов, BL и BH – 8 битови.
ECX – Брояч. Разделя се на CX – 16 битов, CL и CH – 8 битови
EDX – Регистър за данни. Разделя се на DX – 16 битов, DL и DH – 8 битови.

Адресни регистри – 32 битови:

ESI – Индексен регистър за оперенд. В него се съдържа SI – 16 битов.
EDI – Индексен регистър за резултат. DI е неговия 16 битов “брат”.
EBP – Указател на базата. BP – 16 битов.
ESP – Стеков брояч. SP – 16 битов

Флагов регистърEFL – 32 битов – в този регистър са разположени така наречените флагове – а това са местата, където се запомня резултата от последната математично-логична операция, а и не само. Те са еднобитови. Познаването на флаговете е много важно при програмирането под асемблер. Поради тази причина ще се спра подробно на тях в някоя от следващите статии – ако има интерес.
Разбира се процесора съдържа още регистри, чрез които се управляват копроцесора – FPU – 80 битови, MMX – 64 битови и SSE – 128 битови. Необходимо е да споменем още указателя на инструкции или EIP, където се записва последователноста на операциите.

– Сегменти – es, cs, ss, ds, fs, gs – Всичките сегмети са 16 битови и не се използват при програмирането под 32 битовите версии на Windows (напр. – 95, 98, ХР).

5. ОПЕРАТОРИ, ИНСТРУКЦИИ

Когато решим да учим програмиране си купуваме съответната “книжка” в която ни запознават със съответния програмен език. Обикновенно тези “книжки” са от 300-400 станици на том (ако са по-малки по обем почти нищо няма да научите) и преди да преполовите първия том с инструкции, оператори и методики вече сте забравили (освен ако не сте гений) за какво става на въпрос и трябва да започнете да четете отначало. А в края на втори том с “големи” букви прочитате: “В тази книга ви даваме само основата на езика…” и ако все още имате нерви, желание или необходимост – тичате до книжарницата да си купите следващите два тома – този път за напреднали.
ПРИ АСЕМБЛЕР ТАКОВА НЕЩО НЯМА. Всичките оператори заедно с подробните им описания заемат 60 – 70 страници. А на брой, дори и с добавените в по-ново време SSE2 инструкции са под 300.
ТУКА ВИЕ СТЕ ТВОРЕЦА. Защо да обявявате функция чрез def или void ако това не ви се нрави…(вижте примера protoolsbul.asm, където почти всички оператори, константи, API фунции са с български наименования)
Но както и да е. Все пак и тук има правила. Така, че на следващите няколко реда ще ви запозная със структората на една асемблерска програма под Windows, както и с основните асемблерски инструкции използвани в примерната програма дадена в следващата част на статията.
Както споменах в уводната част, Windows една операционна система – подобна на DOS, така че както DOS така и Windows разполагат с набор от функци с определено предназначение. Например досовата команда
“cd <директория>“ е равнозначна на Windows функцията “SetCurrentDirectory <път към директория>”, с тази разлика, че Windows функциите са достъпни предимно на програмно ниво, въпреки че е възможно да ги стартирате (не всички) и директно. Всички функции на Windows са базирани в “dll” файлове намиращи се в системната Windows директория – наименувани най-общо, от създателите си – Windows Application Programming Interface или за краткост – WinAPI. Тези функции включват – работата в графична среда, рисуването на прозорци, менюта, икони, обработката на файлове, ресурси, осъществяват връзката с периферията и разрешават много други задачи. Но най-важното, което трябва да знаем, е че резултата (ако е предвидено да има такъв) от изпълнението на коя и да е WinAPI функция се запомня в генералния входно-изходен регистър – EAX.
Предполагам ще си зададете въпроса – Какво става в другите регистри – може да не съм прав, но моето мнение е – “боза с прифкус на съответната WinAPI функция” (изключение правят Microsoft C Runtime Library като msvcrt.dll). Така, че не се учудвайте на големия обем на компилираните програми написани във Visual BASIC или DELPHI програми, чийто компилатор коректно създава код – съхраняващ това което постоянно променяте. Но тъй като става дума за Асемблер, сами трябва да избирате кое е важно и кое не е. Ето защо няма да е лошо да знаем – как се ползват регистрите при програмиране на Асемблер.
Доста “умувах” как да синтезирам тази материя в няколко реда. Определено не става. Затова реших да ви препоръчам книжката “АСЕМБЛЕР Справочник” от Оливер Мюлер, където това е описано в 5-6 страници, при това доста добре, въпреки че основно се набляга върху TASM – един “поостарял” асемблер, но принципите при ползването на регистрите са непроменени и до сега.
За тези от вас, които са в час с английския – Iczelion tutorial е също едно добро помощно средство – http://win32asm.cjb.net
Тези статии, а и много примери можете да намерите и на руски на – http://www.wasm.ru
Но всъщност, подробното знание върху тази материя би ви била абсолютно необходима, ако гоните скорост при изпълнение на програмата си или програмирате под DOS, но програмистите от Microsoft, а и не само те, са написали толкоз много API функции (само в kernel32.dll са над 900), че е достатъчно само да ги извикате, да вземете резултата от EAX и да го обработите с друга API функция.

Структора на асемблерските програми

Ето как изглежда програмната структора при Flat Assembler:

Код:


; символът “;” се използва за коментар

; ПРЕДПРОЦЕС – в тази част се указват директивите, структорите, константите, 
; макроконструкциите и друга необходима на компилатора информация.

; Например:
format PE GUI 4.0 
; дерективата format указва на компилатора какво да запише в досовата и файловата секции 
; на изпълнимата програма. В случая тя се следва от PE – става въпрос за portable executable
; и GUI 4.0 – указваме субсистема GUI и версията на Wndows – в случая “98”.

include '%fasminc%/win32a.inc'
; директивата include следвана от име на файл зарежда и компилира асемблерски файл
; от диска. 
; В случая това е win32a.inc – файл чрез който се декларират основните константи, 
; структори и помощни макродирективи използвани при програмирането под Windows

Number = 12
My_Number = Number + My_Varyable
; това са константи

struc my_struct
{
  .променлива
  ………………
  .данни
  ………………
}
struct my_struct

; struc е макродиректива, чрез която обособявате структори(като при С++), 
; чрез която обособявате разнообразни типове променливи и данни
; достъпа към тях от вашата програма става чрез my_struct.променлива

macro my_macro arg1,arg2
{
   ; код
   ……
}

; в случая се декларира макродирективата my_macro arg1,arg2
; когато във вашата програма изпишете my_macro arg1,arg2 компилатора записва в 
; изпълнимия файл кода намиращ  се между скобите. 
; В manual-а на Flat Assembler подробно са описани макродирективите и макрологиката


; С това може да се каже, че предпроцеса е завършен.
; Следва вашата програма. За по-стабилната й работа е необходимо да е разделите на секции.
; Например:

; ПРОЦЕС
section '.code' code readable executable ;наименованието в кавичките е по ваш избор
entry start
; дерективата entry следвана от наименование по ваш избор – в случая start указва на 
; компилатора – входната точка на вашата програма.
; Тази директива може да се укаже и в предпроцесните обяви.

start:
        код
        код
        …..
        код

section '.data' data readable writeable
; Това е мястото за променливите и данните.

Seria db 0,0,78,105,99,107,32,80,46 ; серия числа приравнени към Seria
Bool  rb 1 ; заделя 1 байт за променливата Bool 
Str01 db ‘test.bin’,0 ; дава стойност ‘test.bin’ на Str01
Prom dd ? ; променлива Prom – 4 байтова
Prom2 rd 4 ; резервира 4 "елемента" към Prom2, всеки един с размер 4 байта

section '.idata' import data readable writeable
; В тази секция посочвате библиотеките и API функциите използвани в програмата ви.
; Например:

library kernel32,'KERNEL32.DLL', ; символът “” означава, че текста изписан на 
             user32,'USER32.DLL' ,          ; следващия ред се отнася към предния ред.
             mydll, ‘MYDLL.DLL’

import kernel32,
             ExitProcess, ‘ExitProcess’

import user32,
             MessageBox, ‘MessageBoxA’

import mydll,
             Function1, ‘Function1’,
             Function2, ‘Function2’

section '.rsrc' resource data readable
; В тази секция се намира информацията за версията, иконката, прозорците и др., които се
; ползват от вашата програма

Асемблерски инструкции:

В примерната програма дадена в следващата част съм използвал 11 асемблерски инструкции. А те са:
mov, push, pop, call, ret, xor, test, cmp, jmp, je, и jnz.
Какво вършат:
Инстукцията mov (move) означава копиране, прехвърляне. Правилното изписване (мнемоничен код) е
mov цел, източник. Действието, което се извършва е копиране на източник в цел. Това е една от най-често използваните инструкции. Единственото условие е цел и източник да са от един и същ тип. Ако са от различен тип – например цел – 32 битова, а източник – 8 битова трябва да се уеднаквят по следния начин в зависимост от нуждите ни – mov цел, dword [източник] или mov byte[цел], източник.
Инструкциите push и pop сътветно са въвеждане и извеждане в/от стека, call е извикване на подпрограма, а ret (от return) е излизане от подпрограма. Правилния им синтаксис е:
push източник, pop източник, call подпрограма, ret или ret директна_стойност.
Всяка подпрограма, извикана от главната програма, се нуждае от регистър в процесора. Тъй като през процесора преминава огромно количество информация с променливо съдържание е невъзможно да се отдели място (стек) в малкото му памет. Затова се заделя в RAM паметта. За тяхното съхраняване се използва push, а за обратното им повикване се използва pop.
Например:

Код:


подпрограма:
     push аргумент
      код
      …..
      код
      pop аргумент
      ret

Код:


call подпрограма

извежда резултат от изпълнението на подпрограма при стойност аргумент.
Ако изпишем:

Код:


push var
инстукция променяща var
инструкция променяща var
……
pop var

стойноста на var, след pop var ще е тази от момента – push var.
Инструкцията xor (exclusive or) означава изключващо ИЛИ. Мнемоничния код е
xor цел,източник. Отговаря на булевото xor, като резултата се записва в цел. Влияе на флаговете.
Инструкциите test, cmp са инструкции за сравнение и се следват от инструкции за преход, като je (jump if equal), и jnz (jump if not zero).
jmp (от jump) е инструкция за безусловен преход. Инструкцията test се различава от cmp с това, че влияе на флаговете. Мнемоничния им код е:

Код:


….
test оперенд1 , оперенд2 ; сравни оперендите и установи флага – 0 при равенство
jnz етикет1 ; ако флага не е 0 продължи от етикет1 
код
….
код
jmp етикет2 ; продължи от етикет2
етикет1:
    код
    …..
    код
етикет2:
код
…..

или

cmp оперенд1 , оперенд2 ; сравни оперендите
je етикет1 ; ако са равни продължи от етикет1
код
….
код
jmp етикет2 ; продължи от етикет2
етикет1:
    код
    …..
    код
етикет2:
код
…..

В заключение ще спомена още един път – Асемберът е символен език и позволява добавяне на нова символика.
Например ако не ви харесва символът jmp, а предпочитате да е goto – в предпроцеса е достатъчно да изпишете:
[cpp]goto equ jmp[/cpp] и във вашата програма, навсякъде където ви е необходим директен преход може да изписвате

Код:


goto етикет

6 . ПРИМЕРНА ПРОГРАМИ

За да ви спестя някои главоблъскания, реших да ви посоча всичките стъпки, които е необходимо да направите, преди да започнете да програмирате под асемблер или по-точно под Flat Assembler.
Най-напред е необходимо да си свалите програмния пакет от http://www.flatassembler.net/
В момента на публикуване на тази статия – пакета е с наименование fasmw152.zip.
Разархивирайте архива в новосъздадена папка – c:fasmw. След разархивирането влезте в директорията. Там ще откриете четири файла – Fasm.PDF; Fasmw.EXE; Fasmw.INI и License.TXT, както и други три директории – Examples; Include и Source.
Следващата стъпка е настройката на редактора. За целта е необходимо да отворите файла Fasmw.INI – например с NOTEPAD и в края на файла да изпишете следното:

Код:


[Environment]
Fasminc = c:fasmwinclude

Запишете и затворете Fasmw.INI. След това стартирайте Fasmw.EXE.
Фонта, цветовото оформление на текста се настройват от Option/Appearance. Компилатора се настойва от Option/Compiler Setup – настроен е за средно големи проекти. Помощта се настройва от Help/Pick help file. Достъпни са само *.hlp файлове, а помощ получавате при натискане на F1. Ако искате да получите помощ за точно определен дума – просто я маркирайте и натиснете F1. Например ако от програмния текст:
[cpp]invoke ExitProcess,0[/cpp] маркираме “ExitProcess”, след като натиснем F1 ще получим помощ за тази WinAPI функция при условие, че като *.hlp сме посочили WinAPI.hlp. Програмите се компилират чрез Ctrl-F9 или от Run/Compile, а се компилират и стартират при натискане на F9 или Run/Run от менюто. Върху работата с редактора няма да се спирам – подобна е на NOTEPAD, като съществува възможност за вертикално селектиране на текста.
Какво трябва още да знаем:
Fasmw.EXE не бърка в настройките на Windows, така че ако желаете да отваряте всички *.asm и *.inc с този редактор е необходимо да укажете това от Toolbars менюто на Windows или
Start/Settings/Folder Option…/File Types/New Type…

Смятам, че със следващия пример ще разберете – най-общо, как на практика се програмира на Flat Assembler под Windows. За целта написах програмка – визуализираща и прекратяваща Windows процесите – малка и доста полезна за тези които ползват Windows 98, а и не само…

Архива с приложения код може да си изтеглите от: http://www.WinAPIandAssembler.hit.bg/code/ProTools.zip

Изтеглете го и го разпакетирайте в директорията, където е FlatAssembler.
Protools.asm – кода е с предимно асемблерски инструкции, описани в предходната част;
Protools1.asm – в кода са използвани макродерективите .if, .elseif, .else, .repeat, .until
Protoolsbul.asm – копие на Protools1.asm, с тази разлика, че всички макродерективи, оператори, API функции и някои символики са с наименования на български.

ІІІ. КРАЙ

Е… Това е всичко – поне за сега. 😉

❗ При желание за публикуване на тази статия, или части от нея – се свържете първо с автора й:
на e-mail gergina@abv.bg:!:

ДОБАВИ КОМЕНТАР

Коментирай това преди всички други

Извести ме за
avatar
wpDiscuz