问题
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
kernelmain
entry 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, andgetstring
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