From 58ea41fe94a58071b71e9f4d62777524dbef219a Mon Sep 17 00:00:00 2001 From: maxbarsukov Date: Wed, 25 Oct 2023 00:45:36 +0300 Subject: [PATCH] feat(pl): :sparkles: programming languages seminar#4 --- 3 япы/семинары/seminar4/.gitignore | 2 + 3 япы/семинары/seminar4/README.md | 13 ++ 3 япы/семинары/seminar4/exercises.md | 163 +++++++++++++++++++ 3 япы/семинары/seminar4/hello.c | 5 + 3 япы/семинары/seminar4/hello.txt | 1 + 3 япы/семинары/seminar4/makefile | 23 +++ 3 япы/семинары/seminar4/questions/q1.asm | 6 + 3 япы/семинары/seminar4/string.asm | 38 +++++ 3 япы/семинары/seminar4/task1/hello_mmap.asm | 81 +++++++++ 9 files changed, 332 insertions(+) create mode 100644 3 япы/семинары/seminar4/.gitignore create mode 100644 3 япы/семинары/seminar4/README.md create mode 100644 3 япы/семинары/seminar4/exercises.md create mode 100644 3 япы/семинары/seminar4/hello.c create mode 100644 3 япы/семинары/seminar4/hello.txt create mode 100644 3 япы/семинары/seminar4/makefile create mode 100644 3 япы/семинары/seminar4/questions/q1.asm create mode 100644 3 япы/семинары/seminar4/string.asm create mode 100644 3 япы/семинары/seminar4/task1/hello_mmap.asm diff --git a/3 япы/семинары/seminar4/.gitignore b/3 япы/семинары/seminar4/.gitignore new file mode 100644 index 0000000..59b32b2 --- /dev/null +++ b/3 япы/семинары/seminar4/.gitignore @@ -0,0 +1,2 @@ +*.o +hello diff --git a/3 япы/семинары/seminar4/README.md b/3 япы/семинары/seminar4/README.md new file mode 100644 index 0000000..6e04e29 --- /dev/null +++ b/3 япы/семинары/seminar4/README.md @@ -0,0 +1,13 @@ +# Семинар 4: Виртуальная память, загрузка программ + +- Что такое виртуальная память; +- Изучение карты памяти процесса; +- Использование `mmap`; +- Вызов ассемблерного кода из C; +- ELF-файлы; +- Загрузка файла; + + +[Семинар 4](https://gitlab.se.ifmo.ru/programming-languages/cse-programming-languages-fall-2023/main/-/tree/master/seminar-4) + +[Задания](./exercises.md) diff --git a/3 япы/семинары/seminar4/exercises.md b/3 япы/семинары/seminar4/exercises.md new file mode 100644 index 0000000..dc51488 --- /dev/null +++ b/3 япы/семинары/seminar4/exercises.md @@ -0,0 +1,163 @@ +# Задания + +### Вопрос + +Посмотрите, какие у вас запущены процессы. Выберите один (например, оболочку, которой вы пользуетесь) и посмотрите на содержимое директории `/proc/PID/`, где `PID` — его идентификатор. + +### Вопрос + +Что внутри файла `/proc/PID/environ`? + +### Вопрос + +Прочитайте про запуск программ в фоне. Что делают команды `bg`, `fg`, `jobs`? +Напишем [простую программу](./questions/q1.asm), которая входит в бесконечный цикл: + +```nasm +section .data +correct: dq -1 +section .text +global _start +_start: +jmp _start +``` + +Что она делает? Скомпилируйте и запустите её в фоне. + +### Вопрос + +Выведите с помощью `cat` содержимое файла `/proc/PID/maps`, где `PID` — идентификатор процесса, который вы запустили в фоне. + +### Вопрос + +Почему в регионах начальный и конечный адрес в 16-ричном формате заканчиваются всегда на три нуля? + +### Вопрос + +Определите, по каким адресам загружаются секции `.text` и `.data` из примера. Вам может помочь `readelf` и таблица символов. + +### Вопрос + +Определите хотя бы один запрещённый диапазон адресов. + +### Вопрос + +Что такое inode в файловой системе? + +### Вопрос + +Что находится в остальных столбцах? Прочитайте про файл `/proc/PID/maps` в `man procfs`. + +### Вопрос + +Прочитайте в `man mmap` ответы на следующие вопросы: Какие аргументы принимает `mmap`? В чём их смысл? Какой аргумент в каком регистре? + + +## Задание 1 + +Создайте файл `hello.txt` с текстом `Hello, mmap!`. Используя заготовку, отобразите его в память и выведите текст из него в стандартный поток вывода. Не забудьте вызвать `munmap` (его номер системного вызова `11`) и закрыть файл (`close`, номер системного вызова `3`) по завершению работы с файлом. + +- [hello_mmap.asm](./task1/hello_mmap.asm) + +[task1](./task1/) + +## Задание 2 + +Прочитайте документацию по системному вызову `fstat` (номер `5`). Вас будет интересовать поле `st_size` типа `off_t` структуры `struct stat`, которую функция `fstat` заполняет. Используйте его, чтобы корректно вычислить размер файла при выводе данных, и выведите их, используя функцию `print_substring` (принимает на вход два аргумента: адрес начала строки и количество байт для вывода). Полученный размер файла используйте в вызовах `mmap`, `munmap` и `print_substring`. + +Для выполнения задания вам пригодиться следующая информация: + +```c +sizeof(struct stat) = 144 +offsetof(struct stat, st_size) = 48 +sizeof(off_t) = 8 +``` + +Для выделения памяти на хранение структуры `stat` рекомендуется использовать стек. + +[task2](./task2/) + +### Вопрос + +Что делает функция `world`? Объясните принцип её работы. + +[string.asm](./questions/string.asm) + +## Задание 3 + +В этих файлах не хватает нескольких строчек чтобы можно было взаимодействовать с кодом друг друга. Допишите файлы так, чтобы функции `print_string` и `hello` вызывалась и проверьте результат. Подсказка: вспомните, что нужно, чтобы из одного файла с C-кодом вызвать код из другого файла. + +[task3](./task3/) + +### Вопрос + +Какие аргументы может принимает функция языка С по умолчанию? В чем отличие прототипов `int f();` и `int f(void);`? + +### Вопрос + +Изучите `Makefile` [для сборки этой программы](./makefile). Что означают `$@`, `$^`? + +- `$@` - цель (имя файла для сборки) текущего правила. +- `$^` - список всех зависимостей текущего правила, разделенных пробелами. Повторные включения зависимостей удаляются (каждая зависимость включается в список ровно один раз). + +### Вопрос + +Что такое секции `.rodata` и `.bss`? + +### Вопрос + +Что в `nasm` делают директивы `resb`, `resq` и [другие](https://nasm.us/doc/nasmdoc3.html)? + +### Вопрос + +В файле `hello.c`, в какой секции будет выделена строка `=”hello”=`? + +### Вопрос + +Введите `readelf` без аргументов. Прочитайте вывод и определите, какие ключи необходимы, чтобы отобразить три заголовка файла. + +### Вопрос + +Выведите `file header` (ключ `-h` или `--file-header`) для файла `hello.o`. Что такое `Entry point address` и почему его значение `0`? + +### Вопрос + +Выведите `program header` (ключ `-l` или `--program-headers`) для файла `hello.o`. Объясните результат. + +### Вопрос + +Определите адреса загрузки секций `.text` и `.rodata` используя список секций (ключ `-S` или `--section-headers`). + +### Вопрос + +Выведите `program header` для файла `hello`. В какие сегменты попадают `.text` и `.rodata`? Какие адреса загрузки этих сегментов? + +### Вопрос + +Модифицируйте программу на C так, чтобы она входила в бесконечный цикл. Это позволит нам проверить наши догадки про адреса секций и сегментов. + +### Вопрос + +Выведите карту регионов памяти для запущенной программы, с которой мы сегодня работаем. Сопоставьте сегменты, содержащие секции `.rodata` и `.text` регионам памяти. Верно ли, что одному региону памяти соответствует только один сегмент? + +### Вопрос + +Какие регионы соответствуют частям динамических библиотек? У динамических библиотек в Linux расширение `*.so`. + +### Вопрос + +Выполните `ldd hello`. Объясните результат. + +### Вопрос + +Как вы думаете, почему стандартная библиотека C реализована как динамическая библиотека, а не включается в состав исполняемого файла статически, как обычные `.o` файлы? + +## Задание 4 + +Объедините ассемблерный код для вывода содержимого файла с кодом на языке C. Пусть ваша программа будет просить пользователя ввести название файла, а затем выведет его содержимое в стандартный поток вывода используя код, написанный в начале сегодняшнего семинара (сделайте из него функцию `print_file`, которая будет принимать имя файла первым аргументом). Не забудьте, что для корректной работы необходимо следовать соглашениям о вызовах и сохранить `callee-saved` регистры, которые вы используете, в начале своей функции `print_file`. Для вывода сообщений (например `“Please enter file name: “`) используйте собственную реализацию `print_string` из сегодняшнего семинара. +Желающие могут прочитать больше про регионы `[vdso]` и `[vvar]`, использующиеся для ускорения некоторых системных вызовов: + +- `man vdso` +- [Implementing virtual system calls](https://lwn.net/Articles/615809/) + +[task4](./task4/) diff --git a/3 япы/семинары/seminar4/hello.c b/3 япы/семинары/seminar4/hello.c new file mode 100644 index 0000000..9fd41fe --- /dev/null +++ b/3 япы/семинары/seminar4/hello.c @@ -0,0 +1,5 @@ +/* hello.c */ +int main() { + print_string("hello"); + world(); +} diff --git a/3 япы/семинары/seminar4/hello.txt b/3 япы/семинары/seminar4/hello.txt new file mode 100644 index 0000000..89315b0 --- /dev/null +++ b/3 япы/семинары/seminar4/hello.txt @@ -0,0 +1 @@ +Hello, mmap! \ No newline at end of file diff --git a/3 япы/семинары/seminar4/makefile b/3 япы/семинары/seminar4/makefile new file mode 100644 index 0000000..0513572 --- /dev/null +++ b/3 япы/семинары/seminar4/makefile @@ -0,0 +1,23 @@ +CFLAGS = -g -O2 -Wall -Werror -std=c17 -Wdiscarded-qualifiers -Wincompatible-pointer-types -Wint-conversion +CC = gcc +LD = gcc +ASM = nasm +ASMFLAGS =-felf64 -g + +# Если есть код на C, то компоновку тоже нужно производить +# с помощью gcc, а не ld +hello: hello.o string.o + $(LD) -o $@ $^ + +# target : dependencies +# do this +hello.o: hello.c + $(CC) -c $(CFLAGS) -o $@ $< + +string.o: string.asm + $(ASM) $(ASMFLAGS) -o $@ $< + +clean: + $(RM) hello hello.o string.o + +.PHONY: clean diff --git a/3 япы/семинары/seminar4/questions/q1.asm b/3 япы/семинары/seminar4/questions/q1.asm new file mode 100644 index 0000000..ac96e48 --- /dev/null +++ b/3 япы/семинары/seminar4/questions/q1.asm @@ -0,0 +1,6 @@ +section .data +correct: dq -1 +section .text +global _start +_start: +jmp _start diff --git a/3 япы/семинары/seminar4/string.asm b/3 япы/семинары/seminar4/string.asm new file mode 100644 index 0000000..2c13b05 --- /dev/null +++ b/3 япы/семинары/seminar4/string.asm @@ -0,0 +1,38 @@ +; string.asm +global world +global print_string + +section .rodata +message: db ", world!", 10, 0 + +section .text + +string_length: + mov rax, 0 + .loop: + xor rax, rax + .count: + cmp byte [rdi+rax], 0 + je .end + inc rax + jmp .count + .end: + ret + +world: + mov rdi, message + +print_string: + push rax + push rdi + call string_length + mov rsi, [rsp] + mov rdx, rax + mov rax, 1 + mov rdi, 1 + push rcx + syscall + pop rcx + pop rdi + pop rax + ret diff --git a/3 япы/семинары/seminar4/task1/hello_mmap.asm b/3 япы/семинары/seminar4/task1/hello_mmap.asm new file mode 100644 index 0000000..20a578f --- /dev/null +++ b/3 япы/семинары/seminar4/task1/hello_mmap.asm @@ -0,0 +1,81 @@ +; hello_mmap.asm +%define O_RDONLY 0 +%define PROT_READ 0x1 +%define MAP_PRIVATE 0x2 +%define SYS_WRITE 1 +%define SYS_OPEN 2 +%define SYS_MMAP 9 +%define SYS_EXIT 60 +%define FD_STDOUT 1 + +section .data + ; This is the file name. You are free to change it. + fname: db 'hello.txt', 0 + +section .text +global _start + +; use exit system call to shut down correctly +exit: + mov rax, SYS_EXIT + xor rdi, rdi + syscall + +; These functions are used to print a null terminated string +; rdi holds a string pointer +print_string: + push rdi + call string_length + pop rsi + mov rdx, rax + mov rax, SYS_WRITE + mov rdi, FD_STDOUT + syscall + ret + +string_length: + xor rax, rax +.loop: + cmp byte [rdi+rax], 0 + je .end + inc rax + jmp .loop +.end: + ret + +; This function is used to print a substring with given length +; rdi holds a string pointer +; rsi holds a substring length +print_substring: + mov rdx, rsi + mov rsi, rdi + mov rax, SYS_WRITE + mov rdi, FD_STDOUT + syscall + ret + +_start: + ; Вызовите open и откройте fname в режиме read only. + mov rax, SYS_OPEN + mov rdi, fname + mov rsi, O_RDONLY ; Open file read only + mov rdx, 0 ; We are not creating a file + ; so this argument has no meaning + syscall + ; rax holds the opened file descriptor now + + ; Вызовите mmap c правильными аргументами + ; Дайте операционной системе самой выбрать, куда отобразить файл + ; Размер области возьмите в размер страницы + ; Область не должна быть общей для нескольких процессов + ; и должна выделяться только для чтения. + + + ; с помощью print_string теперь можно вывести его содержимое + + ; теперь можно освободить память с помощью munmap + + ; закрыть файл используя close + + ; и выйти + call exit