C:Workshop:2016:D0
Introduction
This session is dedicated to debugging. Your goal is to explore gdb, valgrind and similar tools as much as you can.
Since, debugging is an unbound process the subject is open.
First Step: find the bug !
Here are some code samples with bugs, the idea is to use debugging tools to track the bug.
First a simple Makefile:
# Makefile # CC=clang -fsanitize=address # CC=gcc -fsanitize=address CC=gcc CPPFLAGS= CFLAGS= -Wall -Wextra -std=c99 -O0 -g3 LDFLAGS= LDLIBS= all: clean: rm -f *.o # END
Next: the most basic segfault, basic.c:
# include <stdio.h> # include <stdlib.h> int main() { int *p = NULL; *p = 42; printf("*p = %d\n", *p); return 0; }
Then, more hideous bug, overflow.c:
# include <stdio.h> # include <stdlib.h> # include <string.h> void foo(char *str) { char buf[8]; strcpy(buf, str); printf("buf: %s\n", buf); } int main() { char str[32]; for (size_t i = 0; i < 31; i++) str[i] = 'a'; str[31] = 0; foo(str); return 0; }
For these files, you job is too:
- Compile, run and observe
- Use gdb to find the bug(s)
- Use valgrind to find the bug(s)
- Use the address sanitizer of the compiler to find the bug(s)
Memory Leaks
Similar to the previous section, we now want to find memory leaks, and of course correct them in the following files.
First, leak01.c:
# include <stdio.h> # include <stdlib.h> # include <string.h> void foo(char *str) { char *buf = NULL; size_t len = 1 + strlen(str); buf = calloc(len, 1); strncpy(buf, str, len); printf("buf = %s\n", buf); } int main() { char *str = "A simple string ..."; foo(str); return 0; }
Then, leak02.c:
# include <stdlib.h> # include <stdio.h> # include <string.h> struct list { int data; struct list *next; }; static struct list* add_front(struct list *list, int val) { struct list *tmp = malloc(sizeof (struct list)); tmp->data = val; tmp->next = list; return tmp; } static void print_list(struct list *list) { int line = 2; printf("list = [\n "); for (; list; list = list->next) { if (line > 72) { printf("\n "); line = 2; } line += printf("%4d;", list->data); } printf("\n]\n"); } static struct list* fill_list(size_t len) { struct list *list = NULL; for (size_t i = 0; i < len; i++) list = add_front(list, i); return list; } static void delete_list(struct list *list) { struct list *tmp = list; while (tmp) { memset(tmp, 0, sizeof (struct list)); list = tmp->next; free(tmp); tmp = list; } } int main(int argc, char *argv[]) { size_t len = 8; if (argc > 1) len = strtoul(argv[1], NULL, 10); struct list *list = fill_list(len); print_list(list); delete_list(list); return 0; }
Finally, leak03.c:
# define _XOPEN_SOURCE 700 # include <err.h> # include <stdio.h> # include <stdlib.h> void mycat(FILE *in, FILE *out) { for (;;) { char *line = NULL; size_t n = 0; if (getline(&line, &n, in) == -1) break; fprintf(out, "%s", line); } } int main(int argc, char *argv[]) { FILE *in = stdin; if (argc > 1) { in = fopen(argv[1], "r"); if (!in) err(1, "can't open %s", argv[1]); } mycat(in, stdout); if (argc > 1) fclose(in); return 0; }
For each files, you must:
- Run, compile and observe
- Find memory leaks using valgrind
- Find memory leaks using the address sanitizer of your compiler
- Correct the code
Code optimization
Now, our purpose is to optimize a piece of code. The provided code is a small program that reads lines from its standard input, stores them uniquely and finally prints them in order (with respect to the order defined by strcmp(3).)
Your goal is to:
- Check for bugs and memory leaks
- Use gprof to get some profiling information on the program
- Try to optimize the code as much as you can …
Here is the file lines.c:
# define _XOPEN_SOURCE 700 # include <stdio.h> # include <stdlib.h> # include <string.h> # define LINE_LENGTH 256 static inline size_t max_len(size_t a, size_t b) { return a > b ? a : b; } static void str_swap(char *str1, char *str2) { size_t len = max_len(strlen(str1), strlen(str2)); char *tmp = calloc(len, 1); strncpy(tmp, str1, len); strncpy(str1, str2, len); strncpy(str2, tmp, len); free(tmp); } static int member(char *lines[], size_t len, char *l) { char **cur = lines; for (; cur < lines + len && strcmp(*cur, l); cur++) {} return cur < lines + len; } static void sort_lines(char *lines[], size_t len) { for (size_t i = 0; i < len - 1; i++) { size_t min = i; for (size_t j = i + 1; j < len; j++) { if (strcmp(lines[j], lines[min]) < 0) min = j; } if (min != i) str_swap(lines[i], lines[min]); } } static size_t read_lines(char ***plines) { size_t capa = 1, len = 0; char **lines = malloc(capa * sizeof (char*)); char *tmp= NULL; size_t n = 0; while (getline(&tmp, &n, stdin) != -1) { if (!member(lines, len, tmp)) { len += 1; if (capa <= len) { capa *= 2; lines = realloc(lines, capa * sizeof (char*)); } lines[len - 1] = calloc(n + 1, 1); strncpy(lines[len - 1], tmp, n); sort_lines(lines, len); } free(tmp); tmp = NULL; n = 0; } lines = realloc(lines, len * sizeof (char*)); *plines = lines; return capa; } int main() { size_t len; char **lines; len = read_lines(&lines); for (size_t i = 0; i < len; i++) { printf("lines[%zu]: %s", i, lines[i]); } free(lines); return 0; }