在Windows上获取进程的可执行文件路径

来源:互联网 发布:淘宝禁止出售有毒化学 编辑:程序博客网 时间:2024/06/01 08:39

在Windows上根据进程PID获取其可执行文件的路径,是一个常见的问题。通常我们采用广为人知的API——GetModuleFileNameEx。此函数兼容性极佳,最低支持版本为Windows2000,在许多场合它都工作正常。于是我也在代码中理所应当的使用了它。

忽然某时,我在日志中发现这厮留了一句Error:GetModuleFileNameEx fail, error code is 299。MSDN告诉我这句:Only part of a ReadProcessMemory or WriteProcessMemory request was completed。亲,这是什么意思?MSDN的错误解释有时搞得人真是一头雾水,还是先问问度娘(度娘不行了,再问谷歌)。查得,当我们调用GetModuleFileNameEx函数时,为了获得指定进程的全路径,它内部需要访问进程的PEB头。而64位进程的PEB头地址是保存在64位长度的地址中的,32位进程是保存在32位长度的地址中的,当32位进程访问64位进程的PEB头的时候,采取的是截断访问——只取低32位地址。于是GetModuleFileNameEx就有时正确,有时错误了。日志的主人刚好是个32位进程,也正好跑在了64位系统上。这个Error就这么被触发了。如果编译成64位进程,问题当然就不会存在,可是现实就是不那么让人如愿。
信念——问题的解决是必须的、一定的,只是时间长短而已。
既然上面那条路不通了,就得找另外的路。我不知道,我忘了我是怎么找到NtQueryInformationProcess这个函数的。这是个内核函数,VC2008的MSDN用醒目的字体写下NtQueryInformationProcess may be altered or unavailable in future versions of Windows。所以用起来总是让人有些担心。但是我还是用他了,因为我在需要的平台上验证了他的正确性,x86、x64都没问题。我需要着重说一下的是,这个函数获取的进程的可执行文件的路径的驱动器部分是个内核使用的名字,就像\Device\HarddiskVolume1,于是就得找办法把它转换成盘符。不巧的是Windows应用层并没有直接把内核路径转换成应用层常见的路径的函数。网上搜了一下,大家都是利用GetLogicalDriveStringsW获取所有盘符,然后用QueryDosDeviceW把盘符对应的内核设备名得出来,最后把NtQueryInformationProcess得到的路径名中的驱动器部分替换掉。好像没有其它办法,那么就这样干吧。自测,情况良好。提交之。
这个方法良好的运行了一段时间,又有一处也是这个问题,本来我应该直接把代码复制过去,但是我在无意中发现了另外的解决办法:GetProcessImageFileName。这个函数也可以良好的获取32位进程和64位进程的路径,无论使用它的是32位进程还是64位进程,它所返回的路径的驱动器部分也跟NtQueryInformationProcess一样,是个内核设备名,也需要转换。这个函数是应用层的,没有unavailable的风险,唯一的缺憾就是它的最低支持平台是Windows2003,不过对于我来说已经足够了。于是这次我使用了这个函数。另外在MSDN论坛有人这样建议(看图比较醒目,直接):

后面两个函数,虽然很好,但是要求太高——平台最低是Windows2008。
这次的方法也是良好的运行了一段时间。
又是忽然某时,日志出现了Error,是由于前面某处获取到的进程路径名为空。此时已经严重影响到了产品业务,我必须细细的看。没有错误,只是获取到的路径为空?在整个获取路径过程中,如果获取的那几个函数出问题了,一定会留下“罪证”——错误日志,但是现在没有,那么问题只能出现在转换路径名处。之前我并没有认为这个地方会出问题,所以日志不多。还好问题环境一直存在,并且留了快照,可以让我随意折腾。果断丰富日志,调试。后来发现是QueryDosDeviceW出了问题。在问题机子上,用WinObj工具查看符号链接,C盘链接到\Device\HarddiskVolume1,这也是WinObj获取到的;E盘链接到\Device\HarddiskDmVolumes\Ju-6eb2196e4450Dg0\Volume1,QueryDosDeviceW获取到的也是这个,可是这个又链接到了\Device\HarddiskDmVolumes\PhysicalDmVolumes\BlockVolume1,下面再没有链接了,而用GetProcessImageFileName获取到的驱动器部分是后者,于是没办法替换掉,因为无法匹配到任何一个由QueryDosDeviceW获取到的内核设备名。然后由于我的特殊处理,会认为这种情况获取失败了,返回一个空字符串路径,接着上面的Error就被触发了。那么C盘和E盘有什么区别呢?


啊哈,很明显的区别:磁盘1是动态磁盘。问题很可能就在这。于是我又在C盘下和E盘下作了实验,发现在C盘下的进程,都能按照以前的方法获取,E盘下的都不行。多加了几个动态磁盘,也作了实验。然后确定了问题是动态磁盘引起的。网上查了会动态磁盘,没什么有用的信息。也查不到如何解决这种问题。过了好久,感觉不会再有办法了,比较郁闷。由于这个问题对于我们的产品来说可以规避掉,于是就暂时规避了。
过了些时日,又出现了同样的反馈,然后这个问题的严重性被提升了——必须解决。
还是一个信念——问题的解决是必须的、一定的,只是时间长短而已。
QueryDosDeviceW这条路看来是走不通了,那么GetProcessImageFileName也就不行了。想想还有其它办法吗?自己的脑子不够用,难道别人的脑子也不够用吗?查呗。
在微软的某个网页内,一个评论让我感觉到得救了,那人说可以用wmi获取进程路径。赶紧在命令行下用wmic命令测试了下,工作正常,于是又搭了一个问题环境,测试,OK,可以正常工作,完全不受动态磁盘什么的影响。立马写了一段代码,通过COM接口使用wmi,测试,也OK。Good,Bug即将Fixed。
最后的修复方案是:先用GetModuleFileNameEx获取,若失败了,再用GetProcessImageFileName,若还是失败,再用wmi。
至此,问题解决。


在上面的过程中,还有几个问题,我并没有考虑,到写这篇文章的时候才意识到:
1. Wmi可以做很多事,而且看起来更方便了,而这些事都有对应的API可以完成,但是到现在为止,很少见到有人广泛使用wmi——也许是我见到的代码很少——都用的是API,不知道wmi有没有什么不好的地方。我知道wmi也才不多时,并没有深入去了解。
2. NtQueryInformationProcess是否存在GetProcessImageFileName的问题?

原创粉丝点击