Clang ASan fails when handling exceptions in try/catch block on Windows (AddressSanitizer: access-violation on unknown address)

huangapple go评论111阅读模式
英文:

Clang ASan fails when handling exceptions in try/catch block on Windows (AddressSanitizer: access-violation on unknown address)

问题

设置:Windows 10,Clang 16.0.3,ASan(-fsanitize=address),-O0

任何涉及异常处理的代码,例如 try/catch,都会导致访问冲突的报告(此外,undefined 检查也不会满意),或者会改变程序的行为。

#include <stdexcept>
#include <iostream>

int main() {
    try {
        throw std::runtime_error("test");
    } catch (const std::runtime_error &ex) {
        std::cout << ex.what() << std::endl;
    }
    return 0;
}

从这个列表中的任何变化 const std::runtime_error &ex, const std::runtime_error ex, std::runtime_error &ex, std::runtime_error ex 都会导致某种不良行为。

const std::runtime_error &ex, const std::runtime_error ex 直接导致 ASan 错误报告,如下所示:

==4384==ERROR: AddressSanitizer: access-violation on unknown address 0x00000000000e (pc 0x7ff74e3d12ca bp 0x008e4edcfaf0 sp 0x008e4edcd7f0 T0)
==4384==The signal is caused by a READ memory access.
==4384==Hint: address points to the zero page.
    #0 0x7ff74e3d12c9 in main C:\projects\test\asan.cpp:8
    #1 0x7ff74e478ccf in _CallSettingFrame d:\a01\_work\s\src\vctools\crt\vcruntime\src\eh\amd64\handlers.asm:49
    #2 0x7ff74e46e6bb in __FrameHandler3::CxxCallCatchBlock(struct _EXCEPTION_RECORD *) d:\a01\_work\s\src\vctools\crt\vcruntime\src\eh\frame.cpp:1521
    #3 0x7fffdfbb1715  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x1800a1715)
    #4 0x7ff74e3d113b in main C:\projects\test\asan.cpp:6
    #5 0x7ff74e435d6b in invoke_main d:\a01\_work\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
    #6 0x7ff74e435d6b in __scrt_common_main_seh d:\a01\_work\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #7 0x7fffdf757613  (C:\WINDOWS\System32\KERNEL32.DLL+0x180017613)
    #8 0x7fffdfb626b0  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x1800526b0)

AddressSanitizer 无法提供附加信息。
总结:AddressSanitizer: access-violation C:\projects\test\asan.cpp:8 in main
==4384==ABORTING

std::runtime_error &ex, std::runtime_error ex 有改变的行为,并且会包含一些可能的错误报告:

HГ─@]├HН♣▌f
=================================================================
==23388==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0x7ff72ef69186 in thread T0
    #0 0x7ff72eed1e1d in free C:\src\llvm_package_16.0.3\llvm-project\compiler-rt\lib\asan\asan_malloc_win.cpp:82

Address 0x7ff72ef69186 is a wild pointer inside of access range of size 0x000000000001.
SUMMARY: AddressSanitizer: bad-free C:\src\llvm_package_16.0.3\llvm-project\compiler-rt\lib\asan\asan_malloc_win.cpp:82 in free
==23388==ABORTING

我花了很多时间尝试找到错误的来源,但找不到任何线索。唯一的线索是,编译时不使用地址检查会解决所有问题。

英文:

Setup: Windows 10, Clang 16.0.3, ASan (-fsanitize=address), -O0

Any code related to handling exceptions e.g., try/catch, will result in a report about access-violation (additionally undefined sanitazation also would not be happy about it) or will change the behaviour of the program.

#include &lt;stdexcept&gt;
#include &lt;iostream&gt;

int main() {
    try {
        throw std::runtime_error(&quot;test&quot;);
    } catch (const std::runtime_error &amp;ex) {
        std::cout &lt;&lt; ex.what() &lt;&lt; std::endl;
    }
    return 0;
}

Any variation from this list const std::runtime_error &amp;ex, const std::runtime_error ex, std::runtime_error &amp;ex, std::runtime_error ex result is some sort of bad behavior.

const std::runtime_error &amp;ex, const std::runtime_error ex result directly in ASan error report like this:

==4384==ERROR: AddressSanitizer: access-violation on unknown address 0x00000000000e (pc 0x7ff74e3d12ca bp 0x008e4edcfaf0 sp 0x008e4edcd7f0 T0)
==4384==The signal is caused by a READ memory access.
==4384==Hint: address points to the zero page.
    #0 0x7ff74e3d12c9 in main C:\projects\test\asan.cpp:8
    #1 0x7ff74e478ccf in _CallSettingFrame d:\a01\_work\s\src\vctools\crt\vcruntime\src\eh\amd64\handlers.asm:49
    #2 0x7ff74e46e6bb in __FrameHandler3::CxxCallCatchBlock(struct _EXCEPTION_RECORD *) d:\a01\_work\s\src\vctools\crt\vcruntime\src\eh\frame.cpp:1521
    #3 0x7fffdfbb1715  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x1800a1715)
    #4 0x7ff74e3d113b in main C:\projects\test\asan.cpp:6
    #5 0x7ff74e435d6b in invoke_main d:\a01\_work\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
    #6 0x7ff74e435d6b in __scrt_common_main_seh d:\a01\_work\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #7 0x7fffdf757613  (C:\WINDOWS\System32\KERNEL32.DLL+0x180017613)
    #8 0x7fffdfb626b0  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x1800526b0)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: access-violation C:\projects\test\asan.cpp:8 in main
==4384==ABORTING

While std::runtime_error &amp;ex, std::runtime_error ex have altered behaviour and will contain some gibberish with a potential error report:

HГ─@]├HН♣▌f
=================================================================
==23388==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0x7ff72ef69186 in thread T0
    #0 0x7ff72eed1e1d in free C:\src\llvm_package_16.0.3\llvm-project\compiler-rt\lib\asan\asan_malloc_win.cpp:82

Address 0x7ff72ef69186 is a wild pointer inside of access range of size 0x000000000001.
SUMMARY: AddressSanitizer: bad-free C:\src\llvm_package_16.0.3\llvm-project\compiler-rt\lib\asan\asan_malloc_win.cpp:82 in free
==23388==ABORTING

I've spent a lot of time thying to find the source of the error, but could not find anything. The only clue is that compiling without address sanitazation fixes everything.

答案1

得分: 1

google/sanitizers中有一个与之对应的问题#749,这是LLVM Sanitizers的主页。

建议的解决方法是

对于每个使用EH的函数应用__attribute__((no_sanitize_address)),如果您的应用程序在正常操作中不使用EH,事情将正常工作。

但这违背了我个人对清理的理解,所以稍微好一点的解决方法是将try/catch包装在lambda表达式中并立即调用它。好处是我们可以将__attribute__((no_sanitize_address))专门应用于代码的那一部分,从而限制影响:

#include <stdexcept>
#include <iostream>

int main() {
    []() __attribute__((no_sanitize_address)) {
        try {
            throw std::runtime_error("test");
        } catch (const std::runtime_error &ex) {
            std::cout << ex.what() << std::endl;
        }
    }();
    return 0;
}

现在,让我们通过定义一个宏来使其更美观,以实现相同的功能

#define TRY_CATCH_WRAPPER(code) []() __attribute__((no_sanitize_address)){code}();

但也不要忘记,只有在使用ASan编译时才会在Windows上失败

#ifdef __has_feature
#  if __has_feature(address_sanitizer)
#    define ASAN_ENABLED 1
#  endif
#endif


#if ASAN_ENABLED && (defined(_WIN64) || defined(_WIN32))
#  define TRY_CATCH_WRAPPER(code) []() __attribute__((no_sanitize_address)){code}();
#else
#  define TRY_CATCH_WRAPPER(code) code
#endif

还要让我们更轻松地使用,通过允许lambda隐式捕获变量,而且引用问题似乎是一种良好的做法

#ifdef __has_feature
#  if __has_feature(address_sanitizer)
#    define ASAN_ENABLED 1
#  endif
#endif


#if ASAN_ENABLED && (defined(_WIN64) || defined(_WIN32))
/* 查看:https://github.com/google/sanitizers/issues/749 */
#  define TRY_CATCH_WRAPPER(code) [&]() __attribute__((no_sanitize_address)){code}();
#else
#  define TRY_CATCH_WRAPPER(code) code
#endif

最后,我们可以像这样使用它:

#include <stdexcept>
#include <iostream>

#ifdef __has_feature
#  if __has_feature(address_sanitizer)
#    define ASAN_ENABLED 1
#  endif
#endif

#if ASAN_ENABLED && (defined(_WIN64) || defined(_WIN32))
/* 查看:https://github.com/google/sanitizers/issues/749 */
#  define TRY_CATCH_WRAPPER(code) [&]() __attribute__((no_sanitize_address)){code}();
#else
#  define TRY_CATCH_WRAPPER(code) code
#endif

void sub_main() {
    throw std::runtime_error("Hello, Sailor!");
}

int main() {
    int return_code = 0;

    TRY_CATCH_WRAPPER({
        try {
            sub_main();
            return_code = 0;
        } catch (const std::runtime_error &exc) {
            std::cout << exc.what() << std::endl;
            return_code = 1;
        }
    })

    return return_code;
}

并且异常可以顺利处理,万岁!

请注意:在使用TRY_CATCH_WRAPPER时,{}不是必需的,因为代码已经在宏的内部包装在{}中,但使用clang-format会更好,所以我选择使用它。

英文:

There is a correspoding issue #749 in google/sanitizers, which is the home for LLVM Sanitizers.

The suggested workaround is

> to apply __attribute__((no_sanitize_address)) to every function that uses EH, and if your application doesn't use EH in normal operation, things will work.

But this defeats the point of the sanitization IMHO, so the slightly better workaround would be to wrap the try/catch in a lambda expression and call it immideately. The benefit is that we can apply __attribute__((no_sanitize_address)) specifically to that portion of the code thus limit the impact:

#include &lt;stdexcept&gt;
#include &lt;iostream&gt;

int main() {
    []() __attribute__((no_sanitize_address)) {
        try {
            throw std::runtime_error(&quot;test&quot;);
        } catch (const std::runtime_error &amp;ex) {
            std::cout &lt;&lt; ex.what() &lt;&lt; std::endl;
        }
    }();
    return 0;
}

Now, let's make it more aesthetically pleasing by defining a macro that achieves the same functionality

#define TRY_CATCH_WRAPPER(code) []() __attribute__((no_sanitize_address)){code}();

But also let's not forget that this fails only windows when compiled with ASan

#ifdef __has_feature
#  if __has_feature(address_sanitizer)
#    define ASAN_ENABLED 1
#  endif
#endif


#if ASAN_ENABLED &amp;&amp; (defined(_WIN64) || defined(_WIN32))
#  define TRY_CATCH_WRAPPER(code) []() __attribute__((no_sanitize_address)){code}();
#else
#  define TRY_CATCH_WRAPPER(code) code
#endif

Aaaand let's also make it more seamless to use by allowing lambda to capture variables by reference implicitly + it seems like a good practice to reference the issue

#ifdef __has_feature
#  if __has_feature(address_sanitizer)
#    define ASAN_ENABLED 1
#  endif
#endif


#if ASAN_ENABLED &amp;&amp; (defined(_WIN64) || defined(_WIN32))
/* SEE: https://github.com/google/sanitizers/issues/749 */
#  define TRY_CATCH_WRAPPER(code) [&amp;]() __attribute__((no_sanitize_address)){code}();
#else
#  define TRY_CATCH_WRAPPER(code) code
#endif

In the end we can use this like that:

#include &lt;stdexcept&gt;
#include &lt;iostream&gt;

#ifdef __has_feature
#  if __has_feature(address_sanitizer)
#    define ASAN_ENABLED 1
#  endif
#endif

#if ASAN_ENABLED &amp;&amp; (defined(_WIN64) || defined(_WIN32))
/* SEE: https://github.com/google/sanitizers/issues/749 */
#  define TRY_CATCH_WRAPPER(code) [&amp;]() __attribute__((no_sanitize_address)){code}();
#else
#  define TRY_CATCH_WRAPPER(code) code
#endif

void sub_main() {
    throw std::runtime_error(&quot;Hello, Sailor!&quot;);
}

int main() {
    int return_code = 0;

    TRY_CATCH_WRAPPER({
        try {
            sub_main();
            return_code = 0;
        } catch (const std::runtime_error &amp;exc) {
            std::cout &lt;&lt; exc.what() &lt;&lt; std::endl;
            return_code = 1;
        }
    })

    return return_code;
}

And the exception is handled without any problems, hooray!

>! Note: the {} is not necessary when using TRY_CATCH_WRAPPER as the code is already wrapped in {} inside of the macro, but it is nicer with clang-format, so I go with it.

huangapple
  • 本文由 发表于 2023年8月5日 03:59:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76838834.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定