diff --git a/chapters/data/investigate-memory/guides/memory-leak/.gitignore b/chapters/data/investigate-memory/guides/memory-leak/.gitignore index c868610849..296b2ccf64 100644 --- a/chapters/data/investigate-memory/guides/memory-leak/.gitignore +++ b/chapters/data/investigate-memory/guides/memory-leak/.gitignore @@ -1,3 +1 @@ -/memory_leak -/memory_leak_malloc -/mem.trace +support/ diff --git a/chapters/data/investigate-memory/guides/memory-leak/Makefile b/chapters/data/investigate-memory/guides/memory-leak/Makefile new file mode 100644 index 0000000000..931b12900e --- /dev/null +++ b/chapters/data/investigate-memory/guides/memory-leak/Makefile @@ -0,0 +1,10 @@ +PYTHON = python3 +SCRIPT = generate_skels.py + +skels: + mkdir -p support/src/ + $(PYTHON) $(SCRIPT) --input ./solution/src/ --output ./support/src/ + cp -r solution/tests/ support/tests + +clean: + rm -rf support/ diff --git a/chapters/data/investigate-memory/guides/memory-leak/README.md b/chapters/data/investigate-memory/guides/memory-leak/README.md index 5f309d0869..98e8380e8a 100644 --- a/chapters/data/investigate-memory/guides/memory-leak/README.md +++ b/chapters/data/investigate-memory/guides/memory-leak/README.md @@ -1,19 +1,19 @@ # Memory Leaks -A memory leak occurs when we lose reference to a memory area. -That is, a pointer used to point to a memory area. -And then it's pointing to a new memory area and the old memory area is lost. +A memory leak happens when a memory region is allocated but no longer accessible. +This typically occurs when a pointer that referenced a memory area is redirected to a new memory location, leaving the original memory area unreachable and unable to be freed. -Enter the `chapters/data/investigate-memory/guides/memory-leak/support` directory. +Enter the `chapters/data/investigate-memory/guides/memory-leak` folder, run `make skels`. +Enter the `chapters/data/investigate-memory/guides/memory-leak/support/src` directory. It stores two files showing memory leaks: - one in C++: `memory_leak.cpp` -- one in C: `memory_leak_malloc` +- one in C: `memory_leak_malloc.c` Let's build and run the two executables: ```console -student@os:~/.../memory-leak/support$ make +student@os:~/.../memory-leak/support/src$ make g++ -c -o memory_leak.o memory_leak.cpp cc memory_leak.o -lstdc++ -o memory_leak cc -c -o memory_leak_malloc.o memory_leak_malloc.c @@ -23,25 +23,25 @@ cc memory_leak_malloc.o -lstdc++ -o memory_leak_malloc Running them yields similar output: ```console -student@os:~/.../memory-leak/support$ ./memory_leak -Andrei Popescu is 22 years old and likes Linux -Ioana David is 23 years old and likes macOS -student@os:~/.../lab/support/memory-leak$ ./memory_leak_malloc -Andrei Popescu is 22 years old and likes Linux -Ioana David is 23 years old and likes macOS +student@os:~/.../memory-leak/support/src$ ./memory_leak +Linus Torvalds is 22 years old and likes Linux +Steve Jobs is 23 years old and likes macOS +student@os:~/.../memory-leak/support/src$ ./memory_leak_malloc +Linus Torvalds is 22 years old and likes Linux +Steve Jobs is 23 years old and likes macOS ``` We investigate the memory leaks of the two programs by using [Valgrind](https://valgrind.org/): ```console -student@os:~/.../memory-leak/support$ valgrind ./memory_leak +student@os:~/.../memory-leak/support/src$ valgrind ./memory_leak ==22362== Memcheck, a memory error detector ==22362== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==22362== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info ==22362== Command: ./memory_leak ==22362== -Andrei Popescu is 22 years old and likes Linux -Ioana David is 23 years old and likes macOS +Linus Torvalds is 22 years old and likes Linux +Steve Jobs is 23 years old and likes macOS ==22362== ==22362== HEAP SUMMARY: ==22362== in use at exit: 72 bytes in 1 blocks @@ -58,14 +58,14 @@ Ioana David is 23 years old and likes macOS ==22362== For counts of detected and suppressed errors, rerun with: -v ==22362== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) -student@os:~/.../memory-leak/support$ valgrind ./memory_leak_malloc +student@os:~/.../memory-leak/support/src$ valgrind ./memory_leak_malloc ==22369== Memcheck, a memory error detector ==22369== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==22369== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info ==22369== Command: ./memory_leak_malloc ==22369== -Andrei Popescu is 22 years old and likes Linux -Ioana David is 23 years old and likes macOS +Linus Torvalds is 22 years old and likes Linux +Steve Jobs is 23 years old and likes macOS ==22369== ==22369== HEAP SUMMARY: ==22369== in use at exit: 148 bytes in 1 blocks @@ -93,15 +93,15 @@ We probably also require to preload the libc `malloc` debugging library, so we u Note that the file path used for `LD_PRELOAD` may need to be updated, depending on your distribution: ```console -student@os:~/.../memory-leak/support$ LD_PRELOAD=/lib/x86_64-linux-gnu/libc_malloc_debug.so.0 MALLOC_TRACE=mem.trace ./memory_leak_malloc -Andrei Popescu is 22 years old and likes Linux -Ioana David is 23 years old and likes macOS +student@os:~/.../memory-leak/support/src$ LD_PRELOAD=/lib/x86_64-linux-gnu/libc_malloc_debug.so.0 MALLOC_TRACE=mem.trace ./memory_leak_malloc +Linus Torvalds is 22 years old and likes Linux +Steve Jobs is 23 years old and likes macOS ``` Subsequently, we use the `mtrace` tool to show information about the leaked data: ```console -student@os:~/.../memory-leak/support$ mtrace ./memory_leak_malloc mem.trace +student@os:~/.../memory-leak/support/src$ mtrace ./memory_leak_malloc mem.trace Memory not freed: ----------------- @@ -119,4 +119,28 @@ Valgrind is however more powerful: it works on different types of memory (not on 1. Print the size of the `Student` class and the `struct student` structure to see if it equates to the leak shown by Valgrind. 1. Solve the memory leaks in both programs. - Validate with Valgrind. + Run the checker (`./checker.sh` in the `memory-leak/support/tests/` folder) to check your results. + + Sample run: + +```console +student@so:~/.../support/tests/$ ./checker.sh +make: Entering directory '/home/student/operating-systems/chapters/data/investigate-memory/guides/memory-leak/support/src' +g++ -c -o memory_leak.o memory_leak.cpp +cc memory_leak.o -lstdc++ -o memory_leak +cc -c -o memory_leak_malloc.o memory_leak_malloc.c +cc memory_leak_malloc.o -lstdc++ -o memory_leak_malloc +make: Leaving directory '/home/student/operating-systems/chapters/data/investigate-memory/guides/memory-leak/support/src' +------------------------------------------------- +Checking memory leaks for C executable: ../src/memory_leak +C executable is leak-free! + +Points for ../src/memory_leak: 50/50 +------------------------------------------------- +Checking memory leaks for C++ executable: ../src/memory_leak_malloc +C++ executable is leak-free! + +Points for ../src/memory_leak_malloc: 50/50 +------------------------------------------------- +Total Points: 100/100 +``` diff --git a/chapters/data/investigate-memory/guides/memory-leak/generate_skels.py b/chapters/data/investigate-memory/guides/memory-leak/generate_skels.py new file mode 100644 index 0000000000..697c9d5b61 --- /dev/null +++ b/chapters/data/investigate-memory/guides/memory-leak/generate_skels.py @@ -0,0 +1,151 @@ +#!/usr/bin/python3 -u +# SPDX-License-Identifier: BSD-3-Clause + +import sys +import argparse +import os.path +import re + + +def process_file(src, dst, pattern, replace, remove, replace_pairs, end_string=None): + if not pattern or not replace or not remove: + print( + f"ERROR: The script behaviour is not properly specified for {src}", + file=sys.stderr, + ) + sys.exit(1) + + fin = open(src, "r") + fout = open(dst, "w") + remove_lines = 0 + skip_lines = 0 + uncomment_lines = 0 + end_found = True + + for l in fin.readlines(): + # Skip generation of file. + if "SKIP_GENERATE" in l: + fout.close() + os.remove(dst) + return + + if end_string and end_found == False: + fout.write(l) + if end_string in l: + end_found = True + continue + + if remove_lines > 0: + remove_lines -= 1 + continue + + if skip_lines > 0: + skip_lines -= 1 + m = re.search(pattern, l) + if m: + l = "%s%s\n" % (m.group(1), m.group(3)) + fout.write(l) + continue + + if uncomment_lines > 0: + uncomment_lines -= 1 + for fro, to in replace_pairs: + l = re.sub(fro, to, l) + fout.write(l) + continue + + m = re.search(pattern, l) + if m: + if m.group(2): + skip_lines = int(m.group(2)) + else: + skip_lines = 1 + + if end_string and end_string not in l: + end_found = False + + l = "%s%s\n" % (m.group(1), m.group(3)) + + m = re.search(replace, l) + if m: + if m.group(2): + uncomment_lines = int(m.group(2)) + else: + uncomment_lines = 1 + continue + + m = re.search(remove, l) + if m: + if m.group(2): + remove_lines = int(m.group(2)) + else: + remove_lines = 1 + continue + + fout.write(l) + + fout.close() + + +def main(): + parser = argparse.ArgumentParser( + description="Generate skeletons sources from reference solution sources" + ) + parser.add_argument( + "--input", help="input directory to process files", required=True + ) + parser.add_argument( + "--output", help="output directory to copy processed files", required=True + ) + args = parser.parse_args() + + for root, dirs, files in os.walk(args.input): + new_root = os.path.join(args.output, os.path.relpath(root, args.input)) + for d in dirs: + os.makedirs(os.path.join(new_root, d), exist_ok=True) + + for src in files: + if ( + re.match("Makefile.*$", src) + or re.match(r".*\.sh$", src) + or re.match(r".*\.[sS]$", src) + or re.match(r".*\.py$", src) + ): + pattern = r"(^\s*#\s*TODO)( [0-9]*)(:.*)" + replace = r"(^\s*#\s*REPLACE)( [0-9]*)" + remove = r"(^\s*#\s*REMOVE)( [0-9]*)" + replace_pairs = [("# ", "")] + end_string = None + elif re.match(r".*\.asm$", src): + pattern = r"(^\s*;\s*TODO)( [0-9]*)(:.*)" + replace = r"(^\s*;\s*REPLACE)( [0-9]*)" + remove = r"(^\s*;\s*REMOVE)( [0-9]*)" + replace_pairs = [("; ", "")] + end_string = None + elif ( + re.match(r".*\.[ch]$", src) + or re.match(r".*\.cpp$", src) + or re.match(r".*\.hpp$", src) + ): + pattern = r"(.*/\*\s*TODO)([ 0-9]*)(:.*)" + replace = r"(.*/\*\s*REPLACE)( [0-9]*)" + remove = r"(.*/\*\s*REMOVE)( [0-9]*)" + replace_pairs = [(r"/\* ", ""), (r" \*/", "")] + end_string = "*/" + elif re.match(r".*\.d$", src): + pattern = r"(.*//\s*TODO)([ 0-9]*)(:.*)" + replace = r"(.*//\s*REPLACE)( [0-9]*)" + remove = r"(.*//\s*REMOVE)( [0-9]*)" + replace_pairs = [(r"// ", "")] + end_string = None + else: + continue + + dst = os.path.join(new_root, src) + src = os.path.join(root, src) + print(dst) + process_file(src, dst, pattern, replace, remove, replace_pairs, end_string) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/chapters/data/investigate-memory/guides/memory-leak/support/CPPLINT.cfg b/chapters/data/investigate-memory/guides/memory-leak/solution/src/CPPLINT.cfg similarity index 100% rename from chapters/data/investigate-memory/guides/memory-leak/support/CPPLINT.cfg rename to chapters/data/investigate-memory/guides/memory-leak/solution/src/CPPLINT.cfg diff --git a/chapters/data/investigate-memory/guides/memory-leak/support/Makefile b/chapters/data/investigate-memory/guides/memory-leak/solution/src/Makefile similarity index 100% rename from chapters/data/investigate-memory/guides/memory-leak/support/Makefile rename to chapters/data/investigate-memory/guides/memory-leak/solution/src/Makefile diff --git a/chapters/data/investigate-memory/guides/memory-leak/support/memory_leak.cpp b/chapters/data/investigate-memory/guides/memory-leak/solution/src/memory_leak.cpp similarity index 70% rename from chapters/data/investigate-memory/guides/memory-leak/support/memory_leak.cpp rename to chapters/data/investigate-memory/guides/memory-leak/solution/src/memory_leak.cpp index 4b1a6e29c3..b9ecaa862f 100644 --- a/chapters/data/investigate-memory/guides/memory-leak/support/memory_leak.cpp +++ b/chapters/data/investigate-memory/guides/memory-leak/solution/src/memory_leak.cpp @@ -28,13 +28,20 @@ int main(void) { Student *s; - s = new Student("Andrei Popescu", 22, "Linux"); + s = new Student("Linus Torvalds", 22, "Linux"); s->Print(); - s = new Student("Ioana David", 23, "macOS"); + /* TODO 1: Solve memory leaks. */ + delete s; + + s = new Student("Steve Jobs", 23, "macOS"); s->Print(); + /* REMOVE 2 */ delete s; + /* TODO 1: Print size of student. */ + std::cout << "Size of class Student: " << sizeof(Student) << "\n"; + return 0; } diff --git a/chapters/data/investigate-memory/guides/memory-leak/support/memory_leak_malloc.c b/chapters/data/investigate-memory/guides/memory-leak/solution/src/memory_leak_malloc.c similarity index 71% rename from chapters/data/investigate-memory/guides/memory-leak/support/memory_leak_malloc.c rename to chapters/data/investigate-memory/guides/memory-leak/solution/src/memory_leak_malloc.c index b8fe1c53b8..ed5b368047 100644 --- a/chapters/data/investigate-memory/guides/memory-leak/support/memory_leak_malloc.c +++ b/chapters/data/investigate-memory/guides/memory-leak/solution/src/memory_leak_malloc.c @@ -31,14 +31,21 @@ int main(void) mtrace(); s = malloc(sizeof(*s)); - init_student(s, "Andrei Popescu", 22, "Linux"); + init_student(s, "Linus Torvalds", 22, "Linux"); print_student(s); + /* TODO 1: Solve memory leaks. */ + free(s); + s = malloc(sizeof(*s)); - init_student(s, "Ioana David", 23, "macOS"); + init_student(s, "Steve Jobs", 23, "macOS"); print_student(s); + /* REMOVE 2 */ free(s); + /* TODO 1: Print size of student. */ + printf("Size of struct student: %ld\n", sizeof(struct student)); + return 0; } diff --git a/chapters/data/investigate-memory/guides/memory-leak/solution/tests/checker.sh b/chapters/data/investigate-memory/guides/memory-leak/solution/tests/checker.sh new file mode 100755 index 0000000000..399a7c0538 --- /dev/null +++ b/chapters/data/investigate-memory/guides/memory-leak/solution/tests/checker.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause */ + +# Initialize points +points=0 + +# Function to check memory leaks using valgrind +check_leaks() { + executable=$1 + language=$2 + local_points=0 + + if [ ! -f "$executable" ]; then + echo "$executable not found!" + exit 1 + fi + + echo "Checking memory leaks for $language executable: $executable" + + # Run valgrind to check for memory leaks + valgrind_output=$(valgrind --leak-check=full --error-exitcode=1 ./"$executable" 2>&1) + + # Check if valgrind found any memory leaks + + if echo "$valgrind_output" | grep -q "definitely lost:"; then + echo "Memory leaks detected in $language executable!" + echo "$valgrind_output" | grep "definitely lost:" + elif echo "$valgrind_output" | grep -q "All heap blocks were freed -- no leaks are possible"; then + echo "$language executable is leak-free!" + points=$((points + 50)) # Award 50 points for no leaks + local_points=$((local_points + 50)) + else + echo "No valid memory leak information found." + fi + + echo "" + echo "Points for $executable: $local_points/50" + echo "-------------------------------------------------" +} + +make -C ../src + +# Hardcoded paths for the executables +c_executable="../src/memory_leak" +cpp_executable="../src/memory_leak_malloc" + +echo "-------------------------------------------------" + +# Check memory leaks for C executable +check_leaks "$c_executable" "C" + +# Check memory leaks for C++ executable +check_leaks "$cpp_executable" "C++" + +# Display total points +echo "Total Points: $points/100"