如何构建裸机Hello World目标程序并在SkyEye全数字实时仿真平台上运行及调试?

SkyEye 简介

SkyEye全数字实时仿真平台,简称SkyEye,是一款支持 ARM、TI DSP、PowerPC、X86、SPARC、龙芯、飞腾等多种处理器体系架构的指令级仿真平台。

SkyEye可以部署在桌面计算机上,开发人员可以基于SkyEye提供的已有模型库的组件(如处理器、内存总线、存储器、片上外设、外围总线设备等),通过图形化搭建环境拖拽需要的组件并定制与实际目标机相同的虚拟目标系统。

使运行在真实目标机上的二进制代码可以不加修改直接在虚拟系统中运行,并且可以得到与在真实目标机下相同的执行结果,支持的目标二进制文件主要包括ELF、raw binary、COFF等格式。配合调试器,可以查看到虚拟系统中所有的资源,包括寄存器,存储器和端口系统等。
在这里插入图片描述
SkyEye支持运行的目标系统软件包括操作系统OS软件和裸机应用程序,今天以经典程序hello world为例介绍下裸机程序如何一步步编译构建成二进制文件并在SkyEye上运行调试,处理器架构采用ARM架构。

目标二进制文件构建过程

所需工具:

  • SkyEye安装包;
  • linux系统,本人使用ubuntu 14.04;
  • ARM 交叉编译工具链;

裸机 hello_world.c

程序首先编写hello world程序,创建hello_world.c文件,编写main函数,如下所示:

void main()
{    
    uart_printf("hello world!");    
    return;
}

代码通过调用uart_printf函数输出「hello world」字符串,由于是裸机程序,不能调用库中的printf函数,而是要自己实现输出函数,实现一个最简单的输出字符串的uart_printf函数,如下:

#define UART_TX_ADDR 0x640000
void uart_puts(const char *s)
{    
    while(*s)    
    {        
        *(char *)UART_TX_ADDR = *s++;    
    }    
    return;
}

void uart_printf(char *fmt, ...)
{    
    uart_puts(fmt);
}

上述的功能是把uart_printf要输出的字符串中的每个字符写入0x00640000这个内存地址映射的存储空间中,这个内存地址所在空间映射的是串口设备的发送寄存器的内存地址。

至此,hello world C代码完成,但是处理器刚复位时是不会直接执行C代码的,所以在执行逻辑转移到C代码之前,必须正确设置以下内容:

  1. 全局变量
    • 已初始化
    • 未初始化
  2. 只读数据

C语言使用栈来存储本地变量,传递参数,存储返回地址等。所以在将控制权交给C代码之前,栈必须正确设置。

栈在ARM架构中是非常灵活的,因为它完全由软件实现。ARM CPU寄存器R13被用作栈指针,因此,在启动代码中需要将R13设置为堆栈的大小。

例如:

ldr sp, =0x1000 //初始化堆栈指针

全局变量

编译C代码时,编译器将初始化的全局变量放在.data段。

C语言保证所有未初始化的全局变量都将初始化为零。当编译C程序时,一个名为.bss的独立段用于放置未初始化变量的描述。在PC指针跳转到C代码之前,必须将这些变量对应的内存位置初始化为零。

只读数据

GCC将标记为const的全局变量放在一个名为.rodata的独立段中。.rodata还用于存储字符串常量。

通过上述的必须设置的条件,就能创建链接脚本和启动代码。

启动代码

编写启动代码start.S,该裸机程序只是测试验证通过SkyEye可以加载运行目标程序,所以没有设置ARM工作模式和其他CPU寄存器的初始化,只对堆栈指针SP进行了初始化,然后跳转到C代码的入口函数main执行。

.text    
    .global begin
begin:    
    LDR SP,=0x1000 //初始化堆栈指针    
    bl main //跳转到main函数    
    b . //循环执行该条指令

链接脚本

编写链接脚本hello_world.lds来控制段如何合并以及它们在内存中的位置,如下所示:

.ENTRY(begin) //设置程序入口点SECTIONS {    . = 0x100000; //程序的链接地址是0x100000    . = ALIGN(4) //对当前地址4字节对齐    .text : {        start.o (.text) //CPU上电后,首先执行start.o代码        * (.text);     }    .rodata :{        * (.rodata);    }    . = ALIGN(4) //对当前地址4字节对齐    .data:{        *(.data)    }    . = ALIGN(4) //对当前地址4字节对齐    .bss : {        * (.bss);    }}

构建目标二进制文件使用汇编器as汇编启动代码,如下命令:

arm-linux-as -o start.o start.S

使用编译器gcc编译hello_world.c,加上-g用于调试,如下命令:

arm-linux-gcc -g -o hello_world.o hello_world.c

使用链接器ld链接生成可执行文件,命令如下:

arm-linux-ld -Thello_world.lds -static -o hello_world start.o hello_world.o

由此hello_world目标二进制文件已经构建成功,通过arm-linux-readelf来查看生成的目标二进制文件信息,如下所示:
在这里插入图片描述
也可以编写Makefile来构建和编译工程。

Makefile编写

如下所示:

作者:迪捷软件
链接:https://zhuanlan.zhihu.com/p/345549194
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

#begin    
    CC=arm-linux-gcc    
    LD=arm-linux-ld        

    CFLAGS= -c -Wall -g -O1    
    LDFLAGS= -Thello_world.lds  -static          

    all: hello_world  

    hello_world: start.o hello_world.o   

    $(LD) $(LDFLAGS)  start.o hello_world.o -o hello_world     

    arm-linux-objdump -xS hello_world > hello_world.s  

    start.o:start.S    
    $(CC) $(CFLAGS) start.S      

    hello_world.o:hello_world.c    
    $(CC) $(CFLAGS) hello_world.c   

    clean:    
    rm -rf *.o hello_world *.s
#end

直接输入make就可以生成hello_world二进制文件。

至此,hello_world目标二进制文件已经生成,现在可以基于SkyEye 图形化界面构建虚拟目标系统来运行hello_world程序。

使用SkyEye运行目标二进制程序

创建新的工程

首先,打开SkyEye界面,点击「文件 → 新建」,输入工程名,选择要导入的源码和目标二进制文件即可创建新的工程,工程目录如下:

图形化搭建

双击打开hello_world_testcase.gp打开图形化搭建界面,可以通过先点击所需组件,再点击网格中所要放置位置即可。组件如下图所示:

虚拟目标系统构建

选择单板(通用的base_mach)、CPU(选择ARM926EJS处理器)和内存总线设备memory_space、存储设备RAM和串口设备,如下所示是配置串口设备的内存地址,与上述所说的串口设备的内存映射空间的地址一致。

虚拟目标系统构建

选择单板(通用的base_mach)、CPU(选择ARM926EJS处理器)和内存总线设备memory_space、存储设备RAM和串口设备,如下所示是配置串口设备的内存地址,与上述所说的串口设备的内存映射空间的地址一致。

通过各个模块直接的接口连线来构建整个虚拟目标系统,如下图所示:

至此,虚拟目标系统搭建完成,配置启动脚本后即可加载运行。

启动脚本配置

启动脚本中主要配置要加载的目标二进制文件,如下图所示:

要使用代码覆盖率功能,可以配置使能覆盖率的命令,如下图所示:

加载和运行

点击工程目录,选择菜单栏中的加载图标,当有SkyEye term弹窗并没有报错信息,则说明工程加载成功,可以点击运行按钮,如下图所示:

点击运行,可以看到串口有hello world!输出,如下图所示:

到此,整个裸机程序从编译生成目标二进制文件到通过SkyEye构建虚拟目标系统来加载运行目标程序的完整过程结束了。

下面是介绍SkyEye平台提供的代码覆盖率功能和远程GDB调试功能。

SkyEye调试和代码覆盖率功能

代码覆盖率功能

首先需要在启动脚本中配置使能代码覆盖率的命令,然后加载运行工程,点击暂停,点击覆盖率统计按钮,即可生成覆盖率统计结果,如下图所示:

支持打开和导出覆盖率报告,覆盖率报告内容如下图所示:

GDB远程调试功能选中项目后,可以通过工具栏中,或者通过右键「调试」功能,会有调试配置弹窗,配置如下:

选择调试器中的GDB调试器为ARM架构GDB,如下图所示:

点击调试,即可进入调试界面并在设置的begin入口位置停住,可以进行单步和断点和内存查看等操作,如下图所示:

如下图 debug 按钮 是最常用的 debug 按钮。

  • 表示当前继续执行代码,直到遇到下一个断点,快捷键 F8;
  • 表示暂停执行;
  • 表示停止调试;
  • 表示断开当前远程连接;
  • 表示进入当前函数内部,一步一步执行,快捷键 F5(step into);
  • 表示运行下一行代码,执行当前行,但不进入执行细节,快捷键 F6(step over);
  • 表示退出当前方法,返回到调用层,快捷键为 F7(step return);
  • 表示进行指令单步调试。

可以进行单步,如下所示:

点击全速执行,点击暂停,会发现停在最后一条跳转指令位置,如下所示:

SkyEye主要功能介绍

通过上面的例程讲述了如何构建裸机hello world目标二进制程序并通过SkyEye运行调试的方法,也介绍了SkyEye的调试和代码覆盖率功能,那么,SkyEye还有没有其他的有用的功能协助开发者或测试者进行目标系统软件的验证呢?

答案当然是肯定的,下面列举SkyEye一些主要的功能:

  1. 更灵活快速的虚拟目标系统搭建——通过可视化图形界面拖拽虚拟硬件组件快速搭建;
  2. 仿真状态可控性、确定性和重复性——在虚拟系统上运行的二进制文件与实际目标上运行的二进制文件相同,仿真过程可以通过运行、暂停控制、可以随时重复执行,每次运行结果是确定的,可以使用软件复现问题;
  3. 提供GDB源码调试和汇编级调试工具,使开发者更高效的分析和定位问题;
  4. 提供代码覆盖率和生成报告功能,进行源码和目标码的覆盖率分析;
  5. 提供故障注入功能,可以进行内存和IO的故障注入进行测试;
  6. 提供协同仿真工具,支持与其他异构模型协同仿真;
  7. 提供外设建模工具和二次开发API接口,方便用户进行自定义仿真设备的快速构建和二次开发;
  8. 支持外部数据激励功能,可以通过与上位机软件通过虚拟串口、网络等方式建立通信,通过SkyEye构建的虚拟环境与目标系统软件进行数据的交互,完成闭环测试;
  9. 提供Python API接口,可以通过自动化测试脚本构建所需测试环境,例如监视内存地址,监视和修改应用软件全局变量的值;
  10. Docker容器化支持;
  11. 界面提供自动化测试功能,可以选择所需测试用例自动运行并给出测试结果。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值