Gcc 获取INT 16h键扫描码而不是字符

Gcc 获取INT 16h键扫描码而不是字符,gcc,x86,linker-errors,inline-assembly,bootloader,Gcc,X86,Linker Errors,Inline Assembly,Bootloader,我正在编写一个简单的引导加载程序,我有一个getch函数 char getch() { uint16_t inchar; __asm__ __volatile__ ("int $0x16\n\t" : "=a"(inchar) : "0"(0x0)); return (char)inchar; } 我尝试了第一个显而易见的解决方案,即删除inchar变量的转换为char,但是当我打印它时

我正在编写一个简单的引导加载程序,我有一个getch函数

char getch()
{
   uint16_t inchar;

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

   return (char)inchar;
}
我尝试了第一个显而易见的解决方案,即删除inchar变量的转换为char,但是当我打印它时,仍然返回char而不是代码

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

    }

}
我的目的是实现scanf功能,需要知道Enter键的scancode。当scanf遇到Enter时,它应该停止读取键盘,并返回写入的字符串或整数,具体取决于您是写入数字还是字符

我尝试实现scanf:

当我尝试链接时,它不起作用。它应该返回一个字符串,其中包含从键盘输入的字符。我从链接器中获取此错误:

ld:section.在[0000000000007dfe,0000000000007dff]处加载的sig与在[0000000000007DD800000000007E15]处加载的数据重叠

返回返回值的上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键。您可以测试getch中的ASCII字符的值0x0d回车。您还可以根据C转义序列测试字符\r\n

您得到的链接器错误:

ld:section.在[0000000000007dfe,0000000000007dff]处加载的sig与在[0000000000007DD800000000007E15]处加载的数据重叠

表示您的.data部分已开始与.sig部分重叠。您正在使用的链接器脚本是为有限的512字节引导加载程序设计的。发生此错误是因为您现在拥有的代码和数据超过512字节的容量。使用新的链接器脚本和更复杂的技术,您可以让引导加载程序将内核的其余部分读入内存,然后将控制权转移到内存中。下面的代码是一个示例:

引导加载程序和内核文件与链接器脚本相结合。引导签名0xaa55由链接器生成。 将引导加载程序重新定位到中断向量表和BIOS数据区BDA正上方0x0600处的低内存。 FAR JMP用于将控制转移到重新定位的引导加载程序。 在重新定位的引导加载程序之后,内核在0x800被读入内存。 如果发生错误,将重试磁盘读取。 内核使用一次读取一个扇区。这允许它用于软盘映像。要读取的扇区数由链接器生成。 内核存储在引导加载程序之后的磁盘扇区中。 该代码假设一张1.44MB的软盘,每条磁道有18个扇区和2个磁头。建议软盘映像包含一个与USB软盘/FDD仿真相兼容的适当选项。 堆栈设置为0x0000:0x0000。这意味着它将包装到第一个64kb内存的顶部。第一次推送后,堆栈地址将为0x0000:0xfffe。 BSS段已调零。我们不能假设内存为零。 在调用内核之前,段寄存器被设置为零。CS=DS=ES=FS=GS=0,字符串指令的方向标志被清除,以便向前移动。 使用32位DWORD偏移量将控件传输到C代码中的kernelmainentry点,以与使用-m16选项时GCC处理返回地址的方式兼容。 我提供了一些改进和更改的函数。printchar用于打印单个字符,printstring用于打印以NUL结尾的字符串,getstring用于开始接受来自键盘的用户输入。 getstring获取缓冲区和要读取的最大字符数。当用户按ENTER键时结束。标签被忽略并丢弃。退格防止退格超过缓冲区的开头,并将退格视为破坏性的,因为它会备份光标并将其替换为空格。 示例内核提示输入用户名,并在控制台上将其显示给用户。 link.ld:

bpb.inc:

boot.asm:

img将是一个1.44MB的软盘映像,上面有引导加载程序和内核。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
返回返回值的上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));
}
您不需要扫描代码来确定是否输入ke y被按下了。您可以测试getch中的ASCII字符的值0x0d回车。您还可以根据C转义序列测试字符\r\n

您得到的链接器错误:

ld:section.在[0000000000007dfe,0000000000007dff]处加载的sig与在[0000000000007DD800000000007E15]处加载的数据重叠

表示您的.data部分已开始与.sig部分重叠。您正在使用的链接器脚本是为有限的512字节引导加载程序设计的。发生此错误是因为您现在拥有的代码和数据超过512字节的容量。使用新的链接器脚本和更复杂的技术,您可以让引导加载程序将内核的其余部分读入内存,然后将控制权转移到内存中。下面的代码是一个示例:

引导加载程序和内核文件与链接器脚本相结合。引导签名0xaa55由链接器生成。 将引导加载程序重新定位到中断向量表和BIOS数据区BDA正上方0x0600处的低内存。 FAR JMP用于将控制转移到重新定位的引导加载程序。 在重新定位的引导加载程序之后,内核在0x800被读入内存。 如果发生错误,将重试磁盘读取。 内核使用一次读取一个扇区。这允许它用于软盘映像。要读取的扇区数由链接器生成。 内核存储在引导加载程序之后的磁盘扇区中。 该代码假设一张1.44MB的软盘,每条磁道有18个扇区和2个磁头。建议软盘映像包含一个与USB软盘/FDD仿真相兼容的适当选项。 堆栈设置为0x0000:0x0000。这意味着它将包装到第一个64kb内存的顶部。第一次推送后,堆栈地址将为0x0000:0xfffe。 BSS段已调零。我们不能假设内存为零。 在调用内核之前,段寄存器被设置为零。CS=DS=ES=FS=GS=0,字符串指令的方向标志被清除,以便向前移动。 使用32位DWORD偏移量将控件传输到C代码中的kernelmainentry点,以与使用-m16选项时GCC处理返回地址的方式兼容。 我提供了一些改进和更改的函数。printchar用于打印单个字符,printstring用于打印以NUL结尾的字符串,getstring用于开始接受来自键盘的用户输入。 getstring获取缓冲区和要读取的最大字符数。当用户按ENTER键时结束。标签被忽略并丢弃。退格防止退格超过缓冲区的开头,并将退格视为破坏性的,因为它会备份光标并将其替换为空格。 示例内核提示输入用户名,并在控制台上将其显示给用户。 link.ld:

bpb.inc:

boot.asm:

img将是一个1.44MB的软盘映像,上面有引导加载程序和内核。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

扫描码可以在ah中找到,所以请尝试inchar>>8以获得它。也就是说,按return键会产生回车符\r或换行符\n,但忘记了这些字符中的哪一个。请参阅以获取文档。这是我对scanf:char*readln{char*s[255]的实现;for int i=255;i此错误告诉您,您的代码对于启动扇区来说太长。更具体地说,它告诉您,数据段溢出了可用空间,进入了您在末尾放置的0xaa55签名。启动扇区只有512字节,这对于您要压缩到其中的代码量来说是非常小的空间。通常的做法是ach将编写一个很小的引导扇区,从接下来的几个扇区加载一些负载并跳转到它。该负载可以是引导加载程序的其余部分;您可以根据需要将其变大。该链接器脚本和代码将构建一个512字节的小引导扇区。它不是为@fuz指出的完整内核而设计的。链接器脚本是o不,你仍然可以使用realmode。你将受到内核大小的限制。在最简单的情况下,你可以在0x7E00将内核加载到内存中,然后一直加载到0xFFYou 32kb用于内核。如果创建一个更复杂的加载程序,并将其重新定位到中断表上方的内存中,则可以获得更多。在0x7e00处加载是最简单的。扫描代码可以在ah中找到,因此请尝试inchar>>8来获取它。也就是说,按return键应该会产生一个回车符\r或换行符\n字符,forg
到底是哪一个。请参阅以获取文档。这是我对scanf:char*readln{char*s[255]的实现;for int i=255;i此错误告诉您,您的代码对于启动扇区来说太长。更具体地说,它告诉您,数据段溢出了可用空间,进入了您在末尾放置的0xaa55签名。启动扇区只有512字节,这对于您要压缩到其中的代码量来说是非常小的空间。通常的做法是ach将编写一个很小的引导扇区,从接下来的几个扇区加载一些负载并跳转到它。该负载可以是引导加载程序的其余部分;您可以根据需要将其变大。该链接器脚本和代码将构建一个512字节的小引导扇区。它不是为@fuz指出的完整内核而设计的。链接器脚本是o不,你仍然可以使用realmode。你将受到内核大小的限制。在最简单的情况下,你可以在0x7E00将内核加载到内存中,然后一直加载到0xFFYou 32kb用于内核。如果您创建一个更复杂的加载程序,并将其重新定位到中断表上方的内存中,您可以获得更多。在0x7e00加载是最简单的。这是一个非常好且非常有用的答案。这应该成为一个规范副本。非常有用的答案。感谢您对我的耐心。:这是一个非常好且非常有用的答案应该变成一个标准副本。非常有用的答案。谢谢你对我的耐心
#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
qemu-system-i386 -fda disk.img
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