FRAMEWORK PRO STATICKOU ANALÝZU SKRIPTŮ PRO SHELL

Podobné dokumenty
MATURITNÍ OTÁZKY ELEKTROTECHNIKA - POČÍTAČOVÉ SYSTÉMY 2003/2004 PROGRAMOVÉ VYBAVENÍ POČÍTAČŮ

Úvod do Linuxu. SŠSI Tábor 1

Maturitní otázky z předmětu PROGRAMOVÁNÍ

TÉMATICKÝ OKRUH Softwarové inženýrství

Střední odborná škola a Střední odborné učiliště, Hořovice

Vyučovací hodina. 1vyučovací hodina: 2vyučovací hodiny: Opakování z minulé hodiny. Procvičení nové látky

PSK3-9. Základy skriptování. Hlavička

1. Programování proti rozhraní

Klíčová slova: dynamické internetové stránky, HTML, CSS, PHP, SQL, MySQL,

Matematika v programovacích

PHP framework Nette. Kapitola Úvod. 1.2 Architektura Nette

1 Webový server, instalace PHP a MySQL 13

Rozšíření ksh vůči sh při práci s proměnnými

1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:

Obsah. Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15

Překladač a jeho struktura

DUM 06 téma: Tvorba makra pomocí VBA

Vyřešené teoretické otázky do OOP ( )

Vstupní požadavky, doporučení a metodické pokyny

Instalace a konfigurace web serveru. WA1 Martin Klíma

Programovací jazyky. imperativní (procedurální) neimperativní (neprocedurální) assembler (jazyk symbolických instrukcí)

GTL GENERATOR NÁSTROJ PRO GENEROVÁNÍ OBJEKTŮ OBJEKTY PRO INFORMATICA POWERCENTER. váš partner na cestě od dat k informacím

1. Webový server, instalace PHP a MySQL 13

Algoritmizace a programování

Software602 Form Designer

Proměnné a parametry. predn_08.odt :00:38 1

Vývoj moderních technologií při vyhledávání. Patrik Plachý SEFIRA spol. s.r.o.

WSH Windows Script Hosting. OSY 2 Přednáška číslo 2 opravená verze z

Přednáška 1. Úvod. Historie OS Unix. Architektura OS Unix. Interpret příkazů. SHELL. Zpracování příkazové řádky. Speciální znaky. Zkratky příkazů.

Struktura programu v době běhu

MATLABLINK - VZDÁLENÉ OVLÁDÁNÍ A MONITOROVÁNÍ TECHNOLOGICKÝCH PROCESŮ

Implementace systémů HIPS: historie a současnost. Martin Dráb

Systém souborů (file system, FS)

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:

Profilová část maturitní zkoušky 2017/2018

Funkce, podmíněný příkaz if-else, příkaz cyklu for

TÉMATICKÝ OKRUH Softwarové inženýrství

6 Příkazy řízení toku

C2110 Operační systém UNIX a základy programování

Obsah přednášky. programovacího jazyka. Motivace. Princip denotační sémantiky Sémantické funkce Výrazy Příkazy Vstup a výstup Kontinuace Program

Informační systémy 2008/2009. Radim Farana. Obsah. Nástroje business modelování. Business modelling, základní nástroje a metody business modelování.

Využití OOP v praxi -- Knihovna PHP -- Interval.cz

Programování v jazyce C a C++

ANOTACE vytvořených/inovovaných materiálů

2015 GEOVAP, spol. s r. o. Všechna práva vyhrazena.

INFORMAČNÍ SYSTÉM VIDIUM A VYUŽITÍ MODERNÍCH TECHNOLOGIÍ

1/1 ČESKÁ ZEMĚDĚLSKÁ UNIVERZITA V PRAZE PROVOZNĚ EKONOMICKÁ FAKULTA PŘIJÍMACÍ ŘÍZENÍ 2017/2018

Přednáška 7. Celočíselná aritmetika. Návratový kód. Příkazy pro větvení výpočtu. Cykly. Předčasné ukončení cyklu.

Programovací jazyk Pascal

C2110 Operační systém UNIX a základy programování

3. Je defenzivní programování technikou skrývání implementace? Vyberte jednu z nabízených možností: Pravda Nepravda

1 Nejkratší cesta grafem

EVROPSKÝ SOCIÁLNÍ FOND. Úvod do PHP PRAHA & EU INVESTUJEME DO VAŠÍ BUDOUCNOSTI

Vytvoření bootovatelného média

IS pro podporu BOZP na FIT ČVUT

PHP PHP je skriptovací programovací jazyk dynamických internetových stránek PHP je nezávislý na platformě

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

Relační DB struktury sloužící k optimalizaci dotazů - indexy, clustery, indexem organizované tabulky

Simluátor Trilobota. (projekt do předmětu ROB)

DUM 07 téma: Proměnné, konstanty a pohyb po buňkách ve VBA

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

MAXScript výukový kurz

IB111 Programování a algoritmizace. Programovací jazyky

Podpora skriptování v Audacity

Martin Kopta. Unixové shelly. Středisko Unixových Technologií, 2009

PROGRAMOVÁNÍ V SHELLU

1. Webové služby. K čemu slouží? 2. RPC Web Service. 3. SOA Web Service. 4. RESTful Web services

Programovací jazyky. imperativní (procedurální) neimperativní (neprocedurální) assembler (jazyk symbolických instrukcí)

PRODUKTY. Tovek Tools

Přednáška 8. Proměnné. Psaní a ladění skriptů. Parametry skriptu. Vstup a výstup. Konfigurační soubory shellu. Úvod do Operačních Systémů Přednáška 8

Obsah přednášky. Představení webu ASP.NET frameworky Relační databáze Objektově-relační mapování Entity framework

Webové služby DPD. Verze

Zápis programu v jazyce C#

Systém elektronického rádce v životních situacích portálu

Střední průmyslová škola elektrotechnická Praha 10, V Úžlabině 320 M A T U R I T N Í T É M A T A P Ř E D M Ě T U

INOVACE PŘEDMĚTŮ ICT. MODUL 11: PROGRAMOVÁNÍ WEBOVÝCH APLIKLACÍ Metodika

Procesy a vlákna (Processes and Threads)

Obsah. Začínáme programovat v Ruby on Rails 9. Úvod Vítejte v Ruby 15. O autorovi 9 Poděkování 9

konec šedesátých let vyvinut ze systému Multics původní účel systém pro zpracování textů autoři: Ken Thompson a Denis Ritchie systém pojmnoval Brian

5a. Makra Visual Basic pro Microsoft Escel. Vytvořil Institut biostatistiky a analýz, Masarykova univerzita J. Kalina

Přednáška. Vstup/Výstup. Katedra počítačových systémů FIT, České vysoké učení technické v Praze Jan Trdlička, 2012

Čtvrtek 8. prosince. Pascal - opakování základů. Struktura programu:

4a. Makra Visual Basic pro Microsoft Excel Cyklické odkazy a iterace Makra funkce a metody

PŘÍLOHA C Požadavky na Dokumentaci

Předměty. Algoritmizace a programování Seminář z programování. Verze pro akademický rok 2012/2013. Verze pro akademický rok 2012/2013

Objektové programování

VISUAL BASIC. Přehled témat

Úvod do programovacích jazyků (Java)

TÉMATICKÝ OKRUH Softwarové inženýrství

IB111 Úvod do programování skrze Python Přednáška 13

Začínáme vážně programovat. Řídící struktury Přetypování Vstupně výstupní operace Vlastní tvorba programů

Obsah. Zpracoval:

Konstruktory překladačů

Tabulkový procesor. Základní rysy

ALGORITMIZACE A PROGRAMOVÁNÍ

Knihovna RecDBXLib ZÁZNAMY V DATABOXU TXV

Sdílení dat mezi podprogramy

Transkript:

VYSOKÉ UČENÍ TECHNICKÉ V BRNĚ BRNO UNIVERSITY OF TECHNOLOGY FAKULTA INFORMAČNÍCH TECHNOLOGIÍ ÚSTAV INTELIGENTNÍCH SYSTÉMŮ FACULTY OF INFORMATION TECHNOLOGY DEPARTMENT OF INTELLIGENT SYSTEMS FRAMEWORK PRO STATICKOU ANALÝZU SKRIPTŮ PRO SHELL DIPLOMOVÁ PRÁCE MASTER S THESIS AUTOR PRÁCE AUTHOR Bc. FRANTIŠEK SVOBODA BRNO 2012

VYSOKÉ UČENÍ TECHNICKÉ V BRNĚ BRNO UNIVERSITY OF TECHNOLOGY FAKULTA INFORMAČNÍCH TECHNOLOGIÍ ÚSTAV INTELIGENTNÍCH SYSTÉMŮ FACULTY OF INFORMATION TECHNOLOGY DEPARTMENT OF INTELLIGENT SYSTEMS FRAMEWORK PRO STATICKOU ANALÝZU SKRIPTŮ PRO SHELL A FRAMEWORK FOR STATIC ANALYSIS OF SHELL SCRIPTS DIPLOMOVÁ PRÁCE MASTER S THESIS AUTOR PRÁCE AUTHOR VEDOUCÍ PRÁCE SUPERVISOR Bc. FRANTIŠEK SVOBODA Ing. ALEŠ SMRČKA, Ph.D. BRNO 2012

Abstrakt Cílem této práce je vytvoření systému pro statickou analýzu skriptů některého unixového shellu. Z možných alternativ je jako hlavní předmět zájmu zvolen Bourne-again shell. Součástí projektu je seznámení se s principy statické analýzy, prostudování současných aplikací, které se touto problematikou zabývají, a také probádání syntaxe a architektury vybraného shellu. Výsledkem projektu je návrh a realizace modulárního systému, který umožní zásuvným modulům provádět různé analýzy skriptů. Systém zahrnuje správu zásuvných modulů, prostředky pro jejich interakci, zpracování výstupů a nastavení vstupu. Také je popsáno několik zásuvných modulů, které jsou schopny některé základní analýzy provádět. Abstract The aim of this work is to create framework for static analysis of Unix shell scripts. Bourneagain shell is chosen as the primary subject of interest. The first part of the project discusses a principles of static analysis and current static analysis tools. The diploma thesis presents a framework based on modular system, which enables plug-ins to perform different kinds of analysis on scripts. Framework includes plug-in management, means of interaction, handling outputs and input setting. This thesis also contains the description of a few plug-ins, designed to perform basic analysis of scripts. Klíčová slova statická analýza programů, Unix, shell, skript, bash, zásuvný modul, dynamická knihovna, analýza toku dat, analýza toku řízení Keywords static software analysis, Unix, shell, script, bash, plug-in, dynamic loadable library, data flow analysis, control flow analysis Citace František Svoboda: Framework pro statickou analýzu skriptů pro shell, diplomová práce, Brno, FIT VUT v Brně, 2012

Framework pro statickou analýzu skriptů pro shell Prohlášení Prohlašuji, že jsem tuto dimplomovou práci vypracoval samostatně pod vedením pana Ing. Aleše Smrčky, Ph.D........................ František Svoboda 21. května 2012 c František Svoboda, 2012. Tato práce vznikla jako školní dílo na Vysokém učení technickém v Brně, Fakultě informačních technologií. Práce je chráněna autorským zákonem a její užití bez udělení oprávnění autorem je nezákonné, s výjimkou zákonem definovaných případů.

Obsah 1 Úvod 3 2 Unixový shell 4 2.1 Historie Unixových shellů............................ 4 2.2 Výběr podporovaného shellu........................... 5 2.3 Bash........................................ 5 2.3.1 Běžné chyby v bashi........................... 7 3 Statická analýza kódu 9 3.1 Principy základních analýz........................... 9 3.1.1 Analýza toku řízení........................... 9 3.1.2 Analýza toku dat............................. 9 3.1.3 Meziprocedurální analýza........................ 10 3.1.4 Hledání nežádoucích vzorů....................... 10 3.1.5 Další statické analýzy.......................... 10 3.2 Související nástroje................................ 11 3.2.1 Moose................................... 11 3.2.2 Yasca................................... 12 3.2.3 Splint................................... 12 3.2.4 FindBugs................................. 12 3.3 Problémy statické analýzy skriptů pro bash.................. 13 3.3.1 Důsledek volání neznámých programů................. 13 3.3.2 Rozsah platnosti proměnných...................... 13 3.3.3 Tok dat bez využití proměnných.................... 13 4 Návrh frameworku 15 4.1 Správa zásuvných modulů............................ 16 4.1.1 Struktura zásuvných modulů...................... 16 4.1.2 Komponenty pro správu modulů.................... 16 4.1.3 Proces vytvoření zásuvného modulu.................. 17 4.1.4 Řešení závislostí............................. 18 4.2 Interakce mezi moduly.............................. 18 4.3 Zpracování výstupů............................... 19 4.4 Nastavení vstupu................................. 20 5 Návrh zásuvných modulů 21 5.1 Lexikální a syntaktická analýza......................... 21 5.1.1 Lexikální analýza............................. 22 5.1.2 Syntaktická analýza........................... 23 1

5.1.3 Budování abstraktního syntaktického stromu............. 23 5.1.4 Zpracování požadavků od ostatních modulů.............. 24 5.2 Stav prostředí................................... 24 5.2.1 Hodnoty v tabulkách........................... 24 5.2.2 Tabulka proměnných........................... 25 5.2.3 Způsob komunikace........................... 25 5.3 Překlad prvků abstraktního syntaktického stromu............... 26 5.4 Základní interpret................................ 28 5.4.1 Zpracování programových smyček................... 29 5.4.2 Podmíněné příkazy............................ 29 5.4.3 Volání funkcí............................... 29 5.4.4 Výstupy modulu............................. 30 5.5 Vyhledávání běžných chyb............................ 30 5.6 Mrtvý kód..................................... 30 5.7 Práce se soubory................................. 31 5.8 Modul pro zpracování výstupů......................... 31 6 Implementace a testování 32 6.1 Demonstrační aplikace.............................. 32 6.2 Implementace frameworku............................ 32 6.3 Implementace zásuvných modulů........................ 33 6.3.1 bash parser................................ 33 6.3.2 environment state............................ 33 6.3.3 bashentity translator........................... 34 6.3.4 basic interpreter............................. 34 6.3.5 common mistakes............................. 34 6.4 Testování..................................... 34 6.4.1 Automaticky vygenerované testy.................... 35 6.4.2 Ručně vytvořené testy jednotek..................... 36 6.4.3 Testování na připravené sadě skriptů.................. 38 6.4.4 Korpus testovacích skriptů....................... 39 7 Závěr 41 7.1 Shrnutí výsledků................................. 41 7.2 Možná rozšíření.................................. 42 A Obsah média 46 B Parametry a příklady spuštění demonstrační aplikace 47 C Ukázka konfiguračního souboru pro demonstrační aplikaci 48 D Argumenty modulů 49 E Ukázky z testovacích skriptů 51 F Použitá gramatika 53 2

Kapitola 1 Úvod V dnešních operačních systémech založených na myšlence a principech Unixu je často konfigurace chování systému či jeho služeb řešena pomocí skriptů interpretovaných některým shellem. S rostoucím tlakem na urychlení vývoje a jeho automatizaci je proto potřeba analyzovat chování těchto skriptů. V ideálním případě by jistě uživatel rád věděl, jaké operace daný skript v systému provede, ještě před před jeho samotným spuštěním, které může vést k ohrožení stability systému, nechtěnou změnu v nastavení, ztrátu dat apod. Zjištění chování programu zkoumáním zdrojového kódu se zabývá statická analýza. Aktuálně se však aplikace s touto tématikou zaměřují spíše na jazyky hojně využívané pro tvorbu komerčních aplikací, např. Java, C++, C# apod. Proto je ladění či zjišt ování chování skriptů často prováděno bud ručně, tedy nahlédnutím přímo do kódu, případně jejich spuštěním v nějakém virtuálním prostředí. Cílem této práce je vytvořit prostředí, které umožní zkoumání skriptů automaticky, přičemž toto prostředí by mělo být snadno použitelné a rozšiřitelné. V první kapitole této práce bude zevrubně popsána historie a principy Unixového shellu, srovnání nejvýznamnějších interpretů a popis syntaxe a některých problematických konstrukcí shellu bash. Druhá kapitola se zabývá základními principy statické analýzy programů a popisem některých současných nástrojů, které se touto problematikou zabývají. Také je zde nastíněna problematika statické analýzy skriptů pro shell. Následující kapitoly se věnují návrhu prostředí a některých analýz, užitečných při zkoumání skriptů vybraného shellu. V kapitole číslo 6 jsou zmíněny některé implementační detaily realizace a provedené testy. Závěrečná kapitola je pak určena ke shrnutí dosažených výsledků a stanovení dalšího vývoje. 3

Kapitola 2 Unixový shell V operačních systémech založených na myšlence Unixu je shell velmi mocným nástrojem. Jedná se o interpret příkazů ve formě textu, zadávaných bud interaktivně uživatelem pomocí klávesnice, nebo předaných ve formě skriptu. V Unix-like 1 systémech však není shell přímo zakomponován do systému, ale jedná se o samostatný program. Tato skutečnost dala za vznik řadě různých interpretů, které se od sebe často významně liší nejen syntaxí, ale i chováním. Díky různým přístupům mají vývojáři možnost inspirovat se u konkurence, případně se naopak poučit z cizích chyb a shell tak dále vylepšovat. Na druhé straně však variabilita znamená větší nároky na uživatele, kteří mohou mít potíže správně porozumět skriptům pro jiný shell, než je jejich oblíbený. 2.1 Historie Unixových shellů V následujícím textu bude v kostce popsána historie shellů v prostředí Unix-like operačních systémů. Zmíněni budou pro stručnost pouze nejvýraznější zástupci [8]. Mezi shelly první generace jsou řazeny Thompson shell a PWB shell (Mashey shell). V prvním jmenovaném byly dokonce i konstrukce pro řízení běhu programu jako if a goto realizovány jako samostatné programy. Již v tomto shellu mají původ operátory pro přesměrování vstupu a výstupu, které zůstávají platné dokonce i v jeho dnešních následovnících. Později Thompson shell zavedl také velmi užitečný koncept roury. PWB shell byl rozšířením Thompson shellu o některé řídicí konstrukce (byly doplněny příkazy switch a while), které byly navíc přímo implementovány jako součást shellu. Thompson shell také zavedl použití jednoduchých proměnných. Bourne shell přišel s novými řídicími konstrukcemi (for, case) a konceptem command substitution. Další změnou oproti předchůdcům bylo použití standardního vstupu skriptu jako implicitního vstupu všech příkazů, dosud byl namísto něj použit sám skript. Paralelně s Bourne shellem byl vyvíjen C-shell, jehož cílem bylo přiblížit syntaxi jazyku C. Kromě nové syntaxe nabídnul C-shell vestavěnou podporu aritmetických operací a implementaci řady běžně používaných programů jako součást shellu. Také zde byl představen koncept aliasů, což jsou prakticky makra. Rozšířením C-shellu přišel na svět tcsh, který přidal zejména řadu vylepšení pro použití v interaktivním módu. 1 Unix-like jsou označovány operační systémy, které se chovají podobně jako Unix, přestože ve skutečnosti nemusejí nutně ani historicky vycházet z původního Unixu [12]. Patří sem např. GNU/Linux BSD systémy, MacOS, HP-UX a další. 4

V roce 1983 David Korn uvedl shell ksh zpětně kompatibilní s Bourne shellem, současně ale podporující řadu vlastností C-shellu a vnášející vlastnosti nové, například atributy proměnných. Brzy byl začleněn do řady Unixů, ovšem jednalo se o komerční produkt. Teprve v roce 2000 byl zveřejněn i zdrojový kód pod licencí CPL-1.0. Jako součást projektu GNU vzniknul Bourne-again shell (bash). Snahou bylo nahradit Korn shell open-source alternativou. Postupem času byl vylepšován, až do značné míry nahradil ostatní shelly vycházející z Bourne shellu a je dnes hojně používán. 2.2 Výběr podporovaného shellu Shelly lze podle použití porovnávat ve dvou hlavních kategoriích v interaktivním režimu nebo jako skriptovací jazyk. V rámci této práce je důležitější druhá z jmenovaných a bylo nutné určit, na který shell se primárně zaměřit. Za tímto účelem jsem provedl průzkum napříč několika Unix-like zdarma dostupnými operačními systémy a zjišt oval, kolik skriptů používá který interpret. Počítání proběhlo vždy na zcela čerstvé základní instalaci systému. Nejprve bylo třeba zjistit, které soubory jsou shellovými skripty. K hledání skriptů jsem využil znakové sekvence zvané shebang, pomocí níž se na prvním řádku skriptu uvádí požadovaný interpret. Příkazem find / -type f (while read f; do head -c 50 "$f" grep ^#!.*sh[ \t]*$ head -n 1; done ) >shebangs byly získány první řádky potenciálních skriptů. Poté jsem s využitím zejména aplikací grep, wc a manuálním zkoumáním výsledků dospěl k údajům shrnutým v tabulce 2.1. sh ksh bash csh ostatní freebsd 3027 0 7 5 2 OpenSolaris 454 105 21 2 4 openbsd 61 5 0 1 0 netbsd (full) 301 1 0 1 0 fedora13 485 0 199 1 2 celkem 4328 111 227 10 8 Tabulka 2.1: Počty skriptů různých shellů ve vybraných distribucích Bash i ksh jsou kompatibilní s Bourne shellem, jsou tedy schopny provádět i skripty určené pro interpret sh. Oproti Korn shellu byl však bash ve zkoumaných systémech použit jako interpret častěji. K rozhodnutí dopomohl také fakt, že po nahlédnutí do zdrojových souborů ksh a bash bylo zjištěno, že bash používá ke tvorbě syntaktického analyzátoru program bison. Bude tedy možné velkou část syntaxe použít přímo ze zdrojových kódů shellu, ksh má naproti tomu syntaktickou analýzu implementovaný procedurálně v jazyce C. 2.3 Bash Bourne-again shell (bash) byl vytvořen s cílem zkombinovat užitečné vlastnosti Korn shellu a C-shellu za dodržení specifikace IEEE POSIX Shell [15]. Jak již bylo zmíněno, bash je 5

součástí projektu GNU. Jako takový je šířen pod licencí GNU GPL a je tudíž zdarma včetně zdrojového kódu. Základní syntaxe vychází z Bourne shellu, nabízí však řadu vlastností, usnadňujících práci jak v interaktivním režimu, tak i při vytváření skriptů. Bash nabízí poměrně mocný programovací jazyk [9, Kap. 3]. Obsahuje konstrukce pro běžné programové smyčky, práci s proměnnými, přesměrování vstupů a výstupů, řetězení příkazů pomocí rour, použití funkcí, ale i řadu dalších konstrukcí. Nebudeme zde rozebírat kompletní syntaxi, ale věnujme trochu prostoru alespoň některým zajímavostem. Základní konstrukcí jazyka je jednoduchý příkaz, který sestává ze jména následovaného jeho argumenty a případně přesměrováními. Řídicí konstrukce jako smyčky nebo podmíněné příkazy jsou realizovány pomocí klíčových slov. Je možné také pojmenování skupiny příkazů použitím funkcí nebo skriptů. Hlavním rozdílem mezi těmito přístupy je prostředí vyvolání funkce je provedeno v kontextu shellu, který ji zavolá, zatímco skript je proveden ve zcela novém shellu. Bash se snaží číst vstup po řádcích (v interaktivním módu s využitím knihovny readline, při zpracování skriptů se řádky načítají do bufferu), které jsou následně předávány lexikální analýze. Využití generátoru bison, usnadňuje výstavbu syntaktického analyzátoru, ovšem v tomto případě není gramatika pro tento přístup úplně ideální. Lexikální analýza proto musí uchovávat informace o aktuálním kontextu a zohlednit je při určování typu tokenů. Zajímavá je práce s aliasy, které umožňují definovat v podstatě makra a jejichž rozvoj je prováděn již na úrovni lexikální analýzy. Konstrukce zvaná command substitution způsobuje podobné potíže z podobného důvodu nejprve se musí zpracovat a provést příkaz určený k expanzi, jeho výstup je posléze dále zpracován lexikálním analyzátorem. Dalším fenoménem jsou tzv. here documents a here strings, což jsou konstrukce, umožňující zadat vstup příkazu přímo za jeho tělem, respektive jako jeho součást. Mezi přečtením příkazu a jeho interpretací bash provádí nad jednotlivými slovy příkazu řadu operací. Jsou expandovány proměnné a parametry (parametry jsou zvláštní případy proměnných), které jsou až na výjimky interpretovány jako řetězce. S těmito řetězci lze provádět řadu operací přímo při expanzi, např. nahrazení řetězce jeho délkou, nějakým podřetězcem atd. Pokud je zapnuta příslušná volba, provede se expanze složených závorek, která slouží k zadání více řetězců s nějakou společnou částí zkráceným způsobem. process substitution je něco jako kombinace command substitution a řetězení příkazů. Výskyt této konstrukce je nahrazen názvem souboru, který je použít jako vstup nebo výstup příslušného příkazu, který je spuštěn na pozadí. Znaky tilda jsou nahrazeny cestou k domovskému adresáři uživatele. Aritmetická expanze umožňuje vyhodnocení aritmetických výrazů v shellu a nahrazení výrazu výslednou hodnotou. Po expanzích následuje rozdělení do slov, nebot se může stát, že expanzí vznikne z jednoho slova slov více. Je-li zapnuta příslušná volba, je následně každé slovo použito jako potenciální vzor a bash se jej pokusí porovnat s existujícími soubory a případně nahradit. Řada příkazů je implementována jako vestavěné funkce. Ve většině případů je tato skutečnost prakticky nerozlišitelná od běžných příkazů, ovšem existují výjimky jako export, typeset, readonly, declare nebo local, které umožňují přiřazení proměnných na místě jejich argumentu a v podstatě tak porušují jinak platné pravidlo (které lze změnit pomocí zapnutím příslušné volby shellu), že přiřazení proměnných předchází příkazu. V případě inicializace polí v rámci těchto příkazů dokonce dochází k porušení syntaxe. Přiřazení hodnot proměnným je samo o sobě netriviální. Když je přiřazení proměnných následováno příkazem, stávají se součástí prostředí pouze pro tento příkaz. Pouze přiřazení použitá jako samostatný příkaz nebo jako argumenty vestavěných příkazů zmíněných v předchozím odstavci zůstávají platná coby součást aktuálního prostředí shellu. 6

Když má bash spustit příkaz (tedy na úrovni lexikální analýzy jsou již vyřešeny aliasy), nejprve se zjistí, zda neexistuje funkce daného jména, která je případně vyvolána. Poté se hledá mezi vestavěnými příkazy a teprve nakonec jsou prohledány adresáře v proměnné PATH. Cílem je nalézt spustitelný soubor daného jména, který je posléze spuštěn. Vyhledávání je ovšem přeskočeno v případě, kdy příkaz obsahuje lomítko, pak se použije přímo uvedená cesta. Po určení příkazu následuje zpravidla vytvoření nového prostředí a konečně provedení příkazu. 2.3.1 Běžné chyby v bashi Na internetu [2] lze nalézt seznam známých chyb či konstrukcí, které k chybám mohou vést a dělají potíže méně i více zkušeným programátorům skriptů pro bash. Některé z nich jsou závislé na použití konkrétních programů, jiné jsou spojené přímo s chováním programu bash. Následující výčet popisuje vybranou podmnožinu těchto konstrukcí: $var na této konstrukci není samo o sobě nic špatného, protože ale obsah proměnné může být expandován i na více slov, existuje v řadě situací nebezpečí neočekávaného chování. Například pokud je obsahem proměnné název souboru, který obsahuje mezery, použití této expanze jako např. argumentu nějakého programu, který vyžaduje na daném místě jméno souboru, obdrží tak jmen více, ke všemu pravděpodobně neexistujících. Pro jistotu je tak více než vhodné bud tento styl expanzí uzavřít do dvojitých uvozovek nebo použít novější tvar ${var}, který tento problém odstraňuje. $(command...) tento případ je velmi podobný předchozímu. Problém opět je, že substituce příkazů může expandovat na více slov. Doporučuje se proto uzavírat tuto konstrukci opět do dvojitých uvozovek. Použití && nebo uvnitř příkazů test a [ tyto příkazy totiž uvedené konstrukce nepodporují. Dokonce ani podporovat nemohou, nebot jsou v těchto případech interpretovány jako tokeny, ukončující příkaz. Bud je tedy nutné rozdělit příkaz do dvou oddělených, nebo použít vestavěný příkaz [[, který takovéto spojení podmínek používá. Použití < nebo > uvnitř příkazů test a [ v tomto případě je situace ještě závažnější, namísto porovnání totiž v těchto případech dojde k přesměrování vstupu či výstupu. Přiřazení proměnných uvnitř pipeline bash spouští každý ze zřetězených příkazů v samostatném prostředí. Přiřazení tudíž nemají po dokončení pipeline efekt. Podobné funkce lze však dosáhnout také pomocí pojmenovaných rour nebo při využití substituce procesů s tím rozdílem, že v takovém případě nejsou přiřazení ztracena ani po dokončení příkazu. [bar="$foo"] problém této konstrukce je, že nejsou argumenty odděleny mezerami. Dojde tak k pokusu o vyvolání příkazu s názvem [bar="$foo"], který pravděpodobně nebude nalezen. V o něco lepším případě [ bar="$foo" ] bude podmínka vždy vyhodnocena jako pravdivá, namísto porovnání řetězců totiž dojde k testu na neprázdný argument. [ [ a = b ] && [ c = d ] ] [ je příkaz, snaha o zanořené použití hranatých závorek je tedy chybná ve všech ohledech. 7

read $var, $var=value použití expanze namísto identifikátoru, kterému by měla být přiřazena hodnota, nedopadne velmi pravděpodobně podle představ programátora ani v jednom z uvedených případů. var = value v tomto případě je naopak problém oddělení mezerami, namísto přiřazení je tato konstrukce vyhodnocena jako volání příkazu var s argumenty = a value. Přesměrování vstupu pro příkaz echo příkaz echo nepracuje se standardním vstupem. Doporučeno je proto použití programu cat nebo printf, které pravděpodobněji provedou to, co programátor zamýšlel. [ bar == "$foo" ] operátor == není standardně příkazem [ podporován. for arg in $* kvůli možným mezerám v hodnotách pozičních parametrů může opět docházet k nežádoucímu oddělení slov. Pro expanzi jednotlivých parametrů jako oddělených slov se používá konstrukce "$@" echo "~" expanze snaku tilda se uvnitř uvozovek neprovede. sed s/$foo/good bye/ expanze proměnných se uvnitř apostrofů neprovádí. 8

Kapitola 3 Statická analýza kódu Statická analýza kódu probíhá bez jeho spuštění. Často nachází uplatnění v překladačích, ale také při verifikaci software. V překladačích je běžně prováděna lexikální a posléze syntaktická analýza. Nezřídka následuje několik dalších analýz, které mají za úkol získat přesnější představu o chování programu. Na základě těchto informací lze pak výsledný program optimalizovat nebo upozornit na odhalená rizika či chyby. Tiše bude předpokládán případ uvedených analýz pro imperativní jazyky, podobně jako v použité literatuře [13, 7]. 3.1 Principy základních analýz Většinou je pro potřeby statických analýz kódu nutné vytvořit nějakou jeho pokročilejší reprezentaci, což bývá úkol lexikální a syntaktické analýzy. Lexikální analýza dostane na vstupu zdrojový soubor, z nějž vytvoří sekvenci tokenů. Ta je pak syntaktickým analyzátorem zpracována podle pravidel příslušné gramatiky a výstupem je často stromová struktura, nazývaná abstraktní syntaktický strom [7]. Některé statické analýzy pracují právě nad touto reprezentací programu, a bývají proto nazývány post-syntaktické. Patří mezi ně např. analýza toku řízení nebo analýza toku dat. V moderních programovacích jazycích jsou běžně podporovány funkce, což analýzu často zesložit uje a je nutné základní algoritmy obohatit o řešení situací, které právě použití funkcí přináší[13]. 3.1.1 Analýza toku řízení Cílem analýzy toku řízení je zjistit možné pořadí vykonávaných příkazů. Nejprve se v programu vyhledají základní bloky, což jsou nejdelší sekvence příkazů, které neobsahují větvení. Každý základní blok má jediný vstupní a výstupní bod, což je popořadě první a poslední příkaz v daném bloku. Ze základních bloků je posléze vybudován orientovaný graf, který se nazývá grafem toku řízení. Podmíněné příkazy v programu odpovídají větvení v odpovídajícím grafu toku řízení, smyčky pak vedou k silně souvislým komponentám grafu. 3.1.2 Analýza toku dat Pokud chceme získat konkrétnější představu o činnosti programu, je velmi důležité znát, jakým způsobem manipuluje s daty. Analýz toku dat je hned několik a bývají využívány v překladačích za účelem optimalizací. Mezi nejběžnější z nich patří: Available Expressions Analysis cílem této analýzy je zjistit, ze kterých míst v programu musí proměnné nést hodnotu. 9

Reaching Definition Analysis je velmi podobně předchozí, avšak zjišt uje se, ze kterých přiřazení proměnné v daném místě mohou nést hodnotu. Very Busy Expression Analysis výraz je označen jako very busy, jestliže je vždy nutně vyhodnocen předtím, než je některé z proměnných v něm obsažených přiřazena jiná hodnota. Cílem analýzy je pro každý bod programu zjistit, které výrazy jsou very busy po provedení daného příkazu. Live Variables Analysis proměnná je živá, pokud je možné, že její hodnota bude v budoucnu použita. Tato analýza umí zodpovědět otázku, které proměnné jsou pro dané místo v programu živé. Princip zmíněných data-flow analýz se liší jen minimálně. Na základě tzv. tokových rovnic, které odrážejí sémantiku konstrukcí zkoumaného jazyka, jsou iteračně získávány informace pro všechna místa programu, dokud dochází ke změnám. Když se již výsledek nemění, je nalezen tzv. pevný bod funkce, což je hledaný výsledek. 3.1.3 Meziprocedurální analýza Uvedené analýzy musejí často řešit problematiku volání procedur. Pro zpracování těchto dnes celkem běžných situací je však nutná úprava některých postupů. Jedou z možností, jak situaci řešit, je tzv. inlining tedy nahrazení volání procedury rozvojem jejího těla. V takovém případě pak již pracují algoritmy bez obměny, avšak není možné této metody jednoduše použít, povolíme-li rekurzivní volání. Existuje řada technik, jak se s problémem volání procedur vypořádat, jednoduchý a přesný postup však obecně neexistuje. Například se zavádí různé techniky sledování kontextu, řetězců volání, grafů volání apod. Vhodnost konkrétního přístupu závisí samozřejmě do značné míry na možnostech jazyka na způsobu předávání parametrů, práci s ukazateli apod. V některých případech lze dokonce předpočítat způsob, jakým vyvolání procedury ovlivní výsledek analýzy. Bývá však často nutno použít pesimistický odhad na úkor přesnosti výsledků. 3.1.4 Hledání nežádoucích vzorů V řadě současných nástrojů zaměřených na statickou analýzu programů je prováděno vyhledávání konstrukcí či typických vzorů. Bývají hledány konstrukce, které vždy nebo často vedou k chybám, případně je znám nějaký důvod, proč na ně alespoň vývojáře upozornit. V různých programovacích jazycích se tak může jednat o využívání nebezpečných funkcí, konstrukce s potenciálně nebezpečnými vedlejšími efekty a podobně. Vyhledávání takových vzorů může být realizováno například pomocí regulárních výrazů ve zdrojovém kódu, v některých případech je však možné také využít již předzpracované reprezentace programu. 3.1.5 Další statické analýzy V jazycích s dynamickou alokací paměti je často prováděna analýza ukazatelů (Shape Analysis [16] zahrnuje např. násobné uvolnění paměti, použití nulového ukazatele atd.), při práci s referencemi pak alias analýza a další. Pro některé jazyky jsou dále známy běžně se vyskytující konstrukce, které zpravidla vedou k chybám pak lze vytvořit a vhodně specifikovat vzory, které se následně vyhledávají. V literatuře často zmiňovanou statickou analýzou je 10

abstraktní interpretace, která pro zjednodušení analýzy komplikovaného systému využívá ztrátu určité přesnosti. Provádí se tedy analýza abstraktního, nikoliv reálného systému. 3.2 Související nástroje V současnosti se zabývá statickou analýzou různých jazyků celá řada programů. Některé dokonce nejsou zaměřeny přímo na konkrétní jazyk, avšak v takových případech často bud využívají externích programů, nebo jsou schopné zjistit jen omezené množství informací. Možnosti analýzy shellových skriptů jsou však aktuálně značně omezené, ve většině případů pouze na analýzu syntaxe. Uved me si zde nyní krátký přehled vlastností několika nástrojů, které byly prozkoumány za účelem inspirace. 3.2.1 Moose Open-source projekt Moose [11] je velmi zajímavý hned z několika pohledů. Jedná se o snadno rozšiřitelnou platformu pro zkoumání dat/objektů a vztahů mezi nimi. Systém není vázán na zkoumání konkrétního jazyka, což je řešeno prostřednictvím tzv. importerů, které převádí vstupní data (at už zdrojové kódy v nějakém programovacím jazyce, XML dokumenty či podobně) na modely, se kterými pak systém umí interně pracovat, tedy provádět jejich analýzu. Moose pracoval v průběhu vývoje s několika meta-modely pro popis statické struktury objektově orientovaných systémů. Mezi nimi např. rodina meta-modelů FAMIX, která vznikla dokonce před dnes hojně rozšířeným UML. Prostředí je značně otevřené a rozšiřitelné pomocí zásuvných modulů. Samotné jádro je minimalistické a obsahuje několik tzv. enginů, kterými lze ovládat způsob chodu celého systému. Jako součást platformy dnes mimo jiné existuje podpora GUI pro dotazování, prostředky pro vizualizaci výsledků, provádění řady analýz či celá škála importerů. Obrázek 3.1: Workflow platformy moose Celý systém využívá možností jazyka Smalltalk, v němž je implementován. Implementační jazyk je dokonce využit např. i k dotazování či skriptování. Použití Smalltalku je ale možné současně považovat za jisté úskalí, protože prostředí tohoto jazyka totiž v současnosti využíváno příliš širokou komunitou. 11

3.2.2 Yasca Yasca [14] je nástroj určený ke statické analýze zdrojových kódů v teoreticky jakémkoliv jazyce, což je umožněno voláním existujících nástrojů pro daný jazyk. Pro tyto nástroje je třeba vytvořit zásuvný modul, který se postará o spuštění příslušného programu a zpracuje výsledky analýzy do datové struktury, kterou je posléze schopna Yasca dále zpracovat. Vznik nového zásuvného modulu znamená prakticky vytvoření nové třídy, odvozené od připravené třídy Plugin. Implementačním jazykem je PHP. Yasca nabízí řadu zásuvných modulů, mezi nimiž má významnou pozici zásuvný modul pro vyhledávání vzorů (zadaných pomocí regulárních výrazů) ve zdrojovém kódu. Jednotlivé vzory jsou uloženy v konfiguračních souborech a celá řada jich je předchystána zejména pro jazyk Java. Kromě základních metod pro spuštění analýzy a získání jejích výsledků si může Plugin registrovat callback funkci pro 4 různé události před spuštěním analýz a po jejich dokončení, před zpracováním výsledků a po jejich zpracování. Plugin obsahuje informace jako koncovky souborů, pro které je určen, popis své funkce a některé další. Jednotlivé položky výsledků analýzy nesou název souboru a číslo řádku v souboru, kde bylo zjištěno něco zajímavého, ohodnocení důležitosti nálezu, případně kontext a další informace, přičemž o nastavení těchto položek se stará Plugin. Jedná se o poměrně jednoduchý nástroj, který je však schopen mnoha užitečných akcí. Zajímavá je zejména základní myšlenka integrace celé řady nástrojů (od antivirové kontroly po analýzu částečně kompilovaného kódu) do jediného systému s jednotnou podobou výsledků. 3.2.3 Splint Splint [6] je open-source nástupcem programu Lint a je určen pro analýzu programů v jazyce C. Nastavení prováděných kontrol se provádí pomocí konfiguračních souborů, případně prostřednictvím parametrů na příkazové řádce. Analýzy lze dále ovlivňovat pomocí anotací ve stylizovaných komentářích. Rozšiřitelnost je do jisté míry umožněna pomocí uživatelsky definovaných anotací ve zvláštních konfiguračních souborech. Dalším způsobem je jiný typ komentářů, tzv. control comments, pomocí kterých lze ovlivňovat stav analyzátoru např. nastavením nějakého příznaku apod. Pomocí anotací lze specifikovat, co která funkce dělá s globálními proměnnými, jak pracuje s parametry apod. Pomocí velkého množství anotací je možné analyzátoru značně přiblížit chování programu, což však klade přidané nároky na autory zdrojového kódu. Rozšiřitelnost kontrol je sice omezena pouze na uživatelské anotace, ovšem systém zřejmě dobře funguje, jinak by se pravděpodobně nedočkal verzí pro další programovací jazyky (existují verze pro Python, C++, či JavaScript). 3.2.4 FindBugs FindBugs [4] je program pro hledání chyb ve zdrojových kódech v jazyce Java, založený na principu chybových vzorů (jazykových konstrukcí, které jsou často chybou). Výskyt těchto vzorů je prováděn nad byte kódem, přičemž využívá jeho zvláštní struktury. Je umožněna tvorba uživatelských zásuvných modulů, konfiguraci pomocí XML souborů, použití anotací pro zpřesnění výsledků a nabízí velké množství předchystaných chybových vzorů. Výhodou je také snadná integrace analýzy do procesu překladu. 12

FindBugs nabízí grafické uživatelské rozhraní pro interaktivní prohlížení výsledků, stejně jako rozhraní příkazové řádky, vhodnější spíše pro automatickou analýzu programů např. při překladu. 3.3 Problémy statické analýzy skriptů pro bash Dynamičnost a vlastnosti jazyka bash činí ze statické analýzy nejednoduchý úkol. Oproti programovacím jazykům, v nichž lze program rozebrat až na atomické jednotky s pevnou sémantikou (přiřazení proměnných, základní aritmetické operace apod.), u skriptů tuto možnost většinou nemáme. 3.3.1 Důsledek volání neznámých programů Volané příkazy mají z pohledu interpretu povahu černé skříňky (s výjimkou několika vestavěných). Všechny analýzy, jejichž výsledky jsou závislé na toku řízení, tak narazí na významná omezení, nebot prakticky libovolný příkaz může být použit jako podmínka v řídicích konstrukcích. Pokud tedy nejsme schopni určit navrácenou hodnotu libovolného příkazu v daném kontextu, vždy narazíme na nějakou neurčitost (nemluvě o problému určení kontextu samotného). Namísto množiny podmínek vedoucích k nějakému stavu programu je tak dostáváme množinu programů s různými hodnotami argumentů a celý problém se tak značně komplikuje. Navíc v pozici podmínky může být v bashi naprosto jakýkoliv příkaz, tedy i cyklus, složený příkaz, několik příkazů tvořících pipeline nebo dokonce deklarace funkce. 3.3.2 Rozsah platnosti proměnných Není ale jednoduchá ani samotná práce s proměnnými. Některé komplikace souvisejí se způsobem, jakým bash pracuje s prostředími. Uved me si názorný příklad: v1=3 ; v2[v1+=2]=$(( v1 += 5 )) echo ; echo "v2: ${v2[v1++]} v1: $v1 " Jednotlivé příkazy jsou odděleny středníkem. První způsobí nastavení proměnné v1 na hodnotu 3. Protože ve druhém příkazu přiřazení předchází příkazu echo, provede se pouze pro prostředí tohoto příkazu (a jelikož se jedná o příkaz vestavěný, prakticky se zahodí). Aritmetická expanze se ovšem vyhodnotí v kontextu prostředí shellu a proměnná v1 tak nově nabývá hodnoty 8. Expanze proměnné v2 ve třetím příkazu může dopadnout různě. Byla-li proměnná v2 deklarována jako asociativní pole, bude vypsána bud prázdná hodnota nebo hodnota uložená pod indexem "v1++" a hodnota v1 bude stále 8. V opačném případě se však při zjišt ování indexu vyhodnotí aritmetický výraz a po expanzi v1 tak bude vypsána hodnota 9. Nutno dodat, že pokud bychom otočili pořadí výpisu a první se expandovala v1, nesla by v každém případě hodnotu 8, nebot k případné modifikaci by došlo teprve s expanzí v2. 3.3.3 Tok dat bez využití proměnných Kromě práce s proměnnými je složité sledovat také jiné toky, které vznikají při zpracování skriptů. S využitím řetězení příkazů, přesměrování a konstrukcí jako substituce příkazů či procesů se nezřídka provádí značný objem práce bez nutnosti použití jediné proměnné. Už jen např. udržet povědomí o stavu souborových deskriptorů obnáší řadu úskalí. Pro 13

důslednou analýzu práce se soubory by navíc byla nutná znalost, jakým způsobem každý volaný příkaz (program) nakládá se svými argumenty. Ty jsou v mnoha případech použity právě jako názvy souborů, s nimiž příkaz pracuje. Programy však mohou číst i řadu jiných souborů, jejichž jména nejsou v rámci skriptu nikde viditelná (např. inicializační soubory). Možnost spouštět příkazy na pozadí analýzu dále komplikují přítomností paralelního provádění a asynchronního řízení. Při analýze skriptů se tedy můžeme zaměřit na sledování problematických konstrukcí a pokusit se zjistit alespoň některé informace. Je ovšem nutné počítat s velkou mírou neurčitosti. Tu lze do jisté míry omezit simulací chování zvoleného interpretu, úplné odstranění však nebude pravděpodobně možné nikdy. 14

Kapitola 4 Návrh frameworku Cílem práce je vytvoření systému, který umožní provádět statickou analýzu skriptů pro shell, pro které aktuálně nástroje chybí. Systém by měl být pokud možno dostatečně univerzální a snadno rozšiřitelný pomocí zásuvných modulů, k jejichž vývoji by měl nabídnout dostatečně komfortní prostředí. Rozhodnul jsem se tedy ponechat pevně zakódované pouze nejnezbytnější úkony a většinu práce ponechat na volně přidávaných zásuvných modulech, podobně jako u platformy Moose. Abychom mohli kombinovat funkcionalitu jednotlivých zásuvných modulů, musí být zajištěny prostředky pro jejich konfiguraci, komunikaci a výměnu dat či zjištění přítomnosti konkrétních modulů v systému (např. za účelem řešení případných závislostí). Vzhledem k určení systému je pak samostatnou otázkou zpracování výstupů, aby mohly být výsledky analýz vhodně interpretovány. Bude samozřejmě třeba také nějakým způsobem nastavit a spravovat vstupy systému. Graficky lze základní strukturu frameworku naznačit zjednodušeným diagramem tříd na obrázku 4.1. Obrázek 4.1: Zjednodušený diagram tříd frameworku V diagramu lze kromě bázových tříd pro různé druhy zásuvných modulů pozorovat barevně odlišené subsystémy pro zajištění výše popsaných služeb: 15

Správa zásuvných modulů (modrá barva) Komunikační subsystém (červená barva) Zpracování výstupů (zelená barva) Konfigurace vstupu (žlutá barva) Jednotlivé subsystémy budou propojeny třídou Kernel, která také zaručí jejich zpřístupnění okolí, tedy zejména klientským aplikacím, a zásuvným modulům. Tuto třídu lze realizovat jako návrhový vzor singleton [10] a služby poskytovat prostřednictvím příslušných třídních metod. 4.1 Správa zásuvných modulů Už vzhledem k myšlence, že by většinu užitečné práce měly vykonávat zásuvné moduly, je poměrně zásadní volba implementačního jazyka, jehož možnosti výrazně ovlivní způsob realizace. Při dostatečné znalosti jazyka Smalltalk by bylo možné využít přímo zmíněné platformy Moose, vzhledem k mým zkušenostem byl ale pro realizaci celého systému zvolen jazyk C++. V podstatě jediným řešením pro použití zásuvných modulů v tomto jazyce je použití dynamických knihoven, jejichž spravování musí být proto také součástí systému. 4.1.1 Struktura zásuvných modulů Registraci zásuvného modulu by bylo možno provést vyhledáním konkrétního symbolu uvnitř knihovny prostřednictvím již poměrně starého aplikačního rozhraní. Úskalím této varianty je však velmi problematická typová kontrola. Zajímavější možností se jeví vynutit takovou strukturu knihovny, která by umožnila automatickou registraci zásuvného modulu v systému při zavedení dynamické knihovny do paměti. Za tímto účelem je v systému definována třída PluginFactory (jak její název napovídá, jedná se o použití návrhového vzoru abstract factory [10]). Každý zásuvný modul bude pak povinně obsahovat třídu od ní odvozenou a jednu statickou instanci této třídy. Při zavedení knihovny do paměti se tak vytvoří objekt, který se při své konstrukci zaregistruje prostřednictvím příslušných metod v systému. Jeho jediným úkolem je vytvářet instance třídy odvozené od bázové třídy Plugin, která představuje rozhraní zásuvného modulu vzhledem ke zbytku systému. Teoreticky by bylo možné vynechat tovární třídu a staticky instanciovat přímo objekt typu Plugin. Nelze však vyloučit, že bude mít v budoucnu smysl vytvoření dvou instancí takového objektu. Něco takového by však nebylo jednoduché, protože konkrétní dynamická knihovna může být většinou do paměti zavedena pouze jednou. Popsané řešení umožňuje kontrolu, zda se již příslušná knihovna v paměti nachází. Instance objektů typu Plugin pak lze vytvořit použitím odpovídající továrny. 4.1.2 Komponenty pro správu modulů V sytému jsou z pohledu správy zásuvných modulů důležité zejména dvě kooperující třídy DllManager a PluginManager. Úkolem první z nich je zejména zajištění úkonů, spojených s fyzickou podobu zásuvného modulu coby dynamické knihovny na pevném disku. Vzhledem k popsané skutečnosti, že daná dynamická knihovna může být v paměti pouze jednou, je u této komponenty opět využito návrhového vzoru singleton [10]. DllManager tedy 16

udržuje na jediném místě informace o zavedených knihovnách a jim příslušejících objektech typu PluginFactory. Těchto informací využívá pak třída PluginManager k vytváření objektů typu Plugin. Kromě registrace má třída PluginManager za úkol umožnit vyhledávání a konfiguraci jednotlivých modulů, ale také jejich hromadné spuštění, uvedení do původního stavu apod. Pro snadnou konfiguraci jednotlivých modulů a nastavení, které moduly mají být zavedeny, jsou určeny třídy PluginArgs a PluginConfig. První z jmenovaných představuje poměrně jednoduchou strukturu, uchovávající argumenty pro daný zásuvný modul. Struktura je navržena tak, aby bylo možné argumenty zpracovat např. pomocí běžně používané funkce getopt. Prostřednictvím třídy PluginConfig je předán systému seznam zásuvných modulů a jejich argumentů. Třída umožňuje mimo jiné přidání či odebrání všech zásuvných modulů z daného adresáře či jednoduché filtrování. Struktura subsystému pro správu modulů je naznačena na obrázku 4.2. Obrázek 4.2: Struktura subsystému pro správu zásuvných modulů 4.1.3 Proces vytvoření zásuvného modulu Při požadavku na zavedení zásuvného modulu bude nejprve zjištěno, zda již byla zavedena příslušná dynamická knihovna. Pokud ne, je tak učiněno a nedojde-li ke komplikacím, je výsledkem této akce mimo jiné nově zaregistrovaná továrna. Pomocí továrny je pak vytvořen a zaregistrován příslušný objekt typu Plugin. Proces znázorňuje diagram na obrázku 4.3. Obrázek 4.3: Proces vytvoření a registrace zásuvného modulu 17

4.1.4 Řešení závislostí Jelikož jednotlivé moduly mohou být vyvíjeny nezávisle, může dojít k situaci, kdy některý přestane být zpětně kompatibilní se svými staršími verzemi. Objekty odvozené od třídy Plugin musejí proto implementovat metodu, pomocí které lze zjistit, je-li objekt kompatibilní s požadovanou verzí. 4.2 Interakce mezi moduly Jednou z možností by byla spolupráce prostřednictvím referencí, které by objekt typu Plugin podle potřeby získal např. při inicializaci. Vznikala by tak ale poměrně úzká vazba mezi jednotlivými moduly. Vzhledem k povaze systému je proto zvolena komunikace pomocí událostí, reprezentovaných pomocí objektů odvozených od třídy Event. Jedná se o jednoduchou datovou strukturu, obsahující zejména informaci o svém původu a typu. Typ události je určen celočíselným identifikátorem, jehož význam je definován odesílatelem události (kromě několika rezervovaných pro zvláštní události, využívané frameworkem jako např. pokyn pro zahájení nebo ukončení práce modulu, inicializaci atd.). Aby bylo možné mezi moduly přenášet také nějaká další data (kromě identifikátoru typu), lze pomocí objektů třídy Event přenášet také celočíselný parametr a ukazatel na jakýkoliv objekt, odvozený od třídy Object. Množina typů událostí je tak prakticky neomezená (nebot moduly mohou využívat také datových položek k rozlišení typu události) a závisí na jejich definici jednotlivými moduly. Systém předávání událostí umožňuje nastavit, o kterých skutečnostech chce být modul informován. Pro tyto účely lze konkrétní událost identifikovat pomocí dvojice celočíselných identifikátorů. Prvním je identifikátor odesílatele komponenty, která událost vytvořila. Druhým číslem je typ události, který určuje odesílatel. Každá komponenta se tak může zaregistrovat k reakci na konkrétní událost od konkrétního odesílatele, všechny události od daného odesílatele, události určitého typu od všech odesílatelů nebo dokonce všechny události od všech odesílatelů. Kromě registrace pro příjem konkrétních událostí se mohou zásuvné moduly registrovat k jejich zpracování v rámci jedné ze tří front, které jsou zpracovány sekvenčně. Lze tak zajistit, aby některé moduly zpracovaly událost dříve než jiné. Způsob doručování událostí popisuje algoritmus 4.1. K vyřizování registrací a doručování událostí je určena třída EventManager. V některých případech může být užitečné zaslat událost pouze konkrétnímu příjemci. Nazvěme takovéto události přímo adresované. Jejich primárním účelem je umožnit reakci na události s charakterem dotazu předáním odpovědi. Některé moduly tak mohou snadno zastávat funkci poskytovatele nějaké služby, pro kterou definují strukturu a identifikátory zpráv. Vzhledem k zamýšlenému použití těchto událostí budou vyřizovány přednostně bez využití fronty. Mimo jiné jsou tímto způsobem doručovány zmíněné řídicí příkazy. Kromě přednostního zpracování je další vlastností těchto událostí skutečnost, že jsou privátní. Ostatní komponenty o nich nejsou informovány, nezařídí-li to jeden z účastníků takovéto komunikace explicitně. Pro situace, kdy by byly události dlouhodoběji generovány rychleji, než by se stíhaly zpracovat, umožňuje třída EventManager omezit velikost fronty pomocí dvou limitů. Při dosažení jednoho z nich nejsou jednoduše další události do fronty přidávány, dosažení druhého způsobí vyvolání výjimky. Jejich případné nastavení a využití záleží na aplikaci. 18

Algoritmus 4.1 Doručování událostí Input: Database of listeners, event queue Output: Events in the queue are delivered and processed while queue not empty do event = queue.front listeners = GetListeners(event.type,event.sender) for all listener in listeners.prelisteners do DeliverEvent(listener,event) end for for all listener in listeners.listeners do DeliverEvent(listener,event) end for for all listener in listeners.postlisteners do DeliverEvent(listener,event) end for ModifyRegistrations() PopFront(queue) end while 4.3 Zpracování výstupů Hlavním úkolem celého systému je získání informací o programu. Ty je však nutné také vhodně prezentovat. Jelikož nelze předem určit formát výstupních dat vyžadovaný konkrétní aplikací, bude vhodné i výstupy jednotlivých komponent zpracovávat pomocí zásuvných modulů. Je proto navržena speciální třída Reporter, která poslouží jako základ pro moduly, starající se právě o zpracování a formátování výstupů. Protože již máme k dispozici prostředky ke komunikaci, využijeme jich také pro tyto účely. Je definován zvláštní typ události ReportEvent, která bude obsahovat kromě zprávy pro uživatele také informaci, kde ve zdrojových datech byl nalezen podmět pro hlášení, jaká je důležitost zprávy, kdo je jejím autorem, případně nějakou doplňující informaci. Lze předpokládat, že ne vždy bude žádoucí zpracovávat všechny zprávy v systému generované. Aby nemusely objekty odvozené od třídy Reporter filtrovat zprávy každý zvlášt a nešířilo se zbytečné množství událostí tohoto typu, je v systému zavedena také třída ReportManager. Ta umožňuje provádět filtrování alespoň podle nastavené důležitosti. Namísto generování události přímo jsou tak při generování zprávy předány potřebné informace této třídě. Zde se na základě nastavených filtrů rozhodne, zda bude událost skutečně vytvořena, nebo se zpráva zahodí. Tato komponenta se také stará o nastavení, kam se mají zapsat data po zformátování. Zde nastavený cíl výstupu však je pro zainteresované moduly pouze doporučením. Bude-li totiž současně pracovat více Reporterů, pravděpodobně nebudeme chtít jejich výstupy zapisovat do jediného souboru, spíše je podle potřeby nakonfigurujeme (pokud to ovšem umožňují). Objekty typu Reporter jsou automaticky zaregistrovány jako příjemci všech zpráv typu ReportEvent, kterým je přidělen zvláštní identifikátor typu. Pokud nebude některému Reporteru postačovat filtrování podle důležitosti na úrovni ReportManageru, může zprávy od nežádoucích modulů či nevyhovujícího typu filtrovat lokálně. 19

4.4 Nastavení vstupu Celý systém má sloužit k analýze skriptů, je tedy třeba zajistit také možnost nastavení vstupního souboru pomocí třídy InputManager. O změně vstupu jsou informovány zvláštní událostí rezervovaného typu objekty, kterých se to přímo dotkne. Zdaleka ne všechny analýzy budou totiž pracovat nad zdrojovým souborem, naopak spíše většina z nich využije výstupu syntaktického analyzátoru či jiných analýz. Pro moduly pracující přímo nad skriptem na vstupu je proto připravena zvláštní třída Streamer. Objekty tohoto typu pak obdrží událost o změně vstupu automaticky. 20

Kapitola 5 Návrh zásuvných modulů Od návrhu prostředí systému se nyní přesuňme k vlastním analýzám. V této kapitole bude nastíněna architektura a způsob činnosti několika modulů, které by měly umožnit zjistit alespoň základní informace o skriptech. Také bude znovu upozorněno na některá úskalí spojená s dynamičností jazyka, který bash používá. V rámci této práce byly navrženy a realizovány následující zásuvné moduly: Lexikální a syntaktická analýza úkolem tohoto modulu je vytvořit abstraktní syntaktický strom pro vstupní skript. Při vytváření tohoto stromu jsou generovány definované události. Stav prostředí tento podpůrný modul umožňuje simulovat stav prostředí. Kromě práce s celými prostředími je pomocí událostí možné pracovat s tabulkami proměnných, aliasů a funkcí v prostředí aktuálně aktivním pro daný modul. Překlad prvků abstraktního syntaktického stromu nabízí ostatním komponentám transformace některých podstromů abstraktního syntaktického stromu na jednodušší datové struktury, které bude možné snáze zpracovat. Základní interpret tento modul zpracovává výsledky syntaktické analýzy a překládá je na novou sadu událostí, přičemž se snaží částečně interpretovat skript s využitím zmíněných podpůrných modulů. Vyhledávání běžných chyb modul pro vyhledávání běžných chyb (popsaných již ve druhé kapitole) pracuje s výsledky základního interpretu a snaží se identifikovat a hlásit nebezpečné programové konstrukce. Vyhledávání mrtvého kódu tento modul se snaží identifikovat na základě výsledků základního interpretu v analyzovaném skriptu mrtvý kód. Práce se soubory modul pro hledání a hlášení operací se soubory. Zpracování výstupů modul zpracovávající výstupy analýz v podobě hlášení, která formátuje a vypisuje v podobě jednoduchého HTML dokumentu. 5.1 Lexikální a syntaktická analýza Pro většinu dalších analýz je klíčovým krokem vytvoření abstraktního syntaktického stromu. Jak již ale bylo popsáno dříve, bash umožňuje některé konstrukce, které tento úkol komplikují. 21

Obrázek 5.1: Zjednodušený diagram tříd modulu bash parser. Nejprve načrtněme strukturu modulu, který nese název bash_parser (v grafické podobě ji znázorňuje obrázek 5.1). Práce nad vstupními daty (skriptem) bude rozdělena mezi lexikální a syntaktický analyzátor. Další komponenta dostane na starost vytváření struktur pro reprezentaci programových entit a jejich spojování do výsledného stromu. Vše zastřeší třída odvozená od Streamer, která se postará o začlenění do systému a bude kontrolovat součinnost všech těchto částí. Bylo by jistě možné lexikální a syntaktickou analýzu oddělit do dvou samostatných modulů. Protože však tyto komponenty spolupracují velmi úzce a není pravděpodobné, že by některý z modulů (kromě právě syntaktické analýzy) zajímala reprezentace skriptu jako řetězce tokenů, jeví se přirozenější jejich spojení do jediného celku. 5.1.1 Lexikální analýza Lexikální analýza je poměrně složitá, podobně jako v programu bash totiž řeší situace závislé na aktuálním kontextu. Krom toho je však z pohledu statické analýzy velmi nepříjemná skutečnost, že bash některé části kódu interpretuje již v této fázi zpracování. Konkrétně se jedná například o řešení aliasů nebo expanzi proměnných v některých situacích. Původně byl záměr využít v těchto případech podpůrné moduly, které by umožnily částečnou interpretaci. Od tohoto kroku však bylo nakonec upuštěno, nebot by se ani tak velmi pravděpodobně nepodařilo pokrýt všechny problematické případy, lexikální analýza by se ještě zesložitila, ale hlavně by docházelo ke ztrátě informace (pokud by se samozřejmě nezajistilo nějakým způsobem její uchování). Například pokud by prováděl již lexikální analyzátor expanze proměnných, v abstraktním syntaktickém stromu by se pak již žádné expanze neobjevily (na druhou stranu by se však strom tímto krokem zjednodušil). Mezi konstrukce, které musí vzniklý lexikální analyzátor řešit, patří zpracování aritmetických výrazů, složených přiřazení polí nebo zmíněné expanze proměnných a substituce příkazů. Některé vestavěné příkazy bashe mají navíc nepříjemnou vlastnost umožňují použití některých tokenů na místech, kde se jedná u běžných příkazů o syntaktickou chybu. Pravděpodobně největší výzvou se však stalo zpracování substituce příkazu za využití již poměrně zastaralé notace s použitím zpětných apostrofů, resp. s jejich zanořováním. Aktuální analyzátor je schopen zpracovávat i zanořené substituce v tomto tvaru. V určitých případech je však namísto podstromu, který by reprezentoval daný příkaz, vrácen pouze řetězec s tímto příkazem. Jedná se o situace, kdy je v řetězci s příkazem objevena sekvence "\$", která vede na zanořenou expanzi proměnné, kterou v současnosti není schopen modul rozlišit od expanze, kterou by bash provedl již při přípravě příkazu ke spuštění. Uved me příklad: 22