从Entry Point到main函数调用(4)

转自 http://driftcloudy.iteye.com/blog/1052971

先撇开_ioinit()不谈,IO有点儿麻烦,待有空再去挖掘这些东西。

GetCommandLineA

该函数的汇编代码短得令人发指。

Asm代码 复制代码  收藏代码
  1. 7C812FBD    mov     eax, dword ptr [7C8855F4]   
  2. 7C812FC2    retn   

可见7C8855F4是个特殊的内存地址,其中存放了一个指向命令行缓冲区的指针。通过该指针,可以访问整个exe的完整路径以及后面附加的参数。

 

另外,通过PEB 中的ProcessParameters 也能够访问到CommandLine,但是通过这种方式访问到得是一个Unicode 字符串,而GetCommandLineA 访问的则是ANSI字符串。

 

__crtGetEnvironmentStringsA

在A_ENV.c 文件中可以看到该函数的具体实现。在文件开头有如下描述:

写道
Internal support function. Since GetEnvironmentStrings returns in OEM and we want ANSI ( note that GetEnvironmentVariable returns ANSI! ) and SetFileApistoAnsi( ) does not affect it, we have no choice but to obtain the block in wide character and convert to ANSI.

最后返回的是一个指向当前进程的环境变量的指针,这里所谓的环境变量用一种类似于键值对的方式存储在内存中。

注意首先得到是wide character 版本的环境变量,随后需要将它们转化成ANSI 。

 

来看一下__crtGetEnvironmentStringsA 的实现:

C代码 复制代码  收藏代码
  1. LPVOID __crtGetEnvironmentStringsA( VOID  )   
  2. {   
  3.         static int f_use = 0;   
  4.         wchar_t *wEnv = NULL;   
  5.         wchar_t *wTmp;   
  6.         char *aEnv = NULL;   
  7.         char *aTmp;   
  8.         int nSizeW;   
  9.         int nSizeA;   
  10.   
  11.         /*  
  12.          * Look for 'preferred' flavor. Otherwise use available flavor.  
  13.          * Must actually call the function to ensure it's not a stub.  
  14.          */  
  15.         if ( 0 == f_use )   
  16.         {   
  17.             if ( NULL != (wEnv = GetEnvironmentStringsW()) )   
  18.                 f_use = USE_W;   
  19.   
  20.             else if ( NULL != (aEnv = GetEnvironmentStringsA()) )   
  21.                 f_use = USE_A;   
  22.   
  23.             else  
  24.                 return NULL;   
  25.         }   
  26.            
  27.         /* Use "W" version */  
  28.         if (USE_W == f_use)   
  29.         {           
  30.                  ……           
  31.         }   
  32.   
  33.         /* Use "A" version */  
  34.         if ( USE_A == f_use )   
  35.         {   
  36.                  ……           
  37.         }   
  38.            
  39.         return NULL;   
  40. }  

 

可见实质上,__crtGetEnvironmentStringsA 函数依然是调用了win API,可能是:

GetEnvironmentStringsW  或 GetEnvironmentStringsA

 

有人也许奇怪,为什么__crtGetEnvironmentStringsA 不直接去调用GetEnvironmentStringsA 呢?原因在于,即使是用GetEnvironmentStringsA ,所获取的也并非是 ANSI 的环境变量。

GetEnvironmentStringsA uses the OEM codepage .The "ANSI" version of GetEnvironmentStrings (GetEnvironmentStringsA) returns the strings in the OEM code page, not the ANSI code page . If you need the returned strings in the ANSI code page call the Unicode version (GetEnvironmentStringsW) and translate the returned wide-string to an 8-bit string using a conversion function such as WideCharToMultiByte. Or call GetEnvironmentVariableA, which correctly uses the ANSI code page.

这里很清楚地写到,GetEnvironmentStringsA 返回的其实是OEM 字符,并非 ANSI。如果最终需要获得 ANSI 的环境变量,可以使用 GetEnvironmentStringsW ,然后将所获得的结果从 wide-string 转化成 8-bit string 。

 

因此,__crtGetEnvironmentStringsA 中首先调用了 GetEnvironmentStringsW 之后,设置 f_use = USE_W , 再进入if (USE_W == f_use) 的 语句块,进入转化成ANSI的处理。

 

C代码 复制代码  收藏代码
  1. /*   
  2.      这里将指针wTmp 移动到 environment block 的末尾处   
  3.      特别要注意,wTmp 是一个 wchar_t 类型的指针,因此 ++ 是移动两个字节  
  4.      例如 environment block 的末尾是:  
  5.          45 00   64 00   69 00   74 00   00 00   00(移动到此处)  
  6.          E       d       i       t       \0      00  
  7. */  
  8. wTmp = wEnv;   
  9. while ( *wTmp != L'\0' ) {   
  10.     if ( *++wTmp == L'\0' )   
  11.         wTmp++;   
  12. }   
  13.   
  14. /* 求出environment block 中wide char的个数 +1 */  
  15. nSizeW = wTmp - wEnv + 1;   
  16.   
  17. /* 该API返回转化为byte字符的字节数,其实nSizeA 和 nSizeW  数值相同 */  
  18. nSizeA = WideCharToMultiByte( CP_ACP,0,wEnv,nSizeW,NULL,0,NULL,NULL );   
  19.   
  20. /* 分配一块大小为nSizeA 的内存 */  
  21. if ( (nSizeA == 0) || ((aEnv = (char *)_malloc_crt(nSizeA)) == NULL) )   
  22. {   
  23.     FreeEnvironmentStringsW( wEnv );   
  24.     return NULL;   
  25. }   
  26.   
  27. /* 这里将WideChar 转化为 byte ,并且存放到刚分配的内存区域中 */  
  28. if ( !WideCharToMultiByte( CP_ACP,0,wEnv,nSizeW,aEnv,nSizeA,NULL,NULL ) )   
  29. {   
  30.     _free_crt( aEnv );   
  31.     aEnv = NULL;   
  32. }   
  33.   
  34. FreeEnvironmentStringsW( wEnv );   
  35. return aEnv;  

这里的实现与我想象中有些差别,不太清楚为什么MS的coder 会两次调用WideCharToMultiByte 函数对environment block 进行转化。

 

_setargv

该函数负责为C 程序的"argc" 和 "argv" 参数作好准备,_setargv 的源代码位于 STDARGV.c 文件。它会读取 _acmdln(存放了之前GetCommandLineA返回的指针)来访问command line,然后最关键的步骤是通过调用parse_cmdline 函数来解析command line,并且创建argv 数组。

 

这里仅分析_setargv 中最为核心的代码行。

C代码 复制代码  收藏代码
  1. _TSCHAR *p;   
  2. _TSCHAR *cmdstart;                  /* start of command line to parse */  
  3. int numargs, numchars;   
  4.   
  5. //MAX_PATH 是 260 ,很有趣 ,文件的完整路径最大255 + "." + 后缀(比如exe) + " \0"   
  6. static _TSCHAR _pgmname[ MAX_PATH ];   
  7.   
  8.   
  9. /*   
  10.  * __initmbctable 只能被调用一次,因此会设置一个__mbctype_initialized标记。  
  11.  * __initmbctable 内部会调用_setmbcp 函数去创建一个新的multibyte code page,  
  12.  * 随后置__mbctype_initialized=1  
  13.  */  
  14. if ( __mbctype_initialized == 0 )   
  15.         __initmbctable();   
  16.   
  17.   
  18. /* 将当前进程的exe完整路径复制到_pgmname数组中  
  19.  * 注意GetModuleFileName 是拿不到 程序启动参数args的,它获得仅仅是程序的完整路径而已  
  20.  */  
  21. GetModuleFileName( NULL, _pgmname, sizeof( _pgmname ) / sizeof(_TSCHAR));   
  22. _pgmptr = _pgmname;   
  23.   
  24. /* 如果之前解析出来的_acmdln为空,则采用_pgmptr */  
  25. cmdstart = (*_acmdln == NULCHAR) ? _pgmptr : _acmdln;   
  26.   
  27. /* 计算出 numargs 和 numchars 的大小  */  
  28. parse_cmdline(cmdstart, NULL, NULL, &numargs, &numchars);   
  29.   
  30. /*   
  31.  * 为argv 分配所需的空间   
  32.  * 先是numargs 个指针,前numargs-1 指向路径与参数,最后一个是NULL  
  33.  * 紧接着是numchars 个字符,用来存放numargs-1 个指针所指的内容  
  34.  */  
  35. p = _malloc_crt(numargs * sizeof(_TSCHAR *) + numchars * sizeof(_TSCHAR));   
  36. if (p == NULL)   
  37.         _amsg_exit(_RT_SPACEARG);   
  38.   
  39. /* 为指针P所指向的内存空间里填充argv  */  
  40. parse_cmdline(cmdstart, (char **)p, p + numargs * sizeof(char *), &numargs, &numchars);   
  41.   
  42. /* 至此,argc 与  argv 已经全部现形 */  
  43. __argc = numargs - 1;   
  44. __argv = (char **)p;   

 

numargs 和 numchars 可能比较难以理解。举例来说,现有test0.exe,运行时附加了三个参数 a b c

 

cmdstart 指向的内存地址是0x00141ee0:

这里一共占用了 33 个byte (包含末尾的 00)

 

最后指针 P 指向的内存地址是0x003812c0:

很显然,0x003812c0 开始是指针数组,其中包含了4个有效指针,1个空指针。接着指针数组后的是4个ANSI字符串(红色线框标出),每个字符串以"00"结尾。

所以,0x003812c0 的大小 = 5个指针 + 4个字符串。

numargs   = 5,表示指针数组的大小 ; numchars = 31(0x1F),表示四个字符串一共占用的字节数 。

 

__argc = 5-1

__argv = 0x003812c0

注意,最后返回的时候,需要将numargs-1,毕竟最后1个是空指针。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值