Analisando alocações de memória em Rust utilizando GNU Debugger
Tags: rust
Published: 2021-03-30 00:36:15

Olá pessoal, como vão vocês?

Neste artigo, vou mostrar pra vocês um exemplo de como podemos analisar as alocações de memória que são feitas por baixo dos panos em um programa em [Rust](https://www.rust-lang.org/). Para conseguirmos analisar essas alocações, vamos utilizar de ferramenta o [GNU Debugger](https://www.gnu.org/software/gdb/). Portanto, para seguir adiante, certifiquem-se de que **tanto o Rust quanto o GNU Debugger estejam ambos devidamente instalados**.

Para darmos início, é necessário antes abordarmos **dois** conceitos que são fundamentais para as análises que vamos fazer em diante- e mais do que isso, fundamentais para uma escrita de um código minimamente otimizado:  a **Stack** e o **Heap**.

A **_Stack_** é uma região especial da memória de processamento que armazena as variáveis criadas por cada função. A memória necessária para realizar esse armazenamento, para cada função, é chamado de _stack frame_. Para cada função chamada, um novo _stack frame_ específico a ela é alocado no topo da nossa **Stack**, e a função pode acessar apenas a sua própria _stack frame_. Este comportamento é justamente o que define o escopo das funções. Ao trabalharmos com a **Stack**, o tamanho de cada variável deve ser específicado em __tempo de compilação__, ou seja, se precisarmos por exemplo trabalhar com um __array__, utilizando a **Stack**, este __array__ deve possuir um tamanho exato de quantos elementos vai comportar. Quanto a execução de um programa termina de passar por uma função o __stack frame__ dela é liberado, ou seja, não precisamos nos preocupar em **desalocar manualmente**.

Para termos um relance um pouco mais objetivo de como o **Stack** se comporta, vejamos o exemplo abaixo. Ainda não utilizaremos o GNU Debugger, deixemos ele para o nosso exemplo mais completo.

```
fn main() {
  let a = 2;
  stack_only(a);
}

fn stack_only(_b: i32) {
  let _c = 3;
}
```

O programa em si é muito simples, certo? Ele possui uma função __main__, onde ele começa declarando uma variável __a__ que recebe o valor 2. Em seguida, ele chama a função __stack_only__ e passa de parâmetro para ela a variável __a__. A função __stack_only__ tem em sua assinatura que ela espera um parâmetro, que ela denomina de ___b__ do tipo __i32__, e declara em seu escopo uma variável ___c__ que recebe o valor 3.

Vamos agora então imaginar o **Stack**:
![Representação do Stack vazio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2qu70jd7kr07viif6u17.gif)

Naturalmente, a **Stack** vai se iniciar vazia, assim como nossa imagem acima representa. E então, seguindo os procedimentos que também já mencionamos, nossa função __main__ declara uma variável, logo, ela deve possuir um __stack_frame__ que comporte essa mesma. 

![Representação do Stack com o Stack Frame da função main](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xchbno8738agsgga582q.gif)

Em seguida, antes da função __main__ terminar (logo, nada de desalocações do __stack_frame__ dela ainda), uma outra função é chamada: __stack_only__.
Essa função naturalmente também vai precisar do seu __stack_frame__, e diferente da __main__, ela precisará comportar espaço para duas váriáveis: a variável __b__ que é especificada em sua assinatura e recebe o valor da variável __a__, 2, e a variável __c__ declarada em seu escopo que recebe o valor 3. 
![Representação do Stack com o Stack Frame da função main e com o Stack Frame da função stack_only](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ne769jsfys9u67mgycce.gif)

Agora sim. A função __stack_only__ termina e neste instante, seu __stack_frame__ é liberado, o que deixa nosso **Stack** assim:
![Representação do Stack com o Stack Frame da função main após o Stack Frame da função stack_only ter sido desalocado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xchbno8738agsgga582q.gif) 

Por fim, a função __main__ se encerra e seu __stack_frame__ também é liberado:
![Representação do Stack vazio, após o Stack Frame da função Main ter sido desalocado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2qu70jd7kr07viif6u17.gif)

É importante ressaltarmos que o **Stack** possui um tamanho limitado que é determinado, dentre outros fatores, pela arquitetura do processador, sistema operacional, compilador...
Logo, se utilizarmos por exemplo uma recursão infinita, nossa **Stack** vai eventualmente ser completamente preenchida e na tentativa de alocarmos mais um __stack_frame__ vamos nos deparar com a famigerada **Stack Overflow Exception**.

Simples, certo? As coisas vão mudar um pouco no momento em que introduzirmos um outro conteito: o **Heap**.

O **Heap** se difere da **Stack** por ser uma região da memória de processamento que **não** é automaticamente gerenciada para nós, logo, nós temos que manualmente alocar memória nela e tão importante quanto, manualmente desalocar a memória dela. 
Esquecer de desalocar memórias alocadas no **Heap** é uma das principais causas de **memory leaks** em aplicações.
Diferente da **Stack** que possui seu tamanho definido em **tempo de compilação**, o **Heap** não possui um limite específico, mas sim delimitado apenas pela quantidade de recursos físicos que uma máquina conseguirá prover.

Observemos o exemplo a seguir, analisando apenas as interações com o **Heap**:

```
fn main() {
  let a = 2;
  stack_only(a);
  stack_and_heap()
}

fn stack_only(_b: i32) {
  let _c = 3;
}

fn stack_and_heap() {
  let _d = Box::new(4);
}
```

Diferente da representação do **Stack**, o **Heap** não é uma pilha e muito menos irá 'enpilhar' algo. Podemos visualizá-lo como um espaço onde as alocações irão ocupar blocos cujo tamanho varia conforme o necessário para alocar o desejado. Ele começa vazio, conforme a representação abaixo:

![Representação do Heap e da Stack ambos vazios](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4o5pa7jx7n91ffqi0zq7.gif)

Até que a nossa função _stack_and_heap_ seja executada, nosso **Stack** nesse exemplo será identico ao exemplo anterior. Portanto, vamos partir do momento em que o _stack_frame_ da função _stack_only_ é liberado:

![Representação do Heap e da Stack, mas a Stack após a função stack_frame ter se encerrado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4tyy7rmlhuav3krimxy8.gif)

Ao executarmos a função _stack_and_heap_, vamos ter um **_stack_frame_** para ela também. Até aqui, nada demais:

![Representação do Heap e da Stack, mas a Stack após a função stack_frame ter se encerrado e agora com um Stack Frame para a função stack_and_heap](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jl2sr1imzgr693akeh4n.gif)

Agora as coisas ficam um pouco diferentes. Em Rust o **_Box_** é um **_Smart Pointer_** que irá, vide nosso exemplo, alocar o valor 4 no **_Heap_** e automaticamente alocar assim que essa função for encerrada. A varíavel **_d_**, ao invés de simplesmente receber o valor 4, diferente das atribuições de variáveis que fizemos até então, receberá o endereço do valor que foi alocado no **_Heap_**, que nada mais é do que o ponteiro que leva a ele.

![Representação do Heap e da Stack, com o Heap contento o valor 4 alocado nele e o Stack com um ponteiro para este](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lwpjv5e0m7n6q4grhzjy.gif)

Nosso programa poderia ter vários outros objetos alocados no Heap, e ele ficaria mais ou menos assim:

![Representação do Heap e da Stack, com o Heap contento o valor 4 alocado nele e o Stack com um ponteiro para este e vários outros objetos alocados simulando outras alocações](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vi9wp7dyyv45pozdeltp.gif)

Voltando agora para o nosso exemplo, o **_stack_frame_** da função _stack_and_heap_ será liberado e o valor 4 será desalocado do **_Heap_**, nos deixando no seguinte caso:

![Representação do Heap e da Stack, com o stack frame da função stack_and_heap removido e o valor 4 desalocado do Heap](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uoa0aesyhc0l1n1psd6e.gif)

Vale ressaltar: o valor 4 está sendo desalocado justamente graças ao **_Box_**, que é um **_Smart Pointer_**. Se estivéssemos utilizando um **_raw pointer_** teríamos que fazer essa desalocação de forma manual ou teríamos um **_memory leak_** como falamos anteriormente, e essa seria a nossa representação:

![Representação do Heap e da Stack, com o stack frame da função stack_and_heap removido mas o valor 4 não desalocado do Heap](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfyhf8ujngmh265btdob.gif)

O programa se encerraria e a memória anteriormente alocada no **_Heap_** persistiria e o que iria acontecer com ela passa a depender muito do Sistema Operacional e da arquitetura por trás. Em computadores com Windows ou distribuições UNIX, o Sistema Operacional irá simplesmente reinvidicar a memória para si e utilizá-la em outro processo por exemplo, mas isso pode não ser verdade para aplicações embarcadas. 

Os próximos passos do nosso programa são exatamente os mesmos do exemplo anterior e agora consistem de liberar o **_stack_frame_** da função **_main_**, basicamente nos levando de volta para o cenário inicial.

![Representação do Heap e da Stack ambos vazios](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4o5pa7jx7n91ffqi0zq7.gif)

Sem mais delongas e devidamente aquecidos vamos agora para o **_GNU Debugger_**!

Vamos utilizar o código do nosso último exemplo com pequenas alterações, especificamente para nos ajudar a colocar nossos **_breakpoints_**.

```
use log::info;

fn main() {
  let a = 2;
  stack_only(a);
  stack_and_heap()
}

fn stack_only(_b: i32) {
  let _c = 3;
  info!("Debugging stack_only")
}

fn stack_and_heap() {
  let _d = Box::new(4);
  info!("Debugging stack_and_heap")
}
```

Com nosso exemplo compilado, posso iniciá-lo junto ao **_gdb_** da seguinte forma:

![Executando o GNU Debugger com nosso exemplo compilado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2q2ifhbz77hguwrkis4w.png)

Para uma melhor visualização e interação ao **_gdb_**, vou fazer uma pequena alteração com um comando que a própria interface do CLI dele nos dispõe. 

![Alterando o Layout do gdb](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ixk5m1xueoesqfi23joy.png)

Com essa alteração, o seu display do **gdb** deve ter ficado mais ou menos com a seguinte aparência:

![Aparencia do Layout do gdb após a alteração](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9k9q20c1rz9gnw8d4pm4.PNG)

Agora, podemos por exemplo visualizar nosso código para analisarmos onde podemos colocar alguns **_breakpoints_**. Para isso, podemos utilizar o seguinte comando:
![Listando o código exemplo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2w9s1l39ict89vd34i5d.png)

E nossa visualização no **gdb** será a seguinte:
![Visualização do Código no gdb](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubk8hmn7jayh3cx8pxp9.PNG)

Para colocarmos um **_breakpoint_** em um ponto específico, podemos utilizar o comando:

![Comando para colocar um Breakpoint no código](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0zcy7iulnffv6cujbzce.png)

Visando já alguns pontos interessantes, vamos colocar nas **linhas 4, 10 e 15**
![Breakpoints para as linhas 4, 10 e 15](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h7kahkjirrvvkfqg4o5o.png)

Nosso código listado no GDB deve estar agora com as linhas que colocamos os **breakpoints** devidamente marcadas, como no exemplo abaixo:
![Código no gdb com os breakpoints](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bgyh9i5nbneyu15kvao4.PNG)

Agora, sem mais delongas, vamos executar nosso código utilizando um comando simples, de uma letra só:
![Executando código](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h4m2l7szt5pfpm8djky7.png)

E agora, nosso código deve ter se iniciado e travado no nosso primeiro __breakpoint__, na **linha 4**:
![Código executando com Breakpoint na Linha 4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50lzq7edg5yx59rekal6.png)

Como estamos na função **__main__**, significa que já temos um **__stack_frame__**. Podemos observá-lo utilizando o comando **backtrace**:
 ![Comando para observar como estão os StackFrames](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pahp4ngsz3udawcie655.png)

Como **sabemos** que temos apenas **1 __stack_frame__**, então vamos utilizar o comando passando esse valor. O que nos forneceria o seguinte resultado:
![Observando Stack Frame da função main](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d83r6dsn1ay0x9v46171.png)

Contudo, até então, não existe nenhuma variável alocada dentro do **_stack_frame_**, pois a execução do nosso programa para **logo antes de executar a linha em que posicionamos um __breakpoint__**. Podemos verificar se houve alguma alocação de variável utilizando o comando:
![Comando para verificar alocações de variáveis](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tptrbbypubggevei1f7m.png)

Se executarmos esse comando agora, este será o output:
![Verificando alocação de memória antes do nosso breakpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cdhsqoxmalg29nxzfks3.png)

Para darmos continuidade, vamos apenas passar pela alocação da variável que nosso __breakpoint__ está segurando, antes de entrarmos nas funções, para conseguirmos observar a alocação. Para isso, podemos utilizar o comando **next**: 
![Comando para passar uma linha na execução dos comandos após um breakpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hg1n9phelh6chvkyfzhx.png)

Que nos dá o seguinte resultado:
![Passado o breakpoint utilizando o comando next](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qx5vu6bn8rqw2w5c9gm7.png)

Se agora tentarmos utilizar o comando para analisarmos as alocações de variáveis:
![Alocação da variável 'a' recebendo o valor 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jb9xn6hcyq4akossxm7i.png)

Podemos observar agora que, neste **__stack_frame_**, existe a variável **a** que recebeu o valor **2**. 
Agora, é interessante que 'entremos' dentro da função __stack_only__ para observamos como o que vimos até então pode mudar. Para isso, podemos utilizar o comando **step**:
![Comando para entrarmos em uma função pelo debugger](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6961sm3bfmuuqy41azgl.png)

Se tivéssemos utilizado o comando **next**, a função __stack_only__ teria sido executada e o debugger iria para a função __stack_and_heap__.
Se o comando **step** foi utilizado corretamente, seu gdb deve estar assim:
![gdb após a execução do comando step](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5iqz0sr6231cw7ghphny.png)

Como estamos em outra função, temos outro **__stack_frame_**. Como sabemos que temos dois até então, podemos executar o comando **backtrace** passando o número **2**:
![Analisando agora o stack com dois stack frames](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/305cf2zmx3eahhj2rbpm.png)

Podemos inclusive observar como o **__stack_frame__** da função __stack_only__ está de fato vindo antes do **__stack_frame__**