在c里面有个函数是offsetof,提供结构体里偏移量计算,你查看官网定义发现这个宏是这样写的
#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
#endif /* __KERNEL__ */
这里看到它是将一个0地址转成TYPE的对象指针,在用指针的变量成员的地址就是该偏移量了。这里你也可以将0改成别的数字,只要减去头就可以,当然这里指的是不超过范围的。
原理介绍到这里,现在我们来玩一下变形。
class A{
public:
int publicA;
int publicB;
private:
int a=9;
void JustTest(){
cout << "this is A, a is "<<a<<endl;
};
};
#define MyOffset(type, mem) ((size_t)&((type*)0)->mem )
这时如果我要求A相对于a的偏移是否能利用这个原理呢?答案是不能的,因为这个原理是根据a可以给指针访问而得到的。那么我们又想知道该类的成员偏移量怎么办呢?这里我暂时想到以下方法
class A{
public:
int publicA;
int publicB;
private:
int a=9;
void JustTest(){
cout << "this is A, a is "<<a<<endl;
};
};
class B{
public:
int publicA =1;
int publicB=2;
int a= 10;
void JustTest(){
cout << "this is B, a is "<<a<<endl;
};
};
#define MyOffset(type, mem) ((size_t)&((type*)0)->mem )
B是A的一样的代码,只是权限不同,这就相当于计算了A的偏移变量。这时候我们思考一个问题,如果我们能用B的分布去推算A,那么能不能也同时修改A的私有变量呢?答案是可以的,代码如下
B* test;
B testb;
A testA;
test = &testb;
test->JustTest();
test = (B*)&testA;
test->JustTest();
int* changeA = (int*)((char*)(&testA )+8);
cout <<"changeA " <<*changeA<<endl;
*changeA = 11;
test->JustTest();
通过上述,我们发现我们修改了A的变量,但是在调用B的方法时,我们还是在B的JustTest里面,现在我们再思考下能不能调用A的私有方法?这个暂时还在思考。。。以后想到再写。
上述我们讲到怎么样修改classA的private变量,现在我们开始说回类里函数的调用问题。虽然还是没有解决那个方法,但也总结一下,当作这几天的积累,代码先奉上
#include <iostream>
#include <stdio.h>
typedef unsigned long DWORD;
class tt
{
public:
void foo(int x,char c,char *s)//没有指定类型,默认是__thiscall.
{
printf("\n m_a=%d, %d,%c,%s\n",m_a,x,c,s);
foo2(x,c,s);
}
void __attribute__((__stdcall__)) foo2(int x,char c,char *s)//成员函数指定了__stdcall调用约定.
{
printf("\n m_a=%d, %d,%c,%s\n",m_a,x,c,s);
}
int m_a =895;
};
class Privatett
{
void foo(int x,char c,char *s)//没有指定类型,默认是__thiscall.
{
printf("\n Privatett_m_a=%d, %d,%c,%s\n",m_a,x,c,s);
}
void __attribute__((__stdcall__)) foo2(int x,char c,char *s)//成员函数指定了__stdcall调用约定.
{
printf("\n Privatett_m_a=%d, %d,%c,%s\n",m_a,x,c,s);
}
int m_a =895;
};
template <class ToType, class FromType>
void GetMemberFuncAddr_VC6(ToType& addr,FromType f)
{
union
{
FromType _f;
ToType _t;
}ut;
ut._f = f;
addr = ut._t;
}
int main() {
// int a=100,b=200;
// int result =3;
//
// __asm__ ("movl %1,%0 \n\t"
// "movl $1028,%1\n\t"
// "movl %1,%0": "=r" (result) : "m" (b));
//
// std::cout << "Hello, World! " << result<< std::endl;
// __asm__ ("movl %1,%0 \n\t"
// "movl $1028,%%eax\n\t"
// "movl %%eax,%0": "=r" (result) : "m" (b));
// return 0;
typedef void (__attribute__((__stdcall__)) *FUNCTYPE) ( void * , int,char,char*);//定义对应的非成员函数指针类型,注意指定__stdcall.
typedef void (__attribute__((__stdcall__)) *FUNCTYPE2)(void *,int,char,char*);//注意多了一个void *参数.
tt abc;
abc.m_a = 123;
DWORD ptr;
DWORD This = *(DWORD*)&abc;
DWORD Thest = (DWORD)&abc.m_a;
GetMemberFuncAddr_VC6(ptr,&tt::foo); //取成员函数地址.
std::cout << "before "<< Thest <<std::endl;
FUNCTYPE fnFooPtr = (FUNCTYPE) ptr;//将函数地址转化为普通函数的指针.
__asm__ __volatile__("movq %1,%0\n\t"
"movq %0, %%rcx" : "=r" (This) : "m" (abc));
// __asm__ ("movl $This, %ecx");
// __asm__("movl %esp,%eax"); //看起来很熟悉吧!
fnFooPtr(&abc,5,'e',"7xyz"); //象普通函数一样调用成员函数的地址.
GetMemberFuncAddr_VC6(ptr,&tt::foo2); //取成员函数地址.
FUNCTYPE2 fnFooPtr2 = (FUNCTYPE2) ptr;//将函数地址转化为普通函数的指针.
fnFooPtr2(&abc,5,'a',"7xyz"); //象普通函数一样调用成员函数的地址,注意第一个参数是this指针.
Privatett testd3;
fnFooPtr(&testd3,5,'e',"7xyz"); //象普通函数一样调用成员函数的地址.
fnFooPtr2(&testd3,5,'a',"7xyz"); //象普通函数一样调用成员函数的地址,注意第一个参数是this指针.
std::cout << "Hello, World!" << std::endl;
return 0;
}
这里看到类的普通成员函数里面是没有跟随类的(这里的普通成员函数不包括虚函数),它的函数地址因为不跟随类,所以没有办法计算到私有的普通函数(暂时是我个人没有研究到),但在普通public函数我们能计算到,而且它的地址接入多了一个this的指针,这里你可以看到(void*)这个就是this,按照这个原理,我们可以这样想,如果this可以换成别的对象,会产生什么好玩的事情。上述的例子里就是接tt类的成员函数,但我们传递的是testd3的对象,暂时研究到这里,至于怎样获取类的私有函数,以后有什么发现再补上。
补上上述的私有成员函数的地址以及调用方法,老规矩,代码先上:
//原理:模板动态生成而逃过静态编译的问题
template<typename Tag>
struct result {
/* export it ... */
typedef typename Tag::type type;
static type ptr;
};
template<typename Tag>
typename result<Tag>::type result<Tag>::ptr; //声明指针
template<typename Tag, typename Tag::type p>
struct rob : result<Tag> { //继承result
/* fill it ... */
struct filler {
filler() { result<Tag>::ptr = p; } //将指针复制
};
static filler filler_obj;
};
template<typename Tag, typename Tag::type p>
typename rob<Tag, p>::filler rob<Tag, p>::filler_obj; //filler_obj 静态对象
struct A {
public:
void sayit(){std::cout << "My a is " <<a <<std::endl;}
private:
void f() {
std::cout << "proof! ((((" << std::endl;
a += 10;
}
int a=0;
};
struct Af { typedef void(A::*type)(); };
template class rob<Af, &A::f>;
int main() {
A a;
a.sayit();
(a.*result<Af>::ptr)();
a.sayit();
}
这个是网上找回来的版本,挺复杂的一个例子,主要思想是用static找到函数地址。如果想看简化版,请看下面我写的
#include <iostream>
#include <stdio.h>
class Privatett
{
void foo1()//没有指定类型,默认是__thiscall.
{
printf("\n Privatett_m_a\n");
}
void foo(int x,char c,char *s)//没有指定类型,默认是__thiscall.
{
printf("\n Privatett_m_a=%d, %d,%c,%s\n",m_a,x,c,s);
}
void __attribute__((__stdcall__)) foo2(int x,char c,char *s)//成员函数指定了__stdcall调用约定.
{
printf("\n Privatett_m_a=%d, %d,%c,%s\n",m_a,x,c,s);
}
int m_a =895;
};
struct SaveType
{
typedef void(Privatett::*type)();
};
static SaveType::type testdFor ;
template< typename SaveType::type p>
struct ForTest{
struct filler {
filler() { testdFor = p;
std::cout << "TestdFor "<<std::endl;} //将指针复制
};
static filler filler_obj;
};
template class ForTest<&Privatett::foo1>;
template< typename SaveType::type p>
typename ForTest<p>::filler ForTest<p>::filler_obj;
int main() {
Privatett testd3;
(testd3.*testdFor)();
std::cout << "Hello, World!" << std::endl;
return 0;
}
template class ForTest<&Privatett::foo1>; 这里要注意,关键就在这里。
先看看template class的意思是显式实例化,这里显示实例化的意思是显式实例化只需声明,不需要重新定义。编译器根据模板实现实例声明和实例定义,这里主要思路是借用了显示实例化的实例化,那样编译器会将Privatett::foo1的实例化的代码写好(这里只是口头这样说,真正的说法可以搜搜看显式实例化),而我们没有调用,这个是关键。但里面有个static的变量,static是总存在内存,所以没有选择,编译器会将static的变量初始化,这一切就理所当然的我们将地址拿到了static的变量里面,这里的static是public,就算不是我们也能计算到。而此时,我们拿到了private方法的地址,就可以像普通函数调用也可以。
typename ForTest<p>::filler ForTest<p>::filler_obj;就是初始化对象,从而拿到地址,分析到这里结束,结束这个话题,这里的原理是模板的一个特性,只关心参数类型,不安全检测。