如何在Windows上获取进程的工作目录?

4
如何使用本机API在Windows上获取进程的工作目录(对于另一个进程使用进程句柄或PID)? 我已经查看了进程和线程函数PSAPI函数,但没有找到。也许是WMI?
此外,关于这些主题,PSAPI进程和线程函数有什么关系? 它是否过时?

为什么我觉得“如何获取进程工作目录”这个问题需要插入一个“a”或“当前”的词来彻底澄清其意图? - WhozCraig
@WhozCraig 你说得对,这是我的错误。英语不是我的母语(我是俄罗斯人,住在俄罗斯),所以我并不完全精通它。 - Yury
一种方法是将一个DLL注入到调用GetCurrentDirectory的进程中,以代表你执行该操作。 - Harry Johnston
4个回答

7

对于这个问题,你需要比PSAPI更强大的工具。以下是如何实现(假设为x86架构,并省略错误处理):

ProcessBasicInformation     pbi ;
RTL_USER_PROCESS_PARAMETERS upp ;
PEB   peb ;
DWORD len ;

HANDLE handle = OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid) ;

NtQueryInformationProcess (handle, 0 /*ProcessBasicInformation*/, &pbi,
    sizeof (ProcessBasicInformation), &len) ;

ReadProcessMemory (handle, pbi.PebBaseAddress,    &peb, sizeof (PEB), &len) ;
ReadProcessMemory (handle, peb.ProcessParameters, &upp, sizeof (RTL_USER_PROCESS_PARAMETERS), &len) ;

WCHAR path = new WCHAR[upp.CurrentDirectoryPath.Length / 2 + 1] ;

ReadProcessMemory (handle, upp.CurrentDirectoryPath.Buffer, path, upp.CurrentDirectoryPath.Length, &len) ;

// null-terminate
path[upp.CurrentDirectoryPath.Length / 2] = 0 ;

请注意,除非进程被挂起,否则此方法存在竞争条件。

这是一个非常复杂的工具。难道没有更简单的方法吗? - Yury

2
为了进一步解释Anton的答案,因为您不能像调用普通函数一样简单地调用NtQueryInformationProcess,您必须通过GetModuleHandleW调用Windows ntdll.dll,如下所示:

getcwd.cpp

#include <string>
#include <vector>
#include <cwchar>

#include <windows.h>
#include <winternl.h>

using std::string;
using std::wstring;
using std::vector;
using std::size_t;

// define process_t type
typedef DWORD process_t;

// #define instead of typedef to override
#define RTL_DRIVE_LETTER_CURDIR struct {\
  WORD Flags;\
  WORD Length;\
  ULONG TimeStamp;\
  STRING DosPath;\
}\

// #define instead of typedef to override
#define RTL_USER_PROCESS_PARAMETERS struct {\
  ULONG MaximumLength;\
  ULONG Length;\
  ULONG Flags;\
  ULONG DebugFlags;\
  PVOID ConsoleHandle;\
  ULONG ConsoleFlags;\
  PVOID StdInputHandle;\
  PVOID StdOutputHandle;\
  PVOID StdErrorHandle;\
  UNICODE_STRING CurrentDirectoryPath;\
  PVOID CurrentDirectoryHandle;\
  UNICODE_STRING DllPath;\
  UNICODE_STRING ImagePathName;\
  UNICODE_STRING CommandLine;\
  PVOID Environment;\
  ULONG StartingPositionLeft;\
  ULONG StartingPositionTop;\
  ULONG Width;\
  ULONG Height;\
  ULONG CharWidth;\
  ULONG CharHeight;\
  ULONG ConsoleTextAttributes;\
  ULONG WindowFlags;\
  ULONG ShowWindowFlags;\
  UNICODE_STRING WindowTitle;\
  UNICODE_STRING DesktopName;\
  UNICODE_STRING ShellInfo;\
  UNICODE_STRING RuntimeData;\
  RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[32];\
  ULONG EnvironmentSize;\
}\

// shortens a wide string to a narrow string
static inline string shorten(wstring wstr) {
  int nbytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), NULL, 0, NULL, NULL);
  vector<char> buf(nbytes);
  return string { buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), buf.data(), nbytes, NULL, NULL) };
}

// checks whether process handle is 32-bit or not
static inline bool IsX86Process(HANDLE process) {
  BOOL isWow = true;
  SYSTEM_INFO systemInfo = { 0 };
  GetNativeSystemInfo(&systemInfo);
  if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL)
    return isWow;
  IsWow64Process(process, &isWow);
  return isWow;
}

// helper to open processes based on pid with full debug privileges
static inline HANDLE OpenProcessWithDebugPrivilege(process_t pid) {
  HANDLE hToken;
  LUID luid;
  TOKEN_PRIVILEGES tkp;
  OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
  LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);
  tkp.PrivilegeCount = 1;
  tkp.Privileges[0].Luid = luid;
  tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  AdjustTokenPrivileges(hToken, false, &tkp, sizeof(tkp), NULL, NULL);
  CloseHandle(hToken);
  return OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
}

// helper to get wide character string of pids cwd based on handle
static inline wchar_t *GetCurrentWorkingDirectoryW(HANDLE proc) {
  PEB peb;
  SIZE_T nRead;
  ULONG res_len = 0;
  PROCESS_BASIC_INFORMATION pbi;
  RTL_USER_PROCESS_PARAMETERS upp;
  HMODULE p_ntdll = GetModuleHandleW(L"ntdll.dll");
  typedef NTSTATUS (__stdcall *tfn_qip)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
  tfn_qip pfn_qip = tfn_qip(GetProcAddress(p_ntdll, "NtQueryInformationProcess"));
  NTSTATUS status = pfn_qip(proc, ProcessBasicInformation, &pbi, sizeof(pbi), &res_len);
  if (status) { return NULL; } 
  ReadProcessMemory(proc, pbi.PebBaseAddress, &peb, sizeof(peb), &nRead);
  if (!nRead) { return NULL; }
  ReadProcessMemory(proc, peb.ProcessParameters, &upp, sizeof(upp), &nRead);
  if (!nRead) { return NULL; }
  PVOID buffer = upp.CurrentDirectoryPath.Buffer;
  USHORT length = upp.CurrentDirectoryPath.Length;
  wchar_t *res = new wchar_t[length / 2 + 1];
  ReadProcessMemory(proc, buffer, res, length, &nRead);
  if (!nRead) { return NULL; }
  res[length / 2] = 0;
  return res;
}

// get cwd of pid as a narrow string
string cwd_from_pid(process_t pid) {
  string cwd;
  // open process of pid using full debug privilege
  HANDLE proc = OpenProcessWithDebugPrivilege(pid);
  wchar_t *wcwd = NULL;
  if (IsX86Process(GetCurrentProcess())) {
    if (IsX86Process(proc)) {
      wcwd = GetCurrentWorkingDirectoryW(proc);
    }
  } else {
    if (!IsX86Process(proc)) {
      wcwd = GetCurrentWorkingDirectoryW(proc);
    }
  }
  if (wcwd != NULL) {
    // converts to UTF-8
    cwd = shorten(wcwd);
    // free memory
    delete[] wcwd; 
  }
  // adds trailing slash if one doesn't yet exist or leave empty
  return (cwd.back() == '\\' || cwd.empty()) ? cwd : cwd + "\\";
  // return cwd; // or get the directories completely unmodified
}

// test function (can be omitted)
int main(int argc, char **argv) {
  if (argc == 2) {
    printf("%s", cwd_from_pid(stoul(string(argv[1]), nullptr, 10)).c_str());
    printf("%s", "\r\n");
  } else {
    printf("%s", cwd_from_pid(GetCurrentProcessId()).c_str());
    printf("%s", "\r\n");
  }
  return 0;
}

buildx86.sh

cd "${0%/*}"
g++ getcwd.cpp -o getcwd.exe -std=c++17 -static-libgcc -static-libstdc++ -static -m32

buildx64.sh

cd "${0%/*}"
g++ getcwd.cpp -o getcwd.exe -std=c++17 -static-libgcc -static-libstdc++ -static -m64

请注意,此方法使用的是私有API,因此可能会在没有任何先前文档或通知的情况下发生更改,并因此停止工作。调用进程/可执行文件需要与目标架构相同才能使此方法起作用。否则,它将返回一个空字符串。

如果您知道如何从CreateProcess()读取打印输出,则可以根据目标可执行文件的架构启动适当架构的CLI可执行文件。这意味着依赖于多个可执行文件来构建项目,这会减慢速度,但根据您的用例,它仍然可以接受。显然,除非您每隔一段时间创建一个新进程进行此操作(不要太频繁),否则这并不理想,不应过多地减慢程序速度。


-4
"

"." 总是代表当前目录。我认为它会起作用。

"

1
抱歉问题不够精确,我指的是“另一个进程”。 - Yury

-4
将以下内容翻译成中文,与编程有关,仅返回翻译后的文本:

将您的问题分解为多个问题:

如何在Windows上获取进程工作目录?

这不是指远程进程,而是指当前进程:

NtCurrentPeb()->ProcessParameters->CurrentDirectory.DosPath

如何使用本地API(对于使用进程句柄或PID的另一个进程)在Windows上获取进程工作目录?
这可以通过几种已知和未知(未记录)的方法和函数实现。我现在将不介绍未记录的方法,只提供已记录的函数。

问题是如何获取另一个进程的当前目录,而不是当前进程的目录。 - Harry Johnston
@HarryJohnston 在这种情况下(我没有注意到那部分),我会用更好的答案更新我的回答。不过,周围还是有太多丑陋的代码 :) - Mecanik
@Mecanik,你能更新一下你的答案吗? - Zeltrax

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接