写在前面:
从2024年开始,HNU信息院计科和智能专业统一使用李老师基于UniProton编写的新实验,一共有八个实验,如果仅是按照指导书的内容将实验跑通还是比较容易的(第一个实验除外),而对于想要在将来走系统方向的同学,或者遇上比较苛刻的助教,需要将整个实验的代码完全看懂,那就是一件任务量很大的事情了。我在完成这一系列实验的过程也十分坎坷,获得了很多同学的帮助,向他们表示感谢,本文旨在为下一届的同学提供一些参考和帮助,需要注意的是,每个实验的作业题没有标准的答案,仅由我与其他同学讨论得到,欢迎指正。
一、实验目的
1.安装交叉工具链(aarch64)以及安装QEMU模拟器
2.创建裸机(Bare Metal)程序
3.构件工程
4.完成调试支持
5.建立自动化脚本
二、实验过程
1.配置交叉工具链及安装QEMU模拟器
1.安装交叉工具链
下载工具链,以下载工具链版本为11.2,宿主机为x86 64位 Linux机器为例
wget https://developer.arm.com/-/media/Files/downloads/gnu/11.2-2022.02/binrel/gcc-arm-11.2-2022.02-x86_64-aarch64-none-elf.tar.xz
解压工具链
tar -xf gcc-arm-(按Tab键补全)
重命名工具链目录
mv gcc-arm-(按Tab键补全) aarch64-none-elf
2.将工具链目录加入到环境变量
这里的话需要在终端的命令行中输入vim ~/.bashrc指令,然后在最底下新增加一行将自己的工具链目录添加到环境变量,例如此处为/home/moonwine/arrch64-none-elf/bin,需要根据自己实际的目录进行替换,最后完成编辑后,在命令行输入source ~/.bashrc进行环境变量设置。
3.测试工具链是否安装完成
使用最常用的查看版本指令,如果正常显示则表明成功安装,如果报错多半是环境变量没有添加成功,检查工具链目录是否正确设置。
4.安装QEMU模拟器
sudo apt-get update
sudo apt-get install qemu
sudo apt-get install qemu-system
5.安装Cmake
sudo apt-get install cmake
2.创建裸机程序
1.按照UniProton设计项目的目录层次:
src目录下放置了所有的的源代码,src目录下包含bsp目录以及include目录,bsp目录存放与硬件关系密切的代码,本实验中包含CMakeList.txt以及两个汇编文件start.S和prt_reset_vector.S,include目录中存放项目的绝大部分头文件,如prt_typedef.h文件,该文件中实现了UniProton使用的基本数据类型和结构的定义。
2.在src目录下创建main.c:
就直接复制指导书就好
3.在src/include目录下创建prt_typedef.h:
/*
* Copyright (c) 2009-2022 Huawei Technologies Co., Ltd. All rights reserved.
*
* UniProton is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2009-12-22
* Description: 定义基本数据类型和数据结构。
*/
#ifndef PRT_TYPEDEF_H
#define PRT_TYPEDEF_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif /* __cpluscplus */
#endif /* __cpluscplus */
typedef unsigned char U8;
typedef unsigned short U16;
typedef unsigned int U32;
typedef unsigned long long U64;
typedef signed char S8;
typedef signed short S16;
typedef signed int S32;
typedef signed long long S64;
typedef void *VirtAddr;
typedef void *PhyAddr;
#ifndef OS_SEC_ALW_INLINE
#define OS_SEC_ALW_INLINE
#endif
#ifndef INLINE
#define INLINE static __inline __attribute__((always_inline))
#endif
#ifndef OS_EMBED_ASM
#define OS_EMBED_ASM __asm__ __volatile__
#endif
/* 参数不加void表示可以传任意个参数 */
typedef void (*OsVoidFunc)(void);
#define ALIGN(addr, boundary) (((uintptr_t)(addr) + (boundary) - 1) & ~((uintptr_t)(boundary) - 1))
#define TRUNCATE(addr, size) ((addr) & ~((uintptr_t)(size) - 1))
#ifdef YES
#undef YES
#endif
#define YES 1
#ifdef NO
#undef NO
#endif
#define NO 0
#ifndef FALSE
#define FALSE ((bool)0)
#endif
#ifndef TRUE
#define TRUE ((bool)1)
#endif
#ifndef NULL
#define NULL ((void *)0)
#endif
#define OS_ERROR (U32)(-1)
#define OS_INVALID (-1)
#ifndef OS_OK
#define OS_OK 0
#endif
#ifndef OS_FAIL
#define OS_FAIL 1
#endif
#ifndef U8_INVALID
#define U8_INVALID 0xffU
#endif
#ifndef U12_INVALID
#define U12_INVALID 0xfffU
#endif
#ifndef U16_INVALID
#define U16_INVALID 0xffffU
#endif
#ifndef U32_INVALID
#define U32_INVALID 0xffffffffU
#endif
#ifndef U64_INVALID
#define U64_INVALID 0xffffffffffffffffUL
#endif
#ifndef U32_MAX
#define U32_MAX 0xFFFFFFFFU
#endif
#ifndef S32_MAX
#define S32_MAX 0x7FFFFFFF
#endif
#ifndef S32_MIN
#define S32_MIN (-S32_MAX-1)
#endif
#ifndef LIKELY
#define LIKELY(x) __builtin_expect(!!(x), 1)
#endif
#ifndef UNLIKELY
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#endif
#ifdef __cplusplus
#if __cplusplus
}
#endif /* __cpluscplus */
#endif /* __cpluscplus */
#endif /* PRT_TYPEDEF_H */
4.在src/bsp目录下创建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
5.在src/bsp目录下创建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
6.在src目录下创建链接文件arrch64-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 = .;
... ... ...
}
3.工程构建
1.创建src目录下的CMakeLists.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/moonshine/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}) # 可执行文件
此处需要将set(TOOLCHAIN_PATH)中的路径更改为实际交叉工具链所在位置,此处为:“/home/moonwine/arrch64-none-elf”
2.创建src/bsp目录下的CMakeLists.txt文件:
set(SRCS start.S prt_reset_vector.S )
add_library(bsp OBJECT ${SRCS}) # OBJECT类型只编译生成.o目标文件,但不实际链接成库
此处需要将我们所创建的文件纳入构建系统,即在set中包含start.S和prt_reset_vector.S文件,同时在add_library中加入当前目录bsp
4.编译运行
1.编译
在lab1目录下创建目录build用于存放编译过程产生的一系列中间文件,并进入build目录执行指令(注意一定要在build目录下,如果错在其他目录下执行了指令,需要将生成的中间文件全部删除,否则会报错):
cmake ../src
cmake --build .
对于大多数同学,这个地方第一次执行指令的时候都会产生报错“error while loading shared libraries: libpython3.6m.so.1.0: cannot open shared object file: No such file or directory”,原因是缺少相应的库,需要使用指令进行安装(这里是本实验最难的部分,需要安装一系列的依赖库,目的是为了后面调试使用):
sudo apt-get update sudo apt-get install -y build-essential libssl-dev libffi-dev libbz2-dev libreadline-dev libsqlite3-dev libncurses5-dev libncursesw5-dev xz-utils tk-dev
下载并解压python3.6的源码
wget -c https://www.python.org/ftp/python/3.6.15/Python-3.6.15.tar.xz tar -xf Python-3.6.15.tar.xz
编译安装
cd Python-3.6.15 LDFLAGS="-L/usr/lib/x86_64-linux-gnu" ./configure ./configure --enable-shared
make sudo
make install
sudo ldconfig
2.运行
如果使用cmake指令后没有任何报错,显示built target miniEuler,表明已经完成裸机的创建,接着进入lab1目录下(不在这个目录下运行会报错)运行指令:
qemu-system-aarch64 -machine virt -m 1024M -cpu cortex-a53 -nographic -kernel build/miniEuler -s
正常运行后屏幕上会显示AArch Bare Metal,然后卡住不动,这就是正常的(当时我还以为是什么bug),因为我们的裸机现在只具有打印的功能,打印完之后就进入无限循环。如果想退出裸机内核需要同时按下ctrl和字母键a,再按下x。
5.创建自动化脚本
sh makeMiniEuler.sh
sh runMiniEuler.sh
至此,整个内核的构建已经完成。
三、测试及分析
1.如上图自动化脚本类似,正常运行程序:
我这里使用的是运行指令,使用自动化脚本也是一样的效果。
qemu-system-aarch64代表启动armv8架构的虚拟机
-machine virt 来指定虚拟机类型为 virt
-m 1024M 来指定虚拟机内存大小为 1024M
-cpu cortex-a53 来指定虚拟机的 CPU 类型为 cortex-a53
-nographic 来禁用图形界面
-kernel build/miniEuler 来指定内核映像文件为我们自己的操作系统内核miniEuler
-S 选项表示在启动时暂停虚拟机并等待 gdb 连接。默认服务器端口为1234
2.代码原理分析
main.c:
定义了一个宏UART_REG_WRITE,用于实现将字符写入地址为0x9000000的位置,main.c文件的主题功能就是将字符串AArch64 Bare Metal逐个输入0x9000000处,实现字符串输出。(关于为什么向内存地址0x9000000输入字符串就可以实现字符串输入,这是一种操作系统与硬件交互的规定,这个地址被规定为操作系统与I/O设备之间的借口而非我们平常理解的地址,想要具体深入理解的同学可以自行上网查阅)
start.S:
首先声明两个外部定义的变量OsEnterMain 和 __os_sys_sp_end,其中OsEnterMain来自于prt_reset_vector.S,__os_sys_sp_end来自于脚本aarch64-qemu.ld,然后定义.text.bspinit包含本行之后的代码,表示一个可分配可执行的段,接着定义整个程序的入口OsElxState,程序运行时会跳转到此处开始执行(入口的定义在链接脚本aarch64-qemu.ld中实现)程序开始运行时,首先会执行指令MRS x6,CurrentEL,将系统寄存器的值读入到通用寄存器x6(MRS表示Move to register from system),然后比较该寄存器的低32位w6与0x4是否相等,由于CurrentEL系统寄存器中的索引第2位和第三位表示EL级别,所以等价于判断当前的EL级别是否为1,即内核态,若当前级别处于内核态,则进入start区,首先利用链接文件中定义的全局栈低指针__os_sys_sp_end对栈区进行初始化,然后跳转到prt_reset_vector.S中的OsEntermain进行执行;若当前级别处于用户态,则无法陷入操作系统内核执行内核指令,进入死循环。
prt_reset_vector.S:
跳转进入操作系统内核的主程序main.c,执行完毕后返回(BL指令返回),由于此时还未设置中断处理,故将DAIF寄存器中的Debug、SError、IRQ和FIQ位禁用,最后进入死循环。
aarch64-qemu.ld:
本文件为链接脚本文件,首先通过ENTRY声明整个程序的入口为__text_start,接着定义堆区、栈区大小均为0x10000,然后使用MEMORY定义两个内存空间IMU_SRAM与MMU_MEM,并定义了它们的起始位置分别为0x40000000,0x40800000、大小均为0x800000以及权限均为“rwx”,最后在SECTIONS中定义了__text_start的具体位置,然后将start.S文件中的代码段.text.bspinit插入__text_start后,如此操作,实质上则定义了整个程序的入口为start.S文件中的OsElxState,以及定义了堆区空间和栈区空间的起始位置和结束位置,并将起始代码段、堆栈空间保存于内存空间IMU_SRAM。
. = ALIGN(8);表示将链接器的当前位置对齐为8的倍数
PROVIDE表示定义起始位置和结束位置
. = . + _heap_size(_stack_size);将链接器的当前位置向前偏移堆栈空间的大小,以便为程序的堆栈分配空间。
四、Lab1作业
作业一:商业操作系统都有复杂的构建系统,试简要分析 UniProton 的构建系统。
UniProton通过在根目录下执行 python build.py m4 (m4是指目标平台,还有如hi3093等)进行构建,在这个python文件的主函数中,会根据命令行参数创建一个Compile类的实例,并调用这个实例的UniProtonCompile()方法来开始编译过程,完成项目的构建。
分析构建的核心文件build.py:
#!/usr/bin/env python3
# coding=utf-8
# The build entrance of UniProton.
# Copyright (c) 2009-2023 Huawei Technologies Co., Ltd. All rights reserved.
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 *
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()
#调用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)
# 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(),从而实现使用cmake进行构建,对类中的各方法进行分析:
- __init__方法:根据python的类相关语法可知,本方法相当于C++中的构造函数,在类Compile被实例化之后就会自动调用,获取当前编译的路径信息,配置文件信息,编译选项信息并保存在实例化对象UniProton_build中。
- get_config方法:本方法是进行编译前的准备工作,获取config配置的环境,通过调用get_compile_mode函数来获取编译模式、: 调用 get_cpu_info() 函数获取 CPU 相关信息,并将结果分别赋值给 self.lib_type、self.plam_type、self.hcc_path、self.kconf_dir、self.system 和 self.core,然后检查以上获取的参数是否存在,如果任何一个参数不存在,则打印错误信息并退出程序,最后设置config的文件路径。
- setCmdEnv方法:本方法同样是进行编译前的准备工作,首先获取的当前的时间,按照年-月-日_小时:分钟的格式进行存储,同时创建一个日志文件,用于保存构建过程的日志信息。
- SetCMakeEnviron方法,本方法是设置Cmake编译过程的环境变量,包括 CPU类型、平台类型、库类型、编译选项、编译器路径、打包路径、配置文件路径。
- prepare_env方法,这个方法相当于是一个封装,调用了234三个方法,准备执行cmake,make,makebuildfile,CmakeList需要的环境,在每一次编译前都调用此函数。
- getOsPlatform方法:获取编译环境是arm64还是x86,用户不感知,并将编译环境写入环境变量。
- Cmake方法:本方法是类中的核心方法,执行 CMake 构建过程。首先,它设置了临时构建目录self.build_tmp_dir 的路径,如果 UniProton_binary_dir 存在,就使用它,否则使用 build_dir。然后将这个临时构建目录的路径设置为环境变量 BUILD_TMP_DIR。接着将检查临时构建目录和日志目录是否存在,如果不存在,则创建这些目录。同时,它记录了构建时间标签,并创建了一个 BuilderNolog 对象,用于执行构建操作。如果 make_phase 是 'CREATE_CMAKE_FILE' 或 'ALL',就删除临时构建目录并重建,然后构造 CMake 命令。根据 compile_option 的值,创建不同的 CMake 命令,这里考虑了 'fortify'、'hllt' 和其他情况。然后,函数会执行拼接好的 CMake 命令。如果命令执行失败,它会记录错误信息并返回 False。如果 CMake 命令执行成功,它会记录成功信息并返回 True。
- UniProton_clean方法:本方法在构建过程结束后清理不再需要的文件和文件夹,以释放磁盘空间。
- Make方法:本方法负责执行项目的构建和安装,首先检查make_phase属性是否为'EXECUTING_MAKE'或'ALL',如果是,那么将清理构建目录,并执行make all命令构建项目。如果构建失败,它会记录一条错误日志,并返回False。然后进行判断,如果compile_option属性是'normal', 'coverity', 或'single',它会执行make install命令将项目安装到预定位置。如果安装失败,它也会记录一条错误日志,并返回False。如果日志文件存在,它会调用log_format方法来格式化日志。最后,如果构建和安装都成功,它会记录一条信息日志,并返回True。
- Sdkcompaile方法:本方法是一个验证方法,用于判断当前环境是否需要编译。
- UniProtonCompile方法:该方法是一个函数的封装,调用后首先执行clean操作清除UniProton的缓存,接着调用prepare_env方法根据cpu的编译平台配置相应的编译环境,最后调用Sdkcompaile方法来判断是否编译成功。
作业二:学习如何调试项目。
1.在wsl远程调试项目:
首先在打开一个wsl终端(对应虚拟机终端),并启动程序,加上-S指令冻结CPU,使程序在入口停止等待调试:
然后再新打开一个wsl终端(这里对应虚拟机的终端),在新的终端中启动调试客户端:
在gdb下输入指令:target remote localhost:1234远程连接到第一个终端中运行的程序,并使用disassemble进行反汇编:
使用指令i r查看所有寄存器的值:
使用si指令进行单步运行:
使用x/20xw 0x40000000可以查看从地址0x40000000起始的20个四字节的地址空间中的值,以16进制形式表示,0x40000000是第一条指令对应的机器码的起始地址:
可以使用指令set $x24 = 0x7来设置寄存器x24的值为7,使用p指令可以查看寄存器的内容:
使用指令layout asm可以在运行时实时查看当前运行到具体的哪一条指令:
2.将调试集成到vscode中:
首先在终端中的lab1目录下输入指令code .,在vscode中远程连接虚拟机:
然后与直接在终端中调试相同,在终端处运行程序(或者使用自动化脚本),加上-S指令冻结CPU,使程序在入口停止等待调试:
点击左侧的调试选项,最初会弹出一个launch.json文件,点击创建后会自动填充相关信息,注意将miDebuggerPath修改为自己实际的交叉调试器路径,格式可以参考如下:
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Windows 上的 Bash 启动",
"type": "cppdbg",
"request": "launch",
"program": "输入程序名称,例如 ${workspaceFolder}/a.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"pipeTransport": {
"debuggerPath": "/usr/bin/gdb",
"pipeProgram": "${env:windir}\\system32\\bash.exe",
"pipeArgs": ["-c"],
"pipeCwd": ""
},
"setupCommands": [
{
"description": "为 gdb 启用整齐打印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "将反汇编风格设置为 Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
},
{
"name": "aarch64-gdb",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/miniEuler",
"stopAtEntry": true,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"launchCompleteCommand": "exec-run",
"MIMode": "gdb",
"miDebuggerPath":"/home/moonwine/arrch64-none-elf/bin/aarch64-none-elf-gdb", // 修改成交叉调试器gdb对应位置
"miDebuggerServerAddress": "localhost:1234",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
],
}
准备工作就绪后,开始调试,可以看到程序顺利在入口处停止:
在调试控制台处输入指令-exec x/20xw 0x40000000可以查看从地址0x40000000起始的20个四字节的地址空间中的值,以16进制形式表示:
此外,交叉编译工具链中可以执行gdb的所有调试指令,不过要在gdb指令前加上-exec,如:
显示所有寄存器。-exec info all-registers
查看寄存器内容。-exec p/x $pc
修改寄存器内容。-exec set $x24 = 0x5 (将寄存器x24的值设置为0x5)
修改指定内存位置的内容。-exec set {int}0x4000000 = 0x1 或者 -exec set *((int *) 0x4000000) = 0x1
修改指定MMIO 寄存器的内容。 -exec set *((volatile int *) 0x08010004) = 0x1
退出调试 -exec q
单步执行代码,直到完成输出后进入无限循环:
五、心得体会
1.对操作系统的理解更加深入。
2.深入理解操作系统内核的底层原理。
3.学会对qemu模拟器运行的程序进行调试,对vscode的json配置更加清楚。
4.对Ubuntu的使用也更熟悉。