23 Hata Ayıklama Kütüphanesi

23 Hata Ayıklama Kütüphanesi

Hata ayıklama kütüphanesi size Lua için bir hata ayıklayıcı(debugger) vermez ama kendi hata ayıklayıcınızı yazmak için ihtiyaç duyduğunuz tüm temel imkanları sunar. Performans nedenleriyle bu temel imkanların resmi arayüzü C API üzerindendir. Lua debug kütüphanesi Lua kodu içinden onlara  direkt erişim yoludur.

Diğer kütüphanelerin aksine debug kütüphanesini aşırı titiz kullanmalısınız. Birincisi, işlevlerinden bazıları performans bakımından çok meşhur değildir. İkincisi, yerel bir değişkene onu oluşturan fonksiyonun dışından erişemezsiniz gibi dilin bazı kutsal ilkelerini kırar. Çoğunlukla bu kütüphaneyi programınızın nihai sürümünde aktif etmek istemeyebilir veya debug=nil işleterek ondan kurtulmak isteyebilirsiniz.

debug kütüphanesi iki tip fonksiyon içerir: İçgözlem fonksiyonlar ve kancalar(hook). İçgözlem fonksiyonları, çalışan programın aktif fonksiyonlarının yığınını, geçerli yürütme satırı, yerel değişkenlerin adları ve değerleri gibi çeşitli yönlerini yoklamanıza olanak tanır. Kancalar, programın yürütülmesini izlemenize(trace) imkan verir.

debug kütüphanesinde önemli bir kavram yığın seviyesidir. Yığın seviyesi o anda aktif olan çağrılmış ve henüz dönüş yapmamış olan belirli bir fonksiyona referans olan bir sayıdır. debug kütüphanesini çağıran fonksiyon seviye 1'e, onun çağırdığı fonksiyon seviye 2 'e sahiptir, vb gider.


23.1 İçgözlem imkanlar

debug kütüphanesindeki ana içgözlem fonksiyonu debug.getinfo fonksiyonudur. İlk parametresi, bir fonksiyon veya bir yığın seviyesi olabilir. Misal foo isimli bir fonksiyon için debug.getinfo(foo) çağırdığınızda bu fonksiyon hakkında bazı veriler içeren bir tablo elde edersiniz. Tablo aşağıdaki alanlara sahip olabilir:

source: Fonksiyonun tanımlandığı yer. Fonksiyon bir stringde tanımlanmışsa (loadstring aracılığıyla) source bu stringdir. Fonksiyon bir dosyada tanımlanmışsa source '@' önekli dosya adıdır.

short_src:  source'nın kısa bir versiyonu (60 karaktere kadar), hata mesajları için kullanışlı.

linedefined: Fonksiyonun tanımlandığı source'nin ilk satırı.

lastlinedefined: Fonksiyonun tanımlandığı source'nin son satırı.

what: Bu ne fonksiyonu.   Seçenekler, foo bir Lua fonksiyonu ise “Lua”  bir C fonksiyonu ise “C” ya da  bir Lua öbeğinin ana parçası ise “main”.

name: Fonksiyon için lojikal isim.

namewhat: Önceki alanla alakalı olarak bu neyin ismi. Bu alan “global”, “local”, “method”, “field” veya “” (boş string) olabilir. Boş string, Lua fonksiyon için bir ad bulamadığı anlamına gelir.

nups: Bu fonksiyonun üst değerlerinin(upvalue) sayısı.

activelines: Fonksiyonun aktif satırlar kümesini temsil eden bir tablo. Aktif satır,  boş satır veya sadece yorum değil bazı kodlara sahip satırdır. (Bu bilgilerin tipik kullanımı kesme(break) set etmek içindir. Çoğu debugger,  erişilemez olduğu için aktif satır dışına kesme atmanıza izin vermez.)(activelines alanı Lua 5.1'de yeni).

func: Fonksiyonun kendisi; sonra göreceğiz.

foo bir C fonksiyonu olduğunda Lua onun hakkında fazla bilgiye sahip olamıyor. Bu tür fonksiyonlar için yalnızca what, name ve namewhat alanları elle tutulur.

bir n sayısı ile debug.getinfo(n)'ı çağırdığınızda bu yığın seviyesinde aktif olan fonksiyon hakkında veri elde edersiniz. Örneğin n 1 ise çağrı yapılan fonksiyon hakkında veri elde edersiniz. (n 0 olduğunda  bir C fonksiyonu olan getinfo'nun kendisi hakkında veri elde edersiniz.) n, yığındaki aktif fonksiyonların sayısından büyükse debug.getinfo nil döndürür. Bir sayı ile debug.getinfo çağırarak aktif fonksiyon sorgulaması yaptığınızda sonuç tablosu, fonksiyonun o anda olduğu satırı gösteren currentline adlı ekstra bir alana sahip olur. Dahası, func bu seviyede aktif olan fonksiyona sahip olur.

name alanı yanıltıcıdır. Unutmayın ki fonksiyonlar Lua'da birinci sınıf değerler olduğundan bir fonksiyonun bir adı olmayabilir veya birkaç adı olabilir. Lua, nasıl çağrıldığı görmek için fonksiyonun çağrıldığı koda bakarak fonksiyon için ad bulmaya çalışır. Bu yöntem yalnızca bir sayıyla getinfo çağırdığımızda işler yani özgül bir çağrı hakkında bilgi elde ederiz.

getinfo fonksiyonu efektif değildir. Lua, hata ayıklama bilgisini program yürütülmesini etkilemeyen bir formda tutar; efektif bir veri besleme ikincil hedeftir. Daha iyi performans elde etmek için getinfo, hangi bilginin alınacağının seçildiği, opsiyonel bir ikinci parametreye sahiptir. Bu parametreyle fonksiyon, kullanıcının ihtiyaç duymadığı verileri toplamak için zaman kaybetmez. Bu parametrenin formatı, her harfin devamdaki tabloya göre alanlar grubunu seçtirten bir stringdir:

'n'     name ve namewhat seçilir
'f'     func seçilir
'S'     source, short_src, what, linedefined, ve lastlinedefined seçilir
'l'     currentline seçilir
'L'     activelines seçilir
'u'     nup seçilir

Aşağıdaki fonksiyon debug.getinfo kullanımını gösterir. Aktif yığının ilkel şekilde geriizini yazdırır:

function traceback()
    for level = 1, math.huge do
        local info = debug.getinfo(level, "Sl")
        if not info then break end
        if info.what == "C" then -- bir C fonksiyonu mu?
            print(level, "C function")
        else -- bir Lua fonksiyonu
            print(string.format("[%s]:%d", info.short_src, info.currentline))
        end
    end
end

getinfo'dan daha fazla veri dahil ederek bu fonksiyonu geliştirmek zor değil. Aslında debug kütüphanesi böyle bir gelişmiş versiyon sunar, traceback fonksiyonu. Bizimkinin aksine, debug.traceback sonuçlarını yazdırmaz; bunun yerine  geriizli bir string(genellikle uzun) döndürür.

Yerel değişkenlere erişim

Herhangi bir aktif fonksiyonun yerel değişkenlerini debug.getlocal ile inceleyebiliriz. Bu fonksiyonun iki parametresi vardır: sorguladığınız fonksiyonun yığın seviyesi ve bir değişken indeksi. İki değer döndürür: bu değişkenin adı ve geçerli değeri. Değişken indeksi aktif değişkenlerin sayısından büyükse getlocal nil döndürür. Eğer yığın seviyesi geçersiz ise bir hata yükseltir.  yığın seviyesinin geçerliliğini kontrol etmek için debug.getinfo kullanabiliriz.

Lua yerel değişkenleri fonksiyonda göründükleri sırada numaralar, yalnızca fonksiyonun geçerli kapsamında aktif olan değişkenler sayılır. Örneğin, kod

function foo(a, b)
    local x
    do local c = a - b end
    local a = 1
    while true do
        local name, value = debug.getlocal(1, a)
        if not name then break end
        print(name, value)
        a = a + 1
    end
end

foo(10, 20)

şunu yazdıracak

a   10
b   20
x   nil
a   4

1 İndeksli değişken a (ilk parametre), 2 b, 3 x ve 4 diğer a'dır. getlocal'ın çağrıldığı noktada c hazırda kapsam dışındadır, name ve value henüz kapsam içinde değildir. (Yerel değişkenlerin yalnızca ilklendikleri koddan sonra görünür olduklarını unutmayın.)

debug.setlocal ile yerel değişkenlerin değerlerini de değiştirebilirsiniz. İlk iki parametresi getlocal'daki gibi  yığın seviyesi ve değişken indeksidir. Üçüncü parametresi bu değişken için yeni değerdir. Değişken ismini döndürür veya eğer değişken indeksi kapsam dışındaysa  nil.

Yerel olmayan değişkenlere erişim

Debug kütüphanesi aynı zamanda getupvalue ile bir Lua fonksiyonu tarafından kullanılan yerel olmayan değişkenlere erişmemize imkan verir. Yerel değişkenlerden farklı olarak yerel olmayan değişkenler, fonksiyon aktif olmadığında bile bir fonksiyon tarafından başvuru mevcuttur (Velhasıl  closure 'lar bununla ilgili). Mamafi getupvalue için ilk argüman yığın seviyesi değil fonksiyondur (bir closure daha doğrusu). İkinci argüman değişken indeksidir. Lua yerel olmayan değişkenleri fonksiyonda ilk başvuruldukları düzende numaralar ancak bu sıralama münasip değil çünkü bir fonksiyon aynı adla iki yerel olmayan değişkene erişemez.

Yerel olmayan değişkenleri aynı zamanda debug.setupvalue ile güncelleyebilirsiniz. Tahmin edebileceğiniz gibi üç parametresi var: closure, değişken indeksi ve yeni değer. setlocal gibi, değişkenin adını döndürür veya değişken indeksi ermin dışındaysa nil döndürür.

Liste 23.1, çağrılan fonksiyonunun değişken ismi verilerek herhangi bir değişkeninin değerine nasıl erişebileceğimizi gösterir. İlkin, yerel bir değişken deniyoruz.Verilen ada sahip birden fazla değişken varsa en yüksek indekse sahip olanı almalıyız; bu yüzden daima komple döngüden geçmeliyiz. Bu ada sahip herhangi bir yerel değişken bulamazsak yerel olmayan değişkenleri deneriz. İlk olarak, debug.getinfo ile çağrılan fonksiyonu  elde ederiz ve sonra yerel olmayan değişkenlerini dolaşırız. Nihayetinde, bu ada sahip yerel olmayan bir değişken bulamazsak o zaman global bir değişken elde ederiz. çağrılan fonksiyona erişmek için debug.getlocal ve debug.getinfo çağrılarında ilk argüman olarak 2 sayısının kullanıldığına dikkat.

Liste 23.1. bir değişkenin değerini elde etme:

function getvarvalue(name)
    local value, found

    -- lokal değişkenleri dene
    for i = 1, math.huge do
        local n, v = debug.getlocal(2, i)
        if not n then break end
        if n == name then
            value = v
            found = true
        end
    end
    if found then return value end
    -- lokal olmayan değişkenleri dene
    local func = debug.getinfo(2, "f").func
    for i = 1, math.huge do
        local n, v = debug.getupvalue(func, i)
        if not n then break end
        if n == name then return v end
    end
    -- bulunamadı; çevreden al
    return getfenv(func)[name]
end

Diğer eşyordamlara erişim

debug kütüphanesindeki tüm içgözlem fonksiyonları, ilk argümanları olarak isteğe bağlı bir eşyordam kabul eder, bu şekilde eşyordamı dışarıdan inceleyebiliriz(bu imkan 5.1'de yeni). Örneğin sıradaki örneği ele alalım:

co =coroutine.create(
    function()
        local x = 10
        coroutine.yield()
        error("some error")
    end)

coroutine.resume(co)
print(debug.traceback(co))

traceback çağrısı co eşyordamı üzerinde çalışacak ve şunun gibi bir şeyle sonuçlanacak:

stack traceback:
[C]: in function ’yield’
temp:3: in function <temp:1>

sürüş, resume çağrısına girmez çünkü eşyordam ve ana program farklı yığınlarda çalışır.

Bir eşyordam bir hata yükseltirse yığınını çözmez. Bu, hatadan sonra onu inceleyebileceğimiz anlamına gelir. Örneğimize devam edelim, eşyordamı resume ettirirsek tekrar hataya toslanır:

print(coroutine.resume(co)) --> false temp:4: some error

Şimdi onun geriizini yazdırırsak şöyle bir şey alırız:

stack traceback:
[C]: in function ’error’
temp:4: in function <temp:1>

Bir hatadan sonra bile  bir eşyordamdan yerel değişkenleri aynı zamanda inceleyebiliriz:

print(debug.getlocal(co, 1, 1)) --> x 10

23.2 Kancalar

debug kütüphanesinin kanca mekanizması, bir program çalışırken belirli etkinliklerde çağrılacak olan bir fonksiyon kaydetmemize imkan verir. Bir kancayı tetikleyebilen dört tür olay vardır: çağrı olayları, Lua bir fonksiyonu her çağırdığında olur; dönüş olayları, bir fonksiyon her döndüğü zaman olur; satır olayları, Lua yeni bir kod satırı yürütmeye başladığında olur; ve sayma olayları, talimatların verilen bir sayısından sonra olur. Lua, kancaları çağrıyı üreten olayı betimleyen tek bir argümanla çağırır: ”call“,”return“,”line“ veya ”count". Satır olayları için ikinci bir argüman olarak yeni satır numarasını da  geçer. Bir kanca içinde daha fazla bilgi elde etmek için debug.getinfo çağırmalıyız.

Bir kancayı kaydetmek için debug.sethook iki veya üç argüman ile çağırırız: ilk argüman kanca fonksiyonu; ikinci argüman izlemek istediğimiz olayları betimleyen bir stringdir; ve isteğe bağlı üçüncü argüman, hangi sıklıkla sayma-count olaylarını almak istediğimizi betimleyen bir sayıdır. Çağrı(call), dönüş(return) ve satır(line) olaylarını izlemek için ilk harflerini (‘c’, ‘r’ veya ‘l’) maske stringe ekleriz. sayma olayını izlemek için sadece üçüncü argüman olarak bir sayaç tedarik ederiz. Kancaları kapatmak için  argümansız sethook çağırırız.

Basit bir örnek olarak aşağıdaki kod yorumlayıcının yürüttüğü her bir satırı yazdıran ilkel bir sürüş kurar:

debug.sethook(print, "l")

Bu çağrı kanca fonksiyonu olarak print'i basitçe kurar ve yalnızca satır olaylarında çağrılacağını Lua'a bildirir. Daha ayrıntılı bir sürüş, geçerli dosya adını sürüşe eklemek için getinfo kullanabilir:

function trace(event, line)
    local s = debug.getinfo(2).short_src
    print(s .. ":" .. line)
end

debug.sethook(trace, "l")

23.3 Profiller

Adına rağmen, debug kütüphanesi hata ayıklama dışındaki görevler için de yararlıdır. Yaygın bir görev profil oluşturmaktır. Zaman bazlı bir profil için, C arabirimini kullanmak daha iyi: her kanca için bir Lua çağrısının yükü çok yüksek ve herhangi ölçüyü geçersiz kılabilir. Ancak, sayma profilleri için Lua kodu iyi iş çıkarır. Bu bölümde, çalışırken bir programın bir fonksiyona çağrı sayısını listeleyen ilkel bir profilleyici geliştireceğiz.

Programımızın ana veri yapıları iki tablo: biri, fonksiyonları çağrı sayaçlarına ilişkilendirir, diğeri , fonksiyonları isimlerine ilişkilendirir. İki tablonun indeksleri kendi fonksiyonlarıdır.

local Counters = {}
local Names = {}

Profilleme sonrası isim verilerini alabiliriz ancak aktiflen fonksiyonun adını alırsak daha iyi sonuçlar elde ettiğimizi unutmayın çünkü o zaman Lua ismini bulmak için fonksiyonu çağıran koda bakabilir.

Şimdi kanca fonksiyonunu tanımlıyoruz. Onun işi fonksiyon çağrısını yakalamak ve karşılık gelen sayacı arttırmaktır; aynı zamanda fonksiyon ismini toplar:

local function hook()
    local f = debug.getinfo(2, "f").func
    if Counters[f] == nil then -- ilk kez mi çağrıldı 'f'?
        Counters[f] = 1
        Names[f] = debug.getinfo(2, "Sn")
    else -- sadece sayacı arttır
        Counters[f] = Counters[f] + 1
    end
end

Bir sonraki adım, programı bu kanca ile çalıştırmaktır. Programın ana öbeğinin bir dosyada olduğunu ve kullanıcının bu dosya adını profilleyiciye bir argüman olarak verdiğini varsayacağız:

% lua profiler main-prog

Bu düzende profilleyici dosya adını arg[1] ' den alabilir, kancayı açabilir ve dosyayı çalıştırabilir:

local f = assert(loadfile(arg[1]))
debug.sethook(hook, "c") -- çağrılar için kancayı aç
f() -- ana programı yürüt
debug.sethook() -- kancayı kapa

Son adım sonuçları göstermektir. Bir sonraki fonksiyon bir fonksiyon için bir ad üretir. Lua'da fonksiyon isimleri oldukça belirsiz olduğundan,  dosya:satır çifti şeklinde her fonksiyona konumunu ekliyoruz. Bir fonksiyon isme sahip değilse yalnızca konumunu kullanırız. bir fonksiyon C fonksiyonu ise sadece adını kullanıyoruz(konum yok) .

function getname(func)
    local n = Names[func]
    if n.what == "C" then
        return n.name
    end
    local lc = string.format("[%s]:%s", n.short_src, n.linedefined)
    if n.namewhat ~= "" then
        return string.format("%s (%s)", lc, n.name)
    else
        return lc
    end
end

Nihayetinde, her fonksiyonu sayacıyla yazdırıyoruz:

for func, count in pairs(Counters) do
    print(getname(func), count)
end

Profilleyicimizi 10.2 bölümünde geliştirdiğimiz Markov örneğine uygularsak şunun gibi bir sonuç elde ederiz:

[markov.lua]:4 884723
write 10000
[markov.lua]:0 (f) 1
read 31103
sub 884722
[markov.lua]:1 (allwords) 1
[markov.lua]:20 (prefix) 894723
find 915824
[markov.lua]:26 (insert) 884723
random 10000
sethook 1
insert 884723

Bu sonucun anlamı, 4. satırdaki anonim fonksiyonun(tumkelimeler içinde tanımlanan iteratör fonksiyonu) 884723 kez, write(io.write) 10000 kez çağrıldığı anlamına gelir vb.

Çıktıyı sıralamak , daha iyi fonksiyon adları yazdırmak ve çıktıyı süslemek gibi bu profilleyici için yapabileceğiniz birkaç iyileştirme var. Mamafi bu temel profilleyici olduğu gibi zaten yararlıdır, ki daha gelişmiş araçlar için bir temel olarak kullanılabilir.

Hiç yorum yok:

Yorum Gönder