0%

icebox服务启动失败问题定位记录

通过IceGridGUI启动某个icebox服务时,弹出如下图的错误对话框,启动服务失败。提示找不到icebox.exe程序,但是icebox.exe是存在的,并且在path中,问题非常诡异。下面详细记录问题定位的过程。

背景知识

  • IceGrid的架构中包含icegridregistry.exe和icegridnode.exe两个进程,icegridnode.exe负责启动具体的服务,当从IceGridGUI中start某个服务时, 实际上是调用icegridnode.exe接口,由icegridnode.exe负责启动服务,如果服务是icebox服务,就会根据application.xml中配置的icebox路径运行 icebox,由icebox加载具体服务dll。
  • application.xml中配置的icebox路径,一般配置的icebox程序名,不包含完整路径,icebox程序名根据不同的操作系统和运行环境,程序名有所不同。

初步判断

问题的现象是找不到icebox,因为application.xml中只配置icebox程序名,没有完整路径, 所以推测icegridnode.exe应该是通过系统path寻找icebox的,那么首先自然会想到可能是系统path路径中不包含icebox的路径。

第一次尝试

  • 首先查看系统环境变量是否包含icebox的路径, 经过查看此路径是存在的;
  • 然后查看进程的控制块是否包含系统Path,进程启动时会复制一份系统环境变量到进程控制块,也就是说每个进程都有自己的一份系统环境变量, 因此修改系统Path对已经启动的进程是无效的。那么怎么查看进程的环境变量呢,可以通过procexp.exe工具,经过查看icegridnode.exe进程中系统Path是正确的。至此第一次尝试以失败告终。

继续分析

  • 经过初步诊断排除了外部环境的问题,接下来只能从程序内部来找原因,需要找到出问题的代码位置,根据服务启动的原理,问题应该出在icegridnode.exe中, 关键就是怎么在Ice源代码中快速找到,而目前唯一的线索就是弹出的错误对话框,这应该是突破口;
  • 根据对话框中的错误消息,用关键字"Couldn’t find"在Ice源代码中搜索,这里推荐FindStr(Windows下也有findstr命令)这个工具,经过搜索果然找到了 出问题的代码位置,如下:

  • 源代码的位置找到了,就要开始上终极神器了,那就是windbg,接下来先做一些准备工作,看一下出问题的代码。

icegridnode.exe启动icebox的源代码

查看代码关键的地方是SearchPathW的调用,这是一个Windows API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifdef _WIN32
if(!IceUtilInternal::isAbsolutePath(path))
{
if(path.find('/') == string::npos)
{
//
// Get the absolute pathname of the executable.
//
wchar_t absbuf[_MAX_PATH];
wchar_t* fPart;
wstring ext = path.size() <= 4 || path[path.size() - 4] != '.' ? L".exe" : L"";
if(SearchPathW(NULL, IceUtil::stringToWstring(path).c_str(), ext.c_str(), _MAX_PATH, absbuf, &fPart) == 0)
{
if(_traceLevels->activator > 0)
{
Trace out(_traceLevels->logger, _traceLevels->activatorCat);
out << "couldn't find `" << path << "' executable.";
}
throw string("Couldn't find `" + path + "' executable.");
}
path = IceUtil::wstringToString(absbuf);

SearchPath函数说明

SearchPath定义:

1
2
3
4
5
6
7
8
DWORD WINAPI SearchPath(
_In_opt_ LPCTSTR lpPath,
_In_ LPCTSTR lpFileName,
_In_opt_ LPCTSTR lpExtension,
_In_ DWORD nBufferLength,
_Out_ LPTSTR lpBuffer,
_Out_opt_ LPTSTR *lpFilePart
);
  • Parameters

lpPath [in, optional] The path to be searched for the file. If this parameter is NULL, the function searches for a matching file using a registry-dependent system search path. For more information, see the Remarks section.

  • Return value

If the function succeeds, the value returned is the length, in TCHARs, of the string that is copied to the buffer, not including the terminating null character. If the return value is greater than nBufferLength, the value returned is the size of the buffer that is required to hold the path, including the terminating null character.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

开始第二次尝试,上windbg

  1. 先对icegridnode.exe做了一个userdump保留好环境以防万一, 然后设置好windbg的符号和源代码路径;
  2. 用windbg attach到icegridnode.exe进程开始调试,打开Activator.cpp文件,在370行设置好断点,F5(Go);
  3. 在IceGridGUI工具中start某个服务,windbg中可以看到断点命中。很显然出现本问题一定是SearchPathW返回的0, 那么为什么SearchPathW返回的0,现在就可以一探究竟了,查看SearchPath输入的参数,发现输入的参数都是正确的, Step Into到IceUtil::stringToWstring,其转换结果也是正确的,Step Out继续往下调确认SearchPathW 确实返回的0;
  4. 根据SearchPath函数的说明,返回0表示失败,通过GetLastError可以查询错误码,在windbg中通过!gle命令查询错误码为0,也就是没有错误, 这就奇怪了,输入参数都是正确的,错误码也显示没有错误,为什么找不到icebox呢?
  5. 通过windbg的!peb命令可以查看进程控制块,确认了进程的环境变量系统Path确实包含正确路径,自此陷入僵局,一切看起来都是正常的, 但是SearchPath的返回结果就是不对。

windbg

  • 小知识
    • !stl命令打印stl字符串
    • !gle获取GetLastError错误码
    • !peb命令查看进程控制块

僵局打破

  • 真是山穷水复疑无路,柳暗花明又一村,正当一筹莫展之时,一个误操作反而打破僵局,让新的线索浮出水面, 本来是想detach的,由于windbg卡住没反应,就接着点了关闭windbg,弹出对话框提示等待还是取消,就点了取消, 结果windbg是退出了,但是icegridnode.exe也退出了,幸好之前userdump了,不过毕竟丢失了第一手环境,还是 有些可惜,难道这个问题就此又成了无头冤案,不能啊。
  • 继续,这么奇怪的问题不找出真相让人不爽,既然icegridnode.exe已经退出,那重启一下,看能否重现问题吧, 进入services.msc,启动icegridnode服务,居然报错了,服务无法启动,顿时眼前一亮,马上想到肯定跟用户权限有关系, 查看了icegridnode服务的用户,使用的Local Service用户,那么会不会是Local Service用户权限问题导致的呢?

什么是Local Service用户

参考1参考2

  • LocalSystem 账户

LocalSystem是预设的拥有本机所有权限的本地账户,这个账户跟通常的用户账户没有任何关联,也没有用户名和密码之类的凭证。这个服务账户可以打开注册表的HKEY_LOCAL_MACHINE\Security键,当LocalSystem访问网络资源时,它是作为计算机的域账户使用的。 举例来说,以LocalSystem账户运行的服务主要有:WindowsUpdate Client、 Clipbook、Com+、DHCP Client、Messenger Service、Task Scheduler、Server Service、Workstation Service,还有Windows Installer。

  • Network Service 账户

Network Service账户是预设的拥有本机部分权限的本地账户,它能够以计算机的名义访问网络资源。但是他没有Local System 那么多的权限,以这个账户运行的服务会根据实际环境把访问凭据提交给远程的计算机。Network Service账户通常可以访问Network Service、Everyone组,还有认证用户有权限访问的资源。 举例来说,以Network Service账户运行的服务主要有:Distributed Transaction Coordinator、DNS Client、Performance Logs and Alerts,还有RPC Locator。

  • Local Service 账户

Local Service账户是预设的拥有最小权限的本地账户,并在网络凭证中具有匿名的身份。Local Service账户通常可以访问Local Service、Everyone组还有认证用户有权限访问的资源。 举例来说,以Local Service账户运行的服务主要有:Alerter、Remote Registry、Smart Card、SSDP,还有WebClient。

第三次尝试,验证猜想

  • 感觉真相应该快出来了,接下来验证一下自己的猜想,很简单只要用Local Service用户运行cmd,在cmd中运行where命令就可以验证了;
  • 首先想到runas命令,尝试了一下,对内置用户无法使用;
  • 开始google如何使用Local Service用户运行某个程序,还真找到了方法,通过psexec工具, 这是一个非常强大的工具,点击此处下载,其实里面还有一系列其他工具;
  • 执行psexec.exe -i -u “nt authority\localservice” cmd命令,弹出新的cmd窗口,执行whoami命令,确认确实使用localservice用户运行的, 注意这里localservice用户的全名是nt authority\localservice,然后运行where icebox命令,确实找不到,同时以administrator运行cmd, 确认可以找到,至此可以完全确定问题是由localservice用户权限导致。

进一步找到根源

  • 要修复问题还需要找到到底是什么影响了localservice用户的权限,可以通过对比正常的机器的设置找到差异, 首先想到的是组策略中是否有对localservice用户权限的设置,运行gpedit.msc打开组策略,对比正常机器和问题机器, 确实发现有一项两边是不一样的,但是修改之后并没有效果,下图为正常机器的截图;

  • 然后想到的可能是目录的权限设置问题,对比正常机器和问题机器,发现问题机器少了Users用户组的权限,下图为正常机器截图:

  • 然后在问题机器上参照正常机器添加Users用户组的“Read & execute,List folder contents,Read,Special permissions (Create files/write data,Create folders/append data)”权限,激动人心的时刻出现了,where命令可以正确找到icebox了, icegridnode服务也可以正常启动了,验证了一下在IceGridGUI中start服务也是OK的,然后删除以上权限,再验证是否问题能复现,果然问题复现了, 问题的根源终于找到,问题也得以圆满解决,Happy!!!从正常机器来看,icebox所在目录的权限其实是从D:\继承的,所以也可以通过继承父的权限来修改。

什么是Users用户组

参考

普通用户组,这个组的用户无法进行有意或无意的改动。因此,用户可以运行经过验证的应用程序,但不可以运行大多数旧版应用程序。Users 组是最安全的组,因为分配给该组的默认权限不允许成员修改操作系统的设置或用户资料。Users 组提供了一个最安全的程序运行环境。在经过 NTFS 格式化的卷上,默认安全设置旨在禁止该组的成员危及操作系统和已安装程序的完整性。用户不能修改系统注册表设置、操作系统文件或程序文件。Users 可以关闭工作站,但不能关闭服务器。Users 可以创建本地组,但只能修改自己创建的本地组。 它受administrator、admin 等管理员的管制和安排。比如:你家或你寝室里只有你自己1台电脑,你可以设置两个用户,一个管理员,另一个为普通用户。这样,在管理员身份下,设置文件的“只读”属性,禁止“添加或删除程序”……你可以放心他人不会修改或删除你的资料,另外,也可以防止他人用你的机器来下载东西。

总结

  • 调试的过程是一个抽丝拨茧的过程,层层深入,需要足够的耐心和细心,每走一步前先仔细分析一下,然后验证自己的猜想,任何问题一定有其根本原因, 只要不轻易放弃,通过一定技巧及努力,一定是可以找到问题原因的;
  • 调试的过程可以学到很多新的知识,这个问题中有很多知识其实我也不知道,比如Windows的用户权限知识,但是通过问题线索,顺藤摸瓜,在Google的帮助 下,既学到了新的知识,问题也得以解决,个人能力就是这样提升上来的,但是如果解决问题半途而废,那么隐藏其中的很多原理和相关知识,你就无缘知晓;
  • Windows平台下的windbg绝对是神器,大部分问题,如果windbg调不出问题,那基本上可以断定应该不是程序本身的问题,这么好的东西,还不赶快学;
  • 调试之前一定注意先收集各种线索以及保护好环境,退出调试时一定要detach,以免环境被破坏,这样问题可能就永远无法得见天日;
  • 浏览了一下ice的手册,在Widnows Services一章,提到安装icegridnode为Windows服务时建议使用LocalService用户,并且发现了icacls命令, 可以用于查看和修改文件的权限,基于这些内容对本问题其实也可以做一些推断;
  • 最后其实还有一个疑问,既然没有权限,为什么SearchPath的GetLastError错误码是0,如果这个时候从错误码就能判断错误,那么问题就更容易解决了。当然 也许SearchPath内部还调用了其他API函数,导致GetLastError为0,不管怎么样如果SearchPath的接口设计的好一点,查问题就更容易了。
-------------本文结束感谢您的阅读-------------