Learning_C_Tools

Accessing invalid memory

Running the code below results in exit code 139, a SIGSEGV, a segmentation fault:

int main(int argc, char *argv[]) {
  int *x;
  x = NULL;
  printf("*x: %d", *x);
}

We can investigate this with gcc -g -o null null.c && gdb null:

(gdb) run
Starting program: /home/sam/code/ostep-homework/memory-api/null 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x0000555555555154 in main (argc=1, argv=0x7fffffffdc58) at null.c:8
8	 printf("*x: %d", *x);

How handy! gdb shows the exact line and snippet where the memory violation occurs.

Similarly we can use valgrind:

==78470== Memcheck, a memory error detector
==78470== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==78470== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==78470== Command: ./null
==78470== 

==78470== Invalid read of size 4
==78470==    at 0x109154: main (null.c:8)
==78470==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==78470== 
==78470== 
==78470== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==78470==  Access not within mapped region at address 0x0
==78470==    at 0x109154: main (null.c:8)
==78470==  If you believe this happened as a result of a stack
==78470==  overflow in your program's main thread (unlikely but
==78470==  possible), you can try to increase the size of the
==78470==  main thread stack using the --main-stacksize= flag.
==78470==  The main thread stack size used in this run was 8388608.
==78470== 
==78470== HEAP SUMMARY:
==78470==     in use at exit: 0 bytes in 0 blocks
==78470==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==78470== 
==78470== All heap blocks were freed -- no leaks are possible
==78470== 
==78470== For lists of detected and suppressed errors, rerun with: -s
==78470== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Leaking memory

If instead we leak:

int main(int argc, char *argv[]) {
  int *x = (int*) malloc(sizeof(int));
  *x = 8;
  printf("*x: %d and i'm not freeing it!", *x);
  exit(0);
}

gdb doesn't do anything to help:

(gdb) run
Starting program: /home/sam/code/ostep-homework/memory-api/unfreed 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
*x: 8 and i'm not freeing it![Inferior 1 (process 79352) exited normally]

but valgrind shows us what's still in use at exit:

❯ valgrind --leak-check=full --show-leak-kinds=all ./unfreed
==79571== Memcheck, a memory error detector
==79571== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==79571== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==79571== Command: ./unfreed
==79571== 
*x: 8 and i'm not freeing it!==79571== 
==79571== HEAP SUMMARY:
==79571==     in use at exit: 4 bytes in 1 blocks
==79571==   total heap usage: 2 allocs, 1 frees, 1,028 bytes allocated
==79571== 
==79571== 4 bytes in 1 blocks are still reachable in loss record 1 of 1
==79571==    at 0x4841888: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==79571==    by 0x109171: main (unfreed.c:6)
==79571== 
==79571== LEAK SUMMARY:
==79571==    definitely lost: 0 bytes in 0 blocks
==79571==    indirectly lost: 0 bytes in 0 blocks
==79571==      possibly lost: 0 bytes in 0 blocks
==79571==    still reachable: 4 bytes in 1 blocks
==79571==         suppressed: 0 bytes in 0 blocks
==79571== 
==79571== For lists of detected and suppressed errors, rerun with: -s
==79571== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Not allocating enough memory

For this code

int main(int argc, char *argv[]) {
  int *data = (int *) malloc(100); // should be 100 * sizeof(int)
  data[100] = 0;
  exit(0);
}

Again gdb doesn't help us but valgrind does:

==80746== Invalid write of size 4
==80746==    at 0x109170: main (in /home/sam/code/ostep-homework/memory-api/badarr)
==80746==  Address 0x4a561d0 is 224 bytes inside an unallocated block of size 4,194,032 in arena "client"

Accessing freed memory

When we access memory that we've previously freed:

int main(int argc, char *argv[]) {
  int *data = (int *) malloc(100 * sizeof(int));
  free(data);
  printf("data[64]: %d\n", data[64]);
  exit(0);
}

Surprisingly the program runs okay and just prints 0, but it is not correct, and valgrind again is the hero:

==81308== Command: ./badaccess
==81308== 
==81308== Invalid read of size 4
==81308==    at 0x10919C: main (in /home/sam/code/ostep-homework/memory-api/badaccess)
==81308==  Address 0x4a56140 is 256 bytes inside a block of size 400 free'd
==81308==    at 0x484426F: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==81308==    by 0x109191: main (in /home/sam/code/ostep-homework/memory-api/badaccess)
==81308==  Block was alloc'd at
==81308==    at 0x4841888: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==81308==    by 0x109181: main (in /home/sam/code/ostep-homework/memory-api/badaccess)

Invalid free

Luckily the compiler can warn us of invalid frees:

int main(int argc, char *argv[]) {
  int *data = (int *) malloc(100 * sizeof(int));
  free(&data[64]);
  exit(0);
}

results in

/home/sam/code/ostep-homework/memory-api/badfree.c:7:3: warning: ‘free’ called on pointer ‘data’ with nonzero offset 256 [-Wfree-nonheap-object]
    7 |   free(&data[64]);
      |   ^~~~~~~~~~~~~~~
/home/sam/code/ostep-homework/memory-api/badfree.c:6:23: note: returned from ‘malloc’
    6 |   int *data = (int *) malloc(100 * sizeof(int));
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~
free(): invalid pointer

Because, as mentioned above, the free function takes the exact pointer returned from malloc.