Результаты домашних работ должны быть представлены на GitHub, для чего у тебя должна быть там учётка. Свой код надо хранить строго в приватных репозиториях.
Для выполнения заданий потребуется использовать готовый преконфигурированный Vagrant-образ виртуальной машины, который содержит все необходимые инструменты для запуска, тестирования и отладки кода. Vagrant — это средство управления виртуальными машинами, он будет использоваться для загрузки и запуска подготовленного образа виртуальной машины.
ℹ️
|
Только для пользователей Windows
Поскольку Windows не поддерживает
Все последующие команды следует вводить в терминале «Cygwin Terminal». |
-
Vagrant зависит от VirtualBox (программное обеспечение с открытым программным кодом для виртуализации), поэтому прежде всего необходимо загрузить и установить свежую версию с сайта VirtualBox.
-
Установить свежую версию с сайта Vagrant.
-
После установки Vagrant нужно ввести в терминал следующие команды:
$ mkdir cs162-vm $ cd cs162-vm $ vagrant init cs162/spring2019 $ vagrant up $ vagrant ssh
Эти команды приведут к тому, что произойдет загрузка подготовленного образа виртуальной машины, она будет запущена и будет поднято ssh-соединение с хоста в виртуальную машину. Отметим, что команда «up» требует для выполнения достаточно много времени и устойчивое соединение с интернетом.
-
Все vagrant-команды требуется запускать из директория
cs162-vm
, который был создан ранее. Нельзя удалять этот директорий, иначе vagrant не будет знать как управлять виртуальной машиной (ВМ), которая была создана. -
Команда
vagrant halt
приведёт к остановке виртуальной машины. Для следующего запуска ВМ достаточно ввести только две команды:vagrant up vagrant ssh
Если команда vagrant up
выпадает с ошибкой, попробуй выполнить vagrant provision
и проверить не исправит ли она ошибку. Если совсем всё плохо, можно попробовать полностью удалить ВМ командой vagrant destroy
. Потом снова создать ВМ vagrant up
.
Выполни эти команды чтобы настроить имя пользователя и емейл, которые будут использоваться в git-коммитах. Не забудь заменить ТВОЁ_ИМЯ
и ТВОЯ_ПОЧТА_КОМ
на нормальное имя (лучше латиницей) и на нормальную почту.
$ git config --global user.name "ТВОЁ_ИМЯ" $ git config --global user.email "ТВОЯ_ПОЧТА_КОМ"
Для того, чтобы можно было изнутри ВМ работать с GitHub нужно настроить ssh-ключи.
В терминале ВМ нужно выполнить:
$ ssh-keygen -N "" -f ~/.ssh/id_rsa $ cat ~/.ssh/id_rsa.pub
Первая команда создает новую пару ssh-ключей. Вторая команда выводит на экран публичный ключ. Для доступа к GitHub по ключам нужно перейти по ссылке https://github.com/settings/keys и добавить показанный на экране публичный ключ к GitHub-аккаунту. Можно назвать этот ключ произвольным образом, но лучше, чтобы название что-то обозначало, например, «OS_VM». Ключ должен начинаться с «ssh-rsa» и заканчиваться «vagrant@development».
При выдаче любого задания, включая это, будет предоставлена ссылка, ведущая на GitHub Classroom (надстройка над GitHub для ведения занятий). После перехода по этой ссылке системой будет предложено принять задание, нажав кнопку «Accept this assignment». При этом на GitHub будет создан индивидуальный репозиторий с начальными файлами задания, которые надо будет дополнить. Например, для этого задания будет создан индивидуальный репозиторий с адресом https://github.com/uniyar-os/hw-01-твой_github_юзернейм
. Этот репозиторий будет приватным — данные находящиеся в нём будут доступны только студенту и преподавателю.
Теперь следует: Для этого в терминале ВМ:
-
Сделать копию репозитория (клон) в ВМ.
$ git clone [email protected]:uniyar-os/hw-01-твой_github_юзернейм.git
-
Удостовериться, что в текущем директории внутри ВМ появилась папка
hw-01-твой_github_юзернейм
.$ ls
Именно в в папку
hw-01-твой_github_юзернейм
«склонирован» удаленный репозиторий, и именно в ней следует работать с файлами, регулярно фиксируя изменения в файлах (git commit
) и отправляя (git push
) эти изменения в удаленный репозиторий. -
Перейди в папку
hw-01-твой_github_юзернейм
командой:$ cd w-01-твой_github_юзернейм
В образ ВМ добавлена поддержка удаленного доступа к файлам (SMB-сервер), которая позволяет редактировать файлы внутри папки ВМ пользователя vagrant
(именно в нее ты склонировал репозиторий). То есть можно доступиться до папки ВМ с помощью текстовых редакторов установленных на хостовой системе. Это рекомендуемый способ работы с файлами в этом курсе. Есть и другие, менее удобные способы. Например использовать текстовые редакторы (nano
, vim
) непосредственно в терминале.
-
Открой проводник, нажми Ctrl+L для фокусировки на элементе ввода расположения.
-
Напечатай
\\192.168.162.162\vagrant
и нажмиEnter
. -
Имя пользователя
vagrant
, парольvagrant
.
Тебе должно быть видно содержимое директория пользователя vagrant
, в том числе и hw-01-твой_github_юзернейм
.
-
Открой Finder.
-
В меню выбери «Переход → Подключение к серверу…»
-
В строке адреса вбей
smb://192.168.162.162/vagrant
. -
Имя пользователя
vagrant
, парольvagrant
.
Тебе должно быть видно содержимое директория пользователя vagrant
, в том числе и hw-01-твой_github_юзернейм
.
Перед продвижением следует пробежаться по полезным инструментам, которые входят в набор любого хакера. Умение пользоваться некоторыми из них (например git
, make
) совершенно необходимо при выполнении заданий этого курса. Другие, например gdb
или tmux
, являются усилителями продуктивности. Первая из них помогает искать ошибки в коде, вторая позволяет использовать многозадачность более эффективно. Все описанные ниже инструменты уже находятся внутри ВМ и готовы к использованию.
ℹ️
|
Этот документ не является исчерпывающим руководством по рассматриваемым инструментам. Вместо этого ниже будут предоставлены ссылки на внешние ресурсы описывающие способ работы с тем или иным инструментом. Крайне рекомендуется ознакамливаться со всеми предлагаемыми материалами, даже если они не требуется для выполнения задания. |
Git
— это средство контроля версий, которое помогает отлеживать изменения в коде. GitHub
— это один из множества сервисов для размещения кода. Можно пользоваться git
локально, но проталкивать (git push
) изменения в GitHub
для удобной совместной (с преподавателем) работы.
Возможно, что ты уже знаком с некоторыми командами git
, однако понимание внутренних механизмов работы скрывающихся за относительно простыми командами позволит более глубоко понимать и предсказывать поведение этого инструмента.
Если ты никогда раньше не использовал git
и хочешь разобраться «с самого начала», то начни отсюда.
Программа make
предназначена для автоматического создания исполняемых файлов и библиотек из исходного программного кода. Построение исполняемых файлов описывается с помощью правил, определенных в файле Makefile
, который обычно располагается в корневом директории проекта, который требуется построить. Правила работают довольно интересно: в файле Makefile
с помощью особого синтаксиса описывается список зависимостей и make
анализируя этот файл строит граф зависимостей для построения всего, что требуется. К сожалению особый синтаксис довольно особый, временами он может сбивает с толку, особенно если ты плохо понимаешь, что в действительности делает make
.
Документацию на русском можно найти здесь, а практическое руководство с примерами тут. Конечно же лучше всего читать официальную документацию на английском тут.
Попробуй применить простейший способ использования make
(без Makefile
). Находясь в директории с заданием, можно скомпилировать и слинковать wc.c
просто выполнив:
$ make wc
В результате будет создан исполнимый файл wc
, который можно запустить. Попробуй:
$ ./wc wc.c
А если так? (Подсказка: чтоб разобраться что происходит выполни which wc
.)
$ wc wc.c
Твоё первое задание будет состоять в том, чтобы модифицировать заготовку wc.c
, так, чтобы твоя программа работала также как утилита wc
, встроенная в Linux. Спецификацию утилиты wc
можно прочитать так: man wc
. Важно! Тебе не надо реализовывать поддержку флагов и опций — достаточно просто обрабатывать файл (если файл не задан, то брать входные данные из SDTIN
).
Отлаживать программы на C тяжело. Катастрофические ошибки (крэши) по-умолчанию не выводят ни человекочитаемых объяснений наступивших проблем, ни состояния стека (порядок вызовов). К счастью есть gdb
. Если при компиляции использовать флаг -g
, то в результирующем исполнимом файле будет содержаться необходимая для отладки дополнительная информация (debug symbols
), именно она позволяет gdb
делать магию. При запуске программы из-под gdb
ты сможешь не только следить за состоянием стека, но также проверять значения переменных, менять их, приостанавливать исполнение и много еще чего!
Обычный вариант gdb
поддерживает очень простой интерфейс, поэтому в образе ВМ, который ты используешь, предустановлена более развесистый вариант этого дебаггера — cgdb
(подсветка синтаксиса и несколько дополнительных удобных фичей). Переключение между верхней и нижней панелью в cgdb
осуществляется с помощью i
и ESC
.
Утилита gdb
может запускать новые процессы и прикреплять их (attach
) к существующим процессам (это может быть полезным для отладки твоего кода).
Что почитать: переводная статья на Хабре, туториал на английском и, как всегда, хорошая, но многословная официальная документация.
Разберись как отлаживать программы на примере wc
:
-
При компиляции файла
wc.c
с помощью компилятораgcc
добавь флаг-g
. -
Запусти получившийся исполнимый файл из-под
gdb
. -
Установи точку останова (break point, брекпойнт) на функции
main
. -
Исполни программу до брекпойнта.
-
Попробуй разные команды
gdb
. -
Разберись как передавать аргументы командной строки.
-
Добавь в
main
локальные переменные и попробуй проверить из значения. -
Изучи команды
step
,next
иbreak
.
Программа tmux
— мультиплексор терминала, позволяющий в одной терминальной сессии симулировать несколько консолей.
Запуск новой tmux-сессии осуществляется так:
$ tmux new -s <имя_сессии>
После создания новой сессии ты увидишь обычный терминал. Нажатие ctrl-b + c
приведет к созданию новой консоли, переключить фокус ввода между которыми можно комбинацией ctrl-b + n
, где n — номер консоли. Комбинации клавиш ctrl-b + %
и ctrl-b + "
позволяют разделить консоль на две, вертикально и горизонтально соответственно.
Для открепления (detach) от tmux
примени ctrl-b + d
. Сессия tmux
со всеми созданными консолями и запущенными в них программах продолжает существовать и работать. Вернуться в неё можно так:
tmux attach -t <имя_сессии>
Самая впечатляющая особенность в том, что можно отключить ssh-соединение, а tmux-сессия, созданная в нём продолжит работать. Более того после переподключения по ssh к tmux-сессии можно опять приаттачиться, как это показано выше.
Программа vim
— удобный текстовый редактор для терминала. Стоит научиться использовать его (Учебник на русском, ещё один учебник), хотя некоторые предпочитают emacs.
Какой бы редактор ты не выбрал, нужно научиться правильно и эффективно использовать его для написания кода.
Инструмент ctags
облегчает навигацию по проектам с большим количеством кода, он может сэкономить много времени. Помимо всего прочего ctags
позволяет перескакивать к любому заданному имени (символу) в коде. Если совместить эту возможность с возможностью текстового редактора запоминать переходы «вперед-назад», это очень облегчит анализ кодовой базы.
Если для редактирования кода тобой используется какой-то другой текстовый редактор, то с большой долей вероятности он тоже поддерживает ctags
, надо просто самостоятельно отыскать инструкцию.
Есть шанс, что ты когда-то использовал gcc
для компиляции программ, однако непосредственное применение такого подхода при увеличении количества файлов с исходным кодом приводит к необоснованной сложности запуска процесса компиляции. В этом задании тебе требуется написать Makefile
, который будет управлять компиляцией main.c
, wc.c
и map.c
(возможно есть смысл в том, чтобы добавить флаг -g
к gcc
на этом шаге). Также неплохой мыслью было бы написать правило для удаления бинарных файлов, вызываемое командой make clean
. Если не всё сказанное понятно, прочитай ещё раз раздел 2.2.
Не забудь зафиксировать изменения с помощью git
и протолкнуть их на GitHub
.
Начать думать на C поможет задачка с файлов wc.c
. При работе над ней особое внимание удели тому, как приложения используют операционную систему: передача аргументов из командной строки, чтение файлов, стандартные дескрипторы файлов.
Твоя задача в том, чтобы создать ограниченный клон известной утилиты wc
, которая, в заданном текстовом файле, подсчитывает количество строк, слов и символов. Попробуй позапускать оригинальную wc
в ВМ, чтобы понять как она работает и сделать так же в wc.c
(функциональность дополнительных аргументов реализовывать не надо). Достаточно реализовать поддержку двух режимов запуска wc ИМЯ_ФАЙЛА
и wc
без аргументов. В последнем случае программа должна читать данные из стандартного потока STDIN
.
При работе над этим заданием попрактикуйся с отладкой в gdb
. Используй отладку для пошагового исполнения программы и проверки значений переменных.
Регулярно фиксируй состояния файла wc.c
помощью git
и проталкивай их на GitHub
(бинарные исполняемые файлы добавлять в git
неверно и преступно, разузнай поподробнее про .gitignore
).
Теперь когда ты подразобрался с C и набил руку с предложенными инструментами, настало время разобраться в том, что в действительности происходит при запуске программ и с чем операционной системе приходится иметь дело.
Запусти твой вариант wc
из-под gdb
указав файл для анализа с помощью аргумента командной строки, создай точку останова (break point) при входе в функцию main
, запусти выполнение до точки останова, пошагово дойди до середины программы. Посмотри на состояние стека используя команды where
и/или backtrace
(bt
).
Создай файл gdb.txt
и в любимом текстовом редакторе запиши в этот файл ответы на такие вопросы:
-
Каково значение переменной
argv
? (подсказка:print argv
) -
На что указывает
argv
? (подсказка:print argv[0]
) -
Каков адрес функции
main
? -
Попробуй выполнить
info stack
. Объясни, что видишь. -
Попробуй выполнить
info frame
. Объясни, что видишь. -
Попробуй выполнить
info registers
. В каких регистрах находится информация, которую ты можешь идентифицировать, как имеющую отношение к исполняемому коду?
Не забудь коммитнуть и протолкнуть результат.
В исполняемом файле скрывается больше, чем заметно на первый взгляд. Заглянем же внутрь. Запусти objdump -x -d wc
и ты увидишь, что там есть: несколько сегментов, имена функций и переменных, связанных с адресами функций и величин. Заметь, что все кишочки программы разбиты на куски, хранящиеся в сегментах.
В выводе objdump
эти сегменты перечислены после заголовка Sections
. Ты можешь подразобраться с терминологией, поискав информацию в интернете.
Создай файл objdump.txt
и, разглядывая вывод objdump
, ответь в нём на следующие вопросы:
-
Какой формат используется в этом бинарном файле? Какая архитектура поддерживается?
-
Какие имена сегментов/секций тебе известны?
-
Какой сегмент/секция содержат функцию
main
и какой у неё адрес? (Отличается ли от от того, что был известен изgdb
?) -
Видишь ли ты сегмент стека (stack)? А сегмент кучи (heap)? Объясни.
Добавь файл с ответами в git
.
Так! Теперь ты готов приступить к программе, которая покажет свою же структуру в памяти. Второй файл из задания map.c
— заготовка, которая готова чуть менее, чем полностью. Нужно её модифицировать для получения адресов, которые понадобятся для решения. Результат выполнения программы должен быть примерно такой (адреса могут отличаться).
_main @ 0x4005c2 recur @ 0x40057d _main stack: 0x7fffda11f73c static data: 0x601048 Heap: malloc 1: 0x671010 Heap: malloc 2: 0x671080 recur call 3: stack@ 0x7fffda11f6fc recur call 2: stack@ 0x7fffda11f6cc recur call 1: stack@ 0x7fffda11f69c recur call 0: stack@ 0x7fffda11f66c
Обдумай следующие вопросы и запиши ответы в файл map.txt
.
-
Используй
objdump
с флагом-D
на исполняемом файлеmap
. Какие адреса из вывода./map
определены в исполнимом файле? В каком сегменте/секции каждый из них расположен? -
Составь список важных сегментов и поясни какова роль каждого из них. (Поищи, если нужно, в интернете их названия.)
-
В каком направлении растет стек?
-
Каков размер стек-фрейма для каждого рекурсивного вызова?
-
Где куча? В каком направлении она растет?
-
Являются ли две области памяти выделенные с помощью
malloc()
смежными? Есть ли между ними некоторое незанятое пространство?
Не забывай про git
.
Операционная система имеет дело с размерами динамически меняющихся сегментов: стек и куча. Сколько памяти надо резервировать для них? Отыщи способ для изменения этих ограничений в Linux (тебе понадобится интернет).
Измени файл main.c
так, чтобы при выполнении он выводил максимально доступный размер стека, максимально возможное количество процессов и максимальное количество дескрипторов файлов. В начале при компиляции и запуске main.c
будет выведена информация об ограничениях. К сожалению все значения будут равны нулю. Твоя задача сделать так, чтобы программа вывела действительные ограничения, налагаемые операционной системой (используй мягкие ограничения, а не жёсткие). (Подсказка: запусти man getrlimit
).
Результат должен быть примерно таким:
stack size: 8388608 process limit: 2782 max file descriptors: 1024
Результат, как и всегда, следует зафиксировать в git
и отправить в GitHub
.
Всё!