0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

通过篡改特定代码数据修复单片机BUG的方法

MCU开发加油站 来源:嵌入式基地 2023-05-11 09:22 次阅读
一、前言

嵌入式产品开发中,难以避免地会因为各种原因导致最后出货的产品存在各种各样的BUG,通常会给产品进行固件升级来解决问题。记得之前在公司维护一款BLE产品的时候,由于前期平台预研不足,OTA参数设置不当,导致少数产品出现不能OTA的情况,经过分析只需改变代码中的某个参数数值即可,但是产品在用户手里,OTA是唯一能更新代码的方式,只能给用户重发产品。后来在想,是否可以提前做好一个接口,支持动态地传输少量代码到产品中临时运行,通过修改特定位置的Flash代码数据来修复产品的棘手BUG?多留一个后门,有时候令产品出棘手问题的往往是那么一两行代码或者几个初始化的参数不对,那么这种方法也可以应应急,虽然操作比较骚。

二、创建演示工程

本文以STM32F103C8T6单片机为例创建演示工程,分为app和bootloader两个工程。即将mcu的Flash分为“app”和“bootloader”两个区域, bootloader放在0x8000000为起始的24KB区域内,app放在0x8006000为起始的后续区域。bootloader完成对app的Flash数据修改。

1、app工程

注意app的工程需要在keil上修改ROM起始地址。

c91cdbae-ef1f-11ed-90ce-dac502259ad0.png

还要在app代码的开头设置向量偏移(调用一行代码):


	

NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x6000);

app工程的逻辑为:先顺序执行3个不同速度的LED闪灯过程(20ms、200ms、500ms、切换亮灭),最后进入到一个循环状态每秒切换一次LED的状态闪烁。代码如下:


	

voidinit_led(void) { GPIO_InitTypeDefGPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_All; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); GPIO_ResetBits(GPIOB,GPIO_Pin_10); GPIO_SetBits(GPIOB,GPIO_Pin_10); } voidled_blings_1(void) { uint32_ti; for(i=0;i< 10;i++) { GPIO_SetBits(GPIOB,GPIO_Pin_10); delay_ms(20); GPIO_ResetBits(GPIOB,GPIO_Pin_10); delay_ms(20); } } voidled_blings_2(void) { uint32_ti; for(i=0;i< 10;i++) { GPIO_SetBits(GPIOB,GPIO_Pin_10); delay_ms(200); GPIO_ResetBits(GPIOB,GPIO_Pin_10); delay_ms(200); } } voidled_blings_3(void) { uint32_ti; for(i=0;i< 10;i++) { GPIO_SetBits(GPIOB,GPIO_Pin_10); delay_ms(500); GPIO_ResetBits(GPIOB,GPIO_Pin_10); delay_ms(500); } } intmain() { NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x6000); SysTick_Init(72); init_led(); led_blings_1(); led_blings_2(); led_blings_3(); while(1) { GPIO_SetBits(GPIOB,GPIO_Pin_10); delay_ms(1000); GPIO_ResetBits(GPIOB,GPIO_Pin_10); delay_ms(1000); } }

为了分析汇编和查看bin文件数据,我们需要在keil中添加两条命令,分别生成.dis反汇编和.bin的代码文件。(具体的目录情况依葫芦画瓢)


	

fromelf--text-a-c--output=all.disObjTemplate.axf fromelf--bin--output=test.binObjTemplate.axf

c9309b76-ef1f-11ed-90ce-dac502259ad0.png

先将app的代码烧写进单片机,注意烧写设置里面选择“Erase Sectors”只擦除需要烧写的地方。

2、bootloader工程

在bootloader中分为两部分,不变的代码部分和变动的代码部分(error_process函数)。初次编译的时候error_process写为空函数,当我们有需求对App进行修改的时候,我们重新编译工程对error_process函数进行填充。为了重新编译工程的时候不影响之前函数的链接地址,特意将error_process函数放到代码区的最后0x8000800地址处,理由是原来工程大小是1.51KB,擦除页大小是2KB,所以需要2KB对齐,对齐处的地址就选择0x8000800为起始。代码如下:

	

#defineFLASH_PAGE_SIZE2048 #defineERROR_PROCESS_CODE_ADDR0x8000800 voiderror_process(void)__attribute__((section(".ARM.__at_0x8000800"))); voidinit_led(void) { GPIO_InitTypeDefGPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_All; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); GPIO_ResetBits(GPIOB,GPIO_Pin_10); GPIO_SetBits(GPIOB,GPIO_Pin_10); } uint32_tpageBuf[FLASH_PAGE_SIZE/4]; voiderror_process(void) { } voideraseErrorProcessCode(void) { FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP| FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR); FLASH_ErasePage(ERROR_PROCESS_CODE_ADDR); FLASH_Lock(); } void(*boot_jump2App)(); voidboot_loadApp(uint32_taddr) { uint8_ti; if(((*(vu32*)addr)&0x2FFE0000)==0x20000000) { boot_jump2App=(void(*)())*(vu32*)(addr+4); __set_MSP(*(vu32*)addr); for(i=0;i< 8;i++) { NVIC->ICER[i]=0xFFFFFFFF; NVIC->ICPR[i]=0xFFFFFFFF; } boot_jump2App(); while(1); } } intmain() { uint32_tflag; SysTick_Init(72); flag=*((uint32_t*)ERROR_PROCESS_CODE_ADDR); if((flag!=0xFFFFFFFF)&&(flag!=0)) { init_led(); GPIO_ResetBits(GPIOB,GPIO_Pin_10); delay_ms(1000); delay_ms(1000); error_process(); eraseErrorProcessCode(); } boot_loadApp(0x8006000); while(1); }

一进main函数就读取0x8000800地址处的32位数据,如果不是全F或者全0那么这个地方是有函数体存在需要执行的,那么将LED亮起2秒钟代表bootloader识别到有处理程序需要执行(当然这里还需要加一些error_process代码数据是否完整之类的判断机制,这里演示先略去)。执行完处理程序后将处理程序擦除(数据变为全F),避免以后每次上电都重复擦写Flash。

error_process函数代码的数据由产品正常使用期间通过数据接口传入直接写入到0x8000800处(这部分的demo略去),编译后查看生成的bin文件将error_process部分的代码截取出来传输到Flash地址0x8000800处。bootloader的代码烧写进单片机时注意烧写设置里面选择“Erase Sectors”只擦除需要烧写的地方。keil设置里ROM地址改回0x08000000。 三、修改app的特定参数

在app的工程中以“led_blings_1”函数为例,反汇编如下:


	

$t i.led_blings_1 led_blings_1 0x08006558:b510..PUSH{r4,lr} 0x0800655a:2400.$MOVSr4,#0 0x0800655c:e010..B0x8006580;led_blings_1+40 0x0800655e:f44f6180O..aMOVr1,#0x400 0x08006562:4809.HLDRr0,[pc,#36];[0x8006588]=0x40010c00 0x08006564:f7fffea2....BLGPIO_SetBits;0x80062ac 0x08006568:2014.MOVSr0,#0x14 0x0800656a:f7ffffaf....BLdelay_ms;0x80064cc 0x0800656e:f44f6180O..aMOVr1,#0x400 0x08006572:4805.HLDRr0,[pc,#20];[0x8006588]=0x40010c00 0x08006574:f7fffe98....BLGPIO_ResetBits;0x80062a8 0x08006578:2014.MOVSr0,#0x14 0x0800657a:f7ffffa7....BLdelay_ms;0x80064cc 0x0800657e:1c64d.ADDSr4,r4,#1 0x08006580:2c0a.,CMPr4,#0xa 0x08006582:d3ec..BCC0x800655e;led_blings_1+6 0x08006584:bd10..POP{r4,pc} $d 0x08006586:0000..DCW0 0x08006588:40010c00...@DCD1073810432

由于led是20ms交替亮灭一次,如果我们觉得这个参数有问题想改成100ms,从汇编上来说就是要改变两行代码:

	

0x08006568:2014.MOVSr0,#0x14 0x08006578:2014.MOVSr0,#0x14 改为 0x08006568:20642MOVSr0,#0x64 0x08006578:20642MOVSr0,#0x64

bootloader工程中error_process的函数实现如下:


	

voiderror_process(void) { #defineMODIFY_FUNC_ADDR_START0x08006558 uint32_talignPageAddr=MODIFY_FUNC_ADDR_START/FLASH_PAGE_SIZE*FLASH_PAGE_SIZE; uint32_tcnt,i; //1.copyoldcode memcpy(pageBuf,(void*)alignPageAddr,FLASH_PAGE_SIZE); //2.changecode. //由于Flash操作2KB页的特性,0x08006558不满2kb,因此偏移为0x558,0x558/4=342 pageBuf[90+256]=(pageBuf[90+256]&0xFFFF0000)|0x2064; pageBuf[94+256]=(pageBuf[94+256]&0xFFFF0000)|0x2064; //3.eraseoldcode,copynewcode. FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP| FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR); FLASH_ErasePage(alignPageAddr); cnt=FLASH_PAGE_SIZE/4; for(i=0;i< cnt; i++)     {         FLASH_ProgramWord(alignPageAddr + i * 4,pageBuf[i]); } FLASH_Lock(); }

由于Flash的2KB页擦除特性,这里先将待修改代码区的Flash页数据拷贝到缓冲buffer里,然后修改buffer里的数据,之后擦除Flash相关页,最后将buffer里修改后的数据重新写回到Flash里去。error_process函数的反汇编如下:


	

$t .ARM.__at_0x8000800 error_process 0x08000800:b570p.PUSH{r4-r6,lr} 0x08000802:4d1a.MLDRr5,[pc,#104];[0x800086c]=0x8006000 0x08000804:142a*.ASRSr2,r5,#16 0x08000806:4629)FMOVr1,r5 0x08000808:4819.HLDRr0,[pc,#100];[0x8000870]=0x20000008 0x0800080a:f7fffcbd....BL__aeabi_memcpy;0x8000188 0x0800080e:4818.HLDRr0,[pc,#96];[0x8000870]=0x20000008 0x08000810:f8d00568..h.LDRr0,[r0,#0x568] 0x08000814:f36f000fo...BFCr0,#0,#16 0x08000818:f2420164B.d.MOVr1,#0x2064 0x0800081c:4408.DADDr0,r0,r1 0x0800081e:4914.ILDRr1,[pc,#80];[0x8000870]=0x20000008 0x08000820:f8c10568..h.STRr0,[r1,#0x568] 0x08000824:4608.FMOVr0,r1 0x08000826:f8d00578..x.LDRr0,[r0,#0x578] 0x0800082a:f36f000fo...BFCr0,#0,#16 0x0800082e:f2420164B.d.MOVr1,#0x2064 0x08000832:4408.DADDr0,r0,r1 0x08000834:490e.ILDRr1,[pc,#56];[0x8000870]=0x20000008 0x08000836:f8c10578..x.STRr0,[r1,#0x578] 0x0800083a:f7fffd53..S.BLFLASH_Unlock;0x80002e4 0x0800083e:20355MOVSr0,#0x35 0x08000840:f7fffcca....BLFLASH_ClearFlag;0x80001d8 0x08000844:4628(FMOVr0,r5 0x08000846:f7fffccd....BLFLASH_ErasePage;0x80001e4 0x0800084a:14ae..ASRSr6,r5,#18 0x0800084c:2400.$MOVSr4,#0 0x0800084e:e007..B0x8000860;error_process+96 0x08000850:4a07.JLDRr2,[pc,#28];[0x8000870]=0x20000008 0x08000852:f8521024R.$.LDRr1,[r2,r4,LSL#2] 0x08000856:eb050084....ADDr0,r5,r4,LSL#2 0x0800085a:f7fffd0d....BLFLASH_ProgramWord;0x8000278 0x0800085e:1c64d.ADDSr4,r4,#1 0x08000860:42b4.BCMPr4,r6 0x08000862:d3f5..BCC0x8000850;error_process+80 0x08000864:f7fffcfe....BLFLASH_Lock;0x8000264 0x08000868:bd70p.POP{r4-r6,pc} $d 0x0800086a:0000..DCW0 0x0800086c:08006000.`..DCD134242304 0x08000870:20000008...DCD536870920

那么这124个字节就是最终要传输到0x8000800处的函数数据。传输完毕后软复位mcu,bootloader将app的Flash数据进行篡改,达到改变程序功能的目的。

为什么要在bootloader运行时篡改app的数据?按理说在app运行时接收到error_process函数的更新数据后可以立刻运行,但是由于涉及到对app自身代码的修改,涉及Flash修改的一些相关函数有可能会被暂时破坏而导致代码运行崩溃。

四、跳过app的某些函数

如果想跳过“led_blings_1”函数,有2种方法:

1、函数内部跳过

	
		即将以下汇编语句 0x0800655a:2400.$MOVSr4,#0 修改为 0x0800655a:e013.$B0x08006584

在“led_blings_1”函数入口处指令修改直接跳转到函数出口处。至于汇编的机器码和用法文末有相关资料可以查阅。

因为修改处的字节偏移为0x55a,是pageBuf下标为342元素的高2Byte,需要在error_process函数中做如下修改:


	

pageBuf[342]=(pageBuf[342]&0x0000FFFF)|0xe0130000;

2、函数调用处跳过

main函数汇编如下:


	

$t i.main main 0x080065f8:f44f41c0O..AMOVr1,#0x6000 0x080065fc:f04f6000O..`MOVr0,#0x8000000 0x08006600:f7fffe5c...BLNVIC_SetVectorTable;0x80062bc 0x08006604:2048HMOVSr0,#0x48 0x08006606:f7ffff01....BLSysTick_Init;0x800640c 0x0800660a:f7ffff85....BLinit_led;0x8006518 0x0800660e:f7ffffa3....BLled_blings_1;0x8006558 0x08006612:f7ffffbb....BLled_blings_2;0x800658c 0x08006616:f7ffffd3....BLled_blings_3;0x80065c0 0x0800661a:e011..B0x8006640;main+72 0x0800661c:f44f6180O..aMOVr1,#0x400 0x08006620:4808.HLDRr0,[pc,#32];[0x8006644]=0x40010c00 0x08006622:f7fffe43..C.BLGPIO_SetBits;0x80062ac 0x08006626:f44f707aO.zpMOVr0,#0x3e8 0x0800662a:f7ffff4f..O.BLdelay_ms;0x80064cc 0x0800662e:f44f6180O..aMOVr1,#0x400 0x08006632:4804.HLDRr0,[pc,#16];[0x8006644]=0x40010c00 0x08006634:f7fffe38..8.BLGPIO_ResetBits;0x80062a8 0x08006638:f44f707aO.zpMOVr0,#0x3e8 0x0800663c:f7ffff46..F.BLdelay_ms;0x80064cc 0x08006640:e7ec..B0x800661c;main+36 $d 0x08006642:0000..DCW0 0x08006644:40010c00...@DCD1073810432

下面是调用语句


	

0x0800660e:f7ffffa3....BLled_blings_1;0x8006558

直接将此语句改为空语句nop(0xbf00)即可跳过调用,由于该命令占用4个字节,nop是两个字节的命令,所以替换为两个nop命令。


	

0x0800660e:bf00bf00....NOP

因为修改处的字节偏移为0x60e,是pageBuf下标为387元素的高2Byte和下标为388元素的低2Byte,需要在error_process函数中做如下修改:


	

pageBuf[387]=(pageBuf[387]&0x0000FFFF)|0xbf000000; pageBuf[388]=(pageBuf[388]&0xFFFF0000)|0x0000bf00;

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6002

    文章

    43986

    浏览量

    620934
  • 嵌入式
    +关注

    关注

    4983

    文章

    18297

    浏览量

    288624
  • STM32
    +关注

    关注

    2240

    文章

    10675

    浏览量

    348868
  • 代码
    +关注

    关注

    30

    文章

    4557

    浏览量

    66822
  • BUG
    BUG
    +关注

    关注

    0

    文章

    154

    浏览量

    15581

原文标题:通过篡改特定代码数据修复单片机BUG的方法

文章出处:【微信号:mcugeek,微信公众号:MCU开发加油站】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    单片机bug修复#单片机

    单片机修复
    笑君愁
    发布于 :2022年07月21日 20:15:58

    通过单片机来控制ppt的操作,如使它全屏,退出等

    51单片机连接pl2303通过USB线发送编码数据给电脑,通过单片机的按键来控制电脑中ppt的操作, 如要使ppt全屏,按电脑键盘的F5就可
    发表于 05-19 22:45

    通过单片机来控制ppt的操作,如使它全屏,退出等

    51单片机连接pl2303通过USB线发送编码数据给电脑,通过单片机的按键来控制电脑中ppt的操作, 如要使ppt全屏,按电脑键盘的F5就可
    发表于 05-19 22:57

    详细介绍Flash型单片机的加密解密方法

    HCS12系列单片机为例,介绍一种典型的加解密机制,并着重讨论使用密码加解密的方法以及相应的用户接口程序设计思路。厂商利用单片机进行产品开发时,都会关心其代码
    发表于 10-21 19:59

    单片机Bug战斗的那些经历

    玩转单片机有几年的时间了,从接触51开始就走上了看不到尽头的程序员之路。也许大多会认为,敲了几天几夜代码将作品或者项目完美完成的那一刻是最开心最得意的时候。我却认为,真正快乐的是与Bug斗争的过程
    发表于 11-05 17:09

    单片机Bug战斗的那些经历

    单片机有几年的时间了,从接触51开始就走上了看不到尽头的程序员之路。也许大多会认为,敲了几天几夜代码将作品或者项目完美完成的那一刻是最开心最得意的时候。我却认为,真正快乐的是与Bug斗争的过程,最后
    发表于 12-20 17:15

    单片机程序代码烧写方法

    单片机程序代码烧写方法有专用编程器烧写、编程器/调试器烧写及ISP和IAP单片机代码保护的实际应用:在实际应用中,以上介绍的
    发表于 07-19 09:33

    设计一个求解16位原码数据的补码的程序

    **单片机实验目录单片机实验实验一1.略2.设计一个求解16位原码数据的补码的程序,要求:原码数据放在R1R0中,补码数据存放在R7R6中;
    发表于 12-01 06:01

    LPC824M201JHI33单片机出现的bug现象

    bug现象:燃气表在静置运行状态下,不会出现单片机跑死的现象,只要一跑气,程序就会卡死,表具成为死表现象,重新短接单片机复位引脚,单片机才会复位,其他的外部中断都对表具没有影响。(表
    发表于 01-25 06:21

    单片机LED段码数据生成器

    电子发烧友网站提供《单片机LED段码数据生成器.exe》资料免费下载
    发表于 06-16 23:10 18次下载

    LED段码数据生成器

    LED段码数据生成器 单片机C51常用软件 简单方便。
    发表于 05-18 14:53 7次下载

    AD1984B:高定义音频SoundMAX代码数据Sheet

    AD1984B:高定义音频SoundMAX代码数据Sheet
    发表于 04-15 17:09 0次下载
    AD1984B:高定义音频SoundMAX<b class='flag-5'>代码数据</b>Sheet

    AD1887 AC‘97声音最大代码数据Sheet

    AD1887 AC‘97声音最大代码数据Sheet
    发表于 06-18 12:42 5次下载
    AD1887 AC‘97声音最大<b class='flag-5'>代码数据</b>Sheet

    通过篡改特定代码数据修复单片机BUG方法

    分享文章之前,想问大家,你们开发的产品如果有bug了,你们回通过什么什么方式修复bug
    的头像 发表于 03-03 09:15 533次阅读

    单片机解析g代码方法

    的运动。 解析G代码是将其转化为单片机能够理解和执行的指令集。单片机解析G代码方法主要包括以下几个方面:G
    的头像 发表于 12-22 14:15 536次阅读