訓練
模組
Troubleshoot physical failures on Windows clients - Training
The module examines the methods for identifying and troubleshooting issues related to the device's physical hardware.
使用新的64位安全 Windows 資料類型。
本檔稍早所述的新 64 位安全資料類型定義於 Basetsd.h 中。 此頭檔包含在 Ntdef.h 中,其包含在 Ntddk.h、Wdm.h 和 Ntifs.h 中。
請仔細使用平臺編譯程序宏。
下列假設已不再有效:
#ifdef _WIN32 // 32-bit Windows code
...
#else // 16-bit Windows code
...
#endif
不過,64 位編譯程式會定義_WIN32以提供回溯相容性。
此外,下列假設已不再有效:
#ifdef _WIN16 // 16-bit Windows code
...
#else // 32-bit Windows code
...
#endif
在此情況下,else 子句可以代表_WIN32或_WIN64。
搭配 printf 和 wsprintf 使用適當的格式規範。
使用 %p 以十六進位列印指標。 這是列印指標的最佳選擇。
注意 未來版本的 Visual C++將支援 %I 來列印多型資料。 它會將值視為 64 位 Windows 中的 64 位,以及 32 位 Windows 中的 32 位。 Visual C++也會支援 %I64 來列印 64 位的值。
瞭解您的地址空間。
例如,請勿盲目假設如果位址是核心位址,則必須設定其高階位。 若要取得最低的系統位址,請使用 MM_LOWEST_SYSTEM_ADDRESS 宏。
執行未簽署和已簽署的作業時請小心。
請考慮下列事項:
ULONG x;
LONG y;
LONG *pVar1;
LONG *pVar2;
pVar2 = pVar1 + y * (x - 1);
發生問題的原因是 x 為不帶正負號,這會使整個表達式不帶正負號。 除非 y 是負數,否則這可正常運作。 在此情況下, y 會轉換成不帶正負號的值,並使用32位有效位數、縮放和新增至 pVar1 來評估表達式。 在 64 位 Windows 上,這個 32 位無符號負號數位會變成較大的 64 位正數,這會產生錯誤的結果。 若要修正此問題,請將 x 宣告為帶正負號的值,或明確地在表達式中將它類型轉換成 LONG。
使用十六進位常數和不帶正負號的值時,請小心。
64 位系統上的下列判斷提示不正確:
~((UINT64)(PAGE_SIZE-1)) == (UINT64)~(PAGE_SIZE-1)
PAGE_SIZE = 0x1000UL // Unsigned long - 32 bits
PAGE_SIZE - 1 = 0x00000fff
LHS 運算式:
// Unsigned expansion(UINT64)(PAGE_SIZE -1 ) = 0x0000000000000fff
~((UINT64)(PAGE_SIZE -1 )) = 0xfffffffffffff000
RHS 運算式:
~(PAGE_SIZE-1) = 0xfffff000
(UINT64)(~(PAGE_SIZE - 1)) = 0x00000000fffff000
因此:
~((UINT64)(PAGE_SIZE-1)) != (UINT64)(~(PAGE_SIZE-1))
請小心 NOT 作業。
請考慮下列事項:
UINT_PTR a; ULONG b;
a = a & ~(b - 1);
問題是 ~(b~1) 會產生 0x0000 0000 xxxx xxxx ,而不是0xFFFF FFFF xxxx xxxx。 編譯程式不會偵測到此狀況。 若要修正此問題,請變更程序代碼,如下所示:
a = a & ~((UINT_PTR)b - 1);
計算緩衝區大小時請小心。
請考慮下列事項:
len = ptr2 - ptr1
/* len could be greater than 2**32 */
將指標 轉換成 PCHAR 以進行指標算術。
注意 如果 len 宣告為 INT 或 ULONG,這會產生編譯程式警告。 緩衝區大小,即使正確計算,仍可能超過ULONG的容量。
避免使用計算或硬式編碼指標位移。
使用 結構時,請盡可能使用 FIELD_OFFSET 宏來判斷結構成員的位移。
避免使用硬式編碼指標或句柄值。
請勿將硬式編碼指標或句柄,例如 (HANDLE)0xFFFFFFFF傳遞至 ZwCreateSection 之類的例程。 請改用常數,例如INVALID_HANDLE_VALUE,這些常數可以定義為每個平臺提供適當的值。
請注意,在 64 位 Windows 中,0xFFFFFFFF與 -1 不同。
例如:
DWORD index = 0;
CHAR *p;
// if (p[index-1] == '0') causes access violation on 64-bit Windows!
在 32 位電腦上:
p[index-1] == p[0xffffffff] == p[-1]
在 64 位電腦上:
p[index-1] == p[0x00000000ffffffff] != p[-1]
將索引類型從 DWORD 變更為DWORD_PTR,即可避免此問題。
請小心多型介面。
請勿建立可接受多型數據之 DWORD 類型參數的函式(或其他固定有效位數類型)。 如果數據可以是指針或整數值,則參數類型應該 UINT_PTR 或 PVOID,而不是 DWORD。
例如,請勿建立可接受類型為 DWORD 值的例外狀況參數陣列的函式。 陣列應該是DWORD_PTR值的陣列。 因此,陣列元素可以保存位址或32位整數值。 一般規則是,如果原始類型是 DWORD ,而且必須是指針寬度,請將它 轉換成DWORD_PTR 值。 這就是為什麼原生 Win32 類型有對應的指標精確度類型。 如果您有以多型方式使用 DWORD、 ULONG 或其他 32 位類型的程式代碼(也就是,您真的想要參數或結構成員保留位址),請使用 UINT_PTR 取代目前的類型。
呼叫具有指標 OUT 參數的函式時請小心。
請勿執行此動作:
void GetBufferAddress(OUT PULONG *ptr);
{
*ptr=0x1000100010001000;
}
void foo()
{
ULONG bufAddress;
//
// This call causes memory corruption.
//
GetBufferAddress((PULONG *)&bufAddress);
}
將 bufAddress 類型傳送至 (PULONG *) 可防止編譯程式錯誤。 不過,GetBufferAddress 會將 64 位值寫入至 &bufAddress 的記憶體位置。 因為 bufAddress 只是 32 位值,因此 bufAddress 後面的 32 位會遭到覆寫。 這是一個非常微妙,難以找到的錯誤。
請勿將指標 轉換成 INT、 LONG、 ULONG 或 DWORD。
如果您必須轉換指標以測試某些位、設定或清除位,或操作其內容,請使用 UINT_PTR 或 INT_PTR 類型。 這些類型是調整為 32 位和 64 位 Windows 指標大小的整數類型(例如 32 位 Windows 的 ULONG 和 64 位 Windows 的 _int64 )。 例如,假設您正在移植下列程式代碼:
ImageBase = (PVOID)((ULONG)ImageBase | 1);
在移植程式中,您會變更程序代碼,如下所示:
ImageBase = (PVOID)((ULONG_PTR)ImageBase | 1);
在適當的情況下使用 UINT_PTR 和 INT_PTR (如果您不確定是否需要它們,則使用它們時不會造成傷害。 請勿將您的指標轉換成ULONG、LONG、INT、UINT或 DWORD 類型。
注意HANDLE 定義為 void \,因此將 *HANDLE 值類型傳送至 ULONG 值以測試、設定或清除低兩個位是程式設計錯誤。
使用 PtrToLong 和 PtrToUlong 來截斷指標。
如果您必須截斷 32 位值的指標,請使用 PtrToLong 或 PtrToUlong 函式(定義於 Basetsd.h 中)。 此函式會在呼叫期間停用指標截斷警告。
請仔細使用這些函式。 使用下列其中一個函式截斷指標變數之後,絕不會將產生的 LONG 或 ULONG 轉換回指標。 這些函式會截斷位址的上層 32 位,這通常需要存取原本由指標參考的記憶體。 在不仔細考慮的情況下使用這些函式會導致程式代碼脆弱。
仔細檢查數據結構指標的所有用法。
以下是常見的問題區域:
例如:
struct xx {
DWORD NumberOfPointers;
PVOID Pointers[1];
};
64 位 Windows 中的下列配置不正確,因為編譯程式會以額外的 4 個字節填補 結構,以符合 8 位元組的對齊需求:
malloc(sizeof(DWORD)+100*sizeof(PVOID));
以下說明如何正確執行:
malloc(FIELD_OFFSET(struct xx, Pointers) +100*sizeof(PVOID));
使用 TYPE_ALIGNMENT 宏。
TYPE_ALIGNMENT宏會傳回目前平臺上指定數據類型的對齊需求。 例如:
TYPE_ALIGNMENT(KFLOATING_SAVE) == 4 on x86, 8 on Itanium
TYPE_ALIGNMENT(UCHAR) == 1 everywhere
例如,如下列程序代碼:
ProbeForRead(UserBuffer, UserBufferLength, sizeof(ULONG));
當變更為 下列情況時,會變得更可攜:
ProbeForRead(UserBuffer, UserBufferLength, TYPE_ALIGNMENT(ULONG));
監看公用核心結構中的數據類型變更。
例如, IO_STATUS_BLOCK 結構中的 [資訊 ] 字段現在的類型 為 ULONG_PTR。
使用結構封裝指示詞時請小心。
在 64 位 Windows 上,如果數據結構不對齊,則操作結構的例程,例如 RtlCopyMemory 和 memcpy,將不會有錯誤。 相反地,它們會引發例外狀況。 例如:
#pragma pack (1) /* also set by /Zp switch */
struct Buffer {
ULONG size;
void *ptr;
};
void SetPointer(void *p) {
struct Buffer s;
s.ptr = p; /* will cause alignment fault */
...
}
您可以使用 UNALIGNED 宏來修正此問題:
void SetPointer(void *p) {
struct Buffer s;
*(UNALIGNED void *)&s.ptr = p;
}
不幸的是,在 以Itanium 為基礎的處理器上使用 UNALIGNED 宏非常昂貴。 更好的解決方案是將64位值和指標放在結構的開頭。
注意 如果可能的話,請避免在同一個頭檔中使用不同的封裝層級。
準備 64 位 Windows (使用者模式應用程式移植指南)
訓練
模組
Troubleshoot physical failures on Windows clients - Training
The module examines the methods for identifying and troubleshooting issues related to the device's physical hardware.