**作者:**********wh0am1i@********知道创宇404实验室
时间:2024年4月11日
在前面的文章中,我们已经成功编译并启动了 VxWorks,本文将重点介绍 VxWorks 的启动流程,并使用GDB进行调试以更深入地研究启动过程。
1 编译可调式的 VxWorks
参考资料
首先新建 VSB 项目,配置如图 1-1 所示:
图 1-1 新建 BSP
下一步为 BSP 项目添加 DEBUG 配置选项,操作步骤如下:
在 Project Explorer 视图中展开 BSP 项目
右键单击Source Build Configuration ,然后选择 Edit Source Build Configuration
搜索 debug 关键字,选择 Global Debug Flag
将value
改为 y
最终效果如图1-2 所示:
图 1-2 配置 VSB debug
最后构建 VSB。构建完 VSB 之后创建 VIP,如图 1-3 所示:
图 1-3 新建 VIP
在 VIP 中需要配置 INCLUDE_DEBUG_AGENT
和 INCLUDE_DEBUG_AGENT_START
,可以搜索 DEBUG_AGENT
进行配置,配置完如图 1-4 所示:
图 1-4 配置 Debug agent 图
还需要添加 INCLUDE_SHELL INCLUDE_USB_INIT INCLUDE_USB_XHCI_HCD_INIT INCLUDE_USB_GEN2_STORAGE_INIT
,部分配置项如图 1-5 所示:
图 1-5 配置 shell 图
确保以上配置处于加粗的状态,配置完成后构建 VxWorks 镜像。
2 使用 qemu 启动 VxWorks
参考资料
本次使用 qemu 6.0.1 进行启动,使用 qemu 源码编译安装,步骤如下:
wget https://download.qemu.org/qemu-6.0.1.tar.xztar -xvf qemu-6.0.1.tar.xzcd qemu-6.0.1/./configuremake make install
编译完成之后查看 qemu 版本,如图 2-1 所示:
图 2-1 查看 qemu 版本
进入 VIP/default 目录并找到编译完成的 VxWorks,如图 2-2 所示:
图 2-2 查找 VxWorks
接着使用 qemu-img 创建模拟存储设备,命令如下:
qemu-img create file.img 512M
将 VxWorks 与 file.img 放入同一文件夹内,如图 2-3 所示:
图 2-3 VxWorks 与 file.img 同一文件夹内
使用以下命令启动 VxWorks
qemu-system-x86_64 -machine q35 -m 2048 -smp 8 -serial stdio -kernel vxWorks -nographic -monitor none -device nec-usb-xhci,id=usb0,msi=off,msix=off -drive if=none,id=stick,file=file.img -device usb-storage,bus=usb0.0,drive=stick
启动成功如图 2-4 所示:
图 2-4 启动 VxWorks
3 调试 VxWorks
参考资料
接着使用 qemu 对 VxWorks 进行调试,启动命令如下:
qemu-system-x86_64 -machine q35 -m 2048 -smp 8 -serial stdio -kernel vxWorks -nographic -s -S -monitor none -device nec-usb-xhci,id=usb0,msi=off,msix=off -drive if=none,id=stick,file=file.img -device usb-storage,bus=usb0.0,drive=stick
使用 GDB 对 qemu 进行链接,如图 3-1 所示:
图 3-1 GDB 链接 qemu
首先,GDB在地址0x000000000000fff0
处暂停,对应的源代码位置在<vsb_project>/krnl/configlette/dataSegPad.c
文件中。dataSegPad的作用是确保内存管理单元(MMU)页大小边界对齐,在连接VxWorks时,它被明确指定为加载行上的第一个模块。这样做是为了确保数据段中的数据结构是数据段中的第一个项目,以避免数据段与由文本段占用的页面重叠。
一旦MMU初始化完成,VxWorks便开始其启动流程。第一个执行的函数是sysInit。
图 3-2 sysInit
sysInit函数是VxWorks的启动入口。它的主要功能包括禁用中断、设置堆栈,并调用usrInit()
函数。初始堆栈被设置为从sysInit()
的地址向下增长。这个堆栈仅被usrInit()
函数使用,在此之后不再被使用。随后,程序进入usrInit函数,如图 3-3 所示:
图 3-3 usrInit 函数
通过进一步的调试,在 usrInit 函数中一共调用了以下函数:
sysStart (startType); //清除 BSS 并设置向量表基地址。cacheLibInit (USER_I_CACHE_MODE, USER_D_CACHE_MODE); // 初始化缓存。gpDtbInit = dt_blob_start; // 初始化DTBusrFdtInit ((void*)DTB_RELOC_ADDR, (int)DTB_MAX_LEN); // 初始化 flat device tree 库usrBoardLibInit(); // 初始化板级子系统,提供 BSP 访问 APIusrAimCpuInit (); // 初始化 cpuexcVecInit (); // 初始化exception向量vxCpuLibInit (); // 初始化 CPU 识别函数usrCacheEnable (); // 缓存使能objOwnershipInit (); // 初始化 objOwnerLib 库,其中包含对象所有权函数。objInfoInit (); // 初始化查找的对象功能objLibInit ((OBJ_ALLOC_FUNC)FUNCPTR_OBJ_MEMALLOC_RTN, (OBJ_FREE_FUNC)FUNCPTR_OBJ_MEMFREE_RTN, OBJ_MEM_POOL_ID, OBJ_LIBRARY_OPTIONS); // 初始化 objLib 库,该库提供了 VxWorks 用户对象管理工具的接口。vxMemProbeInit (); // 初始化 vxMemProbe() 异常处理classListLibInit (); // 初始化对象列表semLibInit (); // 初始化信号量 condVarLibInit (); // 初始化condition variables库classLibInit (); // 初始化 class 库kernelBaseInit (); // 初始化内核对象taskCreateHookInit (); // 初始化 task hook 相关sysDebugModeInit (); // 设置 debug flag 让系统处于调试模式umaskLibInit(UMASK_DEFAULT); // 提供对内核环境中 POSIX 文件模式创建掩码的支持(支持 unmask())usrKernelInit (VX_GLOBAL_NO_STACK_FILL); // 初始化内核
特别注意 sysInit 函数中的最后一个函数 usrKernelInit ,usrKernelInit 初始化并启动系统的第一个任务,随后进入 usrRoot ,如图 3-4 所示:
图 3-4 usrRoot 函数
usrRoot 函数是系统第一个任务的入口地址,主要是负责 post-kernel 的初始化,该函数存在大量的初始化函数,具体函数如下:
usrKernelCoreInit (); // 初始化 Event 信号,消息队列,看门狗,hook dbgpoolLibInit (); // 初始化内存池,池中的块大小在池创建时指定的并每块大小一致memInit (pMemPoolStart, memPoolSize, MEM_PART_DEFAULT_OPTIONS); // 初始化 memLib 库,该库主要是用于提供 RTP 堆分配内存块的 APImemPartLibInit (pMemPoolStart, memPoolSize); // 初始化 core 内存块kProxHeapInit (pMemPoolStart, memPoolSize); // 初始化 kernel proximity heap,主要是核心附件的堆分配pgPoolLibInit(); // 初始化 Page PoolpgPoolVirtLibInit(); // 初始化 Page Pool 虚拟空间pgPoolPhysLibInit(); // 初始化 Page Pool 物理空间 usrMmuInit (); // 根据 BSP 的 sysPhysMemDesc 表初始化全局的 MMU 映射pmapInit(); // 提供物理地址映射到 kernel/RTP 的功能kCommonHeapInit (KERNEL_COMMON_HEAP_INIT_SIZE, KERNEL_COMMON_HEAP_INCR_SIZE); // 初始化内核堆,用于内核和内核应用程序的动态内存分配,使用 ANSI 标准的 malloc 、free 进行管理usrKernelCreateInit (); // 初始化 Object creation,比如:消息队列,看门狗,信号usrNetApplUtilInit (); // 初始化 application/stack loggingenvLibInit (ENV_VAR_USE_HOOKS); // 初始化 envLib,为了兼容 UNIX 环境变量,可以用过 putenv 创建修改环境变量edrStubInit (); // 在 BOOT记录中记录 ED&RusrSecHashInit (); // 初始化 secHash,如:MD5,SHA1,SHA256usrDebugAgentBannerInit (); // debug agnet bannerusrShellBannerInit (); // shell banervxbDmaLibInit(); // 初始化 VxBus DMA 子系统vxbIsrHandlerInit (VXB_MAX_INTR_VEC, VXB_MAX_INTR_CHAIN); // 初始化 VxBus ISR handler 方法vxbIntLibInit (VXB_MAX_INTR_DEFER); // 初始化 VxBus 中断vxDyncIntLibInit(); // 初始化支持消息中断的 VxBus 动态中断控制器vxIpiLibInit (); // 初始化对称多处理 (SMP) 和非对称多处理 (AMP) 中断。miiBusFdtLibInit(); // 初始化 MII bus FDT 子系统miiBusLibInit(); // 初始化 MII bus 系统vxbPciInit (); // 初始化 VxBus PCI 子系统库,该子系统库提供 PCI 主机控制器驱动程序vxbPciMsiInit (); // 处理 PCI设备的 MSI 和 MSI-X 中断vxbParamLibInit (); // 初始化driver parameter机制,driver parameter 的默认值可以被 BSP(DST) 覆盖usrIaPciUtilsInit(); // 初始化 intel PCIsysHwInit1 (); // 附加系统的初始化,如 PIC, IPI 向量boardInit(); // 板级初始化kernelIdleTaskActivate(); // 添加对 Idle Tasks 的支持 (SMP Only)usrIosCoreInit (); // 内核 I/OusrNetworkInit0 (); // 初始化网络vxbLibInit (); // 初始化VxBus子系统intStartupUnlock (); // 打开中断sysIntEnableFlagSet(); // 标记中断使能usrSerialInit (); // 设置标准输入、输出设备usrClkInit (); // 初始化时钟cpcInit (); // CPUs Cross-Processor Call (SMP Only)vxdbgCpuLibInit (); // 初始化 VxDBG 对 CPU 的控制pgMgrBaseLibInit(); // 初始化 Basic Page ManagerusrKernelExtraInit (); // 初始化内核其它机制,如:Signal、POSIXusrIosExtraInit (); // 初始化IO系统其它机制,如:系统日志,标准 IO 库usrHostnameSetup (TARGET_HOSTNAME_DEFAULT); // 设置 hostname 为 TARGET_HOSTNAME_DEFAULT,一般情况下为 targetsockLibInit (); // Socket 接口selTaskDeleteHookAdd (); // select机制的初始化cpuPwrLightMgrInit ();cpuPwrMgrEnable (TRUE); // 空闲时 CPU 电源管理cplusCtorsLink (); // 确保在内核启动时调用编译器生成的初始化函数,包括 C++ 静态对象的初始化函数。usrSmpInit (); // 多处理器支持miiBusMonitorTaskInit(); // MII 总线监控任务。usrNetworkInit (); // 完成网络系统初始化usrBanner (); // 启动时显示 Wind River bannerusrToolsInit (); // 软件开发工具,例如 target loader、符号表、debug库、kernel shell等usrAppInit (); // 系统启动后调用项目文件中应用程序的初始化函数 usrAppInit(),用户程序入口
在 usrAppInit 函数中主要是用户自定义的程序,不做深入讨论。最后用图总结一下 VxWorks 的启动流程,如图 3-5 所示:
图 3-5 VxWorks 启动流程图
4 内核应用程序
参考资料
usrAppInit 函数会 VxWorks 启动后启动内核应用程序,那么怎么把程序添加到自启动函数中去呢?在此之前初步认识一下内核应用程序。
在 VxWorks 中内核应用程序在内核空间执行,这一点与 Unix Linux 不同,内核应用程序可以是:
由 object module loader 下载并动态链接到操作系统。
静态链接到操作系统,使其成为 kernel image 的一部分。
首先找到 usrAppInit.c 文件,在 c 文件中到 usrAppInit 函数,其函数内容如图 4-1 所示:
图 4-1 usrAppInit 函数
编写一个函数并使用 taskSpawn 启动,代码如下:
#include <taskLib.h>#include <stdio.h>#include <string.h>void helloWorld () { printf("hello vxworks!");}void usrAppInit (void){#ifdef USER_APPL_INIT USER_APPL_INIT; /* for backwards compatibility */#endif /* TODO: add application specific code here */ taskSpawn("hello", 100, 0, 8192, (FUNCPTR)helloWorld, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);}
编译镜像并启动 VxWorks,如图 4-2 所示:
图 4-2 helloWorld 函数运行成功
接着修改代码,代码如下所示:
void test () { char buf[8]; gets(buf);}
这是一个经典的栈溢出,通过 GDB 来看一下 VxWorks 中各个寄存器的情况,同样编译后启动 VxWorks。在 test 函数下断点,首先进行测试,如图 4-3 所示:
图 4-3 溢出测试
首先是溢出情况,如图 4-4 所示:
图 4-4 GDB 查看溢出情况
在这里可以看到返回地址已经被指向了未知的地址。再查看栈中的情况,如图 4-5 所示:
图 4-5 溢出时栈中数据
而在未溢出的情况下,会跳转到 shellInternalFunctionCall 函数,如图 4-6 所示:
图 4-6 未溢出的情况
未溢出时栈中数据,如图 4-7 所示:
图 4-7 未溢出时栈中数据
再来看看 VxWorks 的保护机制,如图 4-8 所示:
图 4-8 VxWorks 保护机制
VxWorks 并没有什么保护机制,因此在利用漏洞比较方便,可以直接执行 shellcode,同时由于 VxWorks 的特性在程序崩溃时就会重启,因此在利用时需要保证程序不会崩溃退出。
5 与 Linux 内存布局进行对比
参考资料
在 Linux 中操作系统将不同进程的虚拟地址和不同内存的物理地址映射起来,进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存。如图 5-1 所示:
图 5-1 内存映射关系
虚拟地址与物理地址的映射有分段、分页以及结合使用三种方式,在 Linux 中内存分页把虚拟空间和物理空间分成大小固定的页。
虚拟内存分为内核空间和用户空间,根据位数的不同,地址空间的范围也不同,32 位和 64 位范围如图 5-2 所示:
图 5-2 Linux 虚拟内存布局
在 VxWorks 中同样存在虚拟内存,同样使用 MMU 进行管理,但是在 VxWorks 中存在多个分区,可以使用 adrSpaceShow 命令展示当前内存使用情况,如图 5-3 所示:
图 5-3 adrSpaceShow 命令使用
对于32位与64位CPU,VxWorks7 所提供的内存管理机制是相同的,虚拟内存被分区管理,每个分区具有专门的用处和相应的分配机制。
VxWorks 虚拟内存大致结构如下,如图5-4 所示:
图 5-4 VxWorks 虚拟内存布局
Shared User Virtual Memory:共享用户虚拟内存区用于为共享映射分配虚拟内存,如共享数据区、共享库、使用mmap()
的MAP_SHARED
选项进行内存映射
RTP Private Virtual Memory RTP私有虚拟内存区用于创建RTP的私有映射:代码与数据段、RTP堆空间以及使用mmap()
的MAP_PRIVATE
选项进行内存映射,在系统中的所有RTP都可以访问整个RTP私有内存区。所以,RTP使用重叠地址空间管理
Kernel System Virtual Memory 内核系统虚拟内存区包含了内核系统内存。从中可以定位到内核镜像(text、data、bss)、内核临接堆(kernel proximity heap)
Kernel Virtual Memory Pool 内核虚拟内存池用于在内核中实现内存的动态管理。该区域用于按需分配虚拟内存,如创建和扩展内核应用程序、内存映射设备、DMA内存、用户保留内存和一致性内存等需求。
在此基础上还有一个 Global RAM Pool 用于动态分配RAM空间的内部分配,该内存池用于创建或扩充:内核通用堆(kernel common heap)、RTP私有内存与共享内存。全局RAM内存池也为如下对象提供内存:VxWorks内核镜像、用户保留内存、持久内存、DMA32堆空间等。
需要注意的是:VxWorks 字节顺序为 little-endian ,在网络程序中必须使用 htons()
将端口转换为网络字节顺序。
在 VxWorks 中可以针对处理器的MMU配置架构独立的接口,以提供虚拟内存支持。在 BSP 中搜索 MMU 相关的内容,如图 5-5 所示:
图 5-5 配置 MMU
可以通过 VM_PAGE_SIZE
虚拟内存默认分页大小,默认值是 0x1000
即 4KB,如图 5-6 所示:
图 5-6 配置虚拟内存分页大小
VxWorks 中可以通过 vmContextShow()
和 rtpMemShow()
函数排查,对应的需要在 BSP 中添加
vmContextShow 需要添加 INCLUDE_VM_SHOW
和INCLUDE_VM_SHOW_SHELL_CMD
rtpMemShow 需要添加 INCLUDE_MEM_EDR_RTP_SHOW
和 INCLUDE_MEM_EDR_RTP_SHOW_SHELL_CMD
6 总结
参考资料
本次主要是通过调试的方式来熟悉了一下 VxWorks 的启动流程,之所以这么做是为了加深印象,在 VIP 项目启动流程以源码的形式给出了,主要文件在 <VIP_Project>/orhConfig.c
,sysLib.c
,sysAlib.s
文件中。
在编译中的过程并不需要开启调试,VxWorks 的调试模式主要还是针对 WorkBench,本次实验用的版本为 2018 版的 VxWorks,对应的 WorkBranch 对 GDB调试的支持并不好。
VxWorks 作为业界领先的实时操作系统,还有许多内容值得我们学习。
另外一个注意点:WorkBench 在新版中对于GDB 的支持更完善了,并不需要使用这种方式进行调试。
7 参考链接
参考资学完了前面三个程序后,可以说已经入门了单片机开发,能进行以下几种基础操作:控制端口输出,编写中断函数,通过uart口输出调试信息。
[1]https://www.vxworks.net/app/907-vxworks-7-programmer-guide-memory-management
[2] https://mp.weixin.qq.com/s/SUhkdP9i7ie-ZESsCVRWmA
作者名片
往 期 热 门
(点击图片跳转)
戳“阅读原文”更多精彩内容!