Princípios básicos e teste de Asan

Visão geral

Asan é uma ferramenta de detecção de erros de memória desenvolvida pelo Google especificamente para C/C++. Possui as seguintes funções

  • Use memória liberada (ponteiro curinga)√
  • Heap de memória fora dos limites (leitura e gravação)√
  • Pilha de memória fora dos limites (leitura e gravação)√
  • Variável global fora dos limites (leitura e gravação) ×
  • A função retorna variável local ×
  • Vazamento de memória√

Nota: O sistema usado para teste neste artigo é o Ubuntu 16.04, e a versão do GCC é 5.4.0. As funções acima são marcadas com √ para indicar as funções suportadas por esta versão do GCC, e com × marcadas para indicar as funções que não são suportados por esta versão do GCC.

Asan é executado muito rápido e a velocidade de execução do programa testado geralmente é reduzida apenas em 2 vezes (em comparação com a degradação de desempenho de 10 a 20 vezes do valgrind, que já é muito rápida).

Asan consiste em duas partes: módulo de instruções do compilador e biblioteca de tempo de execução:

  • O módulo de instrumentação do compilador
    adiciona instruções de controle ao compilar o programa para monitorar todos os comportamentos de uso de memória do programa.Cada operação de acesso à memória no código será modificada pelo compilador da seguinte forma:

    Código original:

    *address = ...;  // or: ... = *address;
    

    Código compilado:

    if (IsPoisoned(address)) {
      ReportError(address, kAccessSize, kIsWrite);
    }
    *address = ...;  // or: ... = *address;
    

-* Biblioteca de tempo de execução*
é usada para substituir a função malloc/free da biblioteca glibc para implementar operações de alocação e liberação de memória. Após a execução do malloc, a memória antes e depois da memória alocada (chamada de "zona vermelha") será marcada como "envenenada" e a memória liberada será isolada (não alocada temporariamente) e também será marcada como "envenenada" . Status envenenado".

Asan fazia inicialmente parte do LLVM e posteriormente foi integrado à versão 4.8 do GCC.Ele pode rodar em X86, ARM, MIPS, PowerPC e outras plataformas, e o sistema operacional suporta Linux, OS X, iOS, Android e FreeBSD.

Configuração de compilação

Opções de compilação do GCC

  • -fsanitize=address: Habilita detecção de memória fora dos limites

  • -fsanitize=leak: Habilita detecção de vazamento de memória

  • -fsanitize-recover=address: Para garantir a estabilidade, o programa geral em segundo plano não pode simplesmente sair ao encontrar um erro, mas continua em execução. Esta opção é usada para permitir que o programa continue em execução após um erro de memória. É necessário defina a variável de ambiente ASAN_ OPTIONS=halt_on_error=0
    . entrará em vigor; se esta opção não for definida, um erro será relatado e encerrado se houver um erro de memória.

  • -fno-stack-protector: Desativa a proteção contra estouro de pilha

  • -fno-omit-frame-pointer: Desativa a proteção contra estouro de pilha

  • -fno-var-tracking: A opção padrão é -fvar-tracking, o que tornará a operação muito lenta

  • -g1: Indica informações mínimas de depuração. Geralmente a versão de depuração usa -g, ou seja, -g2.

No Makefile, você pode controlar as opções Asan do GCC definindo opções de compilação semelhantes às seguintes.

ASAN_CFLAGS += -fsanitize=address -fsanitize-recover=address

Opções de execução do Asan

ASAN_OPTIONS é a variável de ambiente de opção em execução do Address-Sanitizier.

  • halt_on_error=0: Continue executando após detectar erros de memória
  • detect_leaks=1: Habilita detecção de vazamento de memória
  • malloc_context_size=15: Quando ocorre um erro de memória, o número de níveis de pilha de chamadas exibidos é 15
  • log_path=/var/log/asan.log: Caminho do arquivo de armazenamento de log de problema de verificação de memória
  • supressões=$SUPP_FILE: Impressão blindada de certos erros de memória

O seguinte é definir a variável de ambiente ASAN_OPTIONS:

export ASAN_OPTIONS=halt_on_error=0:use_sigaltstack=0:detect_leaks=1:malloc_context_size=15:log_path=/var/log/asan.log:suppressions=$SUPP_FILE

LSAN_OPTIONS é a variável de ambiente do módulo de detecção de vazamento de memória LeakSanitizier.As opções de execução comumente usadas são as seguintes:

  • exitcode=0: Defina o código de saída de vazamento de memória como 0. O código de saída de vazamento de memória padrão é 0x16.

  • use_unaligned=4: alinhamento de 4 bytes

O seguinte é definir a variável de ambiente LSAN_OPTIONS:

export LSAN_OPTIONS=exitcode=0:use_unaligned=4

Configuração de tempo de execução Asan

Asan assume as operações de aplicação e liberação da memória do aplicativo, que substitui as operações originais malloc e free, como a biblioteca glibc, portanto, você precisa usar LD_PRELOAD para especificar a localização de libasan.so antes de executar o programa.

Exemplo

Além de vazamentos de memória , o Asan , por padrão, sairá do programa imediatamente após detectar um problema de memória e imprimirá registros relevantes de problemas de memória. Em relação ao problema de vazamento de memória, se o programa tem vazamento de memória só será detectado quando o programa sair normalmente.A saída normal mencionada aqui tem várias situações para programas C/C++:

  • função principal, retornar para sair
  • Saia chamando a função exit

Asan não consegue detectar se há vazamento de memória se o processo for encerrado devido a um sinal.

Acesso ao heap fora dos limites (heap-buffer-overflow)

#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
    int *array = malloc(sizeof (int) * 100);
    array[0] = 0;
    int res = array[1 + 100]; //array访问越界
    free(array);

    pause();//程序等待,不退出
    return 0;
}

Comando de compilação:

gcc heapOOB.c -o heapOOB -g -fsanitize=address -fsanitize=leak

Implementação:

==3653==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61400000ffd4 at pc 0x000000400871 bp 0x7ffe50cde9c0 sp 0x7ffe50cde9b0
READ of size 4 at 0x61400000ffd4 thread T0
#0 0x400870 in main /home/jetpack/work/4G/test/asan/heapOOB.c:7
#1 0x7f30b337a83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x400708 in _start (/home/jetpack/work/4G/test/asan/heapOOB+0x400708)
0x61400000ffd4 is located 4 bytes to the right of 400-byte region [0x61400000fe40,0x61400000ffd0)
allocated by thread T0 here:
#0 0x7f30b37bc602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x4007ee in main /home/jetpack/work/4G/test/asan/heapOOB.c:5
#2 0x7f30b337a83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)

Acesso à pilha fora dos limites (stack-buffer-overflow)

#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv) 
{
    int stack_array[100];
    stack_array[100] = 0;//栈访问越界

    pause();
    return 0; 
}

Comando de compilação:

gcc stackOOB.c -o stackOOB -g -fsanitize=address -fsanitize=leak

Resultados do:

==3952==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffdb72e830 at pc 0x0000004008ef bp 0x7fffdb72e660 sp 0x7fffdb72e650
WRITE of size 4 at 0x7fffdb72e830 thread T0
#0 0x4008ee in main /home/jetpack/work/4G/test/asan/stackOOB.c:6
#1 0x7f0c47a8e83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x400748 in _start (/home/jetpack/work/4G/test/asan/stackOOB+0x400748)
Address 0x7fffdb72e830 is located in stack of thread T0 at offset 432 in frame
#0 0x400825 in main /home/jetpack/work/4G/test/asan/stackOOB.c:4
This frame has 1 object(s):
[32, 432) 'stack_array' <== Memory access at offset 432 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
(longjmp and C++ exceptions *are* supported)

Use memória liberada (UseAfterFree)

#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv) 
{
    int *array = malloc(sizeof (int) * 100);
    array[0] = 0;
    free(array);
    int res = array[0]; //使用已经释放的内存

    pause();
    return 0;
}

Comando de compilação:

gcc heapUAF.c -o heapUAF -g -fsanitize=address -fsanitize=leak

Resultados do:

==4385==ERROR: AddressSanitizer: heap-use-after-free on address 0x61400000fe40 at pc 0x000000400877 bp 0x7ffdc9019c20 sp 0x7ffdc9019c10
READ of size 4 at 0x61400000fe40 thread T0
#0 0x400876 in main /home/jetpack/work/4G/test/asan/heapUAF.c:8
#1 0x7f93f8d5683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x400708 in _start (/home/jetpack/work/4G/test/asan/heapUAF+0x400708)
0x61400000fe40 is located 0 bytes inside of 400-byte region [0x61400000fe40,0x61400000ffd0)
freed by thread T0 here:
#0 0x7f93f91982ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)
#1 0x40083f in main /home/jetpack/work/4G/test/asan/heapUAF.c:7
#2 0x7f93f8d5683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
previously allocated by thread T0 here:
#0 0x7f93f9198602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x4007ee in main /home/jetpack/work/4G/test/asan/heapUAF.c:5
#2 0x7f93f8d5683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)

Vazamento de memória

#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv) 
{
    int *array = malloc(sizeof (int) * 100);
    memset(array, 0, 100 * 4);

    return 0;
}

Comando de compilação:

gcc heapLeak.c -o heapLeak -g -fsanitize=address -fsanitize=leak

Resultados do:

==3120==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 400 byte(s) in 1 object(s) allocated from:
#0 0x7f412d5b7602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x40073e in main /home/jetpack/work/4G/test/asan/heapLeak.c:7
#2 0x7f412d17583f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)

SUMMARY: AddressSanitizer: 400 byte(s) leaked in 1 allocation(s).

Em aplicações reais, especialmente dispositivos embarcados, o programa continuará a ser executado até encontrar um problema, como um acesso à memória fora dos limites, resultando em uma falha de segmentação. Portanto, se você quiser fazer o programa sair normalmente a qualquer momento e detectar se há vazamento de memória no programa, você pode enviar um sinal específico para o processo, e
o processo sairá ativamente do programa após receber o sinal, assim acionando a detecção de vazamento de memória Asan.

//heapLeak.c

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
 
static int is_exit = 0;
 
void sig_exit(int num)
{
    if (num == SIGUSR1) {
      //接收到SIGUSR1后,设置is_exit为1
        printf("SIGKILL\n");
        is_exit = 1;
    }   
}
 
int main(int argc, char **argv) 
{
    struct sigaction action;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
 
    action.sa_handler = sig_exit;
    sigaction(SIGUSR1, &action, NULL);
 
    int *array = malloc(sizeof (int) * 100);
    memset(array, 0, 100 * 4); 
 
    while(1) {                                                                                                                                                                                                                               
        if (is_exit) {
        //如果is_exit为1,退出循环,从使程序正常退出
            break;
        }   
        sleep(1);
    }   
 
    return 0;
}

Comando de compilação:

gcc heapLeak.c -o heapLeak -g -fsanitize=address -fsanitize=leak

Processo de teste, execute heapLeak

./heapLeak //等待接收SIGUSR1
//查询进程id
pidof heapLeak
4347

//发送SIGUSR1信号
kill -SIGUSR1 4347

Depois que heapLeak recebe SIGUSR1, ele sai e gera estatísticas de vazamento de memória:

==4347==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 400 byte(s) in 1 object(s) allocated from:
#0 0x7fbcd578d602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x400b31 in main /home/jetpack/work/4G/test/asan/heapLeak.c:26
#2 0x7fbcd534b83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)

SUMMARY: AddressSanitizer: 400 byte(s) leaked in 1 allocation(s).

Acesso à variável local retornado pela função

int *ptr;
void usr_func(void)
{    
    //申请栈内存
    int local[100] = {0};
    ptr = local;
}    
     
int main(void)
{    
    //使用函数栈返回的内存
    *ptr = 0;
    return 0;                                                                                                                                                                                                                                
}

Acesso à variável global fora dos limites

int global_array[100] = {0};                                                                                                                                                                                                                 
 
int main(int argc, char **argv) 
{
    //全局变量global_array访问越界 
    global_array[101];
    return 0;
}

Nota: O acesso à variável global fora dos limites (GlobalOutOfBounds) e o acesso à variável local retornado pela função (UseAfterReturn) não são suportados pelo gcc-5.4.0 por enquanto, portanto, não podem ser testados por enquanto.

Registrando problemas de memória

Se ocorrer um problema, em vez de sair do programa diretamente, você continua a executá-lo e gera o problema de memória em um arquivo. Você pode usar a opção de compilação -fsanitize-recover=address e a variável ASAN_OPTIONS para conseguir isso. Basta declarar a variável antes de executar o programa.

编译时,加上-fsanitize-recover=address选项:
gcc heapOOB.c -o heapOOB -g -fsanitize=address -fsanitize=leak -fsanitize-recover=address

运行时,先声明ASAN_OPTIONS再运行程序:
export ASAN_OPTIONS=halt_on_error=0:use_sigaltstack=0:detect_leaks=1:malloc_context_size=15:log_path=/var/log/asan.log:suppressions=$SUPP_FILE

Se o gcc não suportar a opção de compilação -fsanitize-recover=address, por exemplo, o gcc-5.4.0 que estou usando agora, o programa ainda será encerrado automaticamente quando encontrar um problema, mas o log será enviado para o diretório especificado por log_path, como

asan.log.5089  asan.log.5090  asan.log.5348 //其中,文件后面的数字表示程序的进程号

Capítulo extra

Deixe-me contar sobre minha experiência recente usando Asan. O processo foi bastante triste, mas pode ter algum significado, então gostaria de compartilhar com vocês.

Na verdade, eu nunca tinha usado o Asan antes. Depois de vê-lo um dia, achei que era muito poderoso e poderia ser usado para diversão. Então, escrevi alguns problemas comuns de uso de memória e descobri que os problemas podem ser descobertos na primeira vez. Acontece que eu estava trabalhando em um projeto de Linux embarcado na época. O tamanho do programa era médio e as funções básicas foram desenvolvidas naquela época. Pensei que poderia usar o Asan para testar se havia problemas de memória neste projeto. Lembro que verifiquei algumas informações e analisei como utilizar o Asan no projeto. Depois disso, comecei a usar o Asan, e encontrei um erro naquele momento, e então não ocorreram mais problemas (pensando nisso agora, o maior descuido naquele momento foi não olhar quanto consumo adicional de memória o uso do Asan ferramenta traria para o aplicativo. , você não será acusado injustamente de vazamentos de memória em outros aplicativos;)).

Depois de viver em paz por um tempo, um dia descobri que havia muito pouca memória no sistema. Minha primeira reação foi que havia um vazamento de memória no aplicativo. Por meio de monitoramento de longo prazo, foi determinado que o uso de memória do aplicativo aumentava constantemente. A questão confirma ainda mais a conjectura de vazamento de memória. Posteriormente, por meio de diversas análises e até mesmo utilizando a ferramenta valgrind, nenhum vazamento de memória foi detectado, porém o uso de memória do aplicativo continuou a crescer, o que no final se tornou muito frustrante.

Mais tarde, foi dito na Internet que havia um problema com o alocador de memória integrado da glibc (ptmalloc), que poderia facilmente causar fragmentação da memória. Como resultado, mesmo que muita memória fosse liberada, ela só poderia ser ocupada por processos e não pôde ser retornado ao sistema operacional. Achei que esse era o mesmo problema que encontrei, então consultei muitas informações para aprender sobre o mecanismo de implementação do glibc ptmalloc, como causar fragmentação de memória e como o desempenho é baixo em cenários multi-core e multi-thread. até analisou o desempenho de dois outros alocadores de memória, jemalloc (do Facebook) e tcmalloc (do Google).

No final, eu estava realmente desesperado, então simplesmente substituí o ptmalloc. Acontece que o buildroot suporta a compilação do jemalloc. Então, naturalmente, substituí o jemalloc. Eu não saberia se não mudasse, mas fiquei chocado quando mudei. O uso de memória virtual do aplicativo caiu diretamente de 500M para 60M, e eu estava ajoelhado diretamente para jemalloc naquele momento.

Mais tarde, descobri que o tamanho da memória virtual caiu muito. Não foi uma função do jemalloc, mas sim do aumento no uso da memória virtual devido ao uso do Asan. Foi justamente porque o Asan precisava monitorar o uso da memória em tempo real que ele eventualmente levou ao aumento contínuo no uso de memória.

Haha, eu entendo perfeitamente a história de que os programadores muitas vezes cavam buracos sozinhos, saltam sozinhos e se enterram para experimentar novas tecnologias.

Resumindo algumas experiências:

  • Durante o processo de desenvolvimento do projeto, novas tecnologias e ferramentas devem ser usadas com cautela. Antes do uso, elas devem ser totalmente compreendidas. Registros de uso devem ser mantidos durante o processo. Quando surgirem problemas, eles poderão ser usados ​​para encontrar possíveis causas dos problemas.
  • Você deve ter alguma imunidade a novas tecnologias e novas ferramentas, e não ser muito cabeça quente. Quando você vê as funções e o desempenho que ela promove, você mal pode esperar para aplicá-la ao projeto imediatamente. Na verdade, você está muitas vezes cavando um buraco para si mesmo, e no final você morrerá, não importa o que aconteça.
  • Finalmente, não é que você não queira aprender novas tecnologias e novas ferramentas e não usá-las mais. Em vez disso, você deve sempre manter uma mentalidade de exploração ativa. Quando você encontrar coisas boas, você deve aprendê-las ativamente e esperar que elas aconteçam. as ideias amadurecem antes de aplicá-las ao trabalho real.

Claro, também aprendi muito com esse incidente, como compreender aproximadamente os princípios e o uso de vários alocadores de memória comumente usados ​​e compreender os princípios básicos e o uso de Asan e valgrind, o que será de grande benefício para trabalhos futuros. útil, é uma bênção disfarçada
, haha.

Acho que você gosta

Origin blog.csdn.net/linux_embedded/article/details/129421432
Recomendado
Clasificación