Obtenez les informations de la pile avant que le programme ne plante grâce à la trace sous Linux

Lien vers le texte original : http://www.ccccxy.top/coding/archives/2020/10/23/linux_backtrace_87/
Bienvenue dans les conseils des maîtres !

1. Introduction à la fonction backtrace( )

/* Store up to SIZE return address of the current program state in
   ARRAY and return the exact number of values stored.  */
extern int backtrace (void **__array, int __size) __nonnull ((1));
/*该函数用与获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针数组。参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,最大不超过size大小在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址。*/


/* Return names of functions from the backtrace list in ARRAY in a newly
   malloc()ed memory block.  */
extern char **backtrace_symbols (void *const *__array, int __size)
     __THROW __nonnull ((1));
/*
    backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组. 参数buffer应该是从backtrace函数获取的数组指针,size是该数组中的元素个数(backtrace的返回值),函数返回值是一个指向字符串数组的指针,它的大小同buffer相同.每个字符串包含了一个相对于buffer中对应元素的可打印信息.它包括函数名,函数的偏移地址,和实际的返回地址
    backtrace_symbols生成的字符串都是malloc出来的,但是不要最后一个一个的free,因为backtrace_symbols是根据backtrace给出的call stack层数,一次性的malloc出来一块内存来存放结果字符串的,所以,像上面代码一样,只需要在最后,free backtrace_symbols的返回指针就OK了。这一点backtrace的manual中也是特别提到的。
注意:如果不能为字符串获取足够的空间函数的返回值将会为NULL
*/


/**/
/* This function is similar to backtrace_symbols() but it writes the result
   immediately to a file.  */
extern void backtrace_symbols_fd (void *const *__array, int __size, int __fd)
     __THROW __nonnull ((1));
/*
backtrace_symbols_fd与backtrace_symbols 函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行.它不需要调用malloc函数,因此适用于有可能调用该函数会失败的情况。
*/

Exemple:

void dump(void)
{
    
    
        int i = 0, nptrs = 0;
        void *buf[BACKTRACE_SIZE];	//定义用于存放获取到堆栈信息的指针数组
        char **strings;
        nptrs = backtrace(buf, BACKTRACE_SIZE);	//返回获取到的实际堆栈信息指针数

        printf("backtrace() returned %d addresses\n", nptrs);
        strings = backtrace_symbols(buf, nptrs);	//将buf中存放的信息转换为可打印的字符串信息
        if (strings == NULL)
        {
    
    
                perror("backtrace_symbols");
                exit(EXIT_FAILURE);
        }
        for (i = 0; i < nptrs; i++)
        {
    
    
                printf(" [%02d] %s\n", i, strings[i]);
        }
        free(strings);	//释放一整块存放信息的字符串内存
}

2. Capturez le signal d'exception du système et sortez la pile d'appels via la fonction de signal signal ( )

void (*signal(int sig, void (*func)(int)))(int)

sig– Codes de signal utilisés comme variables dans les gestionnaires de signaux :

macro Signal
SIGABRT (Signal Abort) Le programme s'est terminé anormalement.
SIGFPE (Signal Floating-Point Exception) Une erreur s'est produite dans une opération arithmétique, telle qu'une division par zéro ou un dépassement de capacité (pas nécessairement une opération en virgule flottante).
JOINT (Signal Illegal Instruction) Image de fonction illégale, telle qu'une instruction illégale, généralement due à une variante du code ou à une tentative d'exécution de données.
SIGINT (Signal Interrupt) Les signaux d'interruption, tels que ctrl-C, sont généralement générés par l'utilisateur.
SIGSEGV (Violation de segmentation du signal) Accès à la mémoire illégal, tel que l'accès à des unités de mémoire inexistantes.
TERME CIBLE (Signal Terminate) Un signal de demande de terminaison envoyé à ce programme.

func– Il peut s'agir d'une adresse de fonction personnalisée ou de l'une des fonctions prédéfinies :

fonction prédéfinie illustrer
SIG_DFL gestionnaire de signal par défaut
SIG_IGN ignorer le signal

3. Exemple d'essai

Il est divisé en deux fichiers, le fichier add.c qui implémente la fonction d'augmentation de la valeur de 1, celui qui inclut les informations de backtrace de sortie et le backtrace.c qui inclut la fonction principale de l'entrée du programme.

add.c:

#include <stdio.h>
#include <stdlib.h>

int add1(int num)
{
    
    
        int ret = 0;
        int *pTmp = NULL;
        *pTmp = 1;	//对未分配内存空间的指针进行赋值,模拟访问非法内存段错误
        ret = num + *pTmp;
        return ret;
}

int add(int num)
{
    
    
        int ret = 0;
        ret = add1(num);
        return ret;
}

backtrace.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <execinfo.h>

#define BACKTRACE_SIZE 16

extern int add(int num);

void dump(void)
{
    
    
        int i = 0, nptrs = 0;
        void *buf[BACKTRACE_SIZE];
        char **strings;
        nptrs = backtrace(buf, BACKTRACE_SIZE);

        printf("backtrace() returned %d addresses\n", nptrs);
        strings = backtrace_symbols(buf, nptrs);
        if (strings == NULL)
        {
    
    
                perror("backtrace_symbols");
                exit(EXIT_FAILURE);
        }
        for (i = 0; i < nptrs; i++)
        {
    
    
                printf(" [%02d] %s\n", i, strings[i]);
        }
        free(strings);
}

void signal_handler(int signo)
{
    
    
#if 0
        char buf[64] = {
    
    0};
        sprintf(buf, "cat /proc/%d/maps", getpid());
        system((const char*)buf);
#endif
        printf("\n=================>>>catch signal %d<<<=====================\n", signo);
        printf("Dump stack start...\n");
        dump();
        printf("Dump stack end...\n");
        signal(signo, SIG_DFL);
        raise(signo);
}

int main(int argc, char **argv)
{
    
    
        int sum = 0;
        signal(SIGSEGV, signal_handler);
        sum = add(3);
        printf("sum = %d\n", sum);
        return 0;
}
  1. Analyse et positionnement des messages d'erreur en cas de liaison statique

    [xuanchen@rabbitmq1 backtrace]$ ./backtrace
    
    =================>>>catch signal 11<<<=====================
    Dump stack start...
    backtrace() returned 8 addresses
     [00] ./backtrace(dump+0x2d) [0x400aaa]
     [01] ./backtrace(signal_handler+0x2e) [0x400b70]
     [02] /lib64/libc.so.6(+0x35270) [0x7f146f30a270]
     [03] ./backtrace(add1+0x1a) [0x400bfc]
     [04] ./backtrace(add+0x1c) [0x400c31]
     [05] ./backtrace(main+0x2f) [0x400bc4]
     [06] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f146f2f6c05]
     [07] ./backtrace() [0x4009b9]
    Dump stack end...
    Segmentation fault (core dumped)
    

    Le contenu de libc.so commence à être appelé à la ligne 8, on peut voir que la pile à la ligne 9 est la pile d'erreurs, [03] ./backtrace(add1+0x1a) [0x400bfc].

    On peut voir que l'erreur se produit dans la fonction add1, et l'adresse de ligne correspondante est 0x400bfc, et le décalage par rapport à l'adresse de la fonction add1 est 0x1a.

    addr2line -e <elf文件> <对应地址>À ce stade , les informations de ligne de code correspondantes peuvent être obtenues via l'outil intégré du compilateur , et le résultat est le suivant :

    [xuanchen@rabbitmq1 backtrace]$ addr2line -e backtrace 0x400bfc
    /home/xuanchen/test/backtrace/add.c:8
    

    Le résultat montre que la 8e ligne dans add.c où l'erreur se produit, c'est-à-dire le commentaire dans l'exemple de code ci-dessus.

  2. Analyse et positionnement des informations d'erreur dans le cas d'un lien dynamique

    Dans le cadre d'une liaison dynamique, le fichier add.c doit être compilé pour générer .soun type de bibliothèque dynamique, comme indiqué ci-dessous :

    [xuanchen@rabbitmq1 backtrace]$ gcc -g -rdynamic -O0 add.c -fPIC -shared -o libadd.so
    [xuanchen@rabbitmq1 backtrace]$ ls
    libadd.so
    

    Puis compilez et générez le fichier exécutable correspondant sous forme de lien dynamique :

    [xuanchen@rabbitmq1 backtrace]$ gcc -g -rdynamic -O0 -L. -ladd backtrace.c -o backtrace
    
    

    Le résultat après exécution est :

    [xuanchen@rabbitmq1 backtrace]$ ./backtrace
    00400000-00401000 r-xp 00000000 fd:04 1099675474                         /home/xuanchen/test/backtrace/backtrace
    00601000-00602000 r--p 00001000 fd:04 1099675474                         /home/xuanchen/test/backtrace/backtrace
    00602000-00603000 rw-p 00002000 fd:04 1099675474                         /home/xuanchen/test/backtrace/backtrace
    7f566891c000-7f5668ad4000 r-xp 00000000 fd:00 33624368          /usr/lib64/libc-2.17.so
    7f5668ad4000-7f5668cd4000 ---p 001b8000 fd:00 33624368          /usr/lib64/libc-2.17.so
    7f5668cd4000-7f5668cd8000 r--p 001b8000 fd:00 33624368          /usr/lib64/libc-2.17.so
    7f5668cd8000-7f5668cda000 rw-p 001bc000 fd:00 33624368          /usr/lib64/libc-2.17.so
    7f5668cda000-7f5668cdf000 rw-p 00000000 00:00 0 
    7f5668cdf000-7f5668ce0000 r-xp 00000000 fd:04 1099675477                 /home/xuanchen/test/backtrace/libadd.so
    7f5668ce0000-7f5668edf000 ---p 00001000 fd:04 1099675477                 /home/xuanchen/test/backtrace/libadd.so
    7f5668edf000-7f5668ee0000 r--p 00000000 fd:04 1099675477                 /home/xuanchen/test/backtrace/libadd.so
    7f5668ee0000-7f5668ee1000 rw-p 00001000 fd:04 1099675477                 /home/xuanchen/test/backtrace/libadd.so
    7f5668ee1000-7f5668f02000 r-xp 00000000 fd:00 33624361           /usr/lib64/ld-2.17.so
    7f56690cb000-7f56690ce000 rw-p 00000000 00:00 0 
    7f5669101000-7f5669102000 rw-p 00000000 00:00 0 
    7f5669102000-7f5669103000 r--p 00021000 fd:00 33624361           /usr/lib64/ld-2.17.so
    7f5669103000-7f5669104000 rw-p 00022000 fd:00 33624361           /usr/lib64/ld-2.17.so
    7f5669104000-7f5669105000 rw-p 00000000 00:00 0 
    7ffcca992000-7ffcca9b4000 rw-p 00000000 00:00 0                          [stack]
    7ffcca9e7000-7ffcca9e9000 r-xp 00000000 00:00 0                          [vdso]
    ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
    
    =================>>>catch signal 11<<<=====================
    Dump stack start...
    backtrace() returned 8 addresses
     [00] ./backtrace(dump+0x2d) [0x400b8a]
     [01] ./backtrace(signal_handler+0x6e) [0x400c90]
     [02] /lib64/libc.so.6(+0x35270) [0x7f5668951270]
     [03] libadd.so(add1+0x1a) [0x7f5668cdf6cf]
     [04] libadd.so(add+0x1c) [0x7f5668cdf704]
     [05] ./backtrace(main+0x2f) [0x400ce4]
     [06] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f566893dc05]
     [07] ./backtrace() [0x400a99]
    Dump stack end...
    Segmentation fault (core dumped)
    

    À ce stade, si la méthode du lien statique est toujours utilisée, les résultats obtenus sont les suivants :

    [xuanchen@rabbitmq1 backtrace]$ addr2line -e backtrace 0x7f5668cdf6cf
    ??:0
    [xuanchen@rabbitmq1 backtrace]$ addr2line -e libadd.so 0x7f5668cdf6cf
    ??:0
    

    En effet, la fonction add1 est chargée et liée dans le fichier exécutable via une bibliothèque dynamique. La bibliothèque dynamique n'est chargée dynamiquement que lorsque le programme est en cours d'exécution, et le code de la bibliothèque dynamique est un code indépendant de l'adresse, et les informations de code correspondantes ne peut pas être trouvé par cette méthode d'adressage. .

    Ici, nous obtenons les informations cartographiques du processus en cours en appelant la fonction système

    char buf[64] = {0};
    sprintf(buf, "cat /proc/%d/maps", getpid());
    system((const char*)buf);
    

    On peut voir que la plage d'adresses correspondant à libadd.so est la suivante :

    7f5668cdf000-7f5668ce0000 r-xp 00000000 fd:04 1099675477                 /home/xuanchen/test/backtrace/libadd.so
    7f5668ce0000-7f5668edf000 ---p 00001000 fd:04 1099675477                 /home/xuanchen/test/backtrace/libadd.so
    7f5668edf000-7f5668ee0000 r--p 00000000 fd:04 1099675477                 /home/xuanchen/test/backtrace/libadd.so
    7f5668ee0000-7f5668ee1000 rw-p 00001000 fd:04 1099675477                 /home/xuanchen/test/backtrace/libadd.so
    

    L'adresse dans les informations de pile est 0x7f5668cdf6cf, ce qui correspond à la plage d'adresses de la ligne 1, donc l'adresse de code correcte est 0x7f5668cdf6cf - 7f5668cdf000 = 0x6cf. À ce stade, affichez via addr2line :

    [xuanchen@rabbitmq1 backtrace]$ addr2line -e libadd.so 0x6cf
    /home/xuanchen/test/backtrace/add.c:8
    

    Vous pouvez obtenir le numéro de ligne du code où l'erreur s'est produite.

Guess you like

Origin blog.csdn.net/qq_38894585/article/details/109310194