c++ Clang ASan在Windows上处理try/catch块中的异常时失败(AddressSanitizer:未知地址上的访问冲突)

f0brbegy  于 2023-08-09  发布在  Windows
关注(0)|答案(1)|浏览(288)

安装程序:Windows 10、Clang 16.0.3、ASan(-fsanitize=address)、-O0
任何与处理异常相关的代码,例如try/catch,都会导致关于访问违规的报告(另外,undefined sanitazation也不会对此感到高兴),或者会改变程序的行为。

#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 &exconst std::runtime_error exstd::runtime_error &exstd::runtime_error ex结果不同的结果都是某种不良行为。
const std::runtime_error &exconst std::runtime_error ex直接导致如下错误报告:

==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\6\s\src\vctools\crt\vcruntime\src\eh\amd64\handlers.asm:49
    #2 0x7ff74e46e6bb in __FrameHandler3::CxxCallCatchBlock(struct _EXCEPTION_RECORD *) d:\a01\_work\6\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\6\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
    #6 0x7ff74e435d6b in __scrt_common_main_seh d:\a01\_work\6\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


std::runtime_error &exstd::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


我花了很多时间试图找到错误的来源,但没有找到任何东西。唯一的线索是,编译时不进行地址清理可以修复所有问题。

3pmvbmvn

3pmvbmvn1#

google/sanitizers中有一个对应的issue #749,它是LLVM Sanitizer的所在地。
建议的解决方法是
__attribute__((no_sanitize_address))应用于使用EH的每个函数,如果您的应用程序在正常操作中不使用EH,则可以正常工作。
但这就违背了净化的目的,所以稍微好一点的解决方法是将try/catch Package 在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


Aaaand让我们通过允许lambda隐式地通过引用捕获变量来使它更无缝地使用+引用问题似乎是一个很好的实践

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

#if ASAN_ENABLED && (defined(_WIN64) || defined(_WIN32))
/* SEE: 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))
/* SEE: 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时,{}是不必要的,因为代码已经 Package 在宏内部的{}中,但它更适合clang-format,所以我使用它。

相关问题