Calculando o uso do stack com o GCC

- por Sergio Prado

Categorias: Linguagem C Tags: ,

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

Sem Comentários

Nenhum comentário até agora... é a sua chance de ser o primeiro a comentar!

Faça um Comentário

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.