Вызов C-кода из загрузчика - PullRequest
4 голосов
/ 14 марта 2012

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

У меня есть два вопроса:

  • Является ли соглашение о вызовахтак же, как на x86?А именно, аргументы в стеке, слева направо.
  • Как мне создать сырой двоичный файл с помощью gcc?

Ответы [ 5 ]

5 голосов
/ 16 августа 2012

Вы можете создавать простые двоичные файлы с помощью компоновщика gcc, используя скрипт компоновщика. ключ - это директива OUTPUT_FORMAT (двоичная):

//========================================
FILE: linker.ld
//========================================
OUTPUT_FORMAT(binary)
SECTIONS {
    .text : { *(.text) }
    .data : { *(.data) }
    .bss  : { *(.bss)  }
}
//========================================

Я вызвал компоновщик в make-файле следующим образом (тогда как linker.ld - это файл сценария компоновщика):

//========================================
ld -T linker.ld loaderEntry.o loaderMain.o -o EOSLOAD.BIN -L$(lib) -lsys16
//========================================

Я скомпилировал объектный код с

//========================================
gcc -nostdinc -nostdlib -ffreestanding -c <code files> -o theObjectCode.o
//========================================

чтобы избавиться от стандартных библиотек, которые не работают в 16-битном режиме.

для загрузчика рукопожатия MBR и загрузчика Я использовал следующий код сборки loaderMain.S gcc (loaderMain.o должен быть первым файлом, переданным компоновщику, который будет расположен со смещением адреса 0x0000, как вы можете видеть выше). Я использовал директиву -code16gcc для генерации 16-битного кода. Тем не менее, сгенерированный код, вероятно, не будет работать на старых машинах x86, так как я использовал несовместимые инструкции кода (% esp, $ ebp, отпуск и т. д.), доступные только для более новых моделей.

//========================================
FILE: loaderEntry.S
//========================================
  .text
  .code16gcc

  // the entry point at 0x9000:0x0000 // this is where I did a far call to by the MBR
  .globl loaderMain   // loader C entry function name declaration
  push  %cs           // initialize data segments   with same value as code segment
  pop   %ax           // (I managed only tiny model so far ...)
  movw  %ax, %ds
  movw  %ax, %es
  movw  %ax, %fs
  movw  %ax, %gs
  movw  %ax, %ss      // initialize stack segment with same value as     code segment
  movl  $0xffff, %esp // initialize stack pointers with 0xffff (usage of extended (dword) offsets does not work, so we're stuck in tiny model)
  movl  %esp, %ebp
  call  loaderMain   // call C entry function

  cli // halt the machine for the case the main function dares to return
  hlt
//========================================

код сборки вызывает символ, который был определен в файле языка Си loaderMain.c. для генерации кода, совместимого с 16-битным режимом, вы должны объявить об использовании набора 16-битных команд перед первой строкой кода в каждом используемом вами C-файле. Это может быть сделано только с помощью встроенной инструкции по сборке AFAIK:

  asm(".code16gcc\n"); // use 16bit real mode code set

  /*  ... some C code .. */


  // ... and here is the C entry code ... //
  void loaderMain() {
    uint cmdlen = 0;
    bool terminate = false;
    print(NL);
    print(NL);
    print("*** EOS LOADER has taken over control. ***\r\n\r\n");
    print("Enter commands on the command line below.\r\n");
    print("Command are executed by pressing the <ENTER> key.\r\n");
    print("The command \'help\' shows a list of all EOS LOADER commands.\r\n");
    print("HAVE FUN!\r\n");
    print(NL);
    while (!terminate) {
        print("EOS:>");
        cmdlen = readLine();
        buffer[cmdlen] = '\0';
        print(NL);
        terminate = command();
    }
    shutdown();
  }

До сих пор мне удавалось писать только простой код на C - я до сих пор не преуспел с кодом C ++, и мне удалось только создать крошечную модель памяти (то есть CS, SS, DS и ES все одинаковы). gcc использует только смещения в качестве адресов указателей, поэтому, кажется, трудно преодолеть проблему с моделью памяти timny без дополнительного ассемблерного кода. (Хотя я слышал, что некоторые люди справились с этой проблемой в gcc)

Соглашение о вызовах заключается в том, что последний аргумент помещается первым в стек, и кажется, что все значения выровнены по двойному слову. Ниже приведен пример кода сборки, который можно вызвать в коде C.code16gcc:

//======================
  .text
  .code16gcc

  .globl kbdread             // declares a global symbol so that the function can be called from C
  .type  kbdread, @function  // declares the symbol as a function
kbdread:                     // the entry point label which has to the same as the symbol

  // this is the conventional stack frame for function entry
  pushl %ebp
  movl  %esp, %ebp

  // memory space for local variables would be allocated by decrementing the stack pointer accordingly
  // the parameter arguments are being addressed by the base pointer which points to the same address while bein within the function

  pushw %ds  // I'm paranoid, I know...
  pushw %es
  pushw %fs
  pushl %eax
  pushl %ebx
  pushl %ecx
  pushl %edx
  pushl %esi
  pushl %edi

  xorl %eax, %eax  // calls the keyboard interrupt in order to read char code and scan code
  int  $0x16

  xorl %edi, %edi
  movl 8(%ebp), %edi // moves the pointer to the memory location in which the char code will be stored into EDI             
  movb %al, (%edi)   // moves the char code from AL to the memory location to which EDI points

  xorl %edi, %edi // paranoid again (but who knows how well the bios handles extended registers??)..

  movl 12(%ebp), %edi // moves the pointer to the memory location in which the scan code will be stored into EDI
  movb %ah, (%edi)    // moves the scan code from AH to the memory location to which EDI points

  popl %edi // restoring the values from stack..
  popl %esi
  popl %edx
  popl %ecx
  popl %ebx
  popl %eax
  popw %fs
  popw %es
  popw %ds

  leave  // .. and the conventional end frame for functions.
  ret    // be aware that you are responsible to restore the stack when you have declared local variables on the stack ponter.
         // the leave instruction is a convenience method to do that. but it is part of not early X86 instruction set (as well as extended registers)
         // so be careful which instruftion you actually use if you have to stay compatible with older computer models.
//=====================

Между прочим, объявление заголовка C функции выглядит так:

//=====================
void kbdread(char* pc, (unsigned char)* psc);
//=====================

Надеюсь, это было как-то полезно. Приветствия.

4 голосов
/ 14 марта 2012

Из ваших предыдущих вопросов здесь я предполагаю, что вы хотите создать загрузчик для современного компьютера с архитектурой x86 (т.е. 386 или более поздней).

В реальном режиме по умолчанию размер операнда и адреса составляет 16 бит. К сожалению, GCC не может генерировать 16-битный код сборки x86. Однако, поместив директиву .code16gcc в верхней части каждого файла, вы можете указать as использовать префиксы команд, которые будут переопределять адрес и размер операнда. Эти префиксы более подробно описаны в разделе 3.3.5 Руководство разработчика программного обеспечения для архитектуры Intel 64 и IA-32, том 1 .

Более подробную информацию о .code16gcc можно найти здесь . Обратите внимание, что это руководство 2003 года, и .code16gcc больше не является экспериментальным или, по крайней мере, достаточно стабильным для использования в Linux.

Поскольку gcc не знает, что происходит с соглашением о вызовах ассемблерного кода, оно останется неизменным. Здесь - это скрипт ld, который можно использовать для создания загрузчика.

2 голосов
/ 14 марта 2012

8086 IS x86. 8088/86 использовали разные модели, маленькие, средние, большие, огромные. И в зависимости от модели вы могли / могли бы получить различия в стеке. Огромный / большой адрес возврата - это сегмент и смещение, где маленький адрес возврата - это, например, только смещение (вызывающее изменение всей установки стека). Карл уже упомянул ширину стека.

Скомпилируйте и разберите несколько простых примеров, и это должно стать очевидным. Если gcc не выполняет неплоскую цель, то, возможно, попробуйте djgpp. или watcom или borland (бесплатно).

1 голос
/ 14 марта 2012

Во-первых, 8086 - это x86.

Во-вторых, соглашение о вызовах зависит от используемого вами компилятора и любых его функций, которые могут его изменить (например, вы часто можете указать такие вещи, как cdecl, stdcall, fastcall и т. Д.). Какой компилятор вы используете?

В-третьих, gcc не компилирует код для 16-битных инструкций x86.

Как предложил @dwelch, используйте Open Watcom C / C ++ или древнюю Borland / Turbo C / C ++, которые бесплатны и могут компилировать 16-битный код.

Вот как все это можно сделать, 1 , 2 .

0 голосов
/ 14 марта 2012
  1. Соглашение о вызовах аналогично IA32, за исключением того, что все вводится в 16-битных словах, а не в 32.
  2. Возможно, вы не можете напрямую создать плоский двоичный файл с помощью GCC. GCC все еще поддерживает 16-битные машины Intel? Вы должны иметь возможность использовать objcopy для получения необработанных двоичных данных из вашего связанного объекта. Возможно, вам захочется взглянуть и на скрипты компоновщика .
...