从引导加载程序调用C代码

从引导加载程序调用C代码,c,gcc,assembly,x86,bootloader,C,Gcc,Assembly,X86,Bootloader,我想写一个引导程序。我想编译一些C代码,这样引导加载程序就可以将其加载到内存中并跳转到内存中 我有两个问题: 调用约定与x86上的相同吗?即堆栈上的参数,从左到右 如何使用gcc生成原始二进制文件 调用约定类似于IA32,只是所有内容都是以16位字而不是32位字推送的 您可能无法使用GCC直接创建平面二进制文件。GCC是否仍然支持16位Intel机器?不过,您应该能够使用从链接对象中获取原始二进制数据。你可能也想调查一下 8086是x86。8088/86使用了不同的型号,小型、中型、大型和大

我想写一个引导程序。我想编译一些C代码,这样引导加载程序就可以将其加载到内存中并跳转到内存中

我有两个问题:

  • 调用约定与x86上的相同吗?即堆栈上的参数,从左到右
  • 如何使用gcc生成原始二进制文件
  • 调用约定类似于IA32,只是所有内容都是以16位字而不是32位字推送的
  • 您可能无法使用GCC直接创建平面二进制文件。GCC是否仍然支持16位Intel机器?不过,您应该能够使用从链接对象中获取原始二进制数据。你可能也想调查一下
  • 8086是x86。8088/86使用了不同的型号,小型、中型、大型和大型。根据模型,您可以/将在堆栈上获得差异。大/大返回地址是段和偏移量,例如,小返回地址就是偏移量(导致整个堆栈设置发生更改)。卡尔已经提到了书堆的宽度

    编译和反汇编一些简单的例子,这应该变得显而易见。如果gcc不执行非平面目标,那么可能尝试使用djgpp。或watcom或borland(免费)

    首先,8086是x86

    其次,调用约定特定于您正在使用的编译器及其任何可以更改它的功能(例如,您通常可以指定诸如
    cdecl
    stdcall
    fastcall
    ,等等)。你在用什么编译器

    第三,gcc不会将代码编译为16位x86指令

    正如@dwelch所建议的,使用开放式Watcom C/C++或古老的Borland/Turbo C/C++,它们是免费的,可以编译16位代码


    下面是如何完成这一切的。

    根据您前面的问题,我假设您希望为现代x86机器(即386或更高版本)创建引导加载程序

    在实模式下,默认操作数和地址大小为16位。不幸的是,GCC无法生成16位x86汇编代码。但是,通过将指令
    .code16gcc
    放在每个文件的顶部,您可以告诉
    as
    使用将覆盖地址和操作数大小的指令前缀。英特尔64和IA-32体系结构软件开发人员手册第1卷第3.3.5节详细介绍了这些前缀

    有关
    .code16gcc
    的更多信息,请参见。请注意,本手册来自2003年,并且
    .code16gcc
    不再是实验性的,或者至少稳定到足以供Linux使用


    由于gcc不知道如何处理汇编代码,所以调用约定将保持不变。是可用于生成引导加载程序的ld脚本。

    您可以使用链接器脚本使用gcc链接器创建普通二进制文件。 关键是输出_格式(二进制)指令:

    //========================================
    FILE: linker.ld
    //========================================
    OUTPUT_FORMAT(binary)
    SECTIONS {
        .text : { *(.text) }
        .data : { *(.data) }
        .bss  : { *(.bss)  }
    }
    //========================================
    
    我在makefile中调用了链接器,如下所示(而linker.ld是链接器脚本文件):

    我已经用

    //========================================
    gcc -nostdinc -nostdlib -ffreestanding -c <code files> -o theObjectCode.o
    //========================================
    
    汇编代码调用C语言文件loaderMain.C中定义的符号。 为了生成16位模式兼容的代码,您必须在使用的每个C文件的第一行代码之前声明16位指令集的用法。这只能通过内联汇编指令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头声明如下所示:

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

    希望这能有所帮助。干杯。

    您可能需要检查编译器。我很久没有构建Linux内核了,但我记得2.2系列使用它来编译一些引导代码。还有一个。GCC4.3有一个16位x86补丁:请参阅。
      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();
      }
    
    //======================
      .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.
    //=====================
    
    //=====================
    void kbdread(char* pc, (unsigned char)* psc);
    //=====================