如今項目裏須要實現一個功能以下:cookie
A.exe把B.exe複製到臨時目錄,而後A.exe退出,B.exe負責把A.exe所在的整個目錄刪除。函數
實現:this
A.exe用CreateProcess建立B.exe時,把所在目錄做爲命令行參數傳遞到B.exe。而後B.exe中對這個目錄進行遞歸刪除。atom
A.exe建立進程的代碼。大概以下spa
1 if( !::CreateProcessW( 2 nullptr, // No module name (use command line) 3 pathTmp.m_strPath.GetBuffer(pathTmp.m_strPath.GetLength()), // Command line 4 nullptr, // Process handle not inheritable 5 nullptr, // Thread handle not inheritable 6 FALSE, // Set handle inheritance to FALSE 7 0, // No creation flags 8 nullptr, // Use parent's environment block 9 nullptr, // Use parent's starting directory 10 &si, // Pointer to STARTUPINFO structure 11 &pi ) // Pointer to PROCESS_INFORMATION structure 12 ) 13 { 14 _DTL_LOG_OUT(_T("CreateProcess failed (%d).\n"), GetLastError() ); 15 return 1; 16 }
假設如今A.exe所在的目錄爲C:\test,B.exe在C:\test2,而後經過以上代碼建立了B.exe。命令行
開始執行遞歸刪除文件及目錄,刪除到最後的剩下C:\test這個空目錄時。發現這個文件夾沒有被刪除掉,RemoveDirectoryW返回FALSE,error code 爲ERROR_SHARING_VIOLATION 32 (0x20),用Procexp查看進程打開句柄,發現句柄列表中居然有這個路徑。調試
第一直覺是FindFile時有句柄沒關閉,因而立刻去檢查FindFile部分的代碼,並無任何問題。code
原來的刪除文件夾是使用的SHFileOperation去刪除,卻沒有這個問題。再次用SHFileOperation去刪除,用Procexp看句柄,雖然也是佔用着,可是卻能夠刪除掉。blog
究竟SHFileOperation是怎麼處理的呢?遞歸
下面是個人分析過程:
先在Windbg裏看看吧。附加到B進程。而後
bp SHELL32!SHFileOperationW,
用uf SHELL32!SHFileOperationW進行反彙編,而後單步跟蹤,發現最終是調用了一個
SHELL32!SHFileOperationW+0x168: 7744d321 8b1d70193a77 mov ebx,dword ptr [SHELL32!_imp__SetThreadExecutionState (773a1970)] 7744d327 6801000080 push 80000001h 7744d32c ffd3 call ebx 7744d32e 56 push esi 7744d32f e8c5f7ffff call SHELL32!MoveCopyDriver (7744caf9)
F11繼續步入到MoveCopyDriver
跟到下面這塊
1 SHELL32!MoveCopyDriver+0x3c2: 2 7744cebb 6a00 push 0 3 7744cebd 8d8514f9ffff lea eax,[ebp-6ECh] 4 7744cec3 50 push eax 5 7744cec4 8d8564fbffff lea eax,[ebp-49Ch] 6 7744ceca 50 push eax 7 7744cecb 6804010000 push 104h 8 7744ced0 8d86fc020000 lea eax,[esi+2FCh] 9 7744ced6 50 push eax 10 7744ced7 53 push ebx 11 7744ced8 56 push esi 12 7744ced9 e8c7d6ffff call SHELL32!EnterDir_Move (7744a5a5) 13 7744cede e9bb000000 jmp SHELL32!MoveCopyDriver+0x4a5 (7744cf9e) 14 15 SHELL32!MoveCopyDriver+0x3ea: 16 7744cee3 53 push ebx 17 7744cee4 56 push esi 18 7744cee5 e884acffff call SHELL32!LeaveDir_Delete (77447b6e) 19 7744ceea e9af000000 jmp SHELL32!MoveCopyDriver+0x4a5 (7744cf9e)
前面沒發現什麼特殊的地方。當執行過SHELL32!LeaveDir_Delete時。watch窗口裏@$peb發現CurrentDirectory發生改變。因而果斷的把這個函數反彙編
1 0:000> uf SHELL32!LeaveDir_Delete 2 SHELL32!LeaveDir_Delete: 3 77447b6e 8bff mov edi,edi 4 77447b70 55 push ebp 5 77447b71 8bec mov ebp,esp 6 77447b73 57 push edi 7 77447b74 8b7d0c mov edi,dword ptr [ebp+0Ch] 8 77447b77 57 push edi 9 77447b78 ff153c213a77 call dword ptr [SHELL32!_imp__PathIsRootW (773a213c)] 10 77447b7e 85c0 test eax,eax 11 77447b80 7404 je SHELL32!LeaveDir_Delete+0x18 (77447b86) 12 13 SHELL32!LeaveDir_Delete+0x14: 14 77447b82 33c0 xor eax,eax 15 77447b84 eb2a jmp SHELL32!LeaveDir_Delete+0x42 (77447bb0) 16 17 SHELL32!LeaveDir_Delete+0x18: 18 77447b86 56 push esi 19 77447b87 57 push edi 20 77447b88 e883e8ffff call SHELL32!AvoidCurrentDirectory (77446410) 21 77447b8d 57 push edi 22 77447b8e e8cf8f0000 call SHELL32!Win32RemoveDirectory (77450b62)
這裏先判斷了是否爲根目錄,否就調用SHELL32!AvoidCurrentDirectory,而後才刪除目錄,從名字上這個函數應該就是我要找的了。繼續反彙編該函數
1 0:000> uf SHELL32!AvoidCurrentDirectory 2 SHELL32!AvoidCurrentDirectory: 3 77446410 8bff mov edi,edi 4 77446412 55 push ebp 5 77446413 8bec mov ebp,esp 6 77446415 81ec0c020000 sub esp,20Ch 7 7744641b a108c55977 mov eax,dword ptr [SHELL32!__security_cookie (7759c508)] 8 77446420 56 push esi 9 77446421 8b7508 mov esi,dword ptr [ebp+8] 10 77446424 8945fc mov dword ptr [ebp-4],eax 11 77446427 8d85f4fdffff lea eax,[ebp-20Ch] 12 7744642d 50 push eax 13 7744642e 6804010000 push 104h 14 77446433 ff15a4193a77 call dword ptr [SHELL32!_imp__GetCurrentDirectoryW (773a19a4)] 15 77446439 56 push esi 16 7744643a 8d85f4fdffff lea eax,[ebp-20Ch] 17 77446440 50 push eax 18 77446441 ff15401a3a77 call dword ptr [SHELL32!_imp__lstrcmpiW (773a1a40)] 19 77446447 85c0 test eax,eax 20 77446449 5e pop esi 21 7744644a 751a jne SHELL32!AvoidCurrentDirectory+0x56 (77446466) 22 23 SHELL32!AvoidCurrentDirectory+0x3c: 24 7744644c 8d85f4fdffff lea eax,[ebp-20Ch] 25 77446452 50 push eax 26 77446453 ff15f4203a77 call dword ptr [SHELL32!_imp__PathRemoveFileSpecW (773a20f4)] 27 77446459 8d85f4fdffff lea eax,[ebp-20Ch] 28 7744645f 50 push eax 29 77446460 ff15a8193a77 call dword ptr [SHELL32!_imp__SetCurrentDirectoryW (773a19a8)]
終於看到了真相。這個函數判斷當前目錄是否和刪除的目錄相等,若是相等,則將當前目錄設置爲上一級目錄。
因而我想到了刪除失敗可能和當前目錄有關係,在MSDN裏看到關於CreateProcess關於lpCurrentDirectory參數的介紹,若是爲NULL,則把當前目錄做爲子進程的當前目錄,此時基本能夠肯定,是CurrentDirectory佔用了文件句柄,致使目錄沒法刪除,那麼解決辦法已經很明顯,建立子進程時把lpCurrentDirectory設置成子進程文件的目錄便可。
最後爲了驗證。在nt4的源碼裏找到SHFileOperation的實現。SHFileOperation中使用了一個COPY_STATE結構體來存儲各類設置信息。
1 typedef struct { 2 int nSourceFiles; 3 LPTSTR lpCopyBuffer; // global file copy buffer 4 UINT uSize; // size of this buffer 5 FILEOP_FLAGS fFlags; // from SHFILEOPSTRUCT 6 UINT wFunc; // FO_ function type 7 HWND hwndProgress; // dialog/progress window 8 HWND hwndCaller; // window to do stuff on 9 HWND hwndDlgParent; // parent window for message boxes 10 CONFIRM_DATA cd; // confirmation stuff 11 DWORD dwStartTime; // start time to implement progress timeout 12 DWORD dwShowTime; // when did the dialog get shown 13 14 LPUNDOATOM lpua; // the undo atom that this file operation will make 15 BOOL fNoConfirmRecycle; 16 BOOL bAbort; 17 BOOL fNonCopyProgress; 18 BOOL fMerge; // are we doing a merge of folders 19 20 // folowing fields are used for giving estimated time for completion 21 // feedback to the user during longer than MINTIME4FEEDBACK operations 22 BOOL fShowTime; // 23 DWORD dwTimeLeft; // last reported time left, necessary for histerisis 24 DWORD dwBytesLeft; 25 DWORD dwPreviousTime; // calculate transfer rate 26 DWORD dwBytesRead; // Bytes read in the interval dwNewTime-dwPreviousTime 27 DWORD dwBytesPerSec; 28 LPCTSTR lpszProgressTitle; 29 LPSHFILEOPSTRUCT lpfo; 30 31 BOOL fMove; 32 BOOL fInitialize; 33 WIN32_FIND_DATA wfd; 34 35 } COPY_STATE, *LPCOPY_STATE;
SHFileOperation會將SHFILEOPSTRUCT轉換成COPY_STATE,而後調用一個MoveCopyDriver的函數,將COPY_STATE傳到MoveCopyDriver中,
其中呢會調用一個AvoidCurrentDirectory的函數,這個函數實現以下
1 // make sure we aren't operating on the current dir to avoid 2 // ERROR_CURRENT_DIRECTORY kinda errors 3 4 void AvoidCurrentDirectory(LPCTSTR p) 5 { 6 TCHAR szTemp[MAX_PATH]; 7 8 GetCurrentDirectory(ARRAYSIZE(szTemp), szTemp); 9 if (lstrcmpi(szTemp, p) == 0) 10 { 11 DebugMsg(DM_TRACE, TEXT("operating on current dir(%s), cd .."), p); 12 PathRemoveFileSpec(szTemp); 13 SetCurrentDirectory(szTemp); 14 } 15 }
基本上和以前經過windbg調試看到的差不了多少。
總結:進程的CurrentDirectory也會佔用該文件句柄,因此若是要對該目錄進行刪除的話,就須要先把當前目錄設成和這個沒有關係的其餘目錄。