Calculando o uso do stack com o GCC
- por Sergio Prado
Stack overflow ou estouro de pilha é um dos principais problemas em software, principalmente quando trabalhamos com um RTOS.
Definir o stack de uma tarefa do RTOS não é fácil. Se o stack for superdimensionado, você estará desperdiçando memória RAM. E se o stack for subdimensionado, você corre o risco de um estouro de pilha.
Obs.: Se você não sabe o que é um stack, ou quer saber mais sobre os cuidados que devemos ter ao trabalhar com o stack, dê uma olhada nos artigos “Trabalhando com o stack em linguagem C – Parte 1” e “Trabalhando com o stack em linguagem C – Parte 2“.
Você sabia que o GCC é capaz de calcular, em tempo de compilação, o consumo do stack de qualquer função do sistema? Pois é! E se você utilizar um toolchain baseado no GCC para seus projetos com microcontrolador e RTOS, ele será capaz de calcular o consumo máximo de stack de todas as funções e tarefas da aplicação!
CALCULANDO O STACK COM O GCC
Para gerar as informações de consumo do stack, o processo é simples.
Dado um programa em C ou C++:
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 | #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <pwd.h> void hostname1_print() { char hostname[256]; gethostname(hostname, sizeof(hostname)); puts(hostname); } void hostname2_print(int size) { char hostname[size]; gethostname(hostname, sizeof(hostname)); puts(hostname); } int main(int argc, const char *argv[]) { printf("Hostname 1 is: "); hostname1_print(); printf("Hostname 2 is: "); hostname2_print(512); return 0; } |
Basta compilar com o parâmetro -fstack-usage:
$ gcc -fstack-usage main.c -o main |
O GCC irá gerar um arquivo de extensão .su com informações de consumo do stack para cada arquivo-objeto da aplicação:
$ cat main.su main.c:6:6:hostname1_print 288 static main.c:15:6:hostname2_print 80 dynamic main.c:24:5:main 32 static |
Cada linha deste arquivo representa o consumo do stack por função, incluindo o nome da função, o consumo do stack em bytes e um campo com qualificadores de tipo.
São três os qualificadores de tipo existentes: static, dynamic e bounded.
Se o qualificador for static, significa que o uso do stack é imutável, portanto o cálculo do consumo do stack feito pelo GCC é confiável. Veja por exemplo a função hostname1_print().
Caso o qualificador seja dynamic, o consumo do stack é dependente da execução da aplicação. Veja por exemplo a função hostname2_print(). Se este qualificador aparecer sozinho, o cálculo de uso do stack do GCC não é confiável. Se aparecer o qualificador bounded junto com dynamic, significa que o consumo do stack é variável, porém com o consumo máximo determinado em tempo de compilação, sendo portanto confiável.
Desta forma, se o consumo do stack é uma preocupação para o seu projeto, evite construções da linguagem que façam com que o consumo do stack seja variável, como por exemplo o uso de vetores de tamanho variável e a chamada de funções recursivas.
É possível ainda gerar warnings com o parâmetro -Wstack-usage, e fazer estes warnings se transformarem em erros com o parâmetro -Werror:
$ gcc -fstack-usage -Wstack-usage=256 -Werror main.c -o main main.c: In function ‘hostname1_print’: main.c:6:6: error: stack usage is 288 bytes [-Werror=stack-usage=] void hostname1_print() ^ main.c: In function ‘hostname2_print’: main.c:15:6: error: stack usage might be unbounded [-Werror=stack-usage=] void hostname2_print(int size) ^ cc1: all warnings being treated as errors |
Uma deficiência desta funcionalidade do GCC é que ele retorna apenas o consumo do stack individual de cada função. Se você quiser saber o consumo máximo do stack ao chamar uma função, incluindo o stack de todas funções que ela chama, precisará calcular este consumo manualmente, identificando toda a sequência de chamadas de função e somando o uso do stack de cada uma delas.
Até existe um script em Perl para fazer este cálculo, porém ele foi originalmente escrito para ser utilizado com um toolchain para o AVR, e não funcionou muito bem no meu toolchain nativo do GCC.
Isso me motivou a escrever uma ferramenta que faça este cálculo, basicamente cruzando as informações do arquivo .su com a árvore de chamada de funções da aplicação, gerada por uma ferramenta como o cflow. Se você conhecer uma ferramenta que faça isso, deixe abaixo seus comentários.
Mais informações sobre esta funcionalidade de cálculo do stack estão disponíveis no manual do GCC.
Happy hacking!
Sergio Prado