quarta-feira, 7 de outubro de 2009

SafeMM - Debug Memory Manager para Delphi e C++

Já faz um tempinho que a Embarcadero publicou o SafeMM. De lá para cá eu posterguei inúmeras vezes a realização de testes para entender melhor a proposta desse gerenciador de memória.

Definição
O SafeMM é um gerenciador de memória "prova de conceito" que tem a finalidade de identificar alguns tipos de AV (Access Violation) que normalmente não são caputrados pelo gerenciador de memória padrão do Delphi.

Vamos para um exemplo.



procedure TForm1.Create(Sender: TObject);
const
  DEFAULT_OPTIONS: array[0..2] of string = ('Opção 1', 'Opção 2', 'Opção 3');
var
  i: integer;
  lMyStrList: TStringList;
begin
  lMyStrList := TStringList.Create;
  try
    for i := low(DEFAULT_OPTIONS) to high(DEFAULT_OPTIONS) do
      lMyStrList.Add(DEFAULT_OPTIONS[i]);

    // ... outras instruções
    Self.cbbAction.Items.Assign(lMyStrList);
    // ... outras instruções

    {$IFDEF DEBUG}
    ShowMessage('String list count: ' + IntToStr(lMyStrList.Count));
    {$ENDIF}
  finally
    lMyStrList.Free;
  end;

  Self.cbbAction.ItemIndex := lMyStrList.Count - 1;
  ShowMessage('String list count: ' + IntToStr(lMyStrList.Count));
end;

No exemplo acima uma lista de strings é criada e preenchida com as entradas de um array constante; essa lista é, então, atribuída para o componente "cbbAction"; por último, é posto em seleção o último item da lista.
Um trecho de código bastante comum, mas há uma falha nas duas últimas linhas. A variável "lMyStrList", naquele ponto do código, já está com sua área de memória liberada.

Se você compilar esse exemplo no Delphi (não sei se o comportamento é idêntico para todas as versões - utilizei o Delphi 2010), o segundo ShowMessage retorna o valor 0 (zero) quando tenta acessar o método Count do objeto lMyStrList, mas nenhuma exceção ou falha ocorre. No entanto prever o comportamento da instrução "lMyStrList.Count - 1;" é classe impossível, já que a memória foi liberada e não há qualquer garantia de que os dados anteriormente pertencentes ao objeto ainda estejam intactos na memória.

Apesar do exemplo aqui ser simples, essa falha na lógica de programação pode resultar em um bug complicado e consumir um tempo razoável para rastreá-lo.

Usando o SafeMM
É justamente para situações como essa que o SafeMM se propõem.
Se você utilizar esse gerenciador de memória qualquer instrução de leitura ou escrita à uma área de memória que não deveria estar acessível resultará em um AV (Access Violation).

A sua instalação é muito simples:
Step 1. Acesso o site da Embarcadero (http://cc.embarcadero.com/Item/27241) e faça o download do SafeMM.
Apesar de o código fonte ser livre, é necessário que você seja um usuário registrado. Caso não seja, registre-se e faça o download (a Embarcadero agradece);
Step 2. Descompacte o arquivo em uma pasta qualquer (apenas os arquivos 'SafeMM.pas' e 'SafeMMInstall.pas' são serão utilizados, os demais pode se apagados se assim você desejar);
Step 3. No Delphi, abra o seu projeto e adicione os arquivos 'SafeMM.pas' e 'SafeMMInstall.pas'; build e já está pronto para depurar;

OBS: Eu testei em um projeto relativamente grande, com um executável principal de muitos módulos (.bpl). Só foi necessário acrescentar a referência desses dois arquivos no projeto principal.

Exemplo de um projeto incluindo o SafeMM.
program Project2;

uses
  {$IFDEF DEBUG}
  SafeMM,
  SafeMMInstall,
  {$ENDIF}
  Forms,
  fuPrincipal in 'fuPrincipal.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

Eu utilizei um a diretiva {$IFDEF DEBUG} para garantir que o SafeMM não fosse incluído quando o projeto for buildado para release, ou seja, para a versão final.

Como funciona
O código fonte desse gerenciador de memória é bem pequeno e recomendo dedicar um tempinho para compreender seu funcionamento mais detalhado.
Vou explanar num alto nível como funciona e como é feito para detectar esse tipo de acesso indevido.

O SafeMM substitui as rotinas de gerenciamento de memória (GetMem, FreeMem, ReallocMem e AllocMem) por rotinas próprias (SafeGetMem, SafeFreeMem, SafeAllocMem e SafeReallocMem, respectivamente). Essas rotinas utilizaram listas auxiliares para armazenar registros com informações extras para cada bloco de memória que você for alocado durante a execução do código fonte.
Por exemplo, a linha de código do exemplo acima "lMyStrList := TStrings.Create" necessita alocar memória (que é feito pelo internamente pela rotina SafeGetMem). Nesse momento, uma entrada nessa lista auxiliar é adicionada com diversas informações sobre esse bloco de memória e, entre elas, uma se destaca: "Magic".
A propriedade "Magic", como o próprio nome sugere, é usada para armazenar um código mágico (123123 - decimal). E é utilizada, juntamente com outras variáveis, para manter a integridade desses blocos de informações.
Quando o objeto local lMyStrList é desalocado, o SafeMM realiza várias operações internas e, por último, bloqueia o acesso à região de memória onde o TStringList estava alocado (através da rotina VirtualProtect). Assim, quando você tentar acessar novamente a variável lMyStrList, um AV (Access Violation) é levantado fazendo com que a depuração seja interrompida na linha exata onde ocorreu o acesso indevido.

Simples e funcional!

Considerações
O SafeMM não tem o propósito de substituir o gerenciador de memória padrão do Delphi, ele foi concebido para o processo de depuração.
Um aplicativo normal pode chegar a ter a quantidade memória alocada multiplicada em até 4 vezes. A versão atual é 0.3 e é a mesma desde 29 de março de 2007.

Nenhum comentário: