23 Kasım 2019 Cumartesi

Offset Kavramı Ve Dinamik Dizi Yapımı (2 ve 3 Boyutlu)

Daha önce Offset Kavramı ve Dinamik Dizi Yapımı isminde bir yazı yazmış ve işaretçiler(Pointer) başka bir deyiş ile adresler ile neler yapabileceğini izah etmeye çalışmıştım.

Dinamik bir diziyi implement etmede izlediğim yol şuydu;
Dinamik bellek yöneten fonksiyonları kullanarak, hafızada bir alan tahsis (allocate) etmek ve sonra, fonksiyonun döndürdüğü adres üzerinden hesaplama yaparak ilgili veriye ulaşmakdı.

Çıkardığım hesaplama formülü ise aşağıdaki gibiydi;
Basit bir hesap ile formülümüz aşağıdaki gibi olacaktır.
Kaç Bayt = index X Veri tipinin kapladığı alan(Byte)
Yeni Adres = Başlangıç adresi + Kaç Byte

Biraz düşünmeyle bulduğum bu formül, sadece 1 boyutlu bir dizinin elemanlarına erişim formülüydü. Şöyle ki;
Element_Adress = Base_Adress  + (Index * Element_Size)
Memory_Size = Element_Count * Element_Size

Sonrasında ise Tuğrul hocamın (Write Great Code: Volume 1: Write Great Code, Write Great Code, Volume 2: Thinking Low-Level, Writing High-Level) kitaplarını kabaca incelerken, "Composite Data Types and Memory Objects" konusu gözüme çarptı ve doğrudan o konuyu incelemeye başladım.Kadere bakın ki; Pointer'lar, diziler ve çok boyutlu dizilerin elemanlarına erişim formülü yer alıyordu. :)

2 Boyutlu Dizi Ve Elemanlarına Erişim Formülü
Element_Adress = Base_Adress + (Col_Index * Row_Size + Row_Index) * Element_Size
Memory_Size = Element_Size * (Col_Count * Row_Count)

3 Boyutlu Dizi ve Elemanlarına Erişim Formülü
Element_Adress = Base_Adress + ( (Depth_Index * Col_Size + Col_Index) * Row_Size + Row_Index) * Element_Size
Memory_Size = Element_Size * Depth_Size * (Col_Count * Row_Count)

Formülleri biraz açıklayacak olursak;
Element_Adress hafıza bloğundaki verinin adresini temsil etmektedir.
Base_Adress dinamik bellekte alan tahsis eden fonksiyonların döndürdüğü adresi temsil etmektedir.
Memory_Size ise dizinin hafızada kapladığı alanı temsil etmektedir.
Element_Size ise sizeof(veri_tipi) ile bulduğumuz değerdir.
Col_Size ise dizideki kolon sayını temsil eder.
Row_Size ise dizideki satır sayısını temsil eder.
Depth_Size ise kolon ve satır ikilisinden kaç adet olacağını temsil eder.

Formülleri bilmek hoştur amma, uygulayabilmek başkadır başka diyerek formülü, yine pointer kullanımının mümkün olduğunu bildiğim dillerde kabaca denemeye çalıştım.
Formüller aynı şekilde, farklı sözdizimlerinde(syntax) kodlanmıştır.

2 Boyutlu Dinamik Array implementasyonu (C++) 3 Boyutlu Dinamik Array implementasyonu (C++) 2 Boyutlu Dinamik Array implementasyonu (delphi) 3 Boyutlu Dinamik Array implementasyonu (delphi) 2 Boyutlu Dinamik Array implementasyonu (C#) 3 Boyutlu Dinamik Array implementasyonu (C#) Güncel proje dosyalarına erişim :
Dynamic Array Implementation[Dimensional]
Dynamic Array Implementation[Three-Dimensional]

15 Ağustos 2019 Perşembe

373 KD

7 aralık 2018 de başlayıp, 23 mayıs 2019 tarihleri arasında, Kırklareli / Lüleburgaz  ve Tekirdağ / Çorlu'da 373. kısa dönem piyade er olarak  askerlik görevimi tamamladım.

Acemi birliğim rdm diyarı Kırklareli / Lüleburgaz 65.Mekanize Piyade Tugayı Pamir Kışlası idi.Her ne kadar rdm yeri olsa da ben kimseyle bir problem yaşamadım ve problemli birine denk gelmedim.

Balkan soğuğunu iliklerime kadar hissetim.Boğaz ve bademciklerim aşırı derecede şişti.Fakat acemilik boyunca bir kez bile revire çıkmadım.

Sabahın köründe başlayan, uygun adım ve istikamet akşam son buluyordu.Tabi o kadar eğitim ve istikamet yorgunluğu ardından yerde sürünmek çok daha rahat geliyordu.
Acemilik bu şekilde uygun adım, bol istikamet ve sürünmekle geçti.

Usta birliğinde ise bölüğüm Tekirdağ / Çorlu Emniyet muhafızdı.Pazartesi ve Cuma günleri tören kıyafetleri giyip, M1 tüfekle törene çıkılıyordu.Tören birliği olması nedeniyle m1 tüfek ile uygun adım eğitimleri  ve istikametler yine usta birliğinde de devam etti.

Son zamanlarda denetleme olacağı sebebiyle, koşu, şınav, barfiks ve atış eğitimleri sıklaştırıldı.Denetleme günü, iyi koşucu olmadığımı belirttiğim halde, koşu için seçtiler.30 yaş ve üzeri 16 dk içinde koşması gerekiyordu.Nizamiye girişinden başlayıp atış alanına kadar 3 km'lik bir mesafeyi ayağımda "bot" üzerimde atlet ve deri fabrikasından gelen bok kokusu ile birlikte 15 dk'da koşabildim.

80 kg olarak gidip, 69 kg olarak geri döndüm.
Askerliğin güzel yanı, kısa bir dönem de olsa, kafanızın rahat olması.

12 Temmuz 2019 Cuma

Dinamik Olarak Oluşturan Kodu Çalıştırmak ve JIT mimarisine Düşük Seviyeli Bir Bakış

Hafıza yönetim API'lerinden olan VirtualAlloc fonksiyonunun dokümantasyonunda aşağıdaki gibi açıklama var.

To execute dynamically generated code, use VirtualAlloc to allocate memory and the VirtualProtect function to grant PAGE_EXECUTE access.

Burada hepimizin anlayacağı üzere diyor ki;
Dinamik olarak oluşturulan kodu çalıştırmak için, VirtualAlloc fonksiyonu ile hafıza da tahsis yap ve VirtualProtect fonksiyonu ile de PAGE_EXECUTE yetkisi ver.

Tamam da burada dinamik olarak oluşturulan kod nasıl bir kod olmalı? Java , Delphi, C, C++, C# yoksa Javascript ya da başka bir programlama dili kodu mu?
Elbette hiçbiri değil, CPU'nun anlayacağı kodlar olmalı.

Öyle ise; CPU'nun anlayacağı kodları oluşturup, çalıştırmaya çalışalım. 5 + 4 işlemini CPU'nun anlayacağı şekle dönüştürüp, çalıştırmasını isteyeceğiz.
mov eax,5
add eax,4
ret
Yukarıdaki kodda eax register'a 5 değerini yazıyoruz.Daha sonra ise add komutu ile de üzerine 4 ekliyoruz. ret komutu ile de eax register'daki son değeri geriye döndürüyoruz.



Yanlız buraya kadar yazdığımız kodlar yine CPU'nun anlayabileceği kodlar değil. Yukarıda yazdığımız mov eax,5 ... gibi sembolik kodları VirtualAlloc fonksiyonu ile çalıştırmak istersek yine çalıştıramayacağız.
77C3DBA8 | B8 05 00 00 00           | mov eax,5                               |
77C3DBAD | 83 C0 04                 | add eax,4                               |
77C3DBB0 | C3                       | ret                                     |

Delphi Byte Array
Array [1..9] of Byte = (
$B8, $05, $00, $00, $00, $83, $C0, $04, $C3
);

C/C++ Byte Array
{
0xB8, 0x05, 0x00, 0x00, 0x00, 0x83, 0xC0, 0x04, 0xC3
};
O nedenle yukarıdaki görselde görüleceği üzere asm kodları yerine, byte karşılığını alıp, VirtualAlloc fonksiyonu ile hafıza da tahsis ettiğimiz alana yerleştireceğiz, sonrasında flProtect parametresine de Memory Protection Constants değerlerinden PAGE_EXECUTE ile başlayan değerlerden birini parametre olarak geçeceğiz. O zaman bu cek-caklı vaatlerimizi bir kenara bırakıp, kodlamaya başlayalım.

Aşağıda işletim sistemi api'lerinin kullanımına olanak veren, bildiğim dillerde hazırladığım örnekleri inceleyebilirsiniz.

C#
using System;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;

unsafe class Program
{
    // winnt.h    
    const int MEM_COMMIT = 0x00001000;
    const int MEM_RESERVE = 0x00002000;
    const int MEM_RELEASE = 0x00008000;
    const int PAGE_EXECUTE_READWRITE = 0x40;

    [DllImport("kernel32.dll", SetLastError = true)]
    unsafe static extern IntPtr VirtualAlloc(void* lpAddress, UIntPtr dwSize, int flAllocationType, int flProtect);

    [DllImport("kernel32.dll", SetLastError = true)]
    unsafe static extern bool VirtualFree(IntPtr lpAddress, UIntPtr dwSize, int dwFreeType);


    public delegate int FunctionPtr();
    static byte[] ByteCode = new byte[] { 0xB8, 0x05, 0x00, 0x00, 0x00, 0x83, 0xC0, 0x04, 0xC3 };

    const string FileName = "ByteCode.bin";
    const string ByteCodeURL = "https://github.com/ismailkocacan/Experiment/tree/master/JIT_ExecuteDynamicallyGeneratedCode/tests/" + FileName;

    static void Main(string[] args)
    {
        IntPtr P  = VirtualAlloc(null, (UIntPtr)ByteCode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        Marshal.Copy(ByteCode, 0, P, ByteCode.Length);
        FunctionPtr functionPtr = (FunctionPtr)Marshal.GetDelegateForFunctionPointer(P, typeof(FunctionPtr));
        int result = functionPtr();
        VirtualFree(P, (UIntPtr)ByteCode.Length, MEM_RELEASE);
    }

    static byte[] GetByteCode()
    {
        WebClient client = new WebClient();
        byte[] codeData = client.DownloadData(ByteCodeURL);
        return codeData;
    }

    static void SaveToFile()
    {
        File.WriteAllBytes(FileName, ByteCode);
    }
}


C
#include 

typedef int(*FunctionPtr)();

byte BYTE_CODE[] = { 0xB8, 0x04, 0x00, 0x00, 0x00, 0x83, 0xC0, 0x03, 0xC3 };
const int CODE_SIZE = sizeof BYTE_CODE;

int main()
{
 PVOID P = VirtualAlloc(NULL, CODE_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
 memcpy(P, BYTE_CODE, CODE_SIZE);
 FunctionPtr functionPtr = (FunctionPtr)P;
 int result = functionPtr();
 VirtualFree(P, CODE_SIZE, MEM_RELEASE);
}

Delphi
program Project2;
 
{$APPTYPE CONSOLE}
{$R *.res}
 
uses
 Winapi.Windows,
 System.SysUtils;
 
type
 TFunctionPtr = function: Integer;
 
const
 BYTE_CODE: Array [1 .. 9] of Byte = ($B8, $05, $00, $00, $00, $83, $C0, $04, $C3);
 CODE_SIZE = sizeof(BYTE_CODE);
 
var
 P: Pointer;
 Result: Integer;
 FunctionPtr: TFunctionPtr;
begin
 P := VirtualAlloc(nil, CODE_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
 CopyMemory(P, @BYTE_CODE, CODE_SIZE);
 FunctionPtr := TFunctionPtr(P);
 Result := FunctionPtr();
 VirtualFree(P, CODE_SIZE, MEM_RELEASE);
end.

Yukarıda yaptıklarımızı kısaca açıklayacak olursak;
- VirtualAlloc ile hafızada CODE_SIZE array'in boyu kadar yer açtık.

- PAGE_EXECUTE_READWRITE değerini geçerek bu hafıza bloğunun çalıştırabilir, okunup , yazılabilir olduğunu belirttik.

- CopyMemory ile çalıştırmak istediğimiz kodları hafızaya yerleştirdik.

- VirtualAlloc bize hafızada tahsis ettiği bloğunun başlagıç pointer'ını döndürmüştü.O adreside bir fonksiyon pointer'ına cast edip, çağırdık.

Konunun başlığı "Dinamik Olarak Oluşturan Kodu Çalıştırmak ve JIT mimarisine Düşük Seviyeli Bir Bakış" yazıyor ama ben debugger'da kodu kendim manuel yazdım.
Bu nasıl dinamik oluşturma diyebilirsiniz.Evet haklısınız.
Şimdi kodu dinamik olarak bize oluşturan bir Assembler'a ihtiyacımız var...
"Burada biz daha çok çalıştırma kısmını incelemiş olduk."

Dahası; .Net ve Java gibi run-time bağlı dillerde kullanılan bir teknik. Derlenmiş MSIL ya da Java Byte kodu tekrar CPU'nun anlayacağı koda dönüştürüp çalıştırma işi, işte bu şekilde gerçekleşiyor.Başka bir deyişle "JIT Compile" diye ifade edilmekte...

Şuanda aklıma gelen, gelmeyen pek çok farklı amaç için kullanılabilir.
Injection, dağıtık kod, sanal makine vs.
Kaynak kodlara buradan ulaşabilirsiniz.

8 Eylül 2018 Cumartesi

Offset Kavramı ve Dinamik Dizi Yapımı

Birçok programlama dilinde dinamik dizi kullanabiliyoruz.Dizilerde [] indexer operator yardımıyla dizi elemanlarına erişebiliyoruz.
C++
   int* myArray = new int[2];
   myArray[0] = 10;
   delete[] myArray;
C#
   int[] myArray = new int[2];
   myArray[0] = 10;
Java
   int[] myArray = new int[2];
   myArray[0] = 10;
Delphi
var
  MyArray: array of Integer;
begin
  SetLength(MyArray, 2);
  MyArray[0] := 10;
end.
Yukarıdaki örnekleri incelediğimizde C++, C#, Java gibi dillerde new operatörü, delphi için ise SetLength fonksiyonu yardımıyla dinamik dizileri kullanabiliyoruz.

C dilinde ise; C++, C#, java da bulunan new operatoru ya da delphi'deki gibi bir SetLength fonksiyonu bulunmuyor.Dolayısı ile dinamik dizimizi kendimiz gerçekleştirmemiz gerekiyor.
Aşağıdaki örnekte; 8 byte'lık bir hafıza bloğu tahsis edilip, myArray işaretçisinin [] indexer operatörü yardımıyla değerlere erişiyoruz.
 int* myArray = malloc(sizeof(int) * 2);
 myArray[0] = 10;
 myArray[1] = 20;
 free(myArray);
Sanırım nihayet konuya giriş yapabilmek için gerekli alt yapıyı sağlamış oldum :)
Peki dil C, C++, C#, java ya da delphi ya da bir başka dil farketmeksizin myArray[index] operatorü ile dizi elemanlarına erişmek istediğimde, hafıza bloğunda ilgili index'deki veriye nasıl eriştiğini biliyor muyum ? Bilmiyorum.

Bu yazıda dil seviyesinde index mantığını nasıl gerçekleştirebiliriz bunu anlamaya ve öğrenmeye çalışacağız. Bilindiği üzere C, C++, C#, Delphi gibi dillerde, işaretçileri(pointer), işaretçi aritmetiğini(pointer math) kullanabiliyoruz.Java'da ise yine bildiğim kadarıyla şuan için işaretçi kullanımına dil seviyesinde izin verilmiyor.

İşaretçi(pointer) kullanımına izin veren dillerde index'leme mantığını işaretçiler üzerinden verinin adresini hesaplayarak gerçekleştireceğiz. Dinamik bellek yöneten fonksiyonlar veriyi hafızada tahsis ettikten sonra geriye, veri bloğunun başlangıç adresini untyped pointer(void* veya PVOID veya LPVOID veya Pointer veya IntPtr) tipinde döndürmektedir. C
void *        _RTLENTRY _EXPFUNC malloc(_SIZE_T __size);
Windows API
WINBASEAPI
_Ret_maybenull_
_Post_writable_byte_size_(dwBytes)
DECLSPEC_ALLOCATOR
LPVOID
WINAPI
HeapAlloc(
    _In_ HANDLE hHeap,
    _In_ DWORD dwFlags,
    _In_ SIZE_T dwBytes
    );
C#
IntPtr p = Marshal.AllocHGlobal(size);
Delphi
procedure GetMem(var P: Pointer; Size: Integer);
function HeapAlloc(hHeap: THandle; dwFlags, dwBytes: DWORD): Pointer; stdcall;
UnTyped Pointer'larda aritmetik işlemlere izin verilmediği için, fonksiyonlardan dönen veri tipini typed pointer'lara çevireceğiz. Örneğin C dilinde aşağıdaki gibi untyped pointer tipinde ki p değişkeni için p += 4; işlemine izin verilmeyecektir.
void* p = malloc(sizeof(int) * 2);
p += 4;
[bcc32 Error] main.c(17): E2453 Size of the type 'void' is unknown or zero
Error C2036 'void *': unknown size

Örneğin Delphi dili için aşağıdaki gibi untyped pointer tipide ki P := P + 4; ya da Inc(P,4); işlemine izin verilmeyecektir.
var
  P: Pointer;
begin
  GetMem(P, SizeOf(Integer) * 2);
  P := P + 4;
end.
[dcc32 Error] Project2.dpr(60): E2015 Operator not applicable to this operand type
ya da
var
  P: Pointer;
begin
  GetMem(P, SizeOf(Integer) * 2);
  Inc(P, 4);
end.
[dcc32 Error] Project2.dpr(70): E2001 Ordinal type required
Dolayısı ile hafıza adreslerini index yoluyla hesaplayabilmemiz için typed pointerları kullanmamız gerekiyor.
C
 int* p = malloc(sizeof(int) * 2);
 *p = 10;
  p += 4;
 *p = 20;
free(p);
Delphi
var
  P: ^Integer; // ya da PInteger
begin
  GetMem(P, SizeOf(Integer) * 2);
  P^ := 10;
  Inc(P, 4);
  P^ := 20;
  FreeMem(P, SizeOf(Integer) * 2);
end;
C#
public static IntPtr Add(IntPtr pointer, int offset);
C#'da ise IntPtr(Pointer ya da Handle veri tipi için) yapısında bulunan Add methodu yardımıyla hesaplama yapabiliyoruz.
static void Main(string[] args)
{
  IntPtr p = Marshal.AllocHGlobal(sizeof(int) * 2);
  Marshal.WriteInt32(p, 10);
  IntPtr newP = IntPtr.Add(p, 4);
  Marshal.WriteInt32(newP, 20);
  Marshal.FreeHGlobal(p);
}
Yukarıdaki gibi hafıza bloğunun başlangıç adresini gösteren bir işaretçi üzerinde "doğrudan" değişiklik yapan p += 4; ya da P := P + 4; gibi aritmetik işlemler yazacağımız index'leme mantığı için çok doğru olmayabilir.O nedenle dinamik bellekte veriye işaret eden işaretçi üzerinde doğrudan bir değişiklik yapmadan yeni adresi hesaplayabiliriz.

Basit bir hesap ile formülümüz aşağıdaki gibi olacaktır.
Kaç Bayt = index X Veri tipinin kapladığı alan(Byte)
Yeni Adres = Başlangıç adresi + Kaç Byte

Kabaca başlangıç adresine kaç byte ekleyeceğimizi index yoluyla hesaplayarak verinin offset'ini hesaplamış olduk. Formülü kodlayacak olursak;
C
int index = 0;
int* p = malloc(sizeof(int) * 2);
int* newp = p + (index * sizeof(int));
*newp = 10;
free(p);
Delphi
var
  P, NewP: PInteger;
  Index: Integer;
begin
  Index := 0;
  GetMem(P, SizeOf(Integer) * 2);
  NewP := PInteger(PByte(P) + (Index * SizeOf(Integer)));
  NewP^ := 10;
  FreeMem(P, SizeOf(Integer) * 2);
end;
index değişkeninin değerini değiştirerek dizinin elemanlarına erişim sağlayabiliriz. Yazdığımız kodları eleştirecek olursak; Kodumuz çok da yakışıklı olmadı.
Örneğin; index * sizeof(int) yerine bir offset fonksiyonu yazabiliriz. C
int Offset(int index){
  return index * sizeof(int);
}
int* newp = p + Offset(index);
Delphi
function Offset(Index: Integer): Integer;
begin
  Result := Index * SizeOf(Integer);
end;
NewP := PInteger(PByte(P) + Offset(Index));
Dizinin eleman sayısından, hafızada tahsis edilecek veri bloğununun kaplayacağı alanı hesaplayan bir fonksiyon yazılabilir.
C
int* p = malloc(sizeof(int) * 2);
yerine
int MemSize(int count) {
   return sizeof(int) * count;
}
int* p = malloc(MemSize(2));
Delphi
GetMem(P, SizeOf(Integer) * 2); 
yerine
function MemSize(Count: Integer): Integer;
begin
  Result := SizeOf(Integer) * Count;
end;
GetMem(P, MemSize(2));
Eleştirmeye devam edecek olursak;
Şuana kadar yazdığımız kodların tamamı int veri tipi üzerine idi. Programlama sadece int veri tipinden ibaret değil ki ?
Başka veri tipleri için ne yapacağız ?
Aynı mantığı her veri tipi için tekrar tekrar mı yazacağız ?

İşte tam bu noktada, algoritmayı veri tipinden soyutlamak ya da bağımsız kılmak için, nesne yönelimli programlamanın sağladığı faydalardan yararlanabiliriz. Bunun için C++'da templates, C# ve Delphi'de generics'ler ile daha esnek kodlamalar yapabiliriz.Generics ve templates ile her ne kadar veri tipini esnek hale getirmiş olsak da, algoritmamız her veri tipi için uygun olmayabilir.Bu nokta da generics constraint'ler ile belli tipler için kurallar getirebiliriz. Tabi ki yine C'de untyped pointerlar ile daha generic fonksiyonlar mümkün.

Bahsettiğim generic haline Dynamic Array Implementation adresinden erişebilir, katkı sağlayabilir (mümkünse), gördüğünüz eksik ya da yanlışlar konusunda bilgilendirirseniz sevinirim.

Sağlıcakla.