Add 4th and 5th labs

Yury Kurlykov 2020-04-30 13:38:33 +10:00
parent 1de33e9192
commit bbebc58c2b
Signed by: t1meshift
GPG Key ID: B133F3167ABF94D8
13 changed files with 347 additions and 1 deletions

View File

@ -14,4 +14,6 @@ function(define_lab lab_name)
endfunction() endfunction()
define_lab(lab2) define_lab(lab2)
define_lab(lab3) define_lab(lab3)
define_lab(lab4)
define_lab(lab5)

46
lab4/CMakeLists.txt Normal file
View File

@ -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 ()

92
lab4/README.md Normal file
View File

@ -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 <srcfile>:<lineno>`, условный -- `break <srcfile>:<lineno> if <condition>`.
Программа без освобождения памяти успешно завершит работу, не вызвав остановки в отладчике,
но 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)

50
lab4/aspace.c Normal file
View File

@ -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 <malloc.h>
#include <mcheck.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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;
}

28
lab4/null.c Normal file
View File

@ -0,0 +1,28 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#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;
}

35
lab4/task2.c Normal file
View File

@ -0,0 +1,35 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
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;
}

9
lab4/task6.c Normal file
View File

@ -0,0 +1,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
int main() {
char *a = malloc(1024);
return 0;
}

10
lab4/task7.c Normal file
View File

@ -0,0 +1,10 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
int main() {
int *data = malloc(100 * sizeof(int));
data[100] = 0;
return 0;
}

12
lab4/task8.c Normal file
View File

@ -0,0 +1,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
int main() {
int *data = malloc(100 * sizeof(int));
free(data);
printf("%d\n", data[96]);
return 0;
}

BIN
lab4/valgrind_clion.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

7
lab5/.execme Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
echo "TODO!"
exit 1

34
lab5/CMakeLists.txt Normal file
View File

@ -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 ()

21
lab5/task1.c Normal file
View File

@ -0,0 +1,21 @@
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
/*
1. Используя creat(), write(), fflush(), close(), gettimeofday(), разработать программу, которая
открывает файл,
записывает туда 300 KB,
очищает все записи,
закрывает и
удаляет файл,
а также измеряет и выводит время, затраченное на каждое действие.
*/
int main() {
#error "The task is not done!"
}