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

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

Пример #1 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;
}
<Code>

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

<code>
[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}