【问题标题】:How to convert ld -Ttext option to a linker script?如何将 ld -Ttext 选项转换为链接描述文件?
【发布时间】:2019-07-31 19:10:28
【问题描述】:

我找到了有关创建操作系统的教程,我正在尝试将 make 文件中的链接部分转换为链接器脚本。

这里是教程:https://github.com/ghaiklor/ghaiklor-os-gcc

这是制作文件:

SOURCES = $(shell find cpu drivers include kernel libc -name '*.c')
HEADERS = $(shell find cpu drivers include kernel libc -name '*.h')
OBJ = ${SOURCES:.c=.o cpu/interrupt.o}

ASM = nasm 
CC = gcc  
LD = ld -m elf_i386 
CFLAGS = -g -ffreestanding -Wall -Wextra -fno-exceptions -m32 -std=c11 -fno-pie

ifeq ($(shell uname -s),Darwin)     
    CC = i386-elf-gcc   
    LD = i386-elf-ld 
endif

all: os-image.bin

run: all    
    qemu-system-i386 os-image.bin

clean:  
    rm -rf *.dis *.o *.elf  
    rm -rf *.bin os-image.bin boot/*.bin kernel/*.bin   
    rm -rf boot/*.o cpu/*.o drivers/*.o kernel/*.o libc/*.o

os-image.bin: boot/boot.bin kernel/kernel.bin   
    cat $^ > os-image.bin

boot/boot.bin: boot/boot.asm    
    ${ASM} $< -f bin -o $@

kernel/kernel.bin: boot/kernel_entry.o ${OBJ}   
     ${LD} -o $@ -Ttext 0x1000 $^ --oformat binary

# ${LD} -o $@ -Tlinker.ld
# ${LD} -o $@ -Ttext 0x1000 $^ --oformat binary

%.o: %.c ${HEADERS}     ${CC} ${CFLAGS} -c $< -o $@

%.o: %.asm  ${ASM} $< -f elf -o $@

%.bin: %.asm    ${ASM} $< -f bin -o $@

这是我为这个 make 文件的链接阶段创建链接器脚本的尝试:

ENTRY(_start)
OUTPUT_FORMAT(binary)
INPUT(boot/kernel_entry.o cpu/idt.o cpu/interrupt.o cpu/isr.o cpu/ports.o cpu/timer.o drivers/keyboard.o drivers/screen.o libc/mem.o libc/string.o libc/common.o kernel/kernel.o)
OUTPUT(kernel/kernel.bin)
SECTIONS
{
    . = 0x1000;
    .text : { *(.text) }
    end = .; _end = .; __end = .;
}

boot/kernel_entry.o 没有 .data 或 .bss,这就是我没有将它们包含在链接描述文件中的原因。我知道 -Ttext 0x1000 是假设加载 .text 部分的位置,这就是为什么我将计数器设置为从 0x1000 的地址开始。当我使用 makefile ${LD} -o $@ -Tlinker.ld 中的新链接命令运行系统时,系统无法正常工作,所以我做错了。我尝试添加简单的 .data 和 .bss 部分以及所有其他类型的内容,但仍然无法通过链接脚本使该内容正常工作。任何帮助都会很棒。

谢谢。

【问题讨论】:

  • 对有效的和无效的运行 objdump -h 并进行比较。 (如果您需要帮助解释它,可以将该输出添加到问题中。)
  • 系统在什么情况下不能正常工作?你得到一个磁盘错误,shell没有出现吗?
  • 我担心的一个问题是您的 boot.asm 如何读取内核扇区。它将值 15 硬编码为要读取的扇区数。如果您生成的 kernel.bin 大于 7680 (15*512) 字节,您可能会遇到并非所有内容都加载到内存中的问题。如果您有两个扇区,那么 QEMU 可能会出现读取错误,因为使用硬盘驱动器仿真时,它可能不热衷于读取磁盘映像中可能不存在的扇区。
  • 你说 There is no .data or .bss for the boot/kernel_entry.o 。问题是 kernel_entry.o 可能没有 .data 或 .bss 部分,但其他目标文件可能有它们。但即便如此,尽管没有列出这些部分,但它们仍会在链接描述文件正在处理的部分之后被处理和添加。我也不建议在链接描述文件中使用 INPUT 行。我倾向于删除它并通过 Makefile 中的 LD 传递所有对象,并改用${LD} -o $@ -Tlinker.ld $^
  • 我还建议不要在链接器脚本中使用OUTPUT,并允许链接器命令行指定输出文件。这一切都让事情变得更加通用。

标签: x86 nasm ld osdev linker-scripts


【解决方案1】:

您链接到的教程是针对 64 位代码的。您的 Makefile 和后续的 cmets 建议您尝试将其修改为作为 32 位内核进行汇编/编译/运行。我已将下面讨论的修订项目的副本放在我的wesbite 上。压缩包可以从here下载。

在将内核加载到内存时,您的教程相当愚蠢。它要求您知道要读取多少个扇区并对值进行硬编码。弄错这个可能会导致异常行为。无需对值进行硬编码,您可以让 NASM 包含 kernel.bin inside boot.bin,以便引导加载程序可以计算在组装时要读取的扇区数。并非所有仿真器和真机都支持多轨读取,因此我将修改引导加载程序以使用 LBA 寻址一次读取一个扇区。要了解有关 CHS 到 LBA 转换计算的更多信息,您可以查看我在该主题上的另一个 Stackoveflow answer。修改boot/boot.asm为:

STAGE2_ABS_ADDR  equ 0x01000
STAGE2_RUN_SEG   equ 0x0000
STAGE2_RUN_OFS   equ STAGE2_ABS_ADDR
                                ; Run stage2 with segment of 0x0000 and offset of 0x1000

STAGE2_LOAD_SEG  equ STAGE2_ABS_ADDR>>4
                                ; Segment to start reading Stage2 into
                                ;     right after bootloader

STAGE2_LBA_START equ 1          ; Logical Block Address(LBA) Stage2 starts on
                                ;     LBA 1 = sector after boot sector
STAGE2_LBA_END   equ STAGE2_LBA_START + NUM_STAGE2_SECTORS
                                ; Logical Block Address(LBA) Stage2 ends at
DISK_RETRIES     equ 3          ; Number of times to retry on disk error

bits 16
ORG 0x7c00

; Include a BPB (1.44MB floppy with FAT12) to be more comaptible with USB floppy media
; %include "bpb.inc"

boot_start:
    xor ax, ax                  ; DS=SS=ES=0 for stage2 loading
    mov ds, ax
    mov ss, ax                  ; Stack at 0x0000:0x0000
                                ;     (grows down fromtopof1st 64KiB segment)
    mov sp, 0x0000
    cld                         ; Set string instructions to use forward movement

    ; Read Stage2 1 sector at a time until stage2 is completely loaded
load_stage2:
    mov [bootDevice], dl        ; Save boot drive
    mov bx, MSG_LOAD_KERNEL
    call print_string

    mov di, STAGE2_LOAD_SEG     ; DI = Current segment to read into
    mov si, STAGE2_LBA_START    ; SI = LBA that stage2 starts at
    jmp .chk_for_last_lba       ; Check to see if we are last sector in stage2

.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, STAGE2_LBA_END      ; Have we reached the last stage2 sector?
    jl .read_sector_loop        ;     If we haven't then read next sector

.stage2_loaded:
    call switch_to_pm

.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 bx, diskErrorMsg        ; Display disk error message
    call print_string
    cli
.error_loop:
    hlt
    jmp .error_loop

;    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

%include "boot/print/print_string.asm"
%include "boot/pm/switch_to_pm.asm"
%include "boot/pm/gdt.asm"

bits 32

begin_pm:
jmp 0x1000

; Uncomment these lines if not using a BPB (via bpb.inc)
%ifndef WITH_BPB
numHeads:        dw 2         ; 1.44MB Floppy has 2 heads & 18 sector per track
sectorsPerTrack: dw 18
%endif

bootDevice:      db 0x00
diskErrorMsg:    db "Unrecoverable disk error!", 0
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
MSG_LOAD_KERNEL db "Loading kernel into memory", 0



; Pad boot sector to 510 bytes and add 2 byte boot signature for 512 total bytes
TIMES 510-($-$$) db  0
dw 0xaa55

; Beginning of stage2. This is at 0x1000 and will allow your stage2 to be 32.5KiB
; before running into problems. DL will be set to the drive number originally
; passed to us by the BIOS.

NUM_STAGE2_SECTORS equ (stage2_end-stage2_start+511) / 512
                                ; Number of 512 byte sectors stage2 uses.

stage2_start:
    ; Insert stage2 binary here. It is done this way since we
    ; can determine the size(and number of sectors) to load since
    ;     Size = stage2_end-stage2_start
    incbin "kernel/kernel.bin"

; End of stage2. Make sure this label is LAST in this file!
stage2_end:

您的Makefile 可以进行一些清理工作。我注意到您手动将interrupts.o 添加到OBJ(因为它是一个ASM 文件)。我建议收集所有与内核相关的.asm 文件并将它们添加到OBJ 列表中。我建议将其更改为:

SOURCES  = $(shell find cpu drivers include kernel libc -name '*.c')
KERN_ASM = $(shell find cpu drivers include kernel libc -name '*.asm')
HEADERS  = $(shell find cpu drivers include kernel libc -name '*.h')
OBJ      = ${SOURCES:.c=.o} ${KERN_ASM:.asm=.o}

ASM = nasm
CC = gcc
LD = ld -m elf_i386
OBJCOPY = objcopy

CFLAGS = -g -ffreestanding -Wall -Wextra -fno-exceptions -m32 -std=c11 -fno-pic \
         -fno-asynchronous-unwind-tables

ifeq ($(shell uname -s),Darwin)
        CC = i386-elf-gcc
        LD = i386-elf-ld
        OBJCOPY = i386-elf-objcopy
endif

all: os-image.bin

run: all
        qemu-system-i386 os-image.bin

clean:
        rm -rf *.dis *.o *.elf
        rm -rf *.bin os-image.bin boot/*.bin kernel/*.bin
        rm -rf boot/*.o cpu/*.o drivers/*.o kernel/*.o libc/*.o

# Make a 1.44MiB disk image. Can work for HDA and FDA booting
os-image.bin: kernel/kernel.bin boot/boot.bin
        dd if=/dev/zero of=$@ bs=1024 count=1440
        dd if=$(word 2,$^) of=$@ conv=notrunc

boot/boot.bin: boot/boot.asm
        ${ASM} $< -f bin -o $@

kernel/kernel.bin: kernel/kernel.elf
        ${OBJCOPY} -O binary $^ $@

kernel/kernel.elf: ${OBJ}
        ${LD} -o $@ -Tlinker.ld $^

%.o: %.c ${HEADERS}
        ${CC} ${CFLAGS} -c $< -o $@

%.o: %.asm
        ${ASM} $< -f elf -o $@

%.bin: %.asm
        ${ASM} $< -f bin -o $@

这个 make 文件使用 DD 创建一个 1.44MiB 的软盘映像,可以用作软盘或硬盘驱动器磁盘映像。你会注意到我已经从显式依赖列表中删除了kernel_entry.asm。要使这个新的Makefile 工作您必须将boot/kernel_entry.asm 移动到kernel/kernel_entry.asm。确保您删除 boot/kernel_entry.asm

修改kernel/kernel_entry.asm 以使用.text.entry 部分并将BSS 清零。它可能看起来像这样:

global _start

bits 32
extern kernel_main
extern __bss_start
extern __bss_sizel

section .text.entry

_start:
  ; Zero out the BSS memory area a DWORD at a time
  ; since the memory isn't guaranteed to already be zero
  xor eax, eax
  mov ecx, __bss_sizel
  mov edi, __bss_start
  rep stosd

  ; Call C entry point of kernel
  call kernel_main
  jmp $

适用于这些更改的链接描述文件linker.ld 如下:

OUTPUT_FORMAT(elf32-i386)

SECTIONS {
    . = 0x1000;

    .text : SUBALIGN(4)
    {
        *(.text.entry)       /* Ensure .text.entry appears first */
        *(.text*)
        *(.rodata*)
        *(.data)
    }

    .bss : SUBALIGN(4) {
        __bss_start = .;
        *(COMMON)            /* all COMMON sections from all files */
        *(.bss)              /* all BSS sections from all files */
    }
    . = ALIGN(4);
    __bss_end = .;
    __bss_sizeb = __bss_end - __bss_start;       /* BSS size in bytes */
    __bss_sizel = (__bss_end - __bss_start) / 4; /* BSS size in longs/DWORDs */

    /DISCARD/ : {            /* Remove Unneeded sections */
        *(.eh_frame);
        *(.comment);
    }

    end = .; _end = .; __end = .;
}

它处理您通常在基本操作系统的 ELF 文件中看到的所有正常段。它还使用了一个特殊的.entry.text 部分来确保kernel/kernel_entry.asm 中的代码排在第一位。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-31
    • 2013-05-17
    • 2015-05-02
    • 2013-12-14
    相关资源
    最近更新 更多