先撇開_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:

這裡一共占用了 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個是空指針。