Linux 如何编译baremetal hello_world.c并在qemu-system-aarch64上运行它?

Linux 如何编译baremetal hello_world.c并在qemu-system-aarch64上运行它?,linux,linux-kernel,qemu,arm64,bare-metal,Linux,Linux Kernel,Qemu,Arm64,Bare Metal,作为标题,我想编译hello_world.c程序并在qemu-system-aarch64上运行它。节目如下: #include <stdio.h> int main() { printf("hello world!\n"); } 所以我选择了aarch64 elf(正确吗?)并将其安装在我的ubuntu 16.04机器上,并将bin目录添加到路径中。 如果我只是执行aarch64 elf gcc hello\u world.c我会得到未定义的_exit、_sb

作为标题,我想编译hello_world.c程序并在qemu-system-aarch64上运行它。节目如下:

#include <stdio.h>
int main()
{
printf("hello world!\n");
}
所以我选择了aarch64 elf(正确吗?)并将其安装在我的ubuntu 16.04机器上,并将bin目录添加到路径中。 如果我只是执行
aarch64 elf gcc hello\u world.c
我会得到未定义的_exit、_sbrk、_write、_close、_lseek、_read、_fstat、_isatty函数的引用错误。所以我尝试添加-spec=aem.ve-specs,但它没有抱怨(我不确定这是否正确)。 我试着运行qemu

qemu-system-aarch64 -M virt -cpu cortex-a57 -nographic -smp 1 -m 2048 -kernel a.out

而且它没有给我任何印子。我应该在这里更改什么?

请注意,我只有x86/amd64开发经验。但我认为你最初的一些假设是有缺陷的

首先,默认情况下,简单编译C源代码不会运行裸机。您使用
printf
,编译器随后会在
stdio.h-标准缓冲输入/输出
库中找到它。按照您现在的编译方式,这是动态链接到您的程序的。也就是说,当调用printf时,程序在执行过程中跳入libc。您可以通过readelf看到这一点

readelf --dynamic a.out | grep NEEDED
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
Qemu模拟机器,但不模拟操作系统。现在将动态链接的程序作为内核/OS传递。如果没有libc库,内核如何知道打印?更重要的是,由于基本引导步骤不是由您的程序完成的(设置和初始化RAM、设置设备树等),它如何知道如何执行任何操作

我假设您不想编写ARM64内核,只想进入ARM64的“裸机”开发。可能只是下载Aarch64 Linux发行版(例如)

您可以创建虚拟驱动器,安装操作系统,并在该虚拟机中进行开发,以获得虚拟“裸机”开发;)比如说什么


如果我的假设是错误的,而你确实想进入操作系统开发,那么这个问题太长了,无法在这里回答。但我建议您研究和。

您是对的,您可以使用qemu-system-aarch64来实现您的目标。 根据您的具体需要,您有多种选择:

  • 使用qemu模式,以及gcc
    --specs=rdimon.specs
    newlib、
    或使用另一个半宿主库,如Arm Trusted固件源代码中提供的库-下面的示例使用此方法

  • 提供您自己的
    syscalls.c
    ,并使用
    --specs=nosys.specs
    ld
    选项,这样您就可以在纯金属程序中使用newlib:我建议您在博客上阅读Francesco Balducci的优秀文章-下面的示例使用这种方法

  • 使用一种更像纯金属的方法,如下文所述:它确实使用
    sprintf()
    qemu-virt
    机器的
    pl011
    UART来显示结果字符串

  • gcc\u arm64\u ram.ld

    /******************************************************************************
     * @file     gcc_arm32.ld
     * @brief    GNU Linker Script for Cortex-M based device
     * @version  V2.0.0
     * @date     21. May 2019
     ******************************************************************************/
    /*
     * Copyright (c) 2009-2019 Arm Limited. All rights reserved.
     *
     * SPDX-License-Identifier: Apache-2.0
     *
     * Licensed under the Apache License, Version 2.0 (the License); you may
     * not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     * www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an AS IS BASIS, WITHOUT
     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    MEMORY
    {
      RAM   (rwx) : ORIGIN = __RAM_BASE, LENGTH = __RAM_SIZE
    }
    
    /* Linker script to place sections and symbol values. Should be used together
     * with other linker script that defines memory regions FLASH and RAM.
     * It references following symbols, which must be defined in code:
     *   Reset_Handler : Entry of reset handler
     *
     * It defines following symbols, which code can use without definition:
     *   __exidx_start
     *   __exidx_end
     *   __copy_table_start__
     *   __copy_table_end__
     *   __zero_table_start__
     *   __zero_table_end__
     *   __etext
     *   __data_start__
     *   __preinit_array_start
     *   __preinit_array_end
     *   __init_array_start
     *   __init_array_end
     *   __fini_array_start
     *   __fini_array_end
     *   __data_end__
     *   __bss_start__
     *   __bss_end__
     *   __end__
     *   end
     *   __HeapLimit
     *   __StackLimit
     *   __StackTop
     *   __stack
     */
    ENTRY(Reset_Handler)
    
    SECTIONS
    {
      .text :
      {
        KEEP(*(.vectors))
        *(.text*)
    
        KEEP(*(.init))
        KEEP(*(.fini))
    
        /* .ctors */
        *crtbegin.o(.ctors)
        *crtbegin?.o(.ctors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
        *(SORT(.ctors.*))
        *(.ctors)
    
        /* .dtors */
        *crtbegin.o(.dtors)
        *crtbegin?.o(.dtors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
        *(SORT(.dtors.*))
        *(.dtors)
    
        *(.rodata*)
    
        KEEP(*(.eh_frame*))
      } > RAM
    
      /*
       * SG veneers:
       * All SG veneers are placed in the special output section .gnu.sgstubs. Its start address
       * must be set, either with the command line option �--section-start� or in a linker script,
       * to indicate where to place these veneers in memory.
       */
    /*
      .gnu.sgstubs :
      {
        . = ALIGN(32);
      } > RAM
    */
      .ARM.extab :
      {
        *(.ARM.extab* .gnu.linkonce.armextab.*)
      } > RAM
    
      __exidx_start = .;
      .ARM.exidx :
      {
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
      } > RAM
      __exidx_end = .;
    
      .copy.table :
      {
        . = ALIGN(16);
        __copy_table_start__ = .;
        LONG (__etext)
        LONG (__data_start__)
        LONG (__data_end__ - __data_start__)
        /* Add each additional data section here */
    /*
        LONG (__etext2)
        LONG (__data2_start__)
        LONG (__data2_end__ - __data2_start__)
    */
        __copy_table_end__ = .;
      } > RAM
    
      .zero.table :
      {
        . = ALIGN(16);
        __zero_table_start__ = .;
        /* Add each additional bss section here */
    /*
        LONG (__bss2_start__)
        LONG (__bss2_end__ - __bss2_start__)
    */
        __zero_table_end__ = .;
      } > RAM
    
      /**
       * Location counter can end up 2byte aligned with narrow Thumb code but
       * __etext is assumed by startup code to be the LMA of a section in RAM
       * which must be 4byte aligned 
       */
      __etext = ALIGN(16);
    
      .data : AT (__etext)
      {
        __data_start__ = .;
        *(vtable)
        *(.data)
        *(.data.*)
    
        . = ALIGN(16);
        /* preinit data */
        PROVIDE_HIDDEN (__preinit_array_start = .);
        KEEP(*(.preinit_array))
        PROVIDE_HIDDEN (__preinit_array_end = .);
    
        . = ALIGN(16);
        /* init data */
        PROVIDE_HIDDEN (__init_array_start = .);
        KEEP(*(SORT(.init_array.*)))
        KEEP(*(.init_array))
        PROVIDE_HIDDEN (__init_array_end = .);
    
    
        . = ALIGN(16);
        /* finit data */
        PROVIDE_HIDDEN (__fini_array_start = .);
        KEEP(*(SORT(.fini_array.*)))
        KEEP(*(.fini_array))
        PROVIDE_HIDDEN (__fini_array_end = .);
    
        KEEP(*(.jcr*))
        . = ALIGN(16);
        /* All data end */
        __data_end__ = .;
    
      } > RAM
    
      /*
       * Secondary data section, optional
       *
       * Remember to add each additional data section
       * to the .copy.table above to asure proper
       * initialization during startup.
       */
    /*
      __etext2 = ALIGN(16);
    
      .data2 : AT (__etext2)
      {
        . = ALIGN(16);
        __data2_start__ = .;
        *(.data2)
        *(.data2.*)
        . = ALIGN(16);
        __data2_end__ = .;
    
      } > RAM2
    */
    
      .bss :
      {
        . = ALIGN(16);
        __bss_start__ = .;
        *(.bss)
        *(.bss.*)
        *(COMMON)
        . = ALIGN(16);
        __bss_end__ = .;
      } > RAM AT > RAM
    
      /*
       * Secondary bss section, optional
       *
       * Remember to add each additional bss section
       * to the .zero.table above to asure proper
       * initialization during startup.
       */
    /*
      .bss2 :
      {
        . = ALIGN(16);
        __bss2_start__ = .;
        *(.bss2)
        *(.bss2.*)
        . = ALIGN(16);
        __bss2_end__ = .;
      } > RAM2 AT > RAM2
    */
    
      .heap (COPY) :
      {
        . = ALIGN(16);
        __end__ = .;
        PROVIDE(end = .);
        . = . + __HEAP_SIZE;
        . = ALIGN(16);
        __HeapLimit = .;
      } > RAM
    
      .stack (ORIGIN(RAM) + LENGTH(RAM) - __STACK_SIZE) (COPY) :
      {
        . = ALIGN(16);
        __StackLimit = .;
        . = . + __STACK_SIZE;
        . = ALIGN(16);
        __StackTop = .;
      } > RAM
      PROVIDE(__stack = __StackTop);
    
      /* Check if data + heap + stack exceeds RAM limit */
      ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")
    }
    
    __RAM_BASE = 0x40000000;
    __RAM_SIZE =  0x08000000;
    __STACK_SIZE = 0x00100000;
    __HEAP_SIZE  =  0x00100000;
    INCLUDE gcc_arm64_ram.ld
    
    qemu-virt-aarch64.ld

    /******************************************************************************
     * @file     gcc_arm32.ld
     * @brief    GNU Linker Script for Cortex-M based device
     * @version  V2.0.0
     * @date     21. May 2019
     ******************************************************************************/
    /*
     * Copyright (c) 2009-2019 Arm Limited. All rights reserved.
     *
     * SPDX-License-Identifier: Apache-2.0
     *
     * Licensed under the Apache License, Version 2.0 (the License); you may
     * not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     * www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an AS IS BASIS, WITHOUT
     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    MEMORY
    {
      RAM   (rwx) : ORIGIN = __RAM_BASE, LENGTH = __RAM_SIZE
    }
    
    /* Linker script to place sections and symbol values. Should be used together
     * with other linker script that defines memory regions FLASH and RAM.
     * It references following symbols, which must be defined in code:
     *   Reset_Handler : Entry of reset handler
     *
     * It defines following symbols, which code can use without definition:
     *   __exidx_start
     *   __exidx_end
     *   __copy_table_start__
     *   __copy_table_end__
     *   __zero_table_start__
     *   __zero_table_end__
     *   __etext
     *   __data_start__
     *   __preinit_array_start
     *   __preinit_array_end
     *   __init_array_start
     *   __init_array_end
     *   __fini_array_start
     *   __fini_array_end
     *   __data_end__
     *   __bss_start__
     *   __bss_end__
     *   __end__
     *   end
     *   __HeapLimit
     *   __StackLimit
     *   __StackTop
     *   __stack
     */
    ENTRY(Reset_Handler)
    
    SECTIONS
    {
      .text :
      {
        KEEP(*(.vectors))
        *(.text*)
    
        KEEP(*(.init))
        KEEP(*(.fini))
    
        /* .ctors */
        *crtbegin.o(.ctors)
        *crtbegin?.o(.ctors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
        *(SORT(.ctors.*))
        *(.ctors)
    
        /* .dtors */
        *crtbegin.o(.dtors)
        *crtbegin?.o(.dtors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
        *(SORT(.dtors.*))
        *(.dtors)
    
        *(.rodata*)
    
        KEEP(*(.eh_frame*))
      } > RAM
    
      /*
       * SG veneers:
       * All SG veneers are placed in the special output section .gnu.sgstubs. Its start address
       * must be set, either with the command line option �--section-start� or in a linker script,
       * to indicate where to place these veneers in memory.
       */
    /*
      .gnu.sgstubs :
      {
        . = ALIGN(32);
      } > RAM
    */
      .ARM.extab :
      {
        *(.ARM.extab* .gnu.linkonce.armextab.*)
      } > RAM
    
      __exidx_start = .;
      .ARM.exidx :
      {
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
      } > RAM
      __exidx_end = .;
    
      .copy.table :
      {
        . = ALIGN(16);
        __copy_table_start__ = .;
        LONG (__etext)
        LONG (__data_start__)
        LONG (__data_end__ - __data_start__)
        /* Add each additional data section here */
    /*
        LONG (__etext2)
        LONG (__data2_start__)
        LONG (__data2_end__ - __data2_start__)
    */
        __copy_table_end__ = .;
      } > RAM
    
      .zero.table :
      {
        . = ALIGN(16);
        __zero_table_start__ = .;
        /* Add each additional bss section here */
    /*
        LONG (__bss2_start__)
        LONG (__bss2_end__ - __bss2_start__)
    */
        __zero_table_end__ = .;
      } > RAM
    
      /**
       * Location counter can end up 2byte aligned with narrow Thumb code but
       * __etext is assumed by startup code to be the LMA of a section in RAM
       * which must be 4byte aligned 
       */
      __etext = ALIGN(16);
    
      .data : AT (__etext)
      {
        __data_start__ = .;
        *(vtable)
        *(.data)
        *(.data.*)
    
        . = ALIGN(16);
        /* preinit data */
        PROVIDE_HIDDEN (__preinit_array_start = .);
        KEEP(*(.preinit_array))
        PROVIDE_HIDDEN (__preinit_array_end = .);
    
        . = ALIGN(16);
        /* init data */
        PROVIDE_HIDDEN (__init_array_start = .);
        KEEP(*(SORT(.init_array.*)))
        KEEP(*(.init_array))
        PROVIDE_HIDDEN (__init_array_end = .);
    
    
        . = ALIGN(16);
        /* finit data */
        PROVIDE_HIDDEN (__fini_array_start = .);
        KEEP(*(SORT(.fini_array.*)))
        KEEP(*(.fini_array))
        PROVIDE_HIDDEN (__fini_array_end = .);
    
        KEEP(*(.jcr*))
        . = ALIGN(16);
        /* All data end */
        __data_end__ = .;
    
      } > RAM
    
      /*
       * Secondary data section, optional
       *
       * Remember to add each additional data section
       * to the .copy.table above to asure proper
       * initialization during startup.
       */
    /*
      __etext2 = ALIGN(16);
    
      .data2 : AT (__etext2)
      {
        . = ALIGN(16);
        __data2_start__ = .;
        *(.data2)
        *(.data2.*)
        . = ALIGN(16);
        __data2_end__ = .;
    
      } > RAM2
    */
    
      .bss :
      {
        . = ALIGN(16);
        __bss_start__ = .;
        *(.bss)
        *(.bss.*)
        *(COMMON)
        . = ALIGN(16);
        __bss_end__ = .;
      } > RAM AT > RAM
    
      /*
       * Secondary bss section, optional
       *
       * Remember to add each additional bss section
       * to the .zero.table above to asure proper
       * initialization during startup.
       */
    /*
      .bss2 :
      {
        . = ALIGN(16);
        __bss2_start__ = .;
        *(.bss2)
        *(.bss2.*)
        . = ALIGN(16);
        __bss2_end__ = .;
      } > RAM2 AT > RAM2
    */
    
      .heap (COPY) :
      {
        . = ALIGN(16);
        __end__ = .;
        PROVIDE(end = .);
        . = . + __HEAP_SIZE;
        . = ALIGN(16);
        __HeapLimit = .;
      } > RAM
    
      .stack (ORIGIN(RAM) + LENGTH(RAM) - __STACK_SIZE) (COPY) :
      {
        . = ALIGN(16);
        __StackLimit = .;
        . = . + __STACK_SIZE;
        . = ALIGN(16);
        __StackTop = .;
      } > RAM
      PROVIDE(__stack = __StackTop);
    
      /* Check if data + heap + stack exceeds RAM limit */
      ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")
    }
    
    __RAM_BASE = 0x40000000;
    __RAM_SIZE =  0x08000000;
    __STACK_SIZE = 0x00100000;
    __HEAP_SIZE  =  0x00100000;
    INCLUDE gcc_arm64_ram.ld
    
    startup.s

                    .title startup64.s
                    .arch armv8-a
                    .text
                    .section .text.startup,"ax"    
                    .globl Reset_Handler
    Reset_Handler:
                    ldr x0, =__StackTop
                    mov sp, x0
                    bl  main
    wait:           wfe
                    b wait
                   .end
    
    pl011.c

    #include <stdint.h>
    
    static volatile unsigned int * const UART0DR = ( unsigned int * ) ( uintptr_t * ) 0x9000000;
    
    int putchar(int c)
    {
        *UART0DR = c; /* Transmit char */
         return c;
    }
    
    void putchar_uart0( int c )
    {
        *UART0DR = c; /* Transmit char */
    }
    
    void putc_uart0( int c )
    {
        *UART0DR = c; /* Transmit char */
    }
    
    void print_uart0( const char * s )
    {
        while( *s != '\0' )                     /* Loop until end of string */
        {
            *UART0DR = ( unsigned int ) ( *s ); /* Transmit char */
            s++;                                /* Next char */
        }
    }
    
    void puts_uart0( const char * s )
    {
        while( *s != '\0' )                     /* Loop until end of string */
        {
            *UART0DR = ( unsigned int ) ( *s ); /* Transmit char */
            if (*s == '\n') {
               *UART0DR = ( unsigned int ) ( '\r' );
            } 
            s++;                                /* Next char */
        }
    }
    
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <errno.h>
    
    #include "pl011.h"
    
    // angel/semihosting interface
    #define SYS_WRITE0                       0x04 
    static uint64_t semihosting_call(uint32_t operation, uint64_t parameter)
    {
        __asm("HLT #0xF000");
    }
    
    // syscall stubs
    int _close (int fd)
    {
        errno = EBADF;
        return -1;
    }
    
    int _isatty (int fd)
    {
        return 1;
    }
    
    int _fstat (int fd, struct stat * st)
    {
        errno = EBADF;
        return -1;
    }
    
    off_t _lseek (int fd, off_t ptr, int dir)
    {
        errno = EBADF;
        return (off_t) -1;
    }
    
    int _read (int fd, void *ptr, size_t len)
    {
        errno = EBADF;
        return -1;
    }
    
    int _write (int fd, const char *ptr, size_t len)
    {
        for (size_t i = 0; i < len; i++) {
            putchar_uart0(ptr[i]);
        }
        return len;
    }
    
    void main()
    {
       char buffer[BUFSIZ];
       uint64_t regCurrentEL;
    
       __asm volatile ("mrs %0, CurrentEL" : "=r" (regCurrentEL));
    
       // UART0
       sprintf(buffer, "Hello EL%d World!\n", (regCurrentEL >> 2) & 0b11);
       puts_uart0(buffer);
    
       // angel/semihosting interface
       sprintf(buffer, "Hello semi-hosted EL%d World!\n", (regCurrentEL >> 2) & 0b11);
       semihosting_call(SYS_WRITE0, (uint64_t) (uintptr_t)  buffer);
    
       // newlib -  custom syscalls.c, with _write() using UART0
       printf("Hello EL%d World! (syscalls version)\n", (regCurrentEL >> 2) & 0b11);
    }
    
    qemu-virt-aarch64.c

    #include <stdint.h>
    
    static volatile unsigned int * const UART0DR = ( unsigned int * ) ( uintptr_t * ) 0x9000000;
    
    int putchar(int c)
    {
        *UART0DR = c; /* Transmit char */
         return c;
    }
    
    void putchar_uart0( int c )
    {
        *UART0DR = c; /* Transmit char */
    }
    
    void putc_uart0( int c )
    {
        *UART0DR = c; /* Transmit char */
    }
    
    void print_uart0( const char * s )
    {
        while( *s != '\0' )                     /* Loop until end of string */
        {
            *UART0DR = ( unsigned int ) ( *s ); /* Transmit char */
            s++;                                /* Next char */
        }
    }
    
    void puts_uart0( const char * s )
    {
        while( *s != '\0' )                     /* Loop until end of string */
        {
            *UART0DR = ( unsigned int ) ( *s ); /* Transmit char */
            if (*s == '\n') {
               *UART0DR = ( unsigned int ) ( '\r' );
            } 
            s++;                                /* Next char */
        }
    }
    
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <errno.h>
    
    #include "pl011.h"
    
    // angel/semihosting interface
    #define SYS_WRITE0                       0x04 
    static uint64_t semihosting_call(uint32_t operation, uint64_t parameter)
    {
        __asm("HLT #0xF000");
    }
    
    // syscall stubs
    int _close (int fd)
    {
        errno = EBADF;
        return -1;
    }
    
    int _isatty (int fd)
    {
        return 1;
    }
    
    int _fstat (int fd, struct stat * st)
    {
        errno = EBADF;
        return -1;
    }
    
    off_t _lseek (int fd, off_t ptr, int dir)
    {
        errno = EBADF;
        return (off_t) -1;
    }
    
    int _read (int fd, void *ptr, size_t len)
    {
        errno = EBADF;
        return -1;
    }
    
    int _write (int fd, const char *ptr, size_t len)
    {
        for (size_t i = 0; i < len; i++) {
            putchar_uart0(ptr[i]);
        }
        return len;
    }
    
    void main()
    {
       char buffer[BUFSIZ];
       uint64_t regCurrentEL;
    
       __asm volatile ("mrs %0, CurrentEL" : "=r" (regCurrentEL));
    
       // UART0
       sprintf(buffer, "Hello EL%d World!\n", (regCurrentEL >> 2) & 0b11);
       puts_uart0(buffer);
    
       // angel/semihosting interface
       sprintf(buffer, "Hello semi-hosted EL%d World!\n", (regCurrentEL >> 2) & 0b11);
       semihosting_call(SYS_WRITE0, (uint64_t) (uintptr_t)  buffer);
    
       // newlib -  custom syscalls.c, with _write() using UART0
       printf("Hello EL%d World! (syscalls version)\n", (regCurrentEL >> 2) & 0b11);
    }
    
    运行:

    /opt/qemu-5.2.0/bin/qemu-system-aarch64 -semihosting -m 128M -nographic  -monitor none -serial stdio  -machine virt,gic-version=2,secure=on,virtualization=on -cpu cortex-a53 -kernel virt.elf
    Hello EL3 World!
    Hello semi-hosted EL3 World!
    Hello EL3 World! (syscalls version)
    

    您可以非常轻松地编译aarch64 linux elf二进制文件,然后运行它,但不能使用
    qemu-system-aarch64
    ,而是
    qemu-aarch64
    。你确定这不是你想做的吗?我的印象是你可能真的想要这个。@谢谢,我想要的是使用系统模式。qemu-system-aarch64。这些天我们正在开发一个SoC。然后你需要一个内核,或者至少一些嵌入式库来工作。这里您只有一个已编译的二进制文件,但没有内核,没有libc。我们不能在qemu上运行裸机程序吗?这是一个重要的问题。我认为这应该是可能的。(几年前我们在qemu上运行linux和rtems,但不是baremtal)是的,你可以。但是如果在C代码中有一个
    printf()
    ,它应该做什么?它应该将输出写入标准输出,标准输出应该连接到一些终端硬件。这可以是qemu硬件虚拟化为您提供的模拟硬件,也可以是真正的硬件(可能是一些LCD显示器)。与此(虚拟或物理)硬件交互的例程应该在哪里?交叉编译器将C代码(包括
    printf
    )编译为二进制代码。但是printf例程的实现应该在哪里呢。所以它都是静态链接的(因为它是纯金属工具链,而不是用于编译要在linux上运行的应用程序的linux工具链)。我的理解是,如果我们使用裸金属编译器,基本的工作是由编译器完成的(创建堆栈、堆等)。我们现在正在开发SoC,所以我必须为我们的芯片准备qemu(现在是非常初始的开发阶段),我想用裸金属程序验证qemu模型(如果reg写入,读取是为一个伪外围设备完成的。)哇,这是一个彻底的解释!非常感谢你,我很快就会试试这个。