天天看點

從Entry Point到main函數調用(4)

先撇開_ioinit()不談,IO有點兒麻煩,待有空再去挖掘這些東西。

GetCommandLineA

該函數的彙編代碼短得令人發指。

7C812FBD    mov     eax, dword ptr [7C8855F4]
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 的實作:

LPVOID __crtGetEnvironmentStringsA( VOID  )
{
        static int f_use = 0;
        wchar_t *wEnv = NULL;
        wchar_t *wTmp;
        char *aEnv = NULL;
        char *aTmp;
        int nSizeW;
        int nSizeA;

        /*
         * Look for 'preferred' flavor. Otherwise use available flavor.
         * Must actually call the function to ensure it's not a stub.
         */
        if ( 0 == f_use )
        {
            if ( NULL != (wEnv = GetEnvironmentStringsW()) )
                f_use = USE_W;

            else if ( NULL != (aEnv = GetEnvironmentStringsA()) )
                f_use = USE_A;

            else
                return NULL;
        }
        
        /* Use "W" version */
        if (USE_W == f_use)
        {        
                 ……        
        }

        /* Use "A" version */
        if ( USE_A == f_use )
        {
                 ……        
        }
        
        return NULL;
}      

可見實質上,__crtGetEnvironmentStringsA 函數依然是調用了win API,可能是:

GetEnvironmentStringsW  或 GetEnvironmentStringsA

有人也許奇怪,為什麼__crtGetEnvironmentStringsA 不直接去調用GetEnvironmentStringsA 呢?原因在于,即使是用GetEnvironmentStringsA ,所擷取的也并非是 ANSI 的環境變量。

摘自MSDN: http://msdn.microsoft.com/en-us/library/ms683187%28VS.85%29.aspx 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的處理。

/* 
     這裡将指針wTmp 移動到 environment block 的末尾處 
     特别要注意,wTmp 是一個 wchar_t 類型的指針,是以 ++ 是移動兩個位元組
     例如 environment block 的末尾是:
         45 00   64 00   69 00   74 00   00 00   00(移動到此處)
         E       d       i       t       \0      00
*/
wTmp = wEnv;
while ( *wTmp != L'\0' ) {
	if ( *++wTmp == L'\0' )
		wTmp++;
}

/* 求出environment block 中wide char的個數 +1 */
nSizeW = wTmp - wEnv + 1;

/* 該API傳回轉化為byte字元的位元組數,其實nSizeA 和 nSizeW  數值相同 */
nSizeA = WideCharToMultiByte( CP_ACP,0,wEnv,nSizeW,NULL,0,NULL,NULL );

/* 配置設定一塊大小為nSizeA 的記憶體 */
if ( (nSizeA == 0) || ((aEnv = (char *)_malloc_crt(nSizeA)) == NULL) )
{
	FreeEnvironmentStringsW( wEnv );
	return NULL;
}

/* 這裡将WideChar 轉化為 byte ,并且存放到剛配置設定的記憶體區域中 */
if ( !WideCharToMultiByte( CP_ACP,0,wEnv,nSizeW,aEnv,nSizeA,NULL,NULL ) )
{
	_free_crt( aEnv );
	aEnv = NULL;
}

FreeEnvironmentStringsW( wEnv );
return aEnv;
      

這裡的實作與我想象中有些差别,不太清楚為什麼MS的coder 會兩次調用WideCharToMultiByte 函數對environment block 進行轉化。

_setargv

該函數負責為C 程式的"argc" 和 "argv" 參數作好準備,_setargv 的源代碼位于 STDARGV.c 檔案。它會讀取 _acmdln(存放了之前GetCommandLineA傳回的指針)來通路command line,然後最關鍵的步驟是通過調用parse_cmdline 函數來解析command line,并且建立argv 數組。

這裡僅分析_setargv 中最為核心的代碼行。

_TSCHAR *p;
_TSCHAR *cmdstart;                  /* start of command line to parse */
int numargs, numchars;

//MAX_PATH 是 260 ,很有趣 ,檔案的完整路徑最大255 + "." + 字尾(比如exe) + " \0"
static _TSCHAR _pgmname[ MAX_PATH ];


/* 
 * __initmbctable 隻能被調用一次,是以會設定一個__mbctype_initialized标記。
 * __initmbctable 内部會調用_setmbcp 函數去建立一個新的multibyte code page,
 * 随後置__mbctype_initialized=1
 */
if ( __mbctype_initialized == 0 )
        __initmbctable();


/* 将目前程序的exe完整路徑複制到_pgmname數組中
 * 注意GetModuleFileName 是拿不到 程式啟動參數args的,它獲得僅僅是程式的完整路徑而已
 */
GetModuleFileName( NULL, _pgmname, sizeof( _pgmname ) / sizeof(_TSCHAR));
_pgmptr = _pgmname;

/* 如果之前解析出來的_acmdln為空,則采用_pgmptr */
cmdstart = (*_acmdln == NULCHAR) ? _pgmptr : _acmdln;

/* 計算出 numargs 和 numchars 的大小  */
parse_cmdline(cmdstart, NULL, NULL, &numargs, &numchars);

/* 
 * 為argv 配置設定所需的空間 
 * 先是numargs 個指針,前numargs-1 指向路徑與參數,最後一個是NULL
 * 緊接着是numchars 個字元,用來存放numargs-1 個指針所指的内容
 */
p = _malloc_crt(numargs * sizeof(_TSCHAR *) + numchars * sizeof(_TSCHAR));
if (p == NULL)
        _amsg_exit(_RT_SPACEARG);

/* 為指針P所指向的記憶體空間裡填充argv  */
parse_cmdline(cmdstart, (char **)p, p + numargs * sizeof(char *), &numargs, &numchars);

/* 至此,argc 與  argv 已經全部現形 */
__argc = numargs - 1;
__argv = (char **)p; 
      

numargs 和 numchars 可能比較難以了解。舉例來說,現有test0.exe,運作時附加了三個參數 a b c

cmdstart 指向的記憶體位址是0x00141ee0:

從Entry Point到main函數調用(4)

這裡一共占用了 33 個byte (包含末尾的 00)

最後指針 P 指向的記憶體位址是0x003812c0:

從Entry Point到main函數調用(4)

很顯然,0x003812c0 開始是指針數組,其中包含了4個有效指針,1個空指針。接着指針數組後的是4個ANSI字元串(紅色線框标出),每個字元串以"00"結尾。

是以,0x003812c0 的大小 = 5個指針 + 4個字元串。

numargs   = 5,表示指針數組的大小 ; numchars = 31(0x1F),表示四個字元串一共占用的位元組數 。

__argc = 5-1

__argv = 0x003812c0

注意,最後傳回的時候,需要将numargs-1,畢竟最後1個是空指針。