CMSC216 Lab04: Valgrind and Makefiles
- Due: 11:59pm Sun 28-Sep-2025 on Gradescope
- Approximately 1.00% of total grade
CODE DISTRIBUTION: lab04-code.zip
CHANGELOG:
- Mon Sep 22 03:24:51 PM EDT 2025
- Associated Reading for the lab has been added. Particularly the section in Valgrind's manual on error messages may be useful to students.
- Mon Sep 22 11:44:21 AM EDT 2025
Post 133 reported a testing problem where the directory expected by tests is
makeproblem-create
but the intial directory name is actuallymakeproblem-create-makefile
. Students should rename their directory tomakeproblem-create
using this command.>> mv makeproblem-create-makefile makeproblem-create
The codepack has been updated to reflect this change which was intended to make the folder names shorter to type.
1 Rationale
Memory problems are inevitable in C programs and learning how to effectively identify and resolve them is an essential skill for low-level development. Valgrind is a valuable ally when diagnosing memory problems as it gives very good clues about the nature of the errors so long as one can interpret its error messages. Part of this Lab focuses on fixing existing code where error messages such as "Invalid Writes" and "120 bytes Definitely Lost" are abundant. Completing the lab will give solid practice on sleuthing out memory bugs using such error messages.
Build systems are essential to program development to automate
repetitive tasks such as compiling code, running tests, and tracking
changes which would necessitate running such tasks. The make
build
system is among the oldest build system and is ubiquitous across Unix
systems making it a useful to know a few basics about it. This lab
shows how building simple C projects can be automated with make
and
its Makefile
input format.
Associated Reading
Valgrind has been discussed in lecture but those looking for a quick reference might consult Valgrind's Explanation of error messages as to know how to read its tea leaves.
Students only need to master the basics of make
and its Makefile
format which are discussed in this document. Those wanting a more
complete account should consult the GNU Make Manual as it is the most
widely deployed implementation of make
and its manual is thorough.
Grading Policy
Credit for this exercise is earned by completing the code/asnwers here
and submitting a Zip of the work to Gradescope. Students are
responsible to check that the results produced locally via make test
are reflected on Gradescope after submitting their completed
Zip. Successful completion earns 1 Engagement Point.
Lab Exercises are open resource/open collaboration and students are encouraged to cooperate on labs. Students may submit work as groups of up to 5 to Gradescope: one person submits then adds the names of their group members to the submission.
See the full policies in the course syllabus.
2 Codepack
The codepack for this lab is linked at the top of this document. Always download it and unzip/unpack it. It should contain the following files which are briefly described.
File | Use | Description |
---|---|---|
QUESTIONS.txt |
EDIT | Instructions and Questions to answer: fill in the multiple choice selections in this file. |
mon_main.c |
EDIT | Problem 1 C file to edit and complete; contains bugs that must be fixed |
mon-top5.txt |
Data | Problem 1 Data to read in mon_main.c |
map-all.txt |
Data | Problem 1 Data to read in mon_main.c |
mon-*.txt |
Data | Problem 1 Additional data files |
test_mon_main.org |
Testing | Problem 1 CODE tests, used in make test-prob1 |
makeproblem-demo-little |
Study | Problem 2 Example Makefiles to study |
makeproblem-demo-big |
Study | Problem 2 Example Makefiles to study |
makeproblem-create/Makefile |
EDIT | Problem 2 Template Makefile to complete for problem |
test_makefile.org |
Testing | Problem 2 CODE tests, used in make test-prob2 |
Makefile |
Build | Enables make test and make zip |
QUESTIONS.txt.bk |
Backup | Backup copy of the original file to help revert if needed |
QUESTIONS.md5 |
Testing | Checksum for answers in questions file |
test_quiz_filter |
Testing | Filter to extract answers from Questions file, used in testing |
test_lab04.org |
Testing | Combined QUIZ/CODE tests for this lab |
testy |
Testing | Test running scripts |
3 Problem 1 Background
This problem will focus on debugging an existing C program which was written in a rather sloppy fashion leading to some serious flaws in it including memory problems. This will be a chance to practice reading Valgrind error messages and using them to locate problems in the code and resolve them.
3.1 Context
Your current supervisor has left the following note with the code and data files in the code pack.
TRAINER: We have most of the data we needed in the mon-*.txt files but we need to complete the analysis I promised Prof. Birch by the end of the week. We need the Median Base Points of the samples in the data files. I've written a program in mon_main.c to calculate this in a flexible way but it keeps crashing and I don't have time to debug it. I'm leaving that to you: Sorry! I've heard Valgrind may help to diagnose the memory problems but I never learned to use it myself. Please make corrections to the code to make it work so we can determine the median Base Points for the samples outlined in the tests. -Prof. Oak
3.2 Intended Behavior
The intended behavior of the program mon_main
is to read data files
formatted like mon-top5.txt
which have 3 columns. It will store all
the data in an array, sort it, and then print the median (middle)
element.
>> make gcc -Wall -Werror -g -c mon_main.c gcc -Wall -Werror -g -o mon_main mon_main.o >> ./mon_main usage: ./mon_main <input_file> [filter] >> ./mon_main mon-top5.txt Sorted array: 0: Charmander 309 Fire 1: Squirtle 314 Water 2: Bulbasaur 318 Grass 3: Pikachu 320 Electric 4: Psyduck 320 Water Median BASE_POINTS is 2: Bulbasaur 318 Grass >> ./mon_main mon-top5.txt Water Sorted array: 0: Squirtle 314 Water 1: Psyduck 320 Water Median BASE_POINTS is 1: Psyduck 320 Water >> ./mon_main mon-top5.txt Ground No elements left after filtering on 'Ground'
Unfortunately the behavior of the program at present is somewhat less helpful.
>> ./mon_main mon-top5.txt Segmentation fault (core dumped)
3.3 Instructions
As is indicated (and is often the case with other people's code) the
program has some bugs which will need to be corrected. The ultimate
goals is to correct the code so that it passes the test cases
provided. The QUIZ questions in QUESTIONS.txt
relate to some of
Error messages that will be produced in the tests and are a good way
to get started.
4 Problem 2 Background: Makefiles
Building programs often involves compiling separate source files and
eventually combining them through linking to create an executable.
There are often associated tasks such as running automated tests,
creating distribution files, and generating documentation that may be
supported by the build system as well. Among the oldest build systems
still in use is make
and its associated Makefiles
. This problem
covers the basics of how to create a Makefile
.
4.1 Structure of Makefiles
The basic structure of a Makefile
involves rules which relate
targets, dependencies, and commands. Simple Makefiles
comprise a
list of rules.
# comment above Rule 1 target1 : dependency1A depency1B command1A command1B command1C # comment above Rule 2 target2 : dependency2A command2A command2B # comment above Rule 3 target3 : dependency3A dependency3B dependency3B command3A command3B
When invoked, make target3
will load the contents of the Makefile
and examine the relationship between target3
and its
dependencies. If make
determines it is necessary, it may run
command3A, command3B
in sequence to "update" target3
. In other
cases, make
may determine that there is no need to run additional
commands as target3
is already up to date. Similarly, running make
target1
will possibly run command1A, command1B, command1C
.
NOTE: Commands associated with a rule MUST BE INDENTED WITH A TAB. This is a notoriously bad "feature" of make which can occasionally cause problems. Be aware: leading whitespace for commands should be TAB characters.
The most common kind of target is a file that should be created such
as a .o
file or an executable program. The directory
makeproblem-demo-little
has an example Makefile
showing this:
1: # Simle Makefile to build program main_prog 2: 3: # first rule in the file is the default: typing `make` is the same as 4: # typing `make main_prog` 5: 6: # rule 1 7: main_prog : main_func.o func_01.o 8: gcc -o main_prog main_func.o func_01.o 9: 10: # rule 2 11: main_func.o : main_func.c 12: gcc -c main_func.c 13: 14: # rule 3 15: func_01.o : func_01.c 16: gcc -c func_01.c 17: 18: # rule 4 19: clean : 20: rm -f *.o 21: rm -f main_prog
The rules that appear are
main_prog
depends on themain_func.o
andfunc_01.o
; the program is created by invoking GCC on these filesmain_func.o
depends onmain_func.c
which is created by running GCCfunc_01.o
depends onfunc_01.c
and is created by running GCCclean
is a "phony" target with no dependencies which will remove compile artifacts by using therm
command
When one types make
or make main_prog
in that directory, the
following will appear:
>> cd makeproblem-demo-little/ >> make gcc -c main_func.c gcc -c func_01.c gcc -o main_prog main_func.o func_01.o
Observe that make
detects that the two dependencies for Rule 1 are
incomplete: main_func.o
and func_01.o
are not present. To create
them, it will generate them as targets according to for the rules that
have them as targets, Rule 2 and Rule 3. After running the commands
for these two rules, the commands for Rule 1 are executed to generate
the program.
4.2 Variables and Shortcuts in Makefiles
Makefiles support a large number of features such as explicit
variables, automatic variables, pattern-based rules, and many
others. A few of these are described in the sample file
makeproblem-demo-little/Makefile-shortcuts
. It accomplishes the same
task as the Makefile
but utilizes some features which are useful to
know about. The comments in this file describe some of these features.
1: # Simle Makefile to build program main_prog 2: 3: # first rule in the file is the default: typing `make` is the same as 4: # typing `make main_prog` 5: 6: # rule 1 7: main_prog : main_func.o func_01.o 8: gcc -o main_prog main_func.o func_01.o 9: 10: # rule 2 11: main_func.o : main_func.c 12: gcc -c main_func.c 13: 14: # rule 3 15: func_01.o : func_01.c 16: gcc -c func_01.c 17: 18: # rule 4 19: clean : 20: rm -f *.o 21: rm -f main_prog
By default, typing make
will search for a file named Makefile
but
the following invocation allows using a specific file other than that:
>> make -f Makefile-shortcuts
4.3 Dependency Detection
Makefiles are useful for larger projects to automatically detect
dependencies which must be updated. The example in
makeproblem-demo-big/
shows a "larger" project with more files. Observe
the following:
>> make # COMPILE 1 gcc -c main_func.c gcc -c func_01.c gcc -c func_02.c gcc -c func_03.c gcc -c func_04.c gcc -c func_05.c gcc -c func_06.c gcc -c func_07.c gcc -c func_08.c gcc -c func_09.c gcc -c func_10.c gcc -c func_11.c gcc -c func_12.c gcc -c func_13.c gcc -c func_14.c gcc -c func_15.c gcc -c func_16.c gcc -c func_17.c gcc -c func_18.c gcc -c func_19.c gcc -c func_20.c gcc -o main_prog main_func.o func_01.o func_02.o func_03.o func_04.o func_05.o func_06.o func_07.o func_08.o func_09.o func_10.o func_11.o func_12.o func_13.o func_14.o func_15.o func_16.o func_17.o func_18.o func_19.o func_20.o >> make # COMPILE 2 make: Nothing to be done for 'all'. >> rm func_12.o # delete a .o >> touch func_05.c # make func_05.c look like it has been edited >> make # COMPILE 3 gcc -c func_05.c # only out of sync or gcc -c func_12.c # missing targets are regenerated gcc -o main_prog main_func.o func_01.o func_02.o func_03.o func_04.o func_05.o func_06.o func_07.o func_08.o func_09.o func_10.o func_11.o func_12.o func_13.o func_14.o func_15.o func_16.o func_17.o func_18.o func_19.o func_20.o
The lesson above is in COMPILE 3
: make detects that most of the .o
files are in sync with their source files. Only the missing / out of
sync targets have their rules applied to regenerated them and update
the exectuable program.
5 QUESTIONS.txt File Contents
Below are the contents of the QUESTIONS.txt
file for the lab.
Follow the instructions in it to complete the QUIZ and CODE questions
for the lab.
_________________ LAB04 QUESTIONS _________________ Exercise Instructions ===================== Follow the instructions below to experiment with topics related to this exercise. - For sections marked QUIZ, fill in an (X) for the appropriate response in this file. Use the command `make test-quiz' to see if all of your answers are correct. - For sections marked CODE, complete the code indicated. Use the command `make test-code' to check if your code is complete. - DO NOT CHANGE any parts of this file except the QUIZ sections as it may interfere with the tests otherwise. - If your `QUESTIONS.txt' file seems corrupted, restore it by copying over the `QUESTIONS.txt.bk' backup file. - When you complete the exercises, check your answers with `make test' and if all is well, create a zip file with `make zip' and upload it to Gradescope. Ensure that the Autograder there reflects your local results. - IF YOU WORK IN A GROUP only one member needs to submit and then add the names of their group. PROBLEM 1: A Bit of Debugging ============================= This problem will focus on debugging an existing C program which was written in a rather sloppy fashion leading to some serious flaws in it including memory problems. This will be a chance to practice reading Valgrind error messages and using them to locate problems in the code and resolve them. Context ~~~~~~~ Your current supervisor has left the following note with the code and data files in the code pack. ,---- | TRAINER: We have most of the data we needed in the mon-*.txt files but | we need to complete the analysis I promised Prof. Birch by the end of | the week. We need the Median Base Points of the samples in the data | files. I've written a program in mon_main.c to calculate this in a | flexible way but it keeps crashing and I don't have time to debug | it. I'm leaving that to you: Sorry! I've heard Valgrind may help to | diagnose the memory problems but I never learned to use it | myself. Please make corrections to the code to make it work so we can | determine the median Base Points for the samples outlined in the | tests. | | -Prof. Oak `---- Intended Behavior ~~~~~~~~~~~~~~~~~ The intended behavior of the program `mon_main' is to read data files formatted like `mon-top5.txt' which have 3 columns. It will store all the data in an array, sort it, and then print the median (middle) element. ,---- | >> make | gcc -Wall -Werror -g -c mon_main.c | gcc -Wall -Werror -g -o mon_main mon_main.o | | >> ./mon_main | usage: ./mon_main <input_file> [filter] | | >> ./mon_main mon-top5.txt | Sorted array: | 0: Charmander 309 Fire | 1: Squirtle 314 Water | 2: Bulbasaur 318 Grass | 3: Pikachu 320 Electric | 4: Psyduck 320 Water | Median BASE_POINTS is | 2: Bulbasaur 318 Grass | | >> ./mon_main mon-top5.txt Water | Sorted array: | 0: Squirtle 314 Water | 1: Psyduck 320 Water | Median BASE_POINTS is | 1: Psyduck 320 Water | | >> ./mon_main mon-top5.txt Ground | No elements left after filtering on 'Ground' `---- Unfortunately the behavior of the program at present is somewhat less helpful. ,---- | >> ./mon_main mon-top5.txt | Segmentation fault (core dumped) `---- Instructions ~~~~~~~~~~~~ As is indicated (and is often the case with other people's code) the program has some bugs which will need to be corrected. The ultimate goals is to correct the code so that it passes the test cases provided. The QUIZ questions in `QUESTIONS.txt' relate to some of Error messages that will be produced in the tests and are a good way to get started. PROBLEM 1 QUIZ: Errors in mon_main.c ==================================== Study the code and error messages produced by test cases or command line runs of the program to start determining where memory errors occur and how they may be corrected. Answer the following QUIZ questions. First Test ~~~~~~~~~~ Run the first test via `make test-prob1' or `make test-prob1 testnum=1'. It will fail. Analyze its output, both the Side-by-Side diff output and the Valgrind Report which is lower in the results. Check all of the following that are relevant to the bugs reported. - ( ) Valgrind messages indicate that this is likely a bug in Valgrind itself which should be reported to the tool developers ASAP. - ( ) The Valgrind messages indicate that there is likely a memory problem with writing too far into a block of memory. - ( ) The Valgrind Report indicates that a memory Write is being done some distance BEFORE a malloc()'d block - ( ) The Valgrind Report indicates that a memory Write is being done some distance AFTER a malloc()'d block - ( ) The memory Write is problematic because the wrong C syntax is used in the program: no * or -> is used to dereference a pointer. - ( ) The memory Write is problematic because it is assuming a pointer to an array but not enough space has been allocated The following Valgrind Error message often appears in the VALGRIND REPORT towards the bottom of test results. ,---- | ==85164== Invalid write of size 8 | ==85164== at 0x109762: main (mon_main.c:99) | ==85164== Address 0x4a9e374 is 0 bytes after a block of size 132 alloc'd | ==85164== at 0x48447A8: malloc (vg_replace_malloc.c:446) | ==85164== by 0x109661: main (mon_main.c:83) `---- Check ALL of the following that correspond to information in this message. - ( ) It reports that some variable / array element is being assigned (write-en to) but the pointer to the variable is out of bounds - ( ) It gives a source code line where the out of bounds access is occurring. - ( ) It shows the exact memory address that is being accessed that is out of bounds (in hexadecimal format) - ( ) It gives the relative position of the memory access as just after a block that was allocated using malloc() and the location in source code where that allocation occurred Make some code changes to correct this bug and check that any error messages change. First Test Again ~~~~~~~~~~~~~~~~ Run the first test again and check new error messages. Which part of the code seems to be problematic now? - ( ) The loop that reads data from the file into an array - ( ) The loop that prints out the contents of the sorted array - ( ) The insertion sort routine meant to sort the data - ( ) The indexing to determine the median (it accesses an out-of-bounds element) Use any nearby hints to fix the code and reduce the errors / generate new errors. First Test AGAIN ~~~~~~~~~~~~~~~~ If all has gone well, the output for the first test will now appear correct BUT there are additional problems reported by Valgrind. Check ALL of the following problems appear in the Valgrind report. - ( ) There are Invalid reads AFTER a heap-allocated block - ( ) There are Invalid writes BEFORE a heap-allocated block - ( ) Uninitialized memory used to determine a conditional jump/move - ( ) Memory is lost / leaked as it is not de-allocated before the end of the program One of the problems identified in the Valgrind output is associated with the following lines which illustrate a common beginner mistake (something one wouldn't expect the seasoned Prof. Oak to do but perhaps C isn't his strong suit...) ,---- | char *filter = malloc(sizeof(char)); | if(argc > 2){ // filter present | filter = argv[2]; | } | else{ // default to no filtering | filter = NULL; | } `---- Why is the above code a memory error? - ( ) malloc()'ing memory for filter then re-assigning it to point elsewhere creates a memory leak: the malloc()'d block can never be free()'d - ( ) character pointers like filter should not be pointed to heap-allocated space with malloc() - ( ) When pointers should not be set to NULL as this will cause a segmentation fault - ( ) pointing filter at a string like argv[2] leads to problems later as argv[2] is not heap-allocated so the code that free()'s it will causes an error Use the error messages provided in the Valgrind Report to fix the remaining error messages associated with this test. Hint: there are 3 changes that need to be made. FIRST TEST! ~~~~~~~~~~~ The First test should now pass. In fact, a few more tests may pass. Carry on with this pattern of inspecting test results, especially the VALGRIND REPORT to continue diagnosing errors to correct them. A few hints: - Any attempt to access a NULL pointer will trigger a segmentation fault; this is reported as an "Invalid Read" or "Invalid Write" of the address 0x0. When working with pointers you know may be NULL, check whether they are NULL before attempting to dereference them. - All code paths of exiting must de-allocate all memory. If you are exiting the program early, make sure to free() an malloc()'d blocks and close any files. - Opening files can fail, often due to the wrong filename being typed in. Make sure to check for this condition and report appropriate errors. Attempting to work with unopened file is likely to end in crashes. PROBLEM 1 CODE: Repair mon_main.c ================================= As indicated above, continue debugging mon_main.c until all tests pass. A completely corrected program should give the following results on running tests: ,---- | >> make test-prob1 | gcc -Wall -Werror -g -fstack-protector-all -c mon_main.c | gcc -Wall -Werror -g -fstack-protector-all -o mon_main mon_main.o | ./testy -o md test_mon_main.org | ================================================= | == test_mon_main.org : mon_main application tests | == Running 8 / 8 tests | 1) ./mon_main mon-lvl1.txt Fire : ok | 2) ./mon_main mon-all.txt Grass : ok | 3) ./mon_main mon-top5.txt : ok | 4) ./mon_main mon-lvl1.txt : ok | 5) ./mon_main mon-all.txt : ok | 6) ./mon_main mon-all.txt Ground : ok | 7) ./mon_main no-file.txt : ok | 8) ./mon_main mon-lvl2.txt BadFilter : ok | ================================================= | RESULTS: 8 / 8 tests passed `---- PROBLEM 2 QUIZ: Questions on Makefiles ====================================== Review the presentation of `Makefile' basics in the lab description and analyze the examples provided in the demo directories `makeproblem-demo-little' and `makeproblem-demo-big'. Then answer the following questions. Command Execution ~~~~~~~~~~~~~~~~~ Which of the following best describes when the commands for a rule are run? - ( ) The commands for all targets in the Makefile are all run every time make is used - ( ) Only the commands associated with the target named on the command line are run; e.g. `make targ5` will run commands associated with targ5 - ( ) Commands for a given rule execute only if make detects a its target is needed and then only if the target is missing or older than its dependencies Rule Syntax ~~~~~~~~~~~ Which of the following Rules correctly lays out the syntax for target, dependencies, and commands in a Makefile. ,---- | # RULE A | target : dependency1 dependency2 | command1 | command2 | command3 | | # RULE B | target : dependency1 dependency2 | command1 | command2 | command3 | | # RULE C | dependency1 dependency2 : target | command1 | command2 | command3 | | # RULE D | target : | command1 | command2 | command3 | : dependency1 dependency2 `---- - ( ) RULE A - ( ) RULE B - ( ) RULE C - ( ) RULE D Variance in Makefiles ~~~~~~~~~~~~~~~~~~~~~ Check ALL that are true about Makefile features - ( ) Rules may have 0 dependencies - ( ) Rules may have 0 commands - ( ) Rules have no more than 1 command - ( ) Rules have no more than 1 dependency - ( ) Makefiles can set up explicit variables for use in rules - ( ) Makefiles provide a variety of automatic variables for use in rules - ( ) A Makefile can have deeply nested decencies: targA depends on targB depends on targC depends on targD etc. Problem 2 CODE: makeproblem-create ================================== Complete the template in the subdirectory `makeproblem-create/Makefile' according to the comments there. The essential idea is to build the linked list program from an earlier lab and add a few other targets. Once you add rules for certain targets, you can test them interactively via commands like `make' and `make clean' in that directory. You can also run the automated tests of this via a problem test: ,---- | >> cd lab04-code | >> make test-prob2 # run tests for Makefile under makeproblem-create | ... `----
6 Submission
Follow the instructions at the end of Lab01 if you need a refresher on how to upload your completed lab zip to Gradescope.