UNIVERSIDADE FEDERAL DO RIO GRANDE DO SUL INSTITUTO DE INFORMÁTICA CURSO DE GRADUAÇÃO EM CIÊNCIA DA COMPUTAÇÃO Um depurador para a plataforma Holo Por Fábio Reis Cecin Projeto de Diplomação Prof. Dr. Cláudio Fernando Resin Geyer Orientador Prof. Dr. Jorge Luís Victória Barbosa Co-Orientador Porto Alegre, 29 de abril de 2002 Agradeço a todas as pessoas que contribuíram e contribuem à minha formação como cientista da computação. De forma geral, agradeço a todas as pessoas que contribuíram e contribuem à minha evolução como consciência humana. De coração, espero estar fazendo o mesmo por todos vocês. Agradeço especialmente a todos os professores e colegas com os quais convivi e convivo no Instituto de Informática da UFRGS - um lugar de pessoas generosas, prontas para ajudar e compartilhar. Agradeço aos meus pais, à minha família, e à minha Anelise, com todo o amor. Reservo agradecimento especial ao meu orientador, Prof. Dr. Cláudio Resin Geyer, e ao meu coorientador, Prof. Dr. Jorge Luís Victória Barbosa. Obrigado pelo apoio. "Aqui e agora, rapazes! Aqui e agora!" - Aldous Huxley (A Ilha) Porto Alegre, abril de 2002 2 Sumário LISTA DE FIGURAS .............................................................................................................................5 LISTA DE TABELAS .............................................................................................................................6 RESUMO ................................................................................................................................................7 ABSTRACT ............................................................................................................................................8 1. INTRODUÇÃO...................................................................................................................................9 1.1 TEMA..............................................................................................................................................9 1.2 CONTEXTO ......................................................................................................................................9 1.3 MOTIVAÇÃO ................................................................................................................................. 10 1.4 OBJETIVOS .................................................................................................................................... 10 1.5 ORGANIZAÇÃO DO TEXTO.............................................................................................................. 10 2. HOLO................................................................................................................................................ 12 2.1 HOLOPARADIGMA ......................................................................................................................... 12 2.2 HOLOLINGUAGEM ......................................................................................................................... 13 2.3 HOLOPLATAFORMA 1.0..................................................................................................................15 2.4 HOLO 1.0 ......................................................................................................................................17 2.4.1 Características...................................................................................................................... 17 2.4.2 Exemplo: Jantar dos Filósofos............................................................................................... 19 2.5 DEPURAÇÃO HOLO ........................................................................................................................ 20 2.5.1 Conceito de Depuração .........................................................................................................20 2.5.2 Depuração concorrente.........................................................................................................21 2.5.3 Depuração multiparadigma...................................................................................................22 2.5.4 Depuração distribuída........................................................................................................... 25 2.5.5 Serviços básicos de depuração para Holo 1.0 ........................................................................ 25 2.6 CONSIDERAÇÕES FINAIS ................................................................................................................ 28 3. HOLO EXTENSÃO PARA DEPURAÇÃO (HED).......................................................................... 29 3.1 VISÃO GERAL ................................................................................................................................29 3.2 EXTENSÃO DA HOLOPLATAFORMA 1.0 ........................................................................................... 31 3.3 HOLOJAVA ESTENDIDA ..................................................................................................................33 3.3.1 Visão Geral........................................................................................................................... 33 3.3.2 Formato da informação de depuração ................................................................................... 34 3.3.3 Alterações na gramática e limitações..................................................................................... 36 3.4 HOLO DEBUG INTERFACE - VISÃO GERAL ...................................................................................... 37 3.5 INTERFACES E ALGORITMOS DA HDI.............................................................................................. 37 3.5.1 Gerenciamento de sessões de depuração................................................................................ 39 3.5.2 Breakpoints........................................................................................................................... 40 3 3.5.3 Carga de entes estáticos ........................................................................................................ 40 3.5.4 Execução passo-a-passo........................................................................................................ 41 3.5.5 Inspeção da história de um ente.............................................................................................43 3.5.6 Detecção de mobilidade lógica .............................................................................................. 44 3.5.7 Configuração de requisições ................................................................................................. 44 3.6 HOLO DEBUGGER ..........................................................................................................................45 4. IMPLEMENTAÇÃO ........................................................................................................................ 48 4.1 HOLOJAVA ESTENDIDA ..................................................................................................................48 4.1.1 Visão geral............................................................................................................................ 48 4.1.2 Limitações e possibilidades de expansão................................................................................ 48 4.2 HOLO DEBUG INTERFACE .............................................................................................................. 49 4.2.1 Visão Geral........................................................................................................................... 49 4.2.2 Limitações e possibilidades de expansão................................................................................ 49 4.3 HOLO DEBUGGER ..........................................................................................................................50 4.3.1 Visão Geral........................................................................................................................... 50 4.3.2 Limitações e possibilidades de expansão................................................................................ 50 4.4 DESEMPENHO ................................................................................................................................51 5. CONCLUSÃO ...................................................................................................................................52 BIBLIOGRAFIA...................................................................................................................................54 ANEXOS ...............................................................................................................................................57 ANEXO 1 - C ÓDIGO JAVA GERADO PARA O "JANTAR DOS FILÓSOFOS" ................................................... 57 ANEXO 2 - DOCUMENTAÇÃO (JAVADOC) DA HDI ................................................................................. 61 4 Lista de figuras Figura 1 – Gênese do Holoparadigma.................................................................................. 12 Figura 2 – Composição de entes .......................................................................................... 14 Figura 3 – Forma geral de representação de um ente estático na Hololinguagem ................. 15 Figura 4 – Holoplataforma 1.0............................................................................................. 16 Figura 5 – Problema do "Jantar dos Filósofos", modelado em Holo 1.0 ............................... 18 Figura 6 – Grafo de Invocação de Ações (AIG) de Holo...................................................... 23 Figura 7 – Exemplo de controle gráfico (à direita) para a exibição da HoloTree, a estrutura de árvore em que os entes em Holo são organizados (à esquerda). .................................... 25 Figura 8 – Modelo da Holoplataforma estendida para depuração ......................................... 29 Figura 9 – Holoplataforma 1.0 estendida para depuração através da HED. Em destaque: módulos que receberam extensões ou foram implementados no contexto deste trabalho. .................................................................................................................................... 31 Figura 10 – Geração e utilização da informação que realiza o mapeamento bidirecional entre código Java e programa Holo, necessário para a implementação dos algoritmos de depuração. ................................................................................................................... 34 Figura 11 – Arquivo de informação de depuração para o "Jantar dos Filósofos" .................. 35 Figura 12 – Algoritmo de passo-a-passo do tipo step into ou step over adaptado para implementação da HDI. ............................................................................................... 42 Figura 13 – Interface do Holo Debugger.............................................................................. 45 Figura 14 – Programa Java, classe “holo”, que implementa o ente “holo” do programa Holo que simula o “Jantar dos Filósofos”. ............................................................................ 59 Figura 15 – Programa Java, classe "filosofo", que implementa o ente "filosofo" do programa Holo que simula o "Jantar dos Filósofos”..................................................................... 60 5 Lista de tabelas Tabela 1 – Ações que compõe a janta do filósofo ................................................................ 20 Tabela 2 – Serviços de depuração para Holo 1.0.................................................................. 26 Tabela 3 – Mapeamento da HED para a Holoplataforma 1.0 ............................................... 32 Tabela 4 – Informação de depuração gerada para arquivo pela HoloJava estendida ............. 35 Tabela 5 – Código do arquivo de informação de depuração e os atributos possíveis para uma linha de código Java gerada pela ferramenta HoloJava 1.0 estendida. .......................... 36 Tabela 6 – Interfaces da HDI............................................................................................... 39 Tabela 7 – Comandos do Holo Debugger ............................................................................ 47 6 Resumo A Hololinguagem (de forma abreviada, Holo) é uma linguagem de programação oriunda de uma nova proposta para o desenvolvimento de software, o Holoparadigma. Este trabalho fornece um tratamento inicial para o problema do suporte à depuração de Holoprogramas. Este tratamento é realizado através da construção de uma visão geral sobre o problema, que inclui, por exemplo, o aspecto multiparadigma da linguagem. Desta visão, foi derivada uma proposta de extensão para a Holoplataforma, a Holo Extensão para Depuração (HED). Esta extensão é então materializada sobre a Holo 1.0, a primeira versão da linguagem e da plataforma Holo, que é fortemente embasada na plataforma Java. São desenvolvidos três componentes para implementar a extensão: a HoloJava 1.0 estendida para depuração, a HDI (Holo Debug Interface), uma implementação dos serviços básicos de depuração, e o Holo Debugger, uma ferramenta de depuração para Holo. Palavras-chave: Java, Holo, depurador, multiparadigma, arquitetura de depuradores. 7 Abstract The Hololanguage (briefly, Holo) is a programming language originated from a new standpoint for software development, the Holoparadigm. This work provides an initial treatment for the problem of supporting the debug process of Holo programs. This treatment is achieved through the construction of a general overview of the problem, that includes, for example, the multiparadigm aspect of the language. From this vision, an extension to the Holoplatform is proposed, the Holo Extension for Debugging (HED). This extension is materialized over Holo 1.0, the first version of the Holo language and platform, which are strongly tied to the Java platform. Three software components are developed to implement the extension: the HoloJava 1.0 extended for debugging, the HDI (Holo Debug Interface), an implementation of the basic debugging services, and the Holo Debugger, a debugger tool for Holo. Keywords: Java, Holo, debugger, multiparadigm, debugger architecture. 8 1. Introdução 1.1 Tema Este trabalho trata do problema da depuração de programas escritos para a Hololinguagem (abreviadamente, Holo) [BAR 01], uma linguagem concorrente e multiparadigma. Neste contexto, o trabalho identifica algumas características de Holo que introduzem problemas sobre a depuração de programas, e propõe uma especificação para a arquitetura do depurador da Holoplataforma, a plataforma de desenvolvimento e execução de Holoprogramas. Esta especificação é implementada sobre Holo 1.0, uma versão da linguagem e da plataforma Holo que tem a linguagem e plataforma Java como base para a implementação. 1.2 Contexto Depuradores de programas são ferramentas críticas para o desenvolvimento de software, e são muito pouco estudados, se comparados, por exemplo, aos compiladores [ROS 96, p.1]. Recentemente popularizou-se o emprego de múltiplas threads em programas para a exploração da concorrência. Porém, o desenvolvimento de depuradores para programas multithreaded enfrenta desafios [ROS 96, p. 173], como a eficácia do auxílio oferecido ao programador para o diagnóstico de problemas oriundos de multithreading. Linguagens de programação multiparadigma têm sido continuamente pesquisadas nos últimos anos. O objetivo da criação de modelos integradores de paradigmas é a superação das limitações de cada paradigma e a exploração conjunta de suas características consideradas benéficas [BAR 01]. Torna-se necessário a adaptação das ferramentas de linguagens de programação, como os depuradores, para as características destas linguagens. O Holoparadigma [BAR 01a] é um novo paradigma de software , que busca, através de um esforço integrador abrangente, unificar os benefícios de vários campos de pesquisa em Ciência da Computação. Estes campos são: a exploração, implícita e explícita, da concorrência, distribuição e paralelismo, a programação multiparadigma (paradigmas de orientação a objetos, imperativo e lógico), as técnicas de Engenharia e Arquitetura de Software, entre outros. O Holoparadigma surgiu de pesquisas realizadas no âmbito dos projetos Opera [OPE 02] e Apello [APE 02]. Holo é uma linguagem de programação criada de acordo com os princípios estabelecidos pelo Holoparadigma. Em Holo, realidade é modelada apenas por entes e símbolos. Holo possui uma semântica distribuída, pois, ao expressar a realidade em entes independentes que se comunicam através de troca de símbolos, a exploração do paralelismo expresso naturalmente pelas construções da linguagem pode ser feita de forma automática pela máquina [BAR 02]. A Holoplataforma (a plataforma de execução de programas Holo) atualmente possui um protótipo implementado sobre a linguagem Java [JAV 02]. Este protótipo denomina-se Holoplataforma 1.0, e é baseado na ferramenta HoloJava [BAR 01b], um tradutor de Holo 9 para Java, e em um ambiente integrado de desenvolvimento, o HoloEnv [BAR 99, SOA 00]. Esta versão da plataforma permite o desenvolvimento e execução de programas desenvolvidos para Holo 1.0, o subconjunto da linguagem Holo que se encontra atualmente implementado. 1.3 Motivação A Holoplataforma não possuía suporte para a depuração de programas Holo. Este trabalho pretende preencher esta lacuna de forma satisfatória, através de um modelo que prioriza a flexibilidade, de forma que mudanças na implementação da plataforma ou na linguagem causem um impacto mínimo na solução. A minimização destes impactos reduz a quantidade de módulos que precisam de adaptações ou re-implementações, reduzindo custos. Os módulos da solução fornecem a fundação para a construção de interfaces homemmáquina em ferramentas de depuração Holo. Também fornecem um modelo de depuração que pode ser aproveitado, ou que ao menos serve como parâmetro para novos estudos sobre depuração, caso a Holoplataforma seja implementada na íntegra, sem a utilização da plataforma Java como fundação. A concepção de um suporte para a depuração de programas Holo envolve várias questões de interesse, como a depuração de uma linguagem concorrente, distribuída e multiparadigma. Os aspectos multiparadigma e distribuído não são o foco deste trabalho, mas foi realizado um estudo inicial sobre estes temas, que pode servir como um ponto de partida para discussão futura. 1.4 Objetivos Os objetivos deste trabalho são: • Identificar as necessidades de Holo em relação à depuração de programas; • Fornecer um modelo geral para a arquitetura do depurador da Holoplataforma; • Implementar serviços de depuração para Holo 1.0, o subconjunto de Holo atualmente implementado, através do desenvolvimento de um módulo depurador; • Implementar uma ferramenta de depuração (interface) simples para demonstrar o funcionamento do módulo; • Orientar o projeto do módulo depurador de forma a permitir a posterior integração de uma interface gráfica de depuração ao ambiente HoloEnv; • Documentar a API do módulo depurador para servir de referência para a implementação desta interface gráfica. 1.5 Organização do Texto O capítulo 2 introduz o Holoparadigma e a Hololinguagem, seguindo com uma discussão sobre a Holoplataforma, a plataforma de desenvolvimento e execução de Holo. A seguir, é apresentada a Holoplataforma 1.0, a versão atual da plataforma e desenvolvimento 10 de programas Holo. Após, é descrita a linguagem Holo 1.0, o subconjunto de Holo que é reconhecido atualmente pela Holoplataforma 1.0. Juntamente com esta descrição, é apresentado um exemplo de programa Holo 1.0. Por fim, realiza-se uma revisão sobre o conceito de depuração e uma discussão sobre as necessidades de Holo em relação à depuração de programas, que resulta na especificação dos serviços básicos a serem oferecidos pelo depurador. O capítulo 3 apresenta inicialmente o modelo geral proposto para a arquitetura do depurador da Holoplataforma, o HED (Holo Extensão para Depuração). A seguir, o modelo da HED é adaptado para Holo 1.0, e mostram-se os componentes do modelo que foram supridos pela plataforma Java, e quais foram implementação própria deste trabalho. A seguir, descreve-se o modelo e os algoritmos utilizados no projeto e implementação dos três módulos desenvolvidos neste trabalho: a ferramenta HoloJava estendida, a Holo Debug Interface, e o Holo Debugger. O capítulo 4 trata de questões relativas à implementação dos módulos. É fornecida uma visão geral do ambiente de desenvolvimento, bibliotecas utilizadas e indicadores de quantidade de código produzido para cada um dos módulos. Também é fornecida uma análise das limitações e possibilidades de expansão de cada um deles. Por fim, o capítulo apresenta considerações sobre o desempenho dos programas desenvolvidos. O capítulo 5 faz um resumo do trabalho, destaca resultados e conclusões, e aponta várias perspectivas de trabalhos futuros. 11 2. Holo Este capítulo primeiramente apresenta o Holoparadigma, suas motivações e conceitos básicos. A seguir, é detalhada a Hololinguagem, enumerando as suas construções, destacando suas características básicas e dissecando a definição de Holoprograma. Após, é definido um modelo da Holoplataforma, a plataforma de desenvolvimento e execução de Holo. Por fim, realiza-se uma discussão sobre questões relacionadas à depuração de programas Holo. 2.1 Holoparadigma O ser humano contemporâneo possui uma visão de mundo excessivamente fragmentária, originada do pensamento de Descartes [CAP 96]. Uma conseqüência desagradável desta maneira de enxergar ao mundo é a idéia, formada pelas pessoas, de que elas são seres sem relação direta com os outros seres e do universo em que se situam. Isto as leva a uma incompreensão sobre a própria existência e a um comportamento individualista, que afeta a sociedade como um todo. O pensamento cartesiano não é algo inerentemente negativo. Grande parte do sucesso atual da ciência provém do paradigma de fragmentação e especialização. Porém, ao fragmentarmos a realidade, perdemos a visão do todo e das relações que envolvem a parte estudada de outras partes da realidade [BAR 02, p.37]. Ortega y Gasset [ORT 92, p.72] defende que o movimento que leva a investigação científica a fragmentar-se indefinidamente em problemas particulares exige uma regulagem compensatória. Essa regulagem deve ser realizada através de um movimento no sentido contrário, de síntese do conhecimento existente. O Holoparadigma é uma nova proposta para o desenvolvimento de software, que busca integrar os esforços de vários outros campos de pesquisa em Ciência da Computação. A figura 1 ilustra a gênese do Holoparadigma. Temas Básicos Temas Intermediários Tema Final Paralelismo Implícito Sistemas Distribuídos Multiparadigma Distribuição Implícita Arquitetura de Software Novos Paradigmas Holoparadigma Figura 1 – Gênese do Holoparadigma 12 Resumidamente, os temas que são englobados pelo estudo do Holoparadigma são: • Paralelismo implícito: proposta de que as fontes de paralelismo estejam presentes nas construções da linguagem de programação, e sejam exploradas automaticamente pela plataforma de execução; • Sistemas distribuídos: estudo relacionado a software que executa em vários nodos processadores conectados por redes; • Multiparadigma: criação de linguagens de programação cujo paradigma é uma integração de outros paradigmas ditos “básicos”, como o imperativo, o lógico, o funcional e o orientado a objetos; • Arquitetura de Software [IEE 95, SHA 96]: tema que sintetiza as várias abordagens da Engenharia de Software, se dedicando à descrição de componentes, as interações entre eles e os padrões que guiam sua composição; • Distribuição implícita: exploração automática, pelo ambiente de execução, da capacidade de espalhar componentes dinâmicos de um programa em vários nodos processadores de uma rede; • Novos paradigmas: a criação de um novo paradigma de modelagem e programação de software. Um dos objetivos do Holoparadigma é a busca por um melhor desempenho e aproveitamento dos recursos computacionais através da exploração automática do paralelismo e da distribuição presentes nos programas. A exploração automática também desobriga o programador a planejar o algoritmo exato de utilização dos recursos computacionais, permitindo que este se concentre em outras tarefas, como a especificação e depuração do software. 2.2 Hololinguagem A Hololinguagem (abreviadamente, Holo) é uma linguagem de programação derivada do Holoparadigma. Holo é uma linguagem multiparadigma, que integra os paradigmas básicos de programação imperativo, lógico e orientado a objetos. Estudos recentes buscam a integração do paradigma funcional [DUB 01] à linguagem. De acordo com [BAR 01], o paradigma imperativo, bastante flexível, é fraco na expressão automática de fontes de paralelismo presentes no sistema, que são modeladas por naturalidade pelos paradigmas lógico e orientado a objetos. A integração de paradigmas permite a integração das suas fontes de paralelismo e aumenta a versatilidade da linguagem. A Hololinguagem, de acordo com as premissas do Holoparadigma, visa explorar estas fontes de paralelismo e distribuição de forma implícita. Isto é conseguido à medida que as construções básicas da linguagem, utilizadas pelos programadores para construir os programas, exprimem as fontes de paralelismo de forma bastante sublime. Algumas das construções básicas de Holo são: 13 Ente. O ente é a principal abstração de Holo. Toda existência é um ente, desde a mais simples até a mais complexa. Na linguagem Holo, um ente pode ser estático ou dinâmico. Entes estáticos funcionam como "moldes" para a instanciação (em Holo, clonagem) de entes dinâmicos. Em C++ e Java, ente estático é análogo a classe, e ente dinâmico é análogo a objeto, ou seja, a instância de uma classe. Símbolo. De acordo com [BAR 02, p.41], o símbolo é o átomo de informação no Holoparadigma. Holo propõe a utilização do processamento simbólico como principal instrumento para o tratamento de informações, característica herdada do paradigma em lógica. Neste sentido, a variável lógica e a unificação são consideradas a base do tratamento de símbolos. Assim como variáveis lógicas, variáveis em Holo armazenam símbolos, e não possuem tipo. História. Um ente é uma existência temporal, possuindo passado, presente e futuro, sendo que a história de um ente representa parte do seu passado. Na história, são registradas constatações do presente que são de interesse do ente, na forma de símbolos. A história funciona de maneira bastante semelhante a um blackboard lógico [GAR 95, VRA 95, PFL 97]. Comportamento, ação e interface. Um ente possui um comportamento, isto é, uma descrição do processamento que ele realiza. O comportamento é composto de ações. Ações são análogas a métodos de objetos de linguagens como C++ e Java. Holo estabelece dois tipos básicos de ações: a ação imperativa (IA), oriunda do paradigma imperativo [GHE 98, p.259; SEB 99, p.21], e a ação lógica (LA), oriunda do paradigma em lógica [KOW 79, ROB 92]. Ações multiparadigma (MA) são ações que integram os comportamentos em lógica e imperativo. Por fim, um ente possui uma interface, que descreve suas possíveis relações de comportamento com os demais entes. Nível 0 Ente I n t e r f a c e Comportamento História I n t e r f a c e Comportamento Ente Nível 2 Nível 2 História Ente 1 Ente 2 ... História Ente n Ente História Nível 1 Ente Ente Ente História Ente (a) Ente elementar (b) Ente Composto (c) Exemplo de composição (3 níveis) Figura 2 – Composição de entes Composição de entes e HoloTree. O ente até aqui descrito é classificado como ente elementar. Ele possui uma interface, uma história e um comportamento. É possível a 14 composição de entes em Holo. Um ente composto é um ente elementar que possui outros entes na sua composição. A composição de entes dinâmicos, em tempo de execução do Holoprograma, forma uma estrutura de árvore denominada HoloTree. Os nodos terminais da árvore representam entes elementares, e os outros são entes compostos. Um ente composto assemelha-se a um grupo [LEA 01], podendo ser enxergado como um ambiente compartilhado pelos seus entes componentes. Os entes componentes possuem acesso a história do ente pai, ou seja, a história do seu contexto, como pode ser observado na figura 2c. Esta história atua como um meio de sincronização entre os entes componentes. A HoloTree é uma estrutura dinâmica. Entes podem trocar de contexto, ultrapassando fronteiras entre entes. Esta troca é denominada mobilidade lógica. Um programa Holo é, basicamente, um texto composto por uma seqüência de descrições de entes estáticos (figura 3). O corpo de um ente estático é formado por duas partes: comportamento e história. O comportamento é constituído por um conjunto de ações que implementam a funcionalidade do programa. Estas ações estão disponíveis no interior de um ente e, através da interface, podem ser exportadas para acesso do exterior de um ente. A história é descrita por uma seqüência de Cláusulas Lógicas com Extensões não Imperativas (CLEIs). A história de um ente estático é o valor inicial da história dos entes dinâmicos que serão clonados a partir dele. <nome> (<argumentos>) cloning (<descrição>) interface <ações exportadas>. { I n t e r f a c e Cabeça Comportamento Ações history { Corpo CLEIs História } } Figura 3 – Forma geral de representação de um ente estático na Hololinguagem Se um programa Holo é basicamente composto de uma lista de entes, um ente é basicamente composto de uma lista de ações. No contexto deste trabalho, bastará a informação de que uma ação imperativa é formada por uma seqüência de comandos, e uma ação lógica é uma declaração (seqüência de cláusulas) Prolog. As construções sintáticas de Holo são apresentadas em maior detalhe por [BAR 02]. 2.3 Holoplataforma 1.0 Toda linguagem de programação precisa de uma plataforma, ou seja, um ferramental, de desenvolvimento e execução de programas. A plataforma de Holo é a "Holoplataforma" [BAR 02], e o termo refere-se à plataforma completa de desenvolvimento e execução de programas Holo. Nesta seção, será definido um modelo que contempla apenas o subconjunto da Holoplataforma que é relevante para a discussão deste trabalho. Especificamente, este 15 subconjunto compreende os módulos que já estão em funcionamento no protótipo Java da plataforma, e que são vitais para o ciclo de depuração de programas. Para abreviar o texto, este subconjunto será rotulado simplesmente de Holoplataforma. Atualmente, apenas um subconjunto da linguagem Holo encontra-se implementado. Este subconjunto denomina-se "Holo 1.0". Para demonstrar Holo 1.0, foi implementada a "Holoplataforma 1.0", a primeira versão da plataforma. Esta versão é baseada na plataforma Java [JAV 02]. Nesta versão, para se executar um programa Holo, este é primeiro traduzido para Java, através da ferramenta HoloJava [BAR 01b]. A seguir, o programa traduzido é compilado pelo compilador Java (JavaC), e depois executado por uma Máquina Virtual Java (JVM). Caso o programa Holo possua trechos em lógica, o código Prolog correspondente será gerado pela ferramenta HoloJava, que será depois traduzido para Java através da ferramenta PrologCafe [PRO 02]. A versão atual suporta a execução concorrente de vários entes em um mesmo nodo processador. A concorrência entre entes foi implementada por Java Threads [OAK 99]. Para a comunicação entre os entes (ou seja, sincronização entre as threads que executam o comportamento dos entes) utiliza-se a biblioteca Jada [CIA 02], que provê um mecanismo de blackboards para a implementação da história dos entes. A figura 4 ilustra a implementação da Holoplataforma, inserida na sua realidade de uso. Holocompilador HoloEnv Holoprograma HoloJava Fonte Prolog (Lógica) Programador Fonte Java (Imperativo/OO) Interage PrologCafe JavaC Fonte Java (compilador Java) (Lógica) Holocódigo Interage Bytecode Java do Programa Holo Usuário Final Projeta Execução (Interface com o usuário) Bibliotecas (Java2, Jada, PrologCafe) Máquina Virtual Java (JVM) Holomáquina Figura 4 – Holoplataforma 1.0 A distinção entre a execução projetada do programa e a máquina executando o código do programa foi traçada porque o programador pode interagir mais diretamente com a máquina, além de interagir com a interface do programa. Esta interação direta com a máquina é feita através do módulo depurador, que será apresentado no capítulo 3. 16 Na figura está inserido o HoloEnv, que é um ambiente integrado de desenvolvimento (IDE). Ele esconde os passos intermediários de tradução, compilação e execução de programas, além de prover ferramentas de edição e outras facilidades ao programador Holo. Futuramente, o HoloEnv integrará também uma interface visual para a depuração de programas, baseada no módulo depurador desenvolvido neste trabalho. No âmbito das pesquisas do Holoparadigma, a Holoplataforma será futuramente desligada da plataforma Java, com a implementação de um compilador e de uma máquina virtual, ambos próprios para Holo. A Máquina Virtual Holo executará um código virtual multiparadigma, e terá uma versão capaz de executar um programa Holo de forma distribuída. Como as possibilidades de distribuição de um programa Holo estão implícitas no mesmo, estas serão exploradas automaticamente pelo ambiente. Destaca-se aqui esta re-implementação futura para salientar que o suporte para depuração de programas também precisará ser re-implementado, porém o modelo de depuração abstrato e a ferramenta de depuração devem, idealmente, permanecerem inalterados. Um dos objetivos deste trabalho é o de separar o estudo da interface homemmáquina do depurador Holo, do projeto das APIs depurador-máquina e das implementações destas APIs. 2.4 Holo 1.0 2.4.1 Características Como mencionado na seção anterior, apenas um subconjunto da linguagem Holo, denominado "Holo 1.0", encontra-se implementado atualmente. O depurador implementado neste trabalho atua sobre esta versão da linguagem. As principais características presentes em Holo 1.0 são: • Entes estáticos possuem o nome como especificação de interface; • Entes estáticos podem declarar Ações Modulares Lógicas (MLAs) e Ações Imperativas (IAs); • Entes estáticos podem declarar o estado inicial da sua história; • Ações possuem nome e aridade na especificação da interface; • Comando "clone", para clonagem de transição (instanciação de entes); • Comando "move", para mobilidade lógica de entes; • Perguntas e afirmações, construtivas e destrutivas, para a história do ente; • Acesso à história do contexto do ente (história do pai) através do comando out(history) • Invocação de uma ação sobre o próprio ente (self), ou sobre uma referência a outro ente. Este subconjunto da linguagem já permite o desenvolvimento de aplicações de teste. Um dos problemas clássicos que já é programável em Holo é o do "Jantar dos Filósofos", que 17 exercita a concorrência da linguagem. A seção seguinte mostra uma implementação, em Holo 1.0, destes problema, seguida de uma explicação do seu funcionamento. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 holo() { holo() { writeln('CINCO FILOSOFOS VAO JANTAR.'), writeln('CADA FILOSOFO COME CINCO VEZES.'), writeln('O JANTAR ESTA INICIANDO.....'), for X := 1 to 5 do { clone(filosofo(X),null) } for X := 1 to 5 do { history#end } writeln('O JANTAR ACABOU.') } history { chopstick(1). chopstick(2). chopstick(3). chopstick(4). chopstick(5). ticket. ticket. ticket. ticket. ticket. seat(1,2). seat(2,3). seat(3,4). seat(4,5). seat(5,1). } } filosofo() { filosofo(Ident) { writeln('Fisolofo esta jantando: ',Ident), for Cont := 1 to 5 do { out(history)#ticket, out(history)#seat(#F1,#F2), out(history)#chopstick(#F1), out(history)#chopstick(#F2), for Atraso := 1 to 1000 do {} out(history)!chopstick(F2), out(history)!chopstick(F1), out(history)!seat(F1,F2), out(history)!ticket, writeln('Comendo ',Ident,' - ',Cont) } out(history)!end } } Figura 5 – Problema do "Jantar dos Filósofos", modelado em Holo 1.0 18 2.4.2 Exemplo: Jantar dos Filósofos Este problema é utilizado com freqüência como exemplo canônico de concorrência. O problema especifica um jantar onde há cinco filósofos e apenas cinco talheres. Cada filósofo precisa apoderar-se de dois talheres adjacentes ao seu assento para comer. Após utilizá-los, ele retorna os talheres para que outros filósofos possam comer. O detalhe é o de que os filósofos param freqüentemente de comer para pensar, liberando os talheres, ou seja, o jantar de um filósofo consiste de várias seqüências de obtenção e liberação dos talheres. Problemas como deadlocks e starvation podem ocorrer. A figura 5 lista um programa em Holo 1.0 que implementa o problema do "Jantar dos Filósofos". Este programa possui dois entes estáticos declarados: holo e filósofo. Em tempo de execução, o ente holo (estático) é automaticamente clonado (instanciado) como o ente d_holo (dinâmico), que é a raiz da HoloTree. A ação d_holo.holo/0 (ação holo de aridade zero) é invocada pela thread principal, sendo este o ponto de entrada do programa. O laço das linhas 8 a 11 realiza a clonagem de cinco entes dinâmicos do tipo filósofo, e especifica que a ação construtiva destes entes é a de aridade um, pois passa um argumento para o nome do ente. A ação construtiva deve, obrigatoriamente, possuir o mesmo nome do ente. O parâmetro passado para cada filósofo criado é um número (de 1 a 5) que o identifica na mesa de jantar. Os entes dinâmicos do tipo "filósofo" serão criados no contexto do ente d_holo, sendo portanto instanciados como seus descendentes diretos na HoloTree. O laço das linhas 12 a 15 realiza a retirada de cinco tokens (tuplas de apenas um elemento) de nome "end" da história do ente d_holo. Estes tokens são inseridos na história do ente d_holo pelos entes filósofos à medida em que estes terminam o seu jantar. O operador "#" caracteriza uma pergunta destrutiva, isto é, o token é consumido após sua leitura, e bloqueante, isto é, a thread que executa a pergunta permanecerá bloqueada até que a história do ente d_holo possua um ou mais tokens de nome "end". As linhas 19 a 28 especificam o conteúdo inicial da história do ente d_holo. É interessante ressaltar a forma de especificação de tuplas: o primeiro elemento é seguido dos demais elementos especificados dentro de parênteses e separados por vírgulas. O laço das linhas 36 a 49 constitui a atividade de comer, realizada por um filósofo. Observe-se que as ações que implementam o comportamento do filósofo, através da construção out(history), atuam sobre a história externa do ente filósofo, ou seja, a história do ente d_holo. A janta do filósofo, no exemplo é composta de cinco repetições sobre um conjunto de ações. A tabela 1 detalha esta s ações. Na implementação do filósofo, é introduzida mais uma construção presente em Holo, a afirmação sobre a história, através do operador "!". Por fim, na linha 50, o filósofo avisa que já encerrou sua atividade, com a inclusão do token "end" na história de d_holo. O programa termina quanto todos os filósofos incluem este token na história, e o ente d_holo, a seguir, retira-os da história. 19 Linha(s) Atividade 38 Retira um ticket (avisa que está com fome) 39 Retira um assento (seat) qualquer, recebendo a informação dos talheres adjacentes à esta cadeira específica (F1 e F2) 40 - 41 Aguarda e retira os dois talheres (chopsticks) adjacentes ao seu assento. 42 – 43 Realiza um delay (filósofo comendo) 44 – 45 Devolve os talheres à história 46 Devolve o assento à história 47 Devolve o ticket (não está mais com fome) Tabela 1 – Ações que compõe a janta do filósofo 2.5 Depuração Holo Esta seção introduz a questão da depuração de programas Holo. Primeiramente discute-se o conceito de "depuração", seguido de uma análise de certos aspectos da Hololinguagem e seu impacto no problema da depuração de programas. Por fim, enumeramse os serviços básicos de depuração Holo 1.0, que foram identificados e são oferecidos pelo módulo depurador projetado e implementado por este trabalho. 2.5.1 Conceito de Depuração Os termos "depuração", "depurador" e similares são adaptações de uma outra família de termos originais em inglês ("debugging", "debugger"...), cuja raiz é o termo "bug", que significa o mesmo que "defeito" em um programa. Uma tradução mais literal do termo "debugger" resultaria em algo similar a "eliminador de defeitos", o que é um termo muito menos abrangente do que "depurador". Em [ROS 96, p. 1], o autor reconhece que o termo "debugger", no sentido de eliminador de defeitos, não é o termo mais apropriado, e prossegue para classificar os depuradores de programas como ferramentas que servem para iluminar a natureza dinâmica dos programas, servindo para permitir um melhor entendimento dos mesmos, além de servirem para localizarem e consertarem erros de programação. O termo em português é mais genérico, e pode ser considerado mais apropriado. "Depurar" significa essencialmente "aprimorar". Um depurador de programas é utilizado, em grande parte, em um processo iterativo de aprimoramento do programa. O programador coloca o seu programa sob a "lente" do depurador, retira conclusões e realiza modificações sobre o mesmo, e repete o processo. O objetivo é, neste caso, melhorar o programa em direção a um estado ideal que, entre suas características, encontra-se a ausência de defeitos. Defeitos são causa de falhas, que por sua vez compreendem comportamento errático exibido pelo programa, quando este é executado [FRA 97]. 20 Porém, um depurador não serve apenas para depurar um programa. O depurador de programas é, essencialmente, uma ferramenta de análise do comportamento de um programa, podendo servir até como uma ferramenta didática. E, como fator adicional, se por um lado os termos "depurador" e "debugger" (eliminador de defeitos) não abrangem todo o significado necessário, por outro eles possuem talvez significação excessiva, se considerarmos as ferramentas que hoje são denominadas de "depuradores". Por exemplo, uma ferramenta de análise de performance (profiler) também pode ser considerada um depurador, pois auxilia o programador a aprimorar o seu programa. Pode até ser considerado um "eliminador de defeitos", pois uma baixa performance apresentada por um programa também pode ser considerada uma falha do mesmo. Porém, serviços de profiling não são diretamente associados a depuradores de programas, atualmente. Neste trabalho, o termo "depuração" terá um significado bastante ligado com as atuais ferramentas comerciais que são denominadas de "depuradores de programas" (debuggers). Como exemplo destas ferramentas, podemos citar o depurador integrado aos ambientes JBuilder [BOR 02] e Visual C++ [MIC 02], e o depurador gdb [GDB 02]. Atualmente, observa-se uma tendência à integração de cada vez mais serviços aos depuradores, como profilers, heap checkers (verificadores de consistência da memória), e outros. De qualquer maneira, um certo conjunto de serviços básicos permanece inalterado. O foco deste trabalho é a criação de um depurador Holo que oferece estes serviços básicos para a plataforma Holo 1.0. Estes serviços, como execução passo-a-passo, inserção de breakpoints, e serviços gerais de consulta ao estado do programa, traduzem-se, sem grandes alterações, do contexto de linguagens como Java e C++ para o contexto de Holo. Estes serviços estão relacionados na seção 2.5.5. 2.5.2 Depuração concorrente Um programa concorrente é um programa composto por fluxos de execução independentes que competem continuamente por acesso a recursos compartilhados, como estruturas de dados e fatias de tempo de processamento. Atualmente, o mecanismo de concorrência mais difundido na comunidade de desenvolvimento de software é, certamente, o mecanismo de "threads", tanto que os conceitos são freqüentemente utilizados como se fossem sinônimos. Uma thread geralmente é implementada, pelos sistemas operacionais, como um "processo leve", cujo chaveamento de contexto é bem menos custoso do que o chaveamento de um processo convencional. A concepção de ferramentas de depuração de programas concorrentes, ou "multithreaded", é muito mais complexa do que a implementação de depuradores de programas com apenas um fluxo de execução. Um programa multithreaded é muito mais difícil de ser analisado por um programador, e é responsabilidade da ferramenta de depuração amenizar este impacto, com a criação de mecanismos especiais para controle de programas multithreaded. A implementação destas ferramentas também é problemática, pois o suporte dado pelos sistemas operacionais para a depuração multithreaded é geralmente inadequado [ROS 96, p.179]. A plataforma Java pode ser considerada uma exceção à regra, possuindo 21 uma arquitetura de depuração bastante robusta, concebida para a depuração de uma linguagem fundamentada em multithreading [ROS 96, p.77]. Holo é uma linguagem que gera programas concorrentes. Porém, diferentemente de Java, algumas das fontes de concorrência da linguagem são declaradas implicitamente nas suas construções. Por exemplo, a descrição de um programa através da separação em "entes" permite a exploração automática, pela máquina, da concorrência inter-entes [BAR 02]. Em Holo, o programador não manipula diretamente "threads", sendo que estas compreendem apenas um mecanismo para a implementação da linguagem. Mas se o mecanismo de threads é abstraído do programador pelas construções gramaticais da linguagem, um depurador Holo não pode se dispor tão facilmente desta realidade. O depurador precisa conciliar duas tarefas bastante divergentes: a tarefa de oferecer todo o controle e visão da máquina, necessários para que o programa seja depurado, e a de esconder a implementação da máquina atrás da abstração da linguagem. Como é apontado em [ROS 96], muitos depuradores de linguagens de alto nível, quando estas são compiladas para código nativo de um processador, precisam exibir uma janela de inspeção do programa em nível de instruções, e precisam exibir o conteúdo dos registradores do processador. Ou seja, às vezes é necessário que a ilusão seja conscientemente quebrada e exposta para um usuário, a fim de não comprometer a cobertura da ferramenta. Neste trabalho, optou-se por exibir, nas interfaces do depurador, a realidade da implementação da concorrência de Holo em threads. O programador Holo terá controle sobre a execução das threads do seu programa. Para manter um elo com a abstração Holo, todas as threads são associadas aos seus respectivos entes, sendo que no protótipo atual da Hololinguagem, a concorrência intra-entes ainda não foi implementada, e a concorrência inter-entes é implementada com a criação de uma thread para executar o comportamento de cada ente criado, sendo o ponto de entrada de execução a ação construtiva do ente. Esta abordagem da depuração concorrente em Holo visa aproveitar todo o suporte e toda a tradição da literatura, atualmente presentes para a depuração de programas multithreaded. Para eliminar a exibição das threads pelo depurador, torna-se necessária a criação de uma abstração em Holo para as mesmas, pois o controle das threads pelo programador é essencial para a depuração de programas multithreaded. Esta questão está em aberto. 2.5.3 Depuração multiparadigma Em Holo é possível se identificar estruturas lógicas e estruturas imperativas. A principio, a depuração em Holo poderia ser tratada como uma concatenação de dois problemas totalmente distintos: a depuração de uma linguagem imperativa e a depuração de módulos de linguagem lógica declarativa. Esta abordagem de concatenação seria provavelmente aceitável comercialmente, porém, uma solução mais interessante, que segue a filosofia da linguagem, consistiria na criação de um modelo de depuração com uma 22 semântica que integre os paradigmas básicos de Holo. Algumas possibilidades são apresentadas a seguir. Em Holo, módulos ou trechos declarativos de programa podem ser considerados como uma extensão controlada pela parte imperativa do programa. Isto é determinado pelo Grafo de Invocação de Ações (AIG) de Holo, como mostra a figura 6. Na figura, ações imperativas e multiparadigma (MA, IA, MIA) podem invocar qualquer tipo de ação, mas ações lógicas (LA, MLA) só podem invocar outras ações lógicas. É interessante notar que a maioria das implementações comerciais de linguagens declarativas como Prolog vem com extensões imperativas com o objetivo de implementar interfaces com o “mundo exterior”. Isto pode ser observado nos depuradores dos ambientes Trinc-Prolog [TRI 02], SICStus Prolog [SIC 02] e Amzi Prolog [AMZ 02]. MA IA MIA LA MLA Região Imperativa Região Lógi ógica Figura 6 – Grafo de Invocação de Ações (AIG) de Holo A depuração de programas Prolog é feita principalmente através da utilização de traços de execução [TOB 93] e predicados especiais, que atuam sobre um interpretador Prolog. O controle do fluxo de execução por um depurador Prolog tipicamente atua sobre o modelo da "caixa de procedimento", introduzido por Byrd [BYR 80]. É aparente que o suporte para depuração de programas declarativos tem avançado muito pouco, quando contrastado com a sofisticação atual dos depuradores de linguagens imperativas, como o depurador Java integrado ao ambiente de desenvolvimento "JBuilder Personal" [BOR 02], que é disponibilizado gratuitamente para uso não-comercial. Até aqui, duas constatações foram feitas: a de que o suporte para depuração declarativa é bastante rudimentar se comparado às facilidades presentes nos depuradores comerciais de linguagens imperativas, e a de que, na prática, o paradigma declarativo se beneficia de uma shell de controle imperativo. Destas constatações, surge a idéia de se transportar alguns conceitos da depuração imperativa para a depuração declarativa, visando uma maior integração dos paradigmas de depuração. 23 Por exemplo: o recurso de passo-a-passo, requerimento básico e obrigatório de um depurador comercial de uma linguagem imperativa, poderia ser estendido para que, quando o usuário da ferramenta de depuração realizar um “passo” que o leva de uma região imperativa para uma região declarativa, ele assuma uma semântica diferente. Uma semântica possível, seria a de acompanhar o “raciocínio” realizado pela máquina sobre a declaração do programa lógico. Desta forma, após um “passo”, seria destacado, no texto do programa, a cláusula que está sendo atualmente testada. O “passo -a-passo” em lógica, ao contrário do “passo -a-passo” imperativo, poderia andar “tanto para frente como para trás”, mostrando para o usuário do depurador o mecanismo de backtracking em funcionamento. Este recurso consta em depuradores Prolog como o depurador integrado ao ambiente Trinc-Prolog [TRI 02]. Esta idéia sobre o passo-a-passo é apenas uma das muitas formas possíveis de integrar os paradigmas para um módulo depurador de Holo. A concepção de uma solução otimizada para a integração dos paradigmas de depuração de linguagens lógicas e imperativas transcende os objetivos deste trabalho. Em nível de implementação, o tratamento de Prolog no contexto deste trabalho está restrito à capacidade do depurador em decodificar uma pilha de ativação de uma thread que envolva tanto registros de ativação de MLAs quanto de IAs. Ou seja, o usuário do depurador pode visualizar uma pilha de ativação multiparadigma. Para completar o quadro da depuração multiparadigma em Holo, o paradigma básico da orientação a objetos, integrado por Holo, é tratado na depuração principalmente em nível de exibição das estruturas de dados para o programador. Um programa imperativo puro não possui, nos seus dados, hierarquia que possa ser extraída pelo depurador sem “dicas” fornecidas pelo programador. Um programa muito extenso, com centenas ou milhares de instâncias e símbolos presentes durante a sua execução, é mais facilmente analisado se hierarquias e redes destes símbolos podem ser automaticamente reconstruídas e exibidas pelo depurador. No caso de Holo, por exemplo, a árvore de entes (HoloTree) poderia ser exibida em um controle gráfico do tipo “árvore com nodos expansíveis”, como mostra a figura 7. Isto é muito vantajoso, pois o colapso e a expansão da estrutura de nodos, extraída diretamente das construções da linguagem, permite ao usuário lidar com a enorme quantidade de informação apresentada na interface com mais facilidade. 24 holo ente1 ente2 ente4 ente3 ente5 Figura 7 – Exemplo de controle gráfico (à direita) para a exibição da HoloTree, a estrutura de árvore em que os entes em Holo são organizados (à esquerda). 2.5.4 Depuração distribuída Como discutido anteriormente, um programa Holo possui fontes implícitas e explícitas de paralelismo em seu programa. O objetivo de Holo é que a maneira com que este paralelismo é explorado fique a critério do ambiente distribuído de execução. Este mecanismo, baseado em algum critério de escalonamento, move entes de um nodo para outro, de forma a maximizar o desempenho do sistema. Desta forma, parte do trabalho de se escrever uma aplicação composta de fluxos de execução independentes é absorvida pelas próprias construções da linguagem, que são utilizadas normalmente pelo programador no esforço de descrever o funcionamento correto do programa. A execução de um programa Holo em uma arquitetura distribuída resultaria em conjuntos de entes executando em nodos processadores conectados por uma rede. Entes locais a um nodo processador concorreriam, por exemplo, por tempo de processamento. O protótipo atual de Holo roda apenas sobre um único nodo processador, implementando a concorrência entre entes que compartilham o único nodo. Estudos sobre depuração de programas distribuídos foram feitos por [WAH 92, MEI 96, NER 97]. A depuração distribuída será um problema a ser tratado em Holo, porém, transcende o escopo deste trabalho. 2.5.5 Serviços básicos de depuração para Holo 1.0 A tabela 2 relaciona os serviços básicos identificados como necessários para a implementação de um depurador para Holo 1.0, juntamente com uma descrição dos mesmos. 25 Serviço Descrição Execução passo-a-passo Permite a uma thread executar uma única linha de comando do tipo step into da linguagem Holo, inclusive decompondo chamadas de ações em seus respectivos passos. Execução passo-a-passo Idem a "step into", porém trata chamadas de ações como do tipo step over um passo único. Execução passo-a-passo Faz com que a thread continue a execução da sua ação atual do tipo step out até que esta retorne à ação que a invocou. Criação e remoção de Inserção e remoção de pontos de parada em linhas do breakpoints programa. Estes pontos causam a suspensão do programa no momento em que um fluxo de execução (thread) vai executar a linha em questão. Deve existir a possibilidade do breakpoint suspender apenas a thread que o ativou, e não apenas a suspensão de todo o programa. Inspeção da história de Realiza a consulta ao conteúdo da história de um ente um ente dinâmico, ou seja, obtém a lista de tuplas atualmente presentes na história do mesmo. Inspeção da HoloTree Obtenção do estado atual da HoloTree. Opção para detecção de Causa a interrupção do programa quando uma mobilidade mobilidade lógica lógica (através do comando "move") estiver para ocorrer. Obtenção da lista de Retorna a lista de threads ativas no momento. threads em execução Obtenção da pilha de Retorna a pilha de ativação de uma thread, que mostra a ativação (call stack) de seqüência de chamadas a ações que estão ativas (pendentes) uma thread no momento. A thread deve estar suspensa. Localização de threads Dado um registro de ativação de uma thread (elemento da pilha de ativação), permite a identificação da linha atual em execução no programa. Inspeção de variáveis Dado um registro de ativação de uma thread, retorna uma locais lista de variáveis locais e seus valores, e permite a pesquisa de uma variável local pelo seu nome. Suspensão continuação execução e Permite suspensão e continuação do programa como um da todo (todas as threads) ou apenas de uma thread individualmente. Tabela 2 – Serviços de depuração para Holo 1.0 26 Pela tabela 2, observa-se que a maioria dos serviços do depurador operam no contexto de uma ou mais threads. A linguagem Holo não especifica como deve ser implementada a concorrência entre entes, mas em Holo 1.0 esta concorrência é implementada através da criação de uma thread para executar a ação construtiva de cada ente criado. Portanto, a tendência é a de que a depuração da Hololinguagem envolva um grande número de threads. É importante um bom suporte para o gerenciamento das mesmas. Os serviços de depuração para ações lógicas (MLAs), nesta versão do depurador, limitam-se à identificação de registros de chamadas a MLAs na pilha de ativação de uma thread. Não será possível inspecionar estes registros para a identificação do ponto exato do backtracking no código da MLA, ou inspecionar o valor e nomes das variáveis locais. Porém, os serviços de depuração para a parte imperativa da linguagem, relacionados na tabela, foram totalmente implementados. Com os serviços básicos apresentados na tabela 2, é possível também a implementação de serviços sofisticados através de composição. Alguns exemplos seriam: • Agrupamento de threads ou entes: oferecer comandos que permitam, ao usuário do depurador, atuar sobre grupos de threads ou entes. Bastaria manter a informação de grupo em uma estrutura de dados, e enviar o comando, individualmente, para cada thread ou ente do grupo. Por exemplo, seria possível a suspensão ou continuação de um grupo específico de threads. Também seria possível a requisição de notificação quando qualquer ente de um grupo fosse movido de seu contexto atual na HoloTree. • Seleção ou agrupamento de threads ou entes baseado em atributos: seria possível que, por exemplo, o depurador agrupasse automaticamente threads ou entes automaticamente, no momento em que são criados, ou durante algum momento de sua execução (como após a ocorrência de mobilidade), baseado em atributos dos mesmos. Por exemplo, entes que possuam um nome que inicie com "filosofo", ou que possuam uma certa informação em suas histórias, podem ser identificados pelo depurador, e um comando pré-definido pode ser executado sobre eles como, por exemplo, inserção automática em um grupo. • Breakpoints condicionais: uma característica freqüentemente presente em ferramentas comerciais é a possibilidade de inserção de breakpoints que, após serem atingidos por uma thread, notificam o usuário apenas se certas condições forem satisfeitas. Por exemplo, é possível a criação de breakpoints que apenas são disparados, ou cessam de serem ativados, após um certo número de ativações. Um breakpoint condicional também pode ser sensível apenas a certas threads, ou a certos entes dinâmicos. • Salvamento de informações para análise posterior: a ferramenta de depuração pode, por exemplo, coletar todas as configurações da HoloTree durante um certo período, e gravá-las em um arquivo. Este arquivo pode ser usado, por exemplo, para realizar uma animação gráfica ou uma análise da evolução da HoloTree. 27 Esta composição é responsabilidade da "ferramenta de depuração" propriamente dita, ao passo que os serviços básicos são oferecidos pelo "módulo depurador". Estes serviços básicos são fornecidos, pelo módulo depurador, para a ferramenta através da HDI (Holo Debug Interface), uma API desenvolvida neste trabalho. A ferramenta de depuração desenvolvida neste trabalho (Holo Debugger), que será vista mais adiante, serve como demonstração dos serviços básicos da API. 2.6 Considerações finais É evidente que o problema da depuração de programas Holo oferece muitas frentes para pesquisa e implementação. Por um lado, a depuração de programas multiparadigma é um tema que envolve a integração dos paradigmas básicos em nível de interface do depurador. Apesar de ser possível a implementação simultânea de interfaces distintas no mesmo depurador, é desejável uma solução que integre, parcialmente ou totalmente, os depuradores de linguagens de programação em lógica (Prolog) e depuradores imperativos. Por outro lado, à medida em que avançarem os estudos sobre a plataforma Holo distribuída (DHolo), é interessante avançar estudos sobre o suporte à depuração distribuída que deve ser fornecido por esta plataforma. Neste contexto, este trabalho possui uma implementação focada nos serviços de depuração que atendem às necessidades de Holo 1.0, o que inclui um suporte adequado para a depuração de programas multithreaded. Para este fim, oferece um modelo para a arquitetura do depurador Holo, que pode ser usado também em futuras implementações da plataforma e que deve evoluir juntamente com a pesquisa no âmbito do Holoparadigma. Este modelo é apresentado no capítulo 3. 28 3. Holo Extensão para Depuração (HED) Este capítulo apresenta a Holo Extensão para Depuração (HED), uma extensão ao modelo da Holoplataforma que adiciona suporte à depuração de programas. Primeiramente, é apresentada a Holoplataforma estendida com a inclusão dos módulos necessários para a depuração. A seguir, apresentam-se as modificações realizadas sobre o protótipo Java de Holo para a implementação do depurador. Por fim, são descritos os três componentes do depurador que foram implementados neste trabalho: a extensão da ferramenta HoloJava 1.0 para geração de informação de depuração, a HDI (Holo Debug Interface), a especificação e implementação de uma API para ferramentas de depuração Holo, e o Holo Debugger, uma ferramenta simples de depuração baseada na HDI. 3.1 Visão geral A figura 8 apresenta o modelo da Holoplataforma estendida para depuração. Apesar de implementada sobre a Holoplataforma 1.0, a HED prevê mudanças na implementação da plataforma. O principal componente adicionado foi o Holodepurador, que controla o programa em execução na máquina. Os ambientes de execução do Holodepurador e da Holomáquina podem ser o mesmo, ou podem ser distintos. No último caso, configura-se uma sessão de depuração remota. Os sub-componentes do modelo são: Holocompilador HoloEnv Programador Interage Holoprograma Interage Informação de Depuração Execução (Interface com o usuário) IHM - Interface Homem-Máquina Gerador de Informação de Depuração Holocódigo Projeta Máquina IPD - Interface de Programação do Depurador IDM - Interface de Depuração da Máquina PFD – Processo Front-end do Depurador PBD - Processo Back-end do Depurador PDM - Protocolo Depurador-Máquina Holodepurador Holomáquina Figura 8 – Modelo da Holoplataforma estendida para depuração Gerador de Informação de Depuração. É a parte do compilador responsável por inserir informação no código que é necessária para a implementação dos algoritmos do depurador. Por exemplo, o algoritmo que determina o ponto do programa em que uma thread se encontra. Estando a thread parada, com o seu apontador de instrução indicando uma certa 29 instrução no código do programa, é necessário que esta posição seja traduzida para uma informação acessível ao programador, no caso, o número da linha no texto original do programa. Este mapeamento é resolvido pelo depurador através da inclusão da informação necessária no código. A inclusão da informação no código introduz um overhead sobre a execução. Tipicamente, os compiladores que geram informação de depuração fazem isto de forma opcional, podendo gerar duas versões do código: uma destinada para o usuário final, sem a informação, e uma destinada para ser executada sob o depurador, com a informação. Interface de Depuração da Máquina (IDM). É a API que publica as funções de depuração suportadas pela máquina, tipicamente, funções de baixa granularidade. Processo Back-end do Depurador (PBD). É a parte do depurador que precisa executar próximo à máquina sendo depurada. Interage com a máquina através da IDM. Se a "máquina" é, de fato, uma máquina virtual (como é o caso de Holo) o back-end pode ser outro processo, sob o controle do mesmo sistema operacional, que interage com a máquina através da IDM. O PBD também pode fazer parte da implementação da máquina virtual. A plataforma Java, por exemplo, permite as duas possibilidades. Protocolo Depurador-Máquina (PDM). É um protocolo de comunicação que conecta os ambientes da máquina e do depurador. Conecta o PBD com o resto dosoftware do depurador, executando juntamente ao usuário programador. Processo Front-end do Depurador (PFD). Implementa os serviços de depuração, comunicando-se através do protocolo (PDM) com o back-end (PBD). Interface de Programação do Depurador (IPD). É a API que publica as funções de depuração implementadas pelo PFD, tipicamente funções de alta granularidade, que serão utilizadas para a implementação dos serviços fornecidos para o usuário do depurador. Interface Homem-Máquina (IHM) do Depurador. É a interface com o usuário do depurador. Expõe os controles e os meios de visualização da sessão de depuração ao programador. A funcionalidade dos elementos da interface é acessada pela IPD, e implementada pelo PFD. Este componente também pode implementar serviços mais especializados, baseados nos serviços básicos de depuração publicados pela IPD. O projeto da HED é baseado na arquitetura do depurador da Plataforma Java, a JPDA (Java Platform Debugger Architecture) [JPD 02]. A figura 8 pode ser adaptada para representar a atual versão da plataforma Java (1.4) com pouca modificação. Este projeto foi adotado por permitir a depuração remota de programas, com a conseqüente minimização da intrusão causada pela presença do depurador no ambiente de execução do programa depurado [ROS 96], e também por possuir uma organização bastante modular, que permite que os módulos sejam implementados separadamente (máquina, front-end, back-end e interface do depurador) enquanto aderirem às interfaces e protocolos que os conectam. A JPDA representa a segunda geração de suporte à depuração em Java. Este modelo se revela ideal para depuração de plataformas com máquinas virtuais, como é o caso de Holo e Java. Esta adição à Holoplataforma permite que o programador interaja diretamente com a máquina através da IHM do modelo, ou seja, da ferramenta de depuração, que é a interface 30 com o restante do módulo depurador. Esta interação direta com a máquina tem como objetivo inspecionar o estado e controlar a execução de instruções pelo programa carregado na máquina, sem que o programa do usuário precise explicitamente adicionar suporte para a sua depuração. 3.2 Extensão da Holoplataforma 1.0 Neste trabalho, o modelo de extensão para depuração, apresentado na seção anterior, foi implementado sobre a Holoplataforma 1.0, adicionando o suporte à depuração para o protótipo de Holo. A figura 9 ilustra a configuração atual da implementação da plataforma. A tabela 3 relaciona os componentes do modelo abstrato (HED) com os componentes do modelo implementado (Holoplataforma 1.0 estendida para depuração). Holocompilador HoloEnv Holoprograma Programador Interage HoloJava 1.0 Estendida Código-fonte Prolog (Lógico) Código-fonte Java PrologCafe (Imperativo/OO) JavaC Código-fonte Java (Lógico) Bytecode Java do programa Holo Arquivo de Informação de Depuração Execução Interage (Interface com o usuário) Projeta Bibliotecas (Java2, Java, PrologCafe) Holocódigo IHM Holo Debugger IPD HDI Holo Debug Interface Java Virtual Machine executando bytecode Java que implementa o programa Holo Implementação da HDI (Classes Java) PFD JDI Java Debug Interface Processo Front-end de depuração da JPDA Implementação Java2/JDK Holodepurador JDWP Java Debug Wire Protocol (PDM) Processo Back-end de depuração da JPDA Implementação Java2/JDK (PBD) Holomáquina Figura 9 – Holoplataforma 1.0 estendida para depuração através da HED. Em destaque: módulos que receberam extensões ou foram implementados no contexto deste trabalho. 31 Componente da HED Mapeamento para a extensão de Holo 1.0 IHM Holo Debugger IPD Holo Debug Interface (HDI) PFD Implementação da HDI, baseada na JDI (Java Debug Interface), que é a IPD da plataforma Java. PDM Java Debug Wire Protocol (JDWP) PBD e IPD Java Virtual Machine (JVM) Tabela 3 – Mapeamento da HED para a Holoplataforma 1.0 A implementação da HED sobre a Holoplataforma 1.0 é fortemente embasada na implementação atual da arquitetura do depurador da plataforma Java, a JPDA (Java Platform Debugger Architecture). Os componentes da JPDA implementam todos os serviços de depuração sobre o bytecode Java que implementa o programa Holo. A API de mais alto nível da JPDA, a JDI (Java Debug Interface), é, seguindo a nomenclatura da HED, uma interface de programação para depuradores (IPD) Java. Neste contexto, a JDI foi utilizada para a implementação da HDI. Desta forma, no contexto dos serviços de depuração de programas, a HDI traduz a linguagem Java para Holo, e a JDI, manipulada pelas classes de implementação HDI, traduz o bytecode Java para linguagem Java. Porém, para que os algoritmos da HDI realizem a depuração de Holo sobre Java, é preciso que o programa Holo possua um mapeamento para as instruções da Holomáquina, ou seja, em última análise, para o bytecode Java. Como será detalhado na seção 3.3, a plataforma Java provê o mapeamento do bytecode Java para a linguagem Java, e, para o mapeamento da linguagem Java para Holo, a ferramenta HoloJava foi estendida para gerar esta informação. De acordo com esta configuração, no ambiente depurado, a Holomáquina, residem dois processos: a Máquina Virtual Java (JVM) e o processo back-end do depurador (PBD), que se comunica com a JVM através da JVMDI (Java Virtual Machine Debug Interface, a IDM do modelo). Na prática, o PBD default da plataforma Java é implementado no mesmo processo da JVM (situação exibida pela figura 9), sendo a JVMDI publicada para a construção de depuradores por outros fornecedores de software. O ambiente depurado e o ambiente depurador se comunicam através da JDWP (Java Debug Wire Protocol, o PDM do modelo). O módulo front-end do Holodepurador (PFD) é composto de duas implementações: • Front-end de depuração Java: processo fornecido pela plataforma Java que implementa a JDI (Java Debug Interface), a API de depuração de alto-nível de Java. • Implementação da HDI (Holo Debug Interface): classes Java que controlam o PFD Java através da JDI. A Holo Debug Interface (HDI) é a interface de programação de depuradores Holo, ou seja, a IPD do modelo da HED. A sua implementação esconde a abstração “Máquina Virtual 32 Java”, fornecida pela JDI, substituindo -a por uma interface de programação com uma “Máquina Virtual Holo”. A HDI é a principal contribui ção deste trabalho em nível de implementação. Com base na HDI, foi implementado o depurador Holo Debugger, que é a ferramenta de depuração propriamente dita (a IHM do modelo). É uma ferramenta composta basicamente de uma interface homem-máquina, que é alimentada através de chamadas para a HDI. É importante observar que o Holo Debugger poderia ter sido implementado diretamente sobre a JDI, porém, além de se tornar uma ferramenta bem mais complexa, o depurador estaria amarrado à uma implementação particular da Holoplataforma. Ao seguir o modelo proposto pela HED, é possível que a Holoplataforma seja re-implementada, de forma independente de Java, sem que seja necessário alterar o código-fonte do Holo Debugger. Obviamente, neste cenário, a HDI teria que ser re-implementada, pois a JDI não estaria mais disponível para oferecer a implementação dos serviços de depuração. Porém, a especificação da API HDI poderia permanecer inalterada. 3.3 HoloJava estendida 3.3.1 Visão Geral A ferramenta HoloJava foi modificada para gerar informação de depuração. "Informação de depuração" significa uma informação que permite à máquina e ao depurador relacionarem elementos do código (instruções, endereços) com elementos do programa que o gerou (linhas de texto, identificadores). Este mapeamento entre código e programa é gerado e gravado em “tabelas de símbolos” [ROS 96, p. 157 -161], que não devem ser confundidas com as tabelas de símbolos internas utilizadas pelos compiladores. As tabelas em questão são inseridas no código do programa, e são utilizadas, pela máquina e pelo depurador, para viabilizar a execução dos algoritmos de depuração. Por exemplo, quando o usuário de um depurador quer saber o valor de um identificador cujo nome é “X”, o depurador consulta a tabela para saber em que endereço de memória da máquina se encontra o valor de "X". Da mesma maneira, se o usuário quer que a execução do programa seja interrompida quando a linha do programa de número "47" for atingida por um fluxo de execução (thread), o depurador consulta a tabela para saber em que ponto do código deve ser inserido um breakpoint. No atual protótipo de Holo, a grande parte do trabalho de geração de tabelas de símbolos, e utilização das mesmas em algoritmos de depuração, é suprida pela plataforma Java. A Máquina Virtual Java (JVM) e a JPDA (arquitetura do depurador Java) se encarregam da implementação dos algoritmos de depuração Java a partir da informação gerada e inserida no bytecode Java pelo compilador JavaC. Assim, o mapeamento entre "Código Java" e "Programa Java", no contexto da depuração de programas, é completamente suportado pela plataforma Java. Para implementar a depuração de programas Holo, foi preciso adicionar um mapeamento adicional, entre "Programa Java" e "Programa Holo". Quem cobre este mapeamento é a informação de depuração Holo, que é gerada juntamente com o "Código 33 Holo" pela HoloJava estendida. Assim, fecha-se a ponte entre "Código Java" e "Programa Holo", necessária para a implementação dos serviços e algoritmos de depuração de Holoprogramas. A informação de depuração que é gerada pela HoloJava é separada em dois tipos: • Mapeamento de símbolos: mapeamento entre símbolos Holo e símbolos Java, como nomes de variáveis Holo e Java, entes Holo e classes Java, etc. • Mapeamento de linhas: mapeamento entre números de linha Java e números de linha Holo (linhas do texto-fonte do programa). A figura 10 ilustra o envolvimento dos módulos com a geração e a utilização da informação de depuração. A figura deve ser lida da seguinte maneira: o mapeamento de código realiza a tradução entre o bytecode Java executando na Máquina Virtual Java e as construções do programa Holo que originou este bytecode. Este mapeamento é realizado pela informação de depuração, que é gerada pelo Holocompilador (HoloJava estendida + JavaC) e consumida pelo aparato de depuração de programas Holo (Java VM + JPDA + HDI + Holo Debugger). Código Java Informação de Depuração Java Tempo de Compilação HoloJava Estendida JavaC Programa Java Informação de Depuração Holo Máquina + Depurador Holo Java VM JPDA Programa Holo Mapeamento de Código Tempo de Execução (Depuração) Figura 10 – Geração e utilização da informação que realiza o mapeamento bidirecional entre código Java e programa Holo, necessário para a implementação dos algoritmos de depuração. 3.3.2 Formato da informação de depuração Atualmente, a ferramenta HoloJava estendida para depuração gera um arquivo que contém todas as informações necessárias para a realização do mapeamento entre o código Java gerado e o programa Holo original. A figura 11 lista o arquivo de informação de depuração gerado para o programa Holo que implementa o "Jantar dos Filósofos", apresentado na seção 2.4.2. A tabela 4 lista as construções utilizadas atualmente, pela HoloJava 1.0 estendida, para a geração do arquivo, e o tipo de informação que é expressa por cada uma. 34 #SB:1:holo #IA:3:holo/0/0 5 47 . 6 48 . 7 49 . 8 50 .b. 10 53 ........e 12 62 b. 14 64 .....e 16 70 . 17 71 r #SB:31:filosofo #IA:33:filosofo/0/0 35 21 . 36 22 38 25 39 28 40 34 41 39 42 44 44 49 45 53 46 57 47 61 48 64 50 67 51 70 #END .b. ... ...... ..... ..... .b..e .... .... .... ... ..e ... r Figura 11 – Arquivo de informação de depuração para o "Jantar dos Filósofos" Construção #SB:<linha>:<nome> e #END Informação Indica o início da declaração, na linha Holo especificada, de um ente estático com o nome especificado. A declaração termina com o início da declaração de outro ente, ou com a declaração especial #END, que sinaliza o fim da lista de entes estáticos do programa. #IA:<linha>:<nome>:<aridade>:<retorno> Indica o início da declaração, na linha Holo especificada, de uma ação imperativa (IA) de nome e aridade especificados. Caso a ação possua valor de retorno, o parâmetro <retorno> é 1, caso contrário, será 0. #MLA:<linha>:<nome>:<aridade>:<retorno> Idem à anterior, porém, indica a declaração de uma ação lógica modular. <linha Holo> <linha Java> <lista de atributos> Indica uma entrada para a tabela de mapeamento entre código Holo e Java. Mapeia o número de linha Holo para o conjunto de linhas Java que inicia com o número de linha Java especificado, com a extensão de uma linha para cada símbolo presente na lista de atributos. Tabela 4 – Informação de depuração gerada para arquivo pela HoloJava estendida 35 As linhas do arquivo de informação de depuração que indicam entradas para a tabela de mapeamento de código carregam duas informações: a localização das linhas de código Java que implementam uma linha de código Holo, e a “natureza” de cada uma das instruções Java individuais que implementam esta linha de Holo. A distinção desta natureza das instruções Java foi necessária para a implementação do algoritmo de execução "passo-apasso" do depurador, que será visto em detalhe na seção 3.5.4. Esta informação é indicada pela "lista de atributos" da entrada de mapeamento de código, que atribui um código (no arquivo, representado por um caractere) para cada linha de código Java gerada pela ferramenta HoloJava. Os códigos e os atributos correspondentes da linha Java gerada são relacionados na tabela 5. Código Natureza da Instrução . Indica uma linha Java que não possui nenhuma semântica de desvio. b Indica uma linha Java que possui semântica de desvio local (dentro da ação). c Indica uma linha Java que causa desvio por chamada a uma ação Holo, ou seja, que causa empilhamento de mais um registro de ativação na pilha da thread. e, r Indicam uma linha Java que causa um retorno de ação Holo. (“r” indica retorno por atingir a declaração de fim de ação Holo, que é representada pelo caractere “}”) Tabela 5 – Código do arquivo de informação de depuração e os atributos possíveis para uma linha de código Java gerada pela ferramenta HoloJava 1.0 estendida. 3.3.3 Alterações na gramática e limitações A ferramenta HoloJava, atualmente, é gerada automaticamente pelo gerador de parsers JavaCC [JAV 02a], sendo suprido para este a gramática "decorada" da linguagem, ou seja, a descrição da gramática acrescida de ações (código Java qualquer) associadas à seqüência de tokens que forem reconhecidos pela aplicação do parser. A geração de um arquivo de depuração para HoloJava consistiu da alteração destas ações para incluir a geração do arquivo de informação de depuração. Uma limitação da informação gerada e, conseqüentemente, do depurador implementado, é a ausência de informação de mapeamento entre o código Holo e o código Java que implementa o código Prolog gerado pela ferramenta HoloJava. Esta limitação devese à complexidade excessiva do código Java gerado pela ferramenta PrologCafe. Para 36 viabilizar a realização do mapeamento para o código em lógica de Holo, seria necessário um estudo da estrutura deste código gerado pela ferramenta PrologCafe, ou então, a utilização de outra ferramenta ou estratégia para o tratamento do código Prolog em Holo. 3.4 Holo Debug Interface - Visão Geral Como destacado anteriormente, grande parte da arquitetura do depurador do protótipo é implementada pela JPDA, a arquitetura do depurador de Java. Como um programa Holo é, atualmente, um programa Java quando está executando na máquina Holo (que é essencialmente implementada por uma maquina virtual Java), para o protótipo foi necessário o acréscimo de uma camada adicional de software, a HDI (Holo Debug Interface), que traduz a interface de programação para depuração Java em uma para depuração Holo. A HDI é uma interface de programação que facilita a criação da ferramenta de depuração Holo. Através dela, o programador da ferramenta de depuração pode se concentrar no desenvolvimento da interface homem-máquina de depuração e dos serviços agregados de depuração, abstraindo-se dos detalhes de implementação dos serviços básicos, que são fornecidos pelas classes que implementam a HDI. A ferramenta não precisa saber como, por exemplo, interagir com o protocolo de depuração ou como implementar os algoritmos básicos de depuração. É necessário apenas invocar os métodos sobre as interfaces da HDI e tratar as possíveis exceções. A HDI, atualmente implementada em Java, fornece uma coleção de interfaces Java para acesso às suas classes. A JDI (Java Debug Interface), a camada mais abstrata da JPDA, foi a API utilizada para a implementação da HDI. Uma das características de projeto da JDI, que também foi adotada pela HDI, é a visão da ferramenta de depuração como uma ferramenta que manipula uma "imagem" da máquina. Esta imagem é composta por um conjunto de "espelhos" (mirrors), que são objetos intermediários, que representam os objetos do programa que estão presentes na máquina. Os espelhos manipulados pelo depurador possuem duas funções básicas: refletir o valor mais recente conhecido do objeto real, e atuar como um intermediário (proxy) para a manipulação deste objeto. Para cada tipo diferente de objeto ou entidade identificável da Máquina Virtual Holo (inclusive a própria), foi criada uma interface na HDI. Esta interface esconde a implementação atual do objeto-espelho em questão. Desta forma, a ferramenta de depuração é isolada da implementação dos serviços de depuração oferecidos, caso a interface permaneça constante. 3.5 Interfaces e Algoritmos da HDI A tabela 6 relaciona as principais interfaces da HDI, indicando que tipo de objeto representam e quais operações permitem que sejam realizadas sobre o mesmo. Algumas das interfaces não representam diretamente elementos de modelagem de Holo, mas provedores de serviços de depuração, como gerenciadores de Holomáquinas e de eventos. Todas as interfaces são atuam apenas para uma única sessão de depuração, ou seja, operam no contexto de apenas uma Holomáquina. A única exceção é a interface HoloVirtualMachineManager, que é responsável por disparar as Holomáquinas que estarão sob o comando do depurador. 37 Interface Representa Função principal HoloVirtualMachineManager Um gerenciador Holomáquinas de Iniciar uma sessão de depuração sobre um programa Holo, iniciando a máquina Holo. HoloVirtualMachine Uma Holomáquina Controlar a máquina como um todo (suspensão e cancelamento da execução), obter uma referência para o ente raiz da HoloTree, obter a lista de threads e de entes estáticos do programa. HoloEventRequestManager Interface para requisição de Permitir que o depurador notificação de eventos registre a intenção, junto à Holomáquina, de que deseja ser notificado quando certo tipo de evento ocorrer na execução do programa. HoloEventQueue Interface para acesso à fila Permite a retirada de eventos de eventos gerados pela Holomáquina. HoloThread Uma thread HoloStackFrame Um registro de ativação da Permitir acesso à informação do pilha de ativação de uma contexto de uma chamada de thread ação, como: posição do apontador de instrução da thread no programa Holo e a lista de variáveis locais à chamada de ação em questão. HoloBeingType Um ente estático Fornecer informação estática do ente (nome, lista de ações, linha do programa) HoloAction Uma ação Fornecer informação estática da ação (nome, tipo, aridade, retorno, linha do programa) Permitir a manipulação da thread. Por exemplo, suspensão ou continuação de execução, obtenção da pilha de ativação e obtenção do status da thread (executando, esperando em monitor, morta...) 38 HoloBeingReference Um ente dinâmico Fornecer informação sobre o ente, como o ente estático que o originou, e os seus entes predecessor e sucessores na HoloTree HoloVariable Uma variável Fornecer uma referência a uma variável local a um registro de ativação HoloVariableValue Valor de uma variável String que representa o conteúdo de uma HoloVariable HoloEventRequest Uma requisição de notificação de evento de interesse do depurador, criada através do HoloEventRequestManager. Permite a configuração da requisição. Deriva outras interfaces especializadas para requisição de eventos específicos, como criação de breakpoints e inspeção de história. HoloEvent Um evento que ocorreu na Holomáquina, de interesse do depurador. Acessado por consulta a HoloEventQueue Contém informação sobre o evento gerado. Deriva outras interfaces especializadas que indicam o tipo de evento específico, como notificação de breakpoint atingido ou conclusão de leitura de uma história. HoloLocation Localização de uma linha de código Java que implementa um comando de Holo. Fornecer informação sobre a localização, como a que ação (HoloAction) e a que ente estático (HoloBeingType) pertence. Tabela 6 – Interfaces da HDI As seções seguintes detalham algumas das interfaces da HDI que encapsulam os principais algoritmos do depurador, seguido de uma descrição dos algoritmos empregados, onde for o caso. 3.5.1 Gerenciamento de sessões de depuração A interface HoloVirtualMachineManager atualmente oferece, para a ferramenta de depuração, a opção de se iniciar uma máquina Holo localmente, executando um programa 39 Holo. Caso a máquina inicie com sucesso, uma interface HoloVirtualMachine é retornada. Esta interface representa a máquina Holo, e será utilizada pelo depurador para interagir com a máquina, durante toda a sessão de depuração. O modelo prevê que a interface HoloVirtualMachineManager também forneça serviços de disparo de Holomáquinas remotas, para permitir que o depurador execute em um ambiente isolado da máquina Holo. Também prevê a possibilidade de anexação (attach) do depurador a uma Holomáquina que esteja suspensa devido a um erro. Contudo, estas opções não se encontram implementadas atualmente. 3.5.2 Breakpoints A interface HoloBreakpointRequest representa uma requisição de notificação quando uma thread atingir uma certa linha do programa, fornecida como parâmetro para a requisição. Quando o breakpoint é atingido, um objeto do tipo HoloBreakpointEvent é inserido na fila de eventos do depurador. A implementação dos algoritmos de inserção e remoção de breakpoints é baseada na implementação de breakpoints em Java, através da JDI (Java Debug Interface). Para isto, ao criar um breakpoint em uma linha do programa Holo, através da tabela de símbolos, construída a partir do arquivo de informação de depuração gerado pela HoloJava estendida, é possível identificar a localização, no código Java, da primeira linha de código Java que implementa aquela linha Holo. Assim, é requisitado um breakpoint Java nesta linha, para a JDI. Quando a JDI notifica a implementação da HDI de um evento de breakpoint Java, este evento é traduzido, através de uma tabela que converte breakpoints Holo em Java, para um evento de breakpoint Holo. 3.5.3 Carga de entes estáticos A interface HoloBeingLoadedRequest representa uma requisição de notificação quando um ente estático for carregado pela máquina. Atualmente, o evento de carga de entes estáticos baseia-se na carga e preparação das classes Java que implementam os entes. Quando um ente estático é carregado pela Holomáquina, um objeto do tipo HoloBeingLoadedEvent é inserido na fila de eventos do depurador. A aplicação principal deste evento é na validação de breakpoints [ROS 96, p.111], responsabilidade que é, atualmente, da ferramenta de depuração, e não da implementação da HDI. Quando um usuário do depurador criar um breakpoint em uma certa linha de programa Holo, e o programa não estiver sido ainda iniciado, não é possível saber se este breakpoint aponta para uma linha válida do programa, ou seja, uma linha a que corresponda código gerado (no caso, uma linha que gere código Java). Um breakpoint pode ser validado quando um ente estático é carregado. Quando isto ocorre, um novo objeto, representando aquele ente estático, é adicionado ao depurador. Este objeto pode ser acessado através da interface HoloBeingType, que especifica quais linhas do programa Holo pertencem a código gerado, ou seja, linhas que pertencem à ações deste ente estático. O breakpoint é validado assim que 40 um ente estático carregado contém, nas suas ações, a linha de parada, especificada como parâmetro do breakpoint. 3.5.4 Execução passo-a-passo A interface HoloStepRequest representa uma requisição de execução de um "passo" (geralmente, execução de uma linha) do programa. Como parâmetros para a requisição devem ser fornecidos uma thread, que deve estar suspensa, e o tipo de passo (step into, step over ou step out). Quando a execução do passo é completada, um objeto do tipo HoloStepEvent é colocado na fila de eventos do depurador. O algoritmo de execução passo-a-passo implementado pela HDI é eficiente. Seria possível a implementação de um algoritmo que simplesmente utilizasse a implementação do serviço de passo-a-passo em Java, oferecido pela JDI. Desta forma, algoritmo poderia, por exemplo, realizar um "passo" em Java para cada linha Java que implementa a linha Holo. Porém, como é indicado em [ROS 96, p.124], um algoritmo de passo-a-passo implementado desta forma ("repeated instruction single-step") não é eficiente. Como a relação entre linhas Holo e Java é, tipicamente de 1 para 3, ao contrário da relação entre linguagens de alto nível e código nativo correspondente (instruções de máquina), que podem chegar a 1 para 1000, talvez a implementação por repetição de passos sobre as instruções não causasse um overhead muito grande sobre o algoritmo. Apesar disto, optou-se neste trabalho a implementação do algoritmo "inteligente", seguindo a forma geral apresentada por Rosemberg em [ROS 96, p.119]. O algoritmo implementado requer que cada instrução de máquina (no caso, linha de código Java) que implementa uma linha do programa de alto-nível (no caso, linha de programa Holo), seja decodificada para a obtenção do seu tipo. No caso, é necessário que o algoritmo possa identificar se a instrução nunca causa nenhum tipo de desvio, ou seja, após a sua execução, o apontador de programa sempre aponta para a próxima instrução na seqüência natural do código. Caso a instrução não seja deste tipo, ela deve ser identificada como uma instrução de chamada de função (call instruction) ou uma instrução de desvio (branching instruction). O algoritmo de passo-a-passo da HDI também diferencia instruções de retorno de chamadas para facilitar a implementação dos mesmos. Optou-se por omitir estes detalhes da descrição do algoritmo. A figura 12 apresenta, de forma conjunta, os algoritmos de execução passo-a-passo, dos tipos step into e step over, descrito em [ROS 96, p.121-124] e adaptado para o contexto de Holo sobre Java, implementado na HDI. Neste contexto, por uma "instrução" entenda-se "linhas de código Java" geradas pela ferramenta HoloJava, e por "apontador de instrução", entenda-se um "número de linha e nome de classe Java". 41 Entrada Tipo do passo (step into ou step over), a Thread em estado "suspenso" (ou seja, não apta a receber fatias de tempo do escalonador) que vai realizar o passo, e o apontador para a instrução atual da thread. Saída Thread ainda em estado suspenso, com novo valor para o seu apontador de instrução e, possivelmente, nova configuração da sua pilha de ativação (pilha de chamadas de ações). A instrução apontada é a implementação do próximo comando Holo, na ordem lógica do programa, a ser executado. Método 1. moved := false e batch := false 2. simulated_pc := current_pc 3. se (moved_flag == true) e (simulated_pc aponta para uma instrução que implementa o início de um comando) então: se (batch == false) step into completado, enviar notificação à interface do depurador e interromper algoritmo. senão: fazer batch = false e criar um breakpoint interno nesta instrução que, quando atingido, retornará a execução do algoritmo no passo 2. continuar a execução da thread e interromper o algoritmo. 4. decodifica o tipo de linha Java apontada por simulated_pc (normal, ou algum tipo de instrução de desvio). 5. se (instrução é do tipo normal) então faz moved = true e batch = true, e avança simulated_pc para a próxima instrução na seqüência natural do programa, e retorna para o passo 3. 6. se (batch == true) então fazer batch = false e criar um breakpoint interno nesta linha Java que, quando atingido, retornará a execução do algoritmo no passo 2. continuar a execução da thread e interromper o algoritmo. 7. fazer moved = true 8. se (passo é do tipo step over) e (instrução é do tipo call), então requisitar um passo interno Java do tipo step over que, quando completado, retornará a execução do algoritmo no passo 2. senão, requisitar um passo interno Java do tipo step into que, quando completado, retornará a execução do algoritmo no passo 2. em ambos os casos, continuar a execução da thread e interromper o algoritmo. Figura 12 – Algoritmo de passo-a-passo do tipo step into ou step over adaptado para implementação da HDI. A idéia geral do algoritmo é a de utilização de execução contínua sobre seqüências de instruções que não causam desvio (no caso, intituladas de "normais"). O algoritmo segue avançando um apontador de programa simulado (simulated_pc) até que este aponte para o 42 início da implementação de uma linha Holo, ou que seja encontrada uma instrução de desvio. Quando isto ocorre, o depurador insere um breakpoint interno nesta instrução, e continua a execução da thread, que executará um bloco de instruções sem precisar notificar o depurador. Quando é necessário executar uma instrução de desvio, requisita-se a execução apenas daquela instrução para a máquina. No caso da HDI, é feito um pedido de execução passo-apasso, em nível de Java, para a JDI. O algoritmo de passo do tipo step out é mais simples. Dada uma thread suspensa, basta localizar a instrução, no registro de ativação anterior da pilha de ativação da thread, que sucede a instrução do tipo call que empilhou a chamada atual. Nesta instrução, é inserido um breakpoint interno a HDI que, quando atingido, retorna a execução do algoritmo de step out, que apenas notifica a ferramenta de depuração que o passo foi completado com sucesso. 3.5.5 Inspeção da história de um ente A interface HoloHistoryRequest representa uma requisição de consulta ao conteúdo atual da história de um ente. A ferramenta de depuração faz a requisição para a HDI e, quando a história do ente termina de ser coletada, é retornada através de um evento HoloHistoryEvent, inserido na fila de eventos do depurador. Esta operação foi modelada desta maneira (requisição assíncrona) porque a biblioteca Jada, que implementa a história, não permite a leitura de todas as tuplas da história de forma atômica, e é possível que implementações futuras da história também possuam esta limitação. Como só é possível a leitura de toda a história através da retirada, e posterior re-inserção, de todas as tuplas da história, é possível que uma thread do programa usuário enxergue a história em um estado inconsistente. É possível evitar isso através da suspensão de todo o programa enquanto a leitura é feita. Porém, isto pode não ser desejado pelo usuário da ferramenta de depuração se, por exemplo, uma história pouco acessada possuir uma quantidade grande de elementos. Isto causaria um atraso desnecessário na execução do programa sob o depurador. Sendo este serviço modelado como uma requisição assíncrona, bastou se criar uma thread, a serviço do depurador, para extrair as tuplas da história de forma concorrente ao programa do usuário, enquanto aquela história é protegida de acesso por uma seção crítica. Quando o depurador termina de examinar a história, seu acesso é novamente liberado para as threads da aplicação. Desta maneira, a aplicação pode continuar a executar, sendo que as únicas threads que ficarão temporariamente bloqueadas serão aquelas que aguardam acesso a uma história sob inspeção do depurador. Pode ser desejável que toda a aplicação seja suspensa durante a leitura de uma história. Por exemplo, se o programador quiser se certificar que a inspeção da história não irá afetar a ordem de execução das threads. Para isto, basta usar a HDI para suspender a máquina Holo antes da requisição, e continuar a execução após notificação da conclusão da leitura. Apesar da thread que inspeciona a história estar localizada na Holomáquina, ela é identificada como uma thread auxiliar do depurador, e não é suspensa. 43 3.5.6 Detecção de mobilidade lógica A interface HoloTreeModificationEvent representa uma requisição de notificação, sempre que a HoloTree do programa sofrer alterações, ou seja, sempre que uma mobilidade lógica de entes ocorrer. Quando ocorre mobilidade, um evento do tipo HoloTreeModificationEvent é inserido na fila de eventos do depurador. Após receber o evento, o depurador pode, por exemplo, realizar uma leitura do estado atual da HoloTree para exibição ao usuário. A detecção de mobilidade foi feita através da inserção de código Java especial no código gerado pela HoloJava. Este código insere uma chamada a uma rotina especial, inserida pelo depurador no código do programa, antes da execução do código Java que implementa o comando move de Holo. Esta rotina não realiza nenhuma operação, porém, serve como um ponto para que o depurador insira um breakpoint interno nesta rotina, caso haja uma requisição para monitoração de mobilidade. Quando o breakpoint é atingido, o evento HoloTreeModificationEvent é gerado e enviado para o depurador Holo (cliente da HDI), que tem a opção de ler o estado atual da HoloTree antes que ela seja modificada. Isto é possível através do controle de políticas de suspensão, apresentado na próxima seção. 3.5.7 Configuração de requisições Após o depurador Holo criar uma requisição de notificação de evento, através da HDI, ele pode configurá-la através da interface que representa a requisição, antes que esta seja habilitada e gere eventos. Duas opções de configuração para requisições são importantes: a definição da política de suspensão da requisição, e a criação de uma seqüência de filtros para a requisição. A política de suspensão da requisição determina, quando o evento requisitado ocorrer na máquina Holo, quais threads serão automaticamente suspensas, antes mesmo que o depurador seja notificado do evento. A HDI disponibiliza três opções para políticas de suspensão: • SUSPEND_NONE: Não suspende nenhuma thread quando o evento requisitado ocorrer. O programa continua executando normalmente. Por exemplo, no caso em que um breakpoint é atingido, a aplicação será notificada de que aquele ponto do programa foi atingido, porém, a aplicação não será interrompida naquele ponto. • SUSPEND_EVENT_THREAD: Suspende apenas a thread que causou a geração do evento. Por exemplo, no caso de um breakpoint ser atingido, apenas a thread que atingiu o breakpoint é suspensa. Todas as outras threads da aplicação continuam executando normalmente. • SUSPEND_ALL: Suspende todas as threads da aplicação quando o evento ocorrer. É a política default. Este mecanismo de políticas de suspensão é utilizado pela JDI. Como mencionado na seção 3.5.6, se a política de suspensão do evento de modificação da árvore não for SUSPEND_ALL, é possível que dois ou mais eventos do tipo HoloTreeModificationEvent 44 estejam presentes na fila de eventos da ferramenta de depuração ao mesmo tempo, o que significa a perda da informação sobre um estado intermediário da HoloTree, pela ferramenta. Porém, pode ser de interesse do usuário do depurador apenas a contagem de operações de mobilidade executadas. Neste caso, uma política do tipo "suspend none", que causa um menor atraso à execução da aplicação, é suficiente. Isto também poderia ser aplicado, por exemplo, à criação de contadores de passagens (breakpoints com política "suspend none"). A criação de seqüências de filtros ainda não está completamente implementada na HDI, e trata-se de outro serviço oferecido pela JDI. Por exemplo, seria possível a especificação de breakpoints que só são ativados por certas threads, através de filtros de inclusão ou de exclusão de threads. Um filtro simples, de contagem de ativação, está implementado na HDI. Ele permite a geração de um evento, a partir de uma requisição, somente após um número mínimo de ocorrências daquele evento. Inclusive, após este número de ocorrências, o evento não é mais gerado. Isto pode ser útil para a implementação, pela interface do depurador, de serviços como "run-to-here" (execução até um ponto do programa apontado pelo usuário). No caso, a implementação se dá com a criação de um breakpoint, no ponto desejado do programa, que só pode ser ativado uma única vez. Quando for ativado, ele é automaticamente removido. 3.6 Holo Debugger O Holo Debugger é uma ferramenta de depuração do tipo "linha de comando", como o depurador jdb [JDB 02], mas com uma conveniência adicional de separar as mensagens emitidas pelo depurador e pelo programa. A figura 13 apresenta a interface da ferramenta. Figura 13 – Interface do Holo Debugger Os painéis indicados na figura são: 1. Janela de saída do programa: Exibe mensagens emitidas pelo programa em execução. 2. Janela de saída do depurador: Exibe mensagens emitidas pelo depurador. 3. Caixa de entrada: Espaço para o usuário enviar comandos para o depurador. 45 Os comandos da versão atual do Holo Debugger são implementados a partir dos serviços básicos disponibilizados pela HDI. A tabela 7 lista todos os comandos do depurador, como uma descrição de suas ações. O Holo Debugger atualmente não agrega muito valor aos serviços já oferecidos pela HDI, sendo a validação da API sua principal função. As funções oferecidas pela versão atual do Holo Debugger já permitem que o programador extraia qualquer informação, em tempo de execução, que pode ser expressa em Holo 1.0. Também permite o total controle da execução do programa, podendo este acompanhar a execução de apenas uma thread, individualmente, executando uma linha do seu programa Holo de cada vez. Neste sentido, o Holo Debugger oferece funcionalidade comparável ao depurador Java jdb. Assim como a Sun Microsystems, fornecedora do jdb, fornece o mesmo como um depurador exemplo da sua arquitetura do depurador para Java (JPDA), a interface atual do Holo Debugger é uma demonstração da HED, a arquitetura do depurador proposta por este trabalho e, principalmente da HDI, a interface de programação para depuradores Holo, implementada no contexto da HED. Os resultados obtidos corroboram a implementação da segunda versão do Holo Debugger, integrada ao HoloEnv, que possuirá uma interface gráfica. Um depurador gráfico justifica a oferta de serviços adicionais, compostos dos serviços básicos oferecidos pela HDI. Por exemplo, torna-se viável a criação de diálogos para a especificação de breakpoints com filtros, por exemplo, que desativam o breakpoint para um certo conjunto de threads. É possível, porém pouco prático, que o usuário gerencie este tipo de serviço através de uma interface puramente textual. A especificação através de janelas, menus, e listas é mais prática. Comando Ação load [caminho] <holoprograma > Carrega e executa o holoprograma especificado, [argumentos...] juntamente com uma lista de argumentos. O caminho deve conter o programa na forma compilada, com informação de depuração. Interrompe a execução antes que o primeiro comando Holo execute, retornando o controle ao depurador. run [caminho] <holoprograma> Similar a load, porém não suspende a execução ao [argumentos...] encontrar o primeiro comando Holo. Exit Encerra a sessão de depuração atual. Cls Limpa a janela de mensagens do depurador. Help Exibe a ajuda do depurador. Pause Suspende a execução do programa, ou seja, suspende todas as threads que ainda não estão suspensas. Cont Continua a execução do programa (continua a execução de todas as threads que estão suspensas). 46 sel <nome-ente> Seleciona um ente da HoloTree. Também é o comando utilizado para se selecionar uma thread. A thread selecionada é a thread associada ao ente, ou seja, a thread que foi criada para a execução do comportamento deste ente. Sel Imprime informações sobre o ente e a thread atualmente selecionados, juntamente com a um traço da execução da thread (pilha de ativação) e estado da thread (suspensa, em execução, em espera...). T Lista todas as threads atualmente ativas. S Step into: faz com que a thread selecionada execute uma linha de programa Holo. A thread deve estar suspensa. N Step over: idem a step into, porém trata as chamadas a ações como operações atômicas. R Step out: faz com que a thread selecionada retorne da ação atual. A thread deve estar suspensa. tree Exibe o estado atual da HoloTree. trece [on | off] Liga ou desliga a detecção e impressão, na janela de saída do depurador, de modificações sofridas pela HoloTree, à medida que o programa executa. stop <número-da-linha> Cria um breakpoint (incondicional) em uma linha do programa. clear Lista todos os breakpoints atuais, bem como seu estado (confirmado ou não-confirmado). clear <número-da-linha> Remove todos os breakpoints inseridos em uma linha do programa. locals Exibe todas as variáveis locais ao contexto de execução da thread atualmente selecionada. A lista associa cada nome de variável ao seu valor atual e a que chamada (registro de ativação) pertence. his Exibe o conteúdo da história do ente atualmente selecionado. his <nome-ente> Exibe o conteúdo da história de um ente, dado o seu nome. Tabela 7 – Comandos do Holo Debugger 47 4. Implementação Este capítulo descreve detalhes da implementação dos softwares desenvolvidos neste trabalho: a extensão da ferramenta HoloJava 1.0, a implementação das classes da Holo Debug Interface, e a implementação do Holo Debugger. Todo o software foi desenvolvido em Java. A plataforma de desenvolvimento adotada foi a J2SE (Java2 Standard Edition) versão 1.3. A versão mais atual das ferramentas, incluindo código fonte e documentação, é disponibilizada por WWW no endereço <http://www.inf.ufrgs.br/~fcecin/holo/>. 4.1 HoloJava estendida 4.1.1 Visão geral As alterações na ferramenta HoloJava, versão 1.0, consistem de alterações à gramática da linguagem (holojava.jj) e de alterações nas classes do pacote holoj.lang, que implementa algumas classes base da linguagem Holo. À gramática da linguagem, de forma aproximada, foram adicionadas 480 linhas de código para implementar a geração de informação de depuração, e em torno de 150 linhas de código foram alteradas. Estas modificações não alteram, de forma perceptível, o tempo de execução da ferramenta. Não foram feitos testes para geração de informação de depuração para grandes programas Holo, já que o protótipo da ferramenta não foi desenvolvido ou otimizado para este propósito. As alterações ao pacote holo.lang consistem da adição de uma nova classe (DBVector) de 110 linhas, e de algumas alterações à classe Being, a classe base das classes que implementam entes estáticos de Holo. A classe DBVector re-implementa o vetor de entes da HoloJava (BVector) com sua estrutura interna alterada de forma a facilitar a sua inspeção pela API de depuração de Java, o que viabiliza o serviço de depuração Holo de inspeção da HoloTree. 4.1.2 Limitações e possibilidades de expansão Atualmente não é gerada informação de depuração sobre o conteúdo de MLAs, as ações lógicas modulares (sintaxe Prolog) de Holo 1.0. Isto se traduz basicamente na impossibilidade de acompanhar, junto ao programa Holo, o fluxo de execução dentro de uma MLA. A MLA é enxergada, pelo módulo depurador de Holo, como uma ação atômica. Uma alternativa para resolver este problema, dada a implementação atual do Holocompilador 1.0, que possui como componente a ferramenta PrologCafe, seria a realização do mapeamento do código Java gerado pelo PrologCafe de volta para o Prolog original, e deste, de volta para o programa Holo. Porém, o código gerado pela ferramenta PrologCafe é bastante complexo. Seria necessário um certo entendimento do funcionamento interno da ferramenta. 48 Outra possibilidade seria a inclusão de um interpretador Prolog ao ambiente de execução do programa Holo traduzido. Isto também eliminaria o atual gargalo do Holocompilador 1.0, que é a utilização do conversor PrologCafe, que converte Prolog para Java. Este conversor é responsável por, no mínimo, 90% do tempo necessário para a tradução de Holo para Java, quando o programa Holo contém MLAs. Como desvantagem tem -se que a interpretação de uma linguagem na sua forma original é, geralmente, mais lenta do que a execução de uma forma pré-processada ou compilada. De qualquer forma, é necessário que o interpretador exponha uma API de depuração, para que o usuário do depurador Holo pudesse controlar a execução do programa Prolog de forma indireta, através da depuração do programa Holo, da mesma maneira que é feito atualmente com o mapeamento da depuração de Java para Holo. 4.2 Holo Debug Interface 4.2.1 Visão Geral A implementação da HDI consistiu no desenvolvimento, em Java, de uma hierarquia de interfaces, e sua posterior implementação em classes. A implementação da Holo Debug Interface (HDI) consiste atualmente de 33 classes, e de 36 interfaces para estas classes. As classes somam 4330 linhas de código, e as interfaces somam 1224 linhas, em um total de 5554 linhas. A implementação da HDI utiliza diretamente as interfaces e a implementação default da JDI (Java Debug Interface), ou seja, como provida pela plataforma J2SE da Sun, versão 1.3.1 e posteriores. 4.2.2 Limitações e possibilidades de expansão A versão atual das classes da HDI implementa suporte apenas para o início de uma sessão de depuração local, ou seja, a máquina disparada para executar o programa a ser depurador executa sob o mesmo ambiente do depurador. Poderia ser adicionado suporte para depuração remota, com o depurador iniciando a máquina depurada, ou o contrário. Também poderia ser possível que o depurador abarcasse (attach) uma máquina Holo em execução. A plataforma Java, através da JDI, atualmente provê esta funcionalidade. Seria necessária apenas a implementação das classes e interfaces da HDI para redirecionar este serviço para o Holodepurador. A HDI não oferece uma variedade de filtros para a configuração das requisições de eventos. O único filtro implementado foi o contador de ativação do evento, como exposto na seção 3.5.7. Isto pode ser contornado com realização de filtragem na própria ferramenta de interface de depuração, após esta receber o evento da HDI. Porém, seria interessante que a HDI oferecesse estes filtros para que fosse poupado o trabalho dos desenvolvedores das ferramentas de depuração Holo, além do ganho de performance pela eliminação prévia de eventos não interessantes, que podem gerar, entre outras formas de overhead, suspensões e continuações desnecessárias de várias threads do programa do usuário, devido às políticas de suspensão das requisições de eventos. 49 Outra limitação do protótipo implementado é a ausência de um conjunto de exceções adequado. Quando um erro ocorre internamente à implementação da HDI, geralmente os métodos abortam a execução com uma ou várias mensagens de diagnóstico na saída padrão, mas o programa não é interrompido, ou então um stack trace de Java, geralmente causado por uma exceção da JDI, é exibido para o usuário. Isto pode ser resolvido com a revisão dos pontos de falha e da substituição das mensagens de erro atuais por comandos (Java throw) que levantem exceções próprias da HDI. Em relação à linguagem Holo 1.0, a HDI não oferece todos os serviços de depuração necessários para a inspeção da execução de ações modulares lógicas (MLAs). Atualmente, as chamadas a MLAs apenas aparecem na pilha de ativação de uma thread com o nome da MLA, não sendo possível a execução de um "passo", a inserção de breakpoints, a identificação de valores de variáveis locais ou a localização do ponto de execução atual da thread, dentro do código de uma MLA. Isto depende primeiramente da geração, pela ferramenta HoloJava, de toda a informação de depuração necessária. Após, seria necessária a especificação dos serviços a serem oferecidos. No caso do serviço de "passo-a-passo", por exemplo, seria necessário definir uma semântica para o mesmo, quando executado em código Prolog. E, por fim, deve ser feita a implementação das classes que implementam os serviços. 4.3 Holo Debugger 4.3.1 Visão Geral O Holo Debugger consiste de 8 classes Java, e sua implementação mede 2013 linhas. A ferramenta teve sua interface desenvolvida com controles gráficos básicos do pacote Swing, de Java. As funcionalidades de depuração Holo foram todas desenvolvidas através da utilização das interfaces publicadas pela HDI, ou seja, o Holo Debugger é livre da implementação das interfaces da HDI. A tarefa principal da ferramenta é traduzir os comandos textuais do usuário em requisições para as interfaces da HDI. O Holo Debugger também agrega valor aos serviços oferecidos pela HDI, como a manutenção de uma lista de breakpoints requisitados pelo usuário, e também implementa a validação de breakpoints. 4.3.2 Limitações e possibilidades de expansão A maior limitação da ferramenta é, atualmente, a sua interface textual. Este tipo de interface tanto diminui a interatividade da sessão, por tornar a entrada de comandos mais lenta, quanto torna difícil a exposição, ao usuário, do contexto que ele necessita para analisar o seu programa. Num momento em que as próprias ferramentas e linguagens de programação mostram tendências a se tornarem cada vez mais "visuais", é significativo que as ferramentas de depuração continuem amparando esta tendência. A interface gráfica da ferramenta Holo Debugger já se encontra em desenvolvimento, no âmbito do projeto Holo [HOL 02]. 50 4.4 Desempenho A otimização do tempo de execução não foi uma preocupação principal durante a implementação deste trabalho. Porém, os algoritmos do depurador devem possuir um tempo de execução que evolui de forma satisfatória na medida em que cresce o tamanho do programa de entrada, pelo menos no que diz respeito ao seu modelo, e não à implementação atual dos algoritmos. Dito isto, afirma-se aqui que o Holo Debugger é um depurador suficientemente responsivo. Talvez seja possível que o usuário note uma pequena diferença entre executar um "passo" de execução no Holo Debugger, que utiliza o algoritmo de passo-a-passo implementado pela HDI, e executar o mesmo passo no jdb, o depurador da Sun que serve como exemplo da JDI. Esta conclusão é possível, uma vez que a HDI utiliza a JDI para sua implementação, ou seja, a HDI certamente introduz alguma forma de overhead sobre a JDI. Porém, para o autor deste trabalho, este overhead não pôde ser percebido, ao comparar o tempo gasto pelas atividades comuns entre o Holo Debugger e o jdb. Adicionalmente, observa-se que um programador tipicamente gasta muito mais tempo com outras atividades (entrar comandos no depurador e analisar os dados exibidos pelo mesmo, por exemplo) do que esperando pela execução do programa sob o depurador, se considerarmos apenas o tempo de overhead inserido pelo depurador. Neste contexto, o overhead inserido pelo Holo Debugger é similar ao do depurador jdb, da Sun. 51 5. Conclusão O depurador é uma ferramenta essencial no desenvolvimento de programas. Este trabalho se propôs a buscar uma visão inicial e abrangente para o problema da depuração de programas para a linguagem Holo, incluindo a identificação de questões como a da depuração de linguagens multiparadigma. Dento desta visão, este trabalho propôs uma extensão abstrata para a Holoplataforma, denominada de HED (Holo Extensão para Depuração) que identifica uma série de componentes que devem estar presentes na implementação concreta da plataforma para permitir a depuração de programas. A configuração proposta pela HED não é a única possível, porém, ela possui pelo menos duas vantagens: permite que o ambiente do depurador e o ambiente depurador sejam isolados, mantendo um vínculo através de um protocolo de comunicação, e divide a implementação em camadas, o que facilita o reuso do software do depurador. As mudanças na Holoplataforma, propostas pela HED, foram baseadas na arquitetura do depurador da plataforma Java, a JPDA (Java Platform Debugger Architecture), que ainda não faz parte da especificação padrão da plataforma Java, mas já apresenta resultados positivos, como constatado na sua fácil utilização para a implementação das ferramentas derivadas deste trabalho. Por fim, implementou-se, em Holo 1.0, as alterações propostas pela HED, através da reutilização de componentes da plataforma Java, e do desenvolvimento de três softwares adicionais: a extensão para a ferramenta HoloJava 1.0, a API de depuração HDI (Holo Debug Interface), e a ferramenta de depuração Holo Debugger. Estas ferramentas implementam a grande parte dos serviços identificados como essenciais para o depurador Holo 1.0, sendo que a principal limitação foi a ausência do suporte completo para a depuração das ações lógicas (Prolog). Esta tarefa é indicada como uma proposta de trabalho futuro, juntamente com um estudo mais aprofundado sobre depuração multiparadigma. Deste trabalho, pode-se concluir: • A plataforma Java oferece um suporte sem precedentes para o desenvolvimento de depuradores. Rosemberg, em [ROS 96], faz uma revisão do estado da arte das ferramentas de depuração, e descreve em detalhes as dificuldades de implementação de um depurador para uma linguagem como C/C++ e que precisa acessar diretamente um Sistema Operacional. Muitas das dificuldades apontadas pelo autor não são relevantes no desenvolvimento de depuradores Java. Este trabalho dificilmente possuiria a quantidade de implementação apresentada se a linguagem intermediária de Holo fosse, por exemplo, C++, ou se Holo compilasse diretamente para código nativo. • Este sucesso da plataforma Java foi o motivador da adoção do seu projeto como fonte de inspiração para o projeto da HED. A qualidade deste suporte oferecido deve ser levada em conta, quando do desenvolvimento completo da 52 Holoplataforma, incluindo implementações próprias do compilador e da máquina virtual Holo. • Um depurador é uma ferramenta de implementação crítica, que se beneficia, em vários aspectos, da sua modularização. Isto também foi observado por [BID 99], que realiza um estudo de caso de reutilização de software para a construção de uma ferramenta de depuração. • A implementação de um depurador de alto-nível (HDI), baseado em outro módulo depurador de alto-nível (JDI), não resulta, obrigatoriamente, em overhead significativo, como poderia ser considerado. Não foi verificado se a performance do depurador aumenta proporcionalmente ao tamanho e à complexidade do programa. Como possíveis trabalhos futuros, pode-se citar: • Manutenção do modelo e da implementação, acompanhando o desenvolvimento da linguagem e da plataforma Holo em suas versões futuras (2.0 e posteriores); • Quando for realizado o desenvolvimento do ambiente de execução distribuída de programas Holo (DHolo), será necessário o estudo de questões relativas à construção de depuradores distribuídos; • Estudo de técnicas de replay de execução, que permitam que uma execução do programa seja gravada, para que possa realizar várias sessões de depuração sobre a mesma seqüência de computações da linguagem, através da reconstrução da ordem de eventos que afetam o programa, como ordem de troca de mensagens, de execução de caminhos concorrentes, ou de entrada e saída de dados; • Estudo aprofundado e implementação completa do suporte, no sistema depurador, ao paradigma de programação em lógica; • Desenvolvimento de uma interface gráfica ao Holo Debugger, integrada ao ambiente de desenvolvimento HoloEnv (já em andamento). 53 Bibliografia [AMZ 02] Amzi! Prolog. Disponível por WWW em <http://www.amzi.com>. Acesso em abril de 2002. [APE 02] APPELO – Ambiente de Programação Paralela em Lógica. Disponível em: <http://www.inf.ufrgs.br/procpar/opera/APPELO/>. Acesso em abril de 2002. [BAR 99] Barbosa, J. L. V., Geyer, C. F. R. (1999) "Software Multiparadigma Distribuído", Revista de Informática Teórica e Aplicada (RITA), Porto Alegre, v.6 n.2 p.67-87. [BAR 01] Barbosa, Jorge Luis Victória; Geyer, Cláudio Fernando Resin. (2001) "Uma Linguagem Multiparadigma Orientada ao Desenvolvimento de Software Distribuído". V Simpósio Brasileiro de Linguagens de Programação (SBLP), Curitiba, Brasil. [BAR 01a] Barbosa, Jorge L. V.; Geyer, Cláudio F. R. (2001a) "Integrating Logic Blackboards and Multiple Paradigms for Distributed Software Development". In: International Conference on Parallel and Distributed Processing Techniques and Applications (PDPTA 2001), Las Vegas, United States. Proceedings… Las Vegas: CSREA Press, June 2001. p.808-814. [BAR 01b] Barbosa, J. L. V., Du Bois, A., Pavan, A.; Geyer, C. F. R. (2001b) "HoloJava: Translating a Distributed Multiparadigm Language into Java", In: Conferência Latino Americana de Informática, v.27, Mérida, Venezuela. Proceedings… Mérida: Universidad de Los Andes. [BAR 02] Barbosa, Jorge L. V., (2002) "Holoparadigma: Um Modelo Multiparadigma Orientado ao Desenvolvimento de Software Distribuído". Tese de doutorado. 213p. [BID 99] Biddle, R., Marshall, S., Miller-Williams, J., Tempero, E. (1999) "Reuse of Debuggers for Visualization of Reuse", In: Fifth Symposium on Software Reusability, Los Angeles, United States, Proceedings of… p.92-100. ACM Press. [BOR 02] Borland JBuilder 6 Personal. Disponível por WWW no endereço: <http://www.borland.com/products/jbuilder/personal>. Acesso em abril de 2002. [BYR 80] Byrd, L. (1980) "PROLOG Debugging Facilities", Technical Report, D.A.I. Research Paper 19, Department of Artificial Intelligence, University of Edinburgh. [CAP 96] Capra, Fritjof, (1996) "The Web of Life: A New Scientific Understanding of Living Systems", Anchor Books. [CIA 02] Ciancarini, P., Rossi, D. (2002) "JADA: A coordination toolkit for Java". Disponível em: <http://www.cs.unibo.it/~rossi/jada>. Acesso em abril de 2002. [DUB 01] Du Bois, André Rauber; Barbosa, Jorge Luis Victória; Geyer, Cláudio Fernando Resin. (2001) "Adding Functional Programming into the Holo Language". In: Functional and (Constraint) Logic Programming (WFLP), 10., Kiel, Germany. "Proceedings…" Kiel: Christian-Albrechts-Universität, September. p.45-58. 54 "Proceedings…" Kiel: Christian -Albrechts-Universität, September. p.45-58. [FRA 97] Frankl, Phyllis et al. (1997) "Choosing a testing method to deliver reliability", International Conference on Software Engineering. Proceedings... Boston, United States. ACM Press, p. 68-78. [GAR 95] Garlan, D. et al. (1995) "Research Directions in Software Engineering". ACM Computing Surveys, New York, v.27, n.2, p.257-276, June. [GDB 02] GDB: The GNU Project Debugger. Disponível por WWW <http://www.gnu.org/software/gdb/gdb.html>. Acesso em abril de 2002. [GHE 98] Ghezzi, C.; Jazayeri, M. (1998) "Programming Language Concepts", New York: John Wiley & Sons. 427p. [HOL 02] Projeto Holo - Software Multiparadigma Distribuído. Disponível por WWW em: <http://www.inf.ufrgs.br/~holo>. Acesso em abril de 2002. [IEE 95] IEEE Transactions on Software Engineering. New York, v.21, n.4, April 1995. (Special Issue on Software Architecture) [JAV 02] Java. Disponível em: <http://java.sun.com>. Acesso em abril de 2002. [JAV 02a] JavaCC – The Java Parser Generator. Disponível por WWW <http://www.webgain.com/products/java_cc/>. Acesso em abril de 2002. [JAV 02b] Javadoc Tool Home Page. Disponível em <http://java.sun.com/j2se/javadoc/>. Acesso em abril de 2002. [JDB 02] jdb. Disponível em <http://java.sun.com/products/jpda/doc/soljdb.html>. Acesso em abril de 2002. [JPD 02] Java Platform Debugger Architecture. Disponível por <http://java.sun.com/products/jpda>. Acesso em abril de 2002. [KOW 79] Kowalski, Robert. (1979) "Logic for Problem Solving". New York: Elsevier. [LEA 01] Lea, Doug. "Objects in Groups". Disponível por <http://gee.cs.oswego.edu/dl/groups/>. Acesso em: abril de 2002. [MEI 96] Meier, M. S., Miller, K. L., Pazel, D. P., Rao, J. R., Russell, J. R. (1996) "Experiences with building distributed debuggers", In: Symposium on Parallel and Distributed Tool. Proceedings… Philadelphia, United States. p.70 -79. ACM Press. [MIC 02] Microsoft Visual C++. Disponível por <http://www.microsoft.com>. Acesso em abril de 2002. [NER 97] Neri, D., Pautet, L., Tardieu, S. (1997) "Debugging distributed applications with replay capabilities", In: Annual International Conference on Ada. Proceedings… St. Louis, United States. [OAK 99] Oaks, S., Wong, H. (1999) "Java Threads", 2nd Edition, O'Reilly, 332 pages. [OPE 02] OPERA – Prolog Paralelo. Disponível em: <http://www.inf.ufrgs.br/ procpar/opera/OPERA/index.html/>. Acesso em abril de 2002. 55 WWW WWW WWW no em: em: em: em: endereço: [ORT 92] Ortega y Gasset, José. (1992) "Mision de la Universidad". Madri: Alianza Editorial. 238p. [PFL 97] Pfleger, Karl; Hayes-Roth, Barbara. (1997) "An Introduction to Blackboard-Style Systems Organization", Computer Science Department, Stanford University, July. (Technical report) [PRO 02] PrologCafe. Disponível por WWW u.ac.jp/PrologCafe/>. Acesso em abril de 2002. [ROB 92] Robinson, J. A. (1992) "Logic and Logic Programming". Communications of the ACM, New York, v.35, n.3, p.40-65, March. [ROS 96] Rosemberg, Jonathan B., (1996) "How Debuggers Work: Algorithms, Data Structures and Architecture", John Wiley & Sons. [SEB 99] Sebesta, Robert W. (1999) "Concepts of Wesley. 670p. [SHA 96] Shaw, M.; Garlan, D. "Software Architecture: Perspectives on an Emerging Discipline". New Jersey: Prentice-Hall, 1996. 242p. [SIC 02] SICStus Prolog. Disponível por WWW em <http://www.sics.se/sicstus>. Acesso em abril de 2002. [SOA 00] Soares, L. C. (2000) "Princípios de uma ferramenta CASE para o Holoparadigma", Pelotas: ESIN-UCPel, 60p. (Projeto de Diplomação). [TOB 93] Tobermann, G., Beckstein, C. (1993) "What's in a trace: The box model revisited", In: Proc. of the First Internat. Workshop on Automated and Algorithmic Debugging, Linkoeping, Sweden, v.749 of LNCS, Springer-Verlag. [TRI 02] Trinc-Prolog: Object-Oriented Prolog. Disponível por WWW em <http://www.trincprolog.com>. Acesso em abril de 2002. [VRA 95] Vranes, Sanja; Stanojevic, Mladen. (1995) "Integrating Multiple Paradigms within the Blackboard Framework". IEEE Transactions on Software Engineering, New Debugging York, v.21, n.3, p.244-262, March. [WAH 92] Wahl, N. J., Schach, S. R. (1992) "A paradigm for distributed debugging", In: ACM Annual Computer Science Conference, Kansas City, United States. Proceedings…, p.235-242, ACM Press. 56 em: <http://kaminari.scitec.kobe- Programming Languages". Addison Anexos Anexo 1 - Código Java gerado para o "Jantar dos Filósofos" As figuras 14 e 15 apresentam o código, gerado pela ferramenta HoloJava 1.0 estendida, para o programa Holo listado na figura 5. A listagem possui uma formatação um pouco desagradável, pois o código gerado não foi totalmente organizado para a leitura humana. Porém, dada a listagem do arquivo de informação de depuração gerado pela HoloJava (figura 11) para este mesmo programa, e a descrição do formato do arquivo (seção 3.3.2), é possível que o leitor realize o mesmo mapeamento, entre os dois programas (Holo e Java), realizado pelo módulo depurador de Holo. Duas classes são adicionadas ao programa pela HoloJava 1.0 estendida quando a ferramenta é iniciada com a opção de geração de informação de depuração. Estas classes são "HoloHistoryCollector" e "HoloTreeTracer". É possível encontrar referências a elas nas figuras 14 e 15. Estas classes auxiliam os algoritmos da HDI, de leitura da história de um ente, e de detecção de ocorrência de mobilidade lógica, respectivamente. A listagem destas classes não é incluída aqui, mas pode ser obtida a partir da utilização da ferramenta HoloJava sobre qualquer programa Holo, pois o conteúdo das classes é independente do programa que é traduzido. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import jada.*; import holoj.lang.*; public class holo extends Being{ public static holo _holoroot; public static HoloHistoryCollector _hdi_history_collector_thread; holo(){ this.father=null; this.son=new DBVector(); this.name="holo"; setName(this.name+"-thread"); history = new ObjectSpace(); } static String[] args; // reference to command-line arguments public static void main (String argumentos[]){ holo.args = argumentos; // save reference to command-line arguments // thread do depurador - coletora de histórias de entes _hdi_history_collector_thread = new HoloHistoryCollector(); _hdi_history_collector_thread.setDaemon(true); 57 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 _hdi_history_collector_thread.start(); HoloTreeTracer.tracer.changeStart(); _holoroot = new holo(); HoloTreeTracer.tracer.changeEnd(); _holoroot.start(); } // fim main //inicio run public void run() { Being holo_temp = null; Tuple tuple = null; insert_history(); System.out.println("CINCO FILOSOFOS VAO JANTAR."); System.out.println("CADA FILOSOFO COME CINCO VEZES."); System.out.println("O JANTAR ESTA INICIANDO....."); String X = ""; for (int X_integer = 1;X_integer<=5;X_integer++){ X =""+X_integer; HoloTreeTracer.tracer.changeStart(); holo_temp = new filosofo(this,"varname_0",X); this.son.addElement(holo_temp); HoloTreeTracer.tracer.changeEnd(); holo_temp.start(); holo_temp=null; } for (int X_integer = 1;X_integer<=5;X_integer++){ X =""+X_integer; /*HISTORY 1*/ synchronized (history){ tuple = (Tuple) history.in(new Tuple("end")); } } System.out.println("O JANTAR ACABOU."); } public void history.out history.out history.out insert_history() { (new Tuple ("chopstick","1")); (new Tuple ("chopstick","2")); (new Tuple ("chopstick","3")); 58 79 80 81 82 83 84 85 86 87 88 89 90 91 92 } history.out history.out history.out history.out history.out history.out history.out history.out history.out history.out history.out history.out } (new (new (new (new (new (new (new (new (new (new (new (new Tuple Tuple Tuple Tuple Tuple Tuple Tuple Tuple Tuple Tuple Tuple Tuple ("chopstick","4")); ("chopstick ","5")); ("ticket")); ("ticket")); ("ticket")); ("ticket")); ("ticket")); ("seat","1","2")); ("seat","2","3")); ("seat","3","4")); ("seat","4","5")); ("seat","5","1")); Figura 14 – Programa Java, classe “holo”, que implementa o en te “holo” do programa Holo que simula o “Jantar dos Filósofos”. 1 import jada.*; 2 import holoj.lang.*; 3 4 public class filosofo extends Being { 5 6 7 public String Ident; 8 9 filosofo(Being father,String name, String Ident){ 10 this.Ident=Ident; 11 this.father=father; 12 this.son=new DBVector(); 13 this.name=name; 14 setName(this.name+"-thread"); 15 history = new ObjectSpace(); 16 } 17 18 public void run(){ 19 insert_history(); 20 Tuple tuple=null; 21 Being holo_temp = null; Syste m.out.println("Fisolofo jantando: "+Ident); 22 String Cont = ""; 23 for (int Cont_integer = 1;Cont_integer<=5;Cont_integer++){ 24 Cont =""+Cont_integer; 25 synchronized (father.history) {tuple = (Tuple)father.history.in(new Tuple("ticket")); 26 27 } 28 synchronized (father.history) {tuple = (Tuple)father.history.in(new Tuple("seat",new String().getClass(),new String().getClass()));/*RPAREN2*/ 29 30 } 31 //Position: 3 -- counthist:2 59 32 String F1 = (String)tuple.getItem(1); 33 String F2 = (String)tuple.getItem(2); 34 synchronized (father.history) {tuple = (Tuple)father.history.in(new Tuple("chopstick",new String().getClass()));/*RPAREN2*/ 35 36 } 37 //Position: 2 -- counthist:1 38 F1 = (String)tuple.getItem(1); 39 synchronized (father.history) {tuple = (Tuple)father.history.in(new Tuple("chopstick",new String().getClass()));/*RPAREN2*/ 40 41 } 42 //Position: 2 -- counthist:1 43 F2 = (String)tuple.getItem(1); 44 String Atraso = ""; 45 for (int Atraso_integer = 1;Atraso_integer<=1000;Atraso_integer++ ){ 46 Atraso =""+Atraso_integer; 47 48 } 49 synchronized (father.history) {father.history.out (new Tuple ("chopstick",F2));/*RPAREN2*/ 50 51 } 52 //Position: 2 -- counthist:0 53 synchronized (father.history) {father.history.out (new Tuple ("chopstick",F1));/*RPAREN2*/ 54 55 } 56 //Position: 2 -- counthist:0 57 synchronized (father.history) {father.history.out (new Tuple ("seat",F1,F2));/*RPAREN2*/ 58 59 } 60 //Position: 3 -- counthist:0 61 synchronized (father.history) {father.history.out (new Tuple ("ticket"));/*OFINAL2*/ 62 63 } 64 System.out.println("Comendo "+Ident+" - "+Cont); 65 66 } 67 synchronized (father.history) {father.history.out (new Tuple ("end"));/*OFINAL2*/ 68 69 } 70 } 71 public void insert_history() { 72 } 73 } Figura 15 – Programa Java, classe "filosofo", que implementa o ente "filosofo" do programa Holo que simula o "Jantar dos Filósofos”. 60 Anexo 2 - Documentação (Javadoc) da HDI Este anexo relaciona a documentação das principais interfaces Java publicadas pela Holo Debug Interface. Esta documentação foi gerada pela ferramenta Javadoc [JAV 02b]. A versão completa desta documentação pode ser obtida por WWW através do endereço <http://www.inf.ufrgs.br/~fcecin/holo/docs/index.html>. 61