如何停止为微控制器编写固件并开始工作


您好,我叫Eugene,我厌倦了为微控制器编写固件。 这是怎么发生的以及如何处理,让我们弄清楚。


在使用大型编程C ++,Java,Python等进行编程之后,您就不会想回到小型且笨拙的微控制器了。 到他们微薄的工具和库。 但是有时无事可做,实时和自治的任务没有选择余地。 但是,有些类型的任务在这个领域中难以解决。


例如,很难想象测试设备是嵌入式编程中无聊的课程。 通常,还有方便的工具。 您编写...闪烁...闪烁... LED(有时会登录UART)。 所有笔,没有专门的测试工具。


令人沮丧的是,我们的小型微控制器没有仪器测试。 一切都仅通过固件和调试器进行测试。


研究使用新设备和外围设备需要大量的精力和时间。 一个错误,并且每次都必须重新编译并重新运行该程序。


对于此类实验,像REPL之类的方法更合适,这样您就可以轻松地进行这些工作,至少是微不足道的:


\


如何讲到这,这一系列文章是专门的。


这次,我遇到了一个项目,该项目需要测试相当复杂的设备,其中包含许多我以前不知道的传感器和其他芯片,它们使用了MK的许多外围设备和许多不同的接口。 尤其有趣的是,我没有开发板的固件源代码,因此所有测试都必须从头开始编写,而无需使用源代码的工作时间。


该项目保证了一个好的演讲者,比赛在两个月左右的时间内很有趣(而且可能还会更多)。


好吧,这里我们不会哭。 必须要么再次陷入C语言和无尽固件的困境,要么拒绝或想出一些办法使本课变得容易。 最后,懒惰和好奇心是进步的动力。


上一次,当我了解OpenOCD时,在文档中遇到了一个有趣的观点,例如


http://openocd.org/doc/html/General-Commands.html 15.4 Memory access commands mdw, mdh, mdb —         mww, mwh, mwb —        

有趣的... 可以读写外围寄存器吗?..事实证明是可行的,此外,这些命令可以通过TCL服务器远程执行,TCL服务器在openOCD启动时启动。


这是stm32f103C8T6的LED闪烁示例


 // Step 1: Enable the clock to PORT B RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // Step 2: Change PB0's mode to 0x3 (output) and cfg to 0x0 (push-pull) GPIOC->CRH = GPIO_CRH_MODE13_0 | GPIO_CRH_MODE13_1; // Step 3: Set PB0 high GPIOC->BSRR = GPIO_BSRR_BS13; // Step 4: Reset PB0 low GPIOC->BSRR = GPIO_BSRR_BR13; 

和类似的openOCD命令序列


 mww 0x40021018 0x10 mww 0x40011004 0x300000 mww 0x40011010 0x2000 mww 0x40011010 0x20000000 

而现在,如果您考虑到永恒并考虑MK的固件,那么这些程序的主要目的是写入芯片寄存器。 仅能执行某些操作并且只能与处理器内核一起使用的固件没有实际用途!


注意事项

尽管您当然可以考虑使用地穴(=


许多人会记住有关使用中断的更多信息。 但是它们并非总是必需的,而就我而言,您可以不用它们。


因此,生活越来越好。 在openOCD源代码中,您甚至可以找到使用此接口的有趣示例


python上的空白很好。


很有可能从头文件转换寄存器地址,并开始使用犹太脚本语言编写。 您已经可以准备香槟了,但在我看来这还不够,因为我想使用标准外设库或新的HAL来处理外设,而不是烦恼寄存器。


将库移植到python……我们会做一些噩梦。 因此,您需要在C或... C ++中使用这些库。 在专业人士中,您可以覆盖几乎所有运算符...为其类。


头文件中的基地址用其类的对象替换。


例如,在stm32f10x.h文件中


 #define PERIPH_BB_BASE ((uint32_t)0x42000000) /*!< Peripheral base address in the bit-band region */ 

替换为


 class InterceptAddr; InterceptAddr addr; #define PERIPH_BB_BASE (addr) /*!< Peripheral base address in the bit-band region */ 

但是带有指针的游戏在萌芽状态中被砍断了...


这是一个示例stm32f10x_i2c.c文件:


 FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG) { __IO uint32_t i2creg = 0, i2cxbase = 0; …. /* Get the I2Cx peripheral base address */ i2cxbase = (uint32_t)I2Cx; …. 

因此,有必要截取不同地址的地址。 如何做到这一点可能值得看一下Valgrind,而不是因为他拥有memchecker。 好吧,他确实应该知道如何拦截地址。


展望未来,我会说最好不要去那里...我几乎设法拦截了对地址的呼叫。 对于除此以外的几乎所有情况


 Int * p = ... *p = 0x123; 

可以截取该地址,但是不再可能截取所记录的数据。 仅内部寄存器的名称位于此值,但不能通过memcheck到达。


实际上,Valgrind令我惊讶,在古老的怪物libVEX里面使用了它,我没有在互联网上找到任何信息。 最好在头文件中找到一些文档。


然后还有其他DBI工具。


Frida,Dynamic RIO等等,最后有了Pintool。


PinTool有一些非常好的文档和示例。 尽管我还不够用,但是我不得不做一些实验。 事实证明该工具非常强大,它只会破坏封闭的代码,并且只会限制intel平台的使用(尽管将来可以绕开它)


因此,我们需要拦截特定地址的写入和读取。 让我们看看负责此https://godbolt.org/z/nJS9ci的说明


对于x64,这将是两个操作的MOV。


对于x86,它将是用于写入的MOV和用于读取的MOVZ。


注意:最好不要启用优化,否则可能会出现其他说明。


扰流板方向
 INS_AddInstrumentFunction(EmulateLoad, 0); INS_AddInstrumentFunction(EmulateStore, 0); ..... static VOID EmulateLoad(INS ins, VOID *v) { // Find the instructions that move a value from memory to a register if ((INS_Opcode(ins) == XED_ICLASS_MOV || INS_Opcode(ins) == XED_ICLASS_MOVZX) && INS_IsMemoryRead(ins) && INS_OperandIsReg(ins, 0) && INS_OperandIsMemory(ins, 1)) { INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(loadAddr2Reg), IARG_MEMORYREAD_EA, IARG_MEMORYREAD_SIZE, IARG_RETURN_REGS, INS_OperandReg(ins, 0), IARG_END); // Delete the instruction INS_Delete(ins); } } static VOID EmulateStore(INS ins, VOID *v) { if (INS_Opcode(ins) == XED_ICLASS_MOV && INS_IsMemoryWrite(ins) && INS_OperandIsMemory(ins, 0)) { if (INS_hasKnownMemorySize(ins)) { if (INS_OperandIsReg(ins, 1)) { INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(multiMemAccessStore), IARG_MULTI_MEMORYACCESS_EA, IARG_REG_VALUE, INS_OperandReg(ins, 1), IARG_END); } else if (INS_OperandIsImmediate(ins, 1)) { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)multiMemAccessStore, IARG_MULTI_MEMORYACCESS_EA, IARG_UINT64, INS_OperandImmediate(ins, 1), IARG_END); } } else { if (INS_OperandIsReg(ins, 1)) { INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(storeReg2Addr), IARG_MEMORYWRITE_EA, IARG_REG_VALUE, INS_OperandReg(ins, 1), IARG_MEMORYWRITE_SIZE, IARG_END); } else if (INS_OperandIsImmediate(ins, 1)) { INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(storeReg2Addr), IARG_MEMORYWRITE_EA, IARG_UINT64, INS_OperandImmediate(ins, 1), IARG_UINT32, IARG_MEMORYWRITE_SIZE, IARG_END); } } } } 

在读取地址的情况下,我们调用loadAddr2Reg函数并删除原始指令。 基于此,loadAddr2Reg应该向我们返回必要的值。


有了一条记录,它变得越来越困难...参数可以具有不同的类型,并且可以以不同的方式进行传递,因此您必须在命令之前调用不同的函数。 在32位平台上,将调用multiMemAccessStore,在64位上,将调用storeReg2Addr。 在这里,我们不会从组装线中删除指令。 删除它没有问题,但是在某些情况下,无法模仿它的动作。 该程序由于某种原因有时会崩溃到sigfault。 这对我们而言并不重要,让我们将其写成自己,主要的是,存在拦截参数的可能性。


接下来,我们需要查看需要拦截的地址,并查看stm32f103C8T6芯片的内存映射:


图片
我们对使用SRAM和PERIPH_BASE的地址感兴趣,即从0x20000000到0x20000000 + 128 * 1024和从0x40000000到0x40030000。 好吧,或者说不完全是,当我们回忆起录音指令时,我们无法删除。 因此,这些地址上的记录将在sigfault中失效。 此外,这些地址很有可能会从我们的程序中获取数据,而不是该芯片具有其他地址。 因此,我们绝对需要在某个地方修复它们。 让我们说说某种数组。


我们创建所需大小的数组,然后将其指针替换为基地址定义。


在我们的程序中,标题改为


 #define SRAM_BASE ((uint32_t)0x20000000) /*!< SRAM base address in the alias region */ #define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */ 


  #define SRAM_BASE ((AddrType)pAddrSRAM) #define PERIPH_BASE ((AddrType)pAddrPERIPH) 

其中pAddrSRAM和pAddrPERIPH是指向预分配数组的指针。


现在,我们的PinTool客户端需要以某种方式传达我们如何修复必要的地址。
在我看来,最简单的方法是拦截一个以这种格式返回数组结构的函数:


 typedef struct { addr_t start_addr; //      addr_t end_addr; //   addr_t reference_addr; //   } memoryTranslate; 

例如,对于我们的芯片,它将充满


 map->start_addr = (addr_t)pAddrSRAM; map->end_addr = 96*1024; map->reference_addr = (addr_t)0x20000000U; 

截取该函数并从中获取所需的值并不难:


 IMG_AddInstrumentFunction(ImageReplace, 0); .... static memoryTranslate *replaceMemoryMapFun(CONTEXT *context, AFUNPTR orgFuncptr, sizeMemoryTranslate_t *size) { PIN_CallApplicationFunction(context, PIN_ThreadId(), CALLINGSTD_DEFAULT, orgFuncptr, NULL, PIN_PARG(memoryTranslate *), &addrMap, PIN_PARG(sizeMemoryTranslate_t *), size, PIN_PARG_END()); sizeMap = *size; return addrMap; } static VOID ImageReplace(IMG img, VOID *v) { RTN freeRtn = RTN_FindByName(img, NAME_MEMORY_MAP_FUNCTION); if (RTN_Valid(freeRtn)) { PROTO proto_free = PROTO_Allocate(PIN_PARG(memoryTranslate *), CALLINGSTD_DEFAULT, NAME_MEMORY_MAP_FUNCTION, PIN_PARG(sizeMemoryTranslate_t *), PIN_PARG_END()); RTN_ReplaceSignature(freeRtn, AFUNPTR(replaceMemoryMapFun), IARG_PROTOTYPE, proto_free, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_END); } } 

并使我们的拦截函数如下所示:


 memoryTranslate * getMemoryMap(sizeMemoryTranslate_t * size){ ... return memoryMap; } 

做的最平凡的工作仍然是使客户端成为OpenOCD,在PinTool客户端中,我不想实现它,因此我创建了一个单独的应用程序,PinTool客户端通过名为fifo的通信与之通信。


因此,接口和通信的方案如下:
图片
截取地址0x123的示例的简化工作流程:


图片
让我们看看这里发生了什么:


PinTool客户端启动,它初始化我们的拦截器,启动程序
程序启动,它需要寻址某个线程阵列上的寄存器的地址,调用getMemoryMap函数,PinTool会拦截该函数。 例如,其中一个寄存器设置为地址0x123,我们将对其进行跟踪
PinTool客户端保存未关联地址的值
将控制权转移回我们的程序
此外,在某个位置,我们的跟踪地址0x123处有记录。 StoreReg2Addr函数跟踪此情况
并将写入请求发送到OpenOCD客户端
客户端返回已解析的答案。 如果一切正常,则程序控制返回
此外,在程序中的某个位置,读取发生在跟踪的地址0x123处。
loadAddr2Reg对此进行跟踪,并将OpenOCD请求发送到客户端。
OpenOCD客户端对其进行处理并返回响应
如果一切都很好,但是MK寄存器中的值将返回到程序
该程序继续。
到此为止,完整的源代码和示例将在以下部分中。

Source: https://habr.com/ru/post/zh-CN433504/


All Articles