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)
По выводу можно определить несколько проблем в программе:
- основной процесс возвращает случайное число в статусе завершения - 20
- дочерний процесс вообще завершился с SIGSEGV
- по столбцу PPID можно отследить родственные связи процессов
- столбец 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}