30-12-2020, Saat: 11:03
DELHPI VE ASSEMBLY
Merhaba,
Bir delphi inline assembly eğitim makale serisi başlatmak istiyorum. Makale serisi x64 Windows programlama üzerine olacak.
1: DELPHI İLE ASSEMBLY’E GİRİŞ
Her şeyden önce projeye Windows x64 platformunu eklemeliyiz.
Kodlarımız asm ve end; blokları arasında yer almalı.
Aşğıdaki gibi bir kullanımda assembly kodu yazmamız mümkün olmaz:
Hemen bir sayı döndüren fonksiyon tasarlayalım. Projeye bir unit ekleyelim ve şu şekilde kodlayalım:
Bu unit’i vcl uygulaması yapıyorsanız formun unit’ine ekleyelim. Ben konsol uygulamasına ekledim:
Yukarıda da gördüğünüz gibi sayi adında bir 64 bit integer değişken tanımladım ve Test fonksiyonunu bu değişkene atadım. Daha sonra da konsola writeln ile yazdırdım. Konsolda 1234 yazını gördüm. Peki bu nasıl oldu? Test fonksiyonu nasıl 1234 değerini döndürdü?
Hemen test fonksiyonumuzu inceleyelim:
mov assembly dilinde bulunan atama komutudur. rax ise genel amaçlı kullanılan registerdir. Fonksiyonlar değerleri rax registeri üzerinden döndürür. Bizim return ya da result ile değer döndürmemize gerek yok.
Bu arada komutları büyük ya da küçük yazmanızın bir önemi yok.
Şimdi biraz değişiklik yapalım:
Tekrar programı çalıştırdığımızda bu sefer konsol ekranında 1235 yazdığını göreceksiniz. mov ile rax’e 1234 değerini atadıktan sonra inc komutuyla rax’deki değeri 1 arttırdık. Evet inc komutu Delphi’deki gibi sayıya bir ekler. Tam tersi de dec komutudur. dec komutu sayıyı bir eksiltir.
Çalıştırdığınızda konsolda 1233 yazacaktır.
Fonksiyonumuzu aşağıdaki şekilde değiştirelim:
Bu fonksiyonda bir parametre kullandık. Ve doğrudan bu parametreyi rax registerine atadık ve yine rax ile fonksiyonun değeri döndürüldü.
Bu kod ile konsolda 5 yazısını gördüm.
Parametreleri doğrudan assembly içinde kullanabiliyoruz. Ancak başka ve daha iyi bir yöntem daha var.
Öncelikle registerlerden biraz söz edelim. X64 işlemcilerdeki bazı genel amaçlı registerler şöyledir:
rax, rbx, rcx, rdx, r8, r9, r10, r11. Biz (rbx hariç) genelde bunları kullanacağız. rbx registerine bulaşmak istemememin nedeni daha önceden başka bir fonksiyon ile rbx registerine atanan verinin daha sonra başka bir fonksiyon tarafından kullanılacak olması. rbx’i stack’e kaydedip daha sonra stack’ten tekrar yüklemekle uğraşmak istemiyorum. Stack olayını ilerleyen makalelerde anlatacağım.
Windows x64’te parametreler fonksiyonlara soldan sağa sırasıyla rcx, rdx, r8 ve r9 registerlerine otomatik olarak aktarılır. Yukarıdaki fonksiyonda a parametresi rcx registerine otomatik atanmıştı. Bu fonksiyonunun daha güzel versiyonu şöyle olacak:
Ekrana tekrar parametre a’nın değerini yazdırdı.
Şimdi bir toplama fonksiyonu yazalım. Unit’imizi şu şekilde değiştirelim:
Dpr dosyamız da şöyle olmalı:
Konsolda 11 yazıldığını göreceksiniz.
mov rax, rcx kodu ile rax registerine rcx registerinde yer alan a’nın değerini atadık. Bu satırdan itibaren rax’in aldığı değer a’nın değeridir.
add rax, rdx satırı ile de rax registerine rdx registerinde yer alan b’nin değerini atadık. Bu satırdan itibaren rax’in aldığı değer a’nın ve b’nin değerleri toplamıdır. Otomatik olarak fonksiyon rax değerini döndürecektir.
Bir de çıkartma fonksiyonu yazalım:
sub komutu ilk yazılan operanddan ikincisini çıkaracaktır. İşlemin sonucu ilk operandda olacaktır.
Unit’imizin son hali:
Dpr dosyamızın son hali:
Bir sonraki makalede registerler üzerine biraz daha açıklama yapacağım.
Bu makale serisi özgün olup ilk defa Delphican sitesinde yayımlıyorum.
Faydalı olması ümidiyle.
Hakan DİMDİK
Merhaba,
Bir delphi inline assembly eğitim makale serisi başlatmak istiyorum. Makale serisi x64 Windows programlama üzerine olacak.
1: DELPHI İLE ASSEMBLY’E GİRİŞ
Her şeyden önce projeye Windows x64 platformunu eklemeliyiz.
Kodlarımız asm ve end; blokları arasında yer almalı.
procedure Test; asm end;
Aşğıdaki gibi bir kullanımda assembly kodu yazmamız mümkün olmaz:
procedure Test; begin end;
Hemen bir sayı döndüren fonksiyon tasarlayalım. Projeye bir unit ekleyelim ve şu şekilde kodlayalım:
unit Unit2; interface function Test: int64; implementation function Test:int64; asm mov rax, 1234 end; end.
Bu unit’i vcl uygulaması yapıyorsanız formun unit’ine ekleyelim. Ben konsol uygulamasına ekledim:
program Project1; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, Unit2 in 'Unit2.pas'; var sayi: int64; begin try sayi:=Test; writeln(inttostr(sayi)); Readln; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.
Yukarıda da gördüğünüz gibi sayi adında bir 64 bit integer değişken tanımladım ve Test fonksiyonunu bu değişkene atadım. Daha sonra da konsola writeln ile yazdırdım. Konsolda 1234 yazını gördüm. Peki bu nasıl oldu? Test fonksiyonu nasıl 1234 değerini döndürdü?
Hemen test fonksiyonumuzu inceleyelim:
mov rax, 1234
mov assembly dilinde bulunan atama komutudur. rax ise genel amaçlı kullanılan registerdir. Fonksiyonlar değerleri rax registeri üzerinden döndürür. Bizim return ya da result ile değer döndürmemize gerek yok.
Bu arada komutları büyük ya da küçük yazmanızın bir önemi yok.
Şimdi biraz değişiklik yapalım:
function Test: int64; asm mov rax, 1234 inc rax end;
Tekrar programı çalıştırdığımızda bu sefer konsol ekranında 1235 yazdığını göreceksiniz. mov ile rax’e 1234 değerini atadıktan sonra inc komutuyla rax’deki değeri 1 arttırdık. Evet inc komutu Delphi’deki gibi sayıya bir ekler. Tam tersi de dec komutudur. dec komutu sayıyı bir eksiltir.
function Test: int64; asm mov rax, 1234 dec rax end;
Çalıştırdığınızda konsolda 1233 yazacaktır.
Fonksiyonumuzu aşağıdaki şekilde değiştirelim:
function Test(a:int64):int64; asm mov rax, a end;
Bu fonksiyonda bir parametre kullandık. Ve doğrudan bu parametreyi rax registerine atadık ve yine rax ile fonksiyonun değeri döndürüldü.
sayi:=Test(5); writeln(inttostr(sayi)); Readln;
Bu kod ile konsolda 5 yazısını gördüm.
Parametreleri doğrudan assembly içinde kullanabiliyoruz. Ancak başka ve daha iyi bir yöntem daha var.
Öncelikle registerlerden biraz söz edelim. X64 işlemcilerdeki bazı genel amaçlı registerler şöyledir:
rax, rbx, rcx, rdx, r8, r9, r10, r11. Biz (rbx hariç) genelde bunları kullanacağız. rbx registerine bulaşmak istemememin nedeni daha önceden başka bir fonksiyon ile rbx registerine atanan verinin daha sonra başka bir fonksiyon tarafından kullanılacak olması. rbx’i stack’e kaydedip daha sonra stack’ten tekrar yüklemekle uğraşmak istemiyorum. Stack olayını ilerleyen makalelerde anlatacağım.
Windows x64’te parametreler fonksiyonlara soldan sağa sırasıyla rcx, rdx, r8 ve r9 registerlerine otomatik olarak aktarılır. Yukarıdaki fonksiyonda a parametresi rcx registerine otomatik atanmıştı. Bu fonksiyonunun daha güzel versiyonu şöyle olacak:
function Test(a:int64):int64; asm mov rax, rcx end;
Ekrana tekrar parametre a’nın değerini yazdırdı.
Şimdi bir toplama fonksiyonu yazalım. Unit’imizi şu şekilde değiştirelim:
unit Unit2; interface function Topla(a,b:int64):int64; implementation function Topla(a,b:int64):int64; asm mov rax, rcx add rax, rdx end; end.
Dpr dosyamız da şöyle olmalı:
program Project1; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, Unit2 in 'Unit2.pas'; var sayi:integer; begin try sayi:=Topla(5,6); writeln('Topmlam='+inttostr(sayi)); Readln; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.
Konsolda 11 yazıldığını göreceksiniz.
mov rax, rcx kodu ile rax registerine rcx registerinde yer alan a’nın değerini atadık. Bu satırdan itibaren rax’in aldığı değer a’nın değeridir.
add rax, rdx satırı ile de rax registerine rdx registerinde yer alan b’nin değerini atadık. Bu satırdan itibaren rax’in aldığı değer a’nın ve b’nin değerleri toplamıdır. Otomatik olarak fonksiyon rax değerini döndürecektir.
Bir de çıkartma fonksiyonu yazalım:
function Cikar(a,b:int64):int64; asm mov rax, rcx sub rax, rdx end;
sub komutu ilk yazılan operanddan ikincisini çıkaracaktır. İşlemin sonucu ilk operandda olacaktır.
Unit’imizin son hali:
unit Unit2; interface function Topla(a,b:int64):int64; function Cikar(a,b:int64):int64; implementation function Topla(a,b:int64):int64; asm mov rax, rcx add rax, rdx end; function Cikar(a,b:int64):int64; asm mov rax, rcx sub rax, rdx end; end.
Dpr dosyamızın son hali:
program Project1; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, Unit2 in 'Unit2.pas'; var sayi:integer; begin try sayi:=Topla(5,6); writeln('Topmlam='+inttostr(sayi)); sayi:=Cikar(5,6); writeln('Fark='+inttostr(sayi)); Readln; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.
Bir sonraki makalede registerler üzerine biraz daha açıklama yapacağım.
Bu makale serisi özgün olup ilk defa Delphican sitesinde yayımlıyorum.
Faydalı olması ümidiyle.
Hakan DİMDİK