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
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ů:
- Modul, odkud se provádí volání, například main.lua
- Zda se jedná o volání (call) či o návrat z funkce (return)
- Jméno volané funkce, například module1_f3
- Jméno modulu, kde se nachází volaná funkce, například ./module1.lua
- Čí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:
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:
# | Příklad | URL |
---|---|---|
1 | debug_example1 | https://github.com/tisnik/luajit-examples/tree/master/debug/debug_example1 |
2 | debug_example2 | https://github.com/tisnik/luajit-examples/tree/master/debug/debug_example2 |
3 | debug_example3 | https://github.com/tisnik/luajit-examples/tree/master/debug/debug_example3 |
4 | debug_example4 | https://github.com/tisnik/luajit-examples/tree/master/debug/debug_example4 |
9. Odkazy na Internetu
- Programming in Lua: 23 – The Debug Library
http://www.lua.org/pil/23.html - Programming in Lua: 23.1 – Introspective Facilities
http://www.lua.org/pil/23.1.html - Programming in Lua: 23.2 – Hooks
http://www.lua.org/pil/23.2.html - Lua 5.2 Reference Manual: 6.10 – The Debug Library
http://www.lua.org/manual/5.2/manual.html#6.10 - Factorial (Rosetta Code)
http://rosettacode.org/wiki/Factorial#Lua - Lua Profiler (GitHub)
https://github.com/luaforge/luaprofiler - Lua Profiler (LuaForge)
http://luaforge.net/projects/luaprofiler/ - ctrace
http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua
http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - The Lua VM, on the Web
https://kripken.github.io/lua.vm.js/lua.vm.js.html - Lua.vm.js REPL
https://kripken.github.io/lua.vm.js/repl.html - lua2js
https://www.npmjs.com/package/lua2js - lua2js na GitHubu
https://github.com/basicer/lua2js-dist - Seriál o programovacím jazyku Lua
http://www.root.cz/serialy/programovaci-jazyk-lua/ - Source-to-source compiler
https://en.wikipedia.org/wiki/Source-to-source_compiler - JavaScript is Assembly Language for the Web: Sematic Markup is Dead! Clean vs. Machine-coded HTML
http://www.hanselman.com/blog/JavaScriptIsAssemblyLanguageForTheWebSematicMarkupIsDeadCleanVsMachinecodedHTML.aspx - JavaScript is Web Assembly Language and that's OK.
http://www.hanselman.com/blog/JavaScriptIsWebAssemblyLanguageAndThatsOK.aspx - Dart
https://www.dartlang.org/ - CoffeeScript
http://coffeescript.org/ - TypeScript
http://www.typescriptlang.org/ - Lua (programming language)
http://en.wikipedia.org/wiki/Lua_(programming_language) - Static single assignment form (SSA)
http://en.wikipedia.org/wiki/Static_single_assignment_form - Wikipedia: Mezijazyk
http://cs.wikipedia.org/wiki/Mezijazyk - LuaJIT 2.0 SSA IRhttp://wiki.luajit.org/SSA-IR-2.0
- The LuaJIT Project
http://luajit.org/index.html - LuaJIT FAQ
http://luajit.org/faq.html - LuaJIT Performance Comparison
http://luajit.org/performance.html - LuaJIT 2.0 intellectual property disclosure and research opportunities
http://article.gmane.org/gmane.comp.lang.lua.general/58908 - LuaJIT Wiki
http://wiki.luajit.org/Home - LuaJIT 2.0 Bytecode Instructions
http://wiki.luajit.org/Bytecode-2.0 - Programming in Lua (first edition)
http://www.lua.org/pil/contents.html - Lua 5.2 sources
http://www.lua.org/source/5.2/ - Tcl Plugin Version 3
http://www.tcl.tk/software/plugin/ - JavaScript: The Web Assembly Language?
http://www.informit.com/articles/article.aspx?p=1856657 - asm.js
http://asmjs.org/ - List of languages that compile to JS
https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS - REPL
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - The LLVM Compiler Infrastructure
http://llvm.org/ProjectsWithLLVM/ - clang: a C language family frontend for LLVM
http://clang.llvm.org/ - emscripten
http://kripken.github.io/emscripten-site/ - LLVM Backend („Fastcomp“)
http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html#llvm-backend - Emscripten – Fastcomp na GitHubu
https://github.com/kripken/emscripten-fastcomp - Clang (pro Emscripten) na GitHubu
https://github.com/kripken/emscripten-fastcomp-clang - Why not use JavaScript?
https://ckknight.github.io/gorillascript/ - Lambda the Ultimate: Coroutines in Lua,
http://lambda-the-ultimate.org/node/438 - Coroutines Tutorial,
http://lua-users.org/wiki/CoroutinesTutorial - Lua Coroutines Versus Python Generators,
http://lua-users.org/wiki/LuaCoroutinesVersusPythonGenerators