【问题标题】:Stack Smashing in GCC vs Clang (Possibly due to canaries)GCC 与 Clang 中的堆栈粉碎(可能是由于金丝雀)
【发布时间】:2018-03-06 09:58:35
【问题描述】:

我试图了解 GCC 中 "stack smashing" 错误的可能来源,但不是 Clang。

具体来说,当我只用调试符号编译一段代码时

set(CMAKE_CXX_FLAGS_DEBUG "-g")

并使用 GCC C++ 编译器 (GNU 5.4.0),应用程序崩溃

*** stack smashing detected ***: ./testprogram terminated
Aborted (core dumped)

但是,当我使用 Clang 3.8.0 时,程序完成时没有错误。

我的第一个想法是,也许 GCC 的金丝雀正在捕获 Clang 没有的缓冲区溢出。所以我添加了额外的调试标志

set(CMAKE_CXX_FLAGS_DEBUG "-g -fstack-protector-all")

但是 Clang 仍然编译了一个运行没有错误的程序。对我来说,这表明问题可能不是缓冲区溢出(正如您通常看到的堆栈粉碎错误),而是分配问题。

无论如何,当我添加 ASAN 标志时:

set(CMAKE_CXX_FLAGS_DEBUG "-g -fsanitize=address")

两个编译器都会生成一个程序,该程序会因相同的错误而崩溃。具体来说,

GCC 5.4.0:

==1143==ERROR: AddressSanitizer failed to allocate 0xdfff0001000 (15392894357504) bytes at address 2008fff7000 (errno: 12)
==1143==ReserveShadowMemoryRange failed while trying to map 0xdfff0001000 bytes. Perhaps you're using ulimit -v
Aborted (core dumped)

Clang 3.8.0:

==1387==ERROR: AddressSanitizer failed to allocate 0xdfff0001000 (15392894357504) bytes at address 2008fff7000 (errno: 12)
==1387==ReserveShadowMemoryRange failed while trying to map 0xdfff0001000 bytes. Perhaps you're using ulimit -v
Aborted (core dumped)

有人可以就这个错误的可能来源给我一些提示吗?我很难追踪发生这种情况的线路,因为它位于一个非常大的代码库中。


编辑

问题未解决,但与以下功能有关:

void get_sparsity(Data & data) {
    T x[n_vars] = {};
    T g[n_constraints] = {}; 
    for (Index j = 0; j < n_vars; j++) {

        const T x_j = x[j];
        x[j] = NAN;
        eval_g(n_vars, x, TRUE, n_constraints, g, &data);
        x[j] = x_j;

        std::vector<Index> nonzero_entries;
        for (Index i = 0; i < n_constraints; i++) {
            if (isnan(g[i])) {
                data.flattened_nonzero_rows.push_back(i);
                data.flattened_nonzero_cols.push_back(j);
                nonzero_entries.push_back(i);
            }
        }
        data.nonzeros.push_back(nonzero_entries);
    }
    int internal_debug_point = 5;
}

这样称呼:

get_sparsity(data);
int external_debug_point= 6;

但是,当我在 get_sparsity 函数的最后一行 internal_debug_point = 5 上放置一个调试点时,它会毫无问题地到达该行。但是,在退出函数时,在到达外部调试点external_debug_point = 6 之前,它会因错误而崩溃

received signal SIGABRT, Aborted.
0x00007ffffe315428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54

我的猜测是 GCC 仅在退出该函数时检查金丝雀,因此错误实际上发生在函数内部。这听起来合理吗?如果是这样,那么有没有办法让 GCC 或 clang 进行更频繁的金丝雀检查?

【问题讨论】:

  • 你觉得这个字节数不可疑吗?看起来您已使用地址作为大小。你能提供显示问题的简化代码吗?没有它,我们都只是猜测......
  • 我不知道错误发生在哪里。所以很难追查到。
  • 大小看起来很可疑,但实际上我们在代码中处理的是非常非常大的矩阵。
  • 令我困惑的是,为什么没有 ASAN,clang 不会产生错误,但 GCC 会产生错误
  • 你为什么要分配15,392,894,357,504字节? (除非您期望“非常大的矩阵”为 15Tb。)当您在 gdb 下运行 asan 版本时,什么请求分配?

标签: c++ gcc clang


【解决方案1】:

我怀疑 ASan 内存不足。

我不认为 ASan 错误意味着您的程序正在尝试分配该内存,这意味着 ASan 正在尝试为自己分配它(它说“影子内存”,这是 ASan 用来跟踪您的内存的程序分配)。

如果迭代次数(和数组大小)n_vars 很大,则函数将在每个循环中为新的std::vector 使用额外内存,从而迫使 ASan 跟踪越来越多的内存。

您可以尝试将局部向量移出循环(无论如何这可能会提高函数的性能):

std::vector<Index> nonzero_entries;
for (Index j = 0; j < n_vars; j++) {

    // ...

    for (Index i = 0; i < n_constraints; i++) {
        if (isnan(g[i])) {
            data.flattened_nonzero_rows.push_back(i);
            data.flattened_nonzero_cols.push_back(j);
            nonzero_entries.push_back(i);
        }
    }
    data.nonzeros.push_back(nonzero_entries);
    nonzero_entries.clear();
}

这将为nonzero_entries 重复使用相同的内存,而不是为每次迭代的新向量分配和解除内存。

【讨论】:

    【解决方案2】:

    试图找出堆栈问题的根源无济于事。所以我尝试了不同的方法。通过调试,我缩小了上述函数get_sparsity为罪魁祸首。调试器没有给我任何提示,确切地说是问题发生在哪里,但它在该函数内部的某个地方。有了这些信息,我将该函数 xg 中仅有的两个堆栈变量切换为堆变量,以便 valgrind 可以帮助我找到错误(sgcheck 为空)。具体来说,我将上面的代码修改为

    void get_sparsity(Data & data) {
    
        std::vector<T> x(n_vars, 0);
        std::vector<T> g(n_constraints, 0);
    
        /* However, for our purposes, it is easier to make an std::vector of Eigen
         * vectors, where the ith entry of "nonzero_entries" contains a vector of
         * indices in g for which g(indices) are nonzero when perturbing x(i).
         * If that sounds complicated, just look at the code and compare to
         * the code where we use the sparsity structure.
         */
        for (Index j = 0; j < n_vars; j++) {
    
            const T x_j = x[j];
            x[j] = NAN;
            Bool val = eval_g(n_vars, x.data(), TRUE, n_constraints, g.data(), &data);
            x[j] = x_j;
    
            std::vector<Index> nonzero_entries;
            for (Index i = 0; i < n_constraints; i++) {
                if (isnan(g[i])) {
                    data.flattened_nonzero_rows.push_back(i);
                    data.flattened_nonzero_cols.push_back(j);
                    nonzero_entries.push_back(i);
                }
            }
            data.nonzeros.push_back(nonzero_entries);
        }
        int bob = 5;
        return;
    }
    

    然后valgrinded 找到违规行。现在我知道问题出在哪里,我可以解决问题。

    【讨论】:

    • 您还可以使用 GDB 在堆栈金丝雀的位置上设置硬件观察点。但是将数组移动到堆中也是一个好方法。
    猜你喜欢
    • 1970-01-01
    • 2016-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-14
    • 1970-01-01
    相关资源
    最近更新 更多