Využití knihovny debug v programovacím jazyku Lua

3. 9. 2015
Doba čtení: 27 minut

Sdílet

Programovací jazyk Lua sice ve své základní konfiguraci nabízí vývojářům pouze omezené množství knihoven, ovšem i funkce ze standardních knihoven jsou mnohdy velmi užitečné (a v mnoha jazycích je nenajdeme). Dobrým příkladem je knihovna nazvaná debug, kterou si dnes ve stučnosti představíme.

Obsah

1. Využití knihovny debug v programovacím jazyku Lua

2. Možnosti nabízené knihovnou debug

3. První demonstrační příklad: základní verze modulu umožňujícího trasování programu

4. Druhý demonstrační příklad: zjištění a výpis jména zdrojového kódu a čísla řádku volaných funkcí

5. Třetí demonstrační příklad: zpracování a zobrazení úrovně zanoření volaných funkcí

6. Problém s funkcí pcall při vzniku výjimky

7. Čtvrtý demonstrační příklad: úprava trasování tak, aby se zajistilo zpracování pcall

8. Repositář s demonstračními příklady

9. Odkazy na Internetu

1. Využití knihovny debug v programovacím jazyku Lua

Programovací jazyk Lua je v současnosti používán zejména ve funkci skriptovacího jazyka určeného pro kooperaci s nativními funkcemi, které jsou většinou součástí rozsáhlejších knihoven či aplikací. Samotný interpret tohoto programovacího jazyka i jeho základní knihovny jsou navrženy takovým způsobem, aby se pro jejich překlad mohl použít libovolný překladač jazyka C odpovídající normě ANSI C (C89, C90). Z toho ovšem vyplývají i některá omezení, jelikož standard ANSI C (popř. C89 či C90) předepisuje pouze relativně malé množství funkcí dostupných ve standardní knihovně a přeneseně pak i v knihovnách, které svým programátorům nabízí jazyk Lua. Dobrým příkladem je absence funkcí pro práci se souborovým systémem (vytváření, mazání a procházení adresářů, práva k souborům a adresářům atd.) či absence podpory regulárních výrazů.

Na druhou stranu však mají programátoři k dispozici i knihovnu nazvanou debug (viz též PIL 23), která je určena pro zjišťování či změnu stavu virtuálního stroje tohoto jazyka v době běhu aplikace (runtime). Možnosti nabízené touto knihovnou jsou poměrně velké, protože je možné zkoumat obsah zásobníkových rámců a tím pádem i zjišťovat pořadí volání funkcí, u jednotlivých funkcí lze zjistit, v jakém souboru a na jakém programovém řádku začíná jejich definice, lze přistupovat k lokálním proměnným funkcí atd. Taktéž je možné provést registraci funkcí, které jsou automaticky zavolány virtuálním strojem jazyka Lua ve chvíli, kdy dojde ke zvolené události, například k zavolání libovolné funkce, návratu z funkce apod. Díky této vlastnosti je možné implementovat například trasovací utility (což si ukážeme v navazujících kapitolách), debuggery, profilery apod.

2. Možnosti nabízené knihovnou debug

Programovací jazyk Lua verze 5.2 (což je dnes pravděpodobně nejpoužívanější verze) v knihovně debug nabízí programátorům následující funkce:

# Jméno funkce Význam
1 debug.debug zajistí přechod do interaktivního režimu, obdoba REPLu
2 debug.gethook vrátí informace o nastaveném handleru (funkce+maska příznaků+počet volání, viz též debug.sethook popsaný dále)
3 debug.getinfo zjištění runtime informací o funkci (viz též další kapitoly)
4 debug.getlocal zjištění runtime informací o lokální proměnné či paramtru uloženém na zásobníkovém rámci (lze přistupovat na libovolný rámec)
5 debug.getmetatable vrátí metatabulku pro předanou hodnotu popř. nil, pokud hodnota nemá přiřazenou metatabulku
6 debug.getregistry vrátí speciální tabulku určenou pro sdílení informací mezi Luou a C (podrobnosti se dozvíme příště)
7 debug.getupvalue vrátí externí lokální proměnnou navázanou na funkci (pokud existuje, což ovšem platí jen pro uzávěry)
8 debug.getuservalue vrátí userdata pokud existují (jinak nil)
9 debug.sethook nastavení handleru zavolaného po vzniku nějaké události, viz též další kapitoly s ukázkou kódu
10 debug.setlocal nastavení hodnoty lokální proměnné pomocí manipulace se zásobníkovými rámci (poměrně nebezpečné)
11 debug.setmetatable nastavení nové metatabulky k předané hodnotě
12 debug.setupvalue nastavení externí lokální proměnné navázané na funkci (uzávěr, closure)
13 debug.setuservalue změna userdata
14 debug.traceback vrátí stack trace (jako řetězec), popř. jen předanou zprávu
15 debug.upvalueid vrátí jednoznačný identifikátor externí lokální proměnné. Tento identifikátor je možné využít například na zjištění, zda dva uzávěry nesdílí stejnou proměnnou
16 debug.upvaluejoin změna reference externí lokální proměnné jednoho uzávěru na odlišnou proměnnou v jiném uzávěru

Většině funkcí lze v prvním parametru předat vlákno, s jehož zásobníkovým rámcem se bude manipulovat. Pokud identifikátor vlákna není předán, použije se současně běžící vlákno, tedy vlákno, z něhož je funkce debug.jméno_funkce() volána.

Některé tyto funkce jsou součástí demonstračních příkladů popsaných v navazujících kapitolách, o dalších vybraných funkcích se pak zmíním v příští části tohoto seriálu.

3. První demonstrační příklad: základní verze modulu umožňujícího trasování programu

Podívejme se nyní na využití některých možností knihovny debug v praxi. Mějme aplikaci, která se skládá ze tří modulů pojmenovaných main, module1 a module2, přičemž funkce z jednotlivých modulů se navzájem volají (což mimochodem nevypovídá o dobře navržené struktuře aplikace :-). Budeme chtít, aby se při volání funkcí na standardní výstup vypsaly základní informace o tom, která funkce je zavolána a do jakého modulu tato funkce patří. Toto chování bude zajišťovat modul nazvaný tracer:

main.lua

local tracer = require("tracer")
require("module1")
require("module2")
 
tracer.enable()
 
function f1()
    print("Hello world!")
end
 
function f2()
    f1()
end
 
function f3()
    f2()
end
 
 
f3()
module1_f3()
module2_f3()

module1.lua

function module1_f1()
    print("Hello world from module1!")
end
 
function module1_f2()
    module1_f1()
end
 
function module1_f3()
    module1_f2()
end

module2.lua

function module2_f1()
    module1_f3()
end
 
function module2_f2()
    module2_f1()
end
 
function module2_f3()
    module2_f2()
end

tracer.lua

Nejzajímavější je samozřejmě modul tracer. Ten obsahuje dvojici funkcí nazvaných tracer.eventHandler a tracer.enable. Funkce tracer.enable volá:

debug.sethook(tracer.eventHandler, "cr")

Co toto volání znamená? Provádí se zde registrace handleru představovaného uživatelskou funkcí tracer.eventHandler, přičemž tato funkce je zavolána v případě, že ve virtuálním stroji jazyka Lua dojde k jedné z těchto událostí:

Maska Plný název Význam
c call volání libovolné funkce
r return návrat z libovolné funkce

Samotný handler pak volá debug.getinfo(2) a debug.getinfo(3), kde hodnoty 2 a 3 značí relativní pozici zásobníkového rámce (ty jsou za sebou zřetězeny, podobně jako v klasickém céčku):

debug.getinfo(n) Význam
0 současně probíhající funkce, tedy samotné getinfo()
1 funkce volající getinfo(), což je tracer.eventHandler()
2 volaná funkce (zde zjišťujeme její typ a zda jde o volání či o return)
3 volající funkce (zde zjišťujeme číslo řádku a jméno zdrojového souboru)
4 funkce, která zavolala volající funkci :-)
5 atd. atd.

Struktura vrácená funkcí debug.getinfo(n) se následně použije pro zjištění jména souboru, čísla řádku, typu volané funkce (nativní céčková funkce či Lua funkce) a taktéž typu události (volání či ukončení funkce):

--
-- Lua tracer v.1
--
local tracer = {
}
 
 
 
--
-- Event handler called from Lua VM.
--
function tracer.eventHandler(event)
    local output = ""
 
    local debugInfo = debug.getinfo(3)
 
    local output = "→ → → "
 
    if debugInfo and debugInfo.currentline >= 0 then
        output = output .. debugInfo.short_src, ":" .. debugInfo.currentline .. " "
    end
 
    local debugInfo = debug.getinfo(2)
 
    -- 'what' attribute could have the following values:
    --    "Lua"  for regular Lua functions
    --    "C"    for C function
    --    "main" for the main part of a Lua chunk.
    if debugInfo.what == "main" then
        if event == "call" then
            output = output .. "begin " .. debugInfo.short_src
        else
            output = output .. "end " .. debugInfo.short_src
        end
    -- regular Lua function call
    elseif debugInfo.what == "Lua" then
        output = output .. " •" .. (debugInfo.name or "(unknown Lua code)") .. "•"
    -- C (native) function call
    elseif debugInfo.what == "C" then
        output = output .. " •" .. (debugInfo.name or "(unknown C code)") .. "•"
    -- should not happen :)
    else
        output = output .. " unknown!"
    end
    print(output)
end
 
 
 
--
-- Register event handler for the "call" and "return" events.
--
function tracer.enable()
    -- we want to call event handler for the following events:
    -- "call"
    -- "return"
    debug.sethook(tracer.eventHandler, "cr")
end
 
 
 
return tracer

Výsledky běhu prvního demonstračního příkladu

Po spuštění by se měl na standardním výstupu objevit tento text (který navíc umožňuje otestovat, zda Lua a terminál podporují UTF-8). Povšimněte si, že uvnitř funkce print se podle očekávání volá nějaký nativní kód:

→ → → ./tracer.lua •sethook•
→ → → main.lua •enable•
→ → → main.lua •f3•
→ → → main.lua •f2•
→ → → main.lua •f1•
→ → → main.lua •print•
→ → →  •(unknown C code)•
→ → →  •(unknown C code)•
Hello world!
→ → → main.lua •print•
→ → → main.lua •f1•
→ → → main.lua •f2•
→ → → main.lua •f3•
→ → → main.lua •module1_f3•
→ → → ./module1.lua •module1_f2•
→ → → ./module1.lua •module1_f1•
→ → → ./module1.lua •print•
→ → →  •(unknown C code)•
→ → →  •(unknown C code)•
Hello world from module1!
→ → → ./module1.lua •print•
→ → → ./module1.lua •module1_f1•
→ → → ./module1.lua •module1_f2•
→ → → main.lua •module1_f3•
→ → → main.lua •module2_f3•
→ → → ./module2.lua •module2_f2•
→ → → ./module2.lua •module2_f1•
→ → → ./module2.lua •module1_f3•
→ → → ./module1.lua •module1_f2•
→ → → ./module1.lua •module1_f1•
→ → → ./module1.lua •print•
→ → →  •(unknown C code)•
→ → →  •(unknown C code)•
Hello world from module1!
→ → → ./module1.lua •print•
→ → → ./module1.lua •module1_f1•
→ → → ./module1.lua •module1_f2•
→ → → ./module2.lua •module1_f3•
→ → → ./module2.lua •module2_f1•
→ → → ./module2.lua •module2_f2•
→ → → main.lua •module2_f3•
→ → → end main.lua
→ → →  •(unknown C code)•

4. Druhý demonstrační příklad: zjištění a výpis jména zdrojového kódu a čísla řádku volaných funkcí

Ve druhém demonstračním příkladu si možnosti vyvíjeného traceru poněkud rozšíříme. Zejména budeme vypisovat i informaci o tom, v jakém místě programu (jméno zdrojového souboru + číslo řádku) došlo k volání té které funkce a taktéž se bude provádět rozlišení mezi voláním funkce (call) a návratem z této funkce (return). Pro zpřehlednění zdrojového kódu bylo formátování zprávy při volání/návratu z Lua funkce či nativní céčkové funkce přesunuto do samostatných funkcí nazvaných tracer.formatLuaCall a tracer.formatCCall. Nová podoba modulu tracer vypadá následovně:

tracer.lua

--
-- Lua tracer v.2
--
local tracer = {
}
 
 
 
--
-- Format information about the Lua function call.
--
function tracer.formatLuaCall(event, debugInfo)
    return " " .. event .. " •" .. (debugInfo.name or "(unknown Lua)")
           .. "•"
           .. "  <" .. debugInfo.short_src .. ":" .. debugInfo.linedefined .. ">"
end
 
 
 
--
-- Format information about C function call.
--
function tracer.formatCCall(event, debugInfo)
    return " " .. event .. " •" .. (debugInfo.name or "(unknown C)")
           .. "•"
           .. "  [" .. debugInfo.what .. "]"
end
 
 
 
--
-- Event handler called from Lua VM.
--
function tracer.eventHandler(event)
    local output = ""
 
    local debugInfo = debug.getinfo(3)
 
    local output = "→ → → "
 
    if debugInfo and debugInfo.currentline >= 0 then
        output = output .. debugInfo.short_src, ":" .. debugInfo.currentline .. " "
    end
 
    local debugInfo = debug.getinfo(2)
 
    -- 'what' attribute could have the following values:
    --    "Lua"  for regular Lua functions
    --    "C"    for C function
    --    "main" for the main part of a Lua chunk.
    if debugInfo.what == "main" then
        if event == "call" then
            output = output .. "begin " .. debugInfo.short_src
        else
            output = output .. "end " .. debugInfo.short_src
        end
    -- regular Lua function call
    elseif debugInfo.what == "Lua" then
        output = output .. tracer.formatLuaCall(event, debugInfo)
    -- C (native) function call
    elseif debugInfo.what == "C" then
        output = output .. tracer.formatCCall(event, debugInfo)
    -- should not happen :)
    else
        output = output .. " unknown!"
    end
    print(output)
end
 
 
 
--
-- Register event handler for the "call" and "return" events.
--
function tracer.enable()
    -- we want to call event handler for the following events:
    -- "call"
    -- "return"
    debug.sethook(tracer.eventHandler, "cr")
end
 
 
 
return tracer

Výsledky běhu druhého demonstračního příkladu

Po spuštění druhého demonstračního příkladu již získáme podrobnější výpis. Každý řádek obsahuje celkem pět údajů:

  1. Modul, odkud se provádí volání, například main.lua
  2. Zda se jedná o volání (call) či o návrat z funkce (return)
  3. Jméno volané funkce, například module1_f3
  4. Jméno modulu, kde se nachází volaná funkce, například ./module1.lua
  5. Číslo řádku v rámci modulu, například :9
→ → → ./tracer.lua return •sethook•  [C]
→ → → main.lua return •enable•  <./tracer.lua:75>
→ → → main.lua call •f3•  <main.lua:15>
→ → → main.lua call •f2•  <main.lua:11>
→ → → main.lua call •f1•  <main.lua:7>
→ → → main.lua call •print•  [C]
→ → → call •(unknown C)•  [C]
→ → → return •(unknown C)•  [C]
Hello world!
→ → → main.lua return •print•  [C]
→ → → main.lua return •f1•  <main.lua:7>
→ → → main.lua return •f2•  <main.lua:11>
→ → → main.lua return •f3•  <main.lua:15>
→ → → main.lua call •module1_f3•  <./module1.lua:9>
→ → → ./module1.lua call •module1_f2•  <./module1.lua:5>
→ → → ./module1.lua call •module1_f1•  <./module1.lua:1>
→ → → ./module1.lua call •print•  [C]
→ → → call •(unknown C)•  [C]
→ → → return •(unknown C)•  [C]
Hello world from module1!
→ → → ./module1.lua return •print•  [C]
→ → → ./module1.lua return •module1_f1•  <./module1.lua:1>
→ → → ./module1.lua return •module1_f2•  <./module1.lua:5>
→ → → main.lua return •module1_f3•  <./module1.lua:9>
→ → → main.lua call •module2_f3•  <./module2.lua:9>
→ → → ./module2.lua call •module2_f2•  <./module2.lua:5>
→ → → ./module2.lua call •module2_f1•  <./module2.lua:1>
→ → → ./module2.lua call •module1_f3•  <./module1.lua:9>
→ → → ./module1.lua call •module1_f2•  <./module1.lua:5>
→ → → ./module1.lua call •module1_f1•  <./module1.lua:1>
→ → → ./module1.lua call •print•  [C]
→ → → call •(unknown C)•  [C]
→ → → return •(unknown C)•  [C]
Hello world from module1!
→ → → ./module1.lua return •print•  [C]
→ → → ./module1.lua return •module1_f1•  <./module1.lua:1>
→ → → ./module1.lua return •module1_f2•  <./module1.lua:5>
→ → → ./module2.lua return •module1_f3•  <./module1.lua:9>
→ → → ./module2.lua return •module2_f1•  <./module2.lua:1>
→ → → ./module2.lua return •module2_f2•  <./module2.lua:5>
→ → → main.lua return •module2_f3•  <./module2.lua:9>
→ → → end main.lua
→ → → return •(unknown C)•  [C]

5. Třetí demonstrační příklad: zpracování a zobrazení úrovně zanoření volaných funkcí

Třetí verze traceru se od verze předchozí liší především v tom, že si tracer pamatuje úroveň zanoření jednotlivých funkcí, kterou používá pro odsazení později volaných funkcí při výpisu trasovacích informací na standardní výstup. Tato nová funkcionalita je implementována v tracer.updateTraceLevel(), kde se na základě typu události (volání funkce – call či návrat z funkce – return) mění hodnota lokální proměnné modulu nazvaná tracer.level. Tato hodnota potom řídí počet mezer vypsaných na standardním výstupu:

string.rep("    ", tracer.level)

Nová podoba modulu vypadá následovně:

tracer.lua

--
-- Lua tracer v.3
--
local tracer = {
    level=0
}
 
 
 
--
-- Update trace level (for a bit nicer output).
--
function tracer.updateTraceLevel(debugInfo, event)
    -- decision between calling a function or returning from the function
    if event == "call" then
        tracer.level = tracer.level + 1
    else
        tracer.level = tracer.level - 1
        if tracer.level < 0 then
            tracer.level = 0
        end
    end
end
 
 
 
--
-- Format information about the Lua function call.
--
function tracer.formatLuaCall(event, debugInfo)
    return " " .. event .. " •" .. (debugInfo.name or "(unknown Lua)")
           .. "•"
           .. "  <" .. debugInfo.short_src .. ":" .. debugInfo.linedefined .. ">"
end
 
 
 
--
-- Format information about C function call.
--
function tracer.formatCCall(event, debugInfo)
    return " " .. event .. " •" .. (debugInfo.name or "(unknown C)")
           .. "•"
           .. "  [" .. debugInfo.what .. "]"
end
 
 
 
--
-- Event handler called from Lua VM.
--
function tracer.eventHandler(event)
    local output = ""
 
    local debugInfo = debug.getinfo(3)
 
    local output = tracer.level .. " → → → " .. string.rep("    ", tracer.level)
 
    if debugInfo and debugInfo.currentline >= 0 then
        output = output .. debugInfo.short_src, ":" .. debugInfo.currentline .. " "
    end
 
    local debugInfo = debug.getinfo(2)
 
    tracer.updateTraceLevel(debugInfo, event)
 
    -- 'what' attribute could have the following values:
    --    "Lua"  for regular Lua functions
    --    "C"    for C function
    --    "main" for the main part of a Lua chunk.
    if debugInfo.what == "main" then
        if event == "call" then
            output = output .. "begin " .. debugInfo.short_src
        else
            output = output .. "end " .. debugInfo.short_src
        end
    -- regular Lua function call
    elseif debugInfo.what == "Lua" then
        output = output .. tracer.formatLuaCall(event, debugInfo)
    -- C (native) function call
    elseif debugInfo.what == "C" then
        output = output .. tracer.formatCCall(event, debugInfo)
    -- should not happen :)
    else
        output = output .. " unknown!"
    end
    print(output)
end
 
 
 
--
-- Register event handler for the "call" and "return" events.
--
function tracer.enable()
    -- we want to call event handler for the following events:
    -- "call"
    -- "return"
    debug.sethook(tracer.eventHandler, "cr")
end
 
 
 
return tracer

Výsledky běhu třetího demonstračního příkladu (main.lua)

Po spuštění třetího demonstračního příkladu již získáme podrobnější výpis i s odsazením:

0 → → → ./tracer.lua return •sethook•  [C]
0 → → → main.lua return •enable•  <./tracer.lua:96>
0 → → → main.lua call •f3•  <main.lua:15>
1 → → →     main.lua call •f2•  <main.lua:11>
2 → → →         main.lua call •f1•  <main.lua:7>
3 → → →             main.lua call •print•  [C]
4 → → →                 call •(unknown C)•  [C]
5 → → →                     return •(unknown C)•  [C]
Hello world!
4 → → →                 main.lua return •print•  [C]
3 → → →             main.lua return •f1•  <main.lua:7>
2 → → →         main.lua return •f2•  <main.lua:11>
1 → → →     main.lua return •f3•  <main.lua:15>
0 → → → main.lua call •module1_f3•  <./module1.lua:9>
1 → → →     ./module1.lua call •module1_f2•  <./module1.lua:5>
2 → → →         ./module1.lua call •module1_f1•  <./module1.lua:1>
3 → → →             ./module1.lua call •print•  [C]
4 → → →                 call •(unknown C)•  [C]
5 → → →                     return •(unknown C)•  [C]
Hello world from module1!
4 → → →                 ./module1.lua return •print•  [C]
3 → → →             ./module1.lua return •module1_f1•  <./module1.lua:1>
2 → → →         ./module1.lua return •module1_f2•  <./module1.lua:5>
1 → → →     main.lua return •module1_f3•  <./module1.lua:9>
0 → → → main.lua call •module2_f3•  <./module2.lua:9>
1 → → →     ./module2.lua call •module2_f2•  <./module2.lua:5>
2 → → →         ./module2.lua call •module2_f1•  <./module2.lua:1>
3 → → →             ./module2.lua call •module1_f3•  <./module1.lua:9>
4 → → →                 ./module1.lua call •module1_f2•  <./module1.lua:5>
5 → → →                     ./module1.lua call •module1_f1•  <./module1.lua:1>
6 → → →                         ./module1.lua call •print•  [C]
7 → → →                             call •(unknown C)•  [C]
8 → → →                                 return •(unknown C)•  [C]
Hello world from module1!
7 → → →                             ./module1.lua return •print•  [C]
6 → → →                         ./module1.lua return •module1_f1•  <./module1.lua:1>
5 → → →                     ./module1.lua return •module1_f2•  <./module1.lua:5>
4 → → →                 ./module2.lua return •module1_f3•  <./module1.lua:9>
3 → → →             ./module2.lua return •module2_f1•  <./module2.lua:1>
2 → → →         ./module2.lua return •module2_f2•  <./module2.lua:5>
1 → → →     main.lua return •module2_f3•  <./module2.lua:9>
0 → → → end main.lua
0 → → → return •(unknown C)•  [C]

6. Problém s funkcí pcall při vzniku výjimky

Na základě výstupu traceru, který jsme mohli vidět v předchozí kapitole, by se mohlo zdát, že tracer pracuje bezchybně, ovšem ve skutečnosti tomu tak není. Jeden z problémů vzniká při použití dvojice funkcí pcall+error, což je dvojice speciálních funkcí sloužících k vyvolání výjimky (error) a k jejímu zachycení (pcall). Zmíněný problém spočívá v tom, že při vyvolání výjimky se řízení ihned předá na volající pcall a nedojde tedy ke vzniku události return (jinými slovy to znamená, že dvojice událostí call a return nejsou vyvážené). Podívejme se na kód, který náš tracer vyvede z míry:

main2.lua

local tracer = require("tracer")
require("module1")
require("module2")
 
tracer.enable()
 
function x()
    error("error")
end
 
function pcall_x()
    pcall(x)
end
 
for i=1,10 do
    pcall_x()
end

Výsledky běhu třetího demonstračního příkladu (main2.lua)

Po spuštění příkladu vidíme, že se výpočet úrovně zanoření provádí chybně – při vyvolání výjimky pomocí error a jejím následném zachycení v pcall by se úroveň zanoření funkcí měla snížit o 3 a ne o 1:

0 → → → ./tracer.lua return •sethook•  [C]
0 → → → main2.lua return •enable•  <./tracer.lua:96>
0 → → → main2.lua call •pcall_x•  <main2.lua:11>
1 → → →     main2.lua call •pcall•  [C]
2 → → →         call •(unknown Lua)•  <main2.lua:7>
3 → → →             main2.lua call •error•  [C]
4 → → →                 main2.lua return •pcall•  [C]
3 → → →             main2.lua return •pcall_x•  <main2.lua:11>
2 → → →         main2.lua call •pcall_x•  <main2.lua:11>
3 → → →             main2.lua call •pcall•  [C]
4 → → →                 call •(unknown Lua)•  <main2.lua:7>
5 → → →                     main2.lua call •error•  [C]
6 → → →                         main2.lua return •pcall•  [C]
5 → → →                     main2.lua return •pcall_x•  <main2.lua:11>
4 → → →                 main2.lua call •pcall_x•  <main2.lua:11>
5 → → →                     main2.lua call •pcall•  [C]
6 → → →                         call •(unknown Lua)•  <main2.lua:7>
7 → → →                             main2.lua call •error•  [C]
8 → → →                                 main2.lua return •pcall•  [C]
7 → → →                             main2.lua return •pcall_x•  <main2.lua:11>
6 → → →                         main2.lua call •pcall_x•  <main2.lua:11>
7 → → →                             main2.lua call •pcall•  [C]
8 → → →                                 call •(unknown Lua)•  <main2.lua:7>
9 → → →                                     main2.lua call •error•  [C]
10 → → →                                         main2.lua return •pcall•  [C]
9 → → →                                     main2.lua return •pcall_x•  <main2.lua:11>
8 → → →                                 main2.lua call •pcall_x•  <main2.lua:11>
9 → → →                                     main2.lua call •pcall•  [C]
10 → → →                                         call •(unknown Lua)•  <main2.lua:7>
11 → → →                                             main2.lua call •error•  [C]
12 → → →                                                 main2.lua return •pcall•  [C]
11 → → →                                             main2.lua return •pcall_x•  <main2.lua:11>
10 → → →                                         main2.lua call •pcall_x•  <main2.lua:11>
11 → → →                                             main2.lua call •pcall•  [C]
12 → → →                                                 call •(unknown Lua)•  <main2.lua:7>
13 → → →                                                     main2.lua call •error•  [C]
14 → → →                                                         main2.lua return •pcall•  [C]
13 → → →                                                     main2.lua return •pcall_x•  <main2.lua:11>
12 → → →                                                 main2.lua call •pcall_x•  <main2.lua:11>
13 → → →                                                     main2.lua call •pcall•  [C]
14 → → →                                                         call •(unknown Lua)•  <main2.lua:7>
15 → → →                                                             main2.lua call •error•  [C]
16 → → →                                                                 main2.lua return •pcall•  [C]
15 → → →                                                             main2.lua return •pcall_x•  <main2.lua:11>
14 → → →                                                         main2.lua call •pcall_x•  <main2.lua:11>
15 → → →                                                             main2.lua call •pcall•  [C]
16 → → →                                                                 call •(unknown Lua)•  <main2.lua:7>
17 → → →                                                                     main2.lua call •error•  [C]
18 → → →                                                                         main2.lua return •pcall•  [C]
17 → → →                                                                     main2.lua return •pcall_x•  <main2.lua:11>
16 → → →                                                                 main2.lua call •pcall_x•  <main2.lua:11>
17 → → →                                                                     main2.lua call •pcall•  [C]
18 → → →                                                                         call •(unknown Lua)•  <main2.lua:7>
19 → → →                                                                             main2.lua call •error•  [C]
20 → → →                                                                                 main2.lua return •pcall•  [C]
19 → → →                                                                             main2.lua return •pcall_x•  <main2.lua:11>
18 → → →                                                                         main2.lua call •pcall_x•  <main2.lua:11>
19 → → →                                                                             main2.lua call •pcall•  [C]
20 → → →                                                                                 call •(unknown Lua)•  <main2.lua:7>
21 → → →                                                                                     main2.lua call •error•  [C]
22 → → →                                                                                         main2.lua return •pcall•  [C]
21 → → →                                                                                     main2.lua return •pcall_x•  <main2.lua:11>
20 → → →                                                                                 end main2.lua
19 → → →                                                                             return •(unknown C)•  [C]

7. Čtvrtý demonstrační příklad: úprava trasování tak, aby se zajistilo zpracování pcall

Náprava zmíněného problému s výjimkami je relativně jednoduchá – stačí zjistit, že se zavolala funkce pcall. V tomto případě se aktuální úroveň zanoření zapamatuje v nové proměnné tracer.pcall_level a při návratu z funkce pcall dojde k obnovení proměnné tracer.level na hodnotu zapamatovanou v tracer.pcall_level (lepší by bylo použití zásobníku pro zajištění vnořených pcall, v praxi to však nebude nezbytné). Poslední verze traceru tedy vypadá takto:

tracer.lua

--
-- Lua tracer v.4
--
local tracer = {
    level=0,
    pcall_level=0
}
 
 
 
--
-- Update trace level (for a bit nicer output).
--
function tracer.updateTraceLevel(debugInfo, event)
    -- decision between calling a function or returning from the function
    if event == "call" then
        tracer.level = tracer.level + 1
    else
        tracer.level = tracer.level - 1
        if tracer.level < 0 then
            tracer.level = 0
        end
    end
end
 
 
 
--
-- Call of pcall() function needs special handling, because
-- error() would break normal call-return flow.
-- (even better would be to use a user-defined stack instead of pcall_level)
--
function tracer.handlePcall(debugInfo, event)
    if debugInfo.what == "C" and debugInfo.name == "pcall" then
        if event == "call" then
            tracer.pcall_level = tracer.level
        else
            tracer.level = tracer.pcall_level+1
        end
    end
end
 
 
 
--
-- Format information about the Lua function call.
--
function tracer.formatLuaCall(event, debugInfo)
    return " " .. event .. " •" .. (debugInfo.name or "(unknown Lua)")
           .. "•"
           .. "  <" .. debugInfo.short_src .. ":" .. debugInfo.linedefined .. ">"
end
 
 
 
--
-- Format information about C function call.
--
function tracer.formatCCall(event, debugInfo)
    return " " .. event .. " •" .. (debugInfo.name or "(unknown C)")
           .. "•"
           .. "  [" .. debugInfo.what .. "]"
end
 
 
 
--
-- Event handler called from Lua VM.
--
function tracer.eventHandler(event)
    local output = ""
 
    local debugInfo = debug.getinfo(3)
 
    local output = tracer.level .. " → → → " .. string.rep("    ", tracer.level)
 
    if debugInfo and debugInfo.currentline >= 0 then
        output = output .. debugInfo.short_src, ":" .. debugInfo.currentline .. " "
    end
 
    local debugInfo = debug.getinfo(2)
 
    tracer.handlePcall(debugInfo, event)
    tracer.updateTraceLevel(debugInfo, event)
 
    -- 'what' attribute could have the following values:
    --    "Lua"  for regular Lua functions
    --    "C"    for C function
    --    "main" for the main part of a Lua chunk.
    if debugInfo.what == "main" then
        if event == "call" then
            output = output .. "begin " .. debugInfo.short_src
        else
            output = output .. "end " .. debugInfo.short_src
        end
    -- regular Lua function call
    elseif debugInfo.what == "Lua" then
        output = output .. tracer.formatLuaCall(event, debugInfo)
    -- C (native) function call
    elseif debugInfo.what == "C" then
        output = output .. tracer.formatCCall(event, debugInfo)
    -- should not happen :)
    else
        output = output .. " unknown!"
    end
    print(output)
end
 
 
 
--
-- Register event handler for the "call" and "return" events.
--
function tracer.enable()
    -- we want to call event handler for the following events:
    -- "call"
    -- "return"
    debug.sethook(tracer.eventHandler, "cr")
end
 
 
 
return tracer

Výsledky běhu čtvrtého demonstračního příkladu (main.lua)

Otestujme si nový tracer na původním modulu main.lua. Zde se nevolá ani pcall() ani error(), takže podle očekávání se funkcionalita nijak nezmění:

0 → → → ./tracer.lua return •sethook•  [C]
0 → → → main.lua return •enable•  <./tracer.lua:114>
0 → → → main.lua call •f3•  <main.lua:15>
1 → → →     main.lua call •f2•  <main.lua:11>
2 → → →         main.lua call •f1•  <main.lua:7>
3 → → →             main.lua call •print•  [C]
4 → → →                 call •(unknown C)•  [C]
5 → → →                     return •(unknown C)•  [C]
Hello world!
4 → → →                 main.lua return •print•  [C]
3 → → →             main.lua return •f1•  <main.lua:7>
2 → → →         main.lua return •f2•  <main.lua:11>
1 → → →     main.lua return •f3•  <main.lua:15>
0 → → → main.lua call •module1_f3•  <./module1.lua:9>
1 → → →     ./module1.lua call •module1_f2•  <./module1.lua:5>
2 → → →         ./module1.lua call •module1_f1•  <./module1.lua:1>
3 → → →             ./module1.lua call •print•  [C]
4 → → →                 call •(unknown C)•  [C]
5 → → →                     return •(unknown C)•  [C]
Hello world from module1!
4 → → →                 ./module1.lua return •print•  [C]
3 → → →             ./module1.lua return •module1_f1•  <./module1.lua:1>
2 → → →         ./module1.lua return •module1_f2•  <./module1.lua:5>
1 → → →     main.lua return •module1_f3•  <./module1.lua:9>
0 → → → main.lua call •module2_f3•  <./module2.lua:9>
1 → → →     ./module2.lua call •module2_f2•  <./module2.lua:5>
2 → → →         ./module2.lua call •module2_f1•  <./module2.lua:1>
3 → → →             ./module2.lua call •module1_f3•  <./module1.lua:9>
4 → → →                 ./module1.lua call •module1_f2•  <./module1.lua:5>
5 → → →                     ./module1.lua call •module1_f1•  <./module1.lua:1>
6 → → →                         ./module1.lua call •print•  [C]
7 → → →                             call •(unknown C)•  [C]
8 → → →                                 return •(unknown C)•  [C]
Hello world from module1!
7 → → →                             ./module1.lua return •print•  [C]
6 → → →                         ./module1.lua return •module1_f1•  <./module1.lua:1>
5 → → →                     ./module1.lua return •module1_f2•  <./module1.lua:5>
4 → → →                 ./module2.lua return •module1_f3•  <./module1.lua:9>
3 → → →             ./module2.lua return •module2_f1•  <./module2.lua:1>
2 → → →         ./module2.lua return •module2_f2•  <./module2.lua:5>
1 → → →     main.lua return •module2_f3•  <./module2.lua:9>
0 → → → end main.lua
0 → → → return •(unknown C)•  [C]

Výsledky běhu čtvrtého demonstračního příkladu (main2.lua)

Zajímavější bude test traceru s modulem main2.lua, kde se vyvolávají a zachycují výjimky. Povšimněte si korektního přeskoku ze čtvrté úrovně na úroveň první, například na řádku 7:

ict ve školství 24

0 → → → ./tracer.lua return •sethook•  [C]
0 → → → main2.lua return •enable•  <./tracer.lua:114>
0 → → → main2.lua call •pcall_x•  <main2.lua:11>
1 → → →     main2.lua call •pcall•  [C]
2 → → →         call •(unknown Lua)•  <main2.lua:7>
3 → → →             main2.lua call •error•  [C]
4 → → →                 main2.lua return •pcall•  [C]
1 → → →     main2.lua return •pcall_x•  <main2.lua:11>
0 → → → main2.lua call •pcall_x•  <main2.lua:11>
1 → → →     main2.lua call •pcall•  [C]
2 → → →         call •(unknown Lua)•  <main2.lua:7>
3 → → →             main2.lua call •error•  [C]
4 → → →                 main2.lua return •pcall•  [C]
1 → → →     main2.lua return •pcall_x•  <main2.lua:11>
0 → → → main2.lua call •pcall_x•  <main2.lua:11>
1 → → →     main2.lua call •pcall•  [C]
2 → → →         call •(unknown Lua)•  <main2.lua:7>
3 → → →             main2.lua call •error•  [C]
4 → → →                 main2.lua return •pcall•  [C]
1 → → →     main2.lua return •pcall_x•  <main2.lua:11>
0 → → → main2.lua call •pcall_x•  <main2.lua:11>
1 → → →     main2.lua call •pcall•  [C]
2 → → →         call •(unknown Lua)•  <main2.lua:7>
3 → → →             main2.lua call •error•  [C]
4 → → →                 main2.lua return •pcall•  [C]
1 → → →     main2.lua return •pcall_x•  <main2.lua:11>
0 → → → main2.lua call •pcall_x•  <main2.lua:11>
1 → → →     main2.lua call •pcall•  [C]
2 → → →         call •(unknown Lua)•  <main2.lua:7>
3 → → →             main2.lua call •error•  [C]
4 → → →                 main2.lua return •pcall•  [C]
1 → → →     main2.lua return •pcall_x•  <main2.lua:11>
0 → → → main2.lua call •pcall_x•  <main2.lua:11>
1 → → →     main2.lua call •pcall•  [C]
2 → → →         call •(unknown Lua)•  <main2.lua:7>
3 → → →             main2.lua call •error•  [C]
4 → → →                 main2.lua return •pcall•  [C]
1 → → →     main2.lua return •pcall_x•  <main2.lua:11>
0 → → → main2.lua call •pcall_x•  <main2.lua:11>
1 → → →     main2.lua call •pcall•  [C]
2 → → →         call •(unknown Lua)•  <main2.lua:7>
3 → → →             main2.lua call •error•  [C]
4 → → →                 main2.lua return •pcall•  [C]
1 → → →     main2.lua return •pcall_x•  <main2.lua:11>
0 → → → main2.lua call •pcall_x•  <main2.lua:11>
1 → → →     main2.lua call •pcall•  [C]
2 → → →         call •(unknown Lua)•  <main2.lua:7>
3 → → →             main2.lua call •error•  [C]
4 → → →                 main2.lua return •pcall•  [C]
1 → → →     main2.lua return •pcall_x•  <main2.lua:11>
0 → → → main2.lua call •pcall_x•  <main2.lua:11>
1 → → →     main2.lua call •pcall•  [C]
2 → → →         call •(unknown Lua)•  <main2.lua:7>
3 → → →             main2.lua call •error•  [C]
4 → → →                 main2.lua return •pcall•  [C]
1 → → →     main2.lua return •pcall_x•  <main2.lua:11>
0 → → → main2.lua call •pcall_x•  <main2.lua:11>
1 → → →     main2.lua call •pcall•  [C]
2 → → →         call •(unknown Lua)•  <main2.lua:7>
3 → → →             main2.lua call •error•  [C]
4 → → →                 main2.lua return •pcall•  [C]
1 → → →     main2.lua return •pcall_x•  <main2.lua:11>
0 → → → end main2.lua
0 → → → return •(unknown C)•  [C]

Otázka pro čtenáře: je nyní tracer korektní pro všechny možné případy, které mohou v Lue nastat, nebo může dojít k situaci, kdy se bude špatně vyhodnocovat buď volání funkce nebo návrat z funkce?

8. Repositář s demonstračními příklady

Všechny čtyři dnes popsané demonstrační příklady byly, podobně jako v některých předchozích částech tohoto seriálu, uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/luajit-examples. V tabulce zobrazené pod tímto odstavcem naleznete na zdrojové kódy jednotlivých demonstračních příkladů přímé odkazy:

9. Odkazy na Internetu

  1. Programming in Lua: 23 – The Debug Library
    http://www.lua.org/pil/23.html
  2. Programming in Lua: 23.1 – Introspective Facilities
    http://www.lua.org/pil/23.1.html
  3. Programming in Lua: 23.2 – Hooks
    http://www.lua.org/pil/23.2.html
  4. Lua 5.2 Reference Manual: 6.10 – The Debug Library
    http://www.lua.org/manual/5­.2/manual.html#6.10
  5. Factorial (Rosetta Code)
    http://rosettacode.org/wi­ki/Factorial#Lua
  6. Lua Profiler (GitHub)
    https://github.com/luafor­ge/luaprofiler
  7. Lua Profiler (LuaForge)
    http://luaforge.net/projec­ts/luaprofiler/
  8. ctrace
    http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/
  9. LuaJIT – Just in Time překladač pro programovací jazyk Lua
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua/
  10. LuaJIT – Just in Time překladač pro programovací jazyk Lua (2)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-2/
  11. LuaJIT – Just in Time překladač pro programovací jazyk Lua (3)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-3/
  12. LuaJIT – Just in Time překladač pro programovací jazyk Lua (4)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-4/
  13. LuaJIT – Just in Time překladač pro programovací jazyk Lua (5 – tabulky a pole)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-5-tabulky-a-pole/
  14. LuaJIT – Just in Time překladač pro programovací jazyk Lua (6 – překlad programových smyček do mezijazyka LuaJITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-6-preklad-programovych-smycek-do-mezijazyka-luajitu/
  15. LuaJIT – Just in Time překladač pro programovací jazyk Lua (7 – dokončení popisu mezijazyka LuaJITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-7-dokonceni-popisu-mezijazyka-luajitu/
  16. LuaJIT – Just in Time překladač pro programovací jazyk Lua (8 – základní vlastnosti trasovacího JITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-8-zakladni-vlastnosti-trasovaciho-jitu/
  17. LuaJIT – Just in Time překladač pro programovací jazyk Lua (9 – další vlastnosti trasovacího JITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-9-dalsi-vlastnosti-trasovaciho-jitu/
  18. LuaJIT – Just in Time překladač pro programovací jazyk Lua (10 – JIT překlad do nativního kódu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-10-jit-preklad-do-nativniho-kodu/
  19. LuaJIT – Just in Time překladač pro programovací jazyk Lua (11 – JIT překlad do nativního kódu procesorů s architekturami x86 a ARM)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-11-jit-preklad-do-nativniho-kodu-procesoru-s-architekturami-x86-a-arm/
  20. LuaJIT – Just in Time překladač pro programovací jazyk Lua (12 – překlad operací s reálnými čísly)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-12-preklad-operaci-s-realnymi-cisly/
  21. The Lua VM, on the Web
    https://kripken.github.io/lu­a.vm.js/lua.vm.js.html
  22. Lua.vm.js REPL
    https://kripken.github.io/lu­a.vm.js/repl.html
  23. lua2js
    https://www.npmjs.com/package/lua2js
  24. lua2js na GitHubu
    https://github.com/basicer/lua2js-dist
  25. Seriál o programovacím jazyku Lua
    http://www.root.cz/serialy/pro­gramovaci-jazyk-lua/
  26. Source-to-source compiler
    https://en.wikipedia.org/wiki/Source-to-source_compiler
  27. JavaScript is Assembly Language for the Web: Sematic Markup is Dead! Clean vs. Machine-coded HTML
    http://www.hanselman.com/blog/Ja­vaScriptIsAssemblyLanguage­ForTheWebSematicMarkupIsDe­adCleanVsMachinecodedHTML­.aspx
  28. JavaScript is Web Assembly Language and that's OK.
    http://www.hanselman.com/blog/Ja­vaScriptIsWebAssemblyLangu­ageAndThatsOK.aspx
  29. Dart
    https://www.dartlang.org/
  30. CoffeeScript
    http://coffeescript.org/
  31. TypeScript
    http://www.typescriptlang.org/
  32. Lua (programming language)
    http://en.wikipedia.org/wi­ki/Lua_(programming_langu­age)
  33. Static single assignment form (SSA)
    http://en.wikipedia.org/wi­ki/Static_single_assignmen­t_form
  34. Wikipedia: Mezijazyk
    http://cs.wikipedia.org/wi­ki/Mezijazyk
  35. LuaJIT 2.0 SSA IRhttp://wiki.luajit.org/SSA-IR-2.0
  36. The LuaJIT Project
    http://luajit.org/index.html
  37. LuaJIT FAQ
    http://luajit.org/faq.html
  38. LuaJIT Performance Comparison
    http://luajit.org/performance.html
  39. LuaJIT 2.0 intellectual property disclosure and research opportunities
    http://article.gmane.org/gma­ne.comp.lang.lua.general/58908
  40. LuaJIT Wiki
    http://wiki.luajit.org/Home
  41. LuaJIT 2.0 Bytecode Instructions
    http://wiki.luajit.org/Bytecode-2.0
  42. Programming in Lua (first edition)
    http://www.lua.org/pil/contents.html
  43. Lua 5.2 sources
    http://www.lua.org/source/5.2/
  44. Tcl Plugin Version 3
    http://www.tcl.tk/software/plugin/
  45. JavaScript: The Web Assembly Language?
    http://www.informit.com/ar­ticles/article.aspx?p=1856657
  46. asm.js
    http://asmjs.org/
  47. List of languages that compile to JS
    https://github.com/jashke­nas/coffeescript/wiki/List-of-languages-that-compile-to-JS
  48. REPL
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  49. The LLVM Compiler Infrastructure
    http://llvm.org/ProjectsWithLLVM/
  50. clang: a C language family frontend for LLVM
    http://clang.llvm.org/
  51. emscripten
    http://kripken.github.io/emscripten-site/
  52. LLVM Backend („Fastcomp“)
    http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html#llvm-backend
  53. Emscripten – Fastcomp na GitHubu
    https://github.com/kripken/emscripten-fastcomp
  54. Clang (pro Emscripten) na GitHubu
    https://github.com/kripken/emscripten-fastcomp-clang
  55. Why not use JavaScript?
    https://ckknight.github.i­o/gorillascript/
  56. Lambda the Ultimate: Coroutines in Lua,
    http://lambda-the-ultimate.org/node/438
  57. Coroutines Tutorial,
    http://lua-users.org/wiki/CoroutinesTutorial
  58. Lua Coroutines Versus Python Generators,
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.