• 记一次CurrentDirectory导致的问题


    现在项目里需要实现一个功能如下:

    A.exe把B.exe复制到临时目录,然后A.exe退出,B.exe负责把A.exe所在的整个目录删除。

    实现:

    A.exe用CreateProcess创建B.exe时,把所在目录作为命令行参数传递到B.exe。然后B.exe中对这个目录进行递归删除。

    A.exe创建进程的代码。大概如下

     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).
    "), GetLastError() );
    15             return 1;
    16         }

    假设现在A.exe所在的目录为C: est,B.exe在C: est2,然后通过以上代码创建了B.exe。

    开始执行递归删除文件及目录,删除到最后的剩下C: est这个空目录时。发现这个文件夹没有被删除掉,RemoveDirectoryW返回FALSE,error code 为ERROR_SHARING_VIOLATION 32 (0x20),用Procexp查看进程打开句柄,发现句柄列表中竟然有这个路径。

    第一直觉是FindFile时有句柄没关闭,于是马上去检查FindFile部分的代码,并没有任何问题。

    原来的删除文件夹是使用的SHFileOperation去删除,却没有这个问题。再次用SHFileOperation去删除,用Procexp看句柄,虽然也是占用着,但是却可以删除掉。

    究竟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也会占用该文件句柄,所以如果要对该目录进行删除的话,就需要先把当前目录设成和这个没有关系的其他目录。

  • 相关阅读:
    软件体系结构:二维分层、模块化和开放平台
    Unity手游之路<七>角色控制器
    Unity手游之路<四>3d旋转-四元数,欧拉角和变幻矩阵
    Unity手游之路<三> 基于Unity+Java的聊天室源码
    Unity手游之路<二>Java版服务端使用protostuff简化protobuf开发
    Unity手游之路<一>C#版本Protobuf
    Unity手游之路<八>自动寻路Navmesh之入门
    Unity手游之路<九>自动寻路Navmesh之高级主题
    Unity手游之路<十>自动寻路Navmesh之跳跃,攀爬,斜坡
    raise EnvironmentError("%s not found" % (mysql_config.path,)) EnvironmentError: mysql_config not found 解决办法
  • 原文地址:https://www.cnblogs.com/hwangbae/p/3469921.html
Copyright © 2020-2023  润新知