实验目的

  • 配置实验所需的工程环境
  • 完成基本程序的搭建
  • 创建工程文件,为后续实验做准备

实验内容

安装工具链

  1. 交叉编译工具链

    选用ARM官网的aarch64 11.2版本,并将目录加入到环境变量PATH中。用:

    aarch64-none-elf-gcc --version
    

    测试是否安装成功:

    image-20241024224215923

交叉编译: 在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码,我们就称这种编译器支持交叉编译。这个编译过程就叫交叉编译。简单地说,就是在一个平台上生成另一个平台上的可执行代码。要进行交叉编译,我们需要在主机平台上安装对应的交叉编译工具链,然后用这个交叉编译工具链编译我们的源代码,最终生成可在目标平台上运行的代码。在本实验中,我们利用arm-linux-gcc编译器,可编译出针对Linux ARM平台的可执行代码。

  1. QEMU模拟器

    使用:

    sudo apt-get update
    sudo apt-get install qemu
    sudo apt-get install qemu-system
    

    安装qemu模拟器,QEMU(Quick EMUlator)是一款功能强大的开源模拟器和虚拟化软件,它能够模拟各种处理器架构,包括x86、ARM、MIPS等。QEMU不仅可以在各种平台上模拟不同的处理器架构,还支持虚拟化技术,使得用户可以在虚拟机中运行操作系统。

    通过:

    qemu-system-x86_64 --version
    

    检测是否安装成功:

    image-20241024224248013

  2. CMake

​ 使用:

sudo apt-get install cmake

安装CMake,CMake是一个跨平台的编译(Build)工具,可以用简单的语句来描述所有平台的编译过程。CMake能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。

通过:

cmake --version

检测是否安装成功:

image-20241024224323650

创建裸机(Bare Metal)程序

由于我们的目标是编写一个操作系统,所以我们需要创建一个独立于操作系统的可执行程序,又称 独立式可执行程序(freestanding executable) 或 裸机程序(bare-metal executable) 。这意味着所有依赖于操作系统的库我们都不能使用。比如 std 中的大部分内容(io, thread, file system, etc.)都需要操作系统的支持,所以这部分内容我们不能使用。

但是,不依赖与操作系统的语言特性还是可以继续使用的。

创建项目

参照UniProton设计项目的目录层次,但为了理解方便,将会进行相应简化。其初始目录结构如下图所示:

image-20240409172817224

  • src :放置包括main.c在内的源代码
  • bsp:包括 CMakeLists.txt 和两个汇编文件 start.S 和 prt_reset_vector.S。
  • include:有 prt_typedef.h 头文件,它是 UniProton 所使用的基本数据类型和结构的定义,如 U8、U16、U32、U64等。

下面是各文件的作用:

  • main.c

    #include "prt_typedef.h"
    
    #define UART_REG_WRITE(value, addr)  (*(volatile U32 *)((uintptr_t)addr) = (U32)value)
    
    S32 main(void)
    {
        char out_str[] = "AArch64 Bare Metal";
    
        int length = sizeof(out_str) / sizeof(out_str[0]);
    
        // 逐个输出字符
        for (int i = 0; i < length - 1; i++) {
            UART_REG_WRITE(out_str[i], 0x9000000);
        }
    }
    

    main 函数的主要功能(L12-L13)是把 out_str 中的字符通过宏 UART_REG_WRITE 逐个写入地址为 0x9000000 的地方。

  • prt_typedef.h

    UniProton 所使用的基本数据类型和结构的定义,如 U8、U16、U32、U64等。

  • start.s

     .global   OsEnterMain
        .extern __os_sys_sp_end
    
        .type     start, function
        .section  .text.bspinit, "ax"
        .balign   4
    
        .global OsElxState
        .type   OsElxState, @function
    OsElxState:
        MRS    x6, CurrentEL // 把系统寄存器 CurrentEL 的值读入到通用寄存器 x6 中
        MOV    x2, #0x4 // CurrentEL EL1: bits [3:2] = 0b01
        CMP    w6, w2
    
        BEQ Start // 若 CurrentEl 为 EL1 级别,跳转到 Start 处执行,否则死循环。
    
    OsEl2Entry:
        B OsEl2Entry
    
    Start:
        LDR    x1, =__os_sys_sp_end // 符号在ld文件中定义
        BIC    sp, x1, #0xf // 设置栈指针
    
        B      OsEnterMain
    
    OsEnterReset:
        B      OsEnterReset
    

    用于操作系统的启动代码,主要功能是初始化一些系统寄存器和设置栈指针,然后跳转到 OsEnterMain 函数执行。

  • prt_reset_vector.S

    DAIF_MASK = 0x1C0       // disable SError Abort, IRQ, FIQ
    
        .global  OsVectorTable
        .global  OsEnterMain
    
        .section .text.startup, "ax"
    OsEnterMain:
        BL      main
    
        MOV     x2, DAIF_MASK // bits [9:6] disable SError Abort, IRQ, FIQ
        MSR     DAIF, x2 // 把通用寄存器 x2 的值写入系统寄存器 DAIF 中
    
    EXITLOOP:
        B EXITLOOP
    

    它的作用是启动操作系统,总的来说,这段汇编代码的功能是调用 main 函数,然后设置 DAIF 寄存器以屏蔽SError Abort、IRQ和FIQ(因为中断处理尚未设置),最后进入一个无限循环。

  • aarch64-qemu.ld(仅展示部分)

    ENTRY(__text_start)
    
    _stack_size = 0x10000;
    _heap_size = 0x10000;
    
    MEMORY
    {
        IMU_SRAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x800000 /* 内存区域 */
        MMU_MEM (rwx) : ORIGIN = 0x40800000, LENGTH = 0x800000 /* 内存区域 */
    }
    
    SECTIONS
    {
        text_start = .;
        .start_bspinit :
        {
            __text_start = .; /* __text_start 指向当前位置, "." 表示当前位置 */
            KEEP(*(.text.bspinit))
        } > IMU_SRAM
    
        ... ... ...
    
        .heap (NOLOAD) :
        {
            . = ALIGN(8);
            PROVIDE (__HEAP_INIT = .);
            . = . + _heap_size; /* 堆空间 */
            . = ALIGN(8);
            PROVIDE (__HEAP_END = .);
        } > IMU_SRAM
    
        .stack (NOLOAD) :
        {
            . = ALIGN(8);
            PROVIDE (__os_sys_sp_start = .);
            . = . + _stack_size; /* 栈空间 */
            . = ALIGN(8);
            PROVIDE (__os_sys_sp_end = .);
        } > IMU_SRAM
        end = .;
    
        ... ... ...
    }
    

    该文件为连接脚本,链接脚本主要的工作就是告诉链接器如何将各个目标文件、库中的段进行组织,生成输出文件,该输出文件可以是动态库、可执行文件,大部分的链接脚本都只做这个事,因此链接并不算是一个很难理解的过程。

    通常每个输入文件中都存在多个段,每个段的描述信息被单独保存在段表中,而段的信息单独保存,链接器通过分析段表中的内容,获取段相应的信息,比如段的读写属性、size、文件内偏移。

    链接过程之后生成可加载的输出文件,通常是可执行文件,对于输出文件而言,内容是以 segment 的形式进行组织的,组织的依据是根据各个段的读写属性来确定的,对于代码部分通常是 读+执行 的权限,对于数据通常是 读+写 的属性,相同属性的段被组织在一起,最终在执行时被加载到内存中。

    在给出的部分中,

    • ENTRY(__text_start): 定义程序的入口点为 __text_start

    • MEMORY: 定义了程序的内存布局,比如IMU_SRAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x800000: 定义了一个名为 IMU_SRAM 的内存区域,起始地址为 0x40000000,长度为 0x800000 字节。

    • SECTIONS: 定义了不同段(sections)在内存中的放置方式,比如start_bspinit: 开始了一个名为 .start_bspinit 的段;heap (NOLOAD): 定义了一个 heap 段,但不会加载到内存中;stack (NOLOAD): 定义了一个 stack 段,同样不会加载到内存中。

    这个链接脚本的作用是定义了程序在内存中的布局,指导链接器将不同的段放置到不同的内存区域,并定义了程序的入口点和一些符号,以便在程序中引用。

工程构建

  1. src下的CMakeList.txt

    cmake_minimum_required(VERSION 3.12)
    
    set(CMAKE_SYSTEM_NAME "Generic") # 目标系统(baremental):  cmake/tool_chain/uniproton_tool_chain_gcc_arm64.cmake 写的是Linux
    set(CMAKE_SYSTEM_PROCESSOR "aarch64") # 目标系统CPU
    
    set(TOOLCHAIN_PATH "/home/aarch64-none-elf") # 修改为交叉工具链实际所在目录 build.py config.xml中定义
    set(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-gcc)
    set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-g++)
    set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-gcc)
    set(CMAKE_LINKER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-ld)
    
    # 定义编译和链接等选项
    set(CC_OPTION "-Os   -Wformat-signedness    -Wl,--build-id=none   -fno-PIE -fno-PIE --specs=nosys.specs -fno-builtin -fno-dwarf2-cfi-asm -fomit-frame-pointer -fzero-initialized-in-bss -fdollars-in-identifiers -ffunction-sections -fdata-sections -fno-aggressive-loop-optimizations -fno-optimize-strlen -fno-schedule-insns -fno-inline-small-functions -fno-inline-functions-called-once -fno-strict-aliasing -finline-limit=20  -mlittle-endian -nostartfiles -funwind-tables")
    set(AS_OPTION "-Os   -Wformat-signedness    -Wl,--build-id=none   -fno-PIE -fno-PIE --specs=nosys.specs -fno-builtin -fno-dwarf2-cfi-asm -fomit-frame-pointer -fzero-initialized-in-bss -fdollars-in-identifiers -ffunction-sections -fdata-sections -fno-aggressive-loop-optimizations -fno-optimize-strlen -fno-schedule-insns -fno-inline-small-functions -fno-inline-functions-called-once -fno-strict-aliasing -finline-limit=20  -mlittle-endian -nostartfiles -funwind-tables")
    set(LD_OPTION " ")
    set(CMAKE_C_FLAGS "${CC_OPTION} ")
    set(CMAKE_ASM_FLAGS "${AS_OPTION} ")
    set(CMAKE_LINK_FLAGS "${LD_OPTION} -T ${CMAKE_CURRENT_SOURCE_DIR}/aarch64-qemu.ld") # 指定链接脚本
    set(CMAKE_EXE_LINKER_FLAGS "${LD_OPTION} -T ${CMAKE_CURRENT_SOURCE_DIR}/aarch64-qemu.ld") # 指定链接脚本
    set (CMAKE_C_LINK_FLAGS " ")
    set (CMAKE_CXX_LINK_FLAGS " ")
    
    set(HOME_PATH ${CMAKE_CURRENT_SOURCE_DIR})
    
    set(APP "miniEuler") # APP变量,后面会用到 ${APP}
    project(${APP} LANGUAGES C ASM) # 工程名及所用语言
    set(CMAKE_BUILD_TYPE Debug) # 生成 Debug 版本
    
    include_directories( # include 目录
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/bsp
    )
    
    add_subdirectory(bsp) # 包含子文件夹的内容
    
    list(APPEND OBJS $<TARGET_OBJECTS:bsp>)
    add_executable(${APP} main.c ${OBJS}) # 可执行文件
    

    这段 CMakeLists.txt 文件用于配置 CMake 项目的构建过程,它描述了项目的目标系统、工具链路径、编译选项、链接选项以及源代码组织等内容。比如:

    • set(CMAKE_SYSTEM_NAME "Generic"): 设置了目标系统为 “Generic”,表示目标系统为裸机环境而非特定的操作系统;

    • set(TOOLCHAIN_PATH "/home/aarch64-none-elf"): 设置了交叉编译工具链的安装路径;

    • CC_OPTION定义了 C 编译选项,AS_OPTION定义了汇编编译选项,这两个定义包含了一系列的编译选项,例如优化级别、警告级别、调试信息等。这些选项会在编译过程中传递给相应的编译器。

    • LD_OPTION定义了链接器选项,在这个文件中,LD_OPTION 被设置为空,即没有额外的链接选项。然而,实际项目中可能会根据需要添加一些链接选项,例如链接脚本、库路径等。

    • include_directories(): 这个函数用于指定包含目录,即告诉编译器在哪里搜索头文件。

    • add_subdirectory(bsp): 这个函数用于包含子目录。在这个文件中,将 bsp 子目录包含进来,该子目录中包含了 BSP相关的代码。

    • add_executable(${APP} main.c ${OBJS}): 这个函数用于定义可执行文件。在这个文件中,使用了 add_executable() 函数定义了一个名为 ${APP} 的可执行文件,该可执行文件由 main.cOBJS(BSP 相关的目标文件)组成。

  2. src/bsp下的CMakeLists.txt

    set(SRCS start.S prt_reset_vector.S )
    add_library(bsp OBJECT ${SRCS})  # OBJECT类型只编译生成.o目标文件,但不实际链接成库
    

    这段代码的作用是将 start.Sprt_reset_vector.S 这两个汇编源文件编译成目标文件(.o 文件),但不将它们链接成一个库文件。

编译运行

  • 编译

首先在项目目录 lab1 下创建 build 目录用于编译生成,然后进入 build 目录执行:

cmake ../src
cmake --build .

image-20241024224508279

  • 运行

在项目目录 lab1 下执行:

qemu-system-aarch64 -machine virt -m 1024M -cpu cortex-a53 -nographic -kernel build/miniEuler  -s

image-20241024224440048

调试支持

GDB简单调试方法

通过QEMU运行程序并启动调试服务器,默认端口1234:

qemu-system-aarch64 -machine virt,gic-version=2 -m 1024M -cpu cortex-a53 -nographic -kernel build/miniEuler  -s -S

启动调试客户端:

aarch64-none-elf-gdb build/miniEuler

image-20241024224636661

注意此时要同时打开两个终端!

设置调试参数,开始调试:

(gdb) target remote localhost:1234
(gdb) disassemble
(gdb) n

image-20241024224716405

将调试集成到vscode

打开 main.c 文件,点击 vscode左侧的运行和调试按钮,弹出对话框选择创建 launch.json文件,增加如下配置:

image-20241024224814912

在左边面板顶部选择刚添加的 aarch64-gdb 选项,点击旁边的绿色 开始调试(F5) 按钮开始调试:

image-20241024224747093

在调试时,可以在调试控制台执行gdb命令,如:

image-20241024224838349

image-20240409203021644

image-20240409203050757

自动化脚本

之后编译及运行程序只需要执行:

sh makeMiniEuler.sh
sh runMiniEuler.sh

作业

  1. 商业操作系统都有复杂的构建系统,试简要分析 UniProton 的构建系统。(提示:UniProton 通过在根目录下执行 python build.py m4 (m4是指目标平台,还有如hi3093等)进行构建,所以构建系统的分析可从 build.py 入手进行。)

    在网上找到了build.py文件,接下来进行分段分析:

    import os
    import sys
    import time
    import shutil
    import subprocess
    import platform
    from sys import argv
    UniProton_home = os.path.dirname(os.path.abspath(__file__))
    sys.path.insert(0, "%s/cmake/common/build_auxiliary_script"%UniProton_home)
    from make_buildef import make_buildef
    sys.path.insert(0, "%s/build/uniproton_ci_lib"%UniProton_home)
    import globle
    from logs import BuilderNolog, log_msg
    from get_config_info import *
    

    这段是导入模块,脚本开始时导入了一些标准库和自定义的模块,包括os、sys、time等标准库,例如:

    • import os: 导入了名为 os 的模块,用于与操作系统进行交互。
    • import time: 导入了名为 time 的模块,用于处理时间相关的功能。
    • from make_buildef import make_buildef: 导入了自定义的 make_buildef 函数,这个函数可能用于构建相关的定义文件。

    class Compile:

      # 根据makechoice获取config的配置的环境,compile_mode, lib_type,
      def get_config(self, cpu_type, cpu_plat):
          self.compile_mode = get_compile_mode()
          self.lib_type, self.plam_type, self.hcc_path, self.kconf_dir, self.system, self.core = get_cpu_info(cpu_type, cpu_plat, self.build_machine_platform)
          if not self.compile_mode and self.lib_type and self.plam_type and self.hcc_path and self.kconf_dir:
              log_msg('error', 'load config.xml env error')
              sys.exit(0)
          self.config_file_path = '%s/build/uniproton_config/config_%s'%(self.home_path, self.kconf_dir)
    
          self.objcopy_path = self.hcc_path
    
      def setCmdEnv(self):
          self.build_time_tag = time.strftime('%Y-%m-%d_%H:%M:00')
          self.log_dir = '%s/logs/%s' % (self.build_dir, self.cpu_type)
          self.log_file = '%s.log' % self.kconf_dir
    
      def SetCMakeEnviron(self):
          os.environ["CPU_TYPE"] = self.cpu_type
          os.environ["PLAM_TYPE"] = self.plam_type
          os.environ["LIB_TYPE"] = self.lib_type
          os.environ["COMPILE_OPTION"] = self.compile_option
          os.environ["HCC_PATH"] = self.hcc_path
          os.environ["UNIPROTON_PACKING_PATH"] = self.UniProton_packing_path
          os.environ["CONFIG_FILE_PATH"] = self.config_file_path
          os.environ["LIB_RUN_TYPE"] = self.lib_run_type
          os.environ["HOME_PATH"] = self.home_path
          os.environ["COMPILE_MODE"] = self.compile_mode
          os.environ["BUILD_MACHINE_PLATFORM"] = self.build_machine_platform
          os.environ["SYSTEM"] = self.system
          os.environ["CORE"] = self.core
          os.environ["OBJCOPY_PATH"] = self.objcopy_path
          os.environ['PATH'] = '%s:%s' % (self.hcc_path, os.getenv('PATH'))
    
      # 环境准备,准备执行cmake,make,makebuildfile,CmakeList需要的环境
      # 每次compile之前请调用该函数
      def prepare_env(self, cpu_type, choice):
          # makebuildfile需要的环境kconf_dir
          # cmake需要的环境cmake_env_path,home_path(cmakelist所在的路径),home_path,
          # make cmd拼接需要的环境:home_path,UniProton_make_jx,log_dir,log_file,build_time_tag, UniProton_make_jx
    
          #根据cpu_type, choice从config文件中获取并初始化初始化hcc_path, plam_type, kconf_dir
          #根据输入分支获取
          #从编译镜像环境获取
          self.get_config(cpu_type, choice)
          self.setCmdEnv()
          self.SetCMakeEnviron()
    
      #获取编译环境是arm64还是x86,用户不感知,并将其写入环境变量。
      def getOsPlatform(self):
          self.cmake_env_path = get_tool_info('cmake', 'tool_path')
    
          if platform.uname()[-1] == 'aarch64':
              self.build_machine_platform = 'arm64'
          else:
              self.build_machine_platform = 'x86'
    
      # 获取当前编译的路径信息,配置文件信息,编译选项信息
      def __init__(self, cpu_type: str, make_option="normal", lib_run_type="FPGA", choice="ALL", make_phase="ALL",
                   UniProton_packing_path=""):
          # 当前路径信息
          self.system = ""
          self.objcopy_path = ""
          self.builder = None
          self.compile_mode = ""
          self.core = ""
          self.plam_type = ""
          self.kconf_dir = ""
          self.build_tmp_dir = ""
          self.log_dir = ""
          self.lib_type = ""
          self.hcc_path = ""
          self.log_file = ""
          self.config_file_path = ""
          self.build_time_tag = ""
          self.build_dir = globle.build_dir
          self.home_path = globle.home_path
          self.kbuild_path = globle.kbuild_path
          # 当前选项信息
          self.cpu_type = cpu_type
          self.compile_option = make_option
          self.lib_run_type = lib_run_type
          self.make_choice = choice.lower()
          self.make_phase = make_phase
          self.UniProton_packing_path = UniProton_packing_path if make_phase == "CREATE_CMAKE_FILE" else '%s/output'%self.home_path
          self.UniProton_binary_dir = os.getenv('RPROTON_BINARY_DIR')
          self.UniProton_install_file_option = os.getenv('RPROTON_INSTALL_FILE_OPTION')
          self.UniProton_make_jx = 'VERBOSE=1' if self.UniProton_install_file_option == 'SUPER_BUILD' else 'VERBOSE=1 -j' + str(os.cpu_count())
          # 当前编译平台信息
          self.getOsPlatform()
    
    
    

这一段定义了一个名为 Compile 的类,该类封装了一系列与编译相关的方法。这些方法包括获取配置信息、设置环境变量、准备环境、判断编译平台等。这些方法在后续的构建过程中会被调用,用于配置和准备构建所需的环境和参数。比如:

  • setCmdEnv(self) 函数的作用是设置编译过程中所需的环境变量。
  • SetCMakeEnviron(self) 函数的作用是设置与 CMake 相关的环境变量。
  • __init__ 函数用于初始化 Compile 类的实例对象,在创建对象时设置了对象的一些初始状态,以便后续的编译过程中使用。
#调用cmake生成Makefile文件,需要
    def CMake(self):
        if self.UniProton_binary_dir:
            self.build_tmp_dir = '%s/output/tmp/%s' % (self.UniProton_binary_dir, self.kconf_dir)
        else:
            self.build_tmp_dir = '%s/output/tmp/%s' % (self.build_dir, self.kconf_dir)
        os.environ['BUILD_TMP_DIR'] = self.build_tmp_dir

        if not os.path.exists(self.build_tmp_dir):
            os.makedirs(self.build_tmp_dir)
        if not os.path.exists(self.log_dir):
            os.makedirs(self.log_dir)

        log_msg('info', 'BUILD_TIME_TAG %s' % self.build_time_tag)
        self.builder = BuilderNolog(os.path.join(self.log_dir, self.log_file))
        if self.make_phase in ['CREATE_CMAKE_FILE', 'ALL']:
            real_path = os.path.realpath(self.build_tmp_dir)
            if os.path.exists(real_path):
                shutil.rmtree(real_path)
            os.makedirs(self.build_tmp_dir)
            #拼接cmake命令
            if self.compile_option == 'fortify':
                cmd = '%s/cmake %s -DCMAKE_TOOLCHAIN_FILE=%s/cmake/tool_chain/uniproton_tool_chain.cmake ' \
                      '-DCMAKE_C_COMPILER_LAUNCHER="sourceanalyzer;-b;%sproject" ' \
                      '-DCMAKE_INSTALL_PREFIX=%s &> %s/%s' % (
                self.cmake_env_path, self.home_path, self.home_path, self.cpu_type,
                self.UniProton_packing_path, self.log_dir, self.log_file)
            elif self.compile_option == 'hllt':
                cmd = '%s/cmake %s -DCMAKE_TOOLCHAIN_FILE=%s/cmake/tool_chain/uniproton_tool_chain.cmake ' \
                      '-DCMAKE_C_COMPILER_LAUNCHER="lltwrapper" -DCMAKE_INSTALL_PREFIX=%s &> %s/%s' % (
                self.cmake_env_path, self.home_path, self.home_path, self.UniProton_packing_path, self.log_dir, self.log_file)
            else:
                cmd = '%s/cmake %s -DCMAKE_TOOLCHAIN_FILE=%s/cmake/tool_chain/uniproton_tool_chain.cmake ' \
                      '-DCMAKE_INSTALL_PREFIX=%s &> %s/%s' % (
                self.cmake_env_path, self.home_path, self.home_path, self.UniProton_packing_path, self.log_dir, self.log_file)
            #执行cmake命令
            if self.builder.run(cmd, cwd=self.build_tmp_dir, env=None):
                log_msg('error', 'generate makefile failed!')
                return False

        log_msg('info', 'generate makefile succeed.')
        return True

    def UniProton_clean(self):
        for foldername,subfoldernames,filenames in os.walk(self.build_dir):
            for subfoldername in subfoldernames:
                if subfoldername in ['logs','output','__pycache__']:
                    folder_path = os.path.join(foldername,subfoldername)
                    shutil.rmtree(os.path.relpath(folder_path))
            for filename in filenames:
                if filename == 'prt_buildef.h':
                    file_dir = os.path.join(foldername,filename)
                    os.remove(os.path.relpath(file_dir))
        if os.path.exists('%s/cmake/common/build_auxiliary_script/__pycache__'%self.home_path):
            shutil.rmtree('%s/cmake/common/build_auxiliary_script/__pycache__'%self.home_path)
        if os.path.exists('%s/output'%self.home_path):
            shutil.rmtree('%s/output'%self.home_path)
        if os.path.exists('%s/tools/SRE/x86-win32/sp_makepatch/makepatch'%self.home_path):
            os.remove('%s/tools/SRE/x86-win32/sp_makepatch/makepatch'%self.home_path)
        if os.path.exists('%s/build/prepare/__pycache__'%self.home_path):
            shutil.rmtree('%s/build/prepare/__pycache__'%self.home_path)
        return True


    def make(self):
        if self.make_phase in ['EXECUTING_MAKE', 'ALL']:
            self.builder.run('make clean', cwd=self.build_tmp_dir, env=None)
            tmp = sys.argv
            if self.builder.run(
                    'make all %s &>> %s/%s' % (
                    self.UniProton_make_jx, self.log_dir, self.log_file), cwd=self.build_tmp_dir, env=None):
                log_msg('error', 'make %s %s  failed!' % (self.cpu_type, self.plam_type))
                return False
            sys.argv = tmp
            if self.compile_option in ['normal', 'coverity', 'single']:
                if self.builder.run('make install %s &>> %s/%s' % (self.UniProton_make_jx, self.log_dir, self.log_file), cwd=self.build_tmp_dir, env=None):
                    log_msg('error', 'make install failed!')
                    return False
            if os.path.exists('%s/%s' % (self.log_dir, self.log_file)):
                self.builder.log_format()
        
        log_msg('info', 'make %s %s succeed.' % (self.cpu_type, self.plam_type))
        return True

    def SdkCompaile(self)->bool:
        # 判断该环境中是否需要编译
        if self.hcc_path == 'None':
            return True

        self.MakeBuildef()
        if self.CMake() and self.make():
            log_msg('info', 'make %s %s lib succeed!' % (self.cpu_type, self.make_choice))
            return True
        else:
            log_msg('info', 'make %s %s lib failed!' % (self.cpu_type, self.make_choice))
            return False

    # 对外函数,调用后根据类初始化时的值进行编译
    def UniProtonCompile(self):
        #清除UniProton缓存
        if self.cpu_type == 'clean':
            log_msg('info', 'UniProton clean')
            return self.UniProton_clean()
        # 根据cpu的编译平台配置相应的编译环境。
        if self.make_choice == "all":
            for make_choice in globle.cpu_plat[self.cpu_type]:
                self.prepare_env(self.cpu_type, make_choice)
                if not self.SdkCompaile():
                    return False
        else:
            self.prepare_env(self.cpu_type, self.make_choice)
            if not self.SdkCompaile():
                return False
        return True

    def MakeBuildef(self):

        if not make_buildef(globle.home_path,self.kconf_dir,"CREATE"):
            sys.exit(1)
        log_msg('info', 'make_buildef_file.sh %s successfully.' % self.kconf_dir)

这一段定义了一些编译相关的方法,包括执行 CMake、清理构建环境、执行 Make、编译 UniProton、生成构建定义文件等。这些方法会被 Compile 类的实例对象调用,用于执行实际的编译任务。比如:

  • CMake(self) 方法的作用是启动 CMake 工具来生成项目的构建文件,为后续的编译过程做准备。
  • UniProton_clean(self) 方法的作用是清理 UniProton 项目的构建环境,即删除之前生成的临时文件、日志文件和构建产物,以便进行全新的编译。
  • MakeBuildef(self) 方法的作用是生成构建定义文件(builddef 文件)。
# argv[1]: cpu_plat 表示要编译的平台:
# argv[2]: compile_option 控制编译选项,调用不同的cmake参数,目前只有normal coverity hllt fortify single(是否编译安全c,组件化独立构建需求)
# argv[3]: lib_run_type lib库要跑的平台 faga sim等
# argv[4]: make_choice 
# argv[5]: make_phase 全量构建选项
# argv[6]: UniProton_packing_path lib库的安装位置
if __name__ == "__main__":
    default_para = ("all", "normal", "FPGA", "ALL", "ALL", "")
    if len(argv) == 1:
        para = [default_para[i] for i in range(0, len(default_para))]
    else:
        para = [argv[i+1] if i < len(argv) - 1 else default_para[i] for i in range(0,len(default_para))]

    cur_cpu_type = para[0].lower()
    cur_compile_option = para[1].lower()
    cur_lib_run_type = para[2]
    cur_make_choice = para[3]
    cur_make_phase = para[4]
    cur_UniProton_packing_path = para[5]
    for plat in globle.cpus_[cur_cpu_type]:
        UniProton_build = Compile(plat, cur_compile_option, cur_lib_run_type, cur_make_choice, cur_make_phase, cur_UniProton_packing_path)
        if not UniProton_build.UniProtonCompile():
            sys.exit(1)
    sys.exit(0)

这一段是程序的入口,它根据命令行参数调用 Compile 类进行编译。根据命令行参数解析出编译的相关参数,然后根据这些参数创建 Compile 类的实例对象,并调用 UniProtonCompile 方法进行编译。

  1. 学习如何调试项目。

​ 以.c文件为例。

  1. 向编译器(如gcc 或 g++) 添加-g选项来实现,生成带有调试信息的可执行文件(gcc -g program.c -o program)

  2. 启动gdb (gdb program)

    img

  3. 设置断点,当执行运行到这时,gdb会暂停执行(break main)

  4. 使用r命令运行程序,程序会在你设置的第一个断点处暂停

  5. 单步执行: 使用n命令执行下一行代码

  6. x指令可以用来查看内存的值,p指令可以用来查看变量的值,bt指令用来查看堆栈信息

  7. 一些其他命令

    • 运行命令

    image-20240422213455388

    • 断点

    image-20240422213519545

    • 运行信息

    image-20240422213538821

  8. 使用q指令退出调试

在vscode上调试:

  • 查看指定地址的内存内容。在调试控制台执行 -exec x/20xw 0x40000000 即可,其中 x表示查看命令,20表示查看数量,x表示格式,可选格式包括 Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),t(binary), f(float), a(address), i(instruction), c(char) and s(string).Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).,最后的 w表示字宽,b表示单字节,h表示双字节,w表示四字节,g表示八字节。还可以是指令:-exec x/20i 0x40000000; 字符串:-exec x/20s 0x40000000

image-20241024224927219

  • 显示所有寄存器。-exec info all-registers

image-20240428231618462

  • 查看寄存器内容。-exec p/x $pc

image-20240428231641005

  • 修改寄存器内容。-exec set $x24 = 0x5

image-20240428231709224

  • 修改指定内存位置的内容。-exec set {int}0x4000000 = 0x1 或者 -exec set *((int *) 0x4000000) = 0x1

image-20240428231733858

  • 修改指定MMIO 寄存器的内容。 -exec set *((volatile int *) 0x08010004) = 0x1
  • image-20240428231755606
  • 退出调试 -exec q
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐