Int 0x16 / AH = 0 возвращает код сканирования в старших 16-битах возвращаемого значения.inchar
был фактически определен как 16-битный uint16_t
тип.Все, что вам нужно сделать, это сдвинуть значение inchar
в правильные 8 бит, чтобы поместить код сканирования BIOS из старших 8 бит в младшие 8 бит.
Функция может выглядеть следующим образом:
/* getch that returns the scancode and not the ASCII character */
char getch_scancode()
{
uint16_t inchar;
/* upper 8 bits of inchar are the scancode in AH. */
__asm__ __volatile__ ("int $0x16\n\t"
: "=a"(inchar)
: "0"(0x0));
/* Shift right 8 bits to move scan code to the lower 8-bits */
return ((char)(inchar>>8));
}
Вам не нужен скан-код, чтобы определить, была ли нажата кнопка ENTER .Вы можете проверить символ ASCII из getch
для значения 0x0d (возврат каретки).Вы также можете проверить символ по escape-последовательности C \r
.
Полученная вами ошибка компоновщика:
ld: section.sig, загруженный в [00000000007dfe, 0000000000007dff], перекрывает раздел .data, загруженный в [0000000000007dd8,0000000000007e15]
, сообщает, что ваш раздел .data
начал перекрывать раздел .sig
.Используемый вами скрипт компоновщика был разработан для ограниченного 512-байтового загрузчика.Ошибка возникает из-за того, что теперь у вас больше кода и данных, чем умещается в 512 байт.Используя новый скрипт компоновщика и более сложную технику, вы заставляете загрузчик читать остальную часть ядра в память, а затем передавать ему управление.Приведенный ниже код является примером того, что:
- Файлы загрузчика и ядра объединяются со сценарием компоновщика.Подпись загрузчика 0xaa55 генерируется компоновщиком.
- Перемещает загрузчик в память с низким объемом чуть выше таблицы векторов прерываний, а область данных BIOS (BDA) - 0x0600.
- Используется FAR JMPпередать управление перемещенному загрузчику.
- Ядро считывается в память по адресу 0x800 сразу после перемещения загрузчика.
- При чтении с диска повторяются попытки в случае ошибки.
- Ядро считывается по 1 сектору за один раз, используя перевод LBA в CHS .Это позволяет использовать его на образах дискет.Число читаемых секторов генерируется компоновщиком.
- Ядро хранится на диске в секторах сразу после загрузчика.
- Код предполагает использование дискеты объемом 1,44 МБ с 18 секторами на дорожку.и 2 головы.Предполагается, что образы дискет содержат соответствующий блок параметров BIOS (BPB) для совместимости с эмуляцией USB Floppy / FDD.
- Стек установлен в 0x0000: 0x0000.Это означает, что он будет перенесен на верх первых 64 КБ памяти.После первого нажатия адрес стека будет 0x0000: 0xfffe.
- Сегмент BSS обнуляется.Мы не можем предполагать, что память будет нулевой.
- До вызова ядра регистры сегментов устанавливаются в ноль.CS = DS = ES = FS = GS = 0, и флаг направления для строковых инструкций очищается для движения вперед.
- Управление передается точке входа
kernelmain
в коде C использование 32-битного (DWORD) смещения для совместимости с тем, как GCC обрабатывает адреса возврата при использовании опции -m16
. - Я предоставил несколько улучшенных и измененных функций.
printchar
для печати одного символа, printstring
для печати строки с завершением NUL и getstring
, которая начинает принимать пользовательский ввод с клавиатуры. getstring
принимает буфер и максимальное количество символовчитать.Он заканчивается, когда пользователь нажимает ENTER . TAB игнорируются и выбрасываются. BACKSPACE предотвращает возврат назад после начала буфера и обрабатывает возврат назад как разрушительный, поскольку он создает резервную копию курсора и заменяет его пробелом. - Образец ядра запрашивает имя пользователя и отображаетобратно к пользователю на консоли.
link.ld :
OUTPUT_FORMAT("elf32-i386");
ENTRY(boot_start);
BOOTLOADER_BASE = 0x7c00;
BOOTLOADER_RELOC = 0x600;
SECTOR_SIZE = 512;
KERNEL_BASE = BOOTLOADER_RELOC + SECTOR_SIZE;
SECTIONS
{
__boot_reloc_addr = BOOTLOADER_RELOC;
__boot_base_addr = BOOTLOADER_BASE;
__sector_sizew = SECTOR_SIZE>>1;
. = BOOTLOADER_RELOC;
/* Code and data in boot.o placed between 0x7c00 and 0x7e00 */
.boot : SUBALIGN(0) {
boot.o(.text*)
boot.o(.rodata*)
boot.o(.data)
}
. = BOOTLOADER_RELOC + 0x200 - 2;
/* Boot signature at 510th byte from beginning of bootloader's base */
.sig : {
SHORT(0xaa55);
}
KERNEL_ADJ = KERNEL_BASE - .;
. = KERNEL_BASE;
__disk_load_start = .;
__disk_load_seg = (__disk_load_start) >> 4;
/* Kernel code and data */
.kernel : AT(ADDR(.kernel) - KERNEL_ADJ) SUBALIGN(4) {
*(.text*)
*(.rodata*)
*(.data)
}
__disk_load_end = .;
__disk_load_num_sectors = (__disk_load_end - __disk_load_start + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
.kernel.bss : SUBALIGN(4) {
__bss_start = .;
*(COMMON);
*(.bss)
. = ALIGN(4);
__bss_end = .;
}
__bss_sizew = SIZEOF(.kernel.bss)>>1;
/* Remove unnecessary sections */
/DISCARD/ : {
*(.eh_frame);
*(.comment);
}
}
bpb.inc :
global bpb_disk_info
jmp boot_start
TIMES 3-($-$$) DB 0x90 ; Support 2 or 3 byte encoded JMPs before BPB.
bpb_disk_info:
; Dos 4.0 EBPB 1.44MB floppy
OEMname: db "mkfs.fat" ; mkfs.fat is what OEMname mkdosfs uses
bytesPerSector: dw 512
sectPerCluster: db 1
reservedSectors: dw 1
numFAT: db 2
numRootDirEntries: dw 224
numSectors: dw 2880
mediaType: db 0xf0
numFATsectors: dw 9
sectorsPerTrack: dw 18
numHeads: dw 2
numHiddenSectors: dd 0
numSectorsHuge: dd 0
driveNum: db 0
reserved: db 0
signature: db 0x29
volumeID: dd 0x2d7e5a1a
volumeLabel: db "NO NAME "
fileSysType: db "FAT12 "
boot.asm :
; These symbols are defined by the linker. We use them to zero BSS section
extern __bss_start
extern __bss_sizew
; These symbols are length (in sectors) of the kernel,
; and segment in memory to start reading to
extern __disk_load_num_sectors
extern __disk_load_seg
extern __sector_sizew;
; Mmory address to relocate the bootsector from / to
extern __boot_base_addr
extern __boot_reloc_addr
; This is the C entry point defined in kmain.c
extern kernelmain ; kernelmain is C entry point
global boot_start ; Make this global to suppress linker warning
KERNEL_LBA_START equ 1 ; Logical Block Address(LBA) kernel starts on
; LBA 1 = sector after boot sector
KERNEL_LBA_END equ KERNEL_LBA_START + __disk_load_num_sectors
; Logical Block Address(LBA) kernel ends at
DISK_RETRIES equ 3 ; Number of times to retry on disk error
section .text
bits 16
; Include a BPB (1.44MB floppy with FAT12)
%include "bpb.inc"
boot_start:
; This code up until label .reloc must be position independent
xor eax, eax ; DS=0 since we use ORG 0x7c00. 0x0000<<4+0x7c00=0x7c00
mov ds, ax
mov es, ax
mov ss, ax ; Stack at 0x0000:0x0000
mov esp, eax ; After first push will be 0x0000:0xfffe at top of 64kb
; Copy bootloader from __boot_base_addr (0x7c00) to __boot_reloc_addr (0x600)
; We copy the bootloader to low memory above the BIOS Data Area (BDA) to allow
; more space for the kernel.
cld
mov cx, __sector_sizew
mov si, __boot_base_addr
mov di, __boot_reloc_addr
rep movsw
; Jump to the relocated boot sector and set CS=0
jmp 0x0000:.reloc
.reloc:
; Read kernel 1 sector at a time until kernel loaded
load_kernel:
mov [bootDevice], dl ; Save boot drive
mov di, __disk_load_seg ; DI = Current segment to read into
mov si, KERNEL_LBA_START ; SI = LBA that kernel starts at
jmp .chk_for_last_lba ; Check to see if we are last sector in kernel
.read_sector_loop:
mov bp, DISK_RETRIES ; Set disk retry count
call lba_to_chs ; Convert current LBA to CHS
mov es, di ; Set ES to current segment number to read into
xor bx, bx ; Offset zero in segment
.retry:
mov ax, 0x0201 ; Call function 0x02 of int 13h (read sectors)
; AL = 1 = Sectors to read
int 0x13 ; BIOS Disk interrupt call
jc .disk_error ; If CF set then disk error
.success:
add di, 512>>4 ; Advance to next 512 byte segment (0x20*16=512)
inc si ; Next LBA
.chk_for_last_lba:
cmp si, KERNEL_LBA_END ; Have we reached the last kernel sector?
jl .read_sector_loop ; If we haven't then read next sector
.kernel_loaded:
jmp launch_kernel ; Do realmode initialization and run kernel
.disk_error:
xor ah, ah ; Int13h/AH=0 is drive reset
int 0x13
dec bp ; Decrease retry count
jge .retry ; If retry count not exceeded then try again
error_end:
; Unrecoverable error; print drive error; enter infinite loop
mov si, diskErrorMsg ; Display disk error message
call print_string
cli
.error_loop:
hlt
jmp .error_loop
; Function: print_string
; Display a string to the console on display page 0
;
; Inputs: SI = Offset of address to print
; Clobbers: AX, BX, SI
print_string:
mov ah, 0x0e ; BIOS tty Print
xor bx, bx ; Set display page to 0 (BL)
jmp .getch
.repeat:
int 0x10 ; print character
.getch:
lodsb ; Get character from string
test al,al ; Have we reached end of string?
jnz .repeat ; if not process next character
.end:
ret
; Function: lba_to_chs
; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
; Works for all valid FAT12 compatible disk geometries.
;
; Resources: http://www.ctyme.com/intr/rb-0607.htm
; https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
; https://stackoverflow.com/q/45434899/3857942
; Sector = (LBA mod SPT) + 1
; Head = (LBA / SPT) mod HEADS
; Cylinder = (LBA / SPT) / HEADS
;
; Inputs: SI = LBA
; Outputs: DL = Boot Drive Number
; DH = Head
; CH = Cylinder (lower 8 bits of 10-bit cylinder)
; CL = Sector/Cylinder
; Upper 2 bits of 10-bit Cylinders in upper 2 bits of CL
; Sector in lower 6 bits of CL
;
; Notes: Output registers match expectation of Int 13h/AH=2 inputs
;
lba_to_chs:
push ax ; Preserve AX
mov ax, si ; Copy LBA to AX
xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
div word [sectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT
mov cl, dl ; CL = S = LBA mod SPT
inc cl ; CL = S = (LBA mod SPT) + 1
xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
div word [numHeads] ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
mov dh, dl ; DH = H = (LBA / SPT) mod HEADS
mov dl, [bootDevice] ; boot device, not necessary to set but convenient
mov ch, al ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
shl ah, 6 ; Store upper 2 bits of 10-bit Cylinder into
or cl, ah ; upper 2 bits of Sector (CL)
pop ax ; Restore scratch registers
ret
; Set up segments so they are 0, zero out the BSS memory and transfer
; control to the function kernelmain
launch_kernel:
xor ax, ax
mov es, ax
mov fs, ax
mov gs, ax ; ES=FS=GS=0 (we set DS=SS=0 previously)
; We need to zero out the BSS section. We'll do it a WORD at a time
mov edi, __bss_start ; Start address of BSS
mov ecx, __bss_sizew ; Length of BSS in WORDS
; Clear memory with value in AX (0x0000)
rep stosw ; Do clear using string store instruction
; Clear 2 bytes at a time
call dword kernelmain ; Call kernel's "C" main entry point
.end_loop: ; Loop forever to terminate when kernel main is finished
hlt
jmp .end_loop
section .data
; Uncomment these lines if not using a BPB (via bpb.inc)
; numHeads: dw 2 ; 1.44MB Floppy has 2 heads & 18 sector per track
; sectorsPerTrack: dw 18
bootDevice: db 0x00
diskErrorMsg: db "Unrecoverable disk error!", 0
kmain.c :
#include <stdint.h>
int getch()
{
uint16_t inchar;
__asm__ __volatile__ ("int $0x16\n\t"
: "=a"(inchar)
: "0"(0x0));
return ((unsigned char)inchar);
}
/* getch that returns the scancode and not the ASCII character */
int getch_scancode()
{
uint16_t inchar;
/* upper 8 bits of inchar are the scancode in AH. */
__asm__ __volatile__ ("int $0x16\n\t"
: "=a"(inchar)
: "0"(0x0));
/* Shift right 8 bits to move scan code to the lower 8-bits */
return ((unsigned char)(inchar>>8));
}
void printchar(int chr)
{
/* AH=0x0e, AL=char to print, BH=page, BL=fg color */
__asm__ __volatile__ ("int $0x10"
:
: "a" ((0x0e<<8) | (unsigned char)chr),
"b" (0x0000));
}
void printstring(char *str)
{
while (*str)
printchar (*str++);
}
/* Get NUL terminated string of maximum number of chars. The maximum
* number of characters doesn't include the NULL terminator. Make sure the
* str buffer passed can hold the maximum number characters plus an additional
* byte for the NUL */
char *getstring(char *str, int maxnumchars)
{
char inchar;
int curpos = 0;
/* Do nothing if NULL string or length is 0 */
if (!maxnumchars || !str) return str;
/* Continue string editing until ENTER (\r) is hit */
while ((inchar = getch()) != '\r') {
/* Process backspace, and do not allow backspacing past beginning of string.
* Printing backspace using the BIOS is non-destructive. We must backspace,
* print a space and then backspace once more to simulate a destructive
* backspace */
if (inchar == '\b') {
if (curpos > 0) {
curpos--;
printstring("\b \b");
}
continue;
}
/* Toss away the tab character and do nothing */
else if (inchar == '\t')
continue;
/* Store the keystroke pressed if we haven't reached end of buffer */
if (curpos < maxnumchars) {
str[curpos++] = inchar;
printchar(inchar);
}
}
/* Advance the cursor to the beginning of the next line with
* Carriage return & Line Feed */
printstring ("\r\n");
/* Null terminate the string */
str[curpos] = 0;
return str;
}
char str[41];
void kernelmain()
{
/* Array to receive 40 characters + room for NUL terminator */
printstring("\r\nEnter your name: ");
getstring (str, sizeof(str)-1);
printstring("Your name is: ");
printstring(str);
printstring("\r\n");
return;
}
Для компиляции / сборки и ссылкиВы можете сделать это:
nasm -f elf32 -Fdwarf -g boot.asm -o boot.o
i686-elf-gcc -g -c -m16 -ffreestanding -Os -Wall -fomit-frame-pointer kmain.c -o kmain.o
i686-elf-gcc -nostartfiles -nostdlib -Tlink.ld -o os.elf \
boot.o kmain.o
# Convert os.elf to flat binary file os.bin
objcopy -Obinary os.elf os.bin
# Build 1.44MB disk image
dd if=/dev/zero of=disk.img bs=1024 count=1440
dd if=os.bin of=disk.img conv=notrunc
# Split the boot sector from the complete os.bin file
# These files may not be needed, generate them anyway
dd if=os.bin of=boot.bin bs=512 count=1
dd if=os.bin of=kernel.bin bs=512 seek=1
disk.img
будет образом дискеты 1,44 МБ с загрузчиком и ядром.boot.bin
будет двоичным файлом с 512-байтовым загрузочным сектором, а kernel.bin
- ядром.Возможно, вам не понадобятся boot.bin
и kernel.bin
, но я генерирую их на всякий случай.
Вы должны иметь возможность запустить его в QEMU следующим образом:
qemu-system-i386 -fda disk.img
Вывод в QEMUбудет выглядеть примерно так:
Я рекомендую использовать кросс-компилятор , но вы можете изменить приведенные выше команды так:скомпилировать / связать с вашим родным компилятором.Я не рекомендую это, но это должно работать:
gcc -fno-PIC -g -c -m16 -ffreestanding -Os -Wall -fomit-frame-pointer kmain.c -o kmain.o
ld -melf_i386 -nostartfiles -nostdlib -Tlink.ld -o os.elf \
boot.o kmain.o