LLVM 之 Clang 静态分析器篇(6):程序缺陷诊断——内存重复释放

Author: stormQ

Created: Wednesday, 09. June 2021 08:21PM

Last Modified: Thursday, 17. June 2021 08:27PM



摘要

本文基于release/12.x版本的 LLVM 源码,以插件方式实现了用于检测 C 程序中内存重复释放程序缺陷的plugin.unix.Malloc检查器。从而,便于真正地理解 Clang 静态分析器是如何在unix.Malloc检查器中实现该功能的。除此之外,也有助于按照自身需求修改或扩展 Clang 静态分析器。


研究过程


unix.Malloc检查器的源码实现目录如下:

plugin.unix.Malloc检查器的源码实现目录如下:


C 程序中典型的内存重复释放

本节介绍了几个 C 程序中典型的内存重复释放示例,涉及mallocfreerealloc函数。这些示例均来自 LLVM 项目中的测试文件 malloc.c(定义在 clang/test/Analysis/malloc.c 文件中)。

示例 1:

54 void f2() {
55   int *p = malloc(12);
56   free(p);
57   free(p); // expected-warning{{Attempt to free released memory}}
58 }

如果局部变量p所指向的内存分配成功(大多数情况下),那么上述程序会对该内存重复调用free函数。也就是说,在该情况下会发生内存重复释放。

示例 2:

60 void f2_realloc_0() {
61   int *p = malloc(12);
62   realloc(p,0);
63   realloc(p,0); // expected-warning{{Attempt to free released memory}}
64 }

如果realloc函数的第一个参数oldmem的值不为NULL且第二个参数bytes的值为 0,那么调用realloc(oldmem, bytes)相当于调用free(oldmem),并且realloc函数的返回值为NULL

因此,如果局部变量p所指向的内存分配成功(大多数情况下),那么上述程序相当于对该内存重复调用free函数。也就是说,在该情况下会发生内存重复释放。

示例 3:

109 void reallocSizeZero1() {
110   char *p = malloc(12);
111   char *r = realloc(p, 0);
112   if (!r) {
113     free(p); // expected-warning {{Attempt to free released memory}}
114   } else {
115     free(r);
116   }
117 }

如果局部变量p所指向的内存分配成功(大多数情况下),那么在执行语句char *r = realloc(p, 0);后,p所指向的内存会被释放并且局部变量r的值为NULL。因此,if语句中的条件表达式!r的求值结果会为true,从而调用free(p)(再次释放p所指向的内存)。也就是说,在该情况下会发生内存重复释放。

如果realloc函数的第一个参数oldmem的值为NULL(无论第二个参数bytes的值是什么),那么调用realloc(oldmem, bytes)相当于调用malloc(bytes)

因此,如果局部变量p所指向的内存分配失败(少数情况下),那么在执行语句char *r = realloc(p, 0);后,如果局部变量r所指向的内存也分配失败,那么会调用free(p);语句(由于p的值为NULL,所以什么都不做);否则,会调用free(r);语句(正常释放了r所指向的内存)。因此,这两种情况下程序都是正确的。

示例 4:

1474 // Make sure we catch errors when we free in a function which does not allocate memory.
1475 void freeButNoMalloc(int *p, int x){
1476   if (x) {
1477     free(p);
1478     //user forgot a return here.
1479   }
1480   free(p); // expected-warning {{Attempt to free released memory}}
1481 }

如果参数p的值不为NULL且参数x的求值结果为true,那么上述程序会对p所指向的内存重复调用free函数。也就是说,在该情况下会发生内存重复释放。

示例 5:

1655 void testOffsetZeroDoubleFree() {
1656   int *array = malloc(sizeof(int)*2);
1657   int *p = &array[0];
1658   free(p);
1659   free(&array[0]); // expected-warning{{Attempt to free released memory}}
1660 }

如果局部变量array所指向的内存分配成功,那么由于局部变量p&array[0]都指向同一块内存,因此上述程序会对该内存重复调用free函数。也就是说,在该情况下会发生内存重复释放。

返回上一级


原检查器订阅的程序点

unix.Malloc检查器中,检查器类MallocChecker用于实现内存相关错误(比如:内存泄露内存重复释放)的诊断功能,其源码实现如下(定义在 clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp 文件中):

286 namespace {
287 
288 class MallocChecker
289     :
 public Checker<check::DeadSymbols, check::PointerEscape,
290                      check::ConstPointerEscape, check::PreStmt<ReturnStmt>,
291                      check::EndFunction, check::PreCall, check::PostCall,
292                      check::NewAllocator, check::PostStmt<BlockExpr>,
293                      check::PostObjCMessage, check::Location, eval::Assume> {
// 省略 ...
728 };
// 省略 ...
859 } // end anonymous namespace

从上面的代码可以看出,该检查器类共订阅了 12 个程序点。如果只实现检测 C 程序中内存重复释放的程序缺陷,那么至少需要订阅clang::ento::check::PostCallclang::ento::eval::Assume程序点。

订阅clang::ento::check::PostCall程序点是为了获取mallocfreerealloc函数的参数或返回值,从而用于跟踪内存相关的自定义程序状态。

订阅clang::ento::eval::Assume程序点是为了当程序状态中表示已分配内存的符号被约束为空指针时做一些处理。从而避免在某些情况下误报内存重复释放的程序缺陷。这些情况比如:realloc函数重新分配内存失败。

返回上一级


自己动手实现内存重复释放的检测

step 1: 定义检查器类

1) 需要订阅哪些程序点

要实现检测内存重复释放的功能,我们至少需要订阅clang::ento::check::PostCallclang::ento::eval::Assume程序点。除此之外,最好也订阅clang::ento::check::DeadSymbols程序点(但不是必须的),从而清除不会再用到的自定义程序状态。

2) 定义检查器类

10 namespace {
11 
// 省略 ...
16 
17 class MallocChecker : public Checker<check::PostCall, eval::Assume> {
// 省略 ...
36 
37 public:
// 省略 ...
39 
40   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
41   ProgramStateRef evalAssume(ProgramStateRef State, const SVal &Cond,
42                              bool Assumption)
 const
;
43 };
44 
45 } // anonymous namespace

注:

如果确定仅在局部范围内使用该类,那么可以将检查器类的定义置于匿名空间中。从而,避免链接器将这些符号导出至外部。

订阅clang::ento::check::PostCall程序点后,检查器类必须实现一个函数原型为void checkPostCall(const CallEvent &Call, CheckerContext &C) const的成员函数。否则,会发生编译错误。

同样地,订阅clang::ento::eval::Assume程序点后,检查器类必须实现一个函数原型为ProgramStateRef evalAssume(ProgramStateRef State, const SVal &Cond, bool Assumption) const的成员函数。否则,也会发生编译错误。

step 2: 自定义程序状态

1) 定义状态枚举

10 namespace {
11 
12 enum Kind {
13   Allocated,
14   Released,
15 };
// 省略 ...
44 
45 } // anonymous namespace

其中,枚举值Allocated表示内存处于已分配的状态,枚举值Released表示内存处于已释放的状态。

2) 调用注册宏

47 REGISTER_MAP_WITH_PROGRAMSTATE(RegionState, SymbolRef, int)

参数RegionState,表示自定义程序状态的类型(实质上是一个 C++ 类名,在该宏定义的内部进行定义)。

参数SymbolRef,为llvm::ImmutableMap<Key, Value>Key的数据类型,表示所分配内存对应的符号表达式。

参数int,为llvm::ImmutableMap<Key, Value>Value的数据类型,表示所分配内存的状态(是已分配还是已释放)。

上述宏用于跟踪通过mallocrealloc函数分配的内存的状态,用于判断是否发生了内存重复释放

3) 调用注册宏

48 REGISTER_MAP_WITH_PROGRAMSTATE(ReallocPairs, SymbolRef, SymbolRef)

第一个参数ReallocPairs,表示自定义程序状态的类型(实质上是一个 C++ 类名,在该宏定义的内部进行定义)。

第二个参数SymbolRef,为llvm::ImmutableMap<Key, Value>Key的数据类型,表示realloc函数所分配的新内存对应的符号表达式。

第三个参数SymbolRef,为llvm::ImmutableMap<Key, Value>Value的数据类型,表示realloc函数所释放的旧内存对应的符号表达式。

上述宏用于跟踪realloc函数所分配的新内存和所释放的旧内存之间的关联关系,用于在适当的时候处理realloc函数重新分配内存失败的情况,从而避免误报内存重复释放

step 3: 实现检查器类

1) 实现checkPostCall()成员函数

50 void MallocChecker::checkPostCall(const CallEvent &Call,
51                                   CheckerContext &C) const {
52   if (!Call.isGlobalCFunction()) {
53     return;
54   }
55 
56   if (Call.isCalled(MallocFn)) {
57     checkMalloc(Call, C);
58     return;
59   }
60 
61   if (Call.isCalled(FreeFn)) {
62     checkFree(Call, C);
63     return;
64   }
65 
66   if (Call.isCalled(ReallocFn)) {
67     checkRealloc(Call, C);
68     return;
69   }
70 }

上述代码的逻辑为:

a) 定义并初始化数据成员MallocFnFreeFnReallocFn

10 namespace {
11 
// 省略 ...
16 
17 class MallocChecker : public Checker<check::PostCall, eval::Assume> {
// 省略 ...
20   CallDescription MallocFn;
21   CallDescription FreeFn;
22   CallDescription ReallocFn;
// 省略 ...
37 public:
38   MallocChecker() : MallocFn("malloc"), FreeFn("free"), ReallocFn("realloc") {}
// 省略 ...
43 };
44 
45 } // anonymous namespace

2) 实现malloc函数被调用的处理逻辑

malloc函数被调用的处理逻辑封装在成员函数checkMalloc()中,其源码实现如下:

72 void MallocChecker::checkMalloc(const CallEvent &Call,
73                                 CheckerContext &C) const {
74   ProgramStateRef State = C.getState();
75   State = MallocMemAux(Call, C, State);
76   C.addTransition(State);
77 }

上述代码的逻辑为:获取当前程序状态并调用辅助函数MallocMemAux()进行实际的处理。如果程序状态发生了变化,那么扩展程序状态图,从而让后继节点获知这一信息,并且可以继续探索该执行路径。

需要注意的是, 函数MallocMemAux()的第三个参数ProgramStateRef State之所以存在,是因为 C 语言中除了free函数之外realloc函数也会释放内存,但后者基于的程序状态可以不是当前程序状态。所以,通过添加该参数从而可以在realloc函数中复用该处理逻辑。

a) 实现MallocMemAux()成员函数

79 ProgramStateRef
80 MallocChecker::MallocMemAux(const CallEvent &Call, CheckerContext &C,
81                             ProgramStateRef State) const {
82   if (!State) {
83     return nullptr;
84   }
85 
86   SymbolRef Mem = Call.getReturnValue().getAsSymbol();
87   if (!Mem) {
88     return nullptr;
89   }
90 
91   return State->set<RegionState>(Mem, Allocated);
92 }

上述代码的逻辑为:

3) 实现free函数被调用的处理逻辑

free函数被调用的处理逻辑封装在成员函数checkFree()中,其源码实现如下:

94 void MallocChecker::checkFree(const CallEvent &Call,
95                               CheckerContext &C) const {
96   ProgramStateRef State = C.getState();
97   State = FreeMemAux(Call, C, State);
98   C.addTransition(State);
99 }

上述代码的逻辑为:获取当前程序状态并调用辅助函数FreeMemAux()进行实际的处理。如果程序状态发生了变化,那么扩展程序状态图,从而让后继节点获知这一信息,并且可以继续探索该执行路径。

a) 实现FreeMemAux()成员函数

101 ProgramStateRef
102 MallocChecker::FreeMemAux(const CallEvent &Call, CheckerContext &C,
103                           ProgramStateRef State) const {
104   if (!State) {
105     return nullptr;
106   }
107 
108   SymbolRef Mem = Call.getArgSVal(0).getAsSymbol();
109   if (!Mem) {
110     return nullptr;
111   }
112 
113   const auto K = State->get<RegionState>(Mem);
114   if (!K || *K == Allocated) {
115     return State->set<RegionState>(Mem, Released);
116   }
117   if (*K == Released) {
118     reportDoubleFree(C);
119   }
120 
121   return nullptr;
122 }

上述代码的逻辑为:

需要注意的是:

4) 实现realloc函数被调用的处理逻辑

realloc函数被调用的处理逻辑封装在成员函数checkRealloc()中,其源码实现如下:

124 void MallocChecker::checkRealloc(const CallEvent &Call,
125                                  CheckerContext &C) const {
126   ProgramStateRef State = C.getState();
127   State = ReallocMemAux(Call, C, State);
128   C.addTransition(State);
129 }

上述代码的逻辑为:获取当前程序状态并调用辅助函数ReallocMemAux()进行实际的处理。如果程序状态发生了变化,那么扩展程序状态图,从而让后继节点获知这一信息,并且可以继续探索该执行路径。

a) 实现ReallocMemAux()成员函数

131 ProgramStateRef
132 MallocChecker::ReallocMemAux(const CallEvent &Call, CheckerContext &C,
133                              ProgramStateRef State) const {
134   if (!State) {
135     return nullptr;
136   }
137 
138   Optional<DefinedSVal> Arg0 = Call.getArgSVal(0).getAs<DefinedSVal>();
139   Optional<DefinedSVal> Arg1 = Call.getArgSVal(1).getAs<DefinedSVal>();
140   if (!Arg0 || !Arg1) {
141     return nullptr;
142   }
143 
144   ConstraintManager &CM = C.getConstraintManager();
145   ProgramStateRef StatePtrNotNull, StatePtrIsNull;
146   std::tie(StatePtrNotNull, StatePtrIsNull) = CM.assumeDual(State, *Arg0);
147   ProgramStateRef StateSizeNotZero, StateSizeIsZero;
148   std::tie(StateSizeNotZero, StateSizeIsZero) = CM.assumeDual(State, *Arg1);
149   const bool PtrIsNull = StatePtrIsNull && !StatePtrNotNull;
150   const bool SizeIsZero = StateSizeIsZero && !StateSizeNotZero;
151 
152   if (PtrIsNull) {
153     ProgramStateRef StateMalloc = MallocMemAux(Call, C, StatePtrIsNull);
154     return StateMalloc;
155   }
156 
157   if (!PtrIsNull && SizeIsZero) {
158     ProgramStateRef StateFree = FreeMemAux(Call, C, StateSizeIsZero);
159     return StateFree;
160   }
161 
162   if (ProgramStateRef StateFree = FreeMemAux(Call, C, State)) {
163     ProgramStateRef StateRealloc = MallocMemAux(Call, C, StateFree);
164     if (!StateRealloc) {
165       return nullptr;
166     }
167 
168     SymbolRef FromPtr = Call.getArgSVal(0).getAsSymbol();
169     SymbolRef ToPtr = Call.getReturnValue().getAsSymbol();
170     assert(FromPtr && ToPtr &&
171            "By this point, FreeMemAux and MallocMemAux should have checked "
172            "whether the argument or the return value is symbolic!");
173     return StateRealloc->set<ReallocPairs>(ToPtr, FromPtr);
174   }
175 
176   return nullptr;
177 }

上述代码的逻辑为:

需要注意的是,plugin.unix.Malloc检查器的实现中,如果realloc函数第一个参数的值为NULL且第二个参数的值为 0,那么相当于调用malloc(bytes)。而在unix.Malloc检查器的实现中,如果遇到该情况就直接返回传入的程序状态,未进行其他处理。

5) 报告程序缺陷

a) 定义相关数据成员和成员函数

10 namespace {
11 
// 省略 ...
16 
17 class MallocChecker : public Checker<check::PostCall, eval::Assume> {
18   mutable std::unique_ptr<BugType> DoubleFreeBT;
// 省略 ...
35   void reportDoubleFree(CheckerContext &C) const;
// 省略 ...
43 };
44 
45 } // anonymous namespace

我们需要定义一个数据类型为std::unique_ptr<BugType>的数据成员(这里是DoubleFreeBT),用于表示一个新的程序缺陷。为了在对象析构时能够自动释放内存,因此使用了智能指针。

另外,我们将报告该程序缺陷的处理逻辑封装到成员函数reportDoubleFree()中。

b) 实现程序缺陷报告逻辑

179 void MallocChecker::reportDoubleFree(CheckerContext &C) const {
180   if (!DoubleFreeBT) {
181     DoubleFreeBT.reset(new BugType(this"Double free""Memory Error"));
182   }
183 
184   if (ExplodedNode *N = C.generateErrorNode()) {
185     auto R = std::make_unique<PathSensitiveBugReport>(
186       *DoubleFreeBT, DoubleFreeBT->getDescription(), N);
187     C.emitReport(std::move(R));
188   }
189 }

上述代码的逻辑为:报告内存重复释放的程序缺陷,并终止该执行路径的探索。

6) 实现evalAssume()成员函数

191 ProgramStateRef MallocChecker::evalAssume(ProgramStateRef State,
192                                           const SVal &Cond,
193                                           bool Assumption) const {
194   auto isNull = [&](ProgramStateRef State, SymbolRef Sym) {
195     // Return true if a symbol is NULL.
196     return State->getConstraintManager().isNull(State, Sym).isConstrainedTrue();
197   };
198 
199   for (const auto &TrackedRegion : State->get<RegionState>()) {
200     SymbolRef R = TrackedRegion.first;
201     if (isNull(State, R)) {
202       State = State->remove<RegionState>(R);
203     }
204   }
205 
206   for (const auto &ReallocPair : State->get<ReallocPairs>()) {
207     SymbolRef ToPtr = ReallocPair.first;
208     if (isNull(State, ToPtr)) {
209       State = State->remove<ReallocPairs>(ToPtr);
210       SymbolRef FromPtr = ReallocPair.second;
211       State = State->set<RegionState>(FromPtr, Allocated);
212     }
213   }
214 
215   return State;
216 }

上述代码的逻辑为:

step 4: 构建测试

1) 构建

注:具体过程可参考笔者的另一篇文章:《LLVM 之 Clang 静态分析器篇(2):如何扩展 Clang 静态分析器》。

2) 诊断结果对比

对于同一个测试文件malloc.c,分别运行自己实现的检查器plugin.unix.malloc和 Clang 静态分析器实现的检查器unix.malloc

1) 运行检查器plugin.unix.malloc

执行命令:

$ clang -cc1 -w -load ~/git-projects/llvm-project/build_ninja/lib/MallocCheckerPlugin.so -analyze -analyzer-checker=plugin.unix.Malloc malloc.c -I ~/git-projects/llvm-project/clang/test/Analysis/

输出结果如下:

malloc.c:57:3: warning: Double free [plugin.unix.Malloc]
  free(p); // expected-warning{{Attempt to free released memory}}
  ^~~~~~~
malloc.c:63:3: warning: Double free [plugin.unix.Malloc]
  realloc(p,0); // expected-warning{{Attempt to free released memory}}
  ^~~~~~~~~~~~
malloc.c:113:5: warning: Double free [plugin.unix.Malloc]
    free(p); // expected-warning {{Attempt to free released memory}}
    ^~~~~~~
malloc.c:123:5: warning: Double free [plugin.unix.Malloc]
    free(p); // expected-warning {{Attempt to free released memory}}
    ^~~~~~~
malloc.c:127:3: warning: Double free [plugin.unix.Malloc]
  free(p); // expected-warning {{Attempt to free released memory}}
  ^~~~~~~
malloc.c:749:3: warning: Double free [plugin.unix.Malloc]
  free(p); // expected-warning{{Attempt to free released memory}}
  ^~~~~~~
malloc.c:1480:3: warning: Double free [plugin.unix.Malloc]
  free(p); // expected-warning {{Attempt to free released memory}}
  ^~~~~~~
malloc.c:1490:13: warning: Double free [plugin.unix.Malloc]
  char *m = realloc(a->p, size); // expected-warning {{Attempt to free released memory}}
            ^~~~~~~~~~~~~~~~~~~
malloc.c:1659:3: warning: Double free [plugin.unix.Malloc]
  free(&array[0]); // expected-warning{{Attempt to free released memory}}
  ^~~~~~~~~~~~~~~
9 warnings generated.

从上面的输出结果可以看出,检查器plugin.unix.malloc检测到malloc.c文件中存在 9 个内存重复释放的程序缺陷。

2) 运行检查器unix.malloc

执行命令:

$ clang -cc1 -w -analyze -analyzer-checker=unix.Malloc malloc.c -I ~/git-projects/llvm-project/clang/test/Analysis/

输出结果如下(省略无关的程序缺陷):

// 省略 ...
malloc.c:57:3: warning: Attempt to free released memory [unix.Malloc]
  free(p); // expected-warning{{Attempt to free released memory}}
  ^~~~~~~
malloc.c:63:3: warning: Attempt to free released memory [unix.Malloc]
  realloc(p,0); // expected-warning{{Attempt to free released memory}}
  ^~~~~~~~~~~~
// 省略 ...
malloc.c:113:5: warning: Attempt to free released memory [unix.Malloc]
    free(p); // expected-warning {{Attempt to free released memory}}
    ^~~~~~~
malloc.c:123:5: warning: Attempt to free released memory [unix.Malloc]
    free(p); // expected-warning {{Attempt to free released memory}}
    ^~~~~~~
malloc.c:127:3: warning: Attempt to free released memory [unix.Malloc]
  free(p); // expected-warning {{Attempt to free released memory}}
  ^~~~~~~
// 省略 ...
malloc.c:222:9: warning: Attempt to free released memory [unix.Malloc]
        free(buf); // expected-warning {{Attempt to free released memory}}
        ^~~~~~~~~
// 省略 ...
malloc.c:749:3: warning: Attempt to free released memory [unix.Malloc]
  free(p); // expected-warning{{Attempt to free released memory}}
  ^~~~~~~
// 省略 ...
malloc.c:1480:3: warning: Attempt to free released memory [unix.Malloc]
  free(p); // expected-warning {{Attempt to free released memory}}
  ^~~~~~~
malloc.c:1490:13: warning: Attempt to free released memory [unix.Malloc]
  char *m = realloc(a->p, size); // expected-warning {{Attempt to free released memory}}
            ^~~~~~~~~~~~~~~~~~~
// 省略 ...
malloc.c:1659:3: warning: Attempt to free released memory [unix.Malloc]
  free(&array[0]); // expected-warning{{Attempt to free released memory}}
  ^~~~~~~~~~~~~~~
// 省略 ...
95 warnings generated.

从上面的输出结果可以看出,对于同一个测试文件malloc.c而言,检查器unix.malloc检测到该文件中存在 10 个内存重复释放的程序缺陷。

其中,检查器plugin.unix.malloc未检测到的程序缺陷如下:

217 void reallocfRadar6337483_3() {
218     char * buf = malloc(100);
219     char * tmp;
220     tmp = (char*)reallocf(buf, 0x1000000);
221     if (!tmp) {
222         free(buf); // expected-warning {{Attempt to free released memory}}
223         return;
224     }
225     buf = tmp;
226     free(buf);
227 }

上述程序中的reallocf函数不是 C 语言提供的 API 而是用户自定义的,其行为与realloc的唯一区别在于:前者在重新分配内存失败后仍会释放旧内存。由于检查器plugin.unix.malloc中未对该函数进行建模。因此,不会检测到该程序缺陷。

返回上一级


如何调试

step 1: gdb 调试

1) gdb 调试文件

plugin.malloc.gdb 的内容如下:

file /usr/local/bin/clang
set args -cc1 -w -load ~/git-projects/llvm-project/build_ninja/lib/MallocCheckerPlugin.so -analyze -analyzer-checker=plugin.unix.Malloc -I ~/git-projects/llvm-project/clang/test/Analysis/ malloc.c
set listsize 20
set breakpoint pending on

start
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:57
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:62

b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:67
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:141
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:153
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:158
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:162
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:163
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:165
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:173
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:176

b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:199
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:202
b clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp:211

2) 如何启动

$ gdb -q -x plugin.malloc.gdb

step 2: 辅助调试的 Clang API

Clang API 示例 作用
clang::ento::ProgramState::dump()
  • 命令:p C.getState()->dump()
  • 注:C 的数据类型为 clang::ento::CheckerContext &
  • 打印程序状态
    clang::ento::SymExpr::dump()
  • 命令:p Mem->dump()
  • 注:Mem 的数据类型为 clang::ento::SymbolRef(即 const clang::ento::SymExpr *)
  • 打印符号
    clang::Stmt::dump()
  • 命令:p Call.getOriginExpr()->dump()
  • 注:Call 的数据类型为 const clang::ento::CallEvent &
  • 打印表达式的抽象语法树

    返回上一级


    研究结论

    要实现检测内存重复释放的功能,我们需要跟踪内存的已分配和已释放情况。通过自定义程序状态RegionStateReallocPairs可以做到这一点。前者用于跟踪内存的已分配和已释放情况。从而,在释放内存时可以根据该信息判断是否发生了内存重复释放。后者用于跟踪realloc函数所分配的新内存和所释放的旧内存之间的关联关系。从而,可以在适当的时候处理realloc函数重新分配内存失败的情况以避免误报内存重复释放

    检测内存重复释放的工作原理:当程序调用malloc函数后,将其所分配内存已分配的信息保存到自定义程序状态RegionState中。当程序调用free函数时,如果要释放的内存处于已分配状态或者无法获知确切状态,那么将其更新为已释放状态;如果要释放的内存处于已释放状态,那么表明发生了内存重复释放。当程序调用realloc函数后,根据参数取值的不同可以分为以下三种情况:


    References


    下一篇:LLVM 之 Clang 静态分析器篇(7):程序缺陷诊断——内存泄露

    上一篇:LLVM 之 Clang 静态分析器篇(5):程序缺陷诊断——fopen 和 fclose API 误用

    首页