Her şeyden önce, geçmişe - 16-bit dönemine - geri dönmemiz ve bellek segmentasyonuna bir göz atmamız gerekiyor. Bugün modern işlemcilerde hala var olan ancak long mode üzerinde çalışırken x64 işlemcilerde neyse ki göz ardı edilen bir özellik. Buna göz atmadan önce, 3 temel bellek modeli olduğunu belirtmek gerek:
  1. Fiziksel - Physical.
  2. Düz - Flat.
  3. Bölümlenmiş - Segmented. - Segmentasyon.
Bununla birlikte, işlemcinin içinde bulunabileceği 3 çalışma modu vardır:
  1. Gerçek mod - Real Mode.
  2. Korumalı mod - Protected Mode.
  3. Sistem yönetim modu. (SMM.)
=Bellek Modellerindeki Farklılıklar=

Adından da anlaşılacağı gibi, Fiziksel Bellek Modeli sadece Fiziksel Belleği ifade eder. Doğrudan RAM'e yazılabilir ve kesinlikle hiçbir koruma mekanizması bulundurmaz. Mevcut fiziksel belleğin tamamını adreslediğimizde, sistem çökecek ve çalışmayı durduracaktır.

Düz bellek modeli ise sanal bellek kavramını ekler. Artık adres alanı, her bir baytın doğrudan fiziksel bellekteki bir bayta karşılık geldiği tek bir bitişik sıralı bayt bölgesidir. Düz bellek modelindeki her adres, Linear Memory adres olarak bilinir. Türkçe olarak bu genellikle doğrusal adres alanı olarak adlandırılır.

Bölümlenmiş Bellek Modeli ise adres alanını bölümler ve ardından adı verilen farklı bölgelere ayırarak düz bellek modelinin üzerine inşa eder. Her adres, offset adres ve segmentin base adresi (Segmentin bellekteki fiziksel başlangıç adresi.) birleştirilerek belirlenir. Bu, mantıksal adres olarak bilinir ve bazı durumlarda far pointer olarak da adlandırılır. Base adres, Segment Selector adı verilen özel bir kayıtta saklanır. Offset adres ise Segment Selector tarafından referans verilen segmente bir offsettir.

* Segmentasyon - Bölümleme, özellikle eski x86 mimarilerinde yaygındı ve modern 64-bit işlemcilerde daha az kullanılır hale gelmiştir. Günümüzde, segmentasyon yerine daha çok sayfalama (paging) kullanılmaktadır.*

Sayfalama modelini ayrı bir bellek modeli olarak düşünebilirsiniz, ancak Flat ve Segmented bellek modelleri üzerine inşa edildiğinden, bu bellek modellerinin bir uzantısı olarak adlandırmak daha basit olur. Ayrıca, Windows'un bölümlere ayrılmış bellek modelini kullanmasına rağmen, işlem adres alanının ayrıntılarının geliştiriciden soyutlandığını ve bir işlemin işlem adres alanının düz olduğunu belirtmek de önemlidir. Bu durum özellikle işlemci long mod üzerinde çalıştığında geçerlidir. İşlemcinin farklı çalışma modları da ek olarak değerlendirilecektir.

=Modlardaki Farklılıklar=

Gerçek Mod, bilgisayar ilk açıldığında işlemcinin içinde bulunduğu ilk moddur. Bu, işletim sistemi yüklenmeden önce gerçekleşir. Bölümlere ayrılmış bellek modelini uygular ve çok sınırlı bellek koruması vardır. Bu nedenle bu modda sistemi bozmak çok kolaydır. Adres alanı 20 bit ile sınırlıdır ve bu nedenle sadece 1MB RAM adreslenebilir. Şimdi, her sanal (mantıksal) adres iki ayrı parçaya bölünmüştür: 16 bitlik bir Segment Selector ve 16 bitlik bir Offset. Daha önce de belirtildiği gibi, Segment Selector, segmentin temel adresini ifade eder ve offset bu segmente indekslemek için kullanılan etkin adrestir. Bu daha sonra RAM'deki fiziksel bir bellek adresine eşlemek için kullanılır.

Gerçek Modda çalışırken, her bir kaydın boyutu 16 bit ile sınırlıdır. Kayıtlar çoğunlukla x64'teki benzerlerinin kısaltılmış versiyonlarıdır, ancak halefinde mevcut olan birçok kaydın Gerçek Modda mevcut olmadığını da belirtmekte fayda var. Aklımızda tutmamız gereken birkaç önemli kayıt vardır. Segment kayıtları özellikle önemlidir çünkü segmentli bellek modelini kullanırken kilit öneme sahiptirler.

CS - Code Segment olarak adlandırılır - O anda yürütülmekte olan kod bloğu için Segment Selector'ün adresini içerir.

DS - Data Segment olarak adlandırılır - Programın verileri için Segment Selector'ün adresini içerir.

SS - Stack Segment olarak adlandırılır - Programın yığını için Segment Selector'ün adresini içerir.

ES - Extra Segment olarak adlandırılır - Programın gerektirmesi halinde ek bir Data veya Code segment olarak işlev görür.

Ayrıca özel bir anlamı olmayan ve işletim sistemi tarafından istendiği gibi kullanılan Global Segment ve File Segment gibi segmentler de vardır. Windows x86-x64'te GS segmenti, Thread Environment Block (TEB) taban adresini saklamak için kullanılır.

[CODE highlight="9"]10: kd> r
rax=0000000000000000 rbx=8000000000000000 rcx=000000000000001a
rdx=0000000000041792 rsi=ffffa2bffc7f4588 rdi=ffffe388cc21b4b0
rip=fffff807433fdb50 rsp=ffffe388cc21af88 rbp=ffffe388cc21b029
r8=ffffa2bffc7f4588 r9=8000000000000000 r10=0000fffff8074328
r11=ffffa2d168b457f8 r12=0000000000000082 r13=ffffe388cc21b560
r14=0000000000000000 r15=00007ff8fe8b1000
iopl=0 nv up ei pl zr na po nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00040246
nt!KeBugCheckEx:
fffff807`433fdb50 48894c2408 mov qword ptr [rsp+8],rcx ss:0018:ffffe388`cc21af90=000000000000001a
[/CODE]

Segment Selector'lerini saklayan segment kayıtlarına ek olarak belirli bir segment için etkin adresi (ofset) saklamak için kullanılan işaretçi kayıtları da vardır.

IP -The Instruction Pointer - O anda yürütülmekte olan komutun adresini içerir ve Code Segment ile birlikte kullanılır.

SP - Stack Pointer - Geçerli yığın çerçevesinin üst kısmının adresini içerir ve bu nedenle Data Segment birlikte kullanılır.

BP - The Base Pointer - Geçerli yığın çerçevesinin en altına işaret eder ve Stack Segment birlikte kullanılır.

intel-flat-model.webp


Korumalı Mod - Protected Mode, işlerin biraz daha karmaşık hale gelme eğiliminde olduğu yerdir. Bölümlere ayrılmış bellek modeli hala kullanılmaktadır, ancak daha ayrıntılı bellek koruması sağlamak için bunun üzerine sayfalama eklenmiştir. x64 sistemlerde Korumalı Mod, Longe Mode'un eklenmesiyle daha da genişletilmiştir. Bu noktada dikkat edilmesi gereken en önemli fark, Long Mode'un tüm segment kayıtlarının (GS ve FS hariç) temel adresini 0'a ayarlamasıdır; bu da etkin bir şekilde, adres alanının aslında sayfalamanın eklenmesiyle daha düz bir bellek modeli olduğu anlamına gelir. Şimdilik sadece Korumalı Mod'un segmentli yönüne odaklanacağız ve sayfalama etkinleştirilmemiş gibi davranacağız.

Daha önce belirtildiği gibi aynı segment seçiciler mevcuttur, ancak artık fiziksel bir adres alanına eşlemek için kullanılmazlar. Bunun yerine x86, Global Descriptor Table Register (GDTR) ve Local Descriptor Table Register (LDTR) olmak üzere iki kayıt tanıtmıştır. Local Descriptor Table Windows'ta kullanılmaz ve bu nedenle onu yok sayacağız ve yokmuş gibi davranacağız. GDTR, Global Descriptor Table'ın base adresini içerir. Tablodaki her giriş segment tanımlayıcı olarak bilinir ve doğrusal adres alanındaki belirli bir segmenti tanımlamak için kullanılır. Segment seçici kayıtları Global Descriptor Table'ı (GDT) indekslemek için kullanılır.

Segment seçici hala 16 bitliktir, ancak etkin adres artık bunun yerine 32 bite genişletilmiştir. Doğrusal adres daha önce olduğu gibi aynı şekilde oluşturulur, önemli bir farkla, base adres, segment tanımlayıcıdan alınır ve daha sonra etkin adresle birleştirilir. Elbette bu durumda, doğrusal adres artık 32 bittir ve x86 işletim sistemlerinin 4 GB'a kadar RAM'i adresleyebilmesinin nedeni budur. Sayfalama ile, doğrusal adres daha sonra sayfa tabloları kullanılarak fiziksel bir adrese çevrilir.

Ekran görüntüsü 2024-06-28 021642.webp


GDTR, diğer kayıtlar gibi r komutu ile incelenebilir.

Kod:
10: kd> r gdtr
gdtr=ffff8100be945fb0

Alternatif olarak, GDT'nin base adresi her işlemci için PCR yapısı içinde bulunabilir.

Kod:
10: kd> dt _KPCR -y GdtBase ffff8100bea45000
nt!_KPCR
+0x000 GdtBase : 0xffff8100'be945fb0 _KGDTENTRY64

Tablonun boyutu GDTL yazmacının aşağıdaki gibi dökümü alınarak bulunabilir:

Kod:
10: kd> r gdtl
gdtl=0057

Belirli bir segment tanımlayıcısını dökmek için, dökmek istediğiniz Segment Selector'ün adıyla birlikte dg komutunu kullanmamız gerekiyor.

Kod:
10: kd> dg 0x33
                                                    P Si Gr Pr Lo
Sel        Base              Limit          Type    l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
0033 00000000`00000000 ffffffff`ffffffff Code RE Ac 0 Bg Pg P  Lo 00000e9b

İlk olarak, Temel Adres 0'dır, bu da Longe Mode'da çalışan x64 sistemlerde beklediğimiz şeydir. Temel adres, daha önce kısaca değindiğim gibi neredeyse tüm segmentler için bu şekildedir. Type alanı segment tanımlayıcısının türünü tanımlar ve bu örnekte segment tanımlayıcısı bir kod segmenti kaydı, yani CS kaydı içindir. Diğer iki önemli alan ise Privilege Level ve Present field'dirler. Privilege Level, segmente hangi halka seviyesinde erişilebileceğine karşılık gelen Descriptor Privilege Level'i (DPL) tanımlar. Mevcut alanı segmentin fiziksel bellekte yerleşik olup olmadığını gösterir.

Aslında segment seçici belirli bir formatı izler.

[CODE lang="rich" highlight="2"][...]
Binary: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00110011
[...]
Double: 2.51973e-322[/CODE]

Vurgulanan ilk iki bit requested privilege level'i (RPL) gösterir, bu durumda requested privilege level ring 3 veya User-Mode'unun temsilidir. Üçüncü bit, seçicinin GDT'ye mi (0) yoksa LDT'ye mi (1) karşılık geldiğini gösteren bir bayraktır. Son olarak, son 13 bit bir dizin olarak ayrılmıştır. İndeks işlemci tarafından alınır ve 16 ile çarpılır (x86 sistemlerinde 8), bu daha sonra sırasıyla GDT veya LDT'nin adres tabanına eklenir; tablo gösterge bitinin ayarlanıp ayarlanmadığına, yani seçicinin GDT veya LDT'ye ait olup olmadığına bağlı olarak. Bir segment veya bellek bölümü seçicisine (selector) karşılık gelen bellek tanımlayıcısının (descriptor) adresini sağlayacaktır.

? (dizin * 0n16) + taban

Ekran görüntüsü 2024-06-28 023719.webp


*Buraların tamamıyla alıntı olduğunu ve doğrudan konuyla alakalı olmadığını da belirtmem gerekecek. Daha fazlası için --> The 0x33 Segment Selector

GDT'nin ilk girişi her zaman bir Null Segment tanımlayıcısına işaret eder. Bu özel bir girdidir ve bir segment kaydı ona ayarlanırsa ve daha sonra bir bellek bölgesine erişmek için kullanılırsa bir istisna oluşturmak için kullanılır.

Kod:
10: kd> dg 0x00
                                                    P Si Gr Pr Lo
Sel        Base              Limit          Type    l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
0000 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000

Daha önce de söylediğim gibi, GDT içindeki her giriş bir segment tanımlayıcısına karşılık gelir. Her tanımlayıcı _KGDTENTRY64 adı verilen bir birlik türü ile temsil edilir. Bu, dg komutunun kendisine aktarılan segment seçiciyi ayrıştırmak için kullandığı yapıdır.

Kod:
10: kd> dt _KGDTENTRY64
nt!_KGDTENTRY64
   +0x000 LimitLow         : Uint2B
   +0x002 BaseLow          : Uint2B
   +0x004 Bytes            : <anonymous-tag>
   +0x004 Bits             : <anonymous-tag>
   +0x008 BaseUpper        : Uint4B
   +0x00c MustBeZero       : Uint4B
   +0x000 DataLow          : Int8B
   +0x008 DataHigh         : Int8B

Buraya kadar Windows'ta segmentasyonun kullanımını ve adres çevirisinde nasıl kullanıldığını anlatmaya ve alıntılamaya çalıştım. Sayfalama ve segmentasyonun birlikte nasıl kullanıldığını da göz atalım.

Ekran görüntüsü 2024-06-28 024334.webp


--> Effective, Logical, Linear, Virtual, Physical Address of an Intel CPU's. - Intel Processor Architecture Manual.

Kaynaklar : 0XC5, Code Machine, Windows Learn, Marcus Hutchins, Dejan Lukan, Many But Finite i.e.