§7.3. Что делает программа

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

Внутри системного блока

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

Однако, программы могут делать и другие вещи. Например, взаимодействовать с оборудованием пользователя: выводить данные на экран, считывать нажатия клавиш на клавиатуре, проигрывать звук, работать с сетью; с файловой системой и с другими программами. Всё это работает с помощью системных вызовов — специальных инструкций для обращения к ядру операционной системы.

В некоторых случаях нас не интересует, какие вычисления делает программа — мы хотим лишь узнать, каким образом она работает с операционной системой. Например, это часто относится к проприетарным (англ. proprietary — «собственнический», так говорят о ПО с непубличным исходным кодом или несвободной лицензией) драйверам для различного оборудования. Такие драйвера не всегда выпускаются под все платформы, и часть пользователей попросту не может пользоваться оборудованием. Исследователь может вмешаться в работу драйвера, изучить, как он работает, и портировать драйвер под другую операционную систему.

Отладка

Решая задачи прошлой главы, вы наверное заметили, что неотъемлемая часть программирования — создавать и исправлять ошибки. Если приложение маленькое, то для поиска ошибки часто бывает достаточно перечитать написанный код. Однако в больших приложениях всё не так просто — кода много, а взаимодействие различных модулей приводит к совершенно неожиданным спецэффектам. Для решения этой проблемы был придуман режим отладки — в таком режиме разработчик может пошагово выполнить программу. На каждом шаге он может посмотреть значение той или иной переменной. Некоторые отладчики даже позволяют откатывать приложение назад.

Операционные системы предоставляют специальные возможности для отладки. Например, на Linux и Mac OS есть отдельный системный вызов, ptrace.

Примечание

  • Большинство современных операционных систем так или иначе принадлежат к семейству Unix — среди них GNU/Linux, Android, Mac OS и другие. Важным исключением является Windows, которая настолько отличается от других систем, что только ей пришлось бы посвятить целый параграф. Далее в этом параграфе мы будем говорить только об инструментах, которые работают в мире Unix.

strace

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

Давайте попробуем посмотреть, что делает cat:

Вывод strace выглядит достаточно сложно. Если внимательно пролистать его целиком, то ближе к концу найдём фрагмент, который относится к работе с нашим файлом. cat открывает файл с помощью вызова openat — он возвращает число 3, идентификатор нашего файла. Затем совершается вызов read(3, ...) — так читается содержимое файла. Наконец, последний важный вызов — write(1, ...). Число 1 — специальный идентификатор, который обозначает вывод на экран. Таким образом, системный вызов приводит к выводу содержимого файла, что мы и видим ниже.

В strace большая часть вызовов не интересна для изучения. Для удобства системные вызовы можно фильтровать — например, в данном случае нас интересуют события открытия файлов и чтения из них. Нам понадобится аргумент -e:

Осталось буквально несколько вызовов — те, которые нас интересуют.

Давайте посмотрим, как strace помогает разбираться в проблемах в реальной жизни. Пока вы читали этот параграф, Тимурка решил распространить своё приложение и сделал новую версию, которая позволяет приветствовать даже тех пользователей, чьё имя длиннее девяти символов. Однако, запуская эту программу мы сталкиваемся с проблемой.

Вместо ожидаемого приветствия, мы видим лишь задумчивое мычание. Давайте воспользуемся strace — может быть, мы выясним, что могло пойти не так:

Мы видим, что приложение сначала пытается открыть файл /etc/greeter.conf на чтение (O_RDONLY), а потом, не найдя его, открыть на запись (O_WRONLY|O_CREAT). Доступ к директории ограничен, поэтому программа падает.

Примечание

  • У вас программа могла и заработать. Если так произошло, то это печально: ведь она смогла записать данные в директорию /etc. Либо вы запустили неизвестную программу из интернета от суперпользователя, либо у вашего пользователя есть доступ на запись в /etc. Обе этих причины могут привести к серьёзным проблемам в безопасности вашего компьютера.

Чтобы программа заработала, нужно либо запустить её от пользователя с root-правами, либо разрешить запись в /etc. Но это небезопасно — ведь мы не знаем, что внутри программы, и пока не можем проверить, что она сделает потом. Есть и третий вариант, который мы уже знаем — откроем программу HEX-редактором и исправим /etc/greeter.conf на /tmp/greeter.conf — так файл конфигурации будет храниться в директории для временных файлов, куда писать может любой пользователь. После этого исправления программа начинает работать:

Библиотеки

Разработчики часто переиспользуют код. Во многих программах используются одни и те же действия — открываются файлы, устанавливаются кодировки, совершаются сетевые запросы. Как и в высокоуровневых языках типа Python, существуют скомпилированные библиотеки в виде бинарных файлов. В Windows такие библиотеки имеют формат DLL, а на Unix-подобных системах — SO (англ. shared object — общий объект). Мы можем посмотреть, какие библиотеки использует программа, с помощью утилиты ldd:

Более того, обращения к функциям из библиотек тоже можно отслеживать. Для этого предназначена утилита ltrace. Иногда такой список бывает чуть более полезным, чем вывод strace:

Эти подходы можно использовать и для оконных приложений. Чтобы программы могли рисовать окна и выводить в них данные, существуют специальные оконные менеджеры. Самым популярным оконным менеджером под Linux является X.Org. Он также предоставляет свои библиотеки и вызовы к ним тоже можно смотреть. Для этого в аргументе -l нужно передать, за какими библиотеками следить. Отфильтруем по -l '*x11*' — это все библиотеки, связанные с X.Org.

Выводы

  1. Одна из самых важных процессорных инструкций — совершение системных вызовов. Это позволяет взаимодействовать с другими процессами и оборудованием.

  2. Разработчики часто используют режим отладки, чтобы смотреть, как работает программа и искать ошибки.

  3. Мы можем отлаживать чужие программы и смотреть, какие вызовы они делают. Для этого используется утилита strace.

  4. Многие программы используют библиотеки. С помощью ldd можно узнать, какие библиотеки использует программа, а ltrace позволяет отследить конкретные вызовы к функциям.

§7.4. Что содержит файл программы ⟶