【问题标题】:Link object files from header files in real mode when using GCC -m16 option?使用 GCC -m16 选项时以实模式从头文件链接目标文件?
【发布时间】:2016-12-20 14:02:15
【问题描述】:

我想在我的 c 代码中实现头文件,其中部分包含用于 16 位实模式的 GCC 内联汇编代码,但我似乎有链接问题。这是我的头文件console.h 的样子:

#ifndef CONSOLE_H
#define CONSOLE_H

extern void kprintf(char*);

#endif

这是console.c:

#include "console.h"


void kprintf(char *string)
{
    for(int i=0;string[i]!='\0';i++)
    {
        asm("mov $0x0e,%%ah;"
            "mov $0x00,%%bh;"
            "mov %0,%%al;"
            "int $0x10"::"g"(string[i]):"eax", "ebx");
    }
}

最后一个hellworld.c

asm("jmp main");
#include "console.h"

void main()
{   
    asm("mov $0x1000,%ax;"
        "mov %ax,%es;"
        "mov %ax,%ds");
    char string[]="hello world";
    kprintf(string);

    asm(".rept 512;"
        "hlt;"
        ".endr");
}

我的引导程序在bootloader.asm:

org 0x7c00
bits    16

section .text
mov ax,0x1000
mov ss,ax
mov sp,0x000
mov esp,0xfffe
xor ax,ax
mov es,ax 
mov ds,ax

mov [bootdrive],dl

mov bh,0
mov bp,zeichen    
mov ah,13h
mov bl,06h
mov al,1
mov cx,6
mov dh,010h
mov dl,01h
int 10h

load:
mov dl,[bootdrive]
xor ah,ah
int 13h
jc load

load2:
mov ax,0x1000
mov es,ax
xor bx,bx

mov ah,2
mov al,1
mov cx,2
xor dh,dh

mov dl,[bootdrive]
int 13h
jc load2

mov ax,0
mov es,ax
mov bh,0
mov bp,zeichen3

mov ah,13h
mov bl,06h
mov al,1
mov cx,13
mov dh,010h
mov dl,01h
int 10h

mov ax,0x1000
mov es,ax
mov ds,ax
jmp 0x1000:0x000

zeichen db  'hello2'
zeichen3 db 'soweit so gut'
bootdrive db 0
times   510 - ($-$$)    hlt
dw  0xaa55

现在我使用下面的 buildscript build.sh:

#!bin/sh

nasm -f bin bootloader.asm -o bootloader.bin
gcc hellworld.c -m16 -c -o hellworld.o -nostdlib -ffreestanding
gcc console.c -m16 -c -o console.o -nostdlib link.ld -ffreestanding
ld  -melf_i386 -Ttext=0x0000 console.o hellworld.o -o hellworld.elf
objcopy -O binary hellworld.elf hellworld.bin
cat bootloader.bin hellworld.bin >disk.img
qemu-system-i386 disk.img

和链接脚本link.ld:

/*
*  link.ld
*/
OUTPUT_FORMAT(elf32-i386)
SECTIONS
{
   . = 0x0000;
   .text : { *(.startup); *(.text) }
   .data : { *(.data) }
   .bss  : { *(.bss)  }
}

不幸的是,它无法正常工作,因为它没有打印出预期的hello world。我认为链接命令一定有问题:

ld -melf_i386 -Ttext=0x0000 console.o hellword.o link.ld -o hellworld.elf`

如何正确链接 16 位模式的头文件?

当我直接在hellworld.c 中编写kprintf 函数时,它工作正常。我正在使用Linux Mint Cinnamon Version 18 64 bit 进行开发。

【问题讨论】:

  • 引导加载程序是否工作? qemu 是否不允许您附加调试器以便您可以单步执行代码?
  • 引导加载程序可以正常工作,因为正如我所说,当我在 hellworld.c 中给出函数定义时,它会打印出`“hello world”`
  • 你能不能告诉我调试的命令是什么
  • 如果您有多个 .o 文件,您如何知道您的 asm("jmp main"); 代码是在正确的位置生成的?如果你的 console.o 代码被添加到它前面怎么办。也许您的链接器脚本需要一个 `STARTUP(hellworld.o) 指令。
  • 如果我改变他们的位置或忽略asm("jmp main") 问题仍然存在也没关系

标签: gcc x86 header-files x86-16 osdev


【解决方案1】:

头文件根本不是问题。当您重组代码并将其拆分为多个对象时,它发现了您如何构建以及如何将jmp main 放入最终内核文件中的问题。

我创建了一个set of files,如果您希望测试完整的更改集以查看它们是否能解决您的问题,则可以进行下面讨论的所有调整。


虽然您显示了链接描述文件,但您实际上并没有使用它。在您的构建文件中,您有:

ld  -melf_i386 -Ttext=0x0000 console.o hellworld.o -o hellworld.elf

应该是:

ld  -melf_i386 -Tlink.ld console.o hellworld.o -o hellworld.elf

-c(编译但不链接)与GCC 一起使用时,不要将link.ld 指定为链接描述文件。当您调用 LD 时,可以在链接时指定链接描述文件。这一行:

gcc console.c -m16 -c -o console.o -nostdlib link.ld -ffreestanding

应该是:

gcc console.c -m16 -c -o console.o -nostdlib -ffreestanding

为了让此链接描述文件将jmp main 定位在输出内核文件中第一个的位置,您需要更改:

asm("jmp main");

收件人:

asm(".pushsection .startup\r\n"
    "jmp main\r\n"
    ".popsection\r\n");

.pushsection 将部分临时更改为 .startup,输出指令 jmp main,然后将带有 .popsection 的部分恢复为之前的任何内容。链接描述文件故意将任何内容放在.startup 部分中的任何其他内容之前。这确保了jmp main(或您放置在那里的任何其他指令)作为输出内核文件的第一条指令出现。 \r\n 可以替换为 ;(分号)。 \r\n 如果您曾让 GCC 生成程序集文件,则输出会更漂亮。


正如一个现已删除的问题的 cmets 中所述,您的内核文件超过了单个扇区的大小。当您没有链接描述文件时,默认的会将数据部分放在代码之后。您的代码重复了 hlt 指令,因此您的内核大于 1 个扇区(512 字节),并且您的引导加载程序仅读取带有 Int 13h/AH=2h 的单个扇区。

要纠正这个删除:

asm(".rept 512;"
    "hlt;"
    ".endr");

并将其替换为:

asm("cli;"
    "hlt;");

您应该注意,随着内核的增长,您需要调整在bootloader.asm 中读取的扇区数,以确保所有内核都加载到内存中。

我还建议让 QEMU 和其他虚拟机满意,您只需生成一个众所周知的磁盘映像大小并将引导加载程序和内核放入其中。而不是:

cat bootloader.bin hellworld.bin >disk.img

使用这个:

dd if=/dev/zero of=disk.img bs=1024 count=1440
dd if=bootloader.bin of=disk.img seek=0 conv=notrunc
dd if=hellworld.bin of=disk.img seek=1 conv=notrunc

第一个命令生成一个 1440kb 的零填充文件。这是 1.44MB 软盘的确切大小。第二个命令在第一个扇区中插入bootloader.bin,而不截断磁盘文件。第三条命令将内核文件放入从磁盘第二个扇区开始的磁盘映像中,而不截断磁盘映像。


我提供了一个稍微改进的linker script。它已被修改以删除链接器可能插入内核中的一些潜在的杂物,这些杂物不会有太大用处,并专门标识了一些部分,如.rodata(只读数据)等。

/*
*  link.ld
*/
OUTPUT_FORMAT(elf32-i386)
SECTIONS
{
   . = 0x0000;
   .text : { *(.startup); *(.text) }
   .data : { *(.data); *(.rodata) }
   .bss  : { *(COMMON); *(.bss) }

    /DISCARD/ : {
        *(.eh_frame);
        *(.comment);
        *(.note.gnu.build-id);
    }
}

其他评论

与您的问题无关,但可以删除此代码:

asm("mov $0x1000,%ax;"
    "mov %ax,%es;"
    "mov %ax,%ds");

您在bootloader.asm 中执行此操作,因此使用相同的值再次设置这些段寄存器不会有任何用处。


您可以改进extended assembly template,使用input constraints 通过寄存器EAX(AX) 和EBX 传递您需要的值(BX) 而不是在模板内编码移动。您的代码可能看起来像:

void kprintf(const char *string)
{
    while (*string)
    {
        asm("int $0x10"
        :
        :"a"((0x0e<<8) | *string++), /* AH = 0x0e, AL = char to print */
         "b"(0));                    /* BH = 0x00 page #
                                        BL = 0x00 unused in text mode */
    }
}

&lt;&lt;C bit shift left operator0x0e&lt;&lt;8 会将 0x0e 向左移动 8 位,即 0x0e00| 是按位 OR ,它有效地将要打印的字符放置在低 8 位中。然后该值由程序集模板通过input constraint"a" 传递到EAX 寄存器。

【讨论】:

  • 这是可能的,但过于复杂和成问题。听我的建议,您已经必须编写汇编代码才能在 bootloader.asm 中正确设置,我建议您在此处切换到保护模式。如果你在那里切换到保护模式,那么你可以简单地用-m32而不是-m16编译所有C代码,你必须从C 代码(因为它们不能在保护模式下工作)
  • @albert 正确,您需要一个 GDT 才能进入保护模式,您可以创建 GDT 并在 bootloader.asm 中使用汇编代码加载它。整个 GDT 放在 bootloader.asm 中,加载它的代码,设置段选择器和最终跳转到 C 代码的代码都是用汇编语言编写的(NASM)
  • @albert 不知道你在找什么。 NASM 支持 NASM 命令行上的有限汇编器列表选项-l bootloader.lst 会生成一个列表文件,但它不是很好。但是您可以注释掉(删除)ORG 0x7c00 并使用nasm -g -f elf32 -o bootloader.o bootloader.asmld -Ttext=0x7c00 -melf_i386 -o bootloader.elf bootloader.oobjcopy -O binary bootloader.elf bootloader.binobjdump -St -Mi8086 bootloader.elf &gt;bootloader.lst 组装链​​接。完成后bootloader.elf 应包含符号调试信息。
  • @albert 和bootloader.lst 将包含一个很好的文本文件,其中包含调试符号和源代码,bootloader.bin 将是您可以放置​​在引导扇区中的常规 bin 文件
【解决方案2】:

如果不知道您的bootloader.asm 做了什么,很难说,但是:

  1. 链接顺序一定是错误的;

    ld -melf_i386 -Ttext=0x0000 console.o hellworld.o -o hellworld.elf

    应该是:

    ld -melf_i386 -Ttext=0x0000 hellworld.o console.o -o hellworld.elf

    (编辑:我看到您有一个链接器脚本可以消除重新排列的需要,但您没有将它用于链接)。

  2. 我怀疑您的引导加载程序加载了单个扇区,而您的填充:

    asm(".rept 512;"
        "hlt;"
        ".endr");
    

    ... 防止加载来自其他目标文件的代码,因为它将hellword.o 填充到(超过)一个扇区的大小。

问题与使用头文件无关,这是因为您有两个编译单元成为单独的对象,并且链接时两者的组合大小大于一个扇区(512字节)。

【讨论】:

  • 是的,这是第二件事,谢谢我用cli; halt替换它
  • @albert 我非常有信心第一点也必须得到解决。引导加载程序必须在扇区的开头假设一个入口点,并且确保这一点的唯一方法是在链接时交换对象的顺序。
猜你喜欢
  • 1970-01-01
  • 2012-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-07
  • 2021-06-25
相关资源
最近更新 更多