Ядро падает, когда я добавляю не встроенную функцию - PullRequest
0 голосов
/ 08 декабря 2018

Я написал простой загрузчик и ядро ​​на C (компиляция с помощью компилятора g ++).Когда я пытаюсь создать не встроенные функции, сбой ядра ссылается на 0xefffff54.Регистры SS, DS и др. Равны нулю, но ранее был селектор 0x10 в защищенном режиме.Вот загрузчик, загрузчик и ядро ​​и как я его связываю:

boot.asm

use16
[org 0x7c00]
;;;;;;;;;;;;;;;;;;;;;;;;;;;

section .text

mov bp, 0x9990
mov sp, bp

call loadKernel

cli
lgdt [gdt_desc]

in al, 0x92
or al, 2
out 0x92, al

mov eax, cr0
or eax, 1
mov cr0, eax

jmp 0x8:init_pm

;;;;;;;;;;;;;;;;;;;;;;;;

use32
init_pm:
mov ax, 0x10
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax

mov esp, 0x9990

push ecx
push 1500
call clearConsole
add esp, 4
pop ecx

push eax
push edx

push 0x1B
push hello_world
push 0
call printStr
add esp, 12

pop edx
pop eax
jmp 0x1000
jmp $

;void loadKernel()
loadKernel:
use16
    mov bx, 0x1000
    mov ah, 0x2
    mov dl, 0x80
    mov al, 0x1
    mov ch, 0x0
    mov cl, 0x2
    mov dh, 0x0
    int 0x13
    ret

;void clearConsole(int value)
use32
clearConsole:
    mov ecx, 0
    loop_2:
    cmp ecx, [esp+4]
    jz exit_2
    mov al, 0
    mov ah, 0
    push ecx
    call printChar
    pop ecx
    add ecx, 2
    jmp loop_2
    exit_2:
    ret

;void printStr(byte num, char* str, byte color)
printStr:
    mov ecx, [esp+8]
loop_1:
    mov al, [ecx]
    inc ecx
    test al, al
    jz exit_1
    mov ah, [esp+12]
    push dword [esp+4]
    call printChar
    add esp, 4
    inc dword [esp+4]
    inc dword [esp+4]
    jmp loop_1
exit_1:
    ret

;void printChar(byte num, unsigned char c, byte color)
printChar:
    mov edx, 0xB8000
    add edx, [esp+4]
    mov [edx], al
    mov [edx+1], ah
    ret



;;;;;;;;;;;;;;;;;;;;

hello_world:
db "Loading kernel...", 0

GDT:
;null
dd 0
dd 0

code:
dw 0xffff ; limit
dw 0    ; base
db 0    ; base
db 0x9a ; access rights
db 11001111b ; 4 left - flags, 4 right = limit
db 0        ; base

data:
dw 0xffff
dw 0
db 0
db 0x92
db 11001111b
db 0

gdt_desc:
dw $ - GDT -1
dd GDT

;;;;;;;;;;;;;;;
times 510-($-$$) db 0
dw 0xAA55

loader.asm

use32

section .bss
align 16
stack_bottom:
resb 16384
stack_top:

section .text
extern kernel_main
global _start

_start:
    mov esp, stack_top
    call kernel_main
    jmp $

kernel_main.c

typedef unsigned short uint16_t;
typedef unsigned char uint8_t;

uint16_t* g_pTerminalBuffer;

#define MAX_HEIGHT  25
#define MAX_WIDTH   80

#define true 1
#define false 0

uint8_t g_iTerminalRow;
uint8_t g_iTerminalColumn;

inline uint8_t encodeColor(uint8_t foreground, uint8_t background)
{
    return foreground | background << 4;
}

inline uint16_t encodeChar(uint8_t c, uint8_t color)
{
    return (uint16_t)color << 8 | (uint16_t)c;
}

void initializeTerminal() // fails when i add this function
{
    g_iTerminalRow = 0;
    g_iTerminalColumn = 0;
}

extern "C" void kernel_main()
{
    g_pTerminalBuffer = (uint16_t*)0xB8000;
    g_pTerminalBuffer[2] = encodeChar('T', encodeColor(15, 0));
    while(true){}
}

build.sh

#!/bin/bash
nasm -f bin boot.asm -o boot.bin
nasm -f elf32 loader.asm -o loader.o
~/cross/bin/i386-elf-c++ -ffreestanding -c /home/name/os/kernel_main.c -o /home/name/os/kernel_main.o

ld -m elf_i386 -Ttext 0x1000 -o kernel_main.elf kernel_main.o loader.o
objcopy -R .note -R .comment -S -O binary kernel_main.elf kernel_main.bin

dd if=/dev/zero of=image.bin bs=512 count=2880
dd if=boot.bin of=image.bin conv=notrunc
dd if=kernel_main.bin of=image.bin conv=notrunc bs=512 seek=1

rm ./boot.bin ./kernel_main.bin ./kernel_main.o ./loader.o ./kernel_main.elf
qemu-system-i386 -d guest_errors image.bin

1 Ответ

0 голосов
/ 09 декабря 2018

Без сценария компоновщика по умолчанию в ELF будут помещены секции .text.text.startup), за которыми следуют .rodata*, .data и .bss.Функции в разделе .text будут выводиться в исполняемый файл в том порядке, в котором они встречаются.Компоновщик LD будет обрабатывать объекты в том порядке, в котором они встречаются в командной строке.Вы делаете:

ld -m elf_i386 -Ttext 0x1000 -o kernel_main.elf kernel_main.o loader.o

kernel_main.o является первым, поэтому функции в kernel_main.o будут обрабатываться first .Когда вы определили initializeTerminal, вполне возможно (и, скорее всего, здесь), что оно появится до kernel_main.Если initializeTerminal - первая функция, и вы пытаетесь начать ее выполнение с загрузчика (по адресу 0x1000), вы попадете в неопределенное состояние, и это, вероятно, вызовет тройную ошибку.Тройная ошибка вернула бы вас в реальный режим, поэтому ваши сегменты в дампе могли быть сброшены на 0x0000.

Если вы удалите initializeTerminal, первая обнаруженная функция будет kernel_main.Проницательный наблюдатель может указать, что вы действительно не хотите, чтобы kernel_main казнили напрямую!Вы хотите, чтобы _start был выполнен первым!Вам просто повезло, что kernel_main может быть выполнено без ошибок как есть.Вы действительно хотите, чтобы _start появлялся перед другими функциями.

Быстрое исправление должно состоять в том, чтобы переместить loader.o так, чтобы это был первый объект в командной строке компоновщика:

ld -m elf_i386 -Ttext 0x1000 -o kernel_main.elf loader.o kernel_main.o

Теперь секция .text с _start будет обработана и сначала выведена в конечный исполняемый файл.Это должно решить вашу проблему.

В качестве альтернативы я предпочитаю создавать скрипт компоновщика, который помещает раздел .text в loader.o первым в исполняемом файле.Правильный сценарий компоновщика может избежать беспокойства по поводу порядка, в котором объектные файлы указываются в командной строке.


Ниже я предоставляю версию файлов, которая:

  • Включает скрипт компоновщика для размещения раздела .text` в loader.o, сначала свернутого остальными.
  • Сценарий компоновщика определяет начальную точку (VMA) для ядра 0x1000.
  • Сценарий компоновщика определяет начальный и конечный символы для секции BSS
  • loader.asm инициализируетРаздел BSS обнуляется перед вызовом точки входа C ++ .
  • Хорошей идеей будет поместить встроенные функции в заголовочные файлы.Я отделил подпрограммы видео от kernel_main и поместил глобальные переменные видео в video.c .Встроенные функции находятся в video.h .
  • Используйте значение DL , передаваемое BIOS загрузчику, содержащему номер загрузочного диска.
  • Чтение диска CHS (Int 13h / AH = 02h) требует, чтобы место назначения было указано в ES: BX.Поскольку мы хотим, чтобы ядро ​​загружалось в 0x0000: 0x1000 явно устанавливает ES в 0. Это не гарантирует, что он будет нулевым, когда ваш загрузчик начнет работать.
  • Я строю с оптимизацией на
  • Я строю с отладочной информацией.Это может быть полезно при запуске кода в QEMU с удаленным отладчиком GDB.

Файлы:

link.ld :

OUTPUT_FORMAT("elf32-i386");
/* We define an entry point to keep the linker quiet. This entry point */
ENTRY(_start);

KERNEL_BASE = 0x1000;

SECTIONS
{
    . = KERNEL_BASE;

    .kernel : SUBALIGN(4) {
        /* Ensure .text section of loader.o is first */
        loader.o(.text*);
        *(.text*);
        *(.rodata*);
        *(.data*);
    }

    /* Place the unitialized data in the area after our kernel */
    .bss : SUBALIGN(4) {
        __bss_start = .;
        *(COMMON);
        *(.bss)
        . = ALIGN(4);
        __bss_end = .;
    }
    __bss_sizeb = SIZEOF(.bss);
    __bss_sizel = __bss_sizeb / 4;

    /* Remove sections that won't be relevant to us */
    /DISCARD/ : {
        *(.eh_frame);
        *(.comment);
    }
}

boot.asm :

[ORG 0x7c00]
use16
section .text

mov bp, 0x9990
mov sp, bp

call loadKernel

cli
lgdt [gdt_desc]

in al, 0x92
or al, 2
out 0x92, al

mov eax, cr0
or eax, 1
mov cr0, eax

jmp 0x8:init_pm

;;;;;;;;;;;;;;;;;;;;;;;;

use32
init_pm:
mov ax, 0x10
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax

mov esp, 0x9990

push ecx
push 80*25*2
call clearConsole
add esp, 4
pop ecx

push eax
push edx

push 0x1B
push hello_world
push 0
call printStr
add esp, 12

pop edx
pop eax

jmp 0x1000                  ; Jump to kernel

;void loadKernel()
loadKernel:
use16
    ; ES:BX point to input buffer. Ensure ES=0
    xor ax, ax
    mov es, ax

    ; Use valueof DL passed by bootloader for dirve number
    mov bx, 0x1000
    mov ah, 0x2
    mov al, 0x1
    mov ch, 0x0
    mov cl, 0x2
    mov dh, 0x0
    int 0x13
    ret

;void clearConsole(int value)
use32
clearConsole:
    mov ecx, 0
    loop_2:
    cmp ecx, [esp+4]
    jz exit_2
    mov al, 0
    mov ah, 0
    push ecx
    call printChar
    pop ecx
    add ecx, 2
    jmp loop_2
    exit_2:
    ret

;void printStr(byte num, char* str, byte color)
printStr:
    mov ecx, [esp+8]
loop_1:
    mov al, [ecx]
    inc ecx
    test al, al
    jz exit_1
    mov ah, [esp+12]
    push dword [esp+4]
    call printChar
    add esp, 4
    inc dword [esp+4]
    inc dword [esp+4]
    jmp loop_1
exit_1:
    ret

;void printChar(byte num, unsigned char c, byte color)
printChar:
    mov edx, 0xB8000
    add edx, [esp+4]
    mov [edx], al
    mov [edx+1], ah
    ret



;;;;;;;;;;;;;;;;;;;;

hello_world:
db "Loading kernel...", 0

GDT:
;null
dd 0
dd 0

code:
dw 0xffff ; limit
dw 0    ; base
db 0    ; base
db 0x9a ; access rights
db 11001111b ; 4 left - flags, 4 right = limit
db 0        ; base

data:
dw 0xffff
dw 0
db 0
db 0x92
db 11001111b
db 0

gdt_desc:
dw $ - GDT -1
dd GDT

;;;;;;;;;;;;;;;
times 510-($-$$) db 0
dw 0xAA55

loader.asm :

; These symbols are defined by the linker. We use them to zero BSS section
extern __bss_start
extern __bss_sizel

use32

section .bss
align 16
stack_bottom:
resb 16384
stack_top:

section .text
extern kernel_main
global _start

_start:
    ; We need to zero out the BSS section. We'll do it a DWORD at a time
    mov edi, __bss_start        ; Start address of BSS
    mov ecx, __bss_sizel        ; Length of BSS in DWORDS
    xor eax, eax                ; Set to 0x00000000
    rep stosd                   ; Do clear using string store instruction
                                ;     Clear 4 bytes at a time

    mov esp, stack_top
    call kernel_main
    jmp $

video.h :

typedef unsigned short uint16_t;
typedef unsigned char uint8_t;

extern uint16_t* g_pTerminalBuffer;

#define MAX_HEIGHT  25
#define MAX_WIDTH   80

#define true 1
#define false 0

extern uint8_t g_iTerminalRow;
extern uint8_t g_iTerminalColumn;

extern void initializeTerminal();

inline uint8_t encodeColor(uint8_t foreground, uint8_t background)
{
    return foreground | background << 4;
}

inline uint16_t encodeChar(uint8_t c, uint8_t color)
{
    return (uint16_t)color << 8 | (uint16_t)c;
}

video.c :

#include "video.h"

uint16_t* g_pTerminalBuffer;
uint8_t g_iTerminalRow;
uint8_t g_iTerminalColumn;

void initializeTerminal()
{
    g_iTerminalRow = 0;
    g_iTerminalColumn = 0;
}

kernel_main.c :

#include "video.h"

extern "C" void kernel_main()
{
    g_pTerminalBuffer = (uint16_t*)0xB8000;
    g_pTerminalBuffer[2] = encodeChar('T', encodeColor(15, 0));
    while(true){}
}

Командадля сборки:

nasm -f bin boot.asm -o boot.bin
nasm -f elf32 -g -F dwarf loader.asm -o loader.o
i686-elf-c++ -O3 -g -ffreestanding -c kernel_main.c -o kernel_main.o
i686-elf-c++ -O3 -g -ffreestanding -c video.c -o video.o

ld -m elf_i386 -T link.ld -o kernel_main.elf loader.o video.o kernel_main.o
objcopy -O binary kernel_main.elf kernel_main.bin

dd if=/dev/zero of=image.bin bs=512 count=2880
dd if=boot.bin of=image.bin conv=notrunc
dd if=kernel_main.bin of=image.bin conv=notrunc bs=512 seek=1

Вы можете запустить его так, как вы это делали ранее:

qemu-system-i386 -d guest_errors image.bin

Вы можете запустить QEMU удаленно, используя отладочную информацию с такими командами:

qemu-system-i386 -d guest_errors image.bin -S -s &

gdb kernel_main.elf \
        -ex 'target remote localhost:1234' \
        -ex 'break *kernel_main' \
        -ex 'layout src' \
        -ex 'layout reg' \
        -ex 'continue'

Устанавливает точку останова на символе kernel_main и использует интерфейс TUI командной строки для отображения регистров и исходного кода.

...