SİTE
İÇİ ARAMA |
|
Blogroll |
|
|
|
Sembolik kodlar ve X86 Assembly Dili |
|
Gönderiliyor lütfen bekleyin... |
|
|
Geçen makalemizde debug programını tanımış ve makine kodları ile küçük bir program hazırlamıştık. Sonra bu programı yine debug ile bir dosya haline getirmiştik. Şimdi bu dosya ile biraz
daha yakından ilgilenelim. Yine komut istemine geçiş yapıp debug
programını şekildeki gibi çalıştıralım.
Not:Geçen makalemizdeki dosyayı kaybettiyseniz
buradan indirin ve C:\ dizininde asm adında bir klasör oluşturun ve bu dosyayı oraya kopyalayın.
Şekil 1 - Debug ile program yükleme
Ekranın solunda bir
- işareti göreceksiniz ve başka hiçbir şey olmamış görünecek ama aslında
merhaba.com adlı program dosyasını oluşturan makine kodlarını çoktan hafızaya
yüklediniz bile. Makine
kodlarını görmeden önce sizlere küçük bir tavsiye vermek istiyorum. Assembly
programlama dilinde ayrıntılar çok önemlidir ve her bir ayrıntı kendi çapında
bir araştırma konusu olabilir, örneğin burada yaptığınız program yükleme
işlemini windows kullanıcıları, programın simgesine mouse ile tıklayarak
yapıyorlar ve hatta bu şekilde program hafızaya yüklendikten sonra birde işletim
sistemi tarafından çalıştırılıyor. Şimdi aklınıza şu soru gelmeli; program
simgesinin üzerine gelip mouse ile tık-tık yapıldığı andan itibaren, programın
yüklenip çalıştırılmasına kadar sistemde neler ve nasıl yapılıyor? Öyle değil
mi? Nelerin yapıldığını geçen makalemizi takip ettiyseniz tahmin edebilirsiniz
ama nasıl yapıldığına gelince o iş biraz sistem programcılığına giriyor yani
bizim için biraz erken. Neyse biz konumuza geri dönelim, program şu anda
hafızada ve 0100h offset adresinden itibaren yüklenmiş durumda. Hemen kodları
görelim o zaman.
Şekil 2 - Debug’ın "d" komutu
Debug’ın d komutu "dump" anlamına gelip programlama aleminde kodları ekrana yada kağıda dökmek gibi bir anlamı vardır.
Bizde burada kodları ekranda gösterdik. Neden 0100h adresinden itibaren hafızaya
yerleşti? sorusunun yanıtı program dosyasının .com uzantılı olmasından
kaynaklanır ve geçen makalemizde açıkladığımız gibi sonraki makalelerde ele
alınacak bir konudur. Burada önemli olan husus sizin 0100 offset adresi ile 010B
offset adresleri arasında kalan makine kodlarından bazılarının komut kodları
bazılarının ise data (veri) kodları olduğunu bilmenizdir.
Tabi ki ekrana
yazdırılacak olan MERHABA ASSEMBLY burada data sınıfına giriyor peki komutlar
nerede?
Şekil 3 - Datalarımız...
Kırmızı ile işaretli olan kısımdan öncekiler (dikkat bu kısımdan sonraki kodlar bizi ilgilendirmiyor) yani 0100h ile
010Ah adresleri arasında kalan kısım naçizane komut kodlarımızdır.
Tabi ki bu
şekli ile bize pek bir şey çağrıştırmıyorlar şayet bunlar bizlerin anlayabileceği
dile biraz yakın olsaydı anlardık.
Şekil 4 - Makine ve Assembly Dili Yan yana
Yukarıdaki şekilde makine kodlarını sarı assembly kodlarını kırmızı
çerçevede görebilirsiniz. Tabi ki assembly dili bizlere daha yakın bir dildir.
Assembly dilindeki bu gösterim aynı zamanda sembolik kodlar olarak ta bilinir.
Kodları assembly dilinde görmek için debug’ın "u" (unassembly) komutunu
kullandık, u dan sonra gelen 0100 010A ise hafıza aralığıdır, yani biz burada
0100h-010Ah offset adresleri arasınındaki makine kodlarını assembly dilinde
görmüş olduk. Şimdi yukarıdaki şekilde gördüğümüz makine ve assembly kodlarını
karşılaştıralım;
Assembly dilindeki
MOV AH,09’un makine dilindeki karşılığının B409 olduğu görülüyor ve assembly
programcıları "B409" ile gösterilen makine dilindeki bu ifadeyi iki kısıma
ayırırlar. Bunlar opcode ve operand alanlarıdır.
Tablo 1 :
Opcode ve Operand
Buradaki B4
opcode’u işlenecek olan asıl emirdir ve ancak mikroişlemci (CPU)
tarafından kodu çözüldükten sonra 09 operanıda komut işleme sürecine katılır.
Burada sadece opcode ile operandı ayırmanız yeterlidir assembly dilindeki
ifadeleri ne anlama geldiğini şimdilik önemsemeyin. Intel’in opcode uzunluğu 1 yada 2
byte’lıktır. 2 byte’lık olan opcode’ları 0F ile başlar. Biz şimdilik hep 1
byte’lık opcodelar ile çalışacağız. Operandlar ise herzaman opcode’lardan sonra
gelir ve X86 mimarisinde 1,2,4 veya 8 byte’lık olabilir. Böylece ortaya karma
karışık bir komut seti çıkar, bu yüzdende Intel’in x86 ailesi CISC (Complex
Instruction Set Computers) olarak anılırlar.
Burda 3 byte’lık bir
komut satırı, opcode 1 byte’lık operand 2 byte’lık. Ama şimdi kafamızda 2 tane
soru işareti var, birincisi neden MOV komutunun makine dilinde 2 tane farklı
opcode’u var? (yani B4 ve BA’dan bahsediyoruz ve aslında MOV komutunun makine
kod karşılığı 2 den de fazladır) ve ikinci soru neden MOV DX,010B için operand
kısmı makine dilinde 0B01 olarak ters bir biçimdedir?
Tablo 2 :
Opcode ve Operand
Bu sorların cevabı
X86 uyumlu mikroişlemci mimarisini anlayarak bulabilirsiniz. İlk olarak B4 veya
BA MOV’un karşılığı gibi görünse de öyle değildir, B4= MOV AH’ın BA=MOV DX’in
opcode karşılığıdır. Yani ikisi de farklı assembly ifadeleridir ve farklı
opcode’larının olması son derece normaldir. İkinci sorunun cevabı ise x86
ailesindeki işlemcilerin hafızaya erişme şekillerinin ters sıralı olmasından
kaynaklanır. Tabi ki birde düz olarak byte’ları yerleştirme olayı var, bunlar
kitaplarda little endian byte ordering ve big endian byte ordering olarak geçer.
Aslında byte düzenleri çok çok ileri seviyede önemli konulardır ayrıca
opcode’ları gösterirken seçilen bu hexadecimal değerlerde (B4 ve BA gibi)
rastgele seçilmiş değerler değildir. Bu tür konuları öğrencilik yıllarımdayken
araştırmıştım, gerçekten epey zevkli konular, genellikle yüksek lisans ve
doktora sınıflarında instruction anatomy (komut anatomisi) başlığı altında
incelenen konulardır. Programcılık açısından ise sadece byte düzeni önemlidir.
Bu yüzden burada sadece bun iki kavramı açıklayacağım.
Little Endian Byte Ordering: Hafızda yüklenecek olan byte’lar düşük değerlikli kısmından
itibaren yazılır. Örneğin
1234 gibi iki byte’lık bir veriyi hafızaya 0100 offset adresinden itibaren
yazdığınızı düşünelim, bu işlem bitip hafızaya baktığınızda göreceğiniz şey
0100=34, 0101=12 olacaktır.
Şekil 5 - Little endian byte düzeni
Burada debug’ın "a"
(assembly) komutunu vererek assembly dilinde program yazma moduna geçtik ve dw
talimatı ile hafızaya 1234 değerini girdik ve hafızaya, önce bu sayının düşük
değerlikli byte’ı sonrada yüksek değerlikli byte’ı yazıldı.
Big Endian Byte Ordering: Hafızaya yüklenecek
byte’lar en yüksek değerlikli kısmından itibaren yazılır aynen bizim kağıt üstüne
sayıları yazdığımız gibi. Sizlere burada bir tanede big endian örneği vermek
isterdim fakat şu anda elimde bir Apple PowerPC olmasına rağmen MAC OSX işletim
sistemi için debugger yok :( Aşağıdaki tablo hangi sistemlerin hangi byte
düzenini kullandığını gösteriyor.
İşlemci Türü
|
İşletim
Sistemi
|
Byte Düzeni |
Digital Alpha AXP |
Tru64 UNIX |
little-endian |
Hewlett Packard PA-RISC |
HP-UX |
big-endian |
IBM RS/6000 |
AIX |
big-endian |
Intel x86 |
Linux |
little-endian |
Windows |
little-endian |
Solaris x86 |
little-endian |
Motorola PowerPC |
Macintosh OS X |
big-endian |
Sun SPARC |
SunOS |
big-endian |
Solaris |
big-endian |
SGI R4000 |
Irix |
big-endian |
Tablo 3 : Üreticilerin çoğu big-endian tercih
ederken dünyada en çok kullanılan little-endian byte düzeni :)
CD21
INT 21
B44C MOV AH,4C
CD21 INT 21 |
INT 21’de bir
byte’lık opcode’u ve bir byte’lık operandı olan başka bir komut satırı. Bu arada
MOV AH,4C nin opcode’u ile yukarıda açıkladığımız MOV AH,09’un opcode’larının
aynı olduğuna dikkatinizi çekerim.
Buraya kadar ;
1- Neo’nun
Matrix’i gördüğü gibi bizlerde hafızaya baktığımızda orada gördüğümüz byte’ları
nasıl yorumlayacağımızı gördük, bu yolda kendinizi geliştirmeye kalkarsanız
sizde belki bir gün Matrix’i görebilirsiniz :) Yıllardan beri ben hafıza
dökümlerine bakarım onlarda ekrandan doğru bana bakarlar ama program boyutu
10Kb’ı geçince insan halüsülasyonlar görmeye başlıyor onuda belirteyim :)
2- Makine kodları
ile assembly kodları arasındaki bağıntıyı gördük, Borland gibi bir derleyici (compiler)
yazan bir firma kurmak isterseniz, bu alanda en az bir doktora tezi vermeniz
gerekir. Yani for (int i=0 ; i<100 ; i++) gibi bir ifadenin derleyici ile makine
diline dönüşmesi zannettiğinizden daha karışıktır.
Özetle buraya
kadar derin konuların giriş kısımları anlatıldı. Şimdi sıra
assembly kodlarına bir hacker gibi değil de bir programmer gözüyle bakmaya geldi.
Assembly Kodlarına Bir Assembly
Programcısı Gibi Bakmak
Yazdığımız
programda
iki tane iş yapılıyor. Birincisi ekrana bir dizi byte’ın ASCII görünümünü yazdırmak,
ikincisi bilgisayarı tekrar kullanıcının ellerine bırakmak yani işletim
sistemine geri dönüş.
Şekil 6 - Programımızın parçaları
Yukarıdaki
şekilde algoritmanızın assembly koduna dönüşmüş halini görüyorsunuz. Tamam,
geçen hafta bu programı yazarken algoritma falan hazırlamadık ama önce ekrana
yazdır sonra çık mantığını için de algoritma yazılmaz herhalde :)
Bu program DOS’un
meşhur kesmelerinden (interrupt) 2 tanesini çağırdı aslında, birincisi 9.
fonksiyon olan "Display String", ikincisi fonksiyon numarası 4C olan "Terminate
Program". Bunları söyledikten sonra ne desem kafanız karışacak bu yüzden bu
olayı daha iyi anlamanız için aşağıdaki diyalogu hazırladım. Buradaki acemi
assembly programcı (AAP) azıcık assembly bilgisi olan birini, tecrübeli assembly
programcısı (TAP) yeterli derecede bilgisi olan bir programcıyı ima ediyor.
Acemi
programcının kafasının içinden geçenler;
AAP- Assembly
dilinde ekrana bir şey yazdıracağım!
AAP- Hafızaya dataları girer sonrada bunları ekran kartına gönderirim nasıl olsa
ekran kartı daha sonraki işlemleri kendisi halleder,
AAP- Ama gönderme işlemini nasıl yapacağım, hımmm ben en iyisi bir bilene
sorayım.
AAP- Merhaba TAP, sana bir sorum olacak ekrana MERHABA ASSEMBLY yazdırmak
istiyorum ama ekran kartına byte’ları nasıl göndereceğimi bilmiyorum, bana
anlatırmısın lütfen?
TAP- Hangi işletim sistemini kullanıyorsun?
AAP- Windows!
TAP- Byte’ları ekran kartına göndermene gerek yok, onu senin yerine işletim
sistemi yapar zaten.
AAP- Peki nasıl olacak?
TAP- INT 21’in 9. fonksiyonu senin için birebir, işlemcinin AH kaydedicisine 09,
DX kaydedicisine karakterlerin başlangıç adresini yaz, sonrada 21. interrupt
servisini çağır. Ha bu arada son karakterden sonra hafızaya bir $ işareti koy ki
interrupt servisi yazılacak karakterlerin bittiğini anlasın. Anladın mı?
AAP- Anlar gibi oldum? Ama ben bu işi kendim yapamaz mıyım?
TAP- Yaparsın tabi ama çok ekmek yemen lazım :)
AAP- Nasıl yani?(bu
arada TAP’ın gülüşüne de kızar tabi)
TAP- Bak
AAP kardeş, Windows gibi işletim sistemini yazan programcılar senin gibi
programcıların ekrana bir şey yazmak isteyeceğini, veya klavyeden bir bilgi
alacağını yada yazıcıdan çıktı almak isteyeceğini düşünerek bazı küçük
programları yazıp işletim sistemlerine gömmüşlerdir. Sende bu hizmetlerden
faydalanır ve Amerika’yı yeniden keşfetmeye gerek duymazsın.
AAP- Evet çok akıllıca düşünmüşler doğrusu...
TAP- İşletim sistemi de bu demektir zaten AAP, programcıya, kullanıcıya
bilgisayarı kolayca kullandırabilmek.
AAP- Teşekkür ederim TAP sonra görüşürüz.
TAP- (AAP’nin sonra tekrar gelip başka interrupt servislerini de soracağından
endişelenerek) Hey AAP, bak
buradan diğer interrupt servislerine de bakabilirsin, hani lazım olur diye
söylüyorum...
AAP- Teşekkürler TAP.
Daha sonra AAP
bilgisayarının başına gider yukarıdaki şekildeki programın ilk üç satırını yazar
ve çalıştırır, tabi ki ekranda MERHABA ASSEMBLY görünür ama komut istemindeki
penceresini kilitlenir, çünkü ekrana yazdırma işleminden sonra programı
sonlandırmamıştır. Bunun üzerine programı sonlandırmak için tekrar interrupt
servislerini araştırmaya koyulur ve interrupt 21’in 4C fonksiyonunu keşfeder.
MOV AH,4C ve INT 21 satırlarını da programına ekledikten sonra programı kusursuz
çalışır.
Interruplar
hakkında şimdilik bu bilgiler umarım sizi tatmin etmiştir. İlerleyen
makalelerimizde interruptları daha yakından ele alacağız, bu konular gerçektende
çok zevkli.
Yukarıdaki
diyalog ta kaydediciler (register) diye bir kavram geçti, bunları şimdilik
mikroişlemci içindeki hafıza konumlarına (memory location) benzeyen yapılar
olarak düşünün. Kaydedicilerin listesini görmek için debug programını
kullanabilirsiniz.
Şekil 7 - Kaydedicileri debug ile görmek
için r (registers) komutunu kullanabilirsiniz.
X86
kaydedicilerinin AX, BX, CX, DX, SP, BP... diye adları vardır ve bunların
hepsinin değişik işlevleri mevcuttur. Program yazarken elbette bunları
kullanacağız ama şimdilik üzümünü yiyin bağını sormayın, çünkü bağın yerini
söylersem ve sizde gider o bağa bakarsanız iştahınız kaçabilir. Kafalar fazla
karışmadan yeni bir uygulama yapalım.
10 defa Merhaba Assembly
Yazdırıyoruz
Tabi böyle bir
işi yaptırmanın değişik yolları mevcut ( bir .bat uzantılı script dosyası
hazırlayıp ilk programımızı 10 defa çalıştırmak bunlara dahil değil tabi :) )
Şimdi yazacağımız programda ilk programımızın ilk üç satırını 10 defa
çalıştırıp sonra programı sonlandıracağız. C dilindeki for döngüsü gibi.
Aşağıdaki işlemleri takip edin.
Şekil 8 - 10 defa alt alta MERHABA
ASSEMBLY yazan program
Programı
yukarıdaki gibi hafızaya girdikten sonra bir önceki makalemizden faydalanıp bu
programı dosya haline getirin, byte’ları saymada sizler için pratik olur. Burada
CX kaydedicisine (0A)16=(10)10 değerini sayaç olarak yüklüyoruz. LOOP komutu
CX’in değeri kadar operandı ile belirtilen adrese dallanır, burada dallanılacak
adres 0103 offset adresidir ve bu işlem sayesinde ekrana 10 defa MERHABA
ASSEMBLY yazılır. Daha sonra programımız fonksiyon:4C interrupt 21
ile sonlanıyor. Buradaki string’e dikkat, debug ile assembly modunda hafızaya
bir dizi karakter girmek için DB (Define Byte) talimatını kullanıyoruz.
Talimatlar (daha sonra ayrıntılı bir şekilde göreceğiz) derleyiciye verilir
yani opcode olarak dönüştürülmezler, aynı C’deki int, double, string veri
tipleri
gibi. DB’den sonra şayet byte’ları karakter olarak girmek isterseniz " " arasına
yazmalısınız. Bu işlemi karakterlerin hexadecimal kodlarını yazarak ta
yapabilirsiniz bu sefer her bir karakterden sonra "," koymanız gerekir. Burada her
iki teknikte kullanılmıştır. ASCII kod tablosuna bakacak olursanız 0A=LF yani
Line Feed (bir satır aşağı) 0D=CR yani Carriage Return (Kursör satır başına)
olduğunu görürsünüz, zaten CR ve LF kontrol karakterleri olduğundan dolayı
standart ASCII kod tablosunda A,C,P,Z gibi normal karakter olarak karşılıkları
yoktur mecburen hex. kod karşılıklarını kullanmak zorunda kaldık. INT 21
fonksiyon 9 ile yazdıracağınız karakter dizilerinin sonunda mutlaka 24=$
bulunmalı yoksa bu interrupt servisi hafızada 24 değerini bulana kadar ekrana yazma
işlemine devam eder.
Bu programın
kodları ile oynayarak konuyu daha iyi kavrayabilirsiniz, örneğin 0D karakterini
kodlardan çıkartın yada CX’e yüklenen değeri değiştirin.
Son olarak debug ile bu
programı adım adım çalıştırabilirsiniz, bunun için program yazma işlemi bitince
P (Proceed) komutunu kullanın T (Trace) komutu ile programınız içinden çağrılan interrupt
servislerine de girebilirsiniz (ama çıkabilirmisiniz orasını bilemem) Debug’tan
çıkmadan bir daha programınızı çalıştırmak için "r ip" komutunu kullanarak ip
kaydedicisini 0100 yapın, böylece programın başlangıç adresini doğru ayarlamış
olursunuz.
Şekil 9 - Adım adım programı işlemek
Bir sonraki
makalemizde sayı düzenleri ile ilgili birkaç önemli noktayı, ve assembler
konularını işleyeceğiz. Yorumlarınızı ve önerilerinizi bekliyorum....
Makale:
Sembolik kodlar ve X86 Assembly Dili Assembly ve X86 Programlama Eren Erener
|
|
|
-
-
Eklenen Son 10
-
Bu Konuda Geçmiş 10
Bu Konuda Yazılmış Yazılmış 10 Makale Yükleniyor
Son Eklenen 10 Makale Yükleniyor
Bu Konuda Yazılmış Geçmiş Makaleler Yükleniyor
|
|
|