当前位置:   article > 正文

HNU-2024操作系统实验-Lab1-环境配置

HNU-2024操作系统实验-Lab1-环境配置

写在前面:

从2024年开始,HNU信息院计科和智能专业统一使用李老师基于UniProton编写的新实验,一共有八个实验,如果仅是按照指导书的内容将实验跑通还是比较容易的(第一个实验除外),而对于想要在将来走系统方向的同学,或者遇上比较苛刻的助教,需要将整个实验的代码完全看懂,那就是一件任务量很大的事情了。我在完成这一系列实验的过程也十分坎坷,获得了很多同学的帮助,向他们表示感谢,本文旨在为下一届的同学提供一些参考和帮助,需要注意的是,每个实验的作业题没有标准的答案,仅由我与其他同学讨论得到,欢迎指正。

一、实验目的

1.安装交叉工具链(aarch64)以及安装QEMU模拟器
2.创建裸机(Bare Metal)程序
3.构件工程
4.完成调试支持
5.建立自动化脚本
 

二、实验过程

1.配置交叉工具链及安装QEMU模拟器

1.安装交叉工具链
  1. 下载工具链,以下载工具链版本为11.2,宿主机为x86 64位 Linux机器为例
  2. 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
  3. 解压工具链
  4. tar -xf gcc-arm-(按Tab键补全)
  5. 重命名工具链目录
  6. mv gcc-arm-(按Tab键补全) aarch64-none-elf
2.将工具链目录加入到环境变量

这里的话需要在终端的命令行中输入vim ~/.bashrc指令,然后在最底下新增加一行将自己的工具链目录添加到环境变量,例如此处为/home/moonwine/arrch64-none-elf/bin,需要根据自己实际的目录进行替换,最后完成编辑后,在命令行输入source ~/.bashrc进行环境变量设置。

3.测试工具链是否安装完成

使用最常用的查看版本指令,如果正常显示则表明成功安装,如果报错多半是环境变量没有添加成功,检查工具链目录是否正确设置。

4.安装QEMU模拟器
  1. sudo apt-get update
  2. sudo apt-get install qemu
  3. 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:
  1. /*
  2. * Copyright (c) 2009-2022 Huawei Technologies Co., Ltd. All rights reserved.
  3. *
  4. * UniProton is licensed under Mulan PSL v2.
  5. * You can use this software according to the terms and conditions of the Mulan PSL v2.
  6. * You may obtain a copy of Mulan PSL v2 at:
  7. * http://license.coscl.org.cn/MulanPSL2
  8. * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
  9. * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
  10. * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
  11. * See the Mulan PSL v2 for more details.
  12. * Create: 2009-12-22
  13. * Description: 定义基本数据类型和数据结构。
  14. */
  15. #ifndef PRT_TYPEDEF_H
  16. #define PRT_TYPEDEF_H
  17. #include <stddef.h>
  18. #include <stdint.h>
  19. #include <stdbool.h>
  20. #ifdef __cplusplus
  21. #if __cplusplus
  22. extern "C" {
  23. #endif /* __cpluscplus */
  24. #endif /* __cpluscplus */
  25. typedef unsigned char U8;
  26. typedef unsigned short U16;
  27. typedef unsigned int U32;
  28. typedef unsigned long long U64;
  29. typedef signed char S8;
  30. typedef signed short S16;
  31. typedef signed int S32;
  32. typedef signed long long S64;
  33. typedef void *VirtAddr;
  34. typedef void *PhyAddr;
  35. #ifndef OS_SEC_ALW_INLINE
  36. #define OS_SEC_ALW_INLINE
  37. #endif
  38. #ifndef INLINE
  39. #define INLINE static __inline __attribute__((always_inline))
  40. #endif
  41. #ifndef OS_EMBED_ASM
  42. #define OS_EMBED_ASM __asm__ __volatile__
  43. #endif
  44. /* 参数不加void表示可以传任意个参数 */
  45. typedef void (*OsVoidFunc)(void);
  46. #define ALIGN(addr, boundary) (((uintptr_t)(addr) + (boundary) - 1) & ~((uintptr_t)(boundary) - 1))
  47. #define TRUNCATE(addr, size) ((addr) & ~((uintptr_t)(size) - 1))
  48. #ifdef YES
  49. #undef YES
  50. #endif
  51. #define YES 1
  52. #ifdef NO
  53. #undef NO
  54. #endif
  55. #define NO 0
  56. #ifndef FALSE
  57. #define FALSE ((bool)0)
  58. #endif
  59. #ifndef TRUE
  60. #define TRUE ((bool)1)
  61. #endif
  62. #ifndef NULL
  63. #define NULL ((void *)0)
  64. #endif
  65. #define OS_ERROR (U32)(-1)
  66. #define OS_INVALID (-1)
  67. #ifndef OS_OK
  68. #define OS_OK 0
  69. #endif
  70. #ifndef OS_FAIL
  71. #define OS_FAIL 1
  72. #endif
  73. #ifndef U8_INVALID
  74. #define U8_INVALID 0xffU
  75. #endif
  76. #ifndef U12_INVALID
  77. #define U12_INVALID 0xfffU
  78. #endif
  79. #ifndef U16_INVALID
  80. #define U16_INVALID 0xffffU
  81. #endif
  82. #ifndef U32_INVALID
  83. #define U32_INVALID 0xffffffffU
  84. #endif
  85. #ifndef U64_INVALID
  86. #define U64_INVALID 0xffffffffffffffffUL
  87. #endif
  88. #ifndef U32_MAX
  89. #define U32_MAX 0xFFFFFFFFU
  90. #endif
  91. #ifndef S32_MAX
  92. #define S32_MAX 0x7FFFFFFF
  93. #endif
  94. #ifndef S32_MIN
  95. #define S32_MIN (-S32_MAX-1)
  96. #endif
  97. #ifndef LIKELY
  98. #define LIKELY(x) __builtin_expect(!!(x), 1)
  99. #endif
  100. #ifndef UNLIKELY
  101. #define UNLIKELY(x) __builtin_expect(!!(x), 0)
  102. #endif
  103. #ifdef __cplusplus
  104. #if __cplusplus
  105. }
  106. #endif /* __cpluscplus */
  107. #endif /* __cpluscplus */
  108. #endif /* PRT_TYPEDEF_H */
4.在src/bsp目录下创建start.S:
  1. .global OsEnterMain
  2. .extern __os_sys_sp_end
  3. .type start, function
  4. .section .text.bspinit, "ax"
  5. .balign 4
  6. .global OsElxState
  7. .type OsElxState, @function
  8. OsElxState:
  9. MRS x6, CurrentEL // 把系统寄存器 CurrentEL 的值读入到通用寄存器 x6 中
  10. MOV x2, #0x4 // CurrentEL EL1: bits [3:2] = 0b01
  11. CMP w6, w2
  12. BEQ Start // 若 CurrentEl 为 EL1 级别,跳转到 Start 处执行,否则死循环。
  13. OsEl2Entry:
  14. B OsEl2Entry
  15. Start:
  16. LDR x1, =__os_sys_sp_end // 符号在ld文件中定义
  17. BIC sp, x1, #0xf // 设置栈指针
  18. B OsEnterMain
  19. OsEnterReset:
  20. B OsEnterReset
5.在src/bsp目录下创建prt_reset_vector.S
  1. DAIF_MASK = 0x1C0 // disable SError Abort, IRQ, FIQ
  2. .global OsVectorTable
  3. .global OsEnterMain
  4. .section .text.startup, "ax"
  5. OsEnterMain:
  6. BL main
  7. MOV x2, DAIF_MASK // bits [9:6] disable SError Abort, IRQ, FIQ
  8. MSR DAIF, x2 // 把通用寄存器 x2 的值写入系统寄存器 DAIF 中
  9. EXITLOOP:
  10. B EXITLOOP
6.在src目录下创建链接文件arrch64-qemu.ld:

脚本较长,此处仅展示需要理解的部分:

  1. ENTRY(__text_start)
  2. _stack_size = 0x10000;
  3. _heap_size = 0x10000;
  4. MEMORY
  5. {
  6. IMU_SRAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x800000 /* 内存区域 */
  7. MMU_MEM (rwx) : ORIGIN = 0x40800000, LENGTH = 0x800000 /* 内存区域 */
  8. }
  9. SECTIONS
  10. {
  11. text_start = .;
  12. .start_bspinit :
  13. {
  14. __text_start = .; /* __text_start 指向当前位置, "." 表示当前位置 */
  15. KEEP(*(.text.bspinit))
  16. } > IMU_SRAM
  17. ... ... ...
  18. .heap (NOLOAD) :
  19. {
  20. . = ALIGN(8);
  21. PROVIDE (__HEAP_INIT = .);
  22. . = . + _heap_size; /* 堆空间 */
  23. . = ALIGN(8);
  24. PROVIDE (__HEAP_END = .);
  25. } > IMU_SRAM
  26. .stack (NOLOAD) :
  27. {
  28. . = ALIGN(8);
  29. PROVIDE (__os_sys_sp_start = .);
  30. . = . + _stack_size; /* 栈空间 */
  31. . = ALIGN(8);
  32. PROVIDE (__os_sys_sp_end = .);
  33. } > IMU_SRAM
  34. end = .;
  35. ... ... ...
  36. }

3.工程构建

1.创建src目录下的CMakeLists.txt文件:
  1. cmake_minimum_required(VERSION 3.12)
  2. set(CMAKE_SYSTEM_NAME "Generic") # 目标系统(baremental): cmake/tool_chain/uniproton_tool_chain_gcc_arm64.cmake 写的是Linux
  3. set(CMAKE_SYSTEM_PROCESSOR "aarch64") # 目标系统CPU
  4. set(TOOLCHAIN_PATH "/home/moonshine/aarch64-none-elf") # 修改为交叉工具链实际所在目录 build.py config.xml中定义
  5. set(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-gcc)
  6. set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-g++)
  7. set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-gcc)
  8. set(CMAKE_LINKER ${TOOLCHAIN_PATH}/bin/aarch64-none-elf-ld)
  9. # 定义编译和链接等选项
  10. 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")
  11. 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")
  12. set(LD_OPTION " ")
  13. set(CMAKE_C_FLAGS "${CC_OPTION} ")
  14. set(CMAKE_ASM_FLAGS "${AS_OPTION} ")
  15. set(CMAKE_LINK_FLAGS "${LD_OPTION} -T ${CMAKE_CURRENT_SOURCE_DIR}/aarch64-qemu.ld") # 指定链接脚本
  16. set(CMAKE_EXE_LINKER_FLAGS "${LD_OPTION} -T ${CMAKE_CURRENT_SOURCE_DIR}/aarch64-qemu.ld") # 指定链接脚本
  17. set (CMAKE_C_LINK_FLAGS " ")
  18. set (CMAKE_CXX_LINK_FLAGS " ")
  19. set(HOME_PATH ${CMAKE_CURRENT_SOURCE_DIR})
  20. set(APP "miniEuler") # APP变量,后面会用到 ${APP}
  21. project(${APP} LANGUAGES C ASM) # 工程名及所用语言
  22. set(CMAKE_BUILD_TYPE Debug) # 生成 Debug 版本
  23. include_directories( # include 目录
  24. ${CMAKE_CURRENT_SOURCE_DIR}/include
  25. ${CMAKE_CURRENT_SOURCE_DIR}/bsp
  26. )
  27. add_subdirectory(bsp) # 包含子文件夹的内容
  28. list(APPEND OBJS $<TARGET_OBJECTS:bsp>)
  29. add_executable(${APP} main.c ${OBJS}) # 可执行文件

此处需要将set(TOOLCHAIN_PATH)中的路径更改为实际交叉工具链所在位置,此处为:“/home/moonwine/arrch64-none-elf”

2.创建src/bsp目录下的CMakeLists.txt文件:
  1. set(SRCS start.S prt_reset_vector.S )
  2. add_library(bsp OBJECT ${SRCS}) # OBJECT类型只编译生成.o目标文件,但不实际链接成库

此处需要将我们所创建的文件纳入构建系统,即在set中包含start.S和prt_reset_vector.S文件,同时在add_library中加入当前目录bsp

4.编译运行

1.编译

在lab1目录下创建目录build用于存放编译过程产生的一系列中间文件,并进入build目录执行指令(注意一定要在build目录下,如果错在其他目录下执行了指令,需要将生成的中间文件全部删除,否则会报错):

  1. cmake ../src
  2. cmake --build .

对于大多数同学,这个地方第一次执行指令的时候都会产生报错“error while loading shared libraries: libpython3.6m.so.1.0: cannot open shared object file: No such file or directory”,原因是缺少相应的库,需要使用指令进行安装(这里是本实验最难的部分,需要安装一系列的依赖库,目的是为了后面调试使用):

  1. 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
  2. 下载并解压python3.6的源码
  3. 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
  4. 编译安装
  5. cd Python-3.6.15 LDFLAGS="-L/usr/lib/x86_64-linux-gnu" ./configure ./configure --enable-shared
  6. make sudo
  7. make install
  8. 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.创建自动化脚本

  1. sh makeMiniEuler.sh
  2. 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:

  1. #!/usr/bin/env python3
  2. # coding=utf-8
  3. # The build entrance of UniProton.
  4. # Copyright (c) 2009-2023 Huawei Technologies Co., Ltd. All rights reserved.
  5. import os
  6. import sys
  7. import time
  8. import shutil
  9. import subprocess
  10. import platform
  11. from sys import argv
  12. UniProton_home = os.path.dirname(os.path.abspath(__file__))
  13. sys.path.insert(0, "%s/cmake/common/build_auxiliary_script"%UniProton_home)
  14. from make_buildef import make_buildef
  15. sys.path.insert(0, "%s/build/uniproton_ci_lib"%UniProton_home)
  16. import globle
  17. from logs import BuilderNolog, log_msg
  18. from get_config_info import *
  19. class Compile:
  20. # 根据makechoice获取config的配置的环境,compile_mode, lib_type,
  21. def get_config(self, cpu_type, cpu_plat):
  22. self.compile_mode = get_compile_mode()
  23. 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)
  24. if not self.compile_mode and self.lib_type and self.plam_type and self.hcc_path and self.kconf_dir:
  25. log_msg('error', 'load config.xml env error')
  26. sys.exit(0)
  27. self.config_file_path = '%s/build/uniproton_config/config_%s'%(self.home_path, self.kconf_dir)
  28. self.objcopy_path = self.hcc_path
  29. def setCmdEnv(self):
  30. self.build_time_tag = time.strftime('%Y-%m-%d_%H:%M:00')
  31. self.log_dir = '%s/logs/%s' % (self.build_dir, self.cpu_type)
  32. self.log_file = '%s.log' % self.kconf_dir
  33. def SetCMakeEnviron(self):
  34. os.environ["CPU_TYPE"] = self.cpu_type
  35. os.environ["PLAM_TYPE"] = self.plam_type
  36. os.environ["LIB_TYPE"] = self.lib_type
  37. os.environ["COMPILE_OPTION"] = self.compile_option
  38. os.environ["HCC_PATH"] = self.hcc_path
  39. os.environ["UNIPROTON_PACKING_PATH"] = self.UniProton_packing_path
  40. os.environ["CONFIG_FILE_PATH"] = self.config_file_path
  41. os.environ["LIB_RUN_TYPE"] = self.lib_run_type
  42. os.environ["HOME_PATH"] = self.home_path
  43. os.environ["COMPILE_MODE"] = self.compile_mode
  44. os.environ["BUILD_MACHINE_PLATFORM"] = self.build_machine_platform
  45. os.environ["SYSTEM"] = self.system
  46. os.environ["CORE"] = self.core
  47. os.environ["OBJCOPY_PATH"] = self.objcopy_path
  48. os.environ['PATH'] = '%s:%s' % (self.hcc_path, os.getenv('PATH'))
  49. # 环境准备,准备执行cmake,make,makebuildfile,CmakeList需要的环境
  50. # 每次compile之前请调用该函数
  51. def prepare_env(self, cpu_type, choice):
  52. # makebuildfile需要的环境kconf_dir
  53. # cmake需要的环境cmake_env_path,home_path(cmakelist所在的路径),home_path,
  54. # make cmd拼接需要的环境:home_path,UniProton_make_jx,log_dir,log_file,build_time_tag, UniProton_make_jx
  55. #根据cpu_type, choice从config文件中获取并初始化初始化hcc_path, plam_type, kconf_dir
  56. #根据输入分支获取
  57. #从编译镜像环境获取
  58. self.get_config(cpu_type, choice)
  59. self.setCmdEnv()
  60. self.SetCMakeEnviron()
  61. #获取编译环境是arm64还是x86,用户不感知,并将其写入环境变量。
  62. def getOsPlatform(self):
  63. self.cmake_env_path = get_tool_info('cmake', 'tool_path')
  64. if platform.uname()[-1] == 'aarch64':
  65. self.build_machine_platform = 'arm64'
  66. else:
  67. self.build_machine_platform = 'x86'
  68. # 获取当前编译的路径信息,配置文件信息,编译选项信息
  69. def __init__(self, cpu_type: str, make_option="normal", lib_run_type="FPGA", choice="ALL", make_phase="ALL",
  70. UniProton_packing_path=""):
  71. # 当前路径信息
  72. self.system = ""
  73. self.objcopy_path = ""
  74. self.builder = None
  75. self.compile_mode = ""
  76. self.core = ""
  77. self.plam_type = ""
  78. self.kconf_dir = ""
  79. self.build_tmp_dir = ""
  80. self.log_dir = ""
  81. self.lib_type = ""
  82. self.hcc_path = ""
  83. self.log_file = ""
  84. self.config_file_path = ""
  85. self.build_time_tag = ""
  86. self.build_dir = globle.build_dir
  87. self.home_path = globle.home_path
  88. self.kbuild_path = globle.kbuild_path
  89. # 当前选项信息
  90. self.cpu_type = cpu_type
  91. self.compile_option = make_option
  92. self.lib_run_type = lib_run_type
  93. self.make_choice = choice.lower()
  94. self.make_phase = make_phase
  95. self.UniProton_packing_path = UniProton_packing_path if make_phase == "CREATE_CMAKE_FILE" else '%s/output'%self.home_path
  96. self.UniProton_binary_dir = os.getenv('RPROTON_BINARY_DIR')
  97. self.UniProton_install_file_option = os.getenv('RPROTON_INSTALL_FILE_OPTION')
  98. self.UniProton_make_jx = 'VERBOSE=1' if self.UniProton_install_file_option == 'SUPER_BUILD' else 'VERBOSE=1 -j' + str(os.cpu_count())
  99. # 当前编译平台信息
  100. self.getOsPlatform()
  101. #调用cmake生成Makefile文件,需要
  102. def CMake(self):
  103. if self.UniProton_binary_dir:
  104. self.build_tmp_dir = '%s/output/tmp/%s' % (self.UniProton_binary_dir, self.kconf_dir)
  105. else:
  106. self.build_tmp_dir = '%s/output/tmp/%s' % (self.build_dir, self.kconf_dir)
  107. os.environ['BUILD_TMP_DIR'] = self.build_tmp_dir
  108. if not os.path.exists(self.build_tmp_dir):
  109. os.makedirs(self.build_tmp_dir)
  110. if not os.path.exists(self.log_dir):
  111. os.makedirs(self.log_dir)
  112. log_msg('info', 'BUILD_TIME_TAG %s' % self.build_time_tag)
  113. self.builder = BuilderNolog(os.path.join(self.log_dir, self.log_file))
  114. if self.make_phase in ['CREATE_CMAKE_FILE', 'ALL']:
  115. real_path = os.path.realpath(self.build_tmp_dir)
  116. if os.path.exists(real_path):
  117. shutil.rmtree(real_path)
  118. os.makedirs(self.build_tmp_dir)
  119. #拼接cmake命令
  120. if self.compile_option == 'fortify':
  121. cmd = '%s/cmake %s -DCMAKE_TOOLCHAIN_FILE=%s/cmake/tool_chain/uniproton_tool_chain.cmake ' \
  122. '-DCMAKE_C_COMPILER_LAUNCHER="sourceanalyzer;-b;%sproject" ' \
  123. '-DCMAKE_INSTALL_PREFIX=%s &> %s/%s' % (
  124. self.cmake_env_path, self.home_path, self.home_path, self.cpu_type,
  125. self.UniProton_packing_path, self.log_dir, self.log_file)
  126. elif self.compile_option == 'hllt':
  127. cmd = '%s/cmake %s -DCMAKE_TOOLCHAIN_FILE=%s/cmake/tool_chain/uniproton_tool_chain.cmake ' \
  128. '-DCMAKE_C_COMPILER_LAUNCHER="lltwrapper" -DCMAKE_INSTALL_PREFIX=%s &> %s/%s' % (
  129. self.cmake_env_path, self.home_path, self.home_path, self.UniProton_packing_path, self.log_dir, self.log_file)
  130. else:
  131. cmd = '%s/cmake %s -DCMAKE_TOOLCHAIN_FILE=%s/cmake/tool_chain/uniproton_tool_chain.cmake ' \
  132. '-DCMAKE_INSTALL_PREFIX=%s &> %s/%s' % (
  133. self.cmake_env_path, self.home_path, self.home_path, self.UniProton_packing_path, self.log_dir, self.log_file)
  134. #执行cmake命令
  135. if self.builder.run(cmd, cwd=self.build_tmp_dir, env=None):
  136. log_msg('error', 'generate makefile failed!')
  137. return False
  138. log_msg('info', 'generate makefile succeed.')
  139. return True
  140. def UniProton_clean(self):
  141. for foldername,subfoldernames,filenames in os.walk(self.build_dir):
  142. for subfoldername in subfoldernames:
  143. if subfoldername in ['logs','output','__pycache__']:
  144. folder_path = os.path.join(foldername,subfoldername)
  145. shutil.rmtree(os.path.relpath(folder_path))
  146. for filename in filenames:
  147. if filename == 'prt_buildef.h':
  148. file_dir = os.path.join(foldername,filename)
  149. os.remove(os.path.relpath(file_dir))
  150. if os.path.exists('%s/cmake/common/build_auxiliary_script/__pycache__'%self.home_path):
  151. shutil.rmtree('%s/cmake/common/build_auxiliary_script/__pycache__'%self.home_path)
  152. if os.path.exists('%s/output'%self.home_path):
  153. shutil.rmtree('%s/output'%self.home_path)
  154. if os.path.exists('%s/tools/SRE/x86-win32/sp_makepatch/makepatch'%self.home_path):
  155. os.remove('%s/tools/SRE/x86-win32/sp_makepatch/makepatch'%self.home_path)
  156. if os.path.exists('%s/build/prepare/__pycache__'%self.home_path):
  157. shutil.rmtree('%s/build/prepare/__pycache__'%self.home_path)
  158. return True
  159. def make(self):
  160. if self.make_phase in ['EXECUTING_MAKE', 'ALL']:
  161. self.builder.run('make clean', cwd=self.build_tmp_dir, env=None)
  162. tmp = sys.argv
  163. if self.builder.run(
  164. 'make all %s &>> %s/%s' % (
  165. self.UniProton_make_jx, self.log_dir, self.log_file), cwd=self.build_tmp_dir, env=None):
  166. log_msg('error', 'make %s %s failed!' % (self.cpu_type, self.plam_type))
  167. return False
  168. sys.argv = tmp
  169. if self.compile_option in ['normal', 'coverity', 'single']:
  170. 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):
  171. log_msg('error', 'make install failed!')
  172. return False
  173. if os.path.exists('%s/%s' % (self.log_dir, self.log_file)):
  174. self.builder.log_format()
  175. log_msg('info', 'make %s %s succeed.' % (self.cpu_type, self.plam_type))
  176. return True
  177. def SdkCompaile(self)->bool:
  178. # 判断该环境中是否需要编译
  179. if self.hcc_path == 'None':
  180. return True
  181. self.MakeBuildef()
  182. if self.CMake() and self.make():
  183. log_msg('info', 'make %s %s lib succeed!' % (self.cpu_type, self.make_choice))
  184. return True
  185. else:
  186. log_msg('info', 'make %s %s lib failed!' % (self.cpu_type, self.make_choice))
  187. return False
  188. # 对外函数,调用后根据类初始化时的值进行编译
  189. def UniProtonCompile(self):
  190. #清除UniProton缓存
  191. if self.cpu_type == 'clean':
  192. log_msg('info', 'UniProton clean')
  193. return self.UniProton_clean()
  194. # 根据cpu的编译平台配置相应的编译环境。
  195. if self.make_choice == "all":
  196. for make_choice in globle.cpu_plat[self.cpu_type]:
  197. self.prepare_env(self.cpu_type, make_choice)
  198. if not self.SdkCompaile():
  199. return False
  200. else:
  201. self.prepare_env(self.cpu_type, self.make_choice)
  202. if not self.SdkCompaile():
  203. return False
  204. return True
  205. def MakeBuildef(self):
  206. if not make_buildef(globle.home_path,self.kconf_dir,"CREATE"):
  207. sys.exit(1)
  208. log_msg('info', 'make_buildef_file.sh %s successfully.' % self.kconf_dir)
  209. # argv[1]: cpu_plat 表示要编译的平台:
  210. # argv[2]: compile_option 控制编译选项,调用不同的cmake参数,目前只有normal coverity hllt fortify single(是否编译安全c,组件化独立构建需求)
  211. # argv[3]: lib_run_type lib库要跑的平台 faga sim等
  212. # argv[4]: make_choice
  213. # argv[5]: make_phase 全量构建选项
  214. # argv[6]: UniProton_packing_path lib库的安装位置
  215. if __name__ == "__main__":
  216. default_para = ("all", "normal", "FPGA", "ALL", "ALL", "")
  217. if len(argv) == 1:
  218. para = [default_para[i] for i in range(0, len(default_para))]
  219. else:
  220. para = [argv[i+1] if i < len(argv) - 1 else default_para[i] for i in range(0,len(default_para))]
  221. cur_cpu_type = para[0].lower()
  222. cur_compile_option = para[1].lower()
  223. cur_lib_run_type = para[2]
  224. cur_make_choice = para[3]
  225. cur_make_phase = para[4]
  226. cur_UniProton_packing_path = para[5]
  227. for plat in globle.cpus_[cur_cpu_type]:
  228. UniProton_build = Compile(plat, cur_compile_option, cur_lib_run_type, cur_make_choice, cur_make_phase, cur_UniProton_packing_path)
  229. if not UniProton_build.UniProtonCompile():
  230. sys.exit(1)
  231. sys.exit(0)

在上面的代码中我们可以看到,代码的主体是一个类Compile,主函数通过根据参数对Compile类进行实例化,然后调用类中的方法UniProtonCompile(,从而实现使用cmake进行构建,对类中的各方法进行分析:

  1. __init__方法:根据python的类相关语法可知,本方法相当于C++中的构造函数,在类Compile被实例化之后就会自动调用,获取当前编译的路径信息,配置文件信息,编译选项信息并保存在实例化对象UniProton_build中。
  2. 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的文件路径。
  3. setCmdEnv方法:本方法同样是进行编译前的准备工作,首先获取的当前的时间,按照年-月-日_小时:分钟的格式进行存储,同时创建一个日志文件,用于保存构建过程的日志信息。
  4. SetCMakeEnviron方法,本方法是设置Cmake编译过程的环境变量,包括 CPU类型、平台类型、库类型、编译选项、编译器路径、打包路径、配置文件路径。
  5. prepare_env方法,这个方法相当于是一个封装,调用了234三个方法,准备执行cmake,make,makebuildfile,CmakeList需要的环境,在每一次编译前都调用此函数。
  6. getOsPlatform方法:获取编译环境是arm64还是x86,用户不感知,并将编译环境写入环境变量。
  7. 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。
  8. UniProton_clean方法:本方法在构建过程结束后清理不再需要的文件和文件夹,以释放磁盘空间。
  9. Make方法:本方法负责执行项目的构建和安装,首先检查make_phase属性是否为'EXECUTING_MAKE'或'ALL',如果是,那么将清理构建目录,并执行make all命令构建项目。如果构建失败,它会记录一条错误日志,并返回False。然后进行判断,如果compile_option属性是'normal', 'coverity', 或'single',它会执行make install命令将项目安装到预定位置。如果安装失败,它也会记录一条错误日志,并返回False。如果日志文件存在,它会调用log_format方法来格式化日志。最后,如果构建和安装都成功,它会记录一条信息日志,并返回True。
  10. Sdkcompaile方法:本方法是一个验证方法,用于判断当前环境是否需要编译。
  11. 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修改为自己实际的交叉调试器路径,格式可以参考如下:

  1. {
  2. "version": "0.2.0",
  3. "configurations": [
  4. {
  5. "name": "(gdb) Windows 上的 Bash 启动",
  6. "type": "cppdbg",
  7. "request": "launch",
  8. "program": "输入程序名称,例如 ${workspaceFolder}/a.exe",
  9. "args": [],
  10. "stopAtEntry": false,
  11. "cwd": "${fileDirname}",
  12. "environment": [],
  13. "externalConsole": false,
  14. "pipeTransport": {
  15. "debuggerPath": "/usr/bin/gdb",
  16. "pipeProgram": "${env:windir}\\system32\\bash.exe",
  17. "pipeArgs": ["-c"],
  18. "pipeCwd": ""
  19. },
  20. "setupCommands": [
  21. {
  22. "description": "为 gdb 启用整齐打印",
  23. "text": "-enable-pretty-printing",
  24. "ignoreFailures": true
  25. },
  26. {
  27. "description": "将反汇编风格设置为 Intel",
  28. "text": "-gdb-set disassembly-flavor intel",
  29. "ignoreFailures": true
  30. }
  31. ]
  32. },
  33. {
  34. "name": "aarch64-gdb",
  35. "type": "cppdbg",
  36. "request": "launch",
  37. "program": "${workspaceFolder}/build/miniEuler",
  38. "stopAtEntry": true,
  39. "cwd": "${fileDirname}",
  40. "environment": [],
  41. "externalConsole": false,
  42. "launchCompleteCommand": "exec-run",
  43. "MIMode": "gdb",
  44. "miDebuggerPath":"/home/moonwine/arrch64-none-elf/bin/aarch64-none-elf-gdb", // 修改成交叉调试器gdb对应位置
  45. "miDebuggerServerAddress": "localhost:1234",
  46. "setupCommands": [
  47. {
  48. "description": "Enable pretty-printing for gdb",
  49. "text": "-enable-pretty-printing",
  50. "ignoreFailures": true
  51. }
  52. ]
  53. }
  54. ],
  55. }

准备工作就绪后,开始调试,可以看到程序顺利在入口处停止:

在调试控制台处输入指令-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的使用也更熟悉。

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/797505
推荐阅读
相关标签
  

闽ICP备14008679号