C语言比C++更简洁、执行效率更高、代码量更小,因此在汽车的小控制部件中被广泛使用。MISRA致力于协助汽车 厂商开发安全可靠的软件的跨国协会,其成员包括了全球一些汽车公司、汽车零部件供应商和高校等研究机构,如:宾利汽车、福特汽车、捷豹汽车、TRW 汽车电 子、利兹大学等。MISRA-C针对国际标准化组织(International Organizati on of Standardization,ISO)中C语言在关键嵌入系统中的不安全性进行了补充说明和要求。
MISRA-C先后推出了MISRA-C:1998、MISRA-C:2004、MISRA-C:2012编程规范。MISRA-C:2012将编程的安全性规范包含16条指令和143规则。指令是一种不可能提供执行合规检查所需的完整描述的指导方针,指令需要额外的辅助信息(设计文档、需求规范)和工具(静态分析)才能完成代码的合规性检查。MISRA-C:2012包含9条需要性和7条建议性的指令。规则是对已经提供完整描述需求的指导方针,可以不需要其他任何信息来检查源代码是否符合规则。MISRA-C:2012包含10条强制性规则、32条推荐性规则和101条必需性规则。
与MISRA-C:2004相比,MSRA-C:2012规则分类增加了未使用代码、资源和重叠存储。删除了语言外延,规则也由原来的两类分成三类:必需规则、强制规则、推荐规则。
MISRA-C主要针对汽车,但不仅限于在汽车软件开发上使用,还可以用在其他一些安全相关的关键嵌入式系统。MISRA-C:2012对程序设计中存在的风险从以下4个方面进行控制:语言本身存在的未定义、程序员的失误、程序员对语言和编译器的误解、运行出错。
ISO标准并没有完全指定语言,而是有意将某些方面置于实现的控制之下。部分原因是希望为完全不同的目标处理器支持许多已存在的实现(Implementations)。这能够增加C语言在编程和编译时的灵活性,但也带了潜在的不安全风险。
在C标准中没有做明确规定的地方会用、未指定(Unspecified)、未定义(Undefined)或实现-定义(Implementation-defined)来表述。
未指定行为(Unspecified behavior):ISO标准提供了两种或多种可能性并且在任何情况下都没有强加进一步要求的行为。编译器往往有几种可选的处理方式,C标准没有明确规定按哪种方式处理,编译器可以自己决定,并且也不必写在编译器的文档中, 同一个编译器的不同版本来编译也可能得到不同的结果,因为编译器没有在文档中明确写它会怎么处理。因此,不同版本的编译器也可以选择不同的处理方式。
未定义行为(Undefined behavior):在使用不可移植或错误程序结构或错误数据时的行为,ISO标准里没有对该行为的的要求。C标准没规定怎么处理,编译器很可能也没规定,甚至也没做出错处理,有很多Undefined的情况编译器是检查不出来的,最终会导致运行时错误,比如数组访问越界。
实现-定义行为(Implementation-defined behavior):每个实现都记录了如何做出选择的未指定行为。 C标准没有明确规定char是有符号的还是无符号的,但是要求编译器必须对此做出明确规定,并写在编译器的文档中。编译器可以定义char型是无符号的,也可以定义char型是有符号的,在该编译器所对应的体系结构上哪种实现效率高就可以采用哪种实现,这是C语言中典型的Implementation Defined行为。char和unsigned char都作为字符用的话是没有区别的,但当整数用时有区别,char 整数范围为-128到127( 0x80-0x7F),unsigned char 整数范围为0到255( 0-0xFF )。C语言Implementation Defined的特性与平台和编译器是密不可分,在vc和x86平台上,gcc编译器定义char型是有符号的,而arm嵌入式系统的 arm-linux-gcc 编译器却把 char 定义为 unsigned char。与平台和编译器的密切关联就会出现代码移植上的问题。而C标准这样做的根本原因之一是:优先考虑效率,其次考虑移植性。如果程序员要写可移植的代码,就必须清楚哪些写法是不可移植的,应该避免使用。另一方面,不考虑移植性的问题时,写不可移植的代码有时候也是必要的,比如Linux内核代码使用了很多只有gcc支 持的语法特性以得到最佳的执行效率,因为这些代码具有固定的编译器编译。
在实际编程中,这些与平台、编译器密切相关的操作,程序员在编写车载软件时,必需考虑代码的移植安全,尽可能的避免使用实施-定义行为的C语言编程,从而规避潜在的风险。MISRA-C通过禁止一些实施-定义、未定义、未指定的的编程行为来防范潜在的安全风险。
例如,MISRA-C:2012的规则11.6:不应在指向 void 的指针和算术类型之间执行强制转换。
void * p;
uint32_t u;
p = ( void * ) 0x1234u; /* 不合规 – 实施定义 */
p = ( void * ) 1024.0f; /* 不合规 – 未定义 */
u = ( uint32_t ) p; /* 不合规- 实施定义*/
毫无疑问,ISO-C无法直接被应用在关键的软件系统开发中,而MISRA-C作为ISO-C标准的一个衍生版本,为了安全性,限制了C语言的部分灵活性。
虽然C语言程序可以以一种结构化和可理解的方式进行布局,但C语言使程序员很容易编写难以理解的模糊代码。运算符的规范使得编译器很难检测到编程错误。 例如,以下两段代码都是完全合法的,因此编译器不可能知道其中一个是否被错误地用来代替另一个:
if ( a == b ) /* 测试a和b是否相等*/
if ( a = b ) /* 将b赋值给a,并测试a是否不为零 *
程序员通常会误解该语言的某些领域。 例如,C 比其他一些语言有更多的运算符,因此有大量不同的运算符优先级,其中一些不直观。例如,规则13.3:包含递增 (++) 或递减 (–) 运算符的完整表达式除了由递增或递减运算符引起的副作用外,不应有其他潜在副作用。
u8a = ++u8b + u8c-; //不合规
上述表达式应写成如下更清楚的表达方式:
++u8b;
u8a = u8b + u8c;
u8c--; //合规
C 提供的类型规则也会让熟悉强类型语言的程序员感到困惑。 例如,操作数可能被“提升”为更广泛的类型,这意味着操作产生的类型不一定与操作数的类型相同。例如:
uint16_t u16a = 40000; /* unsigned short 或 unsigned int ? */
uint16_t u16b = 30000; /* unsigned short 或 unsigned int ? */
uint32_t u32x; /* unsigned int 或unsigned long ? */
u32x = u16a + u16b; /* u32x = 70000 or 4464 ? */
预期的结果可能是 70000,但分配给 u32x 的值将取决于 int 的实现大小。 如果int实现大小是 32 位,则加法将发生在 32 位有符号算术中,并且将获得“正确”的值。如果它只有 16 位,则加法将在 16 位无符号算术中进行,将发生回绕并将产生值 4464 (70000 % 65536)。 无符号算术中的回绕定义明确,但由于其效果取决于执行算术的类型,因此其有意使用应当被记录。
编译器可能会提供一些C语言的扩展,开发人员如果不了解而编译器开发人员提供的关于编译器缺陷列表及任何可用的变通方法,则可能无法确认编译器是否符合ISO C的规范,也不知道编译程序所需的资源(时间,内存等),从而忽略了编译器暴露的缺陷。这种不安全的行为主要是由程序员对编译器的不了解造成的对编译、执行的误解。
C 程序可以编译成小而高效的机器代码,但代价是运行时检查的程度非常有限。 C 程序通常不为常见问题提供运行时检查,例如算术异常(例如被零除)、溢出、指针的有效性或数组绑定错误。 C 哲学是程序员负责明确地进行此类检查。但是往往程序员的检查环节缺失时,这些运行时的问题将会给程序造成极大的安全隐患。MISRA-C为了尽量避免这种情况发生,提供了具有广泛指导意见的规范,例如,指令4.1:运行错误应当最小化。
算术错误示例:
float32_t f1 = 1E38f;
float32_t f2 = 10.0f;
float32_t f3 = 0.1f;
float32_t f4 = ( f1 * f2 ) * f3; /* (f1 * f2) 将要溢出 */
float32_t f5 = f1 * ( f2 * f3 ); /* 没有溢出,因为(f2 * f3) 是近似为 1 */
if ( ( f3 >= 0.0f ) && ( f3 <= 1.0f ) )
{
f4 = f3 * 100.0f; /* 没有溢出,因为 f3 已知的范围是在0到1 ,所以乘法的 * 结果将适合 float32_t 类型 */
}
除检查算术错误外,指针算术、数组边界错误、函数参数、指针复引用(或解引用)、动态内存都是程序员要进行的动态检查的领域。
MISRA-C:2012已经能够覆盖标准C编程规范的99%的规则。MISRA-C规范中的指令和规则已经能够很好的帮助程序员和研究人员开发出符合规范的车载软件。
除了上述列举的示例,MISRA-C的规范需要进一步的深入解读,才能在车载软件产品开发过程中规避软件安全风险,本文只是做一个简单的概括性的解读。随着智能车辆的发展,无人驾驶系统中的车辆感知、控制、路径规划完全依靠程序完成,软件系统的安全性就变得尤为重要。作为算法开发工程师、无人驾驶系统工程师、产品经理,完全掌握MISRA-C,对于开发出更安全的智能驾驶车辆尤为重要。