Takip et

ARM Mimarisi’nde Fonksiyon Prolog ve Epilog: Bir Fonksiyonun Giriş ve Çıkışında Gerçekte Neler Olur?

Yüksek seviyeli programlama dillerinde bir fonksiyon çağırmak, genellikle basit bir işlem gibi görünür. Ancak, bu çağrının perde arkasında, işl…

ARM Mimarisi’nde Fonksiyon Prolog ve Epilog: Bir Fonksiyonun Giriş ve Çıkışında Gerçekte Neler Olur?

Yüksek seviyeli programlama dillerinde bir fonksiyon çağırmak, genellikle basit bir işlem gibi görünür. Ancak, bu çağrının perde arkasında, işlemcinin ve işletim sisteminin uyumlu bir şekilde çalışmasını sağlayan karmaşık mekanizmalar bulunur. Özellikle düşük seviyeli programlama ve gömülü sistemler dünyasında, ARM gibi mimarilerde fonksiyonların nasıl çağrıldığı ve geri döndüğü hayati önem taşır. İşte bu noktada “fonksiyon prolog” ve “fonksiyon epilog” kavramları devreye girer. Bu makalede, bir ARM işlemcide bir fonksiyonun girişinde ve çıkışında gerçekte neler olduğunu, yığın yönetimi, kayıt defteri kullanımı ve çağrı sözleşmeleri gibi temel mekanizmaları detaylı bir şekilde inceleyeceğiz.

Fonksiyon Çağrılarının Temelleri ve ARM Mimarisi

Bir programda fonksiyon çağırmak, kodun belirli bir bölümüne atlamak, bu bölümdeki işlemleri tamamlamak ve ardından çağrı yapılan noktaya geri dönmek anlamına gelir. Bu süreç, işlemci durumunun (register’lar) ve program akışının doğru bir şekilde yönetilmesini gerektirir. ARM mimarisi, bu yönetimi sağlamak için belirli kurallar ve donanım mekanizmaları sunar.

Fonksiyon Çağrı Mekanizması

Bir fonksiyon çağrıldığında, işlemci genellikle mevcut program sayacını (Program Counter – PC) bir yere kaydeder ve ardından çağrılan fonksiyonun başlangıç adresine atlar. Fonksiyonun işi bittiğinde, kaydedilen adrese geri dönülerek program akışı kaldığı yerden devam eder. Bu basit döngü, arka planda yığın (stack) ve kayıt defterleri (registers) kullanılarak desteklenir.

ARM Register Setine Genel Bakış

ARM mimarisi, genel amaçlı kayıt defterleri (R0-R12), yığın işaretçisi (Stack Pointer – SP, R13), bağlantı kayıt defteri (Link Register – LR, R14) ve program sayacı (Program Counter – PC, R15) gibi özel kayıt defterlerine sahiptir. Bu kayıt defterleri, fonksiyon çağrılarında kritik roller oynar:

  • SP (R13): Yığının en üstünü gösterir. Fonksiyon çağrılarında yerel değişkenler ve kaydedilen kayıtlar için alan tahsisinde kullanılır.
  • LR (R14): Bir fonksiyon çağrıldığında, çağrıdan sonra geri dönülecek adresi tutar.
  • PC (R15): Her zaman bir sonraki yürütülecek talimatın adresini gösterir.

Çağrı Sözleşmeleri (AAPCS)

Farklı fonksiyonlar ve modüller arasında uyumlu çalışmayı sağlamak için, ARM mimarisi genellikle “ARM Mimari Prosedür Çağrı Standardı” (Application Binary Interface for the ARM Architecture – AAPCS) gibi çağrı sözleşmelerini kullanır. Bu sözleşmeler, hangi kayıt defterlerinin hangi amaçla kullanılacağını, parametrelerin nasıl geçirileceğini ve dönüş değerlerinin nasıl alınacağını belirler. AAPCS, kayıt defterlerini “çağıranın sorumluluğunda kaydedilecek” (caller-saved) ve “çağrılanın sorumluluğunda kaydedilecek” (callee-saved) olarak ikiye ayırır. Bu ayrım, prolog ve epilog mekanizmalarının temelini oluşturur.

Fonksiyon Prolog Nedir ve Neden Gereklidir?

Fonksiyon prolog, bir fonksiyonun çağrılmasıyla birlikte, fonksiyonun kendi kodunu yürütmeye başlamadan önce gerçekleştirdiği bir dizi başlangıç işlemidir. Bu işlemler, fonksiyonun güvenli ve doğru bir şekilde çalışmasını sağlamak, çağıranın durumunu korumak ve fonksiyonun ihtiyaç duyduğu kaynakları hazırlamak için kritik öneme sahiptir.

Yığın Çerçevesinin Oluşturulması

Her fonksiyon çağrısı, yığında kendi “yığın çerçevesini” (stack frame) oluşturur. Prologun ilk adımlarından biri, bu çerçeveyi oluşturmak için yığın işaretçisini (SP) ayarlamaktır. Yığın çerçevesi, fonksiyonun yerel değişkenleri, kaydedilen kayıt defterleri ve bazen de parametreler için bellek alanı sağlar.

Çağrılan Fonksiyonun Kayıtları Kaydetmesi (Callee-saved registers)

AAPCS’ye göre, bazı kayıt defterleri (genellikle R4-R11 ve SP, LR) çağrılan fonksiyon tarafından korunmalıdır. Yani, eğer çağrılan fonksiyon bu kayıt defterlerini kullanacaksa, prolog aşamasında bu kayıt defterlerinin mevcut değerlerini yığına kaydetmesi gerekir. Bu, fonksiyonun işi bittiğinde bu kayıt defterlerinin orijinal değerlerine geri yüklenebilmesini ve çağıran fonksiyonun beklediği durumu bulmasını sağlar.

Yerel Değişkenler İçin Alan Tahsisi

Fonksiyonun içinde tanımlanan yerel değişkenler, genellikle yığın çerçevesi içinde saklanır. Prolog, bu değişkenler için yeterli alanı yığında tahsis etmek üzere SP’yi daha da düşürür (ARM’de yığın genellikle yüksek adreslerden düşük adreslere doğru büyür). Bu, fonksiyonun kendi çalışma alanı üzerinde güvenle işlem yapabilmesini sağlar.

Örnek Bir Prolog Kodu

Aşağıda, tipik bir ARM (32-bit) fonksiyon prologunun basitleştirilmiş bir örneği bulunmaktadır:


my_function:
    PUSH {R4-R7, LR}    ; R4-R7'yi (callee-saved) ve LR'yi yığına kaydet
                        ; LR'yi kaydetmek, bu fonksiyonun başka bir fonksiyonu çağırabileceği anlamına gelir.
    SUB SP, SP, #16     ; Yerel değişkenler için 16 bayt yer ayır (SP'yi düşür)
    ; ... fonksiyonun kendi kodu başlar ...

Bu örnekte, PUSH talimatı, belirtilen kayıt defterlerinin değerlerini yığına kaydeder ve SP'yi otomatik olarak günceller. SUB SP, SP, #16 ise yerel değişkenler için ek alan tahsis eder.

Fonksiyon Epilog Nedir ve Nasıl Çalışır?

Fonksiyon epilog, bir fonksiyonun işini bitirdikten sonra, çağıran fonksiyona geri dönmeden önce gerçekleştirdiği temizlik işlemleridir. Prologun yaptığı işlemlerin tam tersini yaparak, yığın çerçevesini temizler ve çağrının öncesindeki durumu geri yükler.

Yığın Çerçevesinin Temizlenmesi

Epilogun ilk adımlarından biri, prologda yerel değişkenler için ayrılan alanı serbest bırakmaktır. Bu, genellikle SP'yi prologda düşürüldüğü kadar artırarak yapılır. Böylece, yığın çerçevesi temizlenir ve yığın işaretçisi, kaydedilen kayıt defterlerinin olduğu noktaya geri döner.

Kaydedilen Kayıtların Geri Yüklenmesi

Prologda yığına kaydedilen tüm callee-saved kayıt defterleri ve LR, epilog aşamasında yığından geri yüklenir. Bu, çağıran fonksiyonun bu kayıt defterlerinin değerlerinin değişmediğini garanti eder. POP talimatı bu işlemi kolaylaştırır.

Fonksiyonun Geri Dönüşü

Tüm temizlik işlemleri tamamlandıktan sonra, fonksiyonun çağrıldığı yere geri dönmesi gerekir. Bu genellikle, prologda yığına kaydedilen ve epilogda geri yüklenen LR'deki adrese atlayarak yapılır. ARM'de bu, genellikle POP {..., PC} veya BX LR gibi talimatlarla gerçekleştirilir.

Örnek Bir Epilog Kodu

Yukarıdaki prolog örneğine karşılık gelen tipik bir ARM epilog kodu:


    ; ... fonksiyonun kendi kodu biter ...
    ADD SP, SP, #16     ; Yerel değişkenler için ayrılan alanı serbest bırak
    POP {R4-R7, PC}     ; R4-R7'yi ve LR'yi (PC olarak geri yüklenir) yığından geri yükle
                        ; POP {..., PC} talimatı aynı zamanda LR'deki adrese atlar (fonksiyondan döner)

Burada ADD SP, SP, #16 yerel değişken alanını serbest bırakır. POP {R4-R7, PC} ise kaydedilen kayıt defterlerini geri yükler ve LR'deki değeri PC'ye yükleyerek fonksiyonun çağrıldığı yere geri dönmesini sağlar. (Unutmayın, POP {..., PC} aslında LR'nin içeriğini PC'ye yükler ve bu da fonksiyon dönüşünü tetikler).

ARM'de Yığın Yönetimi ve Çağrı Sözleşmeleri (AAPCS)

Yığın, fonksiyon çağrılarının ve yerel değişkenlerin yönetiminde merkezi bir rol oynar. ARM mimarisinde yığın, genellikle yüksek bellek adreslerinden düşük adreslere doğru büyür. Bu, yığın işaretçisinin (SP) bir şeyler yığına eklendiğinde azalması, yığından bir şeyler çıkarıldığında ise artması anlamına gelir.

Yığın İşaretçisi (SP) ve Yığın Büyüme Yönü

ARM'de yığın işaretçisi (SP, R13), her zaman yığının "en üstünü" (yani en düşük adresli, son eklenen öğenin bulunduğu yeri) gösterir. Prolog, yığın çerçevesi oluştururken SP'yi düşürürken, epilog SP'yi artırarak yığını temizler. Bu tutarlı büyüme ve küçülme, fonksiyon çağrılarının güvenli bir şekilde iç içe geçmesini sağlar.

Çağrı Sözleşmelerinin Önemi (Caller/Callee-saved registers)

AAPCS, kayıt defterlerini iki kategoriye ayırarak yığın yönetimini basitleştirir:

  • Caller-saved (Çağıranın Sorumluluğunda Kaydedilecek): R0-R3 gibi kayıtlar, bir fonksiyon çağrılmadan önce çağıran tarafından kaydedilmelidir, eğer çağıran bu değerlere daha sonra ihtiyaç duyacaksa. Çağrılan fonksiyon bu kayıtları serbestçe kullanabilir.
  • Callee-saved (Çağrılanın Sorumluluğunda Kaydedilecek): R4-R11 gibi kayıtlar, eğer çağrılan fonksiyon bu kayıtları kullanacaksa, prologunda yığına kaydetmeli ve epilogunda geri yüklemelidir. Bu, çağıranın bu kayıtların değerlerinin çağrı boyunca değişmeyeceğine güvenebileceği anlamına gelir.

Bu ayrım, gereksiz kayıt kaydetme/geri yükleme işlemlerini azaltarak performansı artırır.

Frame Pointer (FP) Kullanımı ve Önemi

Bazı durumlarda, özellikle hata ayıklama ve yığın izleme (stack tracing) için, bir "çerçeve işaretçisi" (Frame Pointer - FP, genellikle R11) kullanılır. FP, yığın çerçevesinin sabit bir noktasına (genellikle LR'nin veya kaydedilen ilk kayıt defterinin adresi) işaret eder. SP, fonksiyonun yürütülmesi sırasında yerel değişkenlerin tahsisiyle değişebilirken, FP sabittir. Bu, hata ayıklayıcıların veya istisna işleyicilerin yığın çerçeveleri arasında kolayca geçiş yapmasına ve fonksiyon çağrı zincirini yeniden oluşturmasına olanak tanır. Modern derleyiciler, optimizasyon amacıyla FP kullanımını bazen atlayabilir.

Optimizasyonlar ve Derleyici Rolü

Derleyiciler, C/C++ gibi yüksek seviyeli dillerdeki kodları ARM assembly'ye çevirirken, fonksiyon prolog ve epiloglarını otomatik olarak oluşturur. Ancak, her fonksiyon için aynı standart prolog/epilog'u kullanmak yerine, derleyiciler kodu optimize etmek için çeşitli stratejiler uygular.

Leaf Fonksiyon Optimizasyonları

Bir "leaf fonksiyon" (yaprak fonksiyon), başka hiçbir fonksiyonu çağırmayan bir fonksiyondur. Bu tür fonksiyonlar için derleyici, LR'yi yığına kaydetme ihtiyacını ortadan kaldırabilir, çünkü LR'nin değeri hiçbir zaman üzerine yazılmayacaktır. Bu, yığın işlemlerini azaltarak performansı artırır.

Inline Fonksiyonlar ve Prolog/Epilog Olmaması

inline anahtar kelimesiyle işaretlenmiş fonksiyonlar veya derleyicinin otomatik olarak inline etmeye karar verdiği fonksiyonlar, çağrı noktasında doğrudan çağrılan fonksiyonun koduyla değiştirilir. Bu durumda, ayrı bir fonksiyon çağrısı yapılmadığı için prolog ve epilog da oluşturulmaz. Bu, fonksiyon çağrısı yükünü tamamen ortadan kaldırır ancak kod boyutunu artırabilir.

Derleyicinin Prolog/Epilog Üretimi

Derleyiciler, hangi kayıt defterlerinin kullanıldığını, kaç bayt yerel değişken alanına ihtiyaç duyulduğunu ve fonksiyonun başka fonksiyonları çağırıp çağırmadığını analiz ederek en verimli prolog ve epilog dizilerini oluşturur. Optimizasyon seviyeleri (örneğin, -O0, -O1, -O2, -O3), bu dizilerin karmaşıklığını ve boyutunu önemli ölçüde etkileyebilir. Daha yüksek optimizasyon seviyeleri, daha az yığın işlemi ve daha az kayıt kaydetme/geri yükleme ile sonuçlanabilir.

Hata Ayıklama ve Geri İzleme (Stack Tracing)

Prolog ve epilog tarafından oluşturulan yığın çerçeveleri, bir programın hata ayıklanması ve çalışma zamanı hatalarının teşhis edilmesi için vazgeçilmezdir. Bir hata (örneğin, segmentasyon hatası) meydana geldiğinde, "yığın izi" (stack trace) oluşturularak hatanın hangi fonksiyon çağrı zincirinde meydana geldiği belirlenir.

Prolog/Epilog ve Hata Ayıklama

Yığın çerçeveleri, hata ayıklayıcının her bir fonksiyon çağrısının başlangıç adresini, geri dönüş adresini (LR) ve yerel değişkenlerini bulmasına olanak tanır. Prologda kaydedilen kayıt defterleri, fonksiyonun çağrıldığı andaki CPU durumunu yeniden oluşturmaya yardımcı olur.

Yığın İzlemenin Temelleri

Bir yığın izi, genellikle mevcut fonksiyonun SP'sinden başlayarak, her bir yığın çerçevesini yukarı doğru (daha yüksek adreslere doğru) takip ederek oluşturulur. Her çerçevede, bir önceki fonksiyonun geri dönüş adresi ve varsa çerçeve işaretçisi bilgisi bulunur. Bu bilgiler kullanılarak, programın hangi fonksiyonlardan geçtiği ve hatanın nerede meydana geldiği tespit edilebilir.

Frame Pointer'ın Hata Ayıklamadaki Rolü

Eğer bir fonksiyon çerçeve işaretçisi (FP) kullanıyorsa, hata ayıklama süreci çok daha kolay hale gelir. FP, yığın çerçevesi içinde sabit bir referans noktası sağladığı için, hata ayıklayıcı SP'nin değişkenliğinden etkilenmeden bir önceki yığın çerçevesine kolayca geçebilir. Bu, özellikle karmaşık ve dinamik yığın yapılarına sahip programlarda hata ayıklamayı basitleştirir. Bu nedenle, bazı hata ayıklama derlemelerinde (örneğin, -O0 veya -Og), derleyiciler FP kullanımını zorunlu tutabilir.

Sonuç

ARM mimarisinde fonksiyon prolog ve epilog, bir fonksiyonun giriş ve çıkışında gerçekleşen kritik işlemlerdir. Bu mekanizmalar, programın doğru ve güvenli bir şekilde çalışmasını sağlamak, işlemci durumunu korumak ve yığın belleğini verimli bir şekilde yönetmek için temel taşlardır. Yığın çerçevelerinin oluşturulması, kayıt defterlerinin kaydedilmesi ve geri yüklenmesi, yerel değişkenler için alan tahsisi gibi adımlar, her fonksiyon çağrısının arkasındaki gizli kahramanlardır. AAPCS gibi çağrı sözleşmeleri, bu süreçlerin standartlaştırılmasını sağlayarak farklı kod parçalarının sorunsuz bir şekilde bir araya gelmesini mümkün kılar. Derleyicilerin optimizasyonları ve hata ayıklama araçlarının bu mekanizmaları kullanma biçimi, yazılım geliştirme sürecinde verimlilik ve güvenilirlik açısından büyük önem taşır. ARM sistemlerinde düşük seviye programlama yapan veya sistem düzeyinde sorunları çözen herkes için bu kavramları anlamak vazgeçilmezdir.

SSS (Sık Sorulan Sorular)

S1: Prolog/Epilog her fonksiyon için gerekli midir?

Hayır, her fonksiyon için zorunlu değildir. Özellikle derleyiciler tarafından "inline" edilen fonksiyonlar veya bazı "leaf" (başka fonksiyon çağırmayan) fonksiyonlar için prolog/epilog tamamen atlanabilir veya basitleştirilebilir. Ancak, çoğu standart fonksiyon çağrısı için yığın ve kayıt yönetimi amacıyla gereklidir.

S2: Leaf fonksiyonlarda prolog/epilog farklı mıdır?

Evet, leaf fonksiyonlar (başka fonksiyon çağırmayan fonksiyonlar) için prolog ve epilog genellikle daha basittir. Özellikle LR (Link Register) kaydetme ihtiyacı ortadan kalkabilir, çünkü LR'nin değeri fonksiyon içinde değişmeyecektir. Bu, daha az yığın işlemi ve dolayısıyla daha hızlı kod anlamına gelir.

S3: Neden bazı kayıtlar çağıran (caller) bazıları ise çağrılan (callee) tarafından kaydedilir?

Bu ayrım, çağrı sözleşmeleri (AAPCS) tarafından belirlenir ve performansı optimize etmek için yapılır. Çağıranın kaydettiği kayıtlar (caller-saved), çağrılan fonksiyonun bu kayıtları serbestçe kullanabilmesini sağlar. Çağrılanın kaydettiği kayıtlar (callee-saved) ise, çağıranın bu kayıtların değerlerinin fonksiyon çağrısı boyunca korunacağına güvenmesini sağlar. Bu, her iki tarafın da yalnızca gerçekten ihtiyaç duyduğu kayıtları kaydetmesini sağlayarak gereksiz yığın işlemlerini azaltır.

S4: Yığın taşması (stack overflow) prolog/epilog ile nasıl ilişkilidir?

Yığın taşması, bir programın yığın üzerinde ayrılan bellek alanından daha fazlasını kullanmaya çalıştığında meydana gelir. Her fonksiyon çağrısı, prolog aracılığıyla yığında bir yığın çerçevesi oluşturur. Eğer fonksiyonlar çok derinlemesine iç içe çağrılırsa veya çok büyük yerel değişkenler tahsis edilirse, yığın alanı tükenebilir ve bu da yığın taşmasına yol açar. Prolog ve epilog, yığın yönetiminin temelini oluşturduğu için, yığın taşması doğrudan bu mekanizmaların yanlış veya aşırı kullanımından kaynaklanabilir.

S5: ARM64'te prolog/epilog mekanizması farklılık gösterir mi?

ARM64 (AArch64) mimarisi, ARM32'ye (AArch32) göre bazı farklılıklar gösterse de, prolog ve epilogun temel prensipleri aynı kalır. Kayıt defteri kullanımı, yığın yönetimi ve çağrı sözleşmeleri (ARM64 için farklı bir ABI mevcuttur) güncellenmiştir. Örneğin, ARM64'te daha fazla genel amaçlı kayıt defteri (X0-X30) bulunur ve yığın işaretçisi (SP) her zaman 16 bayt hizalı olmalıdır. Ancak, bir fonksiyonun girişinde kayıtları kaydetme ve yığın alanı ayırma, çıkışında ise bunları geri yükleme ve yığını temizleme mantığı aynıdır.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık