Archive for the ‘C/C++’ Category

虚函数

有时候脑袋短路,加上记忆力退化,很多已知的概念又模糊混淆了。唉,杯具。
今天就在想一个拥有虚函数的基类,它的对象是不是没有vptr和vtable?因为它本身很确定了,如下:
class CA
{
public:
 virtual void Func1()
 {
  printf("CA::Func1\n");
 }
};
CA a;
printf("sizeof(a)=%d, sizeof(CA)=%d\n", sizeof(a), sizeof(CA));
a.Func1();
事实是sizeof均为4,也就是说有vptr和vtable!而a.Func1的调用也确实是很确定的:
00424591  lea         ecx,[a]
00424594  call        CA::Func1 (41564Ah)
但若使用指针调用则:
CA *pa = &a;
0042D241  lea         eax,[a]
0042D244  mov         dword ptr [pa],eax
pa->Func1();
0042D247  mov         eax,dword ptr [pa]
0042D24A  mov         edx,dword ptr [eax]
0042D24C  mov         esi,esp
0042D24E  mov         ecx,dword ptr [pa]
0042D251  mov         eax,dword ptr [edx]
0042D253  call        eax
很明显是通过vptr和vtable实现的。想想多态的概念也确实是虚函数+指针/引用的结合实现的。
因此一个类只要拥有虚函数则一定是拥有vptr和vtable的,编译器可以不用区分这是不是多态(指针/引用的对象是基类CA的还是子类CB的)来分别生成代码,如下:
CA *pa = &b;
0042D264  lea         eax,[b]
0042D267  mov         dword ptr [pa],eax
pa->Func1();
0042D26A  mov         eax,dword ptr [pa]
0042D26D  mov         edx,dword ptr [eax]
0042D26F  mov         esi,esp
0042D271  mov         ecx,dword ptr [pa]
0042D274  mov         eax,dword ptr [edx]
0042D276  call        eax 
又在想另一个问题:若一个拥有虚函数的子类继承自一个拥有虚函数的基类,它的对象是不是有两套vptr和vtable?如下
class CB : public CA
{
public:
 virtual void Func2()
 {
  printf("CB::Func2\n");
 }
};
答案是不需要,因为可以通过一套vptr和vtable调用到各自的成员函数,只有多重继承时才会有多套vptr和vtable!

ATL碎语CAtlModule CAtlBaseModule

// CAtlModule模块基类
__declspec(selectany) CAtlModule* _pAtlModule = NULL; // 全局变量, 代表一个模块

struct _ATL_MODULE70
{
 UINT cbSize;
 LONG m_nLockCnt;
 _ATL_TERMFUNC_ELEM* m_pTermFuncs;
 CComCriticalSection m_csStaticDataInitAndTypeInfo;
};
typedef _ATL_MODULE70 _ATL_MODULE;

class ATL_NO_VTABLE CAtlModule : public _ATL_MODULE
{
 CAtlModule() throw()
 {
  // Should have only one instance of a class
  // derived from CAtlModule in a project.
  // 一个工程中仅有一个CAtlModule子类的实例
  // 其一般也是一个全局变量, 因为它会被赋值给_pAtlModule
  ATLASSERT(_pAtlModule == NULL);
  // _ATL_MODULE中的变量初始化
  cbSize = 0;
  m_nLockCnt = 0;
  m_pTermFuncs = NULL;
  
  _pAtlModule = this; // 赋值给_pAtlModule
  
  m_pGIT = NULL;

  if (FAILED(m_csStaticDataInitAndTypeInfo.Init()))
  {
   // 注意此if块中返回时, _pAtlModule已被赋值一个失败初始化的对象
   ATLTRACE(atlTraceGeneral, 0, _T("ERROR : Unable to initialize critical section in CAtlModule\n"));
   ATLASSERT(0);
   CAtlBaseModule::m_bInitFailed = true;
   return;
  }

  // Set cbSize on success.
  cbSize = sizeof(_ATL_MODULE);
 }
};

// CAtlBaseModule的定义和实现分别在VC\atlmfc\include\atlcore.h和VC\atlmfc\src\atl\atls\atlbase.cpp中
// atlbase.cpp定义了其一个全局变量_AtlBaseModule
#pragma init_seg( lib )
CAtlBaseModule _AtlBaseModule;
extern CAtlWinModule _AtlWinModule = CAtlWinModule();

struct _ATL_BASE_MODULE70
{
 UINT cbSize;
 HINSTANCE m_hInst;
 HINSTANCE m_hInstResource;
 bool m_bNT5orWin98;
 DWORD dwAtlBuildVer;
 const GUID* pguidVer;
 CComCriticalSection m_csResource;
 CSimpleArray<HINSTANCE> m_rgResourceInstance;
};
typedef _ATL_BASE_MODULE70 _ATL_BASE_MODULE;

class CAtlBaseModule : public _ATL_BASE_MODULE
{
 CAtlBaseModule::CAtlBaseModule() throw()
 {
  cbSize = sizeof(_ATL_BASE_MODULE);

  m_hInst = m_hInstResource = reinterpret_cast<HINSTANCE>(&__ImageBase);

  m_bNT5orWin98 = false;
  OSVERSIONINFO version;
  memset(&version, 0, sizeof(version));
  version.dwOSVersionInfoSize = sizeof(version);
  ::GetVersionEx(&version);
  if(version.dwPlatformId == VER_PLATFORM_WIN32_NT)
  {
   if(version.dwMajorVersion >= 5)
   {
    m_bNT5orWin98 = true;
   }
  }
  else if(version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
  {
   if((version.dwMajorVersion > 4) || ((version.dwMajorVersion == 4) &&
    (version.dwMinorVersion > 0)))
   {
    m_bNT5orWin98 = true;
   }
  }

  dwAtlBuildVer = _ATL_VER;
  pguidVer = &GUID_ATLVer70;

  if (FAILED(m_csResource.Init()))
  {
   ATLTRACE(atlTraceGeneral, 0, _T("ERROR : Unable to initialize critical section in CAtlBaseModule\n"));
   ATLASSERT(0);
   CAtlBaseModule::m_bInitFailed = true;
  }
 }
}

获取系统调用号

ULONG GetSyscallId(char *pszSyscallName)
{
 if(NULL == pszSyscallName || '\0' == pszSyscallName[0])
 {
  return 0;
 }

 HMODULE hMod = GetModuleHandle(_T("ntdll.dll"));
 if(NULL == hMod)
 {
  return 0;
 }

 unsigned char *pByte = (unsigned char *)GetProcAddress(hMod, pszSyscallName);
 if(NULL == pByte)
 {
  return 0;
 }

 if(0xB8 == pByte[0] && 0xBA == pByte[5])
 {
  return *(ULONG *)(pByte + 1);
 }
 else
 {
  return 0;
 }
}

这个方法的原理很早就知道了,系统调用时会用eax保存系统调用号:
0: kd> u ntdll!NtCreateProcessEx
ntdll!NtCreateProcessEx:
7c92d15e b830000000 mov eax,30h
7c92d163 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c92d168 ff12 call dword ptr [edx]
7c92d16a c22400 ret 24h
7c92d16d 90 nop
一直没实现,今天实现了一下。

实现过程中遇到了一个char比较的问题,上面的代码中一开始pByte我使用的是char*,结果进行char比较时发现char为0xB8和0xBA时却与它们不相等。看了汇编代码才知道是将char movsx符号扩展后再进行dwrod的cmp了。用unsigned char*即可解决问题。编译器应该是因对齐进行了优化。

参考资料:在Windows 2003中HOOK ZwCreateProcessEx

天书夜读2.2思考与练习

int a, b, d;
d = a + b;
int i = 1, c = 0;
while(c < 0x64)
{
	c += i;
}
switch(c)
{
	case 0:
		d = 1;
	case 1:
		d = c;
		break;
	default:
		d = 0;
}
return d;

__declspec(novtable)

novtable:

可用于任何类声明中,但最好只用于纯接口类,即类本身从不实例化。此关键字的声明将阻止编译器对构造和析构函数的vfptr的初始化。可优化编译后代码大小。

如果试图实例化一个用__declspec(novtable)声明的类然后访问类中成员,则会在运行时产生访问错误(access violation,即AV)。

C++中 _declspec(novtable) 的探讨

无觅相关文章插件,快速提升流量