Commit 3a1ea6bc authored by Karsten Lehmann's avatar Karsten Lehmann

feat: Added tests; Removed installer

Three test cases were added:
 1) Normal boot
 2) The file is not on the floppy
 3) An io error is generated

Furthermore a way to install the bootloader using commands available on most
*NIX systems was added. Therefore the installer is not needed anymore.
parent b8e3018e
*.bin
*.BIN
*.flp
*~
sibolo-install
all: simple_boot_loader installer
NASM = nasm
NASM_FLAGS = -f bin
installer:
gcc -o sibolo-install -g -Wall sibolo-install.c
QEMU = qemu-system-i386
QEMU_FLAGS = -cpu 486 -boot order=a
QEMU_DEBUG_FLAGS = -S -gdb tcp::1234
QEMU_DISK_DRIVE_FLAGS = -drive if=ide,media=disk,format=raw
QEMU_FLOPPY_DRIVE_FLAGS = -drive if=floppy,index=0,format=raw
BOOTLOADER_BINARY = bootloader.bin
BOOTLOADER_SOURCE = bootloader.asm
FILENAME_OFFSET = 480
TESTCODE_BINARY = TESTCODE.BIN
TESTCODE_SOURCE = tests/testcode.asm
TESTCODE_83_NAME = TESTCODEBIN
TEST_IMAGE_WORKING = test_working.flp
TEST_IMAGE_IO_ERROR = test_io_error.flp
TEST_IMAGE_NOT_FOUND_ERROR = test_not_found.flp
all: $(BOOTLOADER_BINARY)
$(BOOTLOADER_BINARY): $(BOOTLOADER_SOURCE)
$(NASM) $(NASM_FLAGS) -o $(BOOTLOADER_BINARY) $(BOOTLOADER_SOURCE)
$(TESTCODE_BINARY): $(TESTCODE_SOURCE)
$(NASM) $(NASM_FLAGS) -o $(TESTCODE_BINARY) $(TESTCODE_SOURCE)
$(TEST_IMAGE_WORKING): $(BOOTLOADER_BINARY) $(TESTCODE_BINARY)
rm -f $(@)
mkfs.msdos -C $(@) 1440
dd if=$(BOOTLOADER_BINARY) of=$(@) conv=notrunc bs=512 count=1
echo "$(TESTCODE_83_NAME)" | dd \
of=$(@) \
conv=notrunc \
bs=1 \
count=11 \
seek=$(FILENAME_OFFSET)
set -e; \
LOOP_DEVICE=$$(losetup --find --show $(@)); \
TEST_IMAGE_MOUNTPOINT=$$(mktemp --directory --quiet); \
mount $${LOOP_DEVICE} $${TEST_IMAGE_MOUNTPOINT}; \
cp $(TESTCODE_BINARY) $${TEST_IMAGE_MOUNTPOINT}/; \
umount $${TEST_IMAGE_MOUNTPOINT}; \
rm -rf $${TEST_IMAGE_MOUNTPOINT}; \
losetup -d $${LOOP_DEVICE}
$(TEST_IMAGE_IO_ERROR): $(BOOTLOADER_BINARY)
rm -f $(@)
mkfs.msdos -C $(@) 1440
dd if=$(BOOTLOADER_BINARY) of=$(@) conv=notrunc bs=512 count=1
echo "TEST BIN" | dd \
of=$(@) \
conv=notrunc \
bs=1 \
count=11 \
seek=$(FILENAME_OFFSET)
truncate --size=4096 $(@)
$(TEST_IMAGE_NOT_FOUND_ERROR): $(BOOTLOADER_BINARY)
rm -f $(@)
mkfs.msdos -C $(@) 1440
dd if=$(BOOTLOADER_BINARY) of=$(@) conv=notrunc bs=512 count=1
echo "TEST BIN" | dd \
of=$(@) \
conv=notrunc \
bs=1 \
count=11 \
seek=$(FILENAME_OFFSET)
check: $(TEST_IMAGE_WORKING) $(TEST_IMAGE_NOT_FOUND_ERROR) $(TEST_IMAGE_IO_ERROR)
$(QEMU) \
$(QEMU_FLAGS) \
$(QEMU_FLOPPY_DRIVE_FLAGS),file=$(TEST_IMAGE_WORKING) \
-name "Test working"
$(QEMU) \
$(QEMU_FLAGS) \
$(QEMU_FLOPPY_DRIVE_FLAGS),file=$(TEST_IMAGE_NOT_FOUND_ERROR) \
-name "Test file not found"
$(QEMU) \
$(QEMU_FLAGS) \
$(QEMU_DISK_DRIVE_FLAGS),file=$(TEST_IMAGE_IO_ERROR) \
-name "Test IO error"
debug: $(TEST_IMAGE_WORKING)
$(QEMU) \
$(QEMU_FLAGS) \
$(QEMU_FLOPPY_DRIVE_FLAGS),file=$(TEST_IMAGE_WORKING) \
-name "Debug bootloader, listen on port 1234" \
$(QEMU_DEBUG_FLAGS)
simple_boot_loader:
nasm -f bin -o bootloader.bin bootloader.asm
clean:
rm -rf bootloader.bin
rm -f \
$(BOOTLOADER_BINARY) \
$(TESTCODE_BINARY) \
$(TEST_IMAGE_WORKING) \
$(TEST_IMAGE_IO_ERROR) \
$(TEST_IMAGE_NOT_FOUND_ERROR)
/* Copyright (c) 2018 by Karsten Lehmann <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* This program writes the simple bootloader SiBoLo on a floppy image,
* configures it to load a certain file and keeps the bios parameter block
* of the floppy image.
*/
#include<ctype.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define BOOTLOADER_SIZE 512
/* Prints the usage of this program and exits immediately. */
static void print_usage(char *name)
{
printf("Usage : %s [bootloader.file] [floppy.image] [FILE.BIN]\n"
"The filename of the file the bootloader loads should not exceed 12 characters,\n"
"the base name should be at maximum 8 characters long and\n"
"the length of file extension should not exceed 3 characters.\n",
name);
exit(1);
}
/*
* Validates if a filename is suitable for the usage with a FAT12 file system.
* Check that the name is all uppercase, the base name does not exceed 8 bytes
* and the file extension does not exceed 3 bytes.
*
* Exits on an invalid filename.
*/
static void validate_filename(const char *filename)
{
for (int i=0 ; i<strlen(filename); i++) {
if (filename[i] != '.' && !isupper(filename[i])) {
printf("Error, the name of the file the bootloader loads should only contain uppercase characters.\n");
exit(1);
}
}
if (strchr(filename, '.') != NULL) {
if (strlen(filename) > 12) {
printf("Error, the name of the file the bootloader loads is too long\n");
exit(1);
}
size_t dot_pos = strchr(filename, '.') - filename;
if (dot_pos > 8) {
printf("Error, the base name of the file the bootloader loads is too long\n");
exit(1);
}
if (strlen(filename) - dot_pos > 4) {
printf("Error, the extension of the file the bootloader loads is too long\n");
exit(1);
}
} else {
if (strlen(filename) > 8) {
printf("Error, the base name of the file the bootloader loads is to long\n");
exit(1);
}
}
}
/*
* Reads the path to the bootloader file, the path to the floppy file and the
* name of the file the bootloader should load from the arguments.
*
* Prints the usage of the program and exits immediately if the wrong number of
* arguments is provided or the filename has a wrong format.
*/
static void handle_args(int argc, char* argv[], char** bootloader_file,
char** floppy_file, char** filename)
{
if (argc != 4) {
print_usage(argv[0]);
}
validate_filename(argv[3]);
*bootloader_file = argv[1];
*floppy_file = argv[2];
*filename = argv[3];
}
/*
* Reads the bootloader from a binary file.
*/
static char *read_bootloader(char *bootloader_path)
{
char *bootloader = malloc(BOOTLOADER_SIZE);
if (bootloader == NULL) {
printf("Error while allocating memory for the bootloader\n");
exit(1);
}
FILE *bootloader_file = fopen(bootloader_path, "r");
if (bootloader_file == NULL) {
printf("Error opening %s\n", bootloader_path);
exit(1);
}
fseek(bootloader_file, 0 , SEEK_END);
if (ftell(bootloader_file) != BOOTLOADER_SIZE) {
printf("Error, expected the bootloader to have a size of %d bytes.\n",
BOOTLOADER_SIZE);
exit(1);
}
fseek(bootloader_file, 0, SEEK_SET);
fread(bootloader, BOOTLOADER_SIZE, 1, bootloader_file);
fclose(bootloader_file);
return bootloader;
}
/*
* Converts a filename to the 8.3 format. 8 bytes padded with spaces for the
* short filename and 3 bytes padded with spaces for the short file extension.
*/
static char *format_name_83(const char *filename)
{
char *name_83 = malloc(11);
if (name_83 == NULL) {
printf("Error while allocating memory for the 8.3 name\n");
exit(1);
}
for (int i=0; i<11; i++) {
name_83[i] = ' ';
}
char *short_file_name = name_83, *short_file_ending = &name_83[8];
char *file_ending;
if ((file_ending = strchr(filename, '.')) != NULL) {
file_ending += sizeof(char);
size_t basename_length = file_ending - filename - 1;
size_t ending_length = strlen(file_ending);
memcpy(short_file_name, filename, basename_length);
memcpy(short_file_ending, file_ending, ending_length);
} else {
memcpy(short_file_name, filename, strlen(filename));
}
return name_83;
}
/*
* Determines the position of the filename to load inside the bootloader.
*/
static char *filename_position(char *bootloader)
{
char *placeholder = "PLACEHOLDER";
int j = 0;
for (int i = 0; i < BOOTLOADER_SIZE; i++) {
if (bootloader[i] == placeholder[j]) {
j++;
} else {
j = 0;
}
if (j == 11) {
return &bootloader[i-10];
}
}
printf("Could not locate the position of the filename in the bootloader\n");
exit(1);
}
/*
* Sets the name of the file to load in the bootloader.
*/
static void bootloader_set_filename(char* bootloader, const char* filename)
{
char *name_83 = format_name_83(filename);
char *boot_name = filename_position(bootloader);
memcpy(boot_name, name_83, 11);
free(name_83);
}
/*
* Copies the OEM name and the Extended Bios Parameter Block from the floppy to
* the bootloader (bytes 3 - 0x3d) and verifies the extended boot signature.
*/
static void bootloader_set_bpb(char *bootloader, const char *floppy_name)
{
char *bpb = &bootloader[3];
FILE* floppy = fopen(floppy_name, "r");
if (floppy == NULL) {
printf("Error while reading the file %s\n", floppy_name);
exit(1);
}
fseek(floppy, 3, SEEK_SET);
fread(bpb, 0x3a, 1, floppy);
fclose(floppy);
/*
* Check the extended boot signature to verify if this bios parameter
* block will be understood by the bootloader. The offset of the
* extended boot signature on the floppy is 0x26 bytes. This program
* copies the bios parameter block from byte 3 on the floppy, therefore
* the offset in the variable bpb is 0x26 - 3 = 0x23.
*/
if (bpb[0x23] != 40 && bpb[0x23] != 41) {
printf("Error, found no valid BPB on %s\n", floppy_name);
exit(1);
}
}
/*
* Writes the bootloader to a floppy image.
*/
static void bootloader_write_to_floppy(const char *bootloader, const char *floppy_name)
{
FILE* floppy = fopen(floppy_name, "r+");
if (floppy == NULL)
{
printf("Error while opening %s for writing\n", floppy_name);
exit(1);
}
fwrite(bootloader, BOOTLOADER_SIZE, 1, floppy);
fclose(floppy);
}
int main(int argc, char* argv[])
{
char *bootloader_file, *floppy_file, *file_name;
handle_args(argc, argv, &bootloader_file, &floppy_file, &file_name);
char* bootloader = read_bootloader(bootloader_file);
bootloader_set_filename(bootloader, file_name);
bootloader_set_bpb(bootloader, floppy_file);
bootloader_write_to_floppy(bootloader, floppy_file);
free(bootloader);
}
<?xml version="1.0"?>
<!-- See:
https://sourceware.org/bugzilla/show_bug.cgi?id=22869
https://stackoverflow.com/a/55246894
In short qemu reports i386 as target architecture to gdb and gdb sees
i386 as superset to i8086. Therefore `set architecture i8086` has no
effect. This file provides a simple target description telling gdb to use
the i8086 architecture.
See https://www-zeuthen.desy.de/unix/unixguide/infohtml/gdb/Target-Description-Format.html
for details on the target description format.
Load the file in gdb with `set tdesc filename <path>` -->
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target>
<architecture>i8086</architecture>
</target>
;;; Copyright 2020 by Karsten Lehmann <[email protected]>
;;;
;;; This program is free software: you can redistribute it and/or modify
;;; it under the terms of the GNU General Public License as published by
;;; the Free Software Foundation, either version 3 of the License, or
;;; (at your option) any later version.
;;;
;;; This program is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;;
;;; This is the test code for the simple bootloader. It is more than one
;;; sector in size and displays a short success message including the boot
;;; drive passed in the dl register.
BITS 16
;;; The following code is for initialization
init:
;; Save all registers
push sp
push bp
push ss
push cs
push es
push ds
push di
push si
push dx
push cx
push bx
push ax
;; Setup the data segment
mov ax, 0x7c0
mov ds, ax
;; Save the boot drive
mov [boot_drive], dl
;; Set the video mode to text with 16 rows, 80 columns and 16 colors
xor ah, ah
mov al, 3
int 0x10
;; Jump to the test code
jmp tests
;;; Add a padding to make sure the test binary spans across several sectors on
;;; the floppy
padding:
times 4096 db 0
tests:
;; Print a welcome message, the boot drive and the contents of all registers
mov si, header
call print_string
mov si, msg_boot_drive
call print_string
xor ax, ax
mov al, [boot_drive]
call print_hex
call line_break
mov si, msg_registers
call print_string
mov si, register_ax
call print_string
pop ax
call print_hex
mov si, register_bx
call print_string
pop ax
call print_hex
call line_break
mov si, register_cx
call print_string
pop ax
call print_hex
mov si, register_dx
call print_string
pop ax
call print_hex
call line_break
mov si, register_si
call print_string
pop ax
call print_hex
mov si, register_di
call print_string
pop ax
call print_hex
call line_break
mov si, register_ds
call print_string
pop ax
call print_hex
mov si, register_es
call print_string
pop ax
call print_hex
call line_break
mov si, register_cs
call print_string
pop ax
call print_hex
mov si, register_ss
call print_string
pop ax
call print_hex
call line_break
mov si, register_bp
call print_string
pop ax
call print_hex
mov si, register_sp
call print_string
pop ax
call print_hex
call line_break
loop:
jmp loop
;;; This function rints the string given in DX:SI to the screen.
;;; It respects line breaks
print_string:
push bp
mov bp, sp
sub sp, 2
.print_loop:
lodsb
cmp al, 0
je .done
cmp al, 0xa
jne .no_linebreak
mov [bp-2], si
call line_break
mov si, [bp-2]
jmp .print_loop
.no_linebreak:
mov ah, 0xe
xor bx, bx
int 0x10
jmp .print_loop
.done:
add sp, 2
pop bp
ret
line_break:
mov ah, 3
xor bx, bx
int 0x10
;; Move the cursor to the beginning of the next line
xor dl, dl
inc dh
mov ah, 2
int 0x10
ret
;;; This function prints the value stored in the AX register as hexadecimal
;;; number to the screen.
print_hex:
push bp
mov bp, sp
sub sp, 2
;; Save the value of the AX register on the stack
mov [bp-2], ax
;; Print the prefix '0x'
mov ah, 0xe
mov al, 0x30 ; Ascii '0'
xor bx, bx
int 10h
mov al, 0x78 ; Ascii 'x'
int 10h
ror word [bp-2], 4
mov cx, 4
.print_loop:
rol word [bp-2], 4
;; Fill the AX register with the four most significant bits
mov ax, [bp-2]
and ax, 0xf000
rol ax, 4
;; Translate the number to the ascii code of its hexadecimal
;; representation
mov si, hex_characters
add si, ax
;; Print the hexadecimal representation of the number
mov al, [si]
mov ah, 0xe
int 10h
loop .print_loop
add sp, 2
pop bp
ret
boot_drive: db 0
header: db 0xa, " Simple Bootloader Testcode - Test succeeded", 0xa, 0
hex_characters: db "0123456789ABCDEF"
msg_boot_drive: db " The boot drive passed in the DL register is ", 0
msg_registers: db " Register contents : ", 0xa, 0xa, 0
register_ax: db " AX ", 0
register_bx: db " BX ", 0
register_cx: db " CX ", 0
register_dx: db " DX ", 0
register_si: db " SI ", 0
register_di: db " DI ", 0
register_bp: db " BP ", 0
register_sp: db " SP ", 0
register_ds: db " DS ", 0
register_es: db " ES ", 0
register_cs: db " CS ", 0
register_ss: db " SS ", 0
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment