Search code examples
c++castingfunction-pointerspointer-to-memberreinterpret-cast

how to see content of a method pointer?


typedef int (D::*fptr)(void);
fptr bfunc;
bfunc=&D::Bfunc;
cout<<(reinterpret_cast<unsigned long long>(bfunc)&0xffffffff00000000)<<endl;

complete code available at : https://ideone.com/wRVyTu

I am trying to use reinterpret_cast, but the compiler throws error

prog.cpp: In function 'int main()': prog.cpp:49:51: error: invalid cast from type 'fptr {aka int (D::*)()}' to type 'long long unsigned int'   cout<<(reinterpret_cast<unsigned long long>(bfunc)&0xffffffff00000000)<<endl;

My questions are :

  1. why is reinterpret_cast not suitable for this occasion?

  2. Is there another way, I can see the contents of the method pointer?


Solution

  • Using clang++ to compile a slightly modified version of your code (removed all the cout to not get thousands of lines...), we get this for main:

    define i32 @main() #0 {
    entry:
      %retval = alloca i32, align 4
      %bfunc = alloca { i64, i64 }, align 8
      %dfunc = alloca { i64, i64 }, align 8
      store i32 0, i32* %retval, align 4
      store { i64, i64 } { i64 1, i64 16 }, { i64, i64 }* %bfunc, align 8
      store { i64, i64 } { i64 9, i64 0 }, { i64, i64 }* %dfunc, align 8
      ret i32 0
    }
    

    Note that the bfunc and dfunc are two 64-bit integer values. If I compile for 32-bit x86 it is two i32 (so 32-bit integer values).

    So, if we make main look like this:

    int main() {
        // your code goes here
    
        typedef int (D::*fptr)(void);
    
        fptr bfunc;
        fptr dfunc;
    
        bfunc=&D::Bfunc;
        dfunc=&D::Dfunc;
    
        D d;
        (d.*bfunc)();
    
        return 0;
    }
    

    the generated code looks like this:

    ; Function Attrs: norecurse uwtable
    define i32 @main() #0 {
    entry:
      %retval = alloca i32, align 4
      %bfunc = alloca { i64, i64 }, align 8
      %dfunc = alloca { i64, i64 }, align 8
      %d = alloca %class.D, align 8
      store i32 0, i32* %retval, align 4
      store { i64, i64 } { i64 1, i64 16 }, { i64, i64 }* %bfunc, align 8
      store { i64, i64 } { i64 9, i64 0 }, { i64, i64 }* %dfunc, align 8
      call void @_ZN1DC2Ev(%class.D* %d) #3
      %0 = load { i64, i64 }, { i64, i64 }* %bfunc, align 8
      %memptr.adj = extractvalue { i64, i64 } %0, 1
      %1 = bitcast %class.D* %d to i8*
      %2 = getelementptr inbounds i8, i8* %1, i64 %memptr.adj
      %this.adjusted = bitcast i8* %2 to %class.D*
      %memptr.ptr = extractvalue { i64, i64 } %0, 0
      %3 = and i64 %memptr.ptr, 1
      %memptr.isvirtual = icmp ne i64 %3, 0
      br i1 %memptr.isvirtual, label %memptr.virtual, label %memptr.nonvirtual
    
    memptr.virtual:                                   ; preds = %entry
      %4 = bitcast %class.D* %this.adjusted to i8**
      %vtable = load i8*, i8** %4, align 8
      %5 = sub i64 %memptr.ptr, 1
      %6 = getelementptr i8, i8* %vtable, i64 %5
      %7 = bitcast i8* %6 to i32 (%class.D*)**
      %memptr.virtualfn = load i32 (%class.D*)*, i32 (%class.D*)** %7, align 8
      br label %memptr.end
    
    memptr.nonvirtual:                                ; preds = %entry
      %memptr.nonvirtualfn = inttoptr i64 %memptr.ptr to i32 (%class.D*)*
      br label %memptr.end
    
    memptr.end:                                       ; preds = %memptr.nonvirtual, %memptr.virtual
      %8 = phi i32 (%class.D*)* [ %memptr.virtualfn, %memptr.virtual ], [ %memptr.nonvirtualfn, %memptr.nonvirtual ]
      %call = call i32 %8(%class.D* %this.adjusted)
      ret i32 0
    }
    

    This is not entirely trivial to follow, but in essense:

      %memptr.adj = Read adjustment from bfunc[1]
      %2 = %d[%memptr.adj]
      cast %2 to D*
      %memptr.ptr = bfunc[0]
      if (%memptr.ptr & 1) goto is_virtual else goto is_non_virtual
    
    is_virtual:
      %memptr.virtual=vtable[%memptr.ptr-1]
      goto common
    
    is_non_virtual:
      %memptr.non_virtual = %memptr.ptr
    
    common:
       if we came from 
          is_non_virtual: %8 = %memptr.non_virtual
          is_virtual: %8 = %memptr.virutal
       call %8
    

    I skipped some type-casts and stuff to make it simpler.

    NOTE This is NOT meant to say "this is how it is implemented always. It's one example of what the compiler MAY do. Different compilers will do this subtly differently. But if the function may or may not be virtual, the compiler first has to figure out which. [In the above example, I'm fairly sure we can turn on optimisation and get much better code, but it would presumably just figure out exactly what's going on and remove all of the code, which for understanding how it works is pointless]