From bd5e935ed7759ae154698ede7fa461782aa194ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=BE?= =?UTF-8?q?=D0=B7=D0=BB=D1=8E=D0=BA?= Date: Sun, 8 Oct 2023 22:34:29 +0300 Subject: [PATCH] =?UTF-8?q?lab01:=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC?= =?UTF-8?q?=D0=BD=D0=BE=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=BC=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B2?= =?UTF-8?q?=20Linux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 + labs/lab02/README.md | 843 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 848 insertions(+) create mode 100644 README.md create mode 100644 labs/lab02/README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a52a00a --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Системное программное обеспечение + +## Лабораторные работы + +2. [Системное программирование в Linux](labs/lab02) diff --git a/labs/lab02/README.md b/labs/lab02/README.md new file mode 100644 index 0000000..a49b590 --- /dev/null +++ b/labs/lab02/README.md @@ -0,0 +1,843 @@ +--- +title: Системное программирование в Linux +lang: ru +--- + +Вы должны: + +* Уметь выполнять команды в командной строке Linux. +* Владеть языком C. + +Вы научитесь: + +* Находить и читать документацию по системным функциям. +* Использовать системные функции в программах на C. +* Собирать программы из командной строки Linux. +* Пользоваться отладчиком GDB. +* Пользоваться программой `strace` для диагностики системных вызовов. + +# Системное программирование + +Системное программирование связано с взаимодействием с ядром ОС. +Это может быть использование системных вызовов в пространстве пользователя +или написание кода, который исполняется в ядре (само ядро и драйверы). +Первое значительно проще и чаще встречается. +Технически работа с системными вызовами мало отличается +от работы с любыми другими библиотеками и их API. +Однако нужно понимать концепции и возможности данной ОС. +Например, для Linux это иерархия файловой системы (ФС) +и специальные ФС (`procfs`, `sysfs`) для доступа к объектам ядра, +разграничение прав доступа к файлам, +конкретные возможности, которые ОС предоставляет для ряда задач. +Большая часть системного программирования — это не написание кода, +а изучение документации на системные вызовы ОС и на её возможности. + +# Чтение документации + +Из `man man` можно узнать, что документация состоит из разделов, в частности: + +* 1 — исполняемые программы и команды оболочки; +* 2 — системные вызовы (специфичные для Linux); +* 3 — функции библиотек (не специфичные для Linux); +* 7 — общая информация, не связанная с конкретной функцией. + +Страница с одним и тем же названием может быть в разных разделах. +Например, `man stat` открывает страницу раздела 1 и описывает программу `stat`. +Номер раздела виден в левом верхнем углу страницы: `STAT(1)`. +Функция же `stat()` описана в разделе 3, открывается как `man 3 stat`. +В разделе 2 тоже есть страница про `stat`, причем она больше, чем в разделе 3. +Разница в том, что *stat(3)* описывает стандартные функции для всех POSIX-систем, +а *stat(2)* описывает все, что поддерживает Linux (больше, чем в стандарте). + +Документацию не обязательно читать в терминале: +, +. +Однако `man` соответствует конкретному дистрибутиву Linux и версиям его пакетов, +а часть сведений с сайтов может быть неприменима к данной системе. + +Документацию лучше читать в оригинале (обычно на английском), +чтобы не ждать перевода и не страдать из-за ошибок переводчика. +Автоматические переводчики тоже иногда ошибаются и искажают смысл. + +Узнать о том, что какая-то функция вообще есть, можно из учебников и новостей. +Популярные источники новостей: + (на русском), + (на английском). + +# Выполнение лабораторной работы + +Чтобы изучить на примере, как программировать с использованием системных API, +напишем программу `example1`, которая печатает размеры файлов, +имена которых передаются ей как аргументы при запуске: + +```sh +$ ./example1 example1.c example1 +Size of 'example1.c' is 259 bytes +Size of 'example1' is 87340 bytes +``` + +## Написание кода + +В ходе ЛР предлагается писать код в `vim` +или в Midnight Commander (`mc`) по клавише `F4`. +Преимущество в том, что ими можно пользоваться, просто подключившись к серверу. +Это простые редакторы, но для маленьких лабораторных программ их достаточно. + +Вообще говоря, работа с Linux не ограничивается терминалом. +Под Linux есть графические окружения, в том числе редакторы кода +и интегрированные среды разработки (IDE). +Популярные: Visual Studio Code (не путать с Visual Studio), Eclipse, +QtCreator, CLion (платная), CodeBlocks и много других. +На виртуальные серверы их ставить не нужно, +так как они потребляют много ресурсов. +Однако, если лабораторная работа выполняется на локальной машине, +можно установить, настроить и использовать то, что удобнее. + +Предлагается писать код на C, а не на C++. +Это связано с тем, что системные API описаны в терминах C, +примеры в документации на языке C, а средства C++ в задачах этого курса +менее востребованы, чем в прикладных программах. +Фактически это значит, что `vector` и `string` недоступны, +а вместо `cin` и `cout` из `` +нужно пользоваться `scanf()` и `printf()` из ``. +На практике C++ для системного программирования активно используют, +но только в пространстве пользователя. + +## Работа с аргументами программы + +Создайте файл `example1.c`, введите в него следующий код и сохраните: + +```c +#include + +int +main(int argc, char** argv) { + int i; + + for (i = 1; i < argc; i++) { + printf("Size of '%s' is %d bytes\n", argv[i], 0); + } + return 0; +} +``` + +Заготовка делает почти то же самое, что должна делать программа в итоге, +но вместо размеров печатаются нули. Если вы забыли, как работать с аргументами +программы и функцией `printf()`, +обратитесь к [документации](https://en.cppreference.com/w/c/language/main_function) +и примеру в ней. + +**Задание.** +Ответьте на вопросы для самопроверки: + +* Чему будет равно `argc` при запуске программы как `./example1 foo bar`? +* Какие значения находятся в массиве `argv` при таком запуске? +* Почему в коде выше цикла начинается с 1, а не с 0? +* Как называется первый параметр функции `printf()`? +* Что означают `%s` и `%d` в нем? +* Почему выбраны именно `%s` и `%d`? + +## Компиляция + +Соберите исполняемый файл `example1` из исходного кода в `example1.c`: + +```sh +gcc example1.c -o example1 +``` + +Запустите программу: + +```sh +./example1 example1.c example1 +``` + +## Изучение документации + +Получить сведения о файле, в частности, его размер, можно функцией `stat()`. + +``` +NAME + stat, fstat, lstat, fstatat - get file status +``` + +Одна `man`-страница часто содержит описание семейства функций, +в данном случае четырех функций для получения статуса файла (сведений о нем). + +``` +SYNOPSIS + #include + + int stat(const char *restrict pathname, + struct stat *restrict statbuf); + ... +``` + +Раздел-сводка описывает, как объявлена функция и какие заголовочные файлы +нужно подключить, чтобы использовать её. + +В объявлениях часто встречается ключевое слово `restrict` с указателями. +Оно означает, что если есть два параметра-указателя, то они содержат адреса +разных, не пересекающихся блоков памяти. В случае `stat()` это значит, +что нельзя записать в массив символов имя файла +и передать указатель на этот массив и как `pathname`, и как `statbuf`, +ожидая, что функция сначала прочитает из массива имя файла, +а потом перезапишет содержимое массива сведениями о файле. +На практике так в любом случае стараются не делать, чтобы не запутывать код. + +Первый параметр, `pathname`, имеет тип `const char*`, то есть это строка. +Второй параметр, `statbuf`, имеет тип `struct stat*`, указатель на структуру. +В отличие от C++, в C, если объявлена структура `struct Example { ... }`, +то экземпляр `ex` этой структуры надо объявлять как `struct Example ex`, +а не `Example ex`. + +``` +DESCRIPTION + These functions return information about a file, + in the buffer pointed to by statbuf... + + The stat structure + All of these system calls return a stat structure, + which contains the following fields: + + struct stat { + dev_t st_dev; /* ID of device containing file */ + ... +``` + +Раздел с описанием раскрывает, что именно делает функция, +что передается во входных параметрах и что возвращается в выходных. +Функция `stat()` записывает информацию о файле в поля структуры, +адрес которой передан в параметре-указателе `statbuf`. +Далее описывается, что будет записано в разные поля этой структуры. + +Нас интересует поле с размером файла: + +``` + st_size + This field gives the size of the file + (if it is a regular file or a symbolic link) in bytes. + The size of a symbolic link + is the length of the pathname it contains, + without a terminating null byte. +``` + +У многих функций после описания идет раздел `EXAMPLES` (примеры), +где приводятся небольшие программы, демонстрирующие, как пользоваться функцией. + +Большая часть `man`-страниц заканчивается разделом, +где перечислены связанные страницы. +Например, к теме получения сведений о файле относятся команды `ls` и `stat`, +функции `access()`, `readlink()` и другие: + + +``` +SEE ALSO + ls(1), stat(1), access(2), chmod(2), chown(2), readlink(2), + statx(2), utime(2), capabilities(7), inode(7), symlink(7) +``` + +Итак, необходимо: + +* Объявить переменную типа `struct stat`. +* Передать функции `stat()` имя файла и адрес объявленной переменной. +* Напечатать размер из поля `st_size` объявленной переменной. + +**Задание.** +Модифицируйте код, соберите программу и запустите её так же, как в прошлый раз. + +```c +#include +#include + +int +main(int argc, char** argv) { + struct stat st; + int i; + + for (i = 1; i < argc; i++) { + stat(argv[i], &st); + printf("Size of '%s' is %d bytes\n", argv[i], st.st_size); + } + return 0; +} +``` + + + +**Задание.** +Попробуйте вызвать программу без аргументов, +с несколькими аргументами-именами существующих файлов +и с аргументом-именем файла, которого не существует. +Объясните результаты. + +## Обработка ошибок при вызове функций + +Как понять, что функция не смогла выполнить свою работу, +например, если файла не существует? + +Продолжим чтение документации: + +``` +RETURN VALUE + On success, zero is returned. + On error, -1 is returned, and errno is set to indicate the error. + +ERRORS + EACCES Search permission is denied for one of the directories + in the path prefix of pathname. + (See also path_resolution(7).) + ... + ENOENT A component of pathname does not exist + or is a dangling symbolic link. + ... +``` + +Функция `stat()` возвращает целое число: 0 при успехе, (-1) при ошибках. +Числовой код ошибки, по которому можно понять причину, +записывается в глобальную переменную `errno`. +В частности, если файла не существует, будет `errno == ENOENT` +и аналогично с другими кодами для других причин ошибок. + +Значит, при вызове функции нужно проверить возвращаемое значение, +Если оно отрицательное, то обработать ошибку, +например, напечатать сообщение о ней с кодом: + +```c +int ret = stat(...); +if (ret < 0) { + printf("stat(): errno=%d\n", errno); + continue; +} +``` + +**Задание.** +Добавьте в программу обработку ошибок. Соберите программу. +Проверьте, как программа реагирует на несуществующий файл. + +Коды ошибок трудно и не нужно помнить наизусть. +Есть функции *strerror(3)*, *perror(3)* и другие, чтобы по коду ошибки +получить или напечатать её текстовое описание для пользователя. + +Программа *errno(1)* позволяет получить описание ошибки по коду +(эта программа по умолчанию не установлена): + +```sh +% errno 12 +ENOMEM 12 Cannot allocate memory +``` + +## Исправление ошибок и предупреждений компилятора + +Заменим `for (i = 1; i < argc…` на `for (i = 1; i < argd…` (`argc` → `argd`), +как будто допущена опечатка, и повторим сборку: + +```sh +$ gcc example1.c -o example1 +``` + +Возникнет ошибка, GCC даже подсказывает, в чем она может заключаться: + +``` +example1.c: In function ‘main’: +example1.c:9:21: error: ‘argd’ undeclared (first use in this function); did you mean ‘argv’? + 9 | for (i = 1; i < argd; i++) { + | ^~~~ + | argv +example1.c:9:21: note: each undeclared identifier is reported only once for each function it appears in +``` + +В начале сообщения указана строка с ошибкой (9). +VIM позволяет перейти к строке с заданным номером так: `:9`, Enter. + +**Задание.** +Исправьте ошибку и убедитесь, что программа снова собирается успешно. + +По умолчанию компилятор обнаруживает только ошибки. +Современные компиляторы могут также обнаруживать потенциальные проблемы, +которые не являются ошибками с точки зрения языка C, +но могут привести к неправильной работе программы. +Показ дополнительных предупреждений включается флагами компилятора: + +```sh +$ gcc example1.c -Wall -Wextra -o example1 +``` + +``` +example1.c: In function ‘main’: +example1.c:11:34: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘__off_t’ {aka ‘long int’} [-Wformat=] + 11 | printf("Size of '%s' is %d bytes\n", argv[i], st.st_size); + | ~^ ~~~~~~~~~~ + | | | + | unsigned int __off_t {aka long int} + | %ld +``` + +Действительно, `st.st_size` имеет тип `long int` (обычно 64 бита), +поэтому для него нужен спецификатор `%ld`. +Спецификатор `%d` — для `int` (обычно 32 бита, значения до ~2 млрд.). + +**Задание.** +Исправьте ошибку и убедитесь, что предупреждения больше не возникает. + +**Задание.** +В каком случае проблема проявилась бы? + +**Внимание.** +В этой и последующих работах все программы нужно собирать с такими же +или более строгими флагами, исправляя все возникающие предупреждения. + +## Отладка программ + +*Отладкой (debugging)* называется поиск и устранение ошибок в программе. +Так как системные программы обычно пишутся на C, +и при ошибке не показывается место в коде, где она возникла, +полезна *пошаговая отладка (step-by-step debugging)* — +выполнение программы «строка за строкой» с возможностью остановиться +и просмотреть значения переменных. +*Отладчик (debugger)* — это специальная программа, +которая запускает отлаживаемую и позволяет управлять ходом её выполнения. + +Отладчиком часто пользуются из удобного графического интерфейса IDE. +Технически на том же сервере, что и отлаживаемая программа, +достаточно установить `gdbserver`, чтобы подключаться к нему и отлаживать в IDE. +Зачем тогда знать командную строку GDB? + +* Установить на удаленный сервер GDB может быть намного проще, + чем согласовать подключение к нему при работе в корпоративных сетях. + Текстовые команды можно продиктовать или переслать тому, + кто может подключиться к серверу. + +* Текст сеанса работы с GDB (все команды и их вывод) можно сохранить + и поделиться им с другими специалистами, в отличие от интерактивного GUI. + +* Язык команд GDB позволяет широкую автоматизацию. + Как минимум, команды можно записать и затем быстро повторить действия. + +### Работа с GNU Debugger + +Создайте файл `example2.c` со следующей программой: + +```c +#include + +void +print(const char* message) { + puts(message); +} + +int +main(int argc, char** argv) { + print(argv[0]); + print(argv[1]); + print(argv[2]); +} +``` + +Отладчику нужны сведения о том, как машинный код в исполняемом файле +соотносится с исходным текстом, где в памяти размещены переменные и т. п. +По умолчанию эта отладочная информация не включается в исполняемый файл. +Чтобы компилятор добавил ее, нужен ключ `-g` при компиляции: + +```sh +$ gcc example2.c -g -o example2 +``` + +Запустите программу: + +```sh +$ ./example2 +``` + +``` +./example2 +Segmentation fault (core dumped) +``` + +Программа аварийно завершается («падает»). +Первую строку, `./example2`, программа напечатала сама — это `argv[0]`. +Вторую строку напечатала оболочка, её смысл мы разберем далее. + +Запустим программу под отладчиком, чтобы найти причину аварийного завершения: + +```sh +$ gdb -q --args ./example2 +``` + +``` +Reading symbols from example2... +(gdb) +``` + +Вместо приглашения командной оболочки +появилось приглашение командной строки отладчика, обозначаемое `(gdb)`. + +Отладчик не запускает программу сразу, это нужно сделать командой `run` +(GDB также воспринимает сокращения команд, например, `r` вместо `run`): + +```gdb +(gdb) run +``` + +``` +Starting program: /home/user/example2 +Missing separate debuginfo for /lib64/ld-linux-x86-64.so.2 +Try to install the hash file /usr/lib/debug/.build-id/89/c19f7bc64e27bdd0c57a4b08e124f8c95799ee.debug +Missing separate debuginfo for /lib64/libc.so.6 +Try to install the hash file /usr/lib/debug/.build-id/41/259207a6a4da6595a09db78fa98a4118ae0d0e.debug +/home/user/example2 + +Program received signal SIGSEGV, Segmentation fault. +0x00007f4ef9e7ec59 in ?? () from /lib64/libc.so.6 +``` + +Строки в начале (`Missing separate debuginfo...`) говорят о том, +что отладочной информации для системных библиотек недоступно. +По этой причине в последней строке написано, что аварийный останов случился, +когда выполнялся машинный код в библиотеке `/lib64/libc.so.6`, +но соотнести его с местом в исходном коде библиотеки невозможно (`in ?? ()`). + +Запросим у отладчика *стек вызовов (stack trace),* +то есть из какой функции был вызван код, приведший к аварийному останову, +из какой функции была вызвана эта функция и так далее вплоть до `main()`. +Это делается командой `backtrace` (`bt`): + +```gdb +(gdb) bt +``` + +``` +#0 0x00007f58f4c25c59 in ?? () from /lib64/libc.so.6 +#1 0x00007f58f4b38e80 in puts () from /lib64/libc.so.6 +#2 0x00005578019f114d in print (message=0x0) at example2.c:5 +#3 0x00005578019f1181 in main (argc=1, argv=0x7ffc86ac2d88) at example2.c:11 +``` + +У стека вызовов в данном случае четыре уровня, или *фрейма (frame).* +Нижние два (0 и 1) находятся в библиотеке `/lib64/libc.so.6`. +Для фрейма 1 известно, что это функция `puts()`, +то есть аварийный останов вызван каким-то неизвестным кодом (фрейм 0), +который был вызван функцией `puts()`. + +Больше информации есть о фреймах 2 и 3, относящихся к отлаживаемой программе. +Фрейм 2 находится в функции `print()`, +конкретно это вызов `puts()` на строке 5 файла `example2.c`. +Также показано, что функция вызвана с параметром `message`, который равен 0. +Аналогично для фрейма 3 показано, с какими аргументами вызвана `main()`, +и откуда конкретно в ней сделан вызов `print()`. + +Можно переместиться к нужному фрейму, указав его номер: + +```gdb +(gdb) frame 2 +``` + +``` +#2 0x00005578019f114d in print (message=0x0) at example2.c:5 +5 puts(message); +``` + +Можно также перемещаться вверх (up) и вниз (down) по стеку вызовов: + +```gdb +(gdb) up +``` + +``` +#3 0x00005578019f1181 in main (argc=1, argv=0x7ffc86ac2d88) at example2.c:11 +11 print(argv[1]); +``` + +Пока программа остановлена, можно исследовать значения переменных в ней: + +```gdb +(gdb) print argv[0] +``` + +``` +$1 = 0x7ffc86ac4edc "/home/user/example2" +``` + +```gdb +(gdb) print argv[1] +``` + +``` +$2 = 0x0 +``` + +Итак, проблема вызвана тем, что `puts()` передан нулевой указатель как аргумент, +а взялся он из `argv[1]`, который равен `NULL`, потому что программа +запущена без аргументов (`argc=1`, заполнен только `argv[0]`). + +Выполните следующую команду, её результат понадобится в дальнейшем: + +```gdb +(gdb) gcore coredump +``` + +``` +warning: Memory read failed for corefile section, 4096 bytes at 0xffffffffff600000. +Saved corefile coredump +``` + +Отладку можно завершить: + +```gdb +(gdb) quit +``` + +``` +A debugging session is active. + + Inferior 1 [process 648] will be killed. +``` + +`Quit anyway? (y or n)` **`y`** + +### Отладка post-mortem + +Имея исполняемый файл с отладочной информацией, +можно многое узнать об используемых программой алгоритмах. +Это нежелательно, если исходный код программы закрыт. + +Отладочная информация также существенно увеличивает размер исполняемого файла: + +```sh +$ ./example1 example2 +``` + +``` +Size of 'example2' is 26536 bytes +``` + +По этим причинам разработчики обычно удаляют отладочную информацию, +как минимум, чтобы сэкономить место на диске: + +```sh +$ gcc example2.c -s -o example2-stripped +$ ./example1 example2-stripped +``` + +``` +Size of 'example2-stripped' is 17184 bytes +``` + +**Задание.** +Запустите исполняемый файл `example2-stripped` под отладчиком. +Что изменилось в тексте, который GDB выводит при старте? +Начините выполнение, и когда управление будет возвращено отладчику, +выполните команду `bt`. +Что изменилось в её выводе? + +Можно заметить, что отладить `example2-stripped` фактически невозможно: +отладчик работает, но не получится узнать, к какому месту в исходном коде +относится фрейм, чему равны локальные переменные и параметры функций. + +Как отлаживать программу, которая аварийно завершилась у конечного пользователя? + +Вернемся к тому, что печатала оболочка, когда программа запускалась без GDB: + +``` +Segmentation fault (core dumped) +``` + +Слова «segmentation fault» означают, что аварийное завершение случилось +по причине некорректного доступа к памяти. Могут быть и другие причины. + +Слова «core dumped» означают, что система записала слепок памяти процесса +на момент сбоя (core dump, «корку»). Будет ли слепок записан и куда именно, +зависит от настроек системы и процесса (`man core`). +Так или иначе, этот файл можно получить от пользователя, +который столкнулся с проблемой. + +На учебных стендах получить файл, записанный системой, не получится. +Однако точно такой же файл был создан ранее командой `gcore` в GDB. +Он сохранен в текущей директории в файл `coredump` (аргумент команды). + +Имея исполняемый файл *с отладочной информацией* +и файл core dump, полученный от пользователя, +можно начать отладку: + +```sh +$ gdb -q example2 coredump +``` + +**Задание.** +Какие известные вам команды GDB работают в таком режиме? +Какую информацию они выдают (с отладкой `example2` и `example2-stripped`)? +Почему некоторые не работают? + +Иногда нужно отладить программу, установленную из пакета. +В большинстве дистрибутивов отладочная информация в пакеты не входит, +но есть возможность установить отдельные пакеты с ней. +Например, для ALT Linux: . + +## strace + +Иногда программы недостаточно подробно сообщают о том, +что пытаются сделать и какие ошибки возникают. +Утилита `strace` позволяет проследить все системные вызовы, +которые совершает процесс. +Например, для программы `true` (ничего не делает и завершается успешно): + +```sh +$ strace /bin/true +``` + +``` +execve("/bin/true", ["/bin/true"], 0x7ffe0f8e18f8 /* 10 vars */) = 0 +exit_group(0) = ? ++++ exited with 0 +++ +``` + +Процесс совершил два системных вызова: + +1) `execve()` для запуска своего исполняемого файла +2) `exit_group()` для своего завершения с заданным кодом (0 — успех) + +**Задание.** +По документации `execve()` выясните, что означают первые два аргумента. +Почему второй показан в квадратных скобках? + +Большинство программ в начале делают много системных вызовов, +чтобы загрузить и инициализировать используемые библиотеки, +поэтому вывод `strace` от одного их старта занимает десятки строк. + +На самом деле, вызов `execve()` — это не прямой интерфейс системных вызовов, +а вызов функции из библиотеки `libc`, которая делает системный вызов. +Часто на один системный вызов приходится несколько функций `libc` +схожего назначения. Например, помимо `execve()` для запуска процессов +могут использоваться упрощенные формы: `exec()`, `execv()` и другие. +Так как `execve()` наиболее общая и имеет в точности те же параметры, +что и системный вызов, `exec()` и прочие реализованы через нее, +и `strace` не может различить, вызвана `execve()` напрямую или изнутри `exec()`. +Получив имя системного вызова с помощью `strace`, имеет смысл +сразу уточнить в документации к нему, нет ли более простой функции. + +Почему нужны обертки из `libc`? +Системный вызов — это не просто вызов функции. +Вызов функции представляет собой передачу управления коду функции в памяти. +Но код функции, которая реализует системный вызов, находится в ядре, +а пользовательские процессы не имеют доступа к памяти ядра. +Системные вызовы реализуются специальной командой процессора, +которая принимает не адрес перехода, а номер системного вызова; +параметры передаются особым образом; есть нюансы обработки ошибок. +Обертки скрывают эти сложности. +Для некоторых новых функций оберток нет. +Их требуется вызывать с помощью специальной функции `syscall()`, +как описано на соответствующих `man`-страницах. + +**Задание.** +Получите справку по `strace` и попробуйте использовать флаги `-v` и `-o`. + +**Задание.** +Попробуйте использовать `-e`, чтобы скрыть `execve()`. + +Программа `ltrace` позволяет аналогично отследить вызовы библиотечных функций. +Она не рассматривается в лабораторных работах. + +# Общее контрольное задание + +1. Установить на виртуальном сервере компилятор, отладчик + и программу для наблюдения системных вызовов, которые совершает процесс. + +1. Узнать, какой функцией программа `rm` удаляет файлы + (документировать в отчете, как это сделано). + Написать программу `lab01-rm`, которая удаляет все файлы, + переданные ей как аргументы (обрабатывать каталоги не нужно). + +1. Написать программу `lab01-ls`, которая печатает список файлов в каталогах. + Каталоги передаются в аргументах программы. + Если их не передано, подразумевается текущий каталог. + Формат списка: `дата_создания_ГГГГ-ММ-ДД размер_в_байтах имя_файла`. + Если файл является каталогом, в конце имени нужно приписать `/`. + Некоторые из нужных функций: `opendir()`, `strftime()`. + +# Контрольные вопросы + +1. Чем отличаются ошибки от предупреждений? + +1. Зачем включать дополнительные предупреждения компилятора? + +1. Известен код ошибки, как узнать его смысл? + +1. Как получить список стандартных кодов ошибок, чтобы выбрать подходящий? + +1. Программа завершается сразу после запуска с ошибкой "не найден файл настроек". + В документации не сказано, где и какой файл она ожидает. + Как выяснить это? + +1. Разработчик собрал программу и запустил под отладчиком. + Однако при останове отладчик вместо имен функций показывает `??` и адреса. + В чем проблема и как ее исправить? + +1. Разработчик собрал программу и запустил под отладчиком. + Однако, когда происходит ошибка и управление передается отладчику, + он показывает, что якобы выполнялся не тот код, который вызвал ошибку. + В выводе отладчика есть слова "stack frame corrupted". + В чем дело и как искать ошибку в этом случае? + +# Индивидуальные контрольные задания + +Написанная программа не должна запускать другие программы, +если об этом не сказано явно. +То есть, если нужно повторить работу `id`, например, +написанная программа не должна делать `system("id")` или подобное. + +Если команде, работу которой нужно повторить, передаются аргументы (`FILE`), +они будут передаваться и написанной программе. +Опции (`-a`) передаваться не будут, программа просто должна работать так, +как работает команда с указанной опцией. + +Если используемая функция выделяет ресурсы (память, дескриптор), +которые требуется освободить в вызывающем коде, это должно быть сделано. + +Если любая из задействованных функций из системных библиотек вернет ошибку, +нужно напечатать сообщение, которое начинается с `ERROR: функция:` +и завершить программу с кодом 1. + +Если программа вызвана неправильно, +например, предполагается вызов `program ARG1 ARG2`, +а указан только один аргумент, +нужно напечатать сообщение, начинающееся на `ERROR:`, +и завершить программу с кодом 2. + +## Варианты + +1. Написать программу, которая работает как команда `id` (без параметров). + +1. Написать программу, которая работает как `uname -a`. + +1. Написать программу, которая работает как `mv FILE1 FILE2`. + +1. Написать программу, которая работает как `truncate FILE SIZE`. + +1. Написать программу, которая печатает значение переменной окружения, + имя которой передается программе как параметр. + +1. Написать программу, которая работает как `date %Y-%m-%d %H:%M:%S`. + +1. Написать программу, которая работает как `sleep SECONDS` + (достаточно поддержать целое `SECONDS`). + +1. Написать программу, которая получает как `mkdir DIR`. + В созданный каталог должна быть возможность перейти. + +1. Написать программу, которая выполняет команду, состоящую из аргументов + (например, `program ls -l` выполняет `ls -l`). + +1. Написать программу, которая работает как `pwd`. + +1. Написать программу, которая принимает параметр-целое число + и печатает время в наносекундах, которое занимает + вычисление квадратного корня из этого числа. + +1. Написать программу, которая работает как `realpath FILE`. + +1. Написать программу, которая работает как `env`, + но печатает только переменные, начинающиеся на `P`. + +1. Написать программу, которая работает как `ln -s FROM TO`. + +1. Написать программу, которая выводит максимально возможное количество + процессов (аналог `prlimit -u`; «жесткий» лимит; `UNLIMITED`, если нет).