KURCALAMA HASTALIĞI
Retro bilgisayarİftah olmaz bir bilgisayar kurcalayıcısı mısınız? Bir oyun programını yazdıktan sonra, oynamaya başlamaktansa programı değiştirmeyi mi tercih edersiniz? Bilgisayarınızı yükledikten sonra tam istediğiniz konfigürasyonu sağlamak için bir sürü utility doldurma alışkanlığınız var mı? Programları tepeden tırnağa elden geçirmedikçe içinizin rahat etmediği olur mu? Eğer bu sorulardan herhangi birine evet cevabı veriyorsanız, sizde bir kurcalayıcılık var demektir. “Kurcalama” deyimi kimi iyi kimi kötü birkaç anlam birden taşıyor. Biz burada “yazılım deşen”, ya da “bit çevirenler”den bahsedeceğiz. Modemler aracılığıyla Stratejik Hava Kumandanlığı'nın Savunma İletişim Sistemi'ne nasıl girildiğini, veya en yeni telif koruma sistemlerini etkisiz bırakma yöntemlerini anlatmak niyetinde değiliz. Kendimize daha mütevazi hedefler koyuyoruz; fakat kurcalamacılık ruhuna da sadık kalıyoruz. Bu ay, hafızaya bir makine dili utility'si sokmaya yarayan bir BASIC “yükleme” programıyla başlıyoruz. Makine dili monitörü aracılığıyla bu utility'i hafızanın içinde inceleyeceğiz. Utility'i parçalayıp nasıl çalıştığına bakacağız. Ve son olarak da, utility'nin hafıza içindeki konumunun nasıl değiştirilebileceğini göreceğiz. Neden mi yapıyoruz bütün bunları? İçimizden geldiği için! YÜKLEYİCİ Bugüne kadar bir sürü makine dili yükleme programı görmüşsünüzdür. BASIC'le yazılırlar ve büyük ölçüde ondalık veya onaltılık sayılarla dolu DATA cümlelerinden oluşurlar. Bu sayıların ne anlama geldiğini hiç merak etmemiş miydiniz? Bunu tam olarak anlamanız için, assembly dili konusunda biraz da olsa bilgi sahibi olmanız gerekiyor. Ne yazık ki, bu makalede tek bir dersle assembly dilini öğretmemiz mümkün değil. Gene de, assembly dili uzmanı olmadan da bu tartışmayı izleyebilirsiniz. Burada assembly dilinde yazılmış bir programda nelere dikkat etmeniz gerektiğini ve bazı cevaplara nasıl ulaşacağınızı anlatacağım. (Başında $ olan sayılar onaltılık sayılar, geri kalanlar ise ondalık sayılardır.) 1040. satırdaki CK bütün verilerin toplamını hesaplayıp bir “kontrol-toplamı” (check sum) oluşturur. Son veri değeri okunduktan sonra,
1050. satır, bir byte B verisini hafızada M konumu- — kontrol-toplamı gerçek değerle karna getirir. Başlangıçta, M 1000. satırda 4892'dir. 1100. — şılaştırılarak DATA cümlelerinin yasatırdan okunan ilk byte'ı ise $4C'dir. (Başında $ olan — Zılması sırasında hata yapılıp yapıl-
086 ZYSAROWICZ madığı kontrol edilir. Eğer hata yoksa, 1090. satır işleme girer ve her şeyin yolunda olduğunu bildirir. Bu noktaya kadar, 48920'den başlayarak hafızaya 32 byte veri girmiş bulunmaktadır. Bu veriler, BASIC'ten alabileceğiniz bir makine dili utility'sidir, belirli sayıda özdeş karakteri ekranda yazmaya yarar.
Makine dilindeki hizmet
SİCİM utility'si (hızı hariç) şu BASIC rutinine eşdeğerdir:
10 CH-65
20 FOR J-1 TON
30 PRİNT CHRS (CH);
40 NEXT J
CH, yazılacak karakterin ASCII programlarını nasıl kırabiliriz?
değeri, N ise karakterin yazılma adedidir. Yazmayı FOR-NEXT döngüsü gerçekleştirir. Örneğimizde, AS değeri 65 olan “A” harfi ekranda 500 kere yazılacaktır. Sicim Yükleyici programı doğru bir şekilde işletildikten sonra, hafızada 4892'den başlayan bir makine dili programı bırakacaktır. C-128 BA-17
SİC programında makine dili pro: şu cümlelerle işleme koyabilirsiniz:
10 CH-65 :
20 INT (N/256)
30 LSB — N-256/MSB
40 SYS 4892, CH, LSB, MSB
20 ve 30 numaralı satırlar, maksimumu 65535 olan N değerini her biri 0 ile 255 (dahil) arasında değişen tek byte'lık bir niceliğe dönüştürür. CH değişkeni de N kere yazılacak karakterin ASCII kodunu depolar. SYS komutu bilgisayarın 4892'ye atlayıp, oradaki makine dili programını işleme koymasını sağlar. SYS komutunun ardından gelen ilk parametre, makine dili rutininin başlama adresini (4892'yi) temsil etmektedir. Onu izleyen üç parametre ise, makine dili rutini işleme konmadan önce işlemcinin A sicil, X sicil ve Y sicilinde depolanması gerekli değerlerdir. Bütün bu konulara yabancı olduğunuzu varsayarak, önce başlama adreslerinden ve sicillerden biraz söz edelim. BASIC'te, bir programın dizisel akışını değiştirmek için GOTO veya GOSUB komutlarını kullanırız. Hangi komutu kastettiğimizi ise satır numarasıyla belirtiriz. Yani, bilgisayar GOTO 100 cümlesini işleme koyduğunda, bir sonraki komutu almak için 100. satıra atlar. 100. satırın bilgisayar hafızasında nerede olduğunu bilmeyiz; zaten buna gerek de yoktur. Hafızada 100. satırın yerini bulmak BASIC'in görevleri arasındadır. Nitekim, BASIC'e “yüksek düzeyli dil” denmesinin nedeni de, programcıyı hafızada neyin nerede depolandığını takip etmek gibi basit şeylerden kurtarmasıdır. SYS komutu, düşük düzeyde bir dile inmemize olanak verir. BASIC SYS 4892'yi görünce bir sonraki komut için mikroişlemciyi hafızada 4892 adresine atlatır. İşlemci orada PRINT veya GOTO gibi BASIC cümleleri değil, BASIC çeviricisi programına başvurmadan, kendi başına işleme koyabileceği komutlar bulunacaktır. Birazdan, bu konuda örnekler göreceğiz. SYS komutunun bilgisayar 4892'ye sapmadan önce A, X ve Y sicillerine bazı değerler koyduğunu görmüştük. Siciller, tıpkı el kalkülatörlerindeki hafıza işlevi gibi, mikroişlemci içinde okuma-yazma depolarıdır. C-64'te, BASIC programının SYS komutunu işleme koymadan önce A, X ve Y sicillerinin değerlerini hafızada 780, 781 ve 782 konumlarına POKE etmesi gerekir (Bk. C-64 Programcısının Rehberi, s. 318). C-128'de, kayıt değerleri SYS komutuna dahildir.
MAKİNE DİLİ
Makine diline girmeden önce, size bir akıl verelim: C-64 veya C-128 Programcısının Rehberi'nde (kısaca, PRG'de) 5. bölümü bir okuyun. Bu bölüm assembly dilini ve bilgisayarın iç işleyişlerini gayet güzel özetlemektedir. PRG'ler kitap olarak pek net ve tertipli sayılmasalar da, bir sürü temel ve yararlı bilgi içerirler, bir PRG'nız yoksa, gerçek bir kurcalayıcı sayılmazsınız. Kısacası...
Makine dilinin herhalde en zor tarafları (gerek ikili, gerekse onaltılı sayı sistemi ile) adresleme tarzlarıdır. Assembler programı makine dili programlarını yazmanın içerdiği matematiksel yükü biraz hafifletir, ama tamamen ortadan kaldırmaz. Adresleme tarzlarına gelince, yalnız ve yalnız bol zaman ve pratikle tam olarak öğrenebilirsiniz onları. İsimlerini boş verin. Önemli olan işlevleridir. LDA ($3A,X)'e “endeksli dolaylı adresleme” dendiğini bilmek işinize yaramaz, ama bu komut sonucunda A sicilinin tam olarak nereden yüklendiğini bilmek her şeyi değiştirir. Eğer C-64 kullanıyorsanız, C-128'deki HEX komutunun bir benzerini oluşturmak için 1030. satırı GOSUB 2050 yapmanız gerekli. Ayrıca 64'te 2000'de başlayan satırların eklenmesi gerektiğini de unutmayın. 64'te işe başlamak için: 1. Sicim Yükleyici'yi yukarıda anlatıldığı gibi yükleyip uyarlayın. 2. RUN yazarak Sicim Yükleyici'yi işleme koyun. 3. Makine dili monitör programınızı yükleyip geçin. SİCİM utility'si C-64'te, şimdi bulunduğu adres olan 4892'de genellikle aksayabilir. Çünkü, BASIC programları bazen araya karışmaktadır. Bu tartışmada yalnızca programın hafıza içindeki durumuna bakacak, işleme koymaya çalışmayacağız. Programı C-64'te de kullanılabilecek biçimde harekete geçirme çarelerine ise daha ileride bir göz atarız. C-128'de, Sicim Yükle! yazıp sonra da girerek (eğer değişiklik yapmadıysanız, F8'e basmanız gerekli), SİCİM utility'sini incelemeye başlayabilirsiniz. Nereden başlayacağız? Rutinin hafızaya 4892'den ($131C) itibaren yüklendiğini biliyoruz. Bu konumdan disassemble işlemine başlayalım. Önce D 131C (ya da monitörünüzde geçerli olan disassemble komutunu) yazın. SİCİM rutinin ilk komutları
JMP $1324
EOR ($48,X)
91
EOR $E021, Y
O meçhul 299 komutu ilk bakışta aklınızı karıştırabilir. Bu program gerçekten işleme konabilir midir? Bu rutinin ne işe yarayacağı konusunda ilk ipucunu JMP komutundan alıyoruz. Bu komutla, işlemci koşulsuz olarak adres $1324'e atlamaktadır. Böylece EOR (dışlayıcı-OR) komutlarıyla meçhul komut açıklığa kavuşuyor. O bitler muhtemelen gerçekten komut değil de, yalnızca veri değerleridir. Bilgisayar onları işleme koymaz. Doğruca üzerlerinden atlar. Bu varsayımı doğrulamadan önce disassembly işlemine devam edelim. JMP komutu $1324'e gitmiş olduğu için, bu sefer parçalamaya oradan başlıyoruz. Şimdi karşımızdaki gerçek bir programa benziyor. Bunu nasıl anlayabiliriz? Tecrübeli olmak işe yarar tabii, ama her şeyden önce, burada meçhul bit yok. Ayrıca, adres $FFD2'ye yönelik bir takım JSR talimatları görülüyor. Bu adreste, programın içkin bir parçası olan Kernal rutinlerinden biri bulunuyor. Kernal rutinleri her an kullanıma hazır makine dili rutinlerinden oluşur ve makine dili programlarında sıkça geçer. Şimdi, önce 97. sayfadaki Ekran (Dump?) 1'e bir göz atın. Bu tabloda, adres $133B'de bir RTS komutu görüyoruz. RTS komutu, bir altrutinin sonundaki BASIC RETURN cümlesine eşdeğerdir. Bunun SİCİM programının en son komutu olması da mümkündür.
Programın gerçekten sonuna gelip gelmediğinizi anlamak için, BEQ ve BNE (eşitse sap ve eşit değilse sap) komutlarına bakın. Sapma komutları, koşullu atlama komutlarıdır. İşlemcinin statü sicilindeki flag'ların set ya da reset edilmiş olmasına bağlı olarak program sapar. Sapma komutlarından hiçbirisi RTS komutunun bulunduğu adresin ilerisindeki adreslere gitmemektedir. Demek ki, RTS gerçekten de programın sonudur. Ayrıca, Sicim Yükleyici'nin hafızaya 32 bit sokmuş olduğunu ve $133B'deki RTS'nin 32 bit olduğunu hatırlarsanız, programın sonuna vardığınızı anlamanız güç olmaz! Programı parçalamak mesele değildir. Ama parçalardan bir anlam çıkarmak zor olabilir. Burada da tecrübe işe yarar. Fakat şu küçük rutini yorumlamak o kadar zor olmasa gerek. $1324'ten başlayarak ilk iki komutu alın:
CPX # $00
BEO $132E
İlk komut X-sicilini sıfırla karşılaştırmaktadır. Eğer X-sicili sıfıra eşitse işlemcinin Sıfır flag'ı set, değilse reset olacaktır. Sıfır flag'ı set olursa, BEQ (sıfıra eşitse sap) komutu $132E'ye atlar.
BASIC çağrı programımızın ve Y sicillerine değerler yerleştirdiğini görmüştük. Eğer X sicili sıfırdan büyük bir değerle başlıyorsa, program $1328'de şu komutları yerine getirecektir:
XxX
JSR $FFD2
DEX
BNE $1328
Bu, BASIC'teki FOR-NEXT döngülerine denk düşmektedir. $FFD2'deki altrutin çağrılır, X sicili eksiltilir ve eğer sıfır değilse BNE (sıfıra eşit değilse sap) komutu programın tekrar $FFD2'ye dönmesini sağlar. Böylece, $FFD2 X kere, yani X sicilindeki ilk değer kadar çağrılır. X sıfıra eşitlendikten sonra, adres $132E'deki şu komutlar yerine getirilir:
CPY $00
BEO $133B
Eğer Y-sicili sıfırsa, rutin adres $133B'deki RTS'ye, yani programın sonuna atlar. Y sıfır değilse, $1332'den başlayan şu komutlar yerine getirilecektir:
JSR $FFD2
DEX
BNE $1332
DEY
BNE $1332
Bunlar içiçe FOR-NEXT döngülerine tekabül etmektedir.
FOR Y-? TO ? STEP -1
FOR X-255 TO O STEP -1
GOSUB $FFD2
NEXT X
NEXT Y
X-sicili maksimum değerinden (255) sıfıra indirilir, sonra da Y-sicili eksiltilir. X gene 255'ten dönerek sıfıra iner ve Y eksilmiş olur. Bu, Y sıfıra ininceye kadar sürer. Sonra, $1339'daki son BNE komutu atlanır ve son RTS komutuna varılır ki, bu da BASIC'e dönmektedir.
KERNAL RUTİNLERİ altrutin CHROUT ya da BSOUT diye adlandırılan bir Kernal rutinidir. Bu rutinin tanımını PRG'nizde bulacaksınız. Bu programda, CHROUT (akümülatör diye de adlandırılan) A sicilindeki hareketlerin değerini alıp ilk uygun konumda ekrana vermektedir. Yani CHROUT sizi cursor konumunu gelişmelere paralel olarak değiştirme ve karakteri ekranda hangi adrese vereceğinizi saptama sıkıntısından kurtarmaktadır. Hatırlayacağınız gibi, BASIC çağrı programımız print edilecek karakterin değerini A-siciline yüklemişti. Ayrıca, print edilecek sayıyı X ve Y sicillerine yüklediğini görmüştük. SİCİM utility'si önce $FFD2'yi X kere çağırmış, sonra da içiçe X-Y döngüsünü Y kere tekrarladı. İçiçe X-Y döngüsünün her geçişinde $FFD2 256 kez çağırılarak X 255'ten 0'a indi. Böylece, SİCİM utility'si belirlenmiş olan karakteri tam istediğimiz gibi (X * 256 Y) kez print etmektedir. Kernal rutinlerini öğrenmek başka programları parçalamakta da size yardımcı olabilir. Bu rutinler makine dilinin en yorucu işlemlerine kolay bir çözüm getirdikleri için yaygın olarak kullanılmaktadır. PRG'lerde Kernal rutinleri her yönüyle tanıtılmış ve örnekler verilmiştir. Sıra $131F ile $1323 arasında gördüğümüz veri karakterlerine geldi. Bunları yorumlamak için, M 131F 1323 yazın. Ekran Dump 1'de de gördüğünüz gibi, bunlar bu yazının alınmış olduğu derginin ismine tekabül ediyor. (Eğer C-64 monitörünüz ASCII display vermiyorsa, veri bitlerinizi kendiniz çevirmek zorundasınız) Bu veri bitleri SİCİM utility'sinde kullanılmamıştır, ama çoğu makine dili utility'si bu şekilde düzenlenir. Programın başında bir veri depolama alanı bırakıp, bir JMP komutuyla bu verileri atlamak sık rastlanan bir uygulamadır.
YENİDEN KONUMLANDIRMA
Aslında JMP komutu ve beş veri biti SİCİM programında gerekli değildir. Onları yalnızca herhangi bir makine dili rutinini uyarmak veya harekete geçirmek açısından çok önemli bir noktayı aydınlatmak için kullandık. Herhangi bir makine dili programını harekete geçirmeden önce, yeniden konumlandırmaya elverişli olup olmadığını tespit etmek zorundayız. Eğer yeniden konumlandırılamıyorsa, çalışması için onu uyarlamamız gerekir. JMP cümlesi tek bitliktir ($4C) ve arkasından iki bitlik bir adres gelmektedir. Eğer BEQ ve BNE (sap) komutlarının hexadecimal bitlerine bakarsanız, ikisinin de iki bitlik komutlar olduğunu göreceksiniz. Bir sapmanın ikinci biti adres değil, offset'tir. Örneğin, adres $1326'daki ilk BEQ komutu şu şekildedir:
01326 FO 06 $132E
Disassembler $F0'ın BEQ komutu olduğunu bilmektedir. $06 (komut yerine getirilmeden önce yenilenerek $1328'e çıkmış olan) mevcut hafıza adresine eklenerek $132E elde edilir. Öte yandan $131C'deki JMP komutu şöyledir:
0131C 4C 24 13 JMP $1324
$4C JMP anlamındadır. $24 $13 ise gerçek adres $1324'ün “düşük bit-yüksek bit” formatındaki halidir. Bu programı hafızaya $131C'den başka bir adreste, mesela $C000'de yüklediğinizi varsayalım. Bilgisayara bu programı $C000'de işleme geçirmesini söylediğinizde, programın ilk komutuyla bilgisayar adres $1324'e atlayacaktır. Programınız $C000'deyken $1324'te ne bulunduğunu Allah bilir. Büyük ihtimalle, bilgisayarı kapatıp yeniden başlamanız gerekecektir. Öte yandan, diyelim ki programın yerini değiştirip yukarıda tanımlanan BEQ komutu gayet güzel çalışacaktır. Neden mi? Çünkü komutun iki biti $F0 $06 sabit değil göreli bir sapma adresi vermektedir. Bilgisayar adres $2000'e vardığında $F0'ı görür ve onu izleyen biti bir sonraki komut adresine eklenecek bir nicelik olarak ele alması gerektiğini anlar. BEQ komutu $2000 ve $2001'de yer aldığına göre, bir sonraki komut adresi $2002'dir. İşlemci $06'yı $2002'ye ekleyip, (eğer sıfır flag'ı setse) $2008'deki komuta sapar. BEQ, BNE ve tüm diğer sapma komutları göreli adresler taşır ve program hafızanın neresinde bulunursa bulunsun kusursuz iş görürler.
Eğer bir programın yerini değiştirip hafızanın başka bir yerinden başlamak istiyorsanız, mutlaka adres kullanan tüm komutları “ayarlama”nız lazımdır. Buna örnek olarak JMP ve JSR'yi (altrutine atla) gösterebiliriz. Mutlak adres (örneğimizdeki $FFD2 gibi) programın dışındaki bir sabit konumdaysa, değiştirilmemelidir. Ama (örneğimizdeki JMP $1324 gibi) yeri değiştirilen programın dahilinde bir mutlak adres söz konusuysa, düzeltilmesi gerekir.
C-64 sahipleri bu programı adres $C000 (49152)'de başlayacak şekilde yeniden konumlandırmalılar. $1324 özgün programda dokuzuncu bit olduğuna göre, yeri değiştirilmiş programda JMP gene dokuzuncu bite, yani $C008'e gidecek. Bu yüzden, JMP $1324 komutu JMP şeklinde değiştirilmeli. Bunun için de, Sicim Yükleyici'ndeki ikinci ve üçüncü veri maddeleri 08 C0 yapılmalı (adres bitlerinin tersten depolandığını unutmayın). BEQ ve BNE ayarlanmadan yerleri değiştirilebildiği için, bunlara “yeniden konumlandırılabilir” komutlar denmektedir. Bir programın “yeniden konumlandırılabilir” olması için yalnızca göreli sapma veya sabit adreslere sapmalar içermesi ve verileri doğrudan adreslememesi gerekirdi. Verilerin dolaylı adreslenmesini başka bir tartışmada ele alacağız. SİCİM Programını yeniden konumlandırılabilir hale getirmek için yalnızca ilk sekiz biti atmakla yetinebiliriz. (Zaten onlar örnek niyetineydi.) Ama başka programlar bu kadar kolay dönüştürülemeyebilir. Bütün atlama komutlarının yanısıra, yükleme, depolama ve hafıza değerleri üzerinde işlem yapmaya ilişkin bütün komutları incelemeniz gerekir. Genel olarak, programın yeri değiştirildiğinde bütün bu adresleri de değiştirmek lazımdır.