将16位实模式代码链接到兼容Multiboot的ELF可执行文件时出现LD错误 [英] LD errors while linking 16-bit real mode code into a Multiboot compliant ELF executable

查看:236
本文介绍了将16位实模式代码链接到兼容Multiboot的ELF可执行文件时出现LD错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个兼容Multiboot的 ELF 可执行文件,其中包含我的32位内核.我的主要问题是在生成可执行文件时收到一系列链接器错误:

I'm writing a Multiboot compliant ELF executable containing my 32-bit kernel. My primary problem is that I'm receiving a series of linker errors while producing my executable:

重定位被截断以适合:R_386_16针对`.text'

relocation truncated to fit: R_386_16 against `.text'

下面的链接器脚本,代码和构建脚本

我决定尝试在自己的OS中实现VESA VBE图形.我在 OSDev论坛中找到了现有的VESA驱动程序,试图将其集成到我自己的操作系统中.我尝试将其添加到源目录中,并使用 NASM 进行了组装,然后使用 LD 将其链接到了最终的可执行文件中.我收到的具体错误是:

I have decided to try implementing VESA VBE graphics in my OS. I found an existing VESA driver in the OSDev forum and I tried to integrate it into my own OS. I tried adding it to my source directory, assembled it with NASM and linked it into a final executable with LD. The specific errors I received were:

vesa.asm:(.text+0x64): relocation truncated to fit: R_386_16 against `.text'
obj/vesa.o: In function `svga_mode':
vesa.asm:(.text+0x9d): relocation truncated to fit: R_386_16 against `.text'
vesa.asm:(.text+0xb5): relocation truncated to fit: R_386_16 against `.text'
obj/vesa.o: In function `done':
vesa.asm:(.text+0xc7): relocation truncated to fit: R_386_16 against `.text'

导致错误的行(依次)如下:

The lines causing the error (in order) are the following:

mov ax,[vid_mode]
mov cx,[vid_mode]
mov bx,[vid_mode]
jmp 0x8:pm1

我也用链接器错误"注释了这些行

I have also commented the lines with "Linker error"

这是文件(vesa.asm):

Here is the file (vesa.asm):

BITS    32

global do_vbe

save_idt: dd 0
          dw 0
save_esp: dd 0
vid_mode: dw 0

do_vbe:
cli
mov word [vid_mode],ax
mov [save_esp],esp
sidt [save_idt]
lidt [0x9000] ;; saved on bootup see loader.asm

jmp 0x18:pmode
pmode:
mov ax,0x20
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0
dec eax
mov cr0,eax
jmp 0:realmode1

[bits 16]
realmode1:
xor ax,ax
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov sp,0xf000
sti

;; first zero out the 256 byte memory for the return function from getmodeinfo
cld
;; ax is already zero! I just saved myself a few bytes!!
mov cx,129
mov di,0x5000
rep stosw

mov ax,[vid_mode] ; Linker error 
xor ax,0x13
jnz svga_mode

;; Ok, just a regular mode13
mov ax,0x13
int 0x10
;; we didnt actually get a Vidmode structure in 0x5000, so we 
;; fake it with the stuff the kernel actually uses
mov word [0x5001],0xDD     ; mode attribs, and my favorite cup size
mov word [0x5013],320      ; width
mov word [0x5015],200      ; height
mov byte [0x501a],8        ; bpp
mov byte [0x501c],1        ; memory model type = CGA
mov dword [0x5029],0xa0000 ; screen memory
jmp done

svga_mode:

mov ax,0x4f01 ; Get mode info function
mov cx,[vid_mode] ; Linker error 
or cx,0x4000 ; always try to use linear buffer
mov di,0x5001
int 0x10
mov [0x5000],ah
or ah,ah
jnz done

mov ax,0x4f02 ; Now actually set the mode
mov bx,[vid_mode] ; ; Linker error 
or bx,0x4000
int 0x10

done:
cli
mov eax,cr0
inc eax
mov cr0,eax
jmp 0x8:pm1 ; Linker error

[bits 32]
pm1:
mov eax,0x10
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov dword esp,[save_esp]
lidt [save_idt]
ret

主条目文件(entry.asm):

Main entry file (entry.asm):

extern kmain
extern do_vbe

; Multiboot Header
MBALIGN     equ 1<<0
MEMINFO     equ 1<<1
;VIDINFO        equ 1<<2
FLAGS       equ MBALIGN | MEMINFO; | VIDINFO
MAGIC       equ 0x1BADB002
CHECKSUM    equ -(MAGIC + FLAGS)

section .text
align 4

dd MAGIC
dd FLAGS
dd CHECKSUM
;dd 0
;dd 0
;dd 0
;dd 0
;dd 0
;dd 0
;dd 800
;dd 600
;dd 32

STACKSIZE equ 0x4000

global entry

entry:


    mov esp, stack+STACKSIZE
    push eax

    push ebx

    call do_vbe

    cli
    call kmain

    cli
    hlt
hang:
    jmp hang

section .bss
align 32
stack:
    resb STACKSIZE

我的链接描述文件:

OUTPUT_FORMAT(elf32-i386)
ENTRY(entry)
SECTIONS
 {
   . = 100000;
   .text : { *(.text) }
   .data : { *(.data) }
   .bss  : { *(.bss)  }
 }

我的构建脚本(注意我正在使用Cygwin):

My build script (Note I am using Cygwin):

cd src

for i in *.asm
do
    echo Assembling $i
    nasm -f elf32 -o "../obj/${i%.asm}.o" "$i"
done

for i in *.cpp
do
    echo Compiling $i
    i686-elf-g++ -c "$i" -o "../obj/${i%.cpp}.o" -I ../include --freestanding -fno-exceptions -fno-rtti -std=c++14 -Wno-write-strings
done

for i in *.S
do
    echo Compiling $i
    i686-elf-as -c "$i" -o "../obj/${i%.S}.o"
done

for i in *.c
do
    echo Compiling $i
    i686-elf-gcc -c "$i" -o "../obj/${i%.cpp}.o" -I ../include --freestanding
done

cd ..

i686-elf-ld -m elf_i386 -T linkscript.ld -o bin/kernel.sys obj/*.o 

如果有帮助,这里是目录结构:

If it helps here is the directory structure:

/src Source Files
/include Include files
/obj Object files
/bin Kernel Executable

推荐答案

链接器错误的原因

您收到的此错误:

Reason for the Linker Error

This error you received:

重定位被截断以适合:R_386_16针对`.text'

relocation truncated to fit: R_386_16 against `.text'

有效地告诉您,当链接器尝试解决.text部分中的这些重定位时,它不能这样做,因为它计算出的虚拟内存地址(VMA)不能容纳在16位指针中(_16).

is effectively telling you that when the linker attempted to resolve these relocations within the .text section that it was unable to do so because the Virtual Memory Addresses (VMA) it computed could not fit in a 16-bit pointer (_16).

如果在与 NASM 组装时使用-g -Fdwarf,则可以使用i686-elf-objdump -SDr -Mi8086 vesa.o之类的命令从 OBJDUMP 中产生更多可用的输出.

If you use -g -Fdwarf when assembling with NASM you can produce more usable output from OBJDUMP using a command like i686-elf-objdump -SDr -Mi8086 vesa.o.

  • -S输出源
  • -D用于拆卸,
  • -r显示重定位信息.
  • -S outputs source
  • -D is for disassembly,
  • -r shows the relocation information.

以下是我得到的输出(虽然略有不同,但此处提出的想法仍然适用):

The following is the output I get (it differs slightly but the ideas presented here still apply):

0000004a <realmode1>:

[bits 16]
realmode1:
...
mov ax,[vid_mode] ; Linker error
  63:   a1 0a 00                mov    ax,ds:0xa
                        64: R_386_16    .text
...
mov cx,[vid_mode] ; Linker error
  9a:   8b 0e 0a 00             mov    cx,WORD PTR ds:0xa
                        9c: R_386_16    .text
...
mov bx,[vid_mode] ; ; Linker error
  b2:   8b 1e 0a 00             mov    bx,WORD PTR ds:0xa
                        b4: R_386_16    .text
...
jmp 0x8:pm1 ; Linker error
  c5:   ea ca 00 08 00          jmp    0x8:0xca
                        c6: R_386_16    .text

我删除了对简短性没有任何影响的信息,并在输出中将其替换为....有一个[bits 16]伪指令可以强制将所有内存地址设置为16位,除非被覆盖.例如,c6: R_386_16 .text表示在偏移量(0xc6)处有一个重定位,该重定位是出现在.text节中的16位指针.请记住这一点.现在查看链接描述文件:

I have removed the information that wasn't of any consequence for brevity and replaced it with ... in the output. There is a [bits 16] directive that forces all memory addresses to be 16-bit unless overridden. As an example c6: R_386_16 .text means there is a relocation at offset (0xc6) that is a 16-bit pointer appearing in the .text section. Keep this in mind. Now review the linker script:

. = 100000;
.text : { *(.text) }
.data : { *(.data) }
.bss  : { *(.bss)  }

VMA(原始)为0x100000.在这种情况下,这实际上是所有代码和数据的起点.最终可执行文件中生成的所有地址都将超过0xFFFF,这是可以容纳16位指针的最大值.这就是链接器抱怨的原因.

The VMA (origin) is 0x100000. This is effectively the origin point for all the code and data in this case. All the addresses generated in the final executable will be over 0xFFFF which is the maximum value that can fit in a 16-bit pointer. This is the reason the linker is complaining.

您可以通过在括号[]之间的标签名称之前指定 DWORD 来覆盖默认地址和操作数大小.可以通过在操作数之前指定 DWORD 来对绝对的32位 FAR JMP 进行编码.这些行:

You can override the default address and operand sizes by specifying DWORD before the label name between the brackets [ and ]. An absolute 32-bit FAR JMP can be encoded by specifying DWORD before the operand. These lines:

mov ax,[vid_mode]
mov cx,[vid_mode]
mov bx,[vid_mode]
jmp 0x8:pm1

将成为:

mov ax,[dword vid_mode]
mov cx,[dword vid_mode]
mov bx,[dword vid_mode]
jmp dword 0x8:pm1

如果您汇编修改后的代码并按照上述方法使用 OBJDUMP ,则会得到以下输出(为简便起见,请剪切以下内容):

If you assemble the revised code and use OBJDUMP as discussed above you get this output (cut for brevity):

mov ax,[dword vid_mode] ; Linker error
  63:   67 a1 0a 00 00 00       addr32 mov ax,ds:0xa
                        65: R_386_32    .text
...
mov cx,[dword vid_mode] ; Linker error
  9d:   67 8b 0d 0a 00 00 00    addr32 mov cx,WORD PTR ds:0xa
                        a0: R_386_32    .text
...
mov bx,[dword vid_mode] ; ; Linker error
  b8:   67 8b 1d 0a 00 00 00    addr32 mov bx,WORD PTR ds:0xa
                        bb: R_386_32    .text
...
jmp dword 0x8:pm1 ; Linker error
  ce:   66 ea d6 00 00 00 08    jmp    0x8:0xd6
  d5:   00
                        d0: R_386_32    .text

现在指令中添加了0x660x67前缀,并且地址在指令中占据4个字节.每个重定位的类型都是R_386_32,它告诉链接器要重定位的地址是32位宽.

The instructions now have a 0x66 and 0x67 prefix added to them and the addresses take 4 bytes in the instruction. Each of the relocations are of type R_386_32 which tells the linker that the addresses to relocate are 32-bits wide.

尽管上一节中的更改将消除链接期间的警告,但当运行时可能无法按预期运行(包括崩溃).在80386+上,您可以生成使用32位地址存储数据的16位实模式代码,但必须将CPU置于允许此类访问的模式下.允许通过 DS 段访问值大于0xFFFF的32位指针的模式称为 OSDev Wiki 具有一些可用作此类支持基础的代码.假设尚未重新映射PIC,并且它们处于初始配置,那么按需实现虚幻模式的通常方法是用执行以下操作的方法替换0x0d中断处理程序:

Although the changes in the previous section will eliminate the warnings during linking, when run things may not work as expected (including crashes). On an 80386+ you can generate 16-bit real mode code that uses 32-bit addresses for the data but the CPU has to be put into a mode that allows such access. The mode that allows 32-bit pointers accessed via the DS segment with values above 0xFFFF is called Unreal Mode. OSDev Wiki has some code that could be used as a basis for such support. Assuming the PICs haven't been remapped and are in their initial configuration then the usual way to implement on demand Unreal Mode is to replace the 0x0d Interrupt handler with something that does:

  1. 查询 PIC1 OCW3,以查看是否正在维修IRQ5或是否出现了常规保护错误.在没有PIC重新映射的情况下,#GP故障和IRQ5指向同一中断向量,因此必须对其进行区分.
  2. 如果设置了IRQ5 ISR,则调用先前保存的中断处理程序(链接).至此,我们都完成了.
  3. 如果未设置IRQ5 ISR,则由于常规保护故障而调用了0x0d中断.假定故障是由于无效的数据访问造成的.
  4. 切换到保护模式,并使用包含16位数据描述符的GDT,该描述符的基数为0,限制为0xffffffff.使用相应的选择器设置 ES DS .
  5. 离开保护模式
  6. 从中断处理程序中返回.
  1. Query PIC1 OCW3 to see if IRQ5 is being serviced or whether there was a General Protection fault. Without PIC remapping #GP fault and IRQ5 point at the same interrupt vector so one has to distinguish between them.
  2. If the IRQ5 ISR is set then call the previously saved interrupt handler (chaining). At this point we are all done.
  3. If IRQ5 ISR is not set then 0x0d interrupt was called because of a general protection fault. Assume the fault was because of an invalid data access.
  4. Switch into protected mode and use a GDT containing a 16-bit data descriptor that has a base of 0 and a limit of 0xffffffff. Set ES and DS using the corresponding selector.
  5. Leave protected mode
  6. Return from interrupt handler.

如果已重新映射PIC1,以便不与x86异常处理中断(从int 0x08到int 0x0f)冲突,则不再执行步骤1,2,3. 重新映射PICs 以避免这种冲突在x86 OS设计中很常见.问题中的代码不进行任何PIC重新映射.

If PIC1 has been remapped so as to not conflict with the x86 exception handling interrupts (int 0x08 to int 0x0f) then steps 1,2,3 no longer apply. Remapping the PICs to avoid this conflict is common place in x86 OS design. The code in the question doesn't do any PIC remapping.

如果您想在VM8086任务中使用代码而不是进入实模式,则此机制将不起作用.

This mechanism won't work if you want to ever use the code in a VM8086 task rather than entering real mode.

DOS的HIMEM.SYS在1980年代做了类似的事情,您可以在

DOS's HIMEM.SYS did something similar in the 1980s and you can find a discussion about that in this article if you are interested.

注意:尽管我对使用虚幻模式进行了一般描述,但我不建议您使用此方法.它需要对实模式,保护模式和中断处理有更广泛的了解.

Note : Although I give a general description of using Unreal Mode, I don't recommend this method. It requires more extensive knowledge of real mode, protected mode, interrupt handling.

不是使用大于0xFFFF的32位数据指针,而是确保处理器处于虚幻模式,而是有一种更容易理解的解决方案.一种这样的解决方案是将实模式代码和数据从Multiboot加载程序物理加载到0x100000以上的RAM中复制到实模式中断向量表(IVT)上方的前64KB内存中.这使您可以继续使用16位指针,因为可以使用16位指针(0x0000至0xFFFF)寻址前64KB内存.如果需要,该32位代码仍将能够访问实模式数据.

Rather than using 32-bit data pointers greater than 0xFFFF, and ensuring that the processor is in unreal mode there is a solution that may be easier to understand. One such solution is to copy the real mode code and data from where the Multiboot loader physically loaded into RAM above 0x100000 to the first 64KB of memory memory just above the real mode interrupt vector table (IVT). This allows you to continue using 16-bit pointers because the first 64KB of memory is addressable with a 16-bit pointer (0x0000 to 0xFFFF). The 32-bit code will still be able to access the real mode data if needed.

为此,您将必须创建更复杂的GNU LD

To do this you will have to create a more complex GNU LD linker script (link.ld) that uses a Virtual Memory Address (origin point) in lower memory. Address 0x01000 is a good choice. The Multiboot header will still have to be present at the beginning of the ELF executable.

必须克服的一个问题是,Multiboot加载程序会将代码和数据读入0x100000以上的内存中.必须先手动将16位实模式代码和数据复制到地址0x01000,然后才能使用实模式代码.链接描述文件可以帮助生成符号来计算此类副本的开始和结束地址.

One problem that has to be overcome is that the Multiboot loader will read the code and data into memory above 0x100000. One has to manually copy the 16-bit real mode code and data to address 0x01000 before the real mode code can be used. The linker script can help generate symbols to compute the start and end addresses for such a copy.

请参阅上一节中的代码,以了解执行此操作的链接描述文件link.ld和用于执行复制操作的kernel.c文件.

See the code in the last section for a linker script link.ld that does just that, and a kernel.c file that does the copy.

使用正确调整的VESA代码,您尝试执行的操作应该会起作用.

With properly adjusted VESA code what you are trying to do should work.

  • VESA代码依赖于硬编码地址
  • 在设计时没有考虑到Multiboot,因为它假定内核将被手动加载到特定位置的内存中(< 64KB),并且某些地址在被调用之前已经包含特定数据.
  • 代码未遵循 CDECL 调用约定,因此不能直接从 C 代码中调用.
  • 存在一个错误,该错误将32位代码置于[bits 16]指令下.
  • 代码未显示所需的 GDT 表,但可以从代码中推断出至少需要 5个描述符> GDT 按特定顺序排列.
  • The VESA code relies on hard coded addresses
  • Was not designed with Multiboot in mind as it was assumed the kernel would be manually loaded into memory (<64KB) at a specific place and that certain addresses already contained specific data before being called.
  • The code doesn't follow the CDECL calling convention thus it isn't callable from C code directly.
  • There is a bug that placed 32-bit code under a [bits 16] directive.
  • The code doesn't show the GDT table that was needed but it can be inferred from the code that there needed to be at least 5 descriptors in a GDT in a specific order.
  1. 空描述符
  2. 32位代码段(基数为0,限制为0xffffffff).选择器0x08
  3. 32位数据段(基数为0,限制为0xffffffff).选择器0x10
  4. 16位代码段(基数为0,限制至少为0xffff).选择器0x18
  5. 16位数据段(基数为0,限制至少为0xffff).选择器0x20

作者有以下评论:

在启动时,使用sidt指令将实模式IDT保存在已知位置(我的位置是0x9000),并且不要覆盖内存中的地址0-0x500.它还假定您使用8和16作为PMode中代码和数据的段寄存器.它将功能4f01的结果存储在0x5000,并自动设置第13位(使用帧缓冲区)

On boot up save the real mode IDT at a known place (mine was at 0x9000) using the sidt instruction, and dont overwrite address 0-0x500 in memory. It also assumes that you are using 8 and 16 as segment registers for code and data in PMode. It stores the result of function 4f01 at 0x5000, and automatically sets bit 13 (use framebuffer)


完整示例

以下代码是上述建议的完整实现.使用链接描述文件并生成实模式代码和数据,并将其放置在0x1000开始.该代码使用 C 设置具有32位和16位代码和数据段的正确的 GDT ,并将实模式代码从0x100000以上复制到0x1000.它还修复了以前在VESA驱动程序代码中确定的其他问题.要进行测试,它会切换到视频模式0x13(320x200x256),并将VGA调色板的一部分一次绘制到显示器32位.


A Complete Example

The following code is a complete implementation of what has been suggested above. Use a linker script and generate real mode code and data and place it starting at 0x1000. The code uses C to set up a proper GDT with 32 and 16-bit Code and Data segments, copies the real mode code from above 0x100000 down to 0x1000. It also fixes the other issues previously identified in the VESA driver code. To test it switches in to video mode 0x13 (320x200x256) and paints part of the VGA color palette to the display 32 bits at a time.

link.ld:

OUTPUT_FORMAT("elf32-i386");
ENTRY(mbentry);

/* Multiboot spec uses 0x00100000 as a base */
PHYS_BASE = 0x00100000;
REAL_BASE = 0x00001000;

SECTIONS
{
    . = PHYS_BASE;

    /* Place the multiboot record first */
    .multiboot : {
        *(.multiboot);
    }

    /* This is the tricky part. The LMA (load memory address) is the
     * memory location the code/data is read into memory by the
     * multiboot loader. The LMA is after the colon. We want to tell
     * the linker that the code/data in this section was loaded into
     * RAM in the memory area above 0x100000. On the other hand the
     * VMA (virtual memory address) specified before the colon acts
     * like an ORG directive. The VMA tells the linker to resolve all
     * subsequent code starting relative to the specified VMA. The
     * VMA in this case is REAL_BASE which we defined as 0x1000.
     * 0x1000 is 4KB page aligned (useful if you ever use paging) and
     * resides above the end of the interrupt table and the
     * BIOS Data Area (BDA)
     */

    __physreal_diff = . - REAL_BASE;
    .realmode REAL_BASE : AT(ADDR(.realmode) + __physreal_diff) {
        /* The __realmode* values can be used by code to copy
         * the code/data from where it was placed in RAM by the
         * multiboot loader into lower memory at REAL_BASE
         *
         * . (period) is the current VMA */
        __realmode_vma_start = .;

        /* LOADADDR is the LMA of the specified section */
        __realmode_lma_start = LOADADDR(.realmode);
        *(.text.realmode);
        *(.data.realmode);
    }
    . = ALIGN(4);
    __realmode_vma_end = .;
    __realmode_secsize   = ((__realmode_vma_end)-(__realmode_vma_start));
    __realmode_secsize_l = __realmode_secsize>>2;
    __realmode_lma_end   = __realmode_vma_start + __physreal_diff + __realmode_secsize;

    /* . (period) is the current VMA. We set it to the value that would
     * have been generated had we not changed the VMA in the previous
     * section. The .text section also specified the LMA = VMA with
     * AT(ADDR(.text))
     */
    . += __physreal_diff;
    .text ALIGN(4K): AT(ADDR(.text)) {
        *(.text);
    }

    /* From this point the linker script is typical */
    .data ALIGN(4K) : {
        *(.data);
    }

    .data ALIGN(4K) : {
        *(.rodata);
    }

    /* We want to avoid this section being placed in low memory */
    .eh_frame : {
        *(.eh_frame*);
    }

    .bss ALIGN(4K): {
        *(COMMON);
        *(.bss)
    }

    /* The .note.gnu.build-id section will usually be placed at the beginning
     * of the ELF object. We discard it (if it is present) so that the
     * multiboot header is placed as early as possible in the file. The
     * multiboot header must appear in the first 8K and be on a 4 byte
     * aligned offset per the multiboot spec.
     */
    /DISCARD/ : {
        *(.note.gnu.build-id);
        *(.comment);
    }
}

gdt.inc:

CODE32SEL equ 0x08
DATA32SEL equ 0x10
CODE16SEL equ 0x18
DATA16SEL equ 0x20

vesadrv.asm:

; Video driver code - switches the CPU back into real mode
; Then executes an int 0x10 instruction

%include "gdt.inc"

global do_vbe

bits 16
section .data.realmode
save_idt: dw 0
          dd 0
save_esp: dd 0
vid_mode: dw 0
real_ivt: dw (256 * 4) - 1      ; Realmode IVT has 256 CS:IP pairs
          dd 0                  ; Realmode IVT physical address at address 0x00000

align 4
mode_info:TIMES 129 dw 0        ; Buffer to store mode info from Int 10h/ax=4f01h
                                ; Plus additional bytes for the return status byte
                                ; at beginning of buffer

bits 32
section .text
do_vbe:
    mov ax, [esp+4]             ; Retrieve videomode passed on stack
    pushad                      ; Save all the registers
    pushfd                      ; Save the flags (including Interrupt flag)
    cli
    mov word [vid_mode],ax
    mov [save_esp],esp
    sidt [save_idt]
    lidt [real_ivt]             ; We use a real ivt that points to the
                                ; physical address 0x00000 at the bottom of
                                ; memory. The IVT in real mode is 256*4 bytes
                                ; and runs from physical address 0x00000 to
                                ; 0x00400
    jmp CODE16SEL:pmode16

bits 16
section .text.realmode
pmode16:
    mov ax,DATA16SEL
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax
    mov eax,cr0
    dec eax
    mov cr0,eax
    jmp 0:realmode1

realmode1:
    ; Sets real mode stack to grow down from 0x1000:0xFFFF
    mov ax,0x1000
    mov ss,ax
    xor sp,sp

    xor ax,ax
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax

    ; first zero out the 258 byte memory for the return function from getmodeinfo
    cld
    mov cx,(258/2)              ; 128 words + 1 word for the status return byte
    mov di,mode_info
    rep stosw

    mov ax,[vid_mode]
    xor ax,0x13
    jnz svga_mode

    ; Just a regular mode13
    mov ax,0x13
    int 0x10

    ; Fake a video mode structure with the stuff the kernel actually uses
    mov di, mode_info
    mov word [di+0x01],0xDD     ; mode attribs
    mov word [di+0x13],320      ; width
    mov word [di+0x15],200      ; height
    mov byte [di+0x1a],8        ; bpp
    mov byte [di+0x1c],1        ; memory model type = CGA
    mov dword [di+0x29],0xa0000 ; screen memory
    jmp done

svga_mode:
    mov ax,0x4f01               ; Get mode info function
    mov cx,[vid_mode]
    or cx,0x4000                ; always try to use linear buffer
    mov di,mode_info+0x01
    int 0x10
    mov [mode_info],ah
    or ah,ah
    jnz done

    mov ax,0x4f02               ; Now actually set the mode
    mov bx,[vid_mode]
    or bx,0x4000
    int 0x10

done:
    cli
    mov eax,cr0
    inc eax
    mov cr0,eax
    jmp dword CODE32SEL:pm1     ; To FAR JMP to address > 0xFFFF we need
                                ; to specify DWORD to allow a 32-bit address
                                ; in the offset portion. When this JMP is
                                ; complete CS will be CODE32SEL and processor
                                ; will be in 32-bit protected mode

bits 32
section .text
pm1:
    mov eax,DATA32SEL
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax
    mov dword esp,[save_esp]
    lidt [save_idt]
    popfd                       ; Restore flags (including Interrupt flag)
    popad                       ; Restore registers

    mov eax, mode_info          ; Return pointer to mode_info structure
    ret

vesadrv.h:

#ifndef VESADRV_H
#define VESADRV_H

#include <stdint.h>

extern struct mode_info_t * do_vbe (const uint8_t video_mode);

struct mode_info_t {
    uint8_t status; /* Return value from Int 10/ax=4f01 */

    /* Rest of structure from OSDev Wiki
       http://wiki.osdev.org/VESA_Video_Modes#VESA_Functions
    */
    uint16_t attributes;
    uint8_t winA,winB;
    uint16_t granularity;
    uint16_t winsize;
    uint16_t segmentA, segmentB;

    /* Real mode FAR Pointer.  Physical address
     * computed as (segment<<4)+offset
     */
    uint16_t realFctPtr_offset; /* FAR Pointer offset */
    uint16_t realFctPtr_segment;/* FAR Pointer segment */

    uint16_t pitch; /* bytes per scanline */

    uint16_t Xres, Yres;
    uint8_t Wchar, Ychar, planes, bpp, banks;
    uint8_t memory_model, bank_size, image_pages;
    uint8_t reserved0;

    uint8_t red_mask, red_position;
    uint8_t green_mask, green_position;
    uint8_t blue_mask, blue_position;
    uint8_t rsv_mask, rsv_position;
    uint8_t directcolor_attributes;

    volatile void * physbase;  /* LFB (Linear Framebuffer) address */
    uint32_t reserved1;
    uint16_t reserved2;
} __attribute__((packed));

#endif

gdt.h:

#ifndef GDT_H
#define GDT_H

#include <stdint.h>
#include <stdbool.h>

typedef struct
{
        unsigned short limit_low;
        unsigned short base_low;
        unsigned char base_middle;
        unsigned char access;
        unsigned char flags;
        unsigned char base_high;
} __attribute__((packed)) gdt_desc_t;

typedef struct {
    uint16_t limit;
    gdt_desc_t *gdt;
} __attribute__((packed)) gdtr_t;

extern void gdt_set_gate(gdt_desc_t gdt[], const int num, const uint32_t base,
                         const uint32_t limit, const uint8_t access,
                         const uint8_t flags);

static inline void gdt_load(gdtr_t * const gdtr, const uint16_t codesel,
                            const uint16_t datasel, const bool flush)
{
    /* Dummy variable used as a fake dependency to avoid optimization
     * reordering of the inline assembly. The flush (if requested) must
     * always come after we set the GDT, not before */
    int dummy;

    /* load the GDT register */
    __asm__ __volatile__ ("lgdt %[gdtr]"
                          : "=X"(dummy)
                          : [gdtr]"m"(*gdtr),
                          /* Dummy constraint to ensure what gdtr->gdt points at is fully
                           * realized into memory before we issue LGDT instruction */
                            "m"(*(const gdt_desc_t (*)[]) gdtr->gdt));

    /* This flushes the selector registers to ensure the new
     * descriptors are used. */
    if (flush) {
        /* The indirect absolute jump is because we can't
         * assume that codesel is an immediate value
         * as it may be passed in a register. We build a
         * far pointer in memory and indirectly jump through
         * that pointer. This explicitly sets CS selector */
        __asm__  __volatile__ (
                 "pushl %[codesel]\n\t"
                 "pushl $1f\n\t"
                 "ljmpl *(%%esp)\n"
                 "1:\n\t"
                 "add $8, %%esp\n\t"
                 "mov %[datasel], %%ds\n\t"
                 "mov %[datasel], %%es\n\t"
                 "mov %[datasel], %%ss\n\t"
                 "mov %[datasel], %%fs\n\t"
                 "mov %[datasel], %%gs"
                 : /* No outputs */
                 : "X"(dummy),
                   [datasel]"r"(datasel),
                   [codesel]"g"((uint32_t)codesel));
    }
    return;
}
#endif

gdt.c:

#include "gdt.h"

/* Setup a descriptor in the Global Descriptor Table */
void gdt_set_gate(gdt_desc_t gdt[], const int num, const uint32_t base,
                  const uint32_t limit, const uint8_t access,
                  const uint8_t flags)
{
        /* Setup the descriptor base access */
        gdt[num].base_low = (base & 0xFFFF);
        gdt[num].base_middle = (base >> 16) & 0xFF;
        gdt[num].base_high = (base >> 24) & 0xFF;

        /* Setup the descriptor limits */
        gdt[num].limit_low = (limit & 0xFFFF);
        gdt[num].flags = ((limit >> 16) & 0x0F);

        /* Finally, set up the flags and access byte */
        gdt[num].flags |= (flags << 4);
        gdt[num].access = access;
}

multiboot.asm:

%include "gdt.inc"

STACKSIZE equ 0x4000

bits 32
global mbentry

extern kmain

; Multiboot Header
section .multiboot
MBALIGN     equ 1<<0
MEMINFO     equ 1<<1
VIDINFO     equ 0<<2
FLAGS       equ MBALIGN | MEMINFO | VIDINFO
MAGIC       equ 0x1BADB002
CHECKSUM    equ -(MAGIC + FLAGS)

mb_hdr:
    dd MAGIC
    dd FLAGS
    dd CHECKSUM

section .text
mbentry:
    cli
    cld
    mov esp, stack_top

    ; EAX = magic number. Should be 0x2badb002
    ; EBX = pointer to multiboot_info
    ; Pass as parameters right to left
    push eax
    push ebx
    call kmain

    ; Infinite loop to end program
    cli
endloop:
    hlt
    jmp endloop

section .bss
align 32
stack:
    resb STACKSIZE
stack_top:

kernel.c:

#include <stdint.h>
#include <stdbool.h>
#include "vesadrv.h"
#include "gdt.h"

#define CODE32SEL 0x08
#define DATA32SEL 0x10
#define CODE16SEL 0x18
#define DATA16SEL 0x20
#define NUM_GDT_ENTRIES 5

/* You can get this structure from GRUB's multiboot.h if needed
 * https://www.gnu.org/software/grub/manual/multiboot/html_node/multiboot_002eh.html
 */
struct multiboot_info;

/* Values made available by the linker script */
extern void *__realmode_lma_start;
extern void *__realmode_lma_end;
extern void *__realmode_vma_start;

/* Pointer to graphics memory.Mark as volatile since
 * video memory is memory mapped IO. Certain optimization
 * should not be performed. */
volatile uint32_t * video_gfx_ptr;

/* GDT descriptor table */
gdt_desc_t gdt[NUM_GDT_ENTRIES];

/* Copy the code and data in the realmode section down into the lower
 * 64kb of memory @ 0x00001000. */
static void realmode_setup (void)
{
    /* Each of these __realmode* values is generated by the linker script */
    uint32_t *src_addr = (uint32_t *)&__realmode_lma_start;
    uint32_t *dst_addr = (uint32_t *)&__realmode_vma_start;
    uint32_t *src_end  = (uint32_t *)&__realmode_lma_end;

    /* Copy a DWORD at a time from source to destination */
    while (src_addr < src_end)
        *dst_addr++ = *src_addr++;
}

void gdt_setup (gdt_desc_t gdt[], const int numdesc)
{
    gdtr_t gdtr = { sizeof(gdt_desc_t)*numdesc-1, gdt };

    /* Null descriptor */
    gdt_set_gate(gdt, 0, 0x00000000, 0x00000000, 0x00, 0x0);
    /* 32-bit Code descriptor, flat 4gb */
    gdt_set_gate(gdt, 1, 0x00000000, 0xffffffff, 0x9A, 0xC);
    /* 32-bit Data descriptor, flat 4gb */
    gdt_set_gate(gdt, 2, 0x00000000, 0xffffffff, 0x92, 0xC);
    /* 16-bit Code descriptor, limit 0xffff bytes */
    gdt_set_gate(gdt, 3, 0x00000000, 0x0000ffff, 0x9A, 0x0);
    /* 16-bit Data descriptor, limit 0xffffffff bytes */
    gdt_set_gate(gdt, 4, 0x00000000, 0xffffffff, 0x92, 0x8);

    /* Load global decriptor table, and flush the selectors */
    gdt_load(&gdtr, CODE32SEL, DATA32SEL, true);
}

int kmain(struct multiboot_info *mb_info, const uint32_t magicnum)
{
    struct mode_info_t *pMI;
    uint32_t pixel_colors = 0;

    /* Quiet compiler about unused variables */
    (void) mb_info;
    (void) magicnum;

    /* Setup the GDT */
    gdt_setup(gdt, NUM_GDT_ENTRIES);

    /* Setup real mode code and data */
    realmode_setup();

    /* Switch to video mode 0x13 (320x200x256)
     * The physical address of the mode 13 video memory is
     * 0xa0000 */
    pMI = do_vbe(0x13);
    video_gfx_ptr = pMI->physbase;

    /* Display part of the VGA palette as a test pattern */
    for (int pixelpos = 0; pixelpos < (320*200); pixelpos++) {
        if ((pixel_colors & 0xff) == (320/4))
            pixel_colors = 0;
        pixel_colors += 0x01010101;
        video_gfx_ptr[pixelpos] = pixel_colors;
    }
    return 0;
}

一组简单的命令,用于将上面的代码汇编/编译/链接到名为multiboot.elf ELF 可执行文件中:

A simple set of commands to assemble/compile/link the code above into an ELF executable called multiboot.elf:

nasm -f elf32 -g -F dwarf -o multiboot.o multiboot.asm
nasm -f elf32 -g -F dwarf -o vesadrv.o vesadrv.asm
i686-elf-gcc -std=c99 -g -m32 -O3 -c -fno-exceptions -nostdlib -ffreestanding -Wall -Wextra -o kernel.o kernel.c
i686-elf-gcc -std=c99 -g -m32 -O3 -c -fno-exceptions -nostdlib -ffreestanding -Wall -Wextra -pedantic -o gdt.o gdt.c -lgcc
i686-elf-gcc -m32 -Tlink.ld -ffreestanding -nostdlib -o multiboot.elf multiboot.o kernel.o gdt.o vesadrv.o -lgcc


您可以在我的网站上找到上述代码的副本. .当我在 QEMU 中运行内核时,这就是我看到的:


You can find a copy of the code above on my site. When I run the kernel in QEMU this is what I see:

这篇关于将16位实模式代码链接到兼容Multiboot的ELF可执行文件时出现LD错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆