Russian Language English Language

3. Модели и методы для обоснования выбора состава программных средств ВС

3.1 ПРОБЛЕМЫ ОКОННЫХ СИСТЕМ, ОРИЕНТИРОВАННЫХ НА СЕТЕВОЕ ВЗАИМОДЕЙСТВИЕ, ДЛЯ UNIX СОВМЕСТИМЫХ ОПЕРАЦИОННЫХ СИСТЕМ

3.2 РАБОТА В КОМАНДНОМ ИНТЕРФЕЙСЕ ПОЛЬЗОВАТЕЛЯ UNIX-СИСТЕМ

3.3 ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ В РАМКАХ UNIX-СИСТЕМ


Экспресс информация

Редколлегия журнала

Подписка на новости

Гостевая книга

Предоставление материалов

Письмо в редакцию

На начало


2017, Номер 2 ( 31)



Place for sale
ЛАБОРАТОРНАЯ РАБОТА №4

BC/NW 2017 № 2 (31):3.2

ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ В РАМКАХ UNIX-СИСТЕМ

Орлов Д.А., Филатов А.В.

Изложенный в работе материал имеет учебно-практическое назначение.

 

Цель: Изучение системных команд ОС UNIX, обеспечивающих идентификацию процессов, их синхронизацию и обмен информацией между ними в процессе работы. Разработка взаимодействующих между собой программ, обеспечивающих синхронизированное решение общей задачи в рамках иерархической системы "родитель-потомки" или системы  равноправных взаимодействующих процессов.

Также в самом конце предполагается краткое знакомство gdb-отладчиком.

 

Взаимодействие процессов в ОС UNIX.

 

Само назначение  ОС UNIX в качестве системы массового обслуживания обусловило установку в ней мощного механизма для обеспечения  взаимо действия между  различными проходящими в ней процессами. В самом деле, первоначально цель разработки состояла в том, чтобы обеспечить в машине одновременную работу  многих  пользователей,  ведущих  диалог с ЭВМ или между собой посредством терминалов. Это требовало:

-с одной стороны, обеспечить надежный обмен информацией между машиной и пользователем так, чтобы вся информация точно находила своего адресата;

-с другой стороны, обеспечить надежную, быструю и бесперебойную передачу информации между процессами пользователя и системными процессами, причем последние должны одновременно обслуживать многих пользователей.

В ОС UNIX решение основано на том, что при запуске системы создается главный порождающий процесс (ПРОЦЕСС 1), который инициализирует работу управляющей программы ОБОЛОЧКИ, через которую пользователи и создают свои  процессы. Созданные ОБОЛОЧКОЙ пользовательские процессы могут, в свою очередь, также порождать другие процессы. Таким образом создается иерархия выполняемых процессов. Причем каждый из порожденных процессов при своем создании наследует все свойства  порождающего процесса (за исключением  тех,  которые последний "сознательно" не желает ему отдавать). Вместе с тем,  порожденный процесс в ходе работы может приобрести некоторые новые свойства (приоритетность, ресурсы, информационные блоки и т.д.), которые будут характеризовать его локальные свойства. Как правило, удаление процесса-предка ведет к удалению связанных с ним потомков, хотя это свойство не является всеобщим, т.к. в ОС UNIX в этом случае предусмотрен механизм переподчинения процессов-потомков, потерявших своего предка, его вышестоящим предком.

Для идентификации любой порождаемый в ОС UNIX процесс получает при создании уникальный номер - ИДЕНТИФИКАТОР ПРОЦЕССА (PID).  При запуске PID передается предку. Свой PID процесс может узнать командой

                  GETPID().

Для определения PID порождащего процесса служит команда

                  GETPPID().

Процессы, запускаемые одним предком, объединяются в одну группу, идентификатором которой будет идентификатор процесса-предка PPID. Идентификаторы процессов можно переустанавливать специальными командами ОС UNIX, с которыми следует познакомиться самостоятельно по ранее рекомендованной литературе.

Если процесс завершил работу или был удален досрочно, ОС очистит принадлежащие ему зоны памяти, закроет все связанные с ним файлы и исключит его из очереди процессов. Нормальное завершение процесса выполняется либо  оператором RETURN,  входящим в функцию MAIN программы, либо системным вызовом

                 EXIT (STATUS),

который может появиться в любом месте программы. При этом процессу-потомку будет возвращено значение, которое устанавливается в младшем байте целой переменной STATUS. Процесс-предок может ожидать завершения своего потомка, если в нем установлен системный вызов

                 PID = WAIT(STATUS),

где PID - переменная целого типа,  соответствующая PID завершающегося процесса.

STATUS - в старшем байте содержит код, указываемый в младшем байте операнда EXIT, а в старшем байте устанавливается системный код завершения, устанавливаемый ядром ОС.

Для обеспечения синхронизации выполняемых в ОС UNIX процессов служат СИГНАЛЫ, которые процессы могут передавать друг другу вне зависимости от родственных связей и обрабатывать. Сигналы позволяют определить в процессе посылку в них управляющих символов с терминалов (SIGINT, SIGOUT,  SIGHUP), наличие некоторых аварийных ситуаций (SIGKILL, SIGTRAP, SIGFPE, SIGBUS, SIGSEGV, SIGSYS, SIGPIPE), возникновение некоторого заранее ожидаемого события (SIGALARM), появление непредусмотренного события (SIGTERM, SIGGLD, SIGPWR) или вызова со стороны другого пользователя (SIGUSR1, SIGUSR2). Подробнее с сигналами познакомиться самостоятельно по рекомендованной литературе.

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

                   KILL (PID, SIG),

где  SIG -   посылаемый сигнал,

PID - идентификатор процесса. Если PID = 0, то сигнал будет послан всем процессам, имеющим общий с пославшим процессом идентификатор группы.

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

            #include <signal.h>

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

            int sig;  - сигнал, на который следует реагировать после вы-

                        зова данной функции;

            int (*func)() - функция, которая будет вызвана после получе-

                             ния указанного сигнала.

 ПРИМЕЧАНИЕ: вызовы WAIT, KILL, SIGNAL выполняются только один раз, т.о.

             если необходимо их повторное выполнение, то это необходимо

             определить в программе явно.

      В данном системном вызове имеется две специальные функции

      signal (sig,  SIG_IGN) - ранее определенный сигнал sig будет игно-

                               рирован.

      signal (sig,  SIG_DEL) - реакция на sig как ви  выполненной  ранее

                               функции или принятое по умолчанию.

Сигналы выполняют роль системы управления процессом передачи информации. Сама же передача информации осуществляется поименованным или непоименованным каналами.

Системный вызов

                    PIPE (FD)

лишь декларирует непоименованный канал. FD - массив из двух элементов, первый из них открывает канал на ввод, а второй на вывод. В качестве примера приведены две программы для обеспечения ввода информации в один файл с нескольких терминалов. ВЕДУЩАЯ программа для каждого канала, с которого будет вводится информация, порождает процесс, описанный ВЕДОМОЙ программой. После этого процесс "ведущей программы" перейдет в ожидание сигнала о передаче информации от "ведомой" программы. "Ведомая" программа выполняет ввод информации со своего терминала и ее передачу по каналу, уведомив "ведущую" программу сигналом SIGFPE.

 

             /*

              *   ПРОЦЕСС-ПОТОМОК

              */

             #include <signal.h>

             #include <sys/types.h>

             #include <sys/tty.h>

             main (argc,argv)

             int argc;

             char *argv[];

             {

              int n;

              char buffer[TTYHOG];

              for(;;)

                   {n = read(0,buffer,TTYHOG);

                    if(n == 0)

                      { /* сброс из основной задачи */

                       kill (atoi(argv[1]), SIGFPE);

                       close(1);

                       exit(0);

                      }

                        kill(atoi(argv[1]),SIGTERM);

                        write(1, buffer, n);

                   }

             }

 

        /*

         *   ПРОЦЕСС-ПРЕДОК

         */

         #include <signal.h>

         #include <stdio.h>

         #include <sys/types.h>

         #include <sys/tty.h>

         int fd[2];

         char pids[TTYHOG];

         int slave_count;

         /*

          * программа обработки сигнала SIGTERM

          */

          service()

             {

              int n;

              char buffer[TTYHOG];

              signal(SIGTERM,service);

              n = read(fd[0], buffer, TTYHOG);

              write(1, buffer, n);

             }

         /*

          * программа завершения по сигналу SIGFPE

          */

          sdrop()

             {

              signal(SIGFPE, sdrop);

             if(--slave_count == 0)

                 exit(0);

             }

         /*

          * программа инициализации потомка

          */

          slave(tty)

          char *tty;

             {

              if(fork() == 0)

               { /* переназначение стандартного ввода на терминал */

                 int tfd;

                 close(0);

                 tfd = open(tty,0);

                 if(tfd != 0)

                   {

                     fprintf(stderr,"bad tty %S\n",tty);

                     exit(1);

                   }

              /* переназначение стандартного вывода на канал */

             close(1);

             dup(fd[1]);

             close(fd[0]);

             close(fd[1]);

             /* вызов потомка */

             execl("./slave","slave",pids,0);

             fprintf(stderr,"can't exec slave on %S\n",tty);

             exit(1);

           }

       }

 

        /*

         * основная программа

         */

         main(argc, argv)

         int argc;

         char *argv[];

          {

            int i;

             signal(SIGTERM,service);

             signal(SIGFPE,sdrop);

             sprintf(pids,"%06d",getpid());

             pipe(fd);

             slave_count = argc - 1;

             for(i=1;i<argc;i++)

                  slave(argv[i]);

             close(fd[1]);

             for(;;)

                    pause();

          }

 

Непоименованные каналы, в основном, обеспечивают связь между процессами-родственниками. Кроме того, в непоименованных каналах затруднительно организовать двусторонний обмен информацией. Поэтому в OС UNIX были введены поименованные каналы. Если между двумя любыми процессами создать два таких канала, то между ними можно осуществить двусторонний обмен информацией, который также синхронизируется сигналами.

Именованный канал можно создать системным вызовом

            mknod (namefile, IFIFO|0666, 0)

где namefile - имя канала;

0666 - к каналу разрешен доступ на запись и на чтение любому запросившему процессу.

Именованный канал может создать администратор системы командой

        $/etc/mknod namefile p

        $chmod 666 namefile

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

 

        /*

         *  ДАННЫЕ, ОБЩИЕ ДЛЯ ВСЕХ ПРОГРАММ

         *  PACKET.H

         */

         struct packet

             {

              int pk_pid; /* идентификатор процесса */

              int pk_blk; /* номер блока файла */

              int pk_code;/* код запроса */

             }

        /* коды запросов */

         #define RQ_READ  0  /* запрос на чтение */

         #define CONNECT 1  /* запрос на обслуживание */

         #define SENDPID 2  /* ответ на запрос о подсоединении */

        /* имена каналов */

         #define DNAME  "datapipe"

         #define CNAME  "ctrlpipe"

        /* размер блока файла */

         #define PBUFSIZE 512

 

        /*

         * ПРОГРАММА ОБРАБОТКИ БАЗЫ ДАННЫХ

         */

         #include <stdio.h>

         #include <signal.h>

         #include <fcntl.h>

         #include "packet.h"

         int datapipe,ctrlpipe,datafile,got_sig;

        /* маршрут базы данных */

         char *dataname = "usr/lib/tmac/tmac.c";

         handler()

             {

               signal(SIGUSR1,handler);

               got_sig++;

             }

 

 

         process(pkp,spkp)

         struct packet *pkp,spkp;

          {

           char pbuf[PBUFSIZE];

        /*

         * запись в файл FIFO пройдет ТОЛЬКО если он уже открыт на чтение

         */

           datapipe = open(DNAME,O_WRONLY|ONDELAY);

           switch(pkp->pk_code)

             {

              case CJNNECT:

                           write(datapipe,spkp,sizeof(struct packet));

                           break;

              case RQ_READ:

                           lseek(datafile,pkp->pk_blk*PBUFSIZE,0);

                           read(datafile,pbuf,PBUFSIZE);

                           write(datapipe,pbuf,PBUFSIZE);

                           break;

              default:

                           fprintf(stderr,"unknown packet code\n");

                           exit(1);

             }

             got_sig = 0;

             kill(pkp->pk_pid,SIGUSR1);

             while(!got_sig);

             close(datapipe);

          }

 

          main()

             {

               struct packet pk;

               struct packet sendpk;

               ctrlpipe = open(CNAME,O_RDONLY|O_NDELAY);

               datafile = open(dataname,0);

               handler();

             for(;;)

                    {

                      int n;

                      while(n = read(ctrlpipe, &pk, sizeof(pk)))

                        {

                          process(&pk, &sendpk);

                        }

                    }

             }

 

 

        /*

         *  ПРОГРАММА ТРАНСЛЯЦИИ ЗАПРОСА ПОЛЬЗОВАТЕЛЯ

         */

 

        #include <stdio.h>

        #include <signal.h>

        #include "packet.h"

        int datapipe, ctrlpipe;

        handler()

             { /* перехват сигналов*/

               signal(SIGUSR1,handler);

               got_sig++;

             }

 

        /*

         * программа для связи с процессом, обслуживающим базу данных

         */

         #include <stdio.h>

         #include <signal.h>

         #include <fcntl.h>

         #include "packet.h"

         int datapipe, ctrlpipe;

         extern int got_sig;

         connect()

             {

              struct packet pk;

              datapipe = open(DNAME,O_RDONLY|O_NDELAY);

              ctrlpipe = open(CNAME,O_WRONLY|O_NDELAY);

              if(datapipe < 0 || ctrlpipe < 0)

                 {

                   fprintf(stderr,"cannot open pipe\n");

                   exit(1);

                 }

              pk.pk_pid = getpid();

              pk.pk_code = CONNECT;

              got_sig = 0;

              write(ctrlpipe, &pk, sizeof(pk));

              while( !got_sig);

              read(datapipe, &pk, sizeof(pk));

              kill(pk.pk_pid, SIGUSR1);

              return(pk.pk_pid);

             }

 

         /*

          * функция для считывания одного блока из базы данных

          */

         #include <stdio.h>

         #include <signal.h>

         #include "packet.h"

         extern int datapipe, ctrlpipe, got_sig;

         request (ptr, blk, spid)

         char *ptr;

         int blk;

         int spid;

             {

               struct packet pk;

               pk.pk_pid = getpid();

               pk.pk_blk = blk;

               pk.pk_code = RQ_READ;

               got_sig = 0;

               write(ctrlpipe, &pk, sizeof(pk));

               while (!got_sig);

               read (datapipe, ptr, PBUFSIZE);

               kill(spid, SIGUSR1);

             }

 

        /*

         * главная программа

         */

         main (argc,argv)

         int argc;

         char *argv[];

             {

               int spid; /* идентификатор сервера */

               int blk;

               char buffer[PBUFSIZE];

               blk = atoi(argv[1]);

        /*   перехват сигнала    */

               handler();

               spid = connect();

               for(;;)

                      {

                        request (buffer, blk, spid);

                        fwrite (buffer, PBUFSIZE, 1, stdout);

                      }

             }

     

Создать канал можно и между процессами,  запускаемыми в SHELL. Как это сделать разберите самостоятельно по рекомендованной ранее книге Дунаева.

 

Контрольные вопросы

1.     Что такое процесс и чем он отличается от программы?

2.     Как порождаются процессы в ОC UNIX?

3.     Что такое программный идентификатор процесса? Опишите системные вызовы для работы с ним.

4.     Приведите примеры, где необходимо использовать программный идентификатор процесса.

5.     Как запустить канал непосредственно из SHELL?

6.     Опишите формат системного вызова PIPE и особенности его работы.

7.     Опишите формат системного вызова MKNOD и особенности его работы.

8.     Что такое сигналы и как они используются в ОС UNIX?

9.     Опишите работу системного вызова KILL.

10.                    Опишите работу системного вызова SIGNAL.

11.                    Какие ограничения существуют на длину файла канала?

12.                    В чем особенности и отличия поименованных и непоименованных каналов?

13.                    Сколько сигналов необходимо использовать для синхронизации процессов использующих каналы?

14.                    Каков формат и какие методы использования системного вызова WAIT?

15.                    Опишите работу системного вызова FORK.

16.                    Опишите системный вызов EXEC и его модификации.

17.                    Можно ли использовать системный вызов PIPE для обмена информацией между процессами, не состоящими в родственных отношения? Если можно, то как это выполнить?

18.                    Какие действия выполняет системный вызов EXIT и каков его формат?

 

Задания лабораторной работы.

1)    Перед выполнением работы получите у администратора системы право на работу в системе, а при необходимости и имя поименованного канала.

2)    Зарегистрируйтесь в системе.

3)    Разработайте два процесса, из которых ПРОЦЕСС-ПРЕДОК  порождает ПРОЦЕСС-ПОТОМОК и через непоименованный канал взаимодействует с ним, передавая содержимое некоторого файла поблочно. Если предок не может войти в файл, он должен передать сообщение на вывод STDERR. Потомок получает блоки файла и их выводит на экран, используя STDOUT. По окончании файла предок передает потомку сигнал завершения и после окончания работы потомка (о чем получает сигнал) прекращает свою работу.

4)    Задачу 3 решите при условии, что порождаются два процесса-потомка. Для идентификации выводимой информации каждый из процессов-потомков должен выдать на экран свой PID.

5)    Решите задачу 3 при условии, что потомок снимает информацию из файла, а предок передает ее на экран.

6)    Решите задачу 5 при условии, что два потомка снимают информацию из двух разных файлов, а предок передает информацию на экран, снабдив идентификатором процесса-потомка, приславшего ее.

7)    В рамках задачи 3 организуйте работу с файлом, записи которого переменной длины. Для проведения эксперимента на машине можно использовать, например, описанную в предыдущей лабораторной работе процедуру COPYFILE и создавать файл с клавиатуры.

8)    Перекопируйте файл на экран или в другой файл непосредственно через SHELL. Дополнительные условия возьмите из первой задачи, которую будет решать ваша бригада.

9)    Разработать два процесса ПОТОМОК и ПРЕДОК, связанные непоименованным каналом так, чтобы потомок поблочно забирал информацию из некоторого файла и каждый блок передавал предку. Потомок после передачи блока информации на экран должен уничтожиться, сообщив об этом предку. Т.о. предок, чтобы получить следующий блок информации должен заново создать потомка.

10)         Задачу 9 модернизируйте так, чтобы предок забирал информацию из файла, а потомок ее передавал на экран, а после этого предок запускал нового потомка, перед передачей каждого блока информации.

11)         Задачу 9 переработайте для работы с блоками переменной длины.

12)         Задачу 10 переработайте для работы с блоками переменной длины.

13)         Приведенную в виде примера программу для работы с поименованными каналами переделайте так, чтобы обрабатывать любой файл с записями постоянной длины. Завершите программу так, чтобы по окончании обработки обе программы нормально завершались. Режим обработки файла вы можете выбрать по своему желанию.

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

a.     PID - идентификатор обслуживающей программы;

b.     NUMBER - номер записи;

c.      DATA - дата создания.

d.     Окончанием работы служит признак конца набора данных с терминала (например ctrl+Z).

e.      По окончании работы сервер должен выдать на экран содержимое полученного файла.

15)         В рамках условий задачи 13 обеспечьте возможность работы с записями переменной длины.

16)         Модернизируйте задачу 14 для работы с записями переменной длины.

 

Варианты выполнения лабораторной работы

 

№ Бригады

Пункты задания

1

3, 4, 8

2

3, 5, 8

3

5, 6, 8

4

3, 7, 8

5

3, 9, 11

6

5, 10, 12

7

13, 15

8

14, 16

9

7, 11

 

Отладчик GDB

GDB (сокращение от GNU Debugger)– отладчик, который позволяет отлаживать C и C ++ программы. Этот отладчик работает с выполнимыми файлами, произведенными как gcc, так и другими компиляторами. GDB частично поддерживает и другие языки, например: Modula-2, ФОРТРАН, и Паскаль.

Вы можете использовать GDB многими способами. Родной интерфейс пользователя – интерфейс командной строки, но для него есть также оконные расширения, кроме того с ним может работать редактор Emacs.

GDB обеспечивает Вас четырьмя главными услугами, которые помогают найти ошибки в вашей программе:

• Он позволяет Вам начинать вашу программу и определять что-нибудь, что могло бы затрагивать поведение.

• Он останавливает ваши программы на условиях, которые Вы сами установили.

• Он исследует текущий статус вашей программы в момент ее остановки.

• Он позволяет Вам проводить изменения в вашей программе так, чтобы Вы могли проверять, как эти изменения затронут ошибку и остальную часть программы.

Вы можете вызывать GDB тремя основными способами:

- gdb <программа>

- gdb < программа> -c < файл ядра (coredump)>

- gdb < программа> <pid >

Первым способом, Вы вызываете GDB с <программой> в качестве аргумента, где <программа> - имя выполнимого файла. Вторым способом, при вызове GDB Вы задаёте также < ядро файла > - имя файла, содержащего снимок точного состояния Вашей программы, когда она потерпела крах (например, в случае доступа за границу выделенной области памяти). Вызов GDB и определение файла ядра поставит программу в точно то же самое состояние внутри GDB. Создание файлов ядра для некоторых программ может быть невозможно. Иногда, Вы должны поймать коварный тип ошибки, которая является неустойчивой и не вызывает крах вашей программы. Третий способ вызывать GDB будет удобным в этих случаях. Этим третьим способом Вы вызываете GDB с <программой> и <pid>, где <pid> - номер идентификации процесса управления. Когда избран этот путь, GDB примет на себя к управление, обрабатывает и начинает исследовать процесс, для которого Вы можете затем делать отладку.

Мы собираемся исследовать сессию отладки образца с GDB, используется программа, которая приведена ниже. Программа, которая производит ошибку сегментации.

 

#include < stdio. h >

#include < fcntl. h >

#include <errno.h>

 int openbuffer (filename)

 char * filename;

 int fd;

  if ((fd = open (filename, 0_RDONLY)) <) 0

{

  fprintf (stderr, " Не может открыть % s, errno = < % > d \n ",filename, errno);

  exit (-2);

  return (fd);

}

int readbuffer (fd, buffer, size)

 int fd;

char  *buffer;

int size;

{ int retc;

   if ((retc = read (fd,buffer, size))! = size)

                 if (retc < 0)

{

   fprintf (stderr. " Не может читать, errno = % d\n ", errno);

   exit (-2);

} else if (retc)

                  fprintf (stderr, " Частичное чтение\n ");

                              return (retc);

int countchar (buffer)

 char * buffer;

{

   int cnt;

   char *p;

   p = buffer;

   While (*p)

{

   if ((*p) == '\n ') cnt ++;

   p ++;

} return (cnt);

}

main ()

{

    int fd;

    int cnt = 0;

    char inbuf [1024];

    fd = openbuffer (" /usr/lib/libc. a");

    while (readbuffer (fd, inbuf, 1024)) cnt + = countchar (inbuf);

    printf ("Странный знаковый счетчик= % d\n ", cnt);

    return 0;

}

 

Пусть этот код скомпилирован в файл с именем badbprog. Тогда при запуске badprog будет создан core-файл, содержащий содержимое памяти процесса на момент ошибки сегментации. Если core-файл не создан, следует выполнить команду

 

ulimit -c unlimited

 

которая отключит ограничения на размер core-файла. Чтобы вызвать отладчик GDB для программы badprog и core-файла core, выполните следующую команду:

 

$ gdb badprog core

 

При запуске gdb помимо своей ферсии и условий распространения (GDB – свободно распространяемое ПО) сообщит следующее:

 

Чтение символов от /home/badprog ... выполненный. Чтение символов от /usr/shiib/libsys_s. B.shiib ... done.

Чтение в символах для badprog.c ... done.

Ox3dda in countchar (буфер = 0x3fff89c " Karch > \n __. SYMDEF

SORTED747000476 0

45            if ((*p) == t\n ') cnt ++;

 

Чтобы узнать, в какой точке остановлена программа, можно воспользоваться командой backtrace (сокращённо, bt) или where:

 

( Gdb) where

* 0 Ox3dda in countchar (буфер = 0x3fff89c " Karch > \n __. SYMDEF SORTED747000476.

* 1 Ox3e40 un main () at badprog.c: 58

 

где команда отображает стек вызовов, так что сразу понятно, что ошибка в функции countchar. Эта функция вызвана из main.  < digits >  в начале строки в  стека вывода - структура(фрейм). Вывод командного фрейма 0  переключит контекст и даст контекст функции countchar. Аналогично, вывод фрейма 1 дал бы контекст функции main:

 

( Gdb) frame 0

^ 0 Ox3dda in countchar (buffer = 0x3fff89c "! <arch > \n __, SYMDEF

SORTED747000476

45 , if ((*p) == '\n ') cnt ++;

(Gdb) list 40,50

40 char *p;

41

42 p = buffer;

43, while (p)

44 {

45,  if ((*p) == '\n ") cnt ++;

46 p++;

47}

48 return (cnt);

49}

50

(Gdb) print buffer

$1 = Ox3ff (f89c "! < arch > \n __. SYMDEF SORTED747000476 0 1

100644 58652

 

Посмотреть исходный код, соответствующий текущей точки выполнения программы (или текущей точки в данном стековом кедре) можно командой list (сокращённо, l). Здесь, мы можем видеть, что  воспроизведена ошибка программы. Заканчивающееся условие находится на указателе вместо нахождения его на знаке, указатель указывает на:

 

(Gdb) print p

$1 = Ox4000000 < Address 0x4000000 out of bounds >

( Gdb) print *p.

$2 = Cannot access memory:, address 0x4000000 out of bounds.

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

(Gdb) frame 1

^ 1 Ox3e40 in main() at badprog. c-. 58

58, while (.readbuffer (fd, 1nbuf. l024)) cnt + = countchar (1nbuf);

Давайте взглянем на буфер, в то время как программа находилась в main:

 

( Gdb) print -inbuf

$3 = {" '. < arch > \ri_. SYMDEF SORTED747000476 0 1 100644 58652

\n\

( Gdb) q. '.

 

Мы можем видеть, что можно действительно двигаться от одной функции до другой, и таким образом можно исследовать каждое промежуточное звено, вызывающее функцию от main к функции, которая потерпела крах.

GDB имеет намного более мощные возможности. Далее следуют наиболее важные из них.

Опции командной строки. GDB может принимать опции командной строки, показанные ниже.

-s или -symbols < file > заставят GDB читать символы из другого файла;

-c или -core <file> указывает GDB использовать <file> в качестве core-файла;

-x, -command <file> GDB читает и выполняет команды GDB из файла < f11e>;

-d, -directory <dir> добавляет <dir> к списку каталогов, в которых искать файлы с иходными текстами программ;

-nx заставляет GDB не выполнять любые команды из .gdbinit файла (этот файл содержит GDB команды, которые выполняются каждый раз, когда GDB стартует);

-batch заставляет GDB работать в пакетном режиме. Обычно этот режим используется с командными опциями (из -cоmmand). GDB прекращает выполнение после всех указанных в командном файле команд;

-quiet не будет печатать информацию о лицензировании, когда GDB запущен.

 

Команды. Ниже указан список некоторых ключевых слов, которые Вы можете использовать при отладке программы.

shell <команда> запускает оболочку и выполняет команду.

quit – выход из GDB.

make < args > запускает утилиту MAKE и передает ей <args> для выполнения.

help подсказка GDB относительно категорий команд.

help <category> Подсказка GDB относительно команд данной категории

help <команда> Заставит GDB показать полную помощь относительно команды

complete <str> Задает список GDB команд, которые начинаются с <str>.

run <args> Начинает вашу программу и передает ей <args>. <args> может содержать  переназначение ввода-вывода.

set args<args> - установить список аргументов для команды. <args> может содержать  переназначение ввода-вывода.

show args – отобразить список аргументов

attach <pid>, связывает GDB с работающим процессом с <pid>.

detach Отключает GDB от работающего процесса. Обратите внимание, что, если Вы вышли из GDB или Вы используете команду, в то время как Вы связаны с работающим процессом, Вы убьете процесс. Всегда используйте DETACH, когда действительно хотите сохранить процесс работающим

 

GDB позволяет Вам делать отладку в режиме мультипрограммирования! Ниже содержится набор команд, используемых для этой цели.

thread <номер> задает номер текущей нити процесса.

info thread Выдает список всех thread, которые присутствуют в настоящее время

thread apply < list> <args > применяет команду к списку thread. <list> может быть список номеров thread или ключевое слово ALL, которые указывает, что команда относится ко всем threads. <args> - это используемая команда.

 

Перед рассмотрением списка команд, мы должны определить два понятия, контрольную точку и watchpoint. Контрольная точка (или точка останова) – это указатель на место в коде, который Вы можете устанавливать. Когда код, направленный на контрольную точку, выполняется, программа останавливается. Watchpoint контролирует выражение, и когда оно достигает некоторой величины, которую Вы указали, программа остановится. Вы устанавливаете контрольные точки и watchpoints, используя различные команды, но однажды установив, те же самые команды не будут выполняться и будут удалены. Ниже приведен список команд для остановки программы при некоторых условиях.

break <функция> Устанавливает контрольную точку в начале <функции>;

break <line> Устанавливает контрольную точку линии номер<line >;

break <offset> Контрольная точка установлена в -h/ <offset > от точки, где ваша программа была остановлена перед этим;

break <file>:<f> Устанавливает контрольную точку в начале функции <f> в   исходном файле <f>;

break <file >:<l> Установка контрольной точки в линии номер <l> в исходном файле <file>;

break устанавливает контрольную точку в следующей выполняемой инструкции;

cond <n> <условие> устанавливает условие срабатывания для точки останова с номером n: программа будет остановлена в этой точке, только если <условие> истинно;

tbreak <args> Устанавливает контрольную точку, которая будет удалена как только программа остановится;

info break печатает список всех установленных контрольных точек и watchpoints;

watch <expr> устанавливают watchpoint для выражения <expr>;

dear удаляет набор контрольных точек в следующей инструкции, которая выполняется;

dear <f> удаляет набор контрольных точек в начале функции <f>;

dear <file>:<f> удаляет набор контрольных точек в начале функции <f> в исходном файле <file>;

dear <l> удаляет набор контрольных точек на строке <l>;

dear <file>:<l> удаляет набор контрольных точек строке <l> в исходном файле <file>;

delete удаляет все контрольные точки;

delete <args> удаляет контрольные точки согласно < args>, где <args> – список номеров контрольных точек, заданных в команде info break;

disable отключает все контрольные точки;

disable <args> отключает контрольные точки, указанные в <args>;

enable задействует(активирует) все контрольные точки;

enable <args> задействует контрольные точки, указанные в <args >;

enable once <args> задействует контрольные точки, указанные в <args > так, чтобы они были отключены снова как только программа остановится;

enable delete <args>, задействует контрольные точки, указанные в <args > так, чтобы они были удалены как только программа остановится.

 

Имеются также команды, управляющие пошаговым выполнением программы:

continue обеспечивает выполнение вашей программы, до тех пор, пока не сработает следующая точка останова;

continue <count> – то же самое, что и сontinue. Необязательный <count> - количество раз, которое контрольная точка в этом местоположении будет игнорирована;

next выполнение участка программы, соответствующего одной строке исходного кода в той же самом фрейме. Это означает, что функция будет выполнена целиком, внутрь функции отладчик не проследует;

next <n> выполнить n следующих строк кода;

step продолжает выполнение вашей программы до другой строки исходного файла. В отличие от next, «заходит» в вызываемую функцию, то есть, вызовы функций также выполняются пошагово;

step <n> то же самое, что и step. Необязательный аргумент <b> заставит GDB идти  вперед на n шагов;

stepi выполнить одну инструкцию ассемблера;

finish Продолжает выполнение программы до конца текущей функции;

until остановит программу после завершения цикла;

until <loc> продолжает выполнение программы, пока местоположение <loc> не достигнуто. Аргумент <loc> может быть любым из аргументов, которые принимает команда break (функция, строка кода, файл и строка кода, адрес).

 

GDB позволяет просматривать содержимое памяти.

print <выражение> вывести на экран значение выражения. Эту команду можно использовать для просмотра значения переменной;

backtrace отобразить стек вызовов;

list вывести на экран исходный текст, соответствующий данной точке останова;

dump команда используется для сохранения данных в файл;

dump memory <file> <start> <stop> сохранить содержимое памяти, в файл file, начиная с адреса start, заканчивая (но не включая) адресом stop;

up прейти на уровень выше по стеку вызовов;

down перейти на уровень ниже по стеку вызовов;

frame <n> перейти к стековому кадру с номером n стека вызовов.

 

GDB позволяет Вам обращаться с сигналами, посланными вашим программам (см. таблицы сигналов в следующих лабораторных работах). Очень полезно, если Вы можете блокировать сигнал так, чтобы ваша программа, не видела его, или Вы можете останавливать вашу программу на данном сигнале.

info signals показывает сигналы, в настоящее время обработанные GDB и как их обрабатывать;

handle <signal> <arg> сообщает GDB: обрабатывать <signal> как определено в <arg>, который может принимать одно или несколько следующих значений:

-       pass, позволяет вашей программе получать signal;

-       nopass не позволяет вашей программе получать signal;

-       stop останавливает вашу программу, когда этот сигнал получен;

-       nostop позволяет вашей программе работать, хотя сигнал и получен;

-       print печатает сообщение, когда этот сигнал получен;

-       noprint не печатает сообщение, когда сигнал получен.

-       signal <signal> продолжает выполнение вашей программы и посылает сигнал <signal> сразу после этого.