-
Notifications
You must be signed in to change notification settings - Fork 63
Debugging
Debugging techniques for MentOS kernel and userspace programs.
# Terminal 1: Start QEMU with GDB server
cd build
make qemu-gdb
# Terminal 2: Connect with GDB
gdb --quiet --command=gdb.runOr use cgdb (recommended):
cgdb --quiet --command=gdb.runFirst, generate the gdb.run file (only needed once or after rebuild):
cd build
make gdbinitThis creates a GDB command file that tells GDB where to find symbol information for all binaries.
When you start GDB, MentOS is paused at the bootloader entry. You'll see two breakpoints:
Breakpoint 1: Bootloader entry (boot_main)
(gdb) continueMentOS will run until it hits the first breakpoint in the bootloader.
Breakpoint 2: Kernel entry (kmain)
(gdb) continueMentOS will continue to the kernel's main function.
# Breakpoints
(gdb) break kmain # Break at function
(gdb) break kernel.c:100 # Break at line
(gdb) break sys_fork # Break at syscall
(gdb) info breakpoints # List breakpoints
(gdb) delete 1 # Delete breakpoint 1
# Execution
(gdb) continue # Continue running
(gdb) step # Step into function
(gdb) next # Step over function
(gdb) finish # Run until function returns
# Inspection
(gdb) print variable # Print variable value
(gdb) print *pointer # Dereference pointer
(gdb) print/x value # Print as hex
(gdb) x/10xw 0xC0000000 # Examine memory (hex words)
# Call stack
(gdb) backtrace # Show call stack
(gdb) frame 2 # Switch to frame 2
(gdb) info locals # Show local variables
# Registers
(gdb) info registers # Show all registers
(gdb) print $eax # Print EAX register
# Process info
(gdb) info threads # Show threads (limited in MentOS)(gdb) break boot_main
(gdb) continue
(gdb) step # Step through boot process
(gdb) print multiboot_magic # Check multiboot(gdb) break kmain
(gdb) continue
(gdb) break vmem_init # Break at memory init
(gdb) break vfs_init # Break at VFS init(gdb) break sys_fork # Break at fork syscall
(gdb) break sys_read # Break at read syscall
(gdb) continue
# Trigger syscall from MentOS shell
(gdb) backtrace # See call stack(gdb) break scheduler_pick_next_task
(gdb) continue
# Let scheduler run
(gdb) print runqueue.curr->pid
(gdb) print runqueueMentOS includes a VSCode launch configuration.
- Open MentOS folder in VSCode
- Run
make qemu-gdbin terminal - Press
F5or use "Run > Start Debugging" - Select "(gdb) Attach" configuration
The debugger will connect to QEMU and stop at breakpoints.
The kernel provides logging functions for debug output.
#define LOGLEVEL_EMERG 0 // System unusable
#define LOGLEVEL_ALERT 1 // Action must be taken immediately
#define LOGLEVEL_CRIT 2 // Critical conditions
#define LOGLEVEL_ERR 3 // Error conditions
#define LOGLEVEL_WARNING 4 // Warning conditions
#define LOGLEVEL_NOTICE 5 // Normal but significant (default minimum)
#define LOGLEVEL_INFO 6 // Informational
#define LOGLEVEL_DEBUG 7 // Debug-level messagespr_emerg("System is unusable\n");
pr_alert("Action required\n");
pr_crit("Critical error\n");
pr_err("Error: %d\n", errno);
pr_warning("Warning: %s\n", msg);
pr_notice("Normal but significant\n");
pr_info("Informational message\n");
pr_debug("Debug: value=%d\n", value);Default: Only pr_notice and below are displayed.
Add at the top of your source file:
// Include the kernel log levels
#include "sys/kernel_levels.h"
// Change header prefix
#define __DEBUG_HEADER__ "[MYMOD]"
// Set log level
#define __DEBUG_LEVEL__ LOGLEVEL_DEBUG
// Include debugging header
#include "io/debug.h"Now all levels from DEBUG down to EMERG will be shown with [MYMOD] prefix.
// kernel/src/fs/myfs.c
#include "sys/kernel_levels.h"
#define __DEBUG_HEADER__ "[MYFS ]"
#define __DEBUG_LEVEL__ LOGLEVEL_INFO
#include "io/debug.h"
void myfs_init(void)
{
pr_info("Initializing MyFS\n");
pr_debug("Debug info: %d\n", some_value); // Won't show (INFO level)
}Kernel logs appear in the terminal where you ran make qemu (or in build/serial.log when EMULATOR_OUTPUT_TYPE=OUTPUT_LOG):
[BOOT ] Bootloader starting...
[KERN ] Kernel initialized
[MEM ] Memory: 128MB
[VFS ] Mounted root filesystem
[MYFS ] Initializing MyFSUse standard printf() in userspace programs:
// userspace/bin/myprog.c
#include <stdio.h>
int main(void)
{
printf("Debug: starting\n");
int result = some_function();
printf("Debug: result = %d\n", result);
return 0;
}Output appears in the MentOS shell.
Use assert() for runtime checks:
#include <assert.h>
void my_function(int *ptr)
{
assert(ptr != NULL); // Check pointer
assert(*ptr > 0); // Check value
// ...
}If assertion fails, program aborts with message.
Always check system call return values:
int fd = open("/etc/passwd", O_RDONLY);
if (fd < 0) {
perror("open failed");
return 1;
}Enable memory tracing at build time:
cmake .. -DENABLE_KMEM_TRACE=ON
cmake .. -DENABLE_PAGE_TRACE=ON
makeThis adds logging to all kmalloc(), kfree(), and page allocation calls.
Look for allocation/free imbalances in kernel logs:
[MEM] kmalloc(256) = 0xC0100000
[MEM] kfree(0xC0100000)Unmatched kmalloc = memory leak.
When a page fault occurs, kernel logs:
[PAGE FAULT] addr=0x12345678, error=0x02
EIP: 0xC0001234
CR2: 0x12345678Error code bits:
- Bit 0: Page present (0 = not present, 1 = protection fault)
- Bit 1: Write operation (0 = read, 1 = write)
- Bit 2: User mode (0 = kernel, 1 = user)
Common causes:
- Not present + kernel: Accessing unmapped memory
- Not present + user: Accessing kernel memory from userspace
- Protection + user: Writing to read-only memory
When kernel panics:
KERNEL PANIC: Unable to mount root fs
EIP: 0xC0001234
EAX: 0x00000000 EBX: 0xC0100000
Call trace:
0xC0001234: vfs_mount+0x42
0xC0002000: kmain+0x120-
Note EIP (instruction pointer)
-
Use
objdumpto find function:objdump -d build/mentos/kernel.bin | grep C0001234 -
Check call trace for root cause
If QEMU keeps rebooting:
-
CPU exception during critical operation
-
Usually GDT, IDT, or paging issue
-
Enable GDB and break early:
(gdb) break boot_main (gdb) continue (gdb) step
Cause: Program not in filesystem image
Solution:
make filesystem
make qemuCause: File permissions or not running as root
Solution: Check file permissions in filesystem/
Symptoms: MentOS hangs, no response
Debug:
# Connect with GDB
(gdb) Ctrl+C # Interrupt
(gdb) backtrace # See where it's stuck
(gdb) info registers # Check EIPCause: Syscall failed, check errno
Debug:
#include <errno.h>
int fd = open("/file", O_RDONLY);
if (fd < 0) {
printf("Error: %d (%s)\n", errno, strerror(errno));
}Cause: Invalid memory access
Debug:
-
Use GDB:
(gdb) break main (gdb) continue (gdb) step
-
Check pointers before dereferencing
-
Add assertions:
assert(ptr != NULL);
Use clock() or gettimeofday():
#include <time.h>
clock_t start = clock();
// ... code to measure ...
clock_t end = clock();
double cpu_time = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("Time: %f seconds\n", cpu_time);Add timing logs in critical paths:
pr_info("Starting operation\n");
uint64_t start = read_tsc(); // Read CPU timestamp counter
// ... operation ...
uint64_t end = read_tsc();
pr_info("Operation took %llu cycles\n", end - start);Note: For each example program below, add the source file to userspace/bin/CMakeLists.txt, then rebuild:
make programs
make filesystemGoal: Use GDB to find and fix a crash.
Buggy Program - userspace/bin/buggy_segfault.c:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
// BUG: Not checking if argv[1] exists
char *input = argv[1];
printf("Input: %s\n", input); // CRASH if no argument!
printf("Length: %lu\n", strlen(input));
return 0;
}Steps:
-
Compile and rebuild the filesystem:
make programs && make filesystem -
In MentOS, run without arguments:
/bin/buggy_segfault→ Crashes! -
Connect GDB and set breakpoint:
make qemu-gdb # In another terminal (from build/): gdb --quiet --command=gdb.run (gdb) b main (gdb) c (gdb) n # Step through (gdb) print argv # Inspect arguments (gdb) print argv[1] # NULL!
-
Fix: Add argument checking:
if (argc < 2) { printf("Usage: %s <input>\n", argv[0]); return 1; }
Goal: Use kernel logging to trace execution.
Program - userspace/bin/trace_demo.c:
#include <stdio.h>
#include <unistd.h>
int main() {
printf("Program started\n");
for (int i = 0; i < 5; i++) {
printf("Loop iteration %d\n", i);
sleep(1);
}
printf("Program ending\n");
return 0;
}With Debugging:
# Boot with GDB
make qemu-gdb
# Set breakpoint at specific line
(gdb) b trace_demo.c:8
(gdb) c
# Inspect state
(gdb) print i
(gdb) list # Show source codeGoal: Inspect complex data structures in GDB.
Program - userspace/bin/debug_struct.c:
#include <stdio.h>
#include <stdlib.h>
struct Person {
char name[50];
int age;
int id;
};
int main() {
struct Person people[] = {
{"Alice", 25, 1},
{"Bob", 30, 2},
{"Charlie", 35, 3}
};
printf("People array:\n");
for (int i = 0; i < 3; i++) {
printf(" %s, age %d, ID %d\n",
people[i].name,
people[i].age,
people[i].id);
}
return 0;
}Debug:
(gdb) b debug_struct.c:17 # Breakpoint in loop
(gdb) c
(gdb) print people[0] # Show struct
(gdb) print people[0].name # Show field
(gdb) x/50c people[0].name # Show raw memory
(gdb) n # Step throughGoal: Detect memory errors (uninitialized, leaks, etc.).
Buggy Program - userspace/bin/memory_bug.c:
#include <stdio.h>
#include <stdlib.h>
int main() {
// BUG 1: Use uninitialized variable
int *data = malloc(10 * sizeof(int));
printf("Value: %d\n", data[0]); // Garbage value!
// BUG 2: Access out of bounds
data[100] = 42; // Overflow!
// BUG 3: Memory leak
free(data);
printf("Value after free: %d\n", data[0]); // Use-after-free!
return 0;
}Find Problems:
(gdb) b memory_bug.c:7
(gdb) c
(gdb) print data[0] # Uninitialized!
(gdb) print &data[100] # Out of bounds
(gdb) n # Execute free
(gdb) print data[0] # Use-after-freeFix:
// Initialize
for (int i = 0; i < 10; i++) data[i] = 0;
// Check bounds
if (index >= 10) {
printf("Index out of bounds!\n");
return 1;
}
// Don't access after free
free(data);
data = NULL; // Set to NULL for safetyGoal: Master GDB breakpoints and inspection.
Test Program:
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
int x = 10;
int y = 20;
int sum = add(x, y);
printf("Sum: %d\n", sum);
return 0;
}GDB Session:
# Set breakpoint before line
(gdb) b <filename>.c:3 # In add() function
# Continue to breakpoint
(gdb) c
# Inspect local variables
(gdb) info locals
(gdb) print a
(gdb) print b
# Inspect register
(gdb) info registers
(gdb) disassemble # Show assembly
# Modify variable during execution
(gdb) set variable a = 100
(gdb) print a
# Continue
(gdb) n # Next line
(gdb) s # Step into function
(gdb) finish # Run to function returnGoal: Debug kernel code with GDB.
Steps:
-
Start GDB kernel debugging:
make qemu-gdb # In another terminal (from build/): gdb --quiet --command=gdb.run -
Set kernel breakpoint:
(gdb) b sys_fork # Break on fork syscall (gdb) c
-
In MentOS:
/bin/shell & # Background shell
-
Back in GDB:
# GDB will break when fork is called (gdb) print pid # Inspect variable (gdb) bt # Backtrace - how did we get here?
-
Analyze backtrace:
#0 sys_fork () at kernel/src/process/process.c:123 #1 syscall_handler () at kernel/src/system/syscall.c:45 #2 0x00001234 in [userspace]
Common GDB Commands:
# Set conditional breakpoint
(gdb) b kernel/src/fs/vfs.c:200 if fd > 10
# Breakpoint with command
(gdb) b sys_read
(gdb) commands
> print fd
> print buf
> print count
> continue
> end
# Watchpoint (break on variable change)
(gdb) watch process_count
# List all breakpoints
(gdb) info break
# Delete breakpoint
(gdb) delete 1
# Disable/enable
(gdb) disable 1
(gdb) enable 1Scenario: Your program crashes inconsistently.
Steps:
- Run program until crash
- Use GDB to inspect crash point
- Look at backtrace (
btcommand) - Examine local variables
- Set breakpoints before crash
- Trace execution step-by-step
- Find root cause
- Fix and verify
Disassemble binaries:
objdump -d build/mentos/bootloader.bin | less
objdump -d build/mentos/kernel.bin | grep sys_forkList symbols:
nm build/mentos/kernel.bin | grep kmallocInspect ELF files:
readelf -h build/mentos/kernel.bin
readelf -S build/mentos/kernel.bin # Show sections-
Use meaningful log prefixes (
__DEBUG_HEADER__) - Check return values of all functions
- Initialize variables to catch uninitialized usage
- Use assertions for invariant checking
- Enable tracing during development
- Commit often so you can bisect bugs
- Test incrementally after each change
- Development Guide - Add your own code
- Kernel - Understand kernel internals
- System Calls - Debug syscall issues
Previous: Development Guide | Next: Scheduling →