当前位置: 首页 > news >正文

关于单片机抓取覆盖率的一次尝试

前言

数年前有一个项目,当时使用了gcov抓取了用户态代码的覆盖率。我认为是一个很好的工具和方法。
如果有代码不能运行到,要么就是测试用例不充分,要么就是代码是冗余无效的代码。

做了几年嵌入式了,用户态可以借助linux系统的机制去抓,对于单片机这种没有linux环境的项目如何抓取覆盖率呢。这个疑问在我脑中一直滞留。

这不,最近职场Loser跑路避难结束,在日本成功就职,工作是写点STM32。最近也不加班了,工资降了不少,但WLB基本实现了。所以,这个念头…可以实现以下。我也就来尝试一下,单片机能不能抓取覆盖率数据。

其实是可以的,github上有一个嵌入式gcov的项目,我看到之后就在想,理论上可以为什么不去试一下呢,于是我就自己动手,在qemu的环境下,尝试了一下。

环境

  1. Linux虚拟机一台。
  2. qemu
  3. 其他,什么交叉gcc,python,make,你能点进来看,我想应该都知道吧。
    (我对IDE环境不太熟,就在linux下手搓了,理论上IDE环境也可以)

代码

#include<stdint.h>externuint32_t_estack;externuint32_t_sidata;externuint32_t_sdata;externuint32_t_edata;externuint32_t_sbss;externuint32_t_ebss;intmain(void);voidReset_Handler(void);voidDefault_Handler(void);voidNMI_Handler(void)__attribute__((weak,alias("Default_Handler")));voidHardFault_Handler(void)__attribute__((weak,alias("Default_Handler")));voidMemManage_Handler(void)__attribute__((weak,alias("Default_Handler")));voidBusFault_Handler(void)__attribute__((weak,alias("Default_Handler")));voidUsageFault_Handler(void)__attribute__((weak,alias("Default_Handler")));voidSVC_Handler(void)__attribute__((weak,alias("Default_Handler")));voidDebugMon_Handler(void)__attribute__((weak,alias("Default_Handler")));voidPendSV_Handler(void)__attribute__((weak,alias("Default_Handler")));voidSysTick_Handler(void)__attribute__((weak,alias("Default_Handler")));voidTIM2_IRQHandler(void)__attribute__((weak,alias("Default_Handler")));__attribute__((section(".isr_vector")))constvoid*vector_table[]={&_estack,Reset_Handler,NMI_Handler,HardFault_Handler,MemManage_Handler,BusFault_Handler,UsageFault_Handler,0,0,0,0,SVC_Handler,DebugMon_Handler,0,PendSV_Handler,SysTick_Handler,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,TIM2_IRQHandler,};voidReset_Handler(void){uint32_t*src=&_sidata;uint32_t*dst=&_sdata;while(dst<&_edata){*dst++=*src++;}for(dst=&_sbss;dst<&_ebss;++dst){*dst=0;}(void)main();while(1){}}voidDefault_Handler(void){while(1){}}
#include"stm32f100.h"#include<stdint.h>#include"gcov-io-uart.h"#defineSYSTEM_CORE_CLOCK_HZ8000000UL#ifndefTIMER_IRQ_PERIOD_MS#defineTIMER_IRQ_PERIOD_MS100U#endifstaticvolatileuint32_tg_elapsed_ms;staticvolatileuint32_tg_timer_irq_count;staticvoidirq_enable(void){__asmvolatile("cpsie i");}staticvoiddelay_ms(uint32_tms){constuint32_tstart=g_elapsed_ms;while((uint32_t)(g_elapsed_ms-start)<ms){}}staticvoiduart_putc(usart_t*uart,charch){if(ch==10){uart_putc(uart,13);}while((uart->SR&USART_SR_TXE)==0U){}uart->DR=(uint32_t)ch;}staticvoiduart_puts(usart_t*uart,constchar*text){while(*text!=0){uart_putc(uart,*text++);}}staticvoiduart_put_u32(usart_t*uart,uint32_tvalue){charbuf[10];uint32_tindex=0;if(value==0U){uart_putc(uart,48);return;}while(value>0U&&index<sizeof(buf)){buf[index++]=(char)(48U+(value%10U));value/=10U;}while(index>0U){uart_putc(uart,buf[--index]);}}staticvoiduart1_init(void){RCC->APB2ENR|=RCC_APB2ENR_AFIOEN|RCC_APB2ENR_IOPAEN|RCC_APB2ENR_USART1EN;GPIOA->CRH&=~(0xFUL<<4);GPIOA->CRH|=(0xBUL<<4);/* PA9 USART1_TX. */USART1->CR1=0;USART1->BRR=0x0045;/* 8 MHz / 115200 baud. */USART1->CR1=USART_CR1_TE|USART_CR1_UE;}staticvoiduart2_init(void){RCC->APB2ENR|=RCC_APB2ENR_AFIOEN|RCC_APB2ENR_IOPAEN;RCC->APB1ENR|=RCC_APB1ENR_USART2EN;GPIOA->CRL&=~((0xFUL<<8)|(0xFUL<<12));GPIOA->CRL|=(0xBUL<<8)|(0x4UL<<12);/* PA2 TX, PA3 RX floating input. */USART2->CR1=0;USART2->BRR=0x0045;/* 8 MHz / 115200 baud. */USART2->CR1=USART_CR1_RE|USART_CR1_TE|USART_CR1_UE;}staticvoidtimer_irq_init(void){constuint32_treload=((SYSTEM_CORE_CLOCK_HZ/1000UL)*TIMER_IRQ_PERIOD_MS)-1UL;SYSTICK->CTRL=0;SYSTICK->LOAD=reload;SYSTICK->VAL=0;SYSTICK->CTRL=SYSTICK_CTRL_CLKSOURCE|SYSTICK_CTRL_TICKINT|SYSTICK_CTRL_ENABLE;}voidSysTick_Handler(void){g_elapsed_ms+=TIMER_IRQ_PERIOD_MS;++g_timer_irq_count;}intmain(void){uint32_tseconds=0;uart1_init();uart2_init();timer_irq_init();irq_enable();uart_puts(USART1,"stm32vldiscovery qemu demo\n");while(seconds<10){delay_ms(2000U);++seconds;uart_put_u32(USART1,seconds);uart_puts(USART1,"\n");if(0){uart_puts(USART1,"TEST--->\n");}}uart_puts(USART1,"TEST--->\n");gcov_data_dump_to_uart();}

以上这些都是AI生成的哈,大致就是这么一个思路。我们直接说重要的改动。

1.链接脚本

需要新增一个区域,这个区域要用来放gcov的数据

.gcov_info:{.=ALIGN(4);__gcov_info_start=.;KEEP(*(.gcov_info))KEEP(*(.gcov_info.*))__gcov_info_end=.;.=ALIGN(4);}>FLASH

2.编译参数

编译和链接要新增参数,不然编译不会自动为代码插装

COVERAGE_CFLAGS :=-fprofile-arcs -ftest-coverage -fprofile-info-section COVERAGE_LDFLAGS :=--coverage

3.新增数据导出

我是直接把数据从串口打印出来的,因此需要专门写一个dump函数,借助gcov本身的函数去封装自己的dump机制

#include<stddef.h>#include<stdint.h>#include"stm32f100.h"#include"gcov-io-uart.h"externconststructgcov_info*const__gcov_info_start[];externconststructgcov_info*const__gcov_info_end[];staticvoiduart2_putc(charch){while((USART2->SR&USART_SR_TXE)==0U){}USART2->DR=(uint32_t)ch;}void__gcov_filename_to_gcfn(constchar*filename,void(*dump_fn)(constvoid*data,unsignedlength,void*arg),void*arg);void__gcov_info_to_gcda(conststructgcov_info*info,void(*filename_fn)(constchar*filename,void*arg),void(*dump_fn)(constvoid*data,unsignedlength,void*arg),void*(*allocate_fn)(unsignedlength,void*arg),void*arg);staticvoiddump_hex_byte(uint8_tvalue){staticconstcharhex[]="0123456789abcdef";uart2_putc(hex[(value>>4)&0x0FU]);uart2_putc(hex[value&0x0FU]);}staticvoidcoverage_dump_bytes(constvoid*data,unsignedlength,void*arg){constuint8_t*bytes=(constuint8_t*)data;unsigned*column=(unsigned*)arg;for(unsignedi=0;i<length;++i){dump_hex_byte(bytes[i]);*column+=2U;if(*column>=64U){uart2_putc('\n');*column=0;}}}staticvoidcoverage_dump_filename(constchar*filename,void*arg){__gcov_filename_to_gcfn(filename,coverage_dump_bytes,arg);}staticvoid*coverage_allocate(unsignedlength,void*arg){staticuint8_tbuffer[512];(void)arg;if(length>sizeof(buffer)){returnNULL;}returnbuffer;}voidgcov_data_dump_to_uart(void){unsignedcolumn=0;for(conststructgcov_info*const*info=__gcov_info_start;info!=__gcov_info_end;++info){__gcov_info_to_gcda(*info,coverage_dump_filename,coverage_dump_bytes,coverage_allocate,&column);}}

此外还有点编译问题,会提示链接找不到,因为gcov默认是需要文件系统的,因此需要提供一些文件系统的假接口。不会链接会报错。

#include<stddef.h>#include<stdint.h>externuint32_t_end;externuint32_t_estack;void_exit(intstatus){(void)status;while(1){}}int_close(intfile){(void)file;return-1;}int_fstat(intfile,void*st){(void)file;(void)st;return0;}int_getpid(void){return1;}int_isatty(intfile){(void)file;return1;}int_kill(intpid,intsig){(void)pid;(void)sig;return-1;}int_lseek(intfile,intptr,intdir){(void)file;(void)ptr;(void)dir;return0;}int_read(intfile,char*ptr,intlen){(void)file;(void)ptr;(void)len;return0;}int_write(intfile,constchar*ptr,intlen){(void)file;(void)ptr;returnlen;}void*_sbrk(ptrdiff_tincr){staticuint8_t*heap_end;uint8_t*prev_heap_end;if(heap_end==NULL){heap_end=(uint8_t*)&_end;}prev_heap_end=heap_end;if((heap_end+incr)>=(uint8_t*)&_estack){return(void*)-1;}heap_end+=incr;returnprev_heap_end;}void_init(void){}void_fini(void){}

3.其他脚本

qemu运行脚本

#!/usr/bin/env shset-eu ELF="${1:-build/gcov_all/firmware.elf}"exec qemu-system-arm \-M stm32vldiscovery \-cpu cortex-m3 \-kernel"$ELF"\-nographic \-serial mon:stdio \-serial pty

python获取串口数据脚本

#!/usr/bin/env python3importosimportsysimporttimeimportbinascii device_name="/dev/pts/5"deadline=time.time()+10fd=os.open(device_name,os.O_RDONLY|os.O_NOCTTY|os.O_NONBLOCK)hex_buffer=b""defwait_for_start():globalfdglobaldeadlineglobalhex_buffertry:data=os.read(fd,1024)exceptBlockingIOError:returndeadline=time.time()+5iflen(data):data=data.replace(b"\n",b"")data=data.replace(b"\r",b"")data=data.replace(b" ",b"")hex_buffer+=dataif__name__=="__main__":whiletime.time()<deadline:wait_for_start()print("over")os.close(fd)print(hex_buffer,len(hex_buffer))data_write=binascii.unhexlify(hex_buffer)withopen("stream.bin","ab")asf:f.write(data_write)

运行结果如下,目录下的bin就是全部的覆盖率数据。

4.后续操作

# 提交覆盖率数据arm-none-eabi-gcov-toolmerge-streamstream.bin# 获取覆盖率lcov--capture--directory ${dir}--output-file coverage.info--gcov-tool arm-none-eabi-gcov# 生成htmlgenhtml coverage.info--output-directory html

5.结果


以上 OVER

总结

所以理论上单片机也可以测试覆盖率的。唯一的问题在于内存是否足够。
全局插装编译,代码体积会暴增。我这有一块stm32的f303,目前代码编译完70多K,我全局插装,编完提示内存缺30K,当然我这个项目库比较多,如果我只插装业务,还是够的,但可执行文件好像也变成了120多K,所以总的来说,内存是限制,其他的,都还好。

http://www.cnnetsun.cn/news/3493309.html

相关文章:

  • 赋能实体·智赢未来|零壹岛OPC超级个体训练营-长沙站完美收官
  • 2026衡阳黄金回收白银回收铂金回收靠谱临街实体公安备案支持到店核验门店联系方式推荐
  • li-wen 智能资源管理:机器学习如何预测 OBS 构建系统未来需求
  • 嵌入式通信实战:I2C与CAN总线寄存器级配置与避坑指南
  • Express框架核心原理与Node.js实战指南
  • AI 写完了功能,谁来为凌晨两点的报警负责?
  • Spring Gateway+Sa-Token+Nacos微服务认证鉴权实战
  • MUI框架openWindow方法详解与移动端页面跳转优化
  • alibaba-easyExcel动态列导入导出
  • 单人 + GPT-4o,14 天完成 120 万行 Rust 代码!Lumen 创始人复盘从 Go 到 Rust 全量重写全过程
  • 收银系统实施部署技术文档——从架构到落地的全流程解析
  • 企业级 AI 应用工程实战(十三):AI 应用日志、Token 成本统计与用量计费设计
  • R3nzSkin国服特供版:开源英雄联盟换肤工具终极指南
  • 产业聚焦:水务水质在线监测加速拥抱 TDengine成新风口
  • KeyboardChatterBlocker:三步修复机械键盘连击问题的终极免费方案
  • 西门子 PLC 与 C# 通信_项目实战
  • 2026年口碑好的白钢雕塑,哪家才是专业之选?
  • Windows电脑如何快速安装苹果iPhone USB网络共享驱动:3分钟搞定iPhone个人热点
  • 2026小红书去水印下载工具哪个好?APP网页工具对比与合规指南
  • openai模型性能对比
  • JESD204B时钟架构设计与FPGA实现关键技巧
  • 2026年最新AI agent面试(08)_大模型训练评测
  • 博途软件PLC程序下载与在线监控实战指南
  • 百考通生成:一站式计算机与工程类项目学习与开发平台
  • Meta为AI聊天机器人推安全新功能:守护青少年,遇危险及时通知家长!
  • 高通SA8295域控制器电源设计与EMC优化实践
  • Udio AI中文语境优化实战(独家训练集泄露):让“古风”“赛博朋克”“国潮电子”风格准确率提升310%
  • 基于 Eino 框架从零搭建一个 RAG
  • 你是我的眼
  • PCB收放板机的机械手选型——三轴与六轴的适用边界