Получение 16-тиканального ключа INT вместо символа - PullRequest
0 голосов
/ 11 декабря 2018

Я пишу простой загрузчик, и у меня есть функция getch.

char getch()
{
   uint16_t inchar;

   __asm__ __volatile__ ("int $0x16\n\t"
                        : "=a"(inchar)
                   : "0"(0x0));

   return (char)inchar;
}

Я попробовал первое очевидное решение, которое заключается в удалении приведения к char переменной inchar,но когда я его печатаю, он все равно возвращает символ вместо кода.

Моя println реализация:

void println(char *str)
{
    while (*str) 
    {
        // AH=0x0e, AL=char to print, BH=page, BL=fg color
        __asm__ __volatile__ ("int $0x10"
                              :
                              : "a" ((0x0e<<8) | *str++),
                                "b" (0x0000));

    }

}

Сценарий компоновщика:

ENTRY(start);
SECTIONS
{
    . = 0x7C00;
    .text : {
        /* Place the code in hw.o before all other code */
        boot.o(.text);
        *(.text);
    }

    /* Place the data after the code */
    .data : SUBALIGN(4) {
        *(.data);
        *(.rodata);
    }

    /* Place the boot signature at VMA 0x7DFE */
    .sig : AT(0x7DFE) {
        SHORT(0xaa55);
    }

    /* Place the uninitialised data in the area after our bootloader
     * The BIOS only reads the 512 bytes before this into memory */
    . = 0x7E00;
    .bss : SUBALIGN(4) {
        __bss_start = .;
        *(COMMON);
        *(.bss)
        . = ALIGN(4);
        __bss_end = .;
    }
    __bss_sizeb = SIZEOF(.bss);

    /* Remove sections that won't be relevant to us */
    /DISCARD/ : {
        *(.eh_frame);
        *(.comment);
        *(.note.gnu.build-id);
    }
}

Мое намерение состоит в том, чтобыреализовать функцию scanf и знать код сканирования клавиши Enter .Когда scanf встречает Enter , он должен прекратить чтение с клавиатуры и вернуть то, что написано в виде строки или целого числа, в зависимости от того, пишете ли вы число или символ.

Моя попыткаРеализация scanf:

char* readln() 
{
    char *s[255]; 
    for (int i = 255; i <= 255; ++i) {
        char a[] = {0, 0};
        a[0] = getch();
        s[i] = a[0];
        //println(a);
        if (a[0] == '\r') { 
            break;
            return s;
        }
    } 
} 

Когда я пытаюсь связать, это не работает.Он должен вернуть строку с символами, введенными с клавиатуры.Я получаю эту ошибку от компоновщика:

ld: section .sig загружен в [0000000000007dfe, 00000000007dff] перекрывает раздел .data загружен в [0000000000007dd8,0000000000007e15]

1 Ответ

0 голосов
/ 11 декабря 2018

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будет выглядеть примерно так:

enter image description here


Я рекомендую использовать кросс-компилятор , но вы можете изменить приведенные выше команды так:скомпилировать / связать с вашим родным компилятором.Я не рекомендую это, но это должно работать:

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
...