当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]嵌入式C审查靠人眼翻malloc/free容易漏。实用做法是“Python轻筛做全量初检,clang/cppcheck做深度路径分析,报告进CI”:轻筛抓明显坏味道,深分析走AST/CFG查跨分支空指针和未释放路径,误报用白名单和抑制规则压下去。


嵌入式C审查靠人眼翻malloc/free容易漏。实用做法是“Python轻筛做全量初检,clang/cppcheck做深度路径分析,报告进CI”:轻筛抓明显坏味道,深分析走AST/CFG查跨分支空指针和未释放路径,误报用白名单和抑制规则压下去。

一、轻量正则筛分配与释放

先全量扫工程,标malloc/calloc/realloc/free不成对、分配后未判空、函数入参直接解引用等模式:

import re, os

ALLOC=re.compile(r'(\w+)\s*=\s*(malloc|calloc|realloc)\s*\(')

FREE=re.compile(r'free\s*\(\s*(\w+)\s*\)')

NULLCK=re.compile(r'if\s*\([^)]*?\b\1\b[^)]*?(==\s*NULL|!=NULL)')


def scan_file(path):

   issues=[]

   for i,line in enumerate(open(path,encoding='utf-8',errors='ignore'),1):

       m=ALLOC.search(line)

       if m:

           var=m.group(1)

           nxt=open(path,encoding='utf-8',errors='ignore').read().splitlines()

           # 简化:检查后续20行是否判空

           window=' '.join(nxt[i:i+20])

           if not re.search(r'\bif\b.*\b'+var+r'\b.*NULL',window):

               issues.append((i,var,'malloc未检测NULL'))

           if not FREE.search(window):

               issues.append((i,var,'疑似未free'))

   return issues

正则快但误报多,只当第一道网;自定义my_malloc/my_free、返回给调用者释放、静态池分配都要进白名单。

二、clang AST做路径级检查

装libclang,按函数遍历调用表达式,追踪分配变量是否在每条分支判空、是否有对应free;跨函数可结合call graph只做近似。

from clang.cindex import Index, CursorKind

def analyze(path):

   idx=Index.create(); tu=idx.parse(path,args=['-Iinclude','-std=c11'])

   for n in tu.cursor.walk_preorder():

       if n.kind==CursorKind.CALL_EXPR and n.spelling in ('malloc','calloc'):

           line=n.location.line

           print('alloc',n.spelling,line)

       if n.kind==CursorKind.DECL_REF_EXPR and n.spelling=='free':

           print('free at',n.location.line)

       if n.kind==CursorKind.UNARY_OPERATOR and n.spelling=='*':

           # 解引用前是否缺NULL判断,需要结合父节点控制流细化

           print('deref candidate',n.location.line)

路径敏感空指针、use-after-free、泄漏用Clang Static Analyzer更稳:它基于AST/CFG做符号执行,能分叉“分配成功/失败”两条路径。

# 单文件

clang --analyze -Xanalyzer -analyzer-checker=core.NullDereference,unix.Malloc sample.c

# 工程级出HTML/SARIF

scan-build -o report -enable-checker core.NullDereference,unix.Malloc make

scan-build -sarif -o sarif-out make

core.NullDereference抓“条件里可能NULL、后面解引用”;unix.Malloc抓未释放、double free、realloc覆盖原指针。

三、Python串起CI与报告

用subprocess批量跑clang-analyzer/cppcheck,解析结果生成JSON,按文件/函数/等级汇总:

import subprocess, json, glob

def run_clang_analyze(src):

   r=subprocess.run(['clang','--analyze',

       '-Xanalyzer','-analyzer-checker=core.NullDereference,unix.Malloc',

       '-o','clang_plist',src],capture_output=True,text=True)

   return r.returncode, r.stderr


def collect(cfiles):

   rep=[]

   for f in cfiles:

       rc,err=run_clang_analyze(f)

       for ln in err.splitlines():

           if 'warning:' in ln and ('null' in ln.lower() or 'leak' in ln.lower()):

               rep.append({'file':f,'msg':ln.strip(),'sev':'high'})

   json.dump(rep,open('review.json','w'),ensure_ascii=False,indent=2)

   return rep


# cfiles=glob.glob('src/**/*.c',recursive=True)

clang-tidy可用pytest-clang-tidy插件统一跑,clang-analyzer-、bugprone-开起来,和Python单测同流水线出结果。

四、嵌入式降误报规则

自定义分配器建别名表:my_malloc/my_calloc按malloc对待,mem_pool_alloc若返回静态块则不要求free;

RTOS/中断场景:ISR、tick、DMA回调里禁止动态分配,规则改为“发现malloc即告警”而非查泄漏;

多出口函数用goto cleanup统一释放,扫描器识别cleanup标签和资源栈,避免early return漏free;

第三方库、BSP、startup用.cppcheck-suppressions或.clang-tidy头过滤排除,只审应用层;

realloc必须新指针接收再回赋,直接p=realloc(p,n)在NULL时会丢原块,单独规则拦截;

空指针不仅看malloc,还看外部接口返回值、fopen、socket、ioctl、设备句柄;解引用前无判空且无可证明非空来源才报。

按“正则初筛→clang AST/analyzer深查→白名单抑制→JSON/HTML报告→CI门禁”落地,嵌入式C的空指针崩溃和malloc泄漏可在合入前批量拦截,比纯人工review更稳,也便于按版本比对缺陷趋势。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭