FAVORITAR
FecharPlease login

NuttX: Usando o Simulador para Testes e Debugging de Aplicações

Este artigo faz parte da série Primeiros Passos com o ESP32 e NuttX e mostra como o simulador do NuttX é uma importante ferramenta de apoio para o desenvolvimento, depuração e validação de aplicações no NuttX. Para tal, este artigo faz referência ao conteúdo do artigo NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX e aborda as etapas subsequentes para desenvolver um projeto usando o NuttX a partir de uma aplicação já existente.

O artigo NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX termina com um teste de build do NuttX que inclui a aplicação RTP Tools que foi portada para o NuttX. Para tal, bastaria executar:

Obviamente, este teste funcionaria para qualquer usuário que executasse esses comandos hoje, no NuttX, mas não foi tão simples assim quando o RTP Tools foi portado para o NuttX. Veremos, neste artigo, as alterações necessárias no sistema operacional que possibilitaram o uso do RTP Tools como pensado para a construção da aplicação final.

Obviamente, este teste funcionaria para qualquer usuário que executasse esses comandos hoje, no NuttX, mas não foi tão simples assim quando o RTP Tools foi portado para o NuttX. Veremos, neste artigo, as alterações necessárias no sistema operacional que possibilitaram o uso do RTP Tools como pensado para a construção da aplicação final.

No dia 25 de Junho de 2024, ocorrerá o “Seminário de Sistemas Embarcados e IoT 2024“, no Holiday Inn Anhembi — Parque Anhembi, São Paulo–SP.

Garanta seu ingresso

A Jornada do Desenvolvimento

Este artigo vai começar a explorar o uso do RTP Tools em uma aplicação real com o NuttX. Esta aplicação – uma placa de som externa que recebe o áudio através da rede – poderia integrar, facilmente, um produto.

Antes de propriamente explorar este desenvolvimento, precisaremos entender como o RTP Tools fornece um meio de recebimento de pacotes de áudio pela rede e, também, como estes pacotes podem ser enviados a partir de aplicações disponíveis em um computador pessoal. Para tal, vamos utilizar o Linux como ponto de partida para compilar o RTP Tools e, também, para mostrar como o redirecionamento do áudio pode ser realizado para a rede (calma, isso tudo pode ser feito com o Windows também!). A seguir, será explorada a execução do RTP Tools no NuttX e como o simulador é capaz de emular a aplicação que recebe pacotes de áudio pela rede.

Ainda utilizando o simulador do NuttX, veremos como depurar ( ou “debugar”) uma aplicação com o GDB (GNU Debugger) para encontrar erros de execução. Finalmente, será mostrado técnicas de depuração que nos permitem chegar a raíz de um problema e, assim, resolvê-lo. Vamos lá?

Compilando o RTP Tools no Linux

De modo a “aprender” um pouco mais sobre o RTP Tools e suas aplicações e, para tal, iremos compilá-lo novamente para o Linux (já o fizemos no artigo anterior NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX). Na pasta do repositório do RTP Tools:

Estes comandos irão, respectivamente, configurar o RTP Tools para compilação, compilá-lo e instalá-lo no Linux. Uma vez instalado, podemos verificar o funcionamento das aplicações do RTP Tools como, por exemplo, do rtpdump. Para tal, basta executar man rtpdump.

O rtpdump

De acordo com a descrição do manual do rtpdump:

rtpdump reads RTP and RTCP packets on the given address/port, or from standard input by default, and dumps a processed version to standard output.  The output is suitable as input for rtpplay(1) and rtpsend(1).  The IPv4 address can be specified in dotted decimal notation or as a hostname and defaults to “localhost”.  The port number must be an even number.

Embora ainda não tenha sido falado sobre a motivação para o projeto que necessita do RTP Tools (e, em particular, do rtpdump), esta será a aplicação responsável por receber pacotes RTP pela rede. Estes pacotes, por sua vez, podem ser gerados pelo pulseaudio, que é um servidor de som para sistemas operacionais POSIX.

Transmitindo Pacotes RTP

Para testar o funcionamento do rtpdump no Linux, precisamos, primeiramente transmitir pacotes RTP (Real-time Transport Protocol) que, basicamente, é um protocolo de rede para entrega de pacotes de áudio, como Voz Sobre IP. Para este artigo, iremos explorar duas opções para gerar streams RTP:

PulseAudio

O PulseAudio é, geralmente, a solução padrão para o gerenciamento de áudio em diversos sistemas operacionais Linux. É, por exemplo, a solução padrão para o Ubuntu. O PulseAudio permite redirecionar o áudio do computador para um stream RTP. Para tal, abra um terminal e execute os seguintes comandos:

Brevemente, o primeiro comando cria um sink do tipo null do PulseAudio e o segundo usa o monitor deste sink para enviar pacotes RTP para um endereço de rede, no caso o próprio computador.

pavucontrol

Uma aplicação bastante simples para configurar os sources e sinks, além das placas de som disponíveis no sistema, no PulseAudio é o pavucontrol. Procure-o no gerenciador de pacotes de sua distribuição!

Ao executar os comandos acima para criar um sink do tipo null, podemos verificar, na aba Output Devices:

O ícone ao lado do sink permite selecioná-lo como a saída de áudio padrão do sistema. A aba Playback permite selecionar, para cada fonte de áudio – como o browser, por exemplo – a saída de áudio utilizada:

Observe que, neste exemplo, o áudio do Google Chrome está sendo enviado ao RTP_sink que, por sua vez, encaminha os dados através de pacotes RTP pela rede.

VLC

Para transmitir áudio através da rede usando o protocolo RTP pelo VLC, selecione – no menu superio – Media -> Stream… e, na tela que será aberta, na aba File, clique em + Add e selecione o arquivo de áudio a ser transmitido (preferencialmente, um arquivo não comprimido no formato WAV). A seguir, clique em Stream mais abaixo na tela. Selecione Next e, em Destination Setup, selecione a opção RTP Audio/Video Profile na caixa de seleção New destination. Clique em Add. A tela que será aberta permite configurar o IP de destino que pode ser, por exemplo, 127.0.0.1. A porta, 46998. Clique em Next. Na tela Transcoding Options, desmarque a caixa Activate Transcoding e clique, novamente, em Next. Finalmente, clique em Stream para iniciar a transmissão.

Recebendo Pacotes RTP

Após configurar a transmissão de pacotes RTP, podemos executar a aplicação do RTP Tools que é capaz de receber estes pacotes da rede. Esta aplicação é o rtpdump. Para testá-lo, ainda no Linux:

O comando rtpdump -F ascii /46998 inicia o rtpdump no modo ascii, que imprime na tela algumas informações dos pacotes, e recebe os pacotes da porta 46998 (a mesma que foi configurada para enviar os pacotes RTP). O entendimento do funcionamento do RTP Tools (especificamente, o rtpdump) é fundamental para que possamos avaliar sua performance ao compilamos a mesma aplicação para o NuttX.

Verificando o Envio de Pacotes RTP pela Rede

De forma a verificar o envio de pacotes RTP pelo computador, podemos usar a aplicação Wireshark, que é capaz de monitorar pacotes de rede que trafegam pelas interfaces de rede do computador. Podemos, então, selecionar todas as interfaces do computador e filtrar para observarmos pacotes rtp. A imagem a seguir mostra os pacotes RTP sendo enviados pela rede (no caso, o destino é o próprio computador, através do endereço 127.0.0.1):

Compilando o RTP Tools pela Primeira Vez no NuttX

Considerando que no artigo anterior desta série o RTP Tools foi portado para o NuttX, vamos tentar agora compilá-lo através de uma configuração pré-existente que utiliza o simulador do NuttX como base.

Voltando no Tempo

Vamos considerar o repositório do NuttX tal como ele era quando portamos o NuttX pela primeira vez de modo que seja possível simular todas as etapas necessárias para ter o RTP Tools funcionando com sucesso. O NuttX, àquele momento, tinha sua branch master apontando ao commit f48693eaf5d3cec9161b796be91c7d6d24f09e91. Na pasta nuttx (referente ao repositório do sistema operacional e não ao repositório de aplicações), execute:

Como ainda não temos, neste repositório, uma defconfig específico para o RTP Tools, vamos partir do defconfig sim:tcpblaster e selecionar a aplicação rtpdump tal como fizemos na seção “1, 2, 3, Testando…” de NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX: Replicando o procedimento já descrito anteriormente:

Pelo utilitário do menuconfig, navegue até: Application Configuration → Network Utilities. Nesta tela, selecione a opção Enable RTP Tools. Nas opções que serão descritas, entre em RTP Tools Applications  ---> e selecione a opção Enable rtpdump para compilar a aplicação rtpdump do RTP Tools. Deixe as demais configurações com os valores padrão e saia do utilitário salvando as alterações realizadas.

Problemas durante o Build?

Uma vez selecionada a aplicação do RTP Tools através do make menuconfig, vamos tentar compilá-la pela primeira vez:

O resultado da execução deste comando será algo parecido com:

E, infelizmente, tivemos problemas durante a compilação :/ Vamos analisar em detalhes o problema:

Estas linhas indicam o erro: o arquivo rtptools/notify.c utiliza a macro NSIG que, aparentemente, não está sendo definida pelo NuttX. Vamos procurá-la nos arquivos do NuttX:

O arquivo de cabeçalho include/signal.h define a macro _NSIG (com o caractere _ na frente), muito similar à macro NSIG. Uma rápida pesquisa na internet mostra que este macro é definida também em vários arquivos de cabeçalho signal.h do Linux como, por exemplo, em arch/arm/include/uapi/asm/signal.h. Especificamente, o arquivo arch/sparc/include/uapi/asm/signal.h a define como:

Como o NuttX já define _NSIG, podemos definir NSIG a partir de _NSIG incluindo, em include/signal.h

Feito isso, vamos tentar compilar, novamente o NuttX com o RTP Tools:

E temos, finalmente, o RTP Tools compilado para o NuttX! Precisaremos, é claro, submeter esta simples alteração referente ao NSIG para o NuttX (mas isso fica para o pŕoximo artigo).

Usando o Simulador do NuttX

O simulador do NuttX é uma ferramenta importantíssima de apoio ao desenvolvimento de aplicações e projeto utilizando o NuttX, eliminando, por vezes, a necessidade de ter um hardware real para o desenvolvimento de aplicações e facilitando o processo de depuração destas. Seu uso, entretanto, não é tão bem documentado além da própria documentação oficial do NuttX e, por isso, exploraremos melhor seu uso neste artigo com um exemplo de aplicação prática!

Uma Breve Introdução

O Simulador do NuttX, para todos os efeitos, é apresentado como uma placa (board) dentre as muitas placas suportadas. Da mesma forma, é vista como uma arquitetura (arch) única. Ou seja, ao invés de selecionar um hardware real para compilar o NuttX, podemos selecionar o simulador e compilá-lo da mesma forma. O arquivo de saída da compilação, ao invés de um firmware a ser gravado em uma placa, será uma aplicação que pode ser executada como um programa regular no Linux, Windows ou MacOS.

Considerando-se a documentação do NuttX, temos algumas páginas que documentam o simulador:

Vale destacar também alguns aspectos do simulador:

  • Ele não é uma máquina virtual, é tão somente uma aplicação que implementa uma versão do NuttX que roda em uma thread do sistema operacional do host;
    • Considerando um ambiente de simulação de uma plataforma multi-core, mais de uma thread é utilizada para tal;
  • É uma versão de “baixa fidelidade” ao sistema operacional do NuttX. Por exemplo, não existe suporte a qualquer evento assíncrono como interrupções e, consequentemente, preempção;
  • O simulador implementa, entretanto, um esquema de interrupções “fake” para a realização de troca de contexto, permitindo a simulação de aplicações do NuttX;
  • Considerando o suporte à emulação de diferentes periféricos e sub-sistemas do NuttX, a maioria dos recursos emulados estão disponíveis para o Linux (sim, é recomendável executar o simulador no Linux);

Como, essencialmente, é uma aplicação do espaço do usuário tal como outras aplicações para Linux, é possível utilizarmos as ferramentas de debugging/depuração do Linux ao executar a aplicação e, portanto, aqui se apresenta a grande vantagem do simulador do NuttX: é uma ferramenta extremamente útil para depurar aplicações do NuttX, principalmente aquelas que não dependem especificamente de driver de dispositivos como, por exemplo, aplicações baseadas em TCP/IP, servidores web etc.

Executando o Simulador

Uma vez que tenhamos compilado com sucesso o NuttX com o rtpdump, do RTP Tools, a partir da defconfig sim:tcpblaster, podemos executá-lo com ./nuttx:

Ao executar, no terminal do NuttX, o comando help:

E, então, lá temos o rtpdump. Antes de prosseguirmos com a execução deste comando, vamos verificar a capacidade do simulador de acessar a rede do computador host e, assim, interagir com a rede local e, até mesmo, de acessar a internet.

Acessando a Rede Usando o Simulador

A seção Accessing the Network do guia do Simulador mostra as etapas para acessar os recursos de rede do computador host através do simulador. Antes de executarmos os passos descritos, vamos sair do simulador. Para tal:

Agora, podemos executar os seguintes comandos no terminal:

Este comando configuração as permissões para a aplicação do simulador do NuttX acessar a rede do computador. A seguir, executamos novamente o simulador com o comando ./nuttx e, dentro do simulador:

A seguir, em outro terminal do computador, precisamos identificar uma interface de rede a ser “conectada” ao simulador do NuttX. Para tal, execute:

Neste caso, a interface wlp0s20f3 é a interface de rede principal do sistema: observe a presença de um endereço de IP correspondente a uma rede local (192.168.1.209). Finalmente, vamos executar (ainda no terminal do computador host):

Após a execução do comando, checamos as interfaces do computador host. Dentre as demais interfaces do computador, devemos enxergar a interface nuttx0, por exemplo:

Podemos checar, então, se o simulador é capaz de acessar a rede do computador host: no terminal correspondente ao simulador, executamos:

Ainda no simulador, podemos executar o comando ifconfig para verificar as interfaces de rede criados pelo simulador:

Observe que o IP do simulador é 10.0.1.2. Precisaremos desta informação mais adiante.

RTP Tools no Simulador

É, enfim, chegada a hora de testar o rtpdump no NuttX. Certificando-se que 1) o simulador está com acesso à rede do computador e 2) que pacotes RTP estão sendo enviados para o endereço do simulador. Refira-se à seção Transmitindo Pacotes RTP para configurar a transmissão de pacotes RTP ao endereço do simulador: 10.0.1.2.

Verificando o Envio de Pacotes ao Endereço do Simulador

Usando o software Wireshark, verificamos o envio de pacotes RTP ao endereço do simulador (10.0.1.2):

Executando o rtpdump no Simulador

Finalmente, podemos executar o rtpdump no simulador:

Como podemos observar – e tal como no Linux – o rtpdump é capaz de receber pacotes RTP através do simulador do NuttX e, assim, acabamos de validar o port da aplicação para o NuttX!

Criando uma Configuração com o RTP Tools

Antes de prosseguirmos, vamos salvar a configuração que temos até o momento (ou seja, baseada na defconfig tcpblaster + RTP Tools). Para tal, execute no terminal do computador host, na pasta do repositório do NuttX:

O comando acima cria um arquivo, na raíz do repositório, denominado defconfig que contém as configurações selecionados pelo sistema de build do NuttX. Salvando-se este arquivo, podemos recarregá-lo sempre que quisermos selecionar os mesmos pacotes para serem compilados. Vamos salvá-lo, por exemplo, na pasta de configuração do simulador:

Desta forma, sempre que quisermos recarregar esta configuração, basta executar ./tools/configure.sh sim:rtptools (sim, foi o que fizemos no primeiro artigo da série).

Áudio no Simulador?

A demonstração anterior validou o funcionamento da aplicação rtpdump, do RTP Tools, para o NuttX. Porém, a forma que usamos o rtpdump – com o parâmetro -F ascii – tem pouca serventia em aplicações reais, sendo mais útil como validação da aplicação. Um exemplo de aplicação real, entretanto, seria extrair os dados de áudio – em formato “bruto” – enviados pelos pacotes RTP. Estes dados podem ser, então, redirecionados para o subsistema de áudio do NuttX.

Fica, entretanto, a pergunta: como testar esta aplicação de áudio usando o simulador? Ok, podemos receber pacotes RTP – contendo dados de áudio – mas como testar o redirecionamento destes pacotes para o subsistema de áudio do NuttX usando o simulador, sem a necessidade de utilizarmos uma placa de desenvolvimento com suportes à codecs de áudio (que, talvez, seja o objetivo final)?


Felizmente, o simulador do NuttX permite testar o subsistema de áudio através de uma ponte entre este e o sistemas operacionais hosts com suporte à ALSA (Advanced Linux Sound Architecture) que, como sugere o nome, é utilizado em sistemas Linux. Para testarmos o subsistema, isoladamente, vamos carregar a defconfig relacionada, executando os seguintes comandos na pasta do repositório do NuttX:

E, então, compilamos o simulador com esta configuração do simulador que é capaz de testar o subsistema de áudio do NuttX:

A seguir, podemos executar o simulador tal como anteriormente:

Para testar o subsistema de áudio do NuttX com o simulador precisaremos, antes, de um arquivo de áudio não comprimido. Precisaremos saber, também, a taxa de amostragem, o número de canais e quantos bits/sample foram usados para a amostragem do áudio. O site freewavesamples.com fornece algumas amostras de áudio grátis e fornece, também, as informações sobre a amostragem. Baixe um destes exemplos e salve em uma pasta de seu computador. A configuração sim:alsa usa também o hostfs, que permite que uma pasta do computador host seja “montada” no simulador e que, assim, possamos acessar arquivos do sistema host no simulador. Vamos carregar a pasta que contém o arquivo de áudio que queremos testar no simulador:

A seguir, vamos utilizar o nxplayer – o player de áudio do NuttX – para 1) selecionar o dispositivo de áudio a ser utilizado – no caso, o que faz a ponte com o sistema host através do ALSA – e 2) executar o arquivo de áudio do sistema host, que será acessado através do simulador:

O device /dev/audio/pcm0p representa a placa de áudio para reprodução de áudio não comprimido (PCM). A seguir, selecionamos a reprodução do arquivo montado em /host/ através do comando playraw que, além do caminho do arquivo no simulador, recebe como parâmetro o número de canais (2), a quantidade de bits/sample (16) e a taxa de amostragem (44100). A partir do momento que executarmos este último comando, devemos ouvir, no sistema host, a reprodução do arquivo de áudio. Usando o pavucontrol, por exemplo, vemos na aba Playback:

A entrada denominada ALSA plug-in [nuttx], que está sendo reproduzida na placa de áudio padrão (Built-in Audio Analog Stereo), representa o áudio sendo reproduzido através do simulador do NuttX.

Feita esta introdução das capacidades do simulador do NuttX, partimos para um exemplo de aplicação que usa o RTP Tools e o subsistema de áudio do NuttX!

Debugging com o Simulador

Sabemos, até então, que o RTP Tools é capaz de receber pacotes de áudio transmitidos por pacotes RTP. De outro lado, sabemos que o NuttX possui um subsistema de áudio que é capaz de reproduzir arquivos de áudio não comprimidos. Como poderíamos, então, receber pacotes de áudio RTP e encaminhá-los para o subsistema de áudio do NuttX e termos, finalmente, um exemplo de aplicação real para o RTP Tools? E como testar tudo isso pelo simulador do NuttX?

Para encaminharmos os pacotes de áudio recebidos por RTP ao subsistema de áudio do NuttX (utilizando, por exemplo, o nxplayer), podemos usar um arquivo especial criado através do comando mkfifo. Este comando, que não é exclusivo do NuttX, faz parte do padrão POSIX e cria um arquivo que permite que dados sejam escritos e lidos de uma forma especial denominado FIFO (First-In First-Out). Uma FIFO é, basicamente, uma fila em que os dados escritos há mais tempo serão lidos primeiro. Este tipo de estrutura permite, por exemplo, a utilização de um arquivo FIFO para a troca de dados entre aplicações distintas, com uma aplicação escrevendo no arquivo e outra aplicação lendo dele.

A aplicação real será, então, uma “ponte” entre o rtpdump e o nxplayer. Respectivamente, o rtpdump receberá os pacotes RTP e escreverá os dados, em formato binário, em um arquivo FIFO. Este mesmo arquivo FIFO será aberto no nxplayer, que é a aplicação que permite reproduzir áudio no NuttX.


Partindo-se da defconfig que criamos (rtptools), vamos adicionar os pacotes necessários para acessar o subsistema de áudio do NuttX com o simulador.

Primeiro, vamos carregar o defconfig do RTP Tools

A seguir, criamos um arquivo que compara as diferenças entre as configurações alsa e rtptools e gera uma lista das configurações que existem no primeiro (e não no segundo):

A seguir, adicionaremos estas diferenças às configurações do rtptools:

Finalmente, compilamos novamente o simulador com o comando make.

Teste da Aplicação Real

Antes de executar o simulador, vamos preparar o computador host para 1) acessar a rede do simulador e 2) enviar os pacotes RTP para o simulador (utilizaremos o PulseAudio, no exemplo):

A seguir, executando-se o simulador com o comando ./nuttx, iremos 1) ligar a conexão de rede que permite a comunicação com o computador host e criar um arquivo especial FIFO chamado temp:

Agora, no computador host, vamos ligar a rede do simulador à interface de rede do computador, tal como em Acessando a Rede Usando o Simulador:

Podemos verificar, agora, que o simulador está recebendo os pacotes RTP do computador host:

Uma vez validado o recebimento dos pacotes, podemos usar o comando CTRL+C para parar a execução do rtpdump. Agora, vamos executá-lo novamente gravando os dados (em formato binário) no arquivo FIFO que criamos anteriormente:

Houston, We Have a Problem!

Aparentemente, tivemos algum erro ao executar o rtpdump. Para confirmar, vamos executar o comando ps no simulador para verificar se, realmente, a aplicação não está sendo executada:

Realmente, o rtptump não está sendo executado como esperávamos. A partir deste ponto, precisamos entender o que acontece com a nossa aplicação. Existem, é claro, muitos métodos para depurar a aplicação. Poderíamos, por exemplo, ligar os logs no make menuconfig para obter maiores informações. Vamos, no entanto, usar o GDB para depurar a aplicação e tentar entender a origem do erro reportado.

Usando o GDB com o Simulador

O Simulador do NuttX permite não só avaliarmos as aplicações no NuttX sem a necessidade de um hardware real, mas também permite que executemos o GDB – a famosa ferramenta de depuração do GNU – para avaliar o código executa pelo simulador. Uma vez que o simulador do NuttX é executado como uma aplicação no sistema operacional host, o GDB pode ser utilizado tal como. O Nuttx provê scripts que permitem ao GDB entender o simulador como uma aplicação “standalone” (e não como uma aplicação qualquer do sistema operacional host). O documento Debugging with gdb, da documentação oficial, reporta a utilização do GDB para depuração do NuttX.

Vamos executar, então, o simulador do NuttX com o gdb e tentar entender o que está acontecendo com nossa aplicação. Para tal, execute no computador host:

O comando acima executa o GDB e acessa seu console interativo. A aplicação do simulador do NuttX ainda não está sendo executada. Antes de executá-la, vamos adicionar um breakpoint na aplicação do rtpdump. Lembramos, agora, que o ponto de entrada de cada aplicação no NuttX é a função main() e, portanto, a execução do rtpdump se inicia através da função rtpdump_main. No console do GDB, executamos:

A seguir, vamos executar a aplicação do simulador com o gdb. Para tal, usamos o comando run:

Observe que, agora, temos acesso ao terminal do NSH tal como quando executamos o simulador do NuttX anteriormente. Vamos, novamente, ligar a interface de rede, criar o arquivo FIFO e executar o rtpdump até que nosso breakpoint pause a execução do programa:

Observe que o breakpoint foi atingido assim que executamos o comando rtpdump -F payload -o temp /46998 &: é a partir deste ponto que depuraremos a aplicação até descobrirmos a origem de nosso problema. O log da aplicação (de quando executamos o simulador ainda sem o GDB) nos mostrou que o erro aconteceu na função fwrite. Vamos tentar adicionar um breakpoint nesta função e continuar a execução, então:

A execução é pausada, novamente, ao atingirmos o breakpoint em fwrite. Podemos verificar, por exemplo, se esta função doi chamada pelo rtpdump através do comando bt:

O comando bt (backtrace) mostra as chamadas de funções que foram efetuadas antes de chegarmos ao ponto atual de execução do programa e, como podemos observar, a função packet_handler do rtpdump precedeu a chamada da função fwrite. A depuração, agora, deve seguir linha a linha até encontrarmos o ponto de falha que retorna o erro observado. Para executar o programa linha a linha, podemos usar o comando next (ou n) no GDB. Será necessário, também, entrar em funções aninhadas com o comando step (ou s) para identificar a origem do problema com a função fwrite.

Abordar em detalhes as técnicas de debugging foge um pouco do escopo deste artigo (deixe nos comentários, no entanto, se vocês querem que eu fale mais sobre isso em outro artigo). Vou pular algumas etapas de next e step que nos permitiu chegar até a função pipecommon_write. Vou deixar, porém, o backtrace até chegarmos a este ponto:

Ao executarmos, linha a linha, a função pipecommon_write, chegamos até o ponto retratado abaixo:

Observe que, neste ponto, o valor da variável nwritten (verificado através do comando p nwritten) é igual a 0 e, segundo o código fonte, se o valor for 0, será retornado o valor -EPIPE que, por sinal (como definido em include/errno.h é igual a -32). Encontramos, portanto, o local onde a função fwrite falha. Felizmente, o próprio código fonte contém um comentário que afirma que:


“REVISIT:  “If all file descriptors referring to the read end of a pipe have been closed, then a write will cause a SIGPIPE signal to be generated for the calling process.  If the calling process is ignoring this signal, then write(2) fails with the error EPIPE.”

Ou seja, quando não há leitor de um arquivo FIFO (ou pipe, como é aqui chamado), uma tentativa de escrita falhará com o erro EPIPE. Precisamos saber, entretanto, se este é o comportamento esperado de um arquivo FIFO segundo o padrão POSIX.

E a Compatibilidade com o Padrão POSIX?

Neste ponto, precisamos de um pouco de análise estática do código do rtpdump para entender o porquê da função fwrite estar falhando. Através do processo de debugging, notamos que fwrite chama a função lib_fwrite que, por sua vez, chama a função write, que é definida em vfs/fs_write.c. A função write() é uma das funções definidades pelo padrão POSIX. Ao consultar a documentação referente a esta função na definição do padrão, notamos que um dos código de retorno é, justamente, o EPIPE:

[EPIPE]

An attempt is made to write to a pipe or FIFO that is not open for reading by any process, or that only has one end open. A SIGPIPE signal shall also be sent to the thread.

Confirmamos, assim, que o comportamento da função é tal como o esperado pela definição POSIX.

Se o erro não está relacionado ao comportamento da função fwrite, onde estaria?
Bom, analisando o código do rtpdump, antes de escrever no arquivo FIFO, a aplicação precisa abri-lo para escrita. Isto é feito em rtpdump.c:

Ao adicionarmos breakpoints na função fopen, podemos acompanhar a chamada de funções que acontecem a partir deste ponto:

A função fopen chama a função lib_mode2oflags com os argumentos wb (confirme indicado na chamada de fopen em rtpdump.c). Esta função seleciona as flags (que serão usadas, mais adiante, na função open()) O_WROK, O_CREAT O_TRUNC:

Adentramos, com o comando step do GDB, na função open que, seguindo-se o fluxo de chamada de funções aninhadas, chama a função pipecommon_open a partir de file_vopen:

A função pipecommon_open é, de fato, a função que abre o arquivo especial FIFO. Podemos verificar que ela é executada sem erros, retornando à função file_vopen.

Esse comportamento, no entanto, é o esperado segundo o padrão POSIX?

Consultamos, então, a definição do padrão para a função open() que, em certo momento, define o comportamento quando a função tenta abrir uma FIFO:
When opening a FIFO with O_RDONLY or O_WRONLY set:

If O_NONBLOCK is set, an open() for reading-only shall return without delay. An open() for writing-only shall return an error if no process currently has the file open for reading.

If O_NONBLOCK is clear, an open() for reading-only shall block the calling thread until a thread opens the file for writing. An open() for writing-only shall block the calling thread until a thread opens the file for reading.

Recordamo-nos, novamente, das flags utilizadas para abrir a FIFO pelo rtpdump: O_WROK, O_CREATO_TRUNC foram selecionadas. A flag O_WROK é equivalente (de acordo com o arquivo de cabeçalho nuttx/include/fcntl.h) à flag O_WRONLY e, portanto, a função open deveria bloquear a thread que a chama até que outra thread abra o arquivo FIFO para leitura, de acordo com a norma.

Chegamos, então, a uma hipótese do que possa estar acontecendo com nossa aplicação: ao abrir um arquivo do tipo FIFO (com permissões de somente escrita) sem que outra thread o tenha aberto com permissão de leitura, a thread deveria bloquear a execução até que esta condição seja satisfeita. Uma vez satisfeita, a função de escrita no arquivo não falharia porque, agora, ele estaria aberto e apto a ler os dados que estariam sendo escritos. Ou seja, a aplicação do rtpdump falha porque, neste quesito, o NuttX não está seguindo a norma definida no padrão POSIX sobre o comportamento da função open sob tais circunstâncias.

Compatibilidade POSIX

O NuttX é um sistema em constante evolução e, portanto, sujeito a comportamentos que fogem do padrão POSIX, seja porque não foram ainda implementadas ou porque foram parcialmente implementadas. Felizmente, a utilização do simulador do NuttX com o GDB nos permitiu identificar a raíz do problema sem que fosse necessária a utilização do sistema em um hardware real.

Passamos, agora, à proposta para resolução do problema que é, basicamente, tornar o NuttX compatível com o padrão POSIX em relação à função open quando este for um arquivo do tipo FIFO. Observe que a função pipecommon_open (definida em nuttx/drivers/pipes/pipe_common.c) já implementa o bloqueio de abertura do arquivo FIFO para quando ele for aberto com permissões de somente leitura sem que haja ao menos uma thread que o abra com permissões de escrita:

A incompatibilidade que encontramos ocorre quando o arquivo é aberto como somente escrita sem que outra thread o tenha aberto com permissões de leitura e, portanto, precisaríamos implementar o mesmo tipo de checagem para esta situação. Esta solução foi implementada e subemtida para avaliação da comunidade do NuttX em https://github.com/apache/nuttx/pull/8985 (trataremos, em outro artigo, sobre como contribuir com o projeto do NuttX).

Teste_da_Aplicação_Real_v2

Uma vez solucionado o problema que encontramos ao testarmos o rtpdump pela primeira vez, podemos seguir novamente com o teste da aplicação, agora usando a branch master do repositório do NuttX (que já contém o código da solução). Para tal, vamos recompilar o simulador:

Em outro terminal, associamos o simulador com a interface de rede do computador host, tal como em Acessando a Rede Usando o Simulador:

Ainda no mesmo terminal, configure o encaminhamento do áudio do computador para que seja enviado ao simulador através do protocolo RTP, tal como em PulseAudio, por exemplo.


Agora, voltado ao simulador do NuttX, podemos executar:

Observe que, agora, não temos mais qualquer mensagem de erro no terminal do NuttX. Vamos verificar se o rtpdump está mesmo executando em uma thread com o comando ps:

Como esperado, o rtpdump está executando em background! Podemos agora abrir o nxplayer e executar o arquivo de áudio FIFO que contém os dados dos pacotes de áudio sendo enviados pelo computador host ao simulador do NuttX:

A partir deste momento, podemos verificar que o áudio do simulador está sendo encaminhado de volta ao computador pelo adaptador ALSA (isso, claro, considerando que esteja executando em um sistema Linux com pulseaudio). Podemos verificar novamente o pavucontrol, na aba Playback:

Neste exemplo, o som sendo produzido pelo Google Chrome está sendo encaminhado ao sink chamado RTP_sink que, por sua vez, envia os dados via RTP para o simulador do NuttX. Após recebermos os pacotes de áudio pelo rtpdump no simulador, o encaminhamos ao nxplayer que, finalmente, o reproduz através do adaptador ALSA, voltando novamente ao computador host.

Embora pareça “redundante” – já que estamos enviando áudio do computador host para o computador host (passando, é claro, pelo simulador do NuttX) – este exemplo valida a utilização do NuttX para a nossa aplicação real! Pudemos validar a integração do RTP Tools no NuttX, compilá-lo, executá-lo, identificar problemas não relacionados à aplicação, corrigi-los e, finalmente, validar o projeto antes mesmo de executá-lo em um hardware real. Executando-se a mesma aplicação em uma placa com suporte a áudio e que seja capaz de acessar a rede (via Wi-Fi e/ou cabo), podemos implementar uma espécie de placa de som que é capaz de receber pacotes de áudio através de uma rede de computadores.

Conclusão

Este artigo, que estende o trabalho iniciado em NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX, mostrou como o simulador do NuttX é uma importante ferramenta de desenvolvimento de aplicações com o NuttX sem a necessidade de um hardware real. O simulador permite 1) testar a integração de aplicações externas ao NuttX, 2) depurar o funcionamento da aplicação e 3) depurar o próprio kernel do NuttX e o comportamento de suas interfaces. Desta forma, permite que comportamentos não inesperados possam ser identificados antes mesmo de utilizar a placa final, tudo no computador host!

O próximo artigo desta série irá explorar como os códigos desenvolvidos até então podem ser integrados ao repositório oficial do NuttX, permitindo assim que outros usuários tenham acesso ao código corrigido (e compatível com o padrão POSIX!) do NuttX. Falta, ainda, mostrar uma aplicação com um hardware real: chegaremos lá!

E vocês, caros leitoras e leitores, o que estão achando desta série? Fiquem atentos para conferir os próximos artigos desta série. Dúvidas, críticas e sugestões? Deixe seu comentário na página 😉

Referências

PulseAudio. Ubuntu, 2023. Disponível em: <https://wiki.ubuntu.com/PulseAudio>. Acesso em 29 de julho de 2023.

PulseAudio Volume Control 5.0, 2021. Disponível em: <https://freedesktop.org/software/pulseaudio/pavucontrol/>. Acesso em 29 de julho de 2023.

Primeiros Passos com o ESP32 e NuttX. Embarcados, 2020. Disponível em: <https://embarcados.com.br/serie/primeiros-passos-com-o-esp32-e-o-nuttx/>. Acesso em: 20 de junho de 2023.

SERRANO, Tiago Medicci. Servidor de Som: Raspberry Pi + Home Theater DIY. Embarcados, 2021. Disponível em: <https://embarcados.com.br/servidor-de-som-raspberry-pi-home-theater-diy/>. Acesso em: 20 de junho de 2023.

MONTEIRO, Sara. Primeiros Passos com o ESP32 e o NuttX. Embarcados, 2020. Disponível em: <https://embarcados.com.br/primeiros-passos-com-o-esp32-e-o-nuttx-parte-1/>. Acesso em: 1 de junho de 2023.

DE ASSIS, Alan Carvalho. O que é o RTOS NuttX e por que você deve se importar? Embarcados, 2018. Disponível em: <https://embarcados.com.br/o-que-e-o-rtos-nuttx/>. Acesso em: 1 de junho de 2023.

Getting Started. NuttX, 2023. Disponível em: <https://nuttx.apache.org/docs/latest/quickstart/index.html>. Acesso em: 2 de junho de 2023.

Repositório RTP Tools. GitHub – irtlab/rtptools, 2023. Disponível em: <https://github.com/irtlab/rtptools>. Acesso em: 2 de junho de 2023.

Repositório de Aplicações do Apache Nuttx. GitHub – apache/nuttx-apps, 2023. Disponível em: <https://github.com/apache/nuttx-apps>. Acesso em: 2 de junho de 2023.

Custom Apps How-to. Nuttx, 2023. Disponível em: <https://nuttx.apache.org/docs/latest/guides/customapps.html>. Acesso em: 02 de junho de 2023.

Repositório do Sistema Operacional do Apache Nuttx. GitHub – apache/nuttx, 2023. Disponível em: <https://github.com/apache/nuttx>. Acesso em: 2 de junho de 2023.

Repositório de Mbed-TLS. GitHub – Mbed-TLS/mbedtls, 2023. Disponível em: <https://github.com/Mbed-TLS/mbedtls>. Acesso em: 3 de junho de 2023.

Accessing the Network. NuttX, 2023. Disponível em: <https://nuttx.apache.org/docs/latest/guides/simulator.html#accessing-the-network>. Acesso em: 4 de junho de 2023.

open, openat – open file – IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008), The Open Group Base Specifications Issue 7, 2018. Disponível em: <https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html>. Acesso em 26 de agosto de 2023.

pwrite, write – write on a file – IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008), The Open Group Base Specifications Issue 7, 2018. Disponível em: <https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html>. Acesso em 26 de agosto de 2023.

Agradecimentos

Agradecimento especial aos revisores do artigo: Alan Carvalho de Assis e Sylvio Alves.

Outros artigos da série

<< NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
0 Comentários
Inline Feedbacks
View all comments
Home » Software » NuttX: Usando o Simulador para Testes e Debugging de Aplicações

EM DESTAQUE

WEBINARS

LEIA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste:


Seminário de
Sistemas Embarcados e IoT 2024
 
Data: 25/06 | Local: Hotel Holiday Inn Anhembi, São Paulo-SP
 
GARANTA SEU INGRESSO

 
close-link