|
|
@ -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 (больше, чем в стандарте).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Документацию не обязательно читать в терминале:
|
|
|
|
|
|
|
|
<https://linux.die.net/man/>,
|
|
|
|
|
|
|
|
<https://www.opennet.ru/man.shtml>.
|
|
|
|
|
|
|
|
Однако `man` соответствует конкретному дистрибутиву Linux и версиям его пакетов,
|
|
|
|
|
|
|
|
а часть сведений с сайтов может быть неприменима к данной системе.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Документацию лучше читать в оригинале (обычно на английском),
|
|
|
|
|
|
|
|
чтобы не ждать перевода и не страдать из-за ошибок переводчика.
|
|
|
|
|
|
|
|
Автоматические переводчики тоже иногда ошибаются и искажают смысл.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Узнать о том, что какая-то функция вообще есть, можно из учебников и новостей.
|
|
|
|
|
|
|
|
Популярные источники новостей:
|
|
|
|
|
|
|
|
<https://opennet.ru> (на русском),
|
|
|
|
|
|
|
|
<https://lwn.net> (на английском).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Выполнение лабораторной работы
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Чтобы изучить на примере, как программировать с использованием системных 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` из `<iostream>`
|
|
|
|
|
|
|
|
нужно пользоваться `scanf()` и `printf()` из `<stdio.h>`.
|
|
|
|
|
|
|
|
На практике C++ для системного программирования активно используют,
|
|
|
|
|
|
|
|
но только в пространстве пользователя.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Работа с аргументами программы
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Создайте файл `example1.c`, введите в него следующий код и сохраните:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 <sys/stat.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 <stdio.h>
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 <stdio.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: <https://www.altlinux.org/Debuginfo>.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 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`, если нет).
|