Processes and memory management
You don't need to submit source files or reports for this session.You will need to log into a PC running Linux. If you are at Eurecom, simply log on a PC of rooms 52 or 53. If you were to use another PC running GNU/Linux, I cannot guarantee that the lab works in the same way (outputs could be different, manual pages could be different, etc.).
The session is dedicated to the study of processes and their memory in Operating Systems. It uses C pointers so, if you don't feel at ease with them, you may first read this page on C pointers, until section 4 included. You may also only read the two first sections, and come back to this page whenever necessary.
I. Basics
- Open the slides on Processes
- Without reading the slides, are you able to list the three default streams of a Linux process?
-
Reproduce all commands given in the lecture.
When a
cmd
is given, imagine a concrete command that could be used instead ofcmd
. For eachcmd
of the slides find at least one concrete command that works and one that fails. -
Write a short C program that causes an error, and thats prints to
stderr
a text corresponding to this error. Compile and run your program with a redirection of the error stream to a file. Check that the file contains the expected error message.
II. Memory of processes
This exercise studies the relation between the memory area for global variables, the heap, and the stack.-
Create a file named
proc1.c
with the following code:int * pointerToRandomValue; int computeRandom( int maxValue ) { int myRand = rand() % maxValue; pointerToRandomValue = &myRand; return myRand; } int main( int argc, char *argv[] ) { srand(time(NULL)); /* Seed initialization */ int returned = computeRandom(20); //printf("Hello world!\n"); printf("The random value via the pointer is: %d\n", *pointerToRandomValue); printf("The returned random value is: %d\n", returned) return 0; }
-
Compile:
$ gcc -Wall -o proc1 proc1.c
The compiler should issue warnings and errors. Fix them and execute the program. Note the result you get.
(Help me!) There is a semicolon missing, as well as header files to include. You can find out which header files must be included by usingman
on the external function calls. -
Copy
proc1.c
toproc2.c
. Inproc2.c
uncomment the line with the call toprintf
. Compile and execute. What happens? Try to explain.(Help me!) The pointer points somewhere in the memory region that the call tocomputeRandom
allocated for its stack frame. Immediately after returning fromcomputeRandom
this memory region still contains the random value. Yet, if another function (printf
) is called before the random value is read, the same memory region is used by the new function call and the random value is overwritten. -
To investigate this situation we will use
gdb
, the command-line debugger, that allows examining the content of the memory of a program during execution, and even modifying it. Note: depending on thegdb
version the output can differ from the provided one.-
Recompile
proc2.c
with the-g
option to generate the debugging symbols:$ gcc -Wall -g -o proc2 proc2.c
-
Execute with
gdb
(to quit typequit
, to restart from the beginning typejump _start
):$ gdb proc2
-
Display the source code and find the line number of the call to
computeRandom
:(gdb) list 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 5 int * pointerToRandomValue; 6 7 int computeRandom( int maxValue ) { 8 int myRand = rand() % maxValue; 9 pointerToRandomValue = &myRand; 10 return myRand; (gdb) list 11 } 12 13 int main( int argc, char*argv[] ) { 14 srand(time(NULL)); /* Seed initialization */ 15 16 int returned = computeRandom(20); 17 18 printf("Hello world!\n"); 19 20 printf("The random value via the pointer is: %d\n", *pointerToRandomValue);
Note: you can also use thelist
command with a line number to display the source code around a given line, e.g. line 9:(gdb) list 9 4 5 int * pointerToRandomValue; 6 7 int computeRandom( int maxValue ) { 8 int myRand = rand() % maxValue; 9 pointerToRandomValue = &myRand; 10 return myRand; 11 } 12 13 int main( int argc, char*argv[] ) {
Note: to know more about thegdb
commandCMD
simply typehelp CMD
. Note: commands can be abbreviated if unambiguous (e.g.,l
forlist
,b
forbreak
...) In our example the call is at line 16 (but it could be different in your case, check carefully). Put a breakpoint on the line:(gdb) break 16 Breakpoint 1 at 0x125d: file proc2.c, line 16.
- Run the program from start (if offered to Enable debuginfod for this session answer yes):
(gdb) run Starting program: /homes/pacalet/tmp/proc2 [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, main (argc=1, argv=0x7fffffffe3c8) at proc2.c:16 16 int returned = computeRandom(20);
-
The program is stopped on the breakpoint.
Examine variable
pointerToRandomValue
:(gdb) print pointerToRandomValue $1 = (int *) 0x0
Try to read the memory location it points to:(gdb) print *pointerToRandomValue Cannot access memory at address 0x0
Address 0x0 is not accessible ("null" address). -
Continue the program step by step:
(gdb) step computeRandom (maxValue=32767) at proc2.c:7 7 int computeRandom( int maxValue ) {
After this first step the execution entered thecomputeRandom
function. Display the list of called functions, that is, the backtrace of all stack frames:(gdb) bt #0 computeRandom (maxValue=20) at proc2.c:7 #1 0x0000555555555267 in main (argc=1, argv=0x7fffffffe3c8) at proc2.c:16
As expected functionmain
was first called and then functioncomputeRandom
. -
Our goal is to debug what happens around the call to the
rand
function. Display the source code (list
), find the call torand
and add a breakpoint after the call (check your own line numbers):(gdb) list 2 #include <stdlib.h> 3 #include <time.h> 4 5 int * pointerToRandomValue; 6 7 int computeRandom( int maxValue ) { 8 int myRand = rand() % maxValue; 9 pointerToRandomValue = &myRand; 10 return myRand; 11 } (gdb) break 9 Breakpoint 2 at 0x555555555215: file proc2.c, line 9.
Continue the execution until the breakpoint:(gdb) continue Continuing. Breakpoint 6, computeRandom (maxValue=20) at proc2.c:9 9 pointerToRandomValue = &myRand;
VariablepointerToRandomValue
should still contain address 0 andmyRand
should contain a random value between 0 and 20; check that:(gdb) print pointerToRandomValue $2 = (int *) 0x0 (gdb) print myRand $3 = 11
Go one step ahead and check again the value of the 2 variables:(gdb) step ... (gdb) print pointerToRandomValue $4 = (int *) 0x7fffffffe274 (gdb) print *pointerToRandomValue $5 = 11 (gdb) print &myRand $6 = (int *) 0x7fffffffe274 (gdb) print &maxValue $7 = (int *) 0x7fffffffe26c
We can now print a partial view of the stack. Note:x/8dw
is thegdb
command that examines the memory (x
), and print 8 words (8w
), in decimal (d
) format. Adapt the address, use that ofmaxValue
(0x7fffffffe26c
in our example):(gdb) x/8dw 0x7fffffffe26c 0x7fffffffe26c: 20 0 11 926993152 0x7fffffffe27c: -49932742 -7504 32767 1431655015
-
In function
main
add a breakpoint on theprintf("Hello world\n")
and another on the following call toprintf
:(gdb) break 18 Breakpoint 3 at 0x55555555526a: file proc2.c, line 18. (gdb) break 20 Breakpoint 4 at 0x555555555279: file proc2.c, line 20.
-
We can now continue the execution, exit the
computeRandom
function, and stop just before the firstprintf
:(gdb) continue Continuing. Breakpoint 3, main (argc=1, argv=0x7fffffffe3c8) at proc2.c:18 18 printf("Hello world!\n");
-
Check if the content of the stack frame of the
computeRandom
call is still in memory:(gdb) x/8dw 0x7fffffffe26c 0x7fffffffe26c: 20 -5994 11 474799616 0x7fffffffe27c: -473136351 -5968 32767 1431655015
Even after the program has left thecomputeRandom
function, its stack frame is still available. Continue to call the first following function, that is,printf("Hello world\n")
, and stop before the secondprintf
:(gdb) continue Continuing. Hello world! Breakpoint 4, main (argc=1, argv=0x7fffffffe3c8) at proc2.c:20 20 printf("The random value via the pointer is: %d\n", *pointerToRandomValue);
-
Print again the content of the stack frame of the
computeRandom
call:(gdb) x/8dw 0x7fffffffe26c 0x7fffffffe26c: 32767 0 0 -7208 0x7fffffffe27c: 32767 1431666072 21845 1431655033
As we can see, the content has been overwritten by theprintf("Hello world\n")
call.
-
Recompile
-
As we cannot reliably pass the generated random value through the stack frame from the
computeRandom
call to themain
, we must find another way. Copyproc2.c
toproc3.c
. Editproc3.c
and, without modifying the definition ofpointerToRandomValue
nor the code ofmain
, try to achieve the expected result. You will probably need to allocate some memory on theheap
withmalloc
orcalloc
. Implement and test withgdb
. Do not forget to test the return value ofmalloc
. -
BONUS work #1.
Code two new programs,
procSender.c
andprocReceiver.c
, with just onemain
function in each. InprocSender.c
themain
function shall generate a printable random string of characters, and print it to its standard output stream and to its standard error stream. Do not use static arrays, use dynamic allocations. Do not forget to test the return value ofmalloc
and tofree
the allocated memory after use. InprocReceiver.c
themain
function shall read all strings from its standard input stream, and print them to its standard output stream. Compile the two programs and execute them in a pipe:$ procSender | procReceiver
-
BONUS work #2.
Modify
procReceiver.c
such that it automatically indents a C source code. Assume:-
The source code contains no curly braces in comments or literal text strings (e.g.,
char *cb = "}{";
). - All curly braces are alone in a separate line.
-
Each opening curly brace (
{
) increases the indentation level by 4 spaces for the lines that follow. -
Each closing curly brace (
}
) decreases it by 4 spaces for its own line and the lines that follow. - Finally, all curly braces are properly balanced and nested.
procReceiver
on its own source code:$ cat procReceiver.c | procReceiver
-
The source code contains no curly braces in comments or literal text strings (e.g.,