Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does GCC-generated code read junk from stack?

Consider the following code (uses Eigen):

#include <Eigen/Dense>
#include <iostream>

template<int rows, int cols, int row, class R, class Rv, int N, class... Rs>
inline typename std::enable_if<sizeof...(Rs)==0>::type
    setRow(Eigen::Matrix<R,rows,cols>&)
{}

template<int rows, int cols, int row, class R, class Rv, int N=0, class... Rs>
inline typename std::enable_if<sizeof...(Rs)==cols-N-1>::type
    setRow(Eigen::Matrix<R,rows,cols>& m, Rv val, Rs...args)
{
    m(row,N)=val;
    setRow<rows,cols,row,R,Rv,N+1>(m,args...);
}
template<class T, int R, int C, int CUR_ROW>
class MatrixConstructor
{
    Eigen::Matrix<T,R,C> m;
public:
    MatrixConstructor(const Eigen::Matrix<T,R,C>& m)
        : m(m)
    {}
    MatrixConstructor()
    {}
    template<class...Ts>
    typename std::enable_if<sizeof...(Ts)==C && CUR_ROW<R-1,
    MatrixConstructor<T,R,C,CUR_ROW+1>>::type operator()(Ts... vals)
    {
        setRow<R,C,CUR_ROW>(m,vals...);                                                                                                                                                                                 
        return MatrixConstructor<T,R,C,CUR_ROW+1>(m);
    }

    template<class...Ts>
    typename std::enable_if<sizeof...(Ts)==C && CUR_ROW==R-1,
    Eigen::Matrix<T,R,C>>::type operator()(Ts... vals)
    {
        setRow<R,C,CUR_ROW>(m,vals...);
        return m;
    }
};

void test()
{
    Eigen::Matrix<double,4,3> m=MatrixConstructor<double,4,3,0>()(1,2,3)
                                                                 (4,5,6)
                                                                 (7,8,9)
                                                                 (5,4,3);
    std::cout << m;
}

int main()
{ test(); }

I compile it with gcc-4.8 with full optimizations and option to generate assembly listing. Here's the command I use:

g++ minitest.cpp -I/usr/include/eigen3 -std=c++0x -O3 -march=native -S -masm=intel

(My CPU is Intel(R) Xeon(R) CPU E3-1226 v3 running a 64 bit Linux system — hopefully now -march=native makes sense to the readers.)

What makes me wonder is that some instructions generated with this command line seem to be nonsensical, and even redundant. See e.g. how the test() function starts after setting up stack (for full code for both test() and main() see here):

vmovsd   xmm4, QWORD PTR .LC6[rip] # 1.0
lea      rsi, [rsp+96]
vmovsd   xmm5, QWORD PTR .LC7[rip] # 2.0
vmovsd   QWORD PTR [rsp], xmm4
vmovapd  xmm3, XMMWORD PTR [rsp+16] # What does it read?!
vmovapd  xmm1, XMMWORD PTR [rsp]    # And this!
vmovsd   QWORD PTR [rsp+32], xmm5
vmovsd   xmm0, QWORD PTR .LC8[rip] # 3.0
vmovapd  XMMWORD PTR [rsp+304], xmm3 # And now even save this junk?!
vmovapd  XMMWORD PTR [rsp+192], xmm1
vmovapd  xmm3, XMMWORD PTR [rsp+48]
vmovapd  xmm1, XMMWORD PTR [rsp+32]
vmovsd   QWORD PTR [rsp+64], xmm0
vmovsd   xmm7, QWORD PTR .LC12[rip] # 7.0
vmovapd  XMMWORD PTR [rsp+336], xmm3
vmovapd  XMMWORD PTR [rsp+224], xmm1
vmovapd  xmm3, XMMWORD PTR [rsp+80]
vmovsd   QWORD PTR [rsp+304], xmm7   # Even stranger — overwrites the junk

I've stepped through these reads of junk in debugger and confirmed that after them the xmm3 and xmm1 registers do indeed have nonsensical values. Looking at this reading of undefined values, I'm starting to suspect that my program indeed tries to access some memory which should be inaccessible. But why? Have I introduced a UB somewhere?

I've also tried running the program compiled with -fsanitize=address, but it worked without any crashes.

like image 658
Ruslan Avatar asked Jun 26 '15 12:06

Ruslan


1 Answers

Your code takes the following steps:

  1. Creates an uninitialized MatrixConstructor object with an uninitialized Eigen::Matrix member
  2. Sets one row of the Eigen::Matrix member
  3. Creates a new MatrixConstructor object whose Eigen::Matrix member is initialized with the old MatrixConstruction object's Eigen::Matrix member

So in step 3 you're copying an Eigen::Matrix object that has only had the first row set. The rest of the object's values are still uninitialized. Since these are all temporary objects they're all allocated in the stack, so it's not surprising you're seeing junk being read from the stack.

Note that this assumes that the Eigen::Matrix() constructor doesn't initialize the object, which from a quick look at the source it doesn't appear to do by default.

like image 98
Ross Ridge Avatar answered Oct 19 '22 23:10

Ross Ridge