7

用LLVM/Clang编译器插件实现除法保护(1)

 2 years ago
source link: https://blog.sbw.so/u/llvm-compiler-plugin-div-protect.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

用LLVM/Clang编译器插件实现除法保护(1)

来源: 石博文博客 | 浏览: 1931 | 评论: 1 发表时间: 2019-03-27

LLVM/Clang 提供了编译器插件机制可供扩展编译器的功能,实际上 LLVM/Clang 本身的很多功能也是以插件方式注册到编译器系统中进行实现的。在一些对安全要求极端苛刻的环境下,或是一些特殊的硬件平台中,总有一些奇怪的需求,而这些定制化的功能如果不是很复杂,就没有必要大动干戈去修改编译器,可以使用编译器插件来完成。这个系列文章设想一种需要进行除法保护的应用场景,需要在编译层面对除法进行强制检查,以杜绝除零异常的发生。

某硬件平台由于安全原因不允许除零异常的发生,由于C++无法捕获这种CPU异常进行处理,所以考虑在编译器层面进行预防。约定:所有除法指令必须进行除数检查,当除数为0时,直接返回被除数本身。

为了更明确目标,并提供测试方法,构造了以下示例代码:

#include <iostream>
int dangerous_divison_function(int a, int b)
{
return a / b;
}
int safe_division_function(int a, int b)
{
return b ? a / b : a;
}
int main()
{
int tests[][2] = {
{ 3, 5 },
{ 5, 3 },
{ 5, -3 },
{ 6, 3 },
{ -6, 3 },
{ 6, 0 },
{ -6, 0 },
{ 0, 3 },
{ -0, 3 },
{ 0, 0 },
{ 0, -0 },
// END
{ -1, -1 }
};
for (int i(0); tests[i][0] != -1 && tests[i][1] != -1; ++i)
{
std::cout << "Test for " << tests[i][0] << " / " << tests[i][1] << ':' << std::endl;
std::cout << "Safe Div Result: " << safe_division_function(tests[i][0], tests[i][1]) << ", ";
std::cout << "Dangerous Div Result: " << dangerous_divison_function(tests[i][0], tests[i][1]) << std::endl;
}
std::cout << "Test Finished." << std::endl;
return 0;
}

在上面的测试代码中,有两个除法函数,其中一个是手动进行了除数检测的,而另一个是有“缺陷”的代码。在实际使用中,如果要完全实现对除数的检查,那就要在代码审查上多下功夫,确保足够仔细的去发现这类问题。但是,如果一个除法运算是混在复杂的逻辑中,或是在其它的头文件中定义的宏等等,此时除法运算就不是很明显,审核起来就很容易遗漏。同时在代码中加过多的if-else测试语句也对逻辑本身的可读性有所损害。

所以,编写这个编译器插件的目的就在于:无论代码中是否对除数进行了检查,都要保证运算结果和我们约定的结果相同,同时不能损害已有的逻辑,保证程序的正确性。

我们先使用正常的编译器编译并运行示例代码,可以看到在遇到6/0这个计算时,出现了例外,程序崩溃了:

clang++ -O3 -o build/test main.cpp       
./build/test                             
Test for 3 / 5:
Safe Div Result: 0, Dangerous Div Result: 0
Test for 5 / 3:
Safe Div Result: 1, Dangerous Div Result: 1
Test for 5 / -3:
Safe Div Result: -1, Dangerous Div Result: -1
Test for 6 / 3:
Safe Div Result: 2, Dangerous Div Result: 2
Test for -6 / 3:
Safe Div Result: -2, Dangerous Div Result: -2
Test for 6 / 0:
[1]    18636 floating point exception (core dumped)  ./build/test

然后我们使用同一个编译器,并加载我们的插件,再编译运行:

clang++ -O3 -Xclang -load -Xclang build/libdiv-check.so -Xclang -add-plugin -Xclang DivisionCheck -o build/test-fixed main.cpp
./build/test
Test for 3 / 5:
Safe Div Result: 0, Dangerous Div Result: 0
Test for 5 / 3:
Safe Div Result: 1, Dangerous Div Result: 1
Test for 5 / -3:
Safe Div Result: -1, Dangerous Div Result: -1
Test for 6 / 3:
Safe Div Result: 2, Dangerous Div Result: 2
Test for -6 / 3:
Safe Div Result: -2, Dangerous Div Result: -2
Test for 6 / 0:
Safe Div Result: 6, Dangerous Div Result: 6
Test for -6 / 0:
Safe Div Result: -6, Dangerous Div Result: -6
Test for 0 / 3:
Safe Div Result: 0, Dangerous Div Result: 0
Test for 0 / 3:
Safe Div Result: 0, Dangerous Div Result: 0
Test for 0 / 0:
Safe Div Result: 0, Dangerous Div Result: 0
Test for 0 / 0:
Safe Div Result: 0, Dangerous Div Result: 0
Test Finished.

这两次的编译我们没有更改任何源代码,也没有更改编译器及基本的编译参数,仅仅是加载了一个编译插件。可以看到,这次编译出的二进制中,两个函数的执行结果是相同的,而且避免了除零错误导致的崩溃问题。

下一篇,将开始从零建立开发环境,一步步实现这个编译器插件。

引用本文请以超链接形式保留本文地址

  • 声明: 评论属于其发表者所有,不代表本站的观点和立场.
  • 1楼 路人甲 回复该留言 时间: 2021-10-29

    您好,我最近在入门LLVM,先跟着您的文章大致了解一下,中间遇到了问题。在编译时指令
    clang++ -O3 -Xclang -load -Xclang build/libdiv-check.so -Xclang -add-plugin -Xclang DivisionCheck -o build/test-fixed main.cpp
    中的 -add-plugin -Xlang DivisionCheck 这个找不到,请问DivisionCheck 是在哪里生成的呢?

已有 1 位网友发表了一针见血的评论,你还等什么?

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK