zoukankan      html  css  js  c++  java
  • 对M4芯片的CRC模块改造来计算标准CRC32

    对M4芯片的CRC模块改造来计算标准CRC32

    【摘要】

      最近使用的M4系列芯片中,有用于计算CRC的硬件CRC模块,这个模块计算出的校验和与我们平时使用的CRC32并不相同。下文用CRC’32指代M4的硬件CRC模块计算出的校验和,以与标准CRC32区分。虽然我们可以使用纯软件的方法来计算CRC32,不过,既然有CRC硬件,我们不妨试试对其进行加工,软件硬件配合来进行计算。

    一、 案例概述

      这个硬件CRC模块有两个问题:其一,是其计算的CRC’32,计算过程与标准的CRC32算法并不一致;其二,是传给CRC模块的数据,必须以32位进行分组。若分组后剩余1~3字节,不足32位的情况,是无法计算的,必须补齐32位才行。但补齐之后,CRC值自然也就改变了。

    二、 问题原因分析 

      标准CRC32的模型如下:
     POLY = 0x04C11DB7, INIT_REM = 0xFFFFFFFF, FIN_XOR = 0xFFFFFFFF, REF_IN = TRUE, REF_REM = TRUE
      那么算法不一致,就一定是上述几项有不同。根据Data Sheet的描述:
        Uses CRC-32 (Ethernet) polynomial: 0x4C11DB7.
        The CRC calculator can be reset to 0xFFFF FFFF with the RESET control bit in the CRC_CR register.
      从这两点可以确定的是POLY和INIT_REM的值,剩下的三个虽然不确定,但是取值有限,可以轻易穷举得出。
      FIN_XOR的值虽然是32位的,有着丰富的可能性,但是常见取值只有两个,那就是0x000000000xFFFFFFFF。REF_IN和REF_REM都是布尔值,取值也只有TRUEFALSE。这样组合之后,只有8种可能。依次测试对照,便可以确定,其CRC’32模型如下:
     POLY = 0x04C11DB7, INIT_REM = 0xFFFFFFFF, FIN_XOR = 0x00000000, REF_IN = FALSE, REF_REM = FALSE
      是最糟糕的情况,也就是模型的后三个参数全部不同。

    三、 解决方案

      对于CRC算法不一致的问题,既然已经找到了模型之间的差异,自然就可以进行改造,也就是,在输入时逆序,在输出时逆序,输出后与0xFFFFFFFF异或(也就是全取反)。
      对于剩余不足32位的情况,这是硬件设计上的问题,索性CRC算法是一个纯粹的线性算法,而且是可逆的(这里提到的可逆,不是指用校验和推算原文),因此想要破解是很容易的。破解的方式无外乎两种:其一,是在数组前补若干字节,以凑齐32位;其二,是在数组后补若干字节,以凑齐32位。
      以5字节举例说明,数组是D0 D1 D2 D3 D4,补齐的方式有如下几种:
        前补三字节:?? ?? ?? D0, D1 D2 D3 D4
        前补七字节:?? ?? ?? ??, ?? ?? ?? D0, D1 D2 D3 D4
        前补11字节或者更多:暂不考虑
        后补三字节:D0 D1 D2 D3, D4 ?? ?? ??
        后补七字节:D0 D1 D2 D3, D4 ?? ?? ??, ?? ?? ?? ??
        后补11字节或者更多:暂不考虑
      对于前补字节的方法,我们只要保证这几个字节对计算CRC没有影响便可。换句话说,就是要这几个字节的CRC’32结果是0xFFFFFFFF,这样一来,寄存器的初值是0xFFFFFFFF,处理这几个字节之后的值也是0xFFFFFFFF,可以达到伪造校验和的目的。
      对于后补字节的方法,我们只要简单的补零,并且在补零之后得到临时的CRC’32结果,然后利用CRC算法的可逆性,使用软件计算来逆向消除补零产生的影响,即可得到真正的CRC’32结果。
      对比前补字节和后补字节的方法,前者需要事先计算出要补充的字节,然后交给硬件计算就行了;后者则统一补充零字节,但需要额外编写CRC的逆运算函数。二者相比,我们选择前者,接下来计算那些“魔术字节”。

    四、 实践情况

      根据CRC’32模型,编写M4特有的硬件CRC计算函数软件版如下:

    <span style="font-size:14px;">unsigned long crc32_m4( const unsigned char *buf, unsigned long len )
    {
        unsigned long rem = 0xFFFFFFFF;
        
        unsigned long byte_p = 0;
        int bit_p = 0;
    
        if (buf != 0)
        {
            for (byte_p = 0; byte_p < len; byte_p++)
            {
                for (bit_p = 7; bit_p >= 0; bit_p--)
                {
                    if (((rem >> 31U) ^ (buf[byte_p] >> bit_p)) & 1U)
                    {
                        rem = (rem << 1U) ^ 0x04C11DB7U;
                    }
                    else
                    {
                        rem = rem << 1U;
                    }
                }
            }
        }
    
        return rem;
    }</span>
      CRC算法有一个很奇妙的性质,以32位的CRC来讲,那就是,当数据的位数小于等于32的时候,不同的数据得到的校验和一定不重复。1~4字节的数据,长度相同且内容不同的数据,得到的CRC32一定不同,当然CRC’32也一定不同。进一步说,对于32位的数据,其等效的32位整型值与32位CRC校验和是一一对应的,稍微花点时间就可以穷举出我们需要的“魔术字节”了。
      这样看来,前补三字节的方法很有可能不存在。实际上,对24位数据进行穷举,计算CRC’32的值,确实不存在校验和是0xFFFFFFFF的情况。

      接下来讨论前补七字节的方法。前四字节的32位进行穷举,后三字节固定为零,即要寻找

        CRC’32(?? ?? ?? ?? 00 00 00) == 0xFFFFFFFF 的情况。

    <span style="font-size:14px;">#include <stdio.h>
    #define LENGTH 7
    
    int main()
    {
        unsigned long i[2] = { 0, 0 };
    
        while (1)
        {
            if (crc32_m4 ((unsigned char *)i, LENGTH) == 0xFFFFFFFF)
            {
                printf ("ans: %08X 
    ", i[0]);
                break;
            }
    
            i[0]++;
        }
    
        return 0;
    }</span>
      算出这七个字节是:6A A5 9E 9D 00 00 00
      将#define LENGTH 分别改成6和5,可以算出:
      前补六个字节为:97 46 CD 0A 00 00
      前补五个字节为:CC 60 21 D0 00

    <span style="font-size:14px;">#include <stdint.h>
    #include "stm32f4xx_crc.h"
    #include "stm32f4xx_rcc.h"
    
    uint32_t crc32_std( const uint8_t *buf, uint32_t len )
    {
        uint32_t ans = 0;
        
        if (buf > 0 && buf + (len - 1) > buf)
        {
            uint32_t i = 0, v = 0;
            
            RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_CRC, ENABLE);
            CRC_ResetDR ();
            
            switch (len % 4)
            {
                while (1)
                {
                    v = ((v >> 0x01) & 0x55555555) | ((v & 0x55555555) << 0x01);
                    v = ((v >> 0x02) & 0x33333333) | ((v & 0x33333333) << 0x02);
                    v = ((v >> 0x04) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 0x04);
                    CRC->DR = v; // <-- CRC_CalcCRC v;
                    
                    if (i >= len)
                    {
                        break;
                    }
            
            case 0: // 所有的字节可以按照4字节一组构成32位的组, 故不必前补额外的字节
                    v = buf[i] << 24 | buf[i + 1] << 16 | buf[i + 2] << 8 | buf[i + 3];
                    i += 4;
                    continue;
            
            case 1: // 因多出1个字节无法构成32位, 故前补7字节以将之补齐
                    CRC->DR = 0x6AA59E9D; // <-- CRC_CalcCRC 0x6AA59E9D;
                    v = buf[i];
                    i += 1;
                    continue;
            
            case 2: // 因多出2个字节无法构成32位, 故前补6字节以将之补齐
                    CRC->DR = 0x9746CD0A; // <-- CRC_CalcCRC 0x9746CD0A;
                    v = buf[i] << 8 | buf[i + 1];
                    i += 2;
                    continue;
            
            case 3: // 因多出3个字节无法构成32位, 故前补5字节以将之补齐
                    CRC->DR = 0xCC6021D0; // <-- CRC_CalcCRC 0xCC6021D0;
                    v = buf[i] << 16 | buf[i + 1] << 8 | buf[i + 2];
                    i += 3;
                    continue;
                }
            
            default:
                break;
            }
            
            v = CRC->DR; // <-- CRC_GetCRC;
            
            v = ((v >> 0x01) & 0x55555555) | ((v & 0x55555555) << 0x01);
            v = ((v >> 0x02) & 0x33333333) | ((v & 0x33333333) << 0x02);
            v = ((v >> 0x04) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 0x04);
            v = ((v >> 0x08) & 0x00FF00FF) | ((v & 0x00FF00FF) << 0x08);
            v = ((v >> 0x10) & 0x0000FFFF) | ((v & 0x0000FFFF) << 0x10);
            
            ans = ~v;
            
            RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_CRC, DISABLE);
        }
        
        return ans;
    }</span>
      代码中的CRC->DR是硬件CRC的寄存器,向其写值便是输入数据,从之读数便是获取校验和。代码中对变量v进行的大量位运算,是为了进行高低位的逆序。虽然可以使用查找表法来进行逆序,但是,要使用查找表的话,倒不如直接用查找表来软件计算CRC了。

    五、效果评价

      在STM32F429上,使用该方法计算CRC32,与使用zlib库中的CRC32函数相比,在小数据量时,该方法比不过zlib库;在大数据量时,该方法快过zlib库一点,不是快很多。
      该方法的性能问题主要集中在,逆序时使用的大量位交换操作,这一点比M4的CRYP和HASH差的地方,就在于后两个模块可以定义输入数据的数据类型,由硬件进行位的逆序。

    六、推广建议

      对M4中需要使用的图片资源,事先计算其CRC32的值,同时用zlib库进行压缩,以减少程序的尺寸,降低烧写程序时消耗的时间。程序跑起来,初始化的时候,用zlib库解压缩,并计算解压后的CRC32值来比对,作为校验。

    参考资料

      《DM00031020-Reference manual STM32F42xxx and STM32F43xxx advanced ARM-based 32-bit MCUs.pdf》

  • 相关阅读:
    sss
    sss
    maven tomcat jstl 异常
    第1章 预备知识
    第2章 存储管理
    第8章 虚拟机字节码执行引擎
    第23章 Spring MVC初体验
    第1章 预备知识
    第5章 Java中的锁
    第13章 线程安全与锁优化
  • 原文地址:https://www.cnblogs.com/sugar13/p/10216614.html
Copyright © 2011-2022 走看看