rr - так называемый replay debugger для записи хода исполнения программы, http://rr-project.org

Эксперимент 1 - программа с ошибками

Запись и отслеживание работы программы с fork и pthread_create.

fork_thread.c

#include <stdio.h>

#include <stdlib.h>
#include <errno.h>

#include <unistd.h>
#include <sys/types.h>

#include <sys/wait.h>
#include <pthread.h>

#define ERROR_CREATE_THREAD -11

#define ERROR_JOIN_THREAD   -12
#define SUCCESS        0

void *
helloWorld (void *args)
{
    int i;
    for (i = 0; i < 10; i++)
    {
      printf ("Hello from thread!\n");
      sleep(2);
    }
    return SUCCESS;
}

main ()
{
    pid_t pid;
    int rv;
    pthread_t thread;
    int status;
    int status_addr;
    switch (pid = fork ())
    {
    case -1:
      perror ("fork");
      exit (1);
    case 0:

      status = pthread_create (&thread, NULL, helloWorld, NULL);
      if (status != 0)
    {
      printf ("main error: can't create thread, status = %d\n", status);
      exit (ERROR_CREATE_THREAD);
    }
      printf ("Hello from main!\n");

      printf (" CHILD: Это процесс-потомок!\n");
      printf (" CHILD: Мой PID -- %d\n", getpid ());
      printf (" CHILD: PID моего родителя -- %d\n", getppid ());
      printf
    (" CHILD: Введите мой код возврата  (как можно меньше):");
      scanf (" %d");
      printf (" CHILD: Выход!\n");

      status = pthread_join (thread, (void **) &status_addr);
      if (status != SUCCESS)
    {
      printf ("main error: can't join thread, status = %d\n", status);
      exit (ERROR_JOIN_THREAD);
    }

      printf ("joined with address %d\n", status_addr);

      exit (rv);
    default:
      printf ("PARENT: Это процесс-родитель!\n");
      printf ("PARENT: Мой PID -- %d\n", getpid ());
      printf ("PARENT: PID моего потомка %d\n", pid);
      printf
    ("PARENT: Я жду, пока потомок не вызовет exit()...\n");
      wait (&rv);
      printf ("PARENT: Код возврата потомка:%d\n",
          WEXITSTATUS (rv));
      printf ("PARENT: Выход!\n");
    }
}

Команда сборки, записи работы программы:

    [user1 ~]$ gcc -o ft fork_thread.c -lpthread -ggdb
    [user1 ~]$ rr record ./ft
    [user1 ~]$ du ~/.local/share/rr/ft-4/
    76  /home/user1/.local/share/rr/ft-4/

Для того чтобы просмотреть дерево процессов в записанном образе, необходимо воспользоваться командой ''rr ps'':

    [user1 ~]$ rr ps ~/.local/share/rr/ft-4/
    PID PPID    EXIT    CMD
    19067   --  20  ./ft
    19068   19067   -11 (forked without exec)

По выводу можно определить несколько проблем в программе:

  1. основной процесс возвращает случайное число в статусе завершения - 20
  2. дочерний процесс вообще завершился с SIGSEGV
  3. по столбцу PPID можно отследить родственные связи процессов
  4. столбец CMD показывает каким образом дочерний процесс отпочковался, с fork или c fork+exec

Проверим последовательность выполнения главного процесса:

    [user1 ~]$ rr replay -p 19067
    0x00007f1536ab7430 in _start () from /lib64/ld-linux-x86-64.so.2
    (rr) b main
    Breakpoint 1 at 0x400992: file fork_thread.c, line 32.
    (rr) c
    Continuing.
    Breakpoint 1, main () at fork_thread.c:32
    32    switch (pid = fork ())
    (rr) n
    Hello from thread!
    70        printf ("PARENT: Это процесс-родитель!\n");
    (rr) p pid
    $1 = 19068
    (rr) n
    PARENT: Это процесс-родитель!
    71        printf ("PARENT: Мой PID -- %d\n", getpid ());
    (rr) n
    PARENT: Мой PID -- 19067
    72        printf ("PARENT: PID моего потомка %d\n", pid);
    (rr) n
    PARENT: PID моего потомка 19068
    74      ("PARENT: Я жду, пока потомок не вызовет exit()...\n");
    (rr) n
    PARENT: Я жду, пока потомок не вызовет exit()...
    75        wait (&rv);
    (rr) n
    Hello from main!
     CHILD: Это процесс-потомок!
     CHILD: Мой PID -- 19068
     CHILD: PID моего родителя -- 19067
     CHILD: Введите мой код возврата  (как можно меньше):Hello from thread!
    77            WEXITSTATUS (rv));
    (rr) n
    76        printf ("PARENT: Код возврата потомка:%d\n",
    (rr) p rv
    $2 = 139
    (rr) n
    PARENT: Код возврата потомка:0
    78        printf ("PARENT: Выход!\n");
    (rr) n
    PARENT: Выход!
    80  }
    (rr) n
    __libc_start_main (main=0x40098a `<main>`, argc=1, ubp_av=0x7ffd9e59a958, init=`<optimized out>`, fini=`<optimized out>`, rtld_fini=`<optimized out>`, stack_end=0x7ffd9e59a948) at libc-start.c:308
    308   exit (result);
    (rr) p result
    $3 = 20
    (rr) n
    Program received signal SIGKILL, Killed.
    0x0000000070000002 in ?? ()

Теперь присоединимся ко второму процессу, с аргументом -f, т.к. для этого процесса не было exec, ранее в выводе ''rr ps'' было показано это:

    [user1 ~]$ rr replay -f 19068

И вот результат трассировки, покажу лишь основные моменты:

    (rr) b fork_thread.c:41
    Breakpoint 1 at 0x4009bf: file fork_thread.c, line 41.
    (rr) c
    Continuing.
    Breakpoint 1, main () at fork_thread.c:41
    41        status = pthread_create (&thread, NULL, helloWorld, NULL);
    (rr) n
    Hello from thread!
    PARENT: Это процесс-родитель!
    PARENT: Мой PID -- 19067
    PARENT: PID моего потомка 19068
    PARENT: Я жду, пока потомок не вызовет exit()...
    42        if (status != 0)
    (rr) n
    47        printf ("Hello from main!\n");
    (rr) info threads
    [New Thread 19068.19069]
    Id   Target Id         Frame 
    2    Thread 19068.19069 (mmap_hardlink_2_ft) 0x0000000070000002 in ?? ()

    * 1    Thread 19068.19068 (mmap_hardlink_2_ft) main () at fork_thread.c:47
    (rr) n
    Hello from main!
    51        printf (" CHILD: Это процесс-потомок!\n");
    (rr) n
     CHILD: Это процесс-потомок!
    52        printf (" CHILD: Мой PID -- %d\n", getpid ());
    (rr) n
     CHILD: Мой PID -- 19068
    53        printf (" CHILD: PID моего родителя -- %d\n", getppid ());
    (rr) n
     CHILD: PID моего родителя -- 19067
    55      (" CHILD: Введите мой код возврата  (как можно меньше):");
    (rr) n
    56        scanf (" %d");
    (rr) n
     CHILD: Введите мой код возврата  (как можно меньше):Hello from thread!
    Program received signal SIGSEGV, Segmentation fault.
    0x00007f15363054f2 in _IO_vfscanf_internal (s=`<optimized out>`, format=`<optimized out>`, argptr=argptr@entry=0x7ffd9e59a758, errp=errp@entry=0x0) at vfscanf.c:1826
    1826                *ARG (unsigned int *) = (unsigned int) num.ul;

В результате видим, что запустились два потока, правда второй поток периодически забрасывал свой вывод в отладчик. И проблема произошла на 56-й строке ''scanf'' или при выводе во втором потоке. Но вероятность что это printf выдал ошибку весьма мала. Значит проблема в scanf, куда-то нужно считанное значение загрузить.

Исправляем ошибки таким патчем,

--- fork_thread.c.old   2016-08-21 23:15:01.750331638 +0300
+++ fork_thread.c   2016-08-21 23:15:31.760975983 +0300
@@ -22,7 +22,7 @@ helloWorld (void *args)
   return SUCCESS;
 }

-main ()
+int main ()
 {
   pid_t pid;
   int rv;
@@ -53,7 +53,7 @@ main ()
       printf (" CHILD: PID моего родителя -- %d\n", getppid ());
       printf
    (" CHILD: Введите мой код возврата  (как можно меньше):");
-      scanf (" %d");
+      scanf (" %d", &rv);
       printf (" CHILD: Выход!\n");

       status = pthread_join (thread, (void **) &status_addr);
@@ -77,4 +77,5 @@ main ()
          WEXITSTATUS (rv));
       printf ("PARENT: Выход!\n");
     }
+    return 0;
 }

пересобираем и получаем:

    [user1 ~]$ rr ps ~/.local/share/rr/ft-5/
    PID PPID    EXIT    CMD
    20266   --  0   ./ft
    20267   20266   5   (forked without exec)

Эксперимент 2 - программа с разделяемой памятью

Эксперимент с программой использующей разделяемую память. В описании rr сказано, что разделяемая память в ходит в число ограничений rr. Проверим как это отражается в работе.

Пример взят отсюда http://stackoverflow.com/questions/8186953/shared-memory-with-two-processes-in-c

test2_shared_mem.c

#include <sys/types.h>

#include <sys/ipc.h>
#include <sys/shm.h>

#include <stdio.h>
#include <sys/wait.h>  /* Needed for the wait function */

#include <unistd.h>    /* needed for the fork function */
#include <string.h>    /* needed for the strcat function */

#define SHMSIZE 27
int main() {
   int shmid;
   char *shm;

   if(fork() == 0) {
      shmid = shmget(2009, SHMSIZE, 0);
      shm = shmat(shmid, 0, 0);
      char *s = (char *) shm;

      *s = '\0';  /* Set first location to string terminator, for later append */
      int i;
      for(i=0; i<5; i++) {
         int n;  /* Variable to get the number into */
         printf("Enter number`<%i>`: ", i);
         scanf("%d", &n);
         sprintf(s, "%s%d", s, n);  /* Append number to string */
      }
      strcat(s, "\n");  /* Append newline */
      printf ("Child wrote `<%s>`\n",shm);
      shmdt(shm);
   }
   else {
      /* Variable s removed, it wasn't used */
      /* Removed first call to wait as it held up parent process */
      shmid = shmget(2009, SHMSIZE, 0666 | IPC_CREAT);
      shm = shmat(shmid, 0, 0);
      wait(NULL);
      printf ("Parent reads `<%s>`\n",shm) ;
      shmdt(shm);
      shmctl(shmid, IPC_RMID, NULL);
   }
   return 0;
}

Собираем запускаем записываем:

    [user1 ~]$ gcc -o shmem test2_shared_mem.c -ggdb
    [user1 ~]$ rr record ./shmem 
    rr: Saving execution to trace directory `~/.local/share/rr/shmem-13'.
    Parent reads `<>`
    [user1 ~]$ rr ps
    PID PPID    EXIT    CMD
    19540   --  0   ./shmem
    19541   19540   -11 (forked without exec)
    [user1 ~]$ rr replay -f 19541
    ....
    (rr) b test2_shared_mem.c:14
    Breakpoint 1 at 0x4007a5: file test2_shared_mem.c, line 14.
    (rr) c
    Continuing.
    Breakpoint 1, main () at test2_shared_mem.c:14
    14        shmid = shmget(2009, SHMSIZE, 0);
    (rr) n
    15        shm = shmat(shmid, 0, 0);
    (rr) p shmid
    $1 = -1
    (rr) p errno
    $2 = 2
    (rr) n
    16        char *s = (char *) shm;
    (rr) p shm
    $3 = 0xffffffffffffffff %%`<%%Address 0xffffffffffffffff out of bounds%%>`%%
    (rr) n
    17        *s = '\0';  
    (rr) n
    Program received signal SIGSEGV, Segmentation fault.

Видно, что ''shmid = shmget(2009, SHMSIZE, 0);'' завершилась с кодом -1 и errno=2. Отсутствие проверки привело к тому, что не была перехвачена такая ситуация с ошибкой, в результате падение процесса.

Эксперимент 3 - соединение с базой данных

Пример программы для теста:

Соединение с базой

test3_mysql.c

#include <stdio.h>

#include <stdlib.h>
#include "mysql.h"

MYSQL mysql;
MYSQL_RES *res;
MYSQL_ROW row;
void exiterr(int exitcode)
{
    fprintf(stderr, "%s\n", mysql_error(&mysql));
    exit(exitcode);
}
int main()
{
    uint i = 0;
    if (!(mysql_real_connect(&mysql,NULL,"test","test","test", 0, "/var/lib/mysql/mysql.sock", 0)))
     exiterr(1);
    if (mysql_query(&mysql,"SELECT name,rate FROM emp_master"))
     exiterr(3);
    if (!(res = mysql_store_result(&mysql))) exiterr(4);
    while((row = mysql_fetch_row(res))) {
    for (i=0 ; i < mysql_num_fields(res); i++)
      printf("%s",row[i]);
    printf("\n");
    }
    if (!mysql_eof(res)) exiterr(5);
    mysql_free_result(res);
    mysql_close(&mysql);
    return 0;
}

Собственно сборка и запись:

    [user1 ~]$ gcc -o mtest test3_mysql.c -ggdb -I/usr/include/mysql/ -lmysqlclient -L/usr/lib64/mysql/
    [user1 ~]$ rr record ./mtest
    [user1 ~]$ rr ps ~/.local/share/rr/mtest-0
    PID PPID    EXIT    CMD
    22796   --  0   ./mtest
    [user1 ~]$ rr replay
    ...
    (rr) b main
    Breakpoint 1 at 0x400b03: file test3_mysql.c, line 14.
    (rr) c
    Continuing.
    Breakpoint 1, main () at test3_mysql.c:14
    14    uint i = 0;
    Missing separate debuginfos, use: debuginfo-install mariadb-libs-5.5.50-1.el7_2.x86_64
    (rr) n
    15    if (!(mysql_real_connect(&mysql,NULL,"test","test","test", 0, "/var/lib/mysql/mysql.sock", 0)))
    (rr) n
    17    if (mysql_query(&mysql,"SELECT name,rate FROM emp_master"))
    (rr) n
    19    if (!(res = mysql_store_result(&mysql))) exiterr(4);
    (rr) n
    21      for (i=0 ; i < mysql_num_fields(res); i++)
    (rr) n
    22        printf("%s",row[i]);
    (rr) n
    21      for (i=0 ; i < mysql_num_fields(res); i++)
    (rr) n
    22        printf("%s",row[i]);
    (rr) p *row
    $2 = 0x203a508 "Dan"

Проведу эксперимент и сделаю не чтение, а запись данных в таблицу и помто несколько раз повторю записанный поток исполнения программы:

    [user1 ~]$ gcc -o mtest test3_mysql.c -ggdb -I/usr/include/mysql/ -lmysqlclient -L/usr/lib64/mysql/
    [user1 ~]$ ./mtest
    [user1 ~]$ mysql -utest -ptest -e "select * from test.emp_master"
    | Dan  |   10 |
    | Van  |    5 |
    | Ken  |    5 |
    | Den  |   50 |
    | Rat  |    1 |
    | Fat  |    1 |
    | VVV  |   10 |
    [user1 ~]$ rr record ./mtest
    rr: Saving execution to trace directory `~/.local/share/rr/mtest-1'.
    [user1 ~]$ mysql -utest -ptest -e "select * from test.emp_master"
    | Dan  |   10 |
    | Van  |    5 |
    | Ken  |    5 |
    | Den  |   50 |
    | Rat  |    1 |
    | Fat  |    1 |
    | VVV  |   10 |
    | VVV  |   10 |
    [user1 ~]$ rr replay
    ...
    0x00007fdf0b9c2430 in _start () from /lib64/ld-linux-x86-64.so.2
    (rr) c
    Continuing.
    Program received signal SIGKILL, Killed.
    ...
    [user1 ~]$ mysql -utest -ptest -e "select * from test.emp_master"
    | Dan  |   10 |
    | Van  |    5 |
    | Ken  |    5 |
    | Den  |   50 |
    | Rat  |    1 |
    | Fat  |    1 |
    | VVV  |   10 |
    | VVV  |   10 |
    [user1 ~]$ rr replay
    ...
    0x00007fdf0b9c2430 in _start () from /lib64/ld-linux-x86-64.so.2
    (rr) c
    Continuing.
    ...
    Program received signal SIGKILL, Killed.
    ...
    [user1 ~]$ mysql -utest -ptest -e "select * from test.emp_master"
    | Dan  |   10 |
    | Van  |    5 |
    | Ken  |    5 |
    | Den  |   50 |
    | Rat  |    1 |
    | Fat  |    1 |
    | VVV  |   10 |
    | VVV  |   10 |

    [user1 ~]$ 

Как можно увидеть во время replay данные в базе данных не изменились.

Эксперимент 4 - запись в файл

Проверим, безопасно ли воспроизведение записи при изменении файла. Вот пример программы:

Пример программы по работе с файлом

test4_file.c

#include <stdio.h>

#include <time.h>

int main(){
    FILE *fp = NULL;
    fp = fopen("t1.txt","a");
    if (fp!=NULL) {
        fprintf(fp, "%d\n", (int)time(NULL));
        fclose(fp);
    }
    return 0;
}

И действия в по проверке:

    [user1 ~]$ gcc -o tm test4_file.c -ggdb
    [user1 ~]$ ./tm
    [user1 ~]$ rr record ./tm
    rr: Saving execution to trace directory `~/.local/share/rr/tm-0'.
    [user1 ~]$ cat t1.txt 
    1471957954
    [user1 ~]$ ./tm
    [user1 ~]$ cat t1.txt 
    1471957954
    1471957979
    [user1 ~]$ rr replay
    ...
    0x00007f1fb9c41430 in _start () from /lib64/ld-linux-x86-64.so.2
    (rr) b main
    Breakpoint 1 at 0x400618: file test4_file.c, line 5.
    (rr) n
    Single stepping until exit from function _start,
    which has no line number information.

    Breakpoint 1, main () at test4_file.c:5
    5       FILE *fp = NULL;
    (rr) n
    6       fp = fopen("t1.txt","a");
    (rr) n
    7       if (fp!=NULL) {
    (rr) p fp
    $1 = (FILE *) 0x1d48040
    (rr) n
    8           fprintf(fp, "%d\n", (int)time(NULL));
    (rr) n
    9           fclose(fp);
    (rr) n
    11      return 0;
    ...
    (rr) q
    [user1 ~]$ cat t1.txt 
    1471957954
    1471957979

По выводу файла, видно что все операции безопасные, вес действия отлично ловятся rr.

Эксперимент 5 - работа с семафорами

Первый пример программы, это работа с семафорами в рамках одного процесса и работа с семафорами в рамках разных процессов. В обоих случаях проблем записи процесса не обнаружилось(семафор использовался глобальный, а не локальный)

Примеры программ были взяты здесь и здесь

Пример воспроизведения записанной программы сервера и момент открытия семафора:

    Breakpoint 1, main (argc=1, argv=0x7ffc0e367d68) at sem_server.c:34
    34      sem_key = ftok("./sem_server.c", 42);
    (rr) when
    Current event: 152
    (rr) n
    37      sem_fd = open(SEM_KEY_FILE, O_WRONLY | O_TRUNC | O_EXCL | O_CREAT, 0644);
    (rr) 
    38      if (sem_fd < 0) {
    (rr) p sem_fd
    $1 = 3
    (rr) q
    A debugging session is active.

а также пример другого создания семафора:

    Breakpoint 1, main () at test4_sem.c:28
    28      i[0] = 0; /* argument to threads */
    (rr) 
    29      i[1] = 1;
    (rr) 
    31      sem_init(&mutex, 1, 1);      /* initialize mutex to 1 - binary semaphore */
    (rr) b handler
    Breakpoint 2 at 0x40090a: file test4_sem.c, line 51.
    (rr) p mutex
    $1 = {__size = '\000' `<repeats 31 times>`, __align = 0}
    (rr) c
    Continuing.
    [New Thread 19028.19029]
    [Switching to Thread 19028.19029]
    Breakpoint 2, handler (ptr=0x7ffe0187d2e0) at test4_sem.c:51
    51      x = *((int *) ptr);
    (rr) n
    52      printf("Thread %d: Waiting to enter critical region...\n", x);
    (rr) 
    Thread 0: Waiting to enter critical region...
    53      sem_wait(&mutex);       /* down semaphore */
    (rr) 
    55      printf("Thread %d: Now in critical region...\n", x);
    (rr) p mutex
    $2 = {__size = '\000' `<repeats 31 times>`, __align = 0}

Добавить комментарий

Следующая запись Предыдущая запись