I've made my own kernel. It has 32-bit pm, 16-bit pm, and 16-bit real mode code. I use Qemu to emualate an i686. And when i debug the mode switch parts, the code runs and works. However, the disassembly from disassemble command, and from gdb-dashboard still assume 32bit default opcode sizes, and so it writes garbages.
I'm now forced to compare: qemu instructions logs the object dumped kernel binary and the assembly source code. For code execution
and gdb for machine states (register values, memory values...) This naturally slow down productivity. And being able to just use gdb to look at the disassembly would be nice.
set architecture i8086 doesn't work.
#!/bin/bash
set -eou pipefail
ARG1="${1:-}"
# Define directories
BUILD_DIR="build"
ISO_DIR="isodir"
CFLAGS=("-std=gnu23" "-ffreestanding" "-Wall" "-Wextra")
CFLAGS16=("-std=gnu99" "-ffreestanding" "-Wall" "-Wextra")
LDFLAGS=("-ffreestanding" "-nostdlib" "-lgcc")
NASM_FLAGS32=("-f" "elf32")
NASM_FLAGS16=("-f" "elf")
DEBUG_OPT_LVL="-O0"
RELEASE_OPT_LVL="-O0"
QEMU_DBG_FLAGS=()
if [[ "$ARG1" == "debug" ]]; then
echo "Debug mode enabled"
CFLAGS+=("$DEBUG_OPT_LVL" "-g" "-DDEBUG")
CFLAGS16+=("$DEBUG_OPT_LVL" "-g" "-DDEBUG")
LDFLAGS+=("-g")
NASM_FLAGS32+=("-g" "-F" "dwarf")
NASM_FLAGS16+=("-g" "-F" "dwarf")
QEMU_DBG_FLAGS+=("-s" "-S")
else
echo "In normal mode, $RELEASE_OPT_LVL optimisation"
CFLAGS+=("$RELEASE_OPT_LVL")
CFLAGS16+=("$RELEASE_OPT_LVL")
LDFLAGS+=("$RELEASE_OPT_LVL")
fi
# Create necessary directories
mkdir -p "$BUILD_DIR" "$ISO_DIR/boot/grub"
# The variable for the dirs:
KERNEL="kernel"
REAL16_WRAPPERS="real16_wrappers"
REAL_FUNC="realmode_functions"
GDT="gdt"
IDT="idt"
STDIO="stdio"
STDLIB="stdlib"
OTHER="other"
CODE_ANALYSIS="runtime_code_analysis"
INCLUDE_DIRS=(
"$KERNEL"
"$REAL16_WRAPPERS"
"$REAL_FUNC"
"$GDT"
"$IDT"
"$STDIO"
"$STDLIB"
"$OTHER"
"$CODE_ANALYSIS"
)
# Build SUPER_INCLUDE as an array of -I arguments
SUPER_INCLUDE=()
for dir in "${INCLUDE_DIRS[@]}"; do
SUPER_INCLUDE+=("-I$dir")
done
# Assemble the bootloader assembly
nasm "${NASM_FLAGS32[@]}" "$KERNEL/boot_intel.asm" -o "$BUILD_DIR/boot.o"
i686-elf-gcc "${CFLAGS[@]}" "${SUPER_INCLUDE[@]}" -c "$KERNEL/kernel.c" -o "$BUILD_DIR/kernel.o"
# Compile the print functions.
i686-elf-gcc "${CFLAGS[@]}" -c "$STDIO/string_helper.c" -o "$BUILD_DIR/string_helper.o"
i686-elf-gcc "${CFLAGS[@]}" -c "$STDIO/bit_hex_string.c" -o "$BUILD_DIR/bit_hex_string.o" -std=gnu99 "-I$STDIO" "-I$STDLIB"
i686-elf-gcc "${CFLAGS[@]}" -c "$STDIO/vga_terminal.c" -o "$BUILD_DIR/vga_terminal.o" "-I$STDIO" "-I$OTHER"
i686-elf-gcc "${CFLAGS[@]}" -c "$STDIO/stdio.c" -o "$BUILD_DIR/stdio.o" "-I$STDIO" "-I$OTHER" "-I$STDLIB"
#compile misc helper functions
i686-elf-gcc "${CFLAGS[@]}" -c "$STDLIB/stdlib.c" -o "$BUILD_DIR/stdlib.o"
i686-elf-gcc "${CFLAGS[@]}" -c "$GDT/f1_binary_operation.c" -o "$BUILD_DIR/f1_binary_operation.o" "-I$STDIO" "-I$GDT"
i686-elf-gcc "${CFLAGS[@]}" -c "$GDT/f3_segment_descriptor_internals.c" -o "$BUILD_DIR/f3_segment_descriptor_internals.o" "-I$STDIO" "-I$GDT" "-I$STDLIB"
i686-elf-gcc "${CFLAGS[@]}" -c "$GDT/gdt.c" -o "$BUILD_DIR/gdt.o" "-I$STDIO" "-I$GDT" "-I$STDLIB"
# Compile the CPU functionality activation part
i686-elf-gcc "${CFLAGS[@]}" -c "$IDT/idt.c" -o "$BUILD_DIR/idt.o" "-I$IDT" "-I$GDT" "-I$STDLIB" "-I$STDIO"
nasm "${NASM_FLAGS32[@]}" "$IDT/exception_handler.asm" -o "$BUILD_DIR/exception_handler.o"
i686-elf-gcc "${CFLAGS[@]}" -c "$OTHER/virtual_memory.c" -o "$BUILD_DIR/virtual_memory.o"
i686-elf-gcc "${CFLAGS[@]}" -c "$OTHER/pit_timer.c" -o "$BUILD_DIR/pit_timer.o"
i686-elf-gcc "${CFLAGS[@]}" -c "$CODE_ANALYSIS/address_getter.c" -o "$BUILD_DIR/address_getter.o" "-I$REAL_FUNC" "-I$REAL16_WRAPPERS" "-I$GDT" "-I$STDLIB"
# Compile the real mode 16 code and it's wrappers
i686-elf-gcc "${CFLAGS[@]}" -c "$REAL16_WRAPPERS/call_real16_wrapper.c" -o "$BUILD_DIR/call_real16_wrapper.o" "-I$STDIO" "-I$STDLIB" "-I$GDT"
nasm "${NASM_FLAGS16[@]}" "$REAL16_WRAPPERS/call_realmode_function_wrapper16.asm" -o "$BUILD_DIR/call_realmode_function_wrapper16.o" "-I$REAL16_WRAPPERS"
nasm "${NASM_FLAGS32[@]}" "$REAL16_WRAPPERS/call_realmode_function_wrapper32.asm" -o "$BUILD_DIR/call_realmode_function_wrapper32.o" "-I$REAL16_WRAPPERS"
ia16-elf-gcc "${CFLAGS16[@]}" -c "$REAL_FUNC/realmode_functions.c" -o "$BUILD_DIR/realmode_functions.o"
# Link the kernel and generate the final binary
printf "\n\n====== Start of Linking =====\n\n"
BUILD_OBJECTS=(
"$BUILD_DIR/boot.o"
"$BUILD_DIR/kernel.o"
"$BUILD_DIR/stdlib.o"
"$BUILD_DIR/address_getter.o"
"$BUILD_DIR/string_helper.o"
"$BUILD_DIR/bit_hex_string.o"
"$BUILD_DIR/vga_terminal.o"
"$BUILD_DIR/stdio.o"
"$BUILD_DIR/idt.o"
"$BUILD_DIR/exception_handler.o"
"$BUILD_DIR/virtual_memory.o"
"$BUILD_DIR/pit_timer.o"
"$BUILD_DIR/f1_binary_operation.o"
"$BUILD_DIR/f3_segment_descriptor_internals.o"
"$BUILD_DIR/gdt.o"
"$BUILD_DIR/call_realmode_function_wrapper32.o"
"$BUILD_DIR/call_realmode_function_wrapper16.o"
"$BUILD_DIR/realmode_functions.o"
"$BUILD_DIR/call_real16_wrapper.o"
)
i686-elf-gcc -T linker.ld -o "$BUILD_DIR/myos.bin" "${LDFLAGS[@]}" "${BUILD_OBJECTS[@]}"
printf "\n\n====== End of Linking =====\n\n"
objdump -D -h "$BUILD_DIR/myos.bin" >"$BUILD_DIR/myos.dump"
# Check if the kernel is multiboot-compliant
if grub-file --is-x86-multiboot "$BUILD_DIR/myos.bin"; then
echo "Multiboot confirmed"
else
echo "The file is not multiboot"
exit 1
fi
# Copy the kernel binary and GRUB configuration to the ISO directory
cp "$BUILD_DIR/myos.bin" "$ISO_DIR/boot/myos.bin"
cp "$ISO_DIR/grub.cfg" "$ISO_DIR/boot/grub/grub.cfg"
# Create the ISO image
grub-mkrescue -o "$BUILD_DIR/myos.iso" "$ISO_DIR"
echo "ISO created successfully: $BUILD_DIR/myos.iso"
# start qemu using the binary, bypassing grub.
# -no-reboot: don't reset if the kernel crash
# -d in_asm, int, cpu_reset: Logs every instruction executed by cpu, every interupt and cpu reset events
# -D qemu_instr.log : The log file (redirect -d)
# -serial stdio: redirect COM1 serial port to your terminal
# ===== Pick one of those two
# -kernel "$BUILD_DIR/myos.bin" \
# -cdrom "$BUILD_DIR/myos.iso" \
qemu-system-i386 \
-cdrom "$BUILD_DIR/myos.iso" \
-no-reboot \
"${QEMU_DBG_FLAGS[@]}" \
-d in_asm,int,cpu_reset \
\
-serial stdio & # -D qemu_instr.log \
# qemu-system-i386 -kernel ./build/myos.bin & # or do this to use the binary directly # -cdrom "$BUILD_DIR/myos.iso" # -kernel "$BUILD_DIR/myos.bin" \
QEMU_PID=$!
# Give QEMU a second to start up
sleep 1
# Launch VNC viewer
vncviewer localhost:5900
# After you close the VNC viewer, kill QEMU
kill $QEMU_PID 2>/dev/null
The build script.
then I:
i868-elf-gdb build/myos.bin
(gdb) target remote :1234
and then go on to debug as usual
mon x/10i $pc