diff --git a/CMakeLists.txt b/CMakeLists.txt index b0208c1..6756c7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,4 +14,6 @@ function(define_lab lab_name) endfunction() define_lab(lab2) -define_lab(lab3) \ No newline at end of file +define_lab(lab3) +define_lab(lab4) +define_lab(lab5) \ No newline at end of file diff --git a/lab4/CMakeLists.txt b/lab4/CMakeLists.txt new file mode 100644 index 0000000..9655301 --- /dev/null +++ b/lab4/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.16) +set(CMAKE_C_STANDARD 11) + +# Lab name +set(LAB_NAME "lab4") + +# Lab tasks +list(APPEND SOURCE_FILES + aspace.c + task2.c + null.c + task6.c + task7.c + task8.c + ) +list(APPEND NON_COMPILABLE_SRC +# .execme + ) + +set_source_files_properties(aspace.c PROPERTIES COMPILE_FLAGS -O0) +#set_source_files_properties(null.c PROPERTIES COMPILE_FLAGS -DHANDLE_SIGSEGV) +set_source_files_properties(null.c PROPERTIES COMPILE_FLAGS -g) +set_source_files_properties(task6.c PROPERTIES COMPILE_FLAGS -g) +set_source_files_properties(task7.c PROPERTIES COMPILE_FLAGS -g) +set_source_files_properties(task8.c PROPERTIES COMPILE_FLAGS -g) + +### Here goes the template + +project("${LAB_NAME}" C) + +add_custom_target("${LAB_NAME}") + +foreach (file IN LISTS SOURCE_FILES) + add_executable("${LAB_NAME}_${file}_run" "${file}") + add_dependencies("${LAB_NAME}" "${LAB_NAME}_${file}_run") +endforeach () + +foreach (file IN LISTS NON_COMPILABLE_SRC) + add_custom_command( + TARGET "${LAB_NAME}" POST_BUILD + DEPENDS "${file}" + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/${file}" + "${CMAKE_CURRENT_BINARY_DIR}/${file}" + ) +endforeach () \ No newline at end of file diff --git a/lab4/README.md b/lab4/README.md new file mode 100644 index 0000000..acdc83d --- /dev/null +++ b/lab4/README.md @@ -0,0 +1,92 @@ +# Лабораторная работа №4 + +## Задание 1 + +Было выяснено, что если не инициализировать глобальные (статические) переменные, +то порядок их расположения в памяти не определён. На trunk-версии GCC глобальные +переменные располагаются в памяти в порядке их объявления. На trunk-версии Clang +статические переменные располагаются в порядке их использования. + +Обратный порядок расположения переменных на стеке соблюдается только при компилировании +с помощью Clang. (Проверено на GCC 9.3.0, GCC 6.5.0, Clang 10.0.0) + +Если выделять 256 Кб и выше, то память выделяется в mmap-области. + +Адрес указателя можно получить с помощью оператора "амперсанд": `&pointer`. + +`&p` вернёт адрес, где лежит указатель, в то время как `p` вернёт сам указатель. + +## Задание 2 + +`name` будет перезаписан после второго вызова `scanf()`, но если второй ввод строки +будет меньше, чем первый, то в памяти останется часть первого ввода (с учётом нуль-терминала). + +При попытке записи в `name1` (`name1[0]=name1[3]`) программа даст сигнал SIGSEGV. Согласно +стандарту, запись в переменные типа `char* a = "abc"` вызывает неопределённое поведение. +Но GCC хранит переменные, проинициализированные подобным образом, в секции `.rodata`. + +## Задание 3 + +Разыменовывание нулевого указателя даёт неопределённое поведение. Как правило, это аварийное +завершение программы. В качестве примера были использованы получение значения по адресу 0x0 +и вызов функции по тому же адресу. Второй пример вызывает аварийное завершение программы не +на всех платформах -- на AVR-микроконтроллерах, например, это вызовет перезагрузку. + +## Задание 4 + +GDB остановится на сигнале SIGSEGV, в момент разыменовывания. Для получения значения по адресу +0x0 вывод будет выглядеть так: +```text +Program received signal SIGSEGV, Segmentation fault. +0x0000555555555129 in main () at /home/sh1ft/CLionProjects/os-labs/lab4/null.c:25 +25 int a = *p; +``` + +Для вызова по нулевому адресу вывод будет следующим: +```text +Program received signal SIGSEGV, Segmentation fault. +0x0000000000000000 in ?? () +``` + +Стоит обратить внимание, что для второго случая был выведен лишь адрес аварийного завершения +работы, так как для неизвестной функции по этому адресу компилятор не сгенерировал символы +отладки. + +## Задание 5 + +Valgrind с ключами `--leak-check=yes` сообщит, что программа попыталась обратиться к адресу, +не находящемуся в mapped-области, укажет номер строки в файле, где это произошло. Также Valgrind +выведет, что утечек памяти в программе нет, и завершит работу с кодом SIGSEGV. + +## Задание 6 + +Valgrind с ключами `--vgdb-error=0 --leak-check=yes` даст возможность подключить отладчик GDB. +После присоединения к процессу Valgrind (`target remote | /usr/lib/valgrind/../../bin/vgdb`) +программа будет приостановлена на точке входа `_start`. Ввод команды `continue` продолжит +дальнейшее исполнение программы. Также можно поставить брейкпоинт на определённую строчку кода +командой `break :`, условный -- `break : if `. +Программа без освобождения памяти успешно завершит работу, не вызвав остановки в отладчике, +но Valgrind заметит, что был утерян один блок памяти. + +## Задание 7 + +Ошибка в коде видна невооружённым глазом -- выход за границы массива. Но программа успешно завершит +работу во всех случаях, за исключением граничных случаев, когда по адресу сразу после массива идёт +другая страница памяти. Но Valgrind следит за памятью приложения строже, чем операционная система -- +некорректное обращение к памяти будет сразу замечено, о чём будет сообщено: +```text +==2088068== Invalid write of size 4 +==2088068== at 0x109159: main (task7.c:7) +==2088068== Address 0x4a7c1d0 is 0 bytes after a block of size 400 alloc'd +==2088068== at 0x483977F: malloc (vg_replace_malloc.c:309) +==2088068== by 0x10914A: main (task7.c:6) +``` + +## Задание 8 + +Use-After-Free -- классическая ошибка при работе с динамической памятью. Они не всегда заметны, +особенно если программа успешно выполняется в большинстве случаев (как в этом, например). К +счастью, Valgrind определяет и такие ошибки. В IDE CLion встроена графическая обёртка для данного +инструмента, которая и была использована при выполнении этого задания. + +![Valgrind GUI in CLion](valgrind_clion.png) \ No newline at end of file diff --git a/lab4/aspace.c b/lab4/aspace.c new file mode 100644 index 0000000..28f5ef7 --- /dev/null +++ b/lab4/aspace.c @@ -0,0 +1,50 @@ +/* Example code for Think OS. + +Copyright 2015 Allen Downey +License: Creative Commons Attribution-ShareAlike 3.0 + +*/ +#define PRINT_DISTANCE(a, b) printf("Distance between " #a " and " #b ": %ld\n", (void *) (a) - (void *) (b)) + +#include +#include +#include +#include +#include +#include + +int global; +int another_global; + +int main() { + char malloc_trace_path[4096]; + getcwd(malloc_trace_path, 4096); + strcat(malloc_trace_path, "/malloc_trace.txt"); + setenv("MALLOC_TRACE", malloc_trace_path, 0); + mtrace(); + + int local = 5; + int another_local = 7; + void *p = malloc(128); + void *another_p = malloc((1 << 18)); + + printf("Address of main is %p\n", main); + printf("Address of global is %p\n", &global); + printf("Address of local is %p\n", &local); + printf("Address of another_local is %p\n", &another_local); + printf("Address of p is %p\n", p); + printf("Address of another_p is %p\n\n", another_p); + + PRINT_DISTANCE(&global, &main); + PRINT_DISTANCE(&global, &another_global); + PRINT_DISTANCE(&local, &another_local); + PRINT_DISTANCE(p, another_p); + + malloc_stats(); + + FILE *minfo = fopen("malloc_info.xml", "w"); + malloc_info(0, minfo); + fclose(minfo); + + return 0; +} diff --git a/lab4/null.c b/lab4/null.c new file mode 100644 index 0000000..78a7565 --- /dev/null +++ b/lab4/null.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +#ifdef HANDLE_SIGSEGV +void on_sigsegv() { + printf("SIGSEGV\nNull pointer dereference gives UB.\n"); + exit(0); +} +#endif + +int main() { +#ifdef HANDLE_SIGSEGV + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_flags = SA_SIGINFO; + action.sa_sigaction = on_sigsegv; + sigaction(SIGSEGV, &action, NULL); +#endif + + // Dereference NULL + int *p = NULL; + //(*p)(); // Calling a function from 0x0, yay! + int a = *p; + + return 0; +} \ No newline at end of file diff --git a/lab4/task2.c b/lab4/task2.c new file mode 100644 index 0000000..d7c9fc9 --- /dev/null +++ b/lab4/task2.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +void on_sigsegv() { + printf("SIGSEGV\nWrite access to `char* a = \"abc\"` gives UB.\n"); + exit(0); +} + +int main() { + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_flags = SA_SIGINFO; + action.sa_sigaction = on_sigsegv; + sigaction(SIGSEGV, &action, NULL); + + /* Assume name shorter than 20 chars. */ + char name[20]; + puts("Enter name: "); + scanf("%19s", name); + printf("Hello, %s.\n\n\tNice to see you.\n", name); + + puts("Enter name: "); + scanf("%19s", name); + printf("Hello, %s.\n\n\tNice to see you.\n", name); + + char *name1 = "Anna"; + char a_letter = name1[0]; + name1[0] = name1[3]; + name1[3] = a_letter; + puts(name1); + + return 0; +} \ No newline at end of file diff --git a/lab4/task6.c b/lab4/task6.c new file mode 100644 index 0000000..8264f62 --- /dev/null +++ b/lab4/task6.c @@ -0,0 +1,9 @@ +#include +#include +#include + +int main() { + char *a = malloc(1024); + + return 0; +} \ No newline at end of file diff --git a/lab4/task7.c b/lab4/task7.c new file mode 100644 index 0000000..8082cd8 --- /dev/null +++ b/lab4/task7.c @@ -0,0 +1,10 @@ +#include +#include +#include + +int main() { + int *data = malloc(100 * sizeof(int)); + data[100] = 0; + + return 0; +} \ No newline at end of file diff --git a/lab4/task8.c b/lab4/task8.c new file mode 100644 index 0000000..7fb769f --- /dev/null +++ b/lab4/task8.c @@ -0,0 +1,12 @@ +#include +#include +#include + +int main() { + int *data = malloc(100 * sizeof(int)); + free(data); + + printf("%d\n", data[96]); + + return 0; +} \ No newline at end of file diff --git a/lab4/valgrind_clion.png b/lab4/valgrind_clion.png new file mode 100644 index 0000000..a094613 Binary files /dev/null and b/lab4/valgrind_clion.png differ diff --git a/lab5/.execme b/lab5/.execme new file mode 100755 index 0000000..901d422 --- /dev/null +++ b/lab5/.execme @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +echo "TODO!" +exit 1 \ No newline at end of file diff --git a/lab5/CMakeLists.txt b/lab5/CMakeLists.txt new file mode 100644 index 0000000..5e6e212 --- /dev/null +++ b/lab5/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.16) +set(CMAKE_C_STANDARD 11) + +# Lab name +set(LAB_NAME "lab5") + +# Lab tasks +list(APPEND SOURCE_FILES + task1.c + ) +list(APPEND NON_COMPILABLE_SRC + .execme + ) + +### Here goes the template + +project("${LAB_NAME}" C) + +add_custom_target("${LAB_NAME}") + +foreach (file IN LISTS SOURCE_FILES) + add_executable("${LAB_NAME}_${file}_run" "${file}") + add_dependencies("${LAB_NAME}" "${LAB_NAME}_${file}_run") +endforeach () + +foreach (file IN LISTS NON_COMPILABLE_SRC) + add_custom_command( + TARGET "${LAB_NAME}" POST_BUILD + DEPENDS "${file}" + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/${file}" + "${CMAKE_CURRENT_BINARY_DIR}/${file}" + ) +endforeach () \ No newline at end of file diff --git a/lab5/task1.c b/lab5/task1.c new file mode 100644 index 0000000..117b189 --- /dev/null +++ b/lab5/task1.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include +#include +#include + +/* +1. Используя creat(), write(), fflush(), close(), gettimeofday(), разработать программу, которая +открывает файл, +записывает туда 300 KB, +очищает все записи, +закрывает и +удаляет файл, +а также измеряет и выводит время, затраченное на каждое действие. +*/ + +int main() { +#error "The task is not done!" +} \ No newline at end of file