Как сопоставить раздел с сегментом из выходного файла ELF? - PullRequest
0 голосов
/ 25 сентября 2018

Ну, я написал загрузчик в ассемблере и пытаюсь загрузить с него ядро ​​C.

Это загрузчик:

bits 16
xor ax,ax
jmp 0x0000:boot

extern kernel_main

global boot
boot:
    mov ah, 0x02             ; load second stage to memory
    mov al, 1                ; numbers of sectors to read into memory
    mov dl, 0x80             ; sector read from fixed/usb disk ;0 for floppy; 0x80 for hd
    mov ch, 0                ; cylinder number
    mov dh, 0                ; head number
    mov cl, 2                ; sector number
    mov bx, 0x8000           ; load into es:bx segment :offset of buffer
    int 0x13                 ; disk I/O interrupt

    mov ax, 0x2401
    int 0x15 ; enable A20 bit
    mov ax, 0x3
    int 0x10 ; set vga text mode 3


    cli

    lgdt [gdt_pointer] ; load the gdt table
    mov eax, cr0
    or eax,0x1 ; set the protected mode bit on special CPU reg cr0
    mov cr0, eax
    jmp CODE_SEG:boot2 ; long jump to the code segment


gdt_start:
    dq 0x0
gdt_code:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0
gdt_data:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0
gdt_end:


gdt_pointer:
    dw gdt_end - gdt_start
    dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

bits 32
boot2:
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

;    mov esi,hello
;    mov ebx,0xb8000
;.loop:
;    lodsb
;    or al,al
;    jz haltz
;    or eax,0x0100
;    mov word [ebx], ax
;    add ebx,2
;    jmp .loop
;haltz:
;hello: db "Hello world!",0

mov esp,kernel_stack_top
jmp kernel_main

cli
hlt

times 510 -($-$$) db 0
dw 0xaa55

section .bss
align 4
kernel_stack_bottom: equ $
    resb 16384 ; 16 KB
kernel_stack_top:

, а это ядро ​​C:

__asm__("cli\n");
void kernel_main(void){
  const char string[] = "012345678901234567890123456789012345678901234567890123456789012";
  volatile unsigned char* vid_mem = (unsigned char*) 0xb8000;
  int j=0;
  while(string[j]!='\0'){

    *vid_mem++ = (unsigned char) string[j++];
    *vid_mem++ = 0x09;
  }

for(;;);

}

Теперь я собираю оба источника отдельно в выходной файл ELF.И связав их через скрипт компоновщика, выведите исходный двоичный файл и загрузите его с помощью qemu.

Скрипт компоновщика:

ENTRY(boot)
OUTPUT_FORMAT("binary")

SECTIONS{
  . = 0x7c00;

  .boot1 : {
    *(.boot)
  }

  .kernel : AT(0x7e00){
    *(.text)
    *(.rodata)
    *(.data)
    _bss_start = .;
    *(.bss)
    *(COMMON)
    _bss_end = .;
    *(.comment)
    *(.symtab)
    *(.shstrtab)
    *(.strtab)
  }
  /DISCARD/ : {
        *(.eh_frame)
  }

}

со скриптом сборки:

nasm -f elf32 boot.asm -o boot.o
/home/rakesh/Desktop/cross-compiler/i686-elf-4.9.1-Linux-x86_64/bin/i686-elf-gcc -m32 kernel.c -o kernel.o -e kernel_main -Ttext 0x0 -nostdlib -ffreestanding -std=gnu99 -mno-red-zone -fno-exceptions -nostdlib  -Wall -Wextra
/home/rakesh/Desktop/cross-compiler/i686-elf-4.9.1-Linux-x86_64/bin/i686-elf-ld boot.o kernel.o -o kernel.bin -T linker3.ld
qemu-system-x86_64 kernel.bin

Но я сталкиваюсь с небольшой проблемой.обратите внимание, что строка в ядре C

const char string[] = "012345678901234567890123456789012345678901234567890123456789012";

, когда ее размер равен или меньше 64 байт (вместе с нулевым окончанием).тогда программа работает правильно.

enter image description here

однако, когда размер строки увеличивается с 64 байтов, программа, кажется, не работает

enter image description here

Я пытался отладить его сам и заметил, что когда размер строки меньше или равен 64 байтам, то выходной ELF-файл kernel.o имеет следующее содержимое:

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x1
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4412 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           40 (bytes)
  Number of section headers:         7
  Section header string table index: 4

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 001000 0000bd 00  AX  0   0  1
  [ 2] .eh_frame         PROGBITS        000000c0 0010c0 000034 00   A  0   0  4
  [ 3] .comment          PROGBITS        00000000 0010f4 000011 01  MS  0   0  1
  [ 4] .shstrtab         STRTAB          00000000 001105 000034 00      0   0  1
  [ 5] .symtab           SYMTAB          00000000 001254 0000a0 10      6   6  4
  [ 6] .strtab           STRTAB          00000000 0012f4 00002e 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0x00000000 0x00000000 0x000f4 0x000f4 R E 0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text .eh_frame 

There is no dynamic section in this file.

There are no relocations in this file.

The decoding of unwind sections for machine type Intel 80386 is not currently supported.

Symbol table '.symtab' contains 10 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 SECTION LOCAL  DEFAULT    1 
     2: 000000c0     0 SECTION LOCAL  DEFAULT    2 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 FILE    LOCAL  DEFAULT  ABS kernel.c
     5: 00000000     0 FILE    LOCAL  DEFAULT  ABS 
     6: 00000001   188 FUNC    GLOBAL DEFAULT    1 kernel_main
     7: 000010f4     0 NOTYPE  GLOBAL DEFAULT    2 __bss_start
     8: 000010f4     0 NOTYPE  GLOBAL DEFAULT    2 _edata
     9: 000010f4     0 NOTYPE  GLOBAL DEFAULT    2 _end

No version information found in this file.

Однако, когда размер строки превышает 64 байта, содержимое выглядит примерно так:

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x1
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4432 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           40 (bytes)
  Number of section headers:         8
  Section header string table index: 5

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 001000 000083 00  AX  0   0  1
  [ 2] .rodata           PROGBITS        00000084 001084 000041 00   A  0   0  4
  [ 3] .eh_frame         PROGBITS        000000c8 0010c8 000038 00   A  0   0  4
  [ 4] .comment          PROGBITS        00000000 001100 000011 01  MS  0   0  1
  [ 5] .shstrtab         STRTAB          00000000 001111 00003c 00      0   0  1
  [ 6] .symtab           SYMTAB          00000000 001290 0000b0 10      7   7  4
  [ 7] .strtab           STRTAB          00000000 001340 00002e 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0x00000000 0x00000000 0x00100 0x00100 R E 0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata .eh_frame 

There is no dynamic section in this file.

There are no relocations in this file.

The decoding of unwind sections for machine type Intel 80386 is not currently supported.

Symbol table '.symtab' contains 11 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 SECTION LOCAL  DEFAULT    1 
     2: 00000084     0 SECTION LOCAL  DEFAULT    2 
     3: 000000c8     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 SECTION LOCAL  DEFAULT    4 
     5: 00000000     0 FILE    LOCAL  DEFAULT  ABS kernel.c
     6: 00000000     0 FILE    LOCAL  DEFAULT  ABS 
     7: 00000001   130 FUNC    GLOBAL DEFAULT    1 kernel_main
     8: 00001100     0 NOTYPE  GLOBAL DEFAULT    3 __bss_start
     9: 00001100     0 NOTYPE  GLOBAL DEFAULT    3 _edata
    10: 00001100     0 NOTYPE  GLOBAL DEFAULT    3 _end

No version information found in this file.

Я заметил, что строка теперь находится в разделе .rodata сразмер 41 шестнадцатеричный или 65 байт, который должен отображаться на сегмент, возможно, 0-й сегмент, который равен NULL.И что программа не может найти .rodata.

Я не могу заставить его работать.Я понимаю структуру ELF, но не знаю, как с ними работать.

Предположим, что я новичок и обладаю ограниченными знаниями о низкоуровневом программировании.

1 Ответ

0 голосов
/ 26 сентября 2018

Две серьезные проблемы вызывают большинство проблем:

  • Вы загружаете второй сектор диска в 0x0000: 0x8000, когда весь код ожидает загрузки ядра после загрузчика в 0x0000: 0x7e00
  • Вы компилируете kernel.c прямо в исполняемое имя kernel.o.Вы должны скомпилировать его в соответствующий объектный файл, чтобы он мог пройти ожидаемую фазу связывания при запуске ld.

Чтобы устранить проблему с загрузкой ядра в память нанеправильное расположение в памяти, измените:

mov bx, 0x8000           ; load into es:bx segment :offset of buffer

на:

mov bx, 0x7e00           ; load into es:bx segment :offset of buffer

Чтобы устранить проблему компиляции kernel.c в исполняемый файл ELF с именем kernel.oудалите -e kernel_main -Ttext 0x0 и замените его на -c.Опция -c заставляет GCC создавать объектный файл, который может быть надлежащим образом связан с LD.Измените:

/home/rakesh/Desktop/cross-compiler/i686-elf-4.9.1-Linux-x86_64/bin/i686-elf-gcc -m32 kernel.c -o kernel.o -e kernel_main -Ttext 0x0 -nostdlib -ffreestanding -std=gnu99 -mno-red-zone -fno-exceptions -nostdlib  -Wall -Wextra

на:

/home/rakesh/Desktop/cross-compiler/i686-elf-4.9.1-Linux-x86_64/bin/i686-elf-gcc -m32 -c kernel.c -o kernel.o -nostdlib -ffreestanding -std=gnu99 -mno-red-zone -fno-exceptions -Wall -Wextra

Причина сбоя с более длинными строками

Причина, по которой работает строка с длиной менее 64 байтов, заключается в том, чтокомпилятор генерировал код независимым от позиции способом, инициализируя массив в стеке непосредственными значениями.Когда размер достиг 64 байт, компилятор поместил строку в раздел .rodata, а затем инициализировал массив в стеке, скопировав его из .rodata.Это сделало вашу позицию кода зависимой.Ваш код был загружен с неправильными смещениями и имел неверные исходные точки, в результате чего был получен код, ссылающийся на неправильные адреса, поэтому он не удался.


Другие наблюдения

  • Вы должны инициализировать BSS (.bss) раздел до 0 перед вызовом kernel_main.Это можно сделать в сборке, выполнив итерацию по всем байтам от смещения _bss_start до смещения _bss_end.
  • В результате секция .comment будет передана в ваши двоичные файлы, тратя впустую байты.Вы должны поместить его в раздел /DISCARD/.
  • Вы должны поместить раздел BSS в свой скрипт компоновщика после всех остальных, чтобы он не занимал место в kernel.bin
  • Inboot.asm Вы должны установить SS: SP (указатель стека) в начале перед чтением секторов диска.Это должно быть место, которое не будет мешать вашему коду.Это особенно важно при чтении данных в память с диска, так как вы не знаете, где BIOS поместил текущий стек.Вы не хотите читать поверх текущей области стека.Установка его чуть ниже загрузчика на 0x0000: 0x7c00 должен работать.
  • Перед вызовом кода C вы должны очистить флаг направления, чтобы строковые инструкции использовали движение вперед.Вы можете сделать это, используя инструкцию CLD .
  • В boot.asm вы можете сделать свой код более универсальным, используя номер загрузочного диска, передаваемый BIOS врегистр DL вместо жесткого кодирования его в значение 0x80 (0x80 - первый жесткий диск)
  • Вы можете включить оптимизацию с помощью -O3 или использовать уровень оптимизации -Os для оптимизации под размер кода.
  • Ваш скрипт компоновщика работает не совсем так, как вы ожидаете, хотя и дает правильные результаты.Вы никогда не объявляли секцию .boot в своем файле NASM , поэтому фактически ничего не помещается в секцию вывода .boot1 в скрипте компоновщика.Это работает, потому что он включается в раздел .text в разделе вывода .kernel.
  • Желательно удалить подпись дополнения и загрузки из файла сборки и переместить ее в скрипт компоновщика
  • Вместо того, чтобы ваш скрипт компоновщика выводил двоичный файл напрямую, более полезно выводить в исполняемый формат ELF по умолчанию.Затем вы можете использовать OBJCOPY для преобразования файла ELF в двоичный файл.Это позволяет вам строить с отладочной информацией, которая появится как часть исполняемого файла ELF.Исполняемый файл ELF может использоваться для символической отладки вашего двоичного ядра в QEMU .
  • Вместо непосредственного использования LD для связи используйте GCC .Преимущество этого заключается в том, что библиотека libgcc может быть добавлена ​​без указания полного пути к библиотеке.libgcc - это набор процедур, которые могут потребоваться для генерации кода C с помощью GCC

Пересмотренный исходный код, скрипт компоновщика и команды построения сПриведенные выше замечания приняты во внимание:

boot.asm :

bits 16

section .boot

extern kernel_main
extern _bss_start
extern _bss_len

global boot

    jmp 0x0000:boot
boot:
    ; Place realmode stack pointer below bootloader where it doesn't
    ; get in our way
    xor ax, ax
    mov ss, ax
    mov sp, 0x7c00

    mov ah, 0x02             ; load second stage to memory
    mov al, 1                ; numbers of sectors to read into memory

;   Remove this, DL is already set by BIOS to current boot drive number
;    mov dl, 0x80             ; sector read from fixed/usb disk ;0 for floppy; 0x80 for hd
    mov ch, 0                ; cylinder number
    mov dh, 0                ; head number
    mov cl, 2                ; sector number
    mov bx, 0x7e00           ; load into es:bx segment :offset of buffer
    int 0x13                 ; disk I/O interrupt

    mov ax, 0x2401
    int 0x15 ; enable A20 bit
    mov ax, 0x3
    int 0x10 ; set vga text mode 3


    cli

    lgdt [gdt_pointer] ; load the gdt table
    mov eax, cr0
    or eax,0x1 ; set the protected mode bit on special CPU reg cr0
    mov cr0, eax
    jmp CODE_SEG:boot2 ; long jump to the code segment


gdt_start:
    dq 0x0
gdt_code:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0
gdt_data:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0
gdt_end:


gdt_pointer:
    dw gdt_end - gdt_start
    dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

bits 32
boot2:
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    ; Zero out the BSS area
    cld
    mov edi, _bss_start
    mov ecx, _bss_len
    xor eax, eax
    rep stosb

    mov esp,kernel_stack_top
    call kernel_main

    cli
    hlt


section .bss
align 4
kernel_stack_bottom: equ $
    resb 16384 ; 16 KB
kernel_stack_top:

kernel.c :

void kernel_main(void){
  const char string[] = "01234567890123456789012345678901234567890123456789012345678901234";
  volatile unsigned char* vid_mem = (unsigned char*) 0xb8000;
  int j=0;
  while(string[j]!='\0'){

    *vid_mem++ = (unsigned char) string[j++];
    *vid_mem++ = 0x09;
  }

for(;;);

}

linker3.ld :

ENTRY(boot)

SECTIONS{
  . = 0x7c00;

  .boot1 : {
    *(.boot);
  }

  .sig : AT(0x7dfe){
     SHORT(0xaa55);
  }

  . = 0x7e00;
  .kernel : AT(0x7e00){
    *(.text);
    *(.rodata*);
    *(.data);
    _bss_start = .;
    *(.bss);
    *(COMMON);
    _bss_end = .;
    _bss_len = _bss_end - _bss_start;
  }
  /DISCARD/ : {
    *(.eh_frame);
    *(.comment);
  }

}

Команды для сборки этого загрузчика и ядра:

nasm -g -F dwarf -f elf32 boot.asm -o boot.o
i686-elf-gcc -g -O3 -m32 kernel.c -c -o kernel.o -ffreestanding -std=gnu99 \
    -mno-red-zone -fno-exceptions -Wall -Wextra    
i686-elf-gcc -nostdlib -Wl,--build-id=none -T linker3.ld boot.o kernel.o \
    -lgcc -o kernel.elf
objcopy -O binary kernel.elf kernel.bin

Для символической отладки 32-битного ядра с помощью QEMU вы можете запустить QEMU следующим образом:

qemu-system-i386 -fda kernel.bin -S -s &
gdb kernel.elf \
        -ex 'target remote localhost:1234' \
        -ex 'break *kernel_main' \
        -ex 'layout src' \
        -ex 'continue'

Это запустит ваш файл kernel.bin в QEMU и затем удаленно подключит GDB отладчик.В макете должен отображаться исходный код с перерывом на kernel_main.

...