Assembly 如何在没有操作系统的情况下运行程序?

Assembly 如何在没有操作系统的情况下运行程序?,assembly,x86,operating-system,bootloader,osdev,Assembly,X86,Operating System,Bootloader,Osdev,在没有操作系统运行的情况下,如何独自运行程序? 您是否可以创建计算机可以在启动时加载和运行的汇编程序,例如,从闪存驱动器启动计算机并运行CPU上的程序 在没有操作系统运行的情况下,如何独自运行程序 您将二进制代码放在重新启动后处理器查找的位置(例如ARM上的地址0) 您是否可以创建计算机可以在启动时加载和运行的汇编程序(例如,从闪存驱动器启动计算机并运行驱动器上的程序) 对这个问题的一般回答是:这是可以做到的。 它通常被称为“裸机编程”。 要从闪存驱动器读取数据,您需要知道什么是USB,并且需要

在没有操作系统运行的情况下,如何独自运行程序? 您是否可以创建计算机可以在启动时加载和运行的汇编程序,例如,从闪存驱动器启动计算机并运行CPU上的程序

在没有操作系统运行的情况下,如何独自运行程序

您将二进制代码放在重新启动后处理器查找的位置(例如ARM上的地址0)

您是否可以创建计算机可以在启动时加载和运行的汇编程序(例如,从闪存驱动器启动计算机并运行驱动器上的程序)

对这个问题的一般回答是:这是可以做到的。 它通常被称为“裸机编程”。 要从闪存驱动器读取数据,您需要知道什么是USB,并且需要一些驱动程序来处理此USB。这个驱动器上的程序也必须是某种特定的格式,在某种特定的文件系统上。。。这是引导加载程序通常做的事情,但是如果固件只加载一小段代码,那么您的程序可以包含自己的引导加载程序,因此它是自包含的

许多臂板可以让你做这些事情。有些具有引导加载程序来帮助您进行基本设置

您可能会发现一个关于如何在Raspberry Pi上实现基本操作系统的优秀教程

编辑: 这篇文章和整个wiki.osdev.org将回答您的大部分问题


此外,如果不想直接在硬件上进行实验,可以使用qemu之类的虚拟机监控程序将其作为虚拟机运行。查看如何直接在虚拟化ARM硬件上运行“hello world”。

可运行示例

让我们创建并运行一些在没有操作系统的情况下运行的小型裸机hello world程序:

  • 带有UEFI BIOS 1.16固件的x86笔记本电脑
  • 基于ARM的
我们还将尽可能多地在QEMU仿真器上试用它们,因为这样更安全、更方便开发。QEMU测试已经在预装QEMU 2.11.1的Ubuntu18.04主机上进行

下面所有x86示例以及更多示例的代码都显示在上

如何在x86真实硬件上运行示例

记住,在真正的硬件上运行示例可能是危险的,例如,您可能会错误地擦除磁盘或阻塞硬件:仅在不包含关键数据的旧计算机上执行此操作!或者更好,使用廉价的半一次性Devboard,如覆盆子Pi,见下面的ARM示例

对于典型的x86笔记本电脑,您必须执行以下操作:

  • 将图像刻录到U盘(将破坏您的数据!):

  • 将USB插头插入计算机

  • 打开它

  • 告诉它从USB启动

    这意味着让固件在硬盘之前拾取USB

    如果这不是您的机器的默认行为,请在通电后继续按Enter、F12、ESC或其他类似的奇怪键,直到您获得一个引导菜单,您可以在其中选择从USB引导

    通常可以在这些菜单中配置搜索顺序

  • 例如,在我的T430上,我看到以下内容

    打开后,我必须按Enter键才能进入启动菜单:

    然后,这里我必须按F12选择USB作为引导设备:

    从那里,我可以选择USB作为引导设备,如下所示:

    或者,要更改引导顺序并选择USB具有更高的优先级,这样我就不必每次手动选择它,我会在“启动中断菜单”屏幕上点击F1,然后导航到:

    引导扇区

    在x86上,您可以做的最简单和最低级别的事情是创建一个,它是的类型,然后将其安装到磁盘上

    这里我们创建一个带有单个
    printf
    调用的:

    printf '\364%509s\125\252' > main.img
    sudo apt-get install qemu-system-x86
    qemu-system-x86_64 -hda main.img
    
    结果:

    请注意,即使不做任何操作,也会在屏幕上打印一些字符。这些由固件打印,用于识别系统

    在T430上,我们只看到一个带有闪烁光标的空白屏幕:

    main.img
    包含以下内容:

    • \364
      八进制==
      0xf4
      十六进制:对
      hlt
      指令的编码,它告诉CPU停止工作

      因此,我们的程序不会做任何事情:只启动和停止

      我们使用八进制是因为POSIX没有指定十六进制数

      我们可以通过以下方式轻松获得此编码:

      echo hlt > a.S
      as -o a.o a.S
      objdump -S a.o
      
      哪些产出:

      a.o:     file format elf64-x86-64
      
      
      Disassembly of section .text:
      
      0000000000000000 <.text>:
         0:   f4                      hlt
      
      其中显示了预期的:

      00000000  f4 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |.               |
      00000010  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |                |
      *
      000001f0  20 20 20 20 20 20 20 20  20 20 20 20 20 20 55 aa  |              U.|
      00000200
      
      其中
      20
      是ASCII中的空格

      BIOS固件从磁盘读取这512个字节,将它们放入内存,并将PC设置为第一个字节以开始执行它们

      Hello world引导扇区

      现在我们已经做了一个简单的程序,让我们转到一个hello world

      显而易见的问题是:如何进行IO?有几个选择:

      • 请固件(例如BIOS或UEFI)为我们执行此操作

      • VGA:一种特殊的存储区域,如果写入,它会被打印到屏幕上。可在保护模式下使用

      • 编写驱动程序并直接与显示硬件对话。这是“正确”的方法:更强大,但更复杂

      • 。这是一个非常简单的标准化协议,用于从主机终端发送和接收字符

        在台式机上,它看起来像这样:

        不幸的是,它没有在大多数现代笔记本电脑上公开,但却是开发板的常用方式,请参见下面的ARM示例

        这真是太遗憾了,因为这样的接口真的很有用

      • 使用芯片的调试功能。例如,ARM称之为他们的。在真正的硬件上,它需要一些额外的硬件和软件支持,但在模拟器上,它可以是一个免费、方便的选项

      我们到了
      hd main.img
      
      00000000  f4 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |.               |
      00000010  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |                |
      *
      000001f0  20 20 20 20 20 20 20 20  20 20 20 20 20 20 55 aa  |              U.|
      00000200
      
      .code16
          mov $msg, %si
          mov $0x0e, %ah
      loop:
          lodsb
          or %al, %al
          jz halt
          int $0x10
          jmp loop
      halt:
          hlt
      msg:
          .asciz "hello world"
      
      SECTIONS
      {
          /* The BIOS loads the code from the disk to this location.
           * We must tell that to the linker so that it can properly
           * calculate the addresses of symbols we might jump to.
           */
          . = 0x7c00;
          .text :
          {
              __start = .;
              *(.text)
              /* Place the magic boot bytes at the end of the first 512 sector. */
              . = 0x1FE;
              SHORT(0xAA55)
          }
      }
      
      as -g -o main.o main.S
      ld --oformat binary -o main.img -T link.ld main.o
      qemu-system-x86_64 -hda main.img
      
      void main(void) {
          int i;
          char s[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
          for (i = 0; i < sizeof(s); ++i) {
              __asm__ (
                  "int $0x10" : : "a" ((0x0e << 8) | s[i])
              );
          }
          while (1) {
              __asm__ ("hlt");
          };
      }
      
      .code16
      .text
      .global mystart
      mystart:
          ljmp $0, $.setcs
      .setcs:
          xor %ax, %ax
          mov %ax, %ds
          mov %ax, %es
          mov %ax, %ss
          mov $__stack_top, %esp
          cld
          call main
      
      ENTRY(mystart)
      SECTIONS
      {
        . = 0x7c00;
        .text : {
          entry.o(.text)
          *(.text)
          *(.data)
          *(.rodata)
          __bss_start = .;
          /* COMMON vs BSS: https://stackoverflow.com/questions/16835716/bss-vs-common-what-goes-where */
          *(.bss)
          *(COMMON)
          __bss_end = .;
        }
        /* https://stackoverflow.com/questions/53584666/why-does-gnu-ld-include-a-section-that-does-not-appear-in-the-linker-script */
        .sig : AT(ADDR(.text) + 512 - 2)
        {
            SHORT(0xaa55);
        }
        /DISCARD/ : {
          *(.eh_frame)
        }
        __stack_bottom = .;
        . = . + 0x1000;
        __stack_top = .;
      }
      
      set -eux
      as -ggdb3 --32 -o entry.o entry.S
      gcc -c -ggdb3 -m16 -ffreestanding -fno-PIE -nostartfiles -nostdlib -o main.o -std=c99 main.c
      ld -m elf_i386 -o main.elf -T linker.ld entry.o main.o
      objcopy -O binary main.elf main.img
      qemu-system-x86_64 -drive file=main.img,format=raw
      
      void _exit(int status) {
          __asm__ __volatile__ ("mov r0, #0x18; ldr r1, =#0x20026; svc 0x00123456");
      }
      
      enter a character
      got: a
      new alloc of 1 bytes at address 0x0x4000a1c0
      enter a character
      got: b
      new alloc of 2 bytes at address 0x0x4000a1c0
      enter a character
      
      screen /dev/ttyUSB0 115200