Asembler Dili (Assembly Language) 4
Mantiksal komutlarin
kombine kullanilimi
Buraya kadar anlattigim mantiksal komutlari beraber ve kombine olarak
kullanabiliriz. Sayisal olarak verdigimiz CL register degeri biz bir degisiklik
yapmadigimiz süre ayni kalir ve birden fazla komutta kullanabiliriz. Yine
kaydirma ve çevirme komutlarini beraber kullanabiliriz. Bu sayede ortaya hem degisik hesaplamalar
hemde kisa yoldan güçlü algoritmalar çikabilir.
Daha hizli oldugu ve yer kapladigi opkod sayi daha az oldugu için register
temizlemelerinde veya atlama komutlarinda da karsilastirici olarak
kullanabiliriz.
Örnegin bir registerin
degerini 0 yapmak için XOR REG,REG dememiz yeterli. Buradaki REG olarak
belirttigim sembollerin ikiside mutlaka ayni register olmak zorunda. Register
kendi kendine XOR islemine tabi tuttugu için bütün bitleri reset olacaktir.Ayni
sekilde bir döngü içinde
sayinin yada döngünün bitip bitmedigini (0 olmasini) kontrol için OR REG,REG
komutunu kullanabiliriz. Iki yöntemide asagidaki örnekte kullandim.
mov eax,12345678
xor ebx,ebx
dongu:
shr eax,1
inc ebx
or eax,eax
jnz dongu
Ayni sekilde 32 bitlik bir sayida istedigimiz 16 bitlik yada 8 bitlik bölümü resetleyebiliriz.
Bunuda AND komutu ile yapiyoruz.
mov eax,12345678h ; eax = 12345678
and eax,0ffff0000h ; eax = 12340000
yada tam tersi diger 16 bitlik blok için
mov eax,12345678h ; eax = 12345678h
and eax,0000ffffh ; eax = 00005678h
NOP Komutu
NOP komutu islem yok komutudur. Bu komut normalde herhangi bir islem yapmasada
donanim bazinda hafizayi yenileme gibi küçük bir islevi vardir. Hiçbir
register, bayrak yada adres bu komuttan etkilenmez.
CLI - STI Komutlari
Bu komutlar gerçek modda interruptlarin açilip kapanmasindan önemli bir role
sahiptirler. EFLAG içerisinden Interrupt bayragi sisteme interruptlar ile
ilgili kesmeleri ve maskelemeler bildirir. Eger bir kesmeyi ele geçireceksek
(hook) ve bu islemler herhangi bir kesme yada debug olayina maruz kalmamasi
gerekiyorsa(genel olarak böyledir) ilk olarak CLI komutu çalistirilmali ve
gerekli kritik islem yapildiktan sonra STI ile eski haline (kesmeler açik)
döndürülmeli. Normalde korumali modda dolayisiyla windows altinda bir islevi
yoktur.
INC - DEC komutlari
INC ve DEC komutlari registerleri, adresleri, yada degerleri azaltmak -
arttirmak içindir. Bu komutlar 8,16 ve 32 bit adreslenebilmekte ve bu bit
degerlerinde azaltma- arttirma islemleri yapabilmektedir. Her iki komutta her
çalismasinda sadece 1 bytelik azaltma veya arttirma yapmaktadir. Daha fazla
adimlarda azaltma-arttirma yapmak için ADD ve SUB komutlarini kullanmaliyiz.
Genel kullanilis sekilleri söyledir:
inc byte ptr [402300h] ; 402300h adresindeki degeri +1 arttirir
dec byte ptr [sayi] ; sayi isimli hafiza bölgesini -1 düsürür.
inc eax ; EAX registeri +1 arttirir
dec cl ; CL registeri -1 düsürür
inc dword ptr [label1] ; label1 isimli bölgeyi dword (32bit) cinsinden arttirir
dec dword ptr [sonuc]
; Sonuç isimli hazifa adresini dword (32bit) cinsinden düsürür.
Burada direkt hafiza adresi adresleniyorsa ve belirtigimiz yer istedigimiz bit
cinsinde degilse program derlenirken hata verir ve dosyayi olusturmaz. Eger
DEBUG gibi bir programla direkt kod olarak yaziyorsak ne kadarlik deger adreslediysek o kadar bit
üzerinde islem olur.
DEC ve INC komutlari olusturduklari yeni degerlere göre bayrak registeri
etkilerler. Bu sayede degerleri ve islemleri kontrol altinda tutabiliriz.
Örnegin:
mov eax,1000
dongu1:
dec eax
jnz dongu1
Burada karsilastirma komutu kullanmadan bir DEC EAX komutunun hemen ardindan
bir kontrol olusturabiliyoruz. Böylece EAX = 0 oluncaya kadar döngü devam
ediyor.
Baska bir örnek:
xor ebx,ebx
dongu1:
inc ebx
cmp ebx,1234
jnz dongu1
Burada da arttirilan EBX register degerini kontrol ediyoruz ve özel olarak
bekledigimiz 1234 degerine ulasincaya kadar arttirma yapiyoruz. EBX = 1234
olunca döngü bitiyor ve program jnz dongu1 komutundan sonraya devam ediyor.
IN / OUT Komutlari
Önceki konularda kisaca
bahsettigim "portlar" sistemin kaynaklarina ulasmanin en son
noktasidir. Örnegin sistemimizde takili olan bir ekran karti donanim olarak bir
ID (isaret) içerir. Bu isareti bulan donanim bu kartin kaynaklarini sisteme
açar ve ilgili bölüme
portlar tahsis eder. Böylece sistem ekran kartina bir bilgi gönderecegi zaman
bu portlari kullanir. Portlar sistem ile sistem birimleri arasindaki haberlesme
yollaridir. Bu yollar CPU tarafindan kullaniciya açilmistir. Bunun olmasi
mecburidir çünkü isletim
sistemleri ve ROM bu portlar ile haberlesmeyi saglarlar.
CPU bize 8,16 ve 32
bitlik port haberlesmelerine olanak saglamistir. Bu haberlesme hem okuma hemde
yazma yönlüdür. Bütün portlarin status denilen öndegerlerin belirtildigi bir
status portu vardir. Örnegin diskin bir sektörünü okumamiz gerektiginde önce
hangi sürücü, sectör,track gibi bilgileri vermemiz gerekir. Sonra okuma islemi
için IN komutunu kullanabiliriz.
Gerçek modda portlar
okuma ve yazma için serbertçe kullanildigi halde Korumali modda bu ancak en
yüksek hassasiyet (ring0) ortaminda mümkün olmaktadir. Bu ortami VXD ile veya
w9x tabanli sistemlerde kisitlida olsa DLL ile olusturabiliriz. Gerçek modda
sistem programlamasi mümkün hale gelmesi tamamen portlarin kosulsuz
çalisabilmesindendir. Örnegin bir DOS tabanli
program bütün islemlerini portlar ve direkt adresleme ile yapabilir. Yazilari
VideoRom'a yazabilir ve sesleri portlar ile çikarabilir.
IN komutu, sistem ile
haberlesmede 'al' komutudur. Bu komut ile port okunur. Kullanilisi : IN AL,DX
gibidir. Burada DX register port numarasini ve AL register ise okunan degerin
yüklenecegi registeri gösterir. Fakat A ve DX register sabittir. Yani
biz IN BL,CX diyemeyiz. IN/OUT komutlari için A ve DX registerler ayrilmis
ve asil kullanilandirlar.
IN komutu ile 8,16 ve 32
bitlik okuma yapabiliriz. Bunun için IN AL,DX örnegindeki AL registerin
degerini 16 ve 32 bitlik register degerleri olarak arttiririz. 16 bitlik okuma
için INSW AX,DX ve 32 bitlik okuma için INSD
EAX,DX seklinde kullaniriz. Burada dikkat ederseniz DX hiçbir zaman
degismez. DX register okunacak portun adresini verir ve bu deger 16 bitten
(65535) büyük olamaz. Zaten su anda kullandigimiz sistemler bunun binde birini
bile kullanmiyorlar.
OUT komutu, sistem ile
haberlesmede 'gönder' komutudur. OUT komutu ile DX register ile belirttigimiz
porta 8,16 veya 32 bitlik bir deger gönderebiliriz. Aynen IN komutunda oldugu
gibi A ve DX registerler asil olarak kullanilir ve degismez. Yine OUT komutu
port adresi olarak en büyük 16 bit bir deger kullanabilir. OUTSW 16 bit
gönderme ve OUTSD 32 bitlik bir gönderme yapar.
Ayrica hem IN hemde OUT
komutunu DX register olmadan port degeri vererek de islem yapmak mümkündür.
Örnegin : IN AL,03f8h gibi.
BSWAP komutu
Bu komut sadece 32 bit
olarak çalisir.Bu komut birkaç komutla yapilabilecek bir islevi tek basina ve
hizli olarak yerine getirir. Görevi ise bir registerin bitlerini tersden yer
degistirmektir. Örnegin:
mov eax,001110110b
eax = 001110110b
bswap eax
<---eax--->
eax = 011011100b
Yukarida örnekte görüldügü
gibi bitler bastan sona yerdegisir. Bu komut ASCII den onaltilik yada onluk
degerlere dönüsümlerde çok kullanilir. Ayrica lojik islemlerde vazgeçilmezdir.
CBW ve CBWE komutlari:
Bu komutlar yine bswap
komutu optimize için önemli bir komuttur.A register asil kullanilandir ve amaç
A register üzerinde islem yapmaktir. CBW komutu 16 bitlik degeri 8 bitlik
degere dönüstürür. Elimizde eger 0aa55h gibi bir deger varsa CBW komutundan
sonra registerde sadece 055h degeri kalir. 0aah CBW komutu tarafindan
resetlenir. Ayni sekilde CBWE komutuda registerin yüksek bitini resetleyerek
elimzde 16 bitlik bir deger olmasini saglar.
Örnegin :
mov
ax,0aa55h ax = 0aa55h
cbw
ax = 0055h
CBSW komutunu örneklemek gerekirse:
mov
eax,12345678h ax = 12345678h
cwde
ax = 00005678h
olacaktir.
CDW ve CDQ komutlari
Bu komutlarda CWD ve CBSW
gibi ancak islemleri tam tersdir. Asil olarak AX ve DX registeri alirlar. CWD
komutu 16 bitlik bir degeri 32 bit degerini çikarir. Ancak bu deger ayarlamasi
DX registerde olur. Burada önemli bir konu ise negatif sayilar ve pozitif
sayilardir. Normalde 7fffh kadar olan sayilar negatif sayilir ve DX registeri
bütün bitleri reset (0) olacaktir. 8000h ile 0FFFFh arasi ise pozitif sayilar
olarak kabul görür ve DX registerin bitleri set (1) olacaktir. Örnegin:
mov
ax,0aa55h ax = 0aa55h, dx = 0000
cdw
ax = 0aa55h, dx = 0FFFFh
CDQ komutu ise ayni CDW
komutu gibi genisletme islemi yapar. Bu genisletme 32 bitten 64 bite dogru olur
ve yine CDW komutu gibi sonuç EDX register üzerinde olur.
mov
eax,12345678h eax = 12345678h, edx =
0
cdq
eax = 12345678h, edx = 0 (negatif oldugu için, pozitif olsa 0fffffffh
olacakti)
CLC, STC, CLD, STD
komutlari
Bu komutlar tamamen Carry
flag (bayrak) üzerinde etkilidir. CLC komutu C bayragini 0 (reset) yapar. Bu
sayede atlama komutlari istenilen yere atlamayi saglayabilir. Ayni sekilde STC
komutuda C bayragini 1 (set) yapar.
Bu iki komut prosedürler
için anahtar olarak kullanilabilir. Örnegin :
call
proc1
jc jumpproc1
.....
proc1:
mov al,[data1]
cmp al,0ffh
je setcarry
stc
ret
setcarry:
clc
ret
Burada call proc1 ile
program alt dizini çagriliyor. Hatirlarsaniz bu komut ret komutu ile
kendisinden sonra geri döner ve islemine devam ederdi. Proc1' de data1 isimli
adresten al register bir deger yükleniyor. Ancak deger herhangi bir deger
olabilir. Eger yüklenen deger 0FF ise setcarry isimli yere atlama yapar ve C
bayragini resetler. Eger degilse C bayragi set olacaktir. Durum ne olursa olsun
sonlarindaki RET komutlari çagrildiklari yerden sonraya yani JC JUMPPROC1
adresine dönecektir. Bu atlama komutuda C bayraginin durumu test edecek
ve set ise atlama islemini gerçeklestirecektir.
CLD ve STD komutlari ise Direction Flag (yön
bayragini) sifirlar yada 1 yapar. D bayragi string islemlerinde islem yönünü
tayin eder. Daha önceden anlattigim SCAS,MOVS gibi blok islem komutlarinda SI
ve DI registerlerin yönünü degistirir. Yani normalde D bayragi reset ise
verdigimiz adresten sonraya (ileriye) dogru islem yapar. Ancak D bayragi set
ise geriye dogru islem yapar.
CLI ve STI
komutlari
Bu komutlar I bayragi (
interrupt = kesme bayragi) üzerinde set veya reset islemi yapar. Bu komutlar
kritik islemlerde interrupt çagrimlarini kapatip-açmak için kullanilir.
Dolayisiyla bu iki komut gerçek modda daha fazla kullanim alani bulur.
Örnegin bir TSR yapmak için interrupt degerini alip yerine kendi adresimizi
koymak isteriz. Bu islem esnasinda olusabilecek en küçük kesme çagrimi sistemin
kilitlenmesine ve veri kaybina neden olabilir. Bunun için önce CLI komutu ile
kesme bayragi kapatilir. Isimizi bitince tekrar STI komutu ile bayragi açariz.
Bu komutlar kullanilmadan TSR vektör alimi (hook) yapilamaz mi ? Yapilabilir
ancak her zaman risk tasir. En güvenli yol kesme bayragini açip kapatmaktir.
cli
xor ax,ax
mov ds,eax
mov ax,[21*4]
mov bx,[[21*4+2]
mov word ptr [21*4],myproc
mov word ptr [21*4+2],cs
sti
INT ve INTO, IRET ve
SIGT Komutları
INT komutu daha öncede
bahsettiğim gibi işletim sisteminin yada sistemin kesme tablosundan bir
kesmeyi (interrupt) çağırmayı sağlar. Kesmeler sistem kesmeleri, işletim
sistemi kesmeleri ve boş kesmeler olarak ayrılırlar. Bu kesmelerde dinamik ve
statik olarak ikiye ayrılırlar. Dinamik kesmeler sürekli yada belli zaman
aralıklarıyla çalşırlar. Statik kesmeler ya işlem olunca yada hiç çalışmazlar
Boş kesmeler kullanıcılara ayrılmıştır ve statiktirler. Dinamik kesmelere örnek
olarak 08h kesmesini verebiliriz. Bu sistem zamanlayıcısı (system timer)
kesmesidir ve saniyede 18.2 kez gibi bir periyodu vardır.
Kesme tablosu 0:0
segmentinde başlar var segment:offset mantığı ile çalışır. Yani ilk olarak
kesmenin segmenti ve sonrada adresi yer alır. Korumalı modda ise "kesme
betimleyici tablo" olarak değişmiştir.
![]()
(Yukarıda gerçek modda
interrupt tablosunu görüyorsunuz. Int1'in vectörü 025f:1528
)
Bir kesmenin vektörlerini bulmak için kesme sayısı*4 işlemini yapabiliriz.
Burada 4 olmasının nedeni segment:offset ikisinin herbirinin 16 bitlik değerler
olmasındandır. Kesme vektörlerini değiştirebiliriz. Ancak mutlaka geriçağrım
(callback) olmak zorunda. Aksi takdirde sistem dönüşümü sağlanamadığı için
kilitlenme ve hatalara neden olacaktır.
INT komutlarının seg:off
adreslerinde işleme başladıktan sonra daima IRET komutu ile geriye döner.
Bu komut kesme dönüşümü için tasarlanmış bir RET komutunun türevidir. Aynı
şekilde korumalı modda IRETD komutu geri dönüşüm sağlar.
INTO komutu ise taşma kesmesini çağırmak için
tahsis edilmiştir. Taşma durumunu yani overflow durumu bayrak registerde
kontrol altındadır ve sistem 4 numaralı kesmeyi bu bayrak için tahsis etmiştir.
Özel kesmelerin başında INT 3 kesmesi gelir. Bu kesme CPU' nun vermiş olduğu
"debug" özelliğine yardımcı olarak "kesme noktası"
(breakpoint) özelliğini işletir. Diğer kesme numaraları INT komutu ile iki byte
uzunluğunda iken INT 3 komutu sadece 1 byte uzunluğundadır.
Debug işlemi işlemcinin desteklediği bir özelliktir. Bu özellik Kapan bayrak
(Trap flag) ile aktif hale geçer ve bu bayrak set (1) olduktan sonra
işlemci hem makine komutunda INT 1 kesmesini çağırır. Bu sayede hem registerler
hem komutlar tek tek simule edilebilir.
Windows ortamında ise
Windows debug için yukarıda bahsettiğim temel işlevi yapacak API fonksiyonları
vermiştir. Öncelikle var olan yada yeni oluşturulacak bir process (oluşum,
uygulama) DEBUG_ONLY ve DEBUG_ONLY_THIS_PROCESS şeklinde oluşturulur.Bu oluşumu
CreateProcess ile gerçekleştirebiliriz. WaitForDebugEvent isimli
API fonksiyonu ile olaylar akışına göre kontrol edilir. GetThreadContext ve
SetThreadContext ile o andaki registerler, IP noktası, eflaglar
alınabilir. WaitForDebugEvent sonrasi uygulama hafızaya yüklenmiş ve çalışmaya
hazır ise uygulamanın EFLAG bayrağının TR (Kapan = trap) set ederiz ve uygulama
hem komuttan sonra single-step exception üretir.
Kısaca :
CreateProcess
DEBUG_ONLY,DEBUG_ONLY_THIS_PROCESS
WaitForDebugEvent
.EXCEPTION_DEBUG_EVENT
GetThreadContext, EFLAG
or [eflag],TR
SetThreadContext
...
.EXCEPTIONS_ SINGLE_STEP
GetThreadContext
ReadProcessMemory
...
ContinueDebugEvent
şeklinde
tablolandırabiliriz.
Önemli kesmelerden bazıları:
08h - Sistem Timer kesmesi
10h - Video kesmesi
13h - Disk işlemleri
16h - Klavye işlemleri ve flash bios
21h - Dos fonksiyonları kesmesi
25h - Disk okuma
26h - Disk yazma
33h - Mouse işlemleri
SIGT komutu ise korumalı modda kesme betimleyici tabloyunun
yerini öğrenmeye yarar. Bu sayede DOS ' da olduğu gibi kesme tablosuna
ulaşırız. Yalnız bu tabloda vectörler 4 byte uzunluğunda değildir.32+16 bit
yani 48 bit uzunluğundadır. 48 bitlik bir değeri fword ile gösterebiliyoruz. Bu
tabloda vektörler taban adresi(32bit)+limit (16bit) şeklinde oluşur.
AAA, AAD, AAM, ASD komutları
Bu komutlar matematiksel
toplama, çıkarma, çarpma ve bölme işleminden sonra ondalıktan taşan kısmı
ayırırlar. Bu ASCII ayarlamasıdır ve görevini "ASCII MOD ondalık
olarak" şeklinde özetleyebiliriz. Örneğin AAA komutu:
xor
eax,eax eax = 0
mov al,0a eax = 0ah
aaa
eax = 0109h ( 0ah-9 = 1 = ah)
AAS komutu ise AAA' nın tam tersidir. Çıkarma işlemi sonucunda ondalık
mod olarak düzeltme yapmadır. Yukarıdaki işlemde AAA yenine AAS kullanırsak
sonuç eax =FF04h olacaktır.
AAM komutu ise çarpım işlemi sonunda ondalık düzeltme
yapılmasıdır. Yukarıda örnekde AAA yerine AAM olursa sonuç 0100h
olacaktır. Çünkü 0ah içinde 1 tane 9 var.
AAD komutu ise bölme işlemi sonunda ondalık düzeltme yapılmasıdır.
HLT ve WAIT komutu
Bu komutlar normal
programlarda pek görülmezler. Çünkü işletim sisteminin programlanmasıyla ilgilidirler.
HLT komutu Intel tarafından işlemciyi durdurur şeklinde açıklanmıştır. Ancak
Windows gibi çok işlemcili bir işletim sisteminde işlemciyi durdurmak sözkonusu
değildir. Gerçek modda maskeleme işlemi yapar. Wait komutuda bekleme işlemi
yapmaya yarar ve yine işletim sistemi programlanması ile ilgilidir. Normalde bu
iki komut sistem açılışı esnasında gerekli yerlerde kullanılırlar ancak sonra
pek kullanım durumu sözkonusu olmaz.
CPUID, RTSTC
komutları ve Random
CPUID komutu işlemcinin
türünü, cache( önbellek) uzunluğunu, modelini öğrenmek için CPUID komutunu
kullanırız. Bu komut bize eax registerin 3 değerinde ayrı ayrı bilgiler verir.
Çıkış bilgilerini EBX,ECX,EDX registerlere yükler. Bunlar yazı olabileceği gibi
bit seviyesinde de olabilir. Örneğin Intel bir işlemci için:
mov eax,0
cpuid
mov [data1],ebx ; 'Genu'
mov [data2],ecx ; 'ntel'
mov [data3] ,edx ; 'inel'
Eğer EAX register 1 olursa
CPUID komutu bize yine EAX ve EDX register ile versiyonu hakkında bir bilgi
verecektir.
Eğer EAX register 2 olursa
CPUID komutu bize EAX,EBX,ECX,EDX registerler ile biter bazında cache ve CPU
bilgisi verecektir. Bunların tam listesini Intel dökümanlarından yada ilgili
yardım dosyalarından bulabilirsiniz.
RDSTC komutu CPU sayacını
okur. Bilgisayar çalışmaya başladığı andan itibaren saymayan başlayan sayaçlar
vardır. Bunlar gerek işletim sistemi içerisinde gerekse RDSTC gibi
sistem içerisindedir. RDSTC doğal bir random (rasgele sayı üretici)
denilebilir. CPU' nun hızına bağlı olarak çalışır ve farklı işlemcilerde
farklı değerler gözlenir. RDSTC dinamik bir sayaçtır. Dolayısıyla rasgele sayı
üreten prosedürler içinde kullanılanır. EAX:EDX registerler ile 64 bitlik bir
sonuca ulaşılabilir.
Şimdide randomlar yani
rasgele üretilen sayılar konusuna değinmek istiyorum. Bu konu pek çok alanda
önemli bir yer tutar. Maalesef bu konuda Türkçe olarak dile getirilen fikirler
ya bir yerden alıntı olarak karşımıza çıkıyor yada iyice araştırılmadan
söyleniliyor.
Randomlar (rasgele sayı
üreticileri) dinamik random ve statik random olarak ikiye
ayrılır. Dinamik randomlar her çağrımda ve çalıştırmada farklı farklı sayılar
üretir ve ortaya "tekrar edilmesi imkansız" bir sayı rutini çıkar. Bu
random prosedürlerinin çekirdeği RDSTC gibi rasgele sayı üreten bir komuttan
yada GetTickCount gibi bir API fonksiyonundan oluşur.
Statik randomlar ise
kullanıcının verdiği sayı ile matematiksel ve lojik işlemler yaparak istenilen
değerde modlar yani sınırlar. Bunlar tam olarak gerçek randomlar değildir.
Çünkü her defasında aynı sabit sayı girişi olduğu için ve aynı işlemler
yapıldığı için aynı sayılar oluşur. Dolayısıyla rasgelelik olayı sahtedir.
Ancak bu randomlar demolar - tanıtımlar gibi sabit animasyon, efekt yapan
uygulamalardan kullanılır. Böylece istenilen sahnelerin veya olayların
gerçekleşmesi sağlanır.
RDSTC ile işlemci hızını
bulmaya yarayan örnek:
;------------------------------------------------------------------------------
.data
capt db 'CPU Test',0
format db 'CPU Speed = %lu Mhz',0
buffer db 30 dup (0)
.code
main:
xor eax,eax
db 0fh,31h ;rdtsc
mov ebx,eax
call Sleep, 1000
db 0fh,31h ;rdtsc
sub eax,ebx
sub eax,8
xor edx,edx
mov ecx,1000000
div ecx
call _wsprintfA, offset buffer, offset format, eax
call MessageBoxA, 0 ,offset buffer, offset capt, 0
call ExitProcess , 0
;------------------------------------------------------------------------------
Kısaca program ilk olarak o andaki CPU sayacını alıyor. Sonra 1.000ms
(1 sn) bekletiliyor ve yine o andaki CPU sayacı alınıp birbirinden çıkarılıyor.
İndirgemek için 1.000.000' a bölünüyor ve işlemcinin 1 saniyede yaptığı işlem
gücü yani işlemci hızını buluyor
0 yorum yazılmıştır