Getting INT 16h key scancode instead of character

冷暖自知 提交于 2021-01-28 09:26:00

问题


I'm writing a simple bootloader, and I have a getch function.

char getch()
{
   uint16_t inchar;

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

   return (char)inchar;
}

I tried the first obvious solution, which is remove the casting to char of the inchar variable, but when I print it still returning a char instead of code.

My println implementation:

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));

    }

}

Linker script:

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);
    }
}

My intention is to implement the scanf function and need to know the scancode of the Enter key. When scanf encounters an Enter it should stop reading the keyboard and return what is written as a string, or integer depending on whether you write a number or a character.

My attempt at implementing 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;
        }
    } 
} 

When I try to link, it doesn't work. It should return a string with characters entered from the keyboard. I get this error from the linker:

ld: section .sig loaded at [0000000000007dfe,0000000000007dff] overlaps section .data loaded at [0000000000007dd8,0000000000007e15]


回答1:


Int 0x16/AH=0 returns the scan code in the upper 16-bits of the returned value. inchar was actually defined as a 16-bit uint16_t type. All you need to do is shift the value of inchar to the right 8 bits to place the BIOS scan code in the upper 8 bits into the lower 8 bits.

The function could look like:

/* 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));
}

You don't need the scancode to determine if the ENTER key was pressed. You can test the ASCII character from getch for the value 0x0d (carriage return). You can also test the character against the C escape sequence \r.


The linker error you are getting:

ld: section .sig loaded at [0000000000007dfe,0000000000007dff] overlaps section .data loaded at [0000000000007dd8,0000000000007e15]

Is saying that your .data section has begun overlapping the .sig section. The linker script you are using was designed for a limited 512 byte bootloader. The error is occurring because you now have more code and data than can fit in 512-bytes. Using a new linker script and a more complex technique you get the bootloader to read the rest of the kernel into memory and then transfer control to it. The code below is an example that:

  • The bootloader and kernel files are combined with a linker script. The boot signature 0xaa55 is generated by the linker.
  • Relocates the bootloader to low memory just above the interrupt vector table and the BIOS Data Area (BDA) at 0x0600.
  • A FAR JMP is used to transfer control to the relocated bootloader.
  • The kernel is read into memory at 0x800 just after the relocated bootloader.
  • Disk reads are retried in the event of error.
  • The kernel is read 1 sector at a time using LBA to CHS translation. This allows it to be used on floppy disk images. The number of sectors to read is generated by the linker.
  • The kernel is stored on disk in the sectors right after the bootloader.
  • The code assumes a 1.44MB floppy with 18 sectors per track and 2 heads. It is suggested that floppy disk images contain a proper BIOS Parameter Block (BPB) for compatibility with USB Floppy/FDD emulation.
  • The stack is set to 0x0000:0x0000. This means it will wrap to the top of the first 64kb of memory. After the first push the stack address will be 0x0000:0xfffe.
  • The BSS segment is zeroed out. We can't assume memory will be zero.
  • Prior to calling the kernel the segment registers are set to zero. CS=DS=ES=FS=GS=0 and the direction flag for string instructions is cleared for forward movement.
  • Control is transferred to the kernelmainentry point in the C code using a 32-bit (DWORD) offset to be compatible with the way GCC handles return addresses when using the -m16 option.
  • I've provided a few improved and changed functions. printchar for printing a single character, printstring for printing a NUL terminated string, and getstring that starts accepting user input from the keyboard.
  • getstring take a buffer and a maximum number of characters to read. It ends when the user presses ENTER. TAB are ignored and thrown away. BACKSPACE prevents backspacing past the beginning of the buffer and treats backspace as destructive, as it backs up the cursor and replaces it with a space.
  • The sample kernel prompts for the user's name and displays it back to the user on the console.

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;
}

To compile/assemble and link you can do this:

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 will be a 1.44MB floppy image with bootloader and kernel on it. boot.bin will be the binary file with the 512-byte boot sector, and kernel.bin is the kernel. You may not need boot.bin and kernel.bin but I generate them just in case.

You should be able to run it in QEMU like this:

qemu-system-i386 -fda disk.img

The output in QEMU would look similar to:


I recommend using a cross compiler, but you can modify the commands above to compile/link with your native compiler. I don't recommend it, but it should work:

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


来源:https://stackoverflow.com/questions/53714458/getting-int-16h-key-scancode-instead-of-character

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!