NANDHOO.

Graphics, Compilers, Debugging, and Security

Chapter 19: Graphics, Compilers, Debugging, and Security


Introduction


This final chapter explores advanced topics that round out your system programming knowledge: graphics programming (framebuffers, GPU basics), compiler internals (how C becomes machine code), advanced debugging techniques, and security considerations. These topics are essential for professional system programmers working on real-world projects.


Why This Matters


Modern systems require understanding graphics (even servers need console output), compiler behavior (for optimization and debugging), debugging skills (for finding complex bugs), and security (to protect systems from vulnerabilities). These topics bridge system programming and practical software engineering.


How to Study This Chapter


  1. Experiment widely - Try different tools and techniques
  2. Read documentation - Each topic has extensive resources
  3. Practice debugging - Debug real programs, not just examples
  4. Think security first - Consider vulnerabilities in all code
  5. Stay curious - These are deep topics worthy of continued study

Graphics Programming


Framebuffer Basics


A framebuffer is a region of memory representing pixel data. Writing to it changes what's displayed on screen.


Simple framebuffer (VGA text mode):

#include <stdint.h>

#define VGA_MEMORY 0xB8000 #define VGA_WIDTH 80 #define VGA_HEIGHT 25


void put_pixel(int x, int y, char c, uint8_t color) { uint16_t *vga = (uint16_t *)VGA_MEMORY; vga[y * VGA_WIDTH + x] = (color << 8) | c; }


void clear_screen(void) { for (int y = 0; y < VGA_HEIGHT; y++) { for (int x = 0; x < VGA_WIDTH; x++) { put_pixel(x, y, ' ', 0x0F); } } }


void draw_box(int x, int y, int w, int h) { for (int i = 0; i < w; i++) { put_pixel(x + i, y, '-', 0x0F); put_pixel(x + i, y + h - 1, '-', 0x0F); } for (int i = 0; i < h; i++) { put_pixel(x, y + i, '|', 0x0F); put_pixel(x + w - 1, y + i, '|', 0x0F); } }


Linear Framebuffer (Graphics Mode)


For pixel-based graphics:


struct framebuffer {
    uint32_t *buffer;
    int width;
    int height;
    int pitch;  // Bytes per scanline
};

void put_pixel_rgb(struct framebuffer *fb, int x, int y, uint32_t color) { if (x < 0 || x >= fb->width || y < 0 || y >= fb->height) return;


fb->buffer[y * fb->width + x] = color;

}


void draw_rectangle(struct framebuffer *fb, int x, int y, int w, int h, uint32_t color) { for (int dy = 0; dy < h; dy++) { for (int dx = 0; dx < w; dx++) { put_pixel_rgb(fb, x + dx, y + dy, color); } } }


void draw_line(struct framebuffer *fb, int x0, int y0, int x1, int y1, uint32_t color) { // Bresenham's line algorithm int dx = abs(x1 - x0); int dy = abs(y1 - y0); int sx = x0 < x1 ? 1 : -1; int sy = y0 < y1 ? 1 : -1; int err = dx - dy;


while (1) {
    put_pixel_rgb(fb, x0, y0, color);

    if (x0 == x1 && y0 == y1)
        break;

    int e2 = 2 * err;
    if (e2 > -dy) {
        err -= dy;
        x0 += sx;
    }
    if (e2 < dx) {
        err += dx;
        y0 += sy;
    }
}

}


Linux Framebuffer Device


#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>

int main() { int fd = open("/dev/fb0", O_RDWR); if (fd < 0) { perror("open"); return 1; }


// Get screen info
struct fb_var_screeninfo vinfo;
ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);

printf("Resolution: %dx%d, %d bpp\n",
       vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);

// Map framebuffer
size_t screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
uint8_t *fbp = mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

if (fbp == MAP_FAILED) {
    perror("mmap");
    close(fd);
    return 1;
}

// Draw red rectangle
for (int y = 100; y < 200; y++) {
    for (int x = 100; x < 200; x++) {
        int offset = (y * vinfo.xres + x) * 4;
        fbp[offset + 0] = 0;      // Blue
        fbp[offset + 1] = 0;      // Green
        fbp[offset + 2] = 255;    // Red
        fbp[offset + 3] = 0;      // Alpha
    }
}

munmap(fbp, screensize);
close(fd);

return 0;

}


GPU Programming Basics


OpenGL example (minimal):

#include <GL/gl.h>
#include <GL/glut.h>

void display(void) { glClear(GL_COLOR_BUFFER_BIT);


// Draw triangle
glBegin(GL_TRIANGLES);
glColor3f(1.0, 0.0, 0.0);  // Red
glVertex2f(-0.5, -0.5);
glColor3f(0.0, 1.0, 0.0);  // Green
glVertex2f(0.5, -0.5);
glColor3f(0.0, 0.0, 1.0);  // Blue
glVertex2f(0.0, 0.5);
glEnd();

glFlush();

}


int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowSize(800, 600); glutCreateWindow("OpenGL Triangle"); glutDisplayFunc(display); glutMainLoop(); return 0; }


Vulkan (modern, lower-level):

  • More complex but more control
  • Used in modern games and graphics applications
  • Lower overhead than OpenGL

Compiler Internals


Compilation Stages


Source Code (C)
    ↓
Preprocessing (cpp)
    ↓
Compilation (cc1)
    ↓
Assembly Code (.s)
    ↓
Assembly (as)
    ↓
Object Code (.o)
    ↓
Linking (ld)
    ↓
Executable

Viewing Intermediate Stages


# Preprocessing only
gcc -E program.c -o program.i

Compilation to assembly

gcc -S program.c -o program.s


Assembly to object file

gcc -c program.c -o program.o


Linking

gcc program.o -o program


All in one

gcc program.c -o program


Assembly Output


C code:

int add(int a, int b) {
    return a + b;
}

Assembly (x64, AT&T syntax):

gcc -S -O0 add.c

add.s

add: pushq %rbp movq %rsp, %rbp movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl -4(%rbp), %edx movl -8(%rbp), %eax addl %edx, %eax popq %rbp ret


With optimization:

gcc -S -O2 add.c

add.s (much simpler!)

add: leal (%rdi,%rsi), %eax ret


Optimization Levels


-O0  # No optimization (default, good for debugging)
-O1  # Basic optimizations
-O2  # Moderate optimizations (recommended for production)
-O3  # Aggressive optimizations
-Os  # Optimize for size
-Og  # Optimize for debugging experience

Compiler Flags for System Programming


# Include debugging symbols
gcc -g program.c -o program

Enable all warnings

gcc -Wall -Wextra -Werror program.c -o program


Position-independent code (for shared libraries)

gcc -fPIC -shared library.c -o library.so


Static linking

gcc -static program.c -o program


Control optimization

gcc -O2 program.c -o program


Kernel-style compilation

gcc -nostdlib -ffreestanding -fno-stack-protector -mno-red-zone


Generate assembly listing

gcc -Wa,-adhln=program.lst -c program.c


Show what compiler is doing

gcc -v program.c -o program


Link Time Optimization (LTO)


# Enable LTO
gcc -flto -O2 file1.c file2.c -o program

Can optimize across compilation units


Advanced Debugging


GDB Power Features


Setting up GDB:

# Compile with debug symbols
gcc -g -O0 program.c -o program

Start GDB

gdb ./program


Advanced GDB commands:

# Breakpoints
break main
break file.c:42
break function if x > 10

Watchpoints (break when variable changes)

watch variable_name


Catchpoints (break on events)

catch syscall catch throw


Conditional breakpoints

break main if argc > 1


Examine memory

x/10x rsp # 10 hex words at stack pointer x/s 0x404000 # String at address x/10i rip # 10 instructions at instruction pointer


Disassemble

disassemble main disassemble /m main # With source


Register manipulation

set rax=0x42printrax = 0x42 print rax


Call functions

call printf("Debug: %d\n", x)


Backtrace

bt bt full # With local variables


Thread debugging

info threads thread 2 thread apply all bt


Core dump analysis

gdb program core


GDB scripting:

# .gdbinit or script file
define printstack
    set $i = 0
    while $i < 10
        x/x ($rsp + $i * 8)
        set $i = $i + 1
    end
end

Valgrind


Memory leak detection:

# Compile with debug symbols
gcc -g program.c -o program

Run with Valgrind

valgrind --leak-check=full ./program


More detailed

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./program


Example output:

==12345== HEAP SUMMARY:
==12345==     in use at exit: 100 bytes in 1 blocks
==12345==   total heap usage: 1 allocs, 0 frees, 100 bytes allocated
==12345==
==12345== 100 bytes in 1 blocks are definitely lost
==12345==    at malloc (in /usr/lib/valgrind/...)
==12345==    by main (program.c:10)

AddressSanitizer (ASan)


Compile-time memory error detector:

# Compile with ASan
gcc -fsanitize=address -g program.c -o program

Run

./program


Detects:

  • Buffer overflows
  • Use-after-free
  • Use-after-return
  • Double-free
  • Memory leaks

strace (System Call Tracer)


# Trace system calls
strace ./program

Trace specific syscalls

strace -e open,read,write ./program


Follow forks

strace -f ./program


Save to file

strace -o trace.txt ./program


Time syscalls

strace -c ./program


Example output:

open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0"..., 832) = 832
close(3) = 0

ltrace (Library Call Tracer)


# Trace library calls
ltrace ./program

Show timestamps

ltrace -t ./program


Core Dumps


Enable core dumps:

# Set core dump size limit
ulimit -c unlimited

Run program (will create core file on crash)

./program


Debug with GDB

gdb ./program core


Analyzing core dump:

(gdb) bt          # Backtrace
(gdb) info registers
(gdb) x/10x $rsp  # Examine stack

Security in System Programming


Common Vulnerabilities


1. Buffer Overflow:

// Vulnerable
void vulnerable(char *input) {
    char buffer[64];
    strcpy(buffer, input);  // No bounds checking!
}

// Safe void safe(char *input) { char buffer[64]; strncpy(buffer, input, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; }


2. Format String Vulnerability:

// Vulnerable
printf(user_input);  // User controls format string!

// Safe printf("%s", user_input);


3. Integer Overflow:

// Vulnerable
size_t size = user_provided_size;
char *buf = malloc(size);  // What if size wraps around?

// Safe if (size > MAX_ALLOWED_SIZE) return -1; char *buf = malloc(size);


4. Use-After-Free:

// Vulnerable
free(ptr);
// ... later ...
*ptr = value;  // Accessing freed memory!

// Safe free(ptr); ptr = NULL; // Prevent accidental use


Secure Coding Practices


// Always check return values
int fd = open("file.txt", O_RDONLY);
if (fd < 0) {
    perror("open");
    return -1;
}

// Use safe string functions strncpy(dest, src, sizeof(dest) - 1); dest[sizeof(dest) - 1] = '\0';


// Check array bounds if (index >= array_size) { return -1; }


// Validate input if (user_size > MAX_SIZE || user_size == 0) { return -1; }


// Clear sensitive data memset(password, 0, sizeof(password));


// Use const for read-only data void process(const char *input);


Compiler Security Features


# Stack canaries (detect buffer overflows)
gcc -fstack-protector-strong program.c -o program

Position Independent Executable (ASLR support)

gcc -fPIE -pie program.c -o program


Enable all warnings

gcc -Wall -Wextra -Werror program.c -o program


Fortify source (runtime checks)

gcc -D_FORTIFY_SOURCE=2 -O2 program.c -o program


No executable stack

gcc -z noexecstack program.c -o program


Full RELRO (read-only relocations)

gcc -Wl,-z,relro,-z,now program.c -o program


Static Analysis Tools


Cppcheck:

sudo apt-get install cppcheck
cppcheck --enable=all program.c

Clang Static Analyzer:

scan-build gcc program.c -o program

Fuzzing


AFL (American Fuzzy Lop):

# Compile with AFL
afl-gcc program.c -o program

Run fuzzer

afl-fuzz -i testcases/ -o findings/ ./program @@


System Hardening


ASLR (Address Space Layout Randomization):

# Check if ASLR enabled
cat /proc/sys/kernel/randomize_va_space
# 0 = disabled, 1 = partial, 2 = full

Enable ASLR

echo 2 | sudo tee /proc/sys/kernel/randomize_va_space


SELinux/AppArmor:

  • Mandatory access control
  • Restricts what processes can do
  • Essential for production systems

Performance Profiling


Using perf


# Record performance data
sudo perf record ./program

Analyze

sudo perf report


Profile CPU usage

sudo perf top


Count events

sudo perf stat ./program


Using gprof


# Compile with profiling
gcc -pg program.c -o program

Run program (generates gmon.out)

./program


Analyze

gprof program gmon.out > analysis.txt


Best Practices Summary


Code Quality

  • Use version control (Git)
  • Write tests (unit tests, integration tests)
  • Use static analysis tools
  • Enable all compiler warnings
  • Code review

Security

  • Validate all input
  • Check all return values
  • Use safe string functions
  • Clear sensitive data
  • Follow principle of least privilege

Debugging

  • Use version control to track changes
  • Add assertions liberally
  • Use debugger, don't just add printf
  • Test edge cases
  • Write reproducible test cases

Performance

  • Profile before optimizing
  • Optimize algorithms, not micro-optimizations
  • Use appropriate data structures
  • Consider cache effects
  • Benchmark changes

Key Concepts


  • Framebuffers provide direct pixel/text access
  • Compilers transform C through preprocessing, compilation, assembly, and linking
  • Optimization levels trade compile time and code size for speed
  • GDB is essential for debugging system programs
  • Valgrind detects memory errors and leaks
  • strace shows system calls
  • Buffer overflows are common security vulnerabilities
  • Compiler security features help prevent exploits
  • Static analysis finds bugs without running code

Common Mistakes


  1. Trusting user input - Always validate
  2. Ignoring return values - Check for errors
  3. Buffer overflows - Use safe string functions
  4. Not using debugger - GDB saves time
  5. Premature optimization - Profile first
  6. No error handling - Handle all error cases
  7. Ignoring warnings - Fix them, don't ignore

Debugging Tips


  • Reproduce reliably - Can't fix what you can't reproduce
  • Minimize test case - Remove unrelated code
  • Binary search - Comment out half, find which half has bug
  • Print intermediate values - Verify assumptions
  • Use assertions - Catch bugs early
  • Check documentation - Read man pages
  • Ask for help - Describe problem clearly

Mini Exercises


  1. Write framebuffer code to draw shapes
  2. Examine assembly output of optimized vs unoptimized code
  3. Use GDB to debug a segfault
  4. Find memory leaks with Valgrind
  5. Trace system calls with strace
  6. Write secure string handling functions
  7. Enable all compiler security features
  8. Use static analyzer to find bugs
  9. Profile program with perf
  10. Write fuzzer test cases

Review Questions


  1. What are the stages of C compilation?
  2. How do optimization levels affect code?
  3. What vulnerabilities does buffer overflow cause?
  4. How does Valgrind detect memory leaks?
  5. What compiler flags improve security?

Reference Checklist


By the end of this chapter, you should be able to:

  • Understand framebuffer graphics
  • Know compilation stages (preprocessing, assembly, linking)
  • Use compiler optimization flags
  • Debug with GDB (breakpoints, watchpoints, backtrace)
  • Find memory errors with Valgrind
  • Trace system calls with strace
  • Recognize common security vulnerabilities
  • Write secure system code
  • Use static analysis tools
  • Profile program performance

Conclusion: Your System Programming Journey


Congratulations on completing this comprehensive system programming course! You've learned:


  • Foundations: What system programming is and why it matters
  • Architecture: CPU design, memory organization, instruction sets
  • Low-Level Programming: Assembly language for multiple architectures
  • Memory Management: Virtual memory, paging, MMU
  • Boot Process: BIOS, bootloaders, kernel initialization
  • Kernel Development: Building kernels for x86/x64 and ARM
  • Linux Programming: System calls, processes, IPC, shell scripting
  • Kernel Modules: Extending Linux kernel functionality
  • Advanced Topics: Graphics, compilers, debugging, security

Where to Go from Here


Continue Learning:

  • Contribute to open-source projects (Linux kernel, QEMU, etc.)
  • Build your own operating system
  • Study embedded systems programming
  • Learn about real-time systems
  • Explore hardware design (FPGA, HDL)

Career Paths:

  • Kernel developer
  • Device driver engineer
  • Embedded systems programmer
  • Security researcher
  • Systems architect
  • Compiler engineer

Resources:

  • Linux Kernel Development by Robert Love
  • Operating Systems: Three Easy Pieces by Remzi Arpaci-Dusseau
  • Computer Systems: A Programmer's Perspective by Bryant & O'Hallaron
  • Linux kernel source code
  • OSDev wiki (osdev.org)

Final Thoughts


System programming is challenging but immensely rewarding. You now have the knowledge to:

  • Understand how computers really work
  • Write low-level, efficient code
  • Debug complex system issues
  • Contribute to operating systems and drivers
  • Build systems from the ground up

Keep experimenting, keep learning, and keep building. The world needs skilled system programmers!




Key Takeaway: System programming encompasses graphics, compilers, debugging, and security. Master these tools and techniques to become a complete system programmer capable of tackling any low-level challenge.