阅读:10553回复:12
用VS2010调试跟踪firefox8.0
我需要对firefox进行跟踪调试,以便尽快搞清楚firefox的工作流程。我想用VS2010来做,在网上没有找到操作方法,不过网上有利用VS2005和VS2008进行调试的例子。参照其步骤,我来探究一下如何使用VS2010对Firefox进行调试。参考资料:
Debugging Firefox using Visual Studio .NET 2005 Debugging Firefox using Visual Studio .NET 2008 (2005)(完美调试firefox) 如何用vc调试firefox(本文后面部分也引用了前面第一个链接) 下面是我尝试用VS2010(我使用的是VS2010中文旗舰版)调试Firefox的记录: 第一步:利用源代码编译firefox。 这个过程有专门的教程类文章,我使用的是firefox的8.0版本的源代码。之前我也编译过,但是都是默认的选项,我现在需要编译可以调试的debug版,因此我从新编译一遍。我的mozconfig文件内容是: mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/ff-dbg ac_add_options --enable-application=browser ac_add_options --disable-optimize ac_add_options --enable-debug 这样写不一定是最好的,但至少是可用的,我用这个编译通过了。 第二步,启动VS2010,选择文件->打开->项目/解决方案, 选择相应目录下的firefox.exe。打开。这个可以参考前面给出的资料链接。 图片:1解决方案.jpg 第三步,设置环境。 右键点击上图所示的firefox,选择属性,出现属性页,该页面与资料中VS2005和VS2008的都不大一样。如下图: 图片:2环境设置1.jpg 我在这里纠结了很久。因为不知道怎样将资料中说的环境设置MOZ_NO_REMOTE=1添加进去(可能是我对VS操作不熟悉吧),后来才知道可以点击环境右边的按钮,然后弹出一个窗口,如下图: 图片:3环境设置2.jpg 第四步,跟踪firefox执行过程。 这个在楼下细述。不知道发帖有没有长度限制。。 把我的mozconfig文件附上吧!最终我发现还是只能发三个附件,这个到三楼吧! |
|
|
1楼#
发布于:2014-06-28 16:06
谢谢楼主的分享,辛苦了
|
|
2楼#
发布于:2011-12-05 21:54
恩,pdb不要无故删掉了
有一定基础用cdb/windbg调试也挺方便的 |
|
3楼#
发布于:2011-12-05 21:54
刚开始图片还可以显示的,现在图片也无法显示了,估计是CSDN在作怪,需要看插图的去我的CSDN看吧,本论坛一帖只能上传三个附件因此无法在这里帖全部截图了。
|
|
|
4楼#
发布于:2011-12-05 21:54
调试小结
我调试跟踪firefox源代码的目的是找到firefox加载网页文件到渲染页面这个过程的处理代码,我的这个目的到现在为止还没达到。但是对firefox的源代码进行跟踪和调试所涉及到的内容也基本上就是这些了。因此做一个关于调试跟踪的小结。先说明一下我使用的相关软件、程序的版本等信息吧,操作系统是win XP;Visual Studio是2010版;firefox的源代码是8.0版本的;编译firefox还下载了一个MozillaBuildSetup的exe,这个是下载的最新版本的。整个过程完整的步骤如下: 1, 下载并安装所需程序,下载需要的源代码(VS不用说了,firefox 8.0、MozillaBuildSetup) 2, 解压firefox的源代码到某一个目录,并修改mozconfig文件,将修改得到的mozconfig文件放到firefox源代码的顶级目录下 3, 一般MozillaBuildSetup是安装在C:\mozilla-build\下的,运行其内的start-msvc10.bat,产生一个命令行窗口,在其中将目录跳转到firefox源代码所在的目录,运行make -f client.mk build,接着就是漫长的等待其编译结束 4, 编译结束之后,打开VS2010,选择文件\打开\项目/解决方案。在其内选择编译得到的firefox.exe。 5, 设置firefox.exe这个项目的属性,将环境设置一个自定义的MOZ_NO_REMOTE值为1,如果不想程序打开profilemanager选择profile,还可以设置参数-P profilename。(profilename就是想要用来进行本次调试使用的profile的名称) 6, 设置好之后就可以进行调试了,VS2010下调试常用操作快捷键F9:在光标行设置断点,F10:单步步过,F11:单步步入,F5:全速执行,ctrl+F10:运行到光标处 7, 选择文件/打开/文件…可以打开某个特定的文件并在其中执行设置断点等调试操作 8, 如果要修改某个文件,则修改之后需要重新执行第三步的编译过程,编译完了只需要重新打开第四步建立的firefox.sln即可对修改之后的进行调试。 上面容易犯迷糊的地方是编写mozconfig文件、编译firefox源代码以及用VS调试firefox.exe。 |
|
|
5楼#
发布于:2011-12-05 21:54
打开与修改文件
按照网上其他人给出的方法,打开了firefox工程之后,设置好环境变量,自己就可以打开一些文件,下断点,进行调试等。但是我一直没有找到打开某个特定文件的方法,如下图是firefox工程点击右键时的选项:不过可以添加现有项目,这样可以把firefox.exe目录下的其他测试程序都打开来调试一下,上图中我就打开了TestGetURL等几个测试程序。因此,我总也没找到人家说的打开文件如何打开。 后来在网上问别人,终于知道了我的问题出在哪里了。其实打开文件就在VS的文件菜单里面。这也只能怨我自己对VS不熟悉。打开文件的菜单截图如下: 也可以快捷键ctrl+O打开,我打开了文件~\firefox-8.0.source\netwerk\base\src\nsBaseChannel.cpp,并在GetNotificationCallbacks函数上设置一个断点。接着运行程序,程序果然就断在了这个地方,如下图: 这下好了,我可以随便想打开哪个文件就打开哪个文件并设置断点进行调试了,下一步要坐的就是修改文件,并使我做的修改能够起作用。 我的想法是在某个文件里添加一句打印信息,如果我添加的打印信息能够成功显示,那么就说明修改起作用了,我在d:\firefox-8.0.source\toolkit\xre\nsWindowsWMain.cpp文件中的wmain函数中添加了一句printf("Usage: TestGetURL <url>\n");这句是直接从TestGetURL的源文件中复制过来的。 修改之后我再次F10发现VS会提示文件已经修改,选择是,程序会按照原来的执行,选择否则会显示无可用源。于是我就试着对firefox重新编译了一遍。也不知道是为什么,我没有按照最开始那样运行make -f client.mk build,我直接与运行了make -f client.mk,这样编译花费了很久,但是比第一次编译花的时间短很多。重新编译之后,我再次F10运行修改了的程序。运行完毕修改之处就显现了我写的打印信息,如下图: 如此,就证明了我可以修改某个源文件,重新编译一遍,然后就可以对修改之后的程序进行跟踪调试了,虽然步骤比较繁琐,但是这找到了一条可行的方法。不过也说明编译的时候可以用make -f client.mk,但是第一次编译最好还是用make -f client.mk build,因为我不敢保证第一次编译的时候不加build会出现什么结果。 |
|
|
6楼#
发布于:2011-12-05 21:54
Firefox主窗口创建过程研究
到此为止,可以说已经到了firefox比较关键的地方了,其打开过程弄得比较清楚了,现在先来理清楚firefox是如何创建浏览器窗口,并最终展示在用户面前。这里先分析一下AppShell->Run()开始调用到消息循环开始这个过程。当程序运行到~\firefox-8.0.source\toolkit\components\startup\nsAppRunner.cpp第35444行(在函数XRE_main里面)调用appStartup->Run()的时候,单步跟踪进去,发现该函数首先对环境进行了一下判断(如,有没有打开的firefox窗口,或者打开的窗口已经调用了推出函数),然后直接调用函数mAppShell->Run(),跟进这个函数。 函数mAppShell->Run()首先调用了mozilla::widget::StartAudioSession();函数,然后直接调用nsBaseAppShell::Run()。StartAudioSession()的功能是启用音频会话,跟进nsBaseAppShell::Run()函数。 该函数首先获取当前进程*thread = NS_GetCurrentThread();然后调用MessageLoop::current()->Run();,跟进这个函数。 MessageLoop::current()->Run()函数只有两句话:AutoRunState save_state(this);(保存当前状态)和RunHandler();(运行消息处理器),跟进RunHandler()。 RunHandler()就在Run()函数后面,直接调用其下面的函数RunInternal(); RunInternal()直接调用pump_->Run(this); pump_是一个MessagePump类的对象,其Run函数中有一个for循环,就是前面提到的消息处理循环。 上面这个过程的调用堆栈截图如下: 描述:调用堆栈 图片:3.jpg |
|
|
7楼#
发布于:2011-12-05 21:54
事先确定profile情况下的跟踪
我发现的相似的东西如下两图所示:第一张图是开始运行时的堆栈调用情况,第二张图是新建进程之后,中断时的堆栈调用。我发现的相似的地方是两个进程的入口函数都一样是firefox.exe里面的wmainCRTStartup函数,而其后依次调用了__tmainCRTStartup、wmain、NS_internal_main、do_main、XRE_main。这些都是一样的,而之后就不一样了,前面看过XRE_main这个函数,有一千多行,在这个函数里面有很多分支,这里我又注意到这两个进程执行过程是如此相似,唯一不同的就是第一次运行这个进程的时候还没有选择合适的profile。而选择完profile之后,程序是不是又回到了开始的地方,继续执行呢?因此我就想,如果我在程序一开始的时候就给它选定profile,是不是就可以不用打开profilemanager了呢,在联系到运行特定profile的命令行方式打开profile一般是:"D:\Program Files\mozilla firefox 7\firefox.exe" -P "default"。而用bat文件打开firefox时,其命令一般是: set MOZ_NO_REMOTE=1 start "" "D:\Program Files\mozilla firefox\firefox.exe" -P default 因此,我考虑MOZ_NO_REMOTE=1这个已经在环境中设置了,如果我在让这个程序运行的时候带上-P "default"之类的参数应该就可以省掉选择profile那些步骤而直接用给定的profile打开firefox了。于是我首先进行了尝试。在前面设置环境的地方,有一个参数,其值是空着的,我抱着试试的态度,在里面写上了-P sourcetest,如下图,sourcetest是我为这次调试新创建的profile。其内的设置全部是默认的。 这样我一步一步的跟下去。还是按照第一次的方法,这次我跟下去果然没有打开profilemanager,这说明那里的参数设置对了,而且打开的窗口也是预期的效果。在跟的过程中,我遇到了rv = mAppShell->Run();appStartup->Run();、一个for循环:for (;;) {…}注意这个for循环的循环体我省略了,而其循环条件,我没有改动,也就是说这个for循环是个死循环,在这个死循环里面调用了NS_ProcessNextEvent这个函数继续跟进去,到了~\firefox-8.0.source\ipc\chromium\src\base\message_loop.cc文件中,这个文件一看名称就是处理消息循环的,这些循环内部究竟做了哪些操作,今天已经来不及去跟踪了。 调试跟踪到这里,我想我终于将firefox从用户双击图标到打开浏览器这个过程弄清楚了。简单的说就是如果用户指定了profile,就直接打开firefox,如果没有指定就打开profilemanager,让用户选择或者创建profile然后再打开firefox。经过实际测试及这里的跟踪调试,可以知道,程序确定profile有两种方式,一种是设置了moz-no-remote=1的情况下,用-p profilename的方式运行,另一种是没有设置moz-no-remote=1,且当前有已经在运行的firefox则使用在运行的firefox的profile来打开。其他情况都会打开profilemanager供用户选择或创建profile。 |
|
|
8楼#
发布于:2011-12-05 21:54
跨进程调试
前面弄清楚了一个问题,只要执行的时候需要用户选择profile,那么选择完profile之后就会创建新的进程来创建firefox。我的想法很直接,只要我能够跟进这个新创建的进程然后一步一步的跟着程序走就可以找到firefox运行过程中是如何打开窗口,如何打开网页的了。于是我在网上各种寻找资料,不过依然没有找到我想要的东西,找到的最相关的就是多线程的调试,可是我是跨进程的。最后请教高手,虽然他一开始也不知道怎么回事,但是凭他丰富的经验,很快就帮我找到了跨进程调试的方法。我之所以找不到这方法,很大程度上还是因为我对VS不是很熟悉的缘故,为什么这么说呢?原来跨进程调试只需要在创建进程之后,选择调试->附加到进程…(英文版好像是attach to process)就可以了。这种方式虽然可以进行跨进程调试,但是有一个缺点,没法让新建的进程在正式运行时打住。比如,一般的调试只要按F10一般都会停在main(或者wmain等)函数的开始处,而这个跨进程的不可以,一旦进程创建,这个新的进程就全速执行了,而且在attach到这个新的进程之前即使在源文件中设置了断点,程序也不会在设置的断点处停下,必须是attach到了这个新的进程之后中断调试才可以。这样中断的时候程序运行到哪里了就不好说了。虽然可以跨进程调试了,但是这样调试起来根本达不到我的预期的效果,不过我注意到调用堆栈里面大部分都是调用的xul.dll这个里面的函数,因此我又想去捣鼓xul.dll这个文件,但是费尽周折依然没有进展。 几乎进入了不知道该怎么做了的时候,我发现了两个进程中比较相似的一些东西,楼下详述。 |
|
|
9楼#
发布于:2011-12-05 21:54
其他情况的跟踪
其他情况主要包括:[*] 环境设置不做变动,保持默认值。 [*] 设置为启动firefox时打开空白页/打开主页等 环境设置保持默认值 直接按F10,开始调试。这一次没有打开profilemanager,而是直接使用了电脑上的默认profile。打开了主页。跟踪依据最开始的跟踪方式,在确定需要跟进的地方按F11,其他地方按F10,不过,在~\firefox-8.0.source\toolkit\xre\nsAppRunner.cpp文件里的XRE_main函数中没有发生预期的效果,也就是没有打开profilemanager的过程。因此在XRE_main函数处设置断点,进行第二遍跟踪。 第二遍仔细跟踪XRE_main函数发现在如下图的地方,打开了firefox: 而前面的选择profile的SelectProfile函数的调用在该函数后大约50行左右的地方。设置了环境MOZ_NO_REMOTE=1时也执行了这句话的,但并没有在这里打开firefox而是接着执行了后面的一部分代码之后打开了profilemanager。这里我跟进这个nativeApp->Start(&canRun); 注意,注释说在检查是否已经有一个firefox在运行,有的话使用这个操作(start)来通知窗口发送一个与该进程命令行选项相关的请求。如果没有firefox在运行,则创建这个消息窗口。我在调试跟踪的时候确实有一个firefox在运行,所以这里就发送了一个请求,在下图所示的地方: 这个请求发送过后,就打开了一个firefox窗口。之后该函数返回,其返回值rv使得nativeApp->Start(&canRun);后面的if条件满足,因此就直接依次返回。 这样看来,我要将我在运行的那个firefox关闭之后再进行这次跟踪。关闭之后,发现也是运行到rv = msgWindow.SendRequest();的时候打开firefox。唯一不同的是这次打开的是关闭前的网页,因为我的default profile的设置就是这样。由于前一次有firefox在运行,打开的时候打开了主页。因此跟踪一下这个SendRequest()函数,到了下图所示的地方: 这里的两个函数都是windows API函数,先将窗口置于最前端,然后发送消息。查看一下MSDN,sendmessage的第二个参数为发送的消息,第一个参数为目标窗口句柄。最后一个参数为(LPARAM) lParam // data (PCOPYDATASTRUCT),可能是WM_COPYDATA的数据吧!该函数执行之后,firefox就打开了,打开的是主页。现在需要搞清楚在哪个地方创建的mHandle这个句柄。一直往前发现在~\firefox-8.0.source\xpcom\glue\standalone\nsXPCOMGlue.cpp文件中加载xul.dll的时候创建了新线程。这个线程在打开firefox过程中一直存在,说明很可能是在这个进程中创建的mHandle窗口句柄。涉及到另外的进程了,到后面研究,下面看看环境依然设置为MOZ_NO_REMOTE=1的时候,让其打开空白页看看是怎样进行的。 打开空白页 将前面打开的firefox的选项设置为启动firefox的时候打开空白页。下面是跟踪过程,如果与第一遍一样的话就不记录了。没有什么特别的,只要是设置了moz-no-remote=1的都是先打开profilemanager然后调用CreateProcess创建新的进程运行firefox。 |
|
|
10楼#
发布于:2011-12-05 21:54
选择profile到创建新进程
这之间有一个函数LaunchChild(~\firefox-8.0.source\toolkit\xre\nsAppRunner.cpp文件中第1554-1634行),跟进去看看,该函数在我跟踪的时候执行了的代码为:static nsresult LaunchChild(nsINativeAppSupport* aNative, PRBool aBlankCommandLine = PR_FALSE) { aNative->Quit(); // release DDE mutex, if we're holding it SaveToEnv("MOZ_LAUNCHED_CHILD=1"); #if defined(ANDROID) #else #if defined(XP_MACOSX) #else nsCOMPtr<nsILocalFile> lf; nsresult rv = XRE_GetBinaryPath(gArgv[0], getter_AddRefs(lf)); if (NS_FAILED(rv)) return rv; #if defined(XP_WIN) nsAutoString exePath; rv = lf->GetPath(exePath); if (!WinLaunchChild(exePath.get(), gRestartArgc, gRestartArgv)) return NS_ERROR_FAILURE; return NS_ERROR_LAUNCHED_CHILD_PROCESS; } 直接看WinLaunchChild函数,关键代码 WinLaunchChild(const PRUnichar *exePath, int argc, char **argv) { … for (int i = 0; i < argc; ++i) { argvConverted[i] = AllocConvertUTF8toUTF16(argv[i]); if (!argvConverted[i]) { return FALSE; } } BOOL ok = WinLaunchChild(exePath, argc, argvConverted); … } 最后又调用BOOL ok = WinLaunchChild(exePath, argc, argvConverted);就在这段代码的后面,BOOL WinLaunchChild(const PRUnichar *exePath, int argc, PRUnichar **argv)对比前面一个WinLaunchChild函数的参数,发现第三个参数类型不一样,一个为char **型,一个为PRUnichar **,上面只是将最后一个参数转换了一下类型,函数实际执行在后面的函数体里面,这两个函数的代码在~\firefox-8.0.source\toolkit\xre\nsWindowsRestart.cpp文件里面的第237-303行。 BOOL WinLaunchChild(const PRUnichar *exePath, int argc, PRUnichar **argv) { PRUnichar *cl; BOOL ok; cl = MakeCommandLine(argc, argv); STARTUPINFOW si = {sizeof(si), 0}; PROCESS_INFORMATION pi = {0}; ok = CreateProcessW(exePath, cl, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); if (ok) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { } free(cl); return ok; } 除了exePath传给了CreateProcessW函数,其他参数都用在了MakeCommandLine函数里,其返回值cl作为CreateProcessW的一个参数。另外,还定义了结构体si、pi。 要搞清楚firefox是如何打开网页就得研究这个新创建的线程,不过到现在为止我感觉已经对firefox打开过程的粗粒度分析完毕,但是好像没有什么实际的收获,接下来先让firefox打开些别的起始页试试。 |
|
|
11楼#
发布于:2011-12-05 21:54
Firefox运行流程初步跟踪
我使用的mozconfig文件:mozconfig.rar 前面对firefox打开火狐主页进行了跟踪,跟到了windows API 函数CreateProcess。该函数是创建一个新的进程,我认为要继续跟踪,可能需要进入这里创建的新进程才行。暂且搁下,我现在打算对firefox的运行流程进行一下粗粒度的跟踪,我的想法是一直按F10,直到程序主线程执行完毕,看看firefox在启动过程中和启动之后,程序都执行了哪些步骤。 首先将前面下的所有断点禁用,然后按第一次F10,程序开始执行,在nsWindowsWMain.cpp文件中wmain函数开始处。 接着按F10,程序执行经过如下代码段(我只贴出我在跟踪过程中实际执行了的代码,全部贴出来太多了): #ifndef XRE_DONT_PROTECT_DLL_LOAD mozilla::SanitizeEnvironmentVariables(); mozilla::NS_SetDllDirectory(L""); #endif 接下来,执行了下面这几句: char **argvConverted = new char*[argc + 1]; if (!argvConverted) return 127; for (int i = 0; i < argc; ++i) { argvConverted[i] = AllocConvertUTF16toUTF8(argv[i]); if (!argvConverted[i]) { return 127; } } argvConverted[argc] = NULL; 这里先定义了**argvConverted,然后判断其是否为空,为空直接返回127(0xFF)否则对这个数组的每一个元素执行AllocConvertUTF16toUTF8(argv);并判断是否为空。为空则返回127。按F10,单步执行下去,发现没有返回,而是直接执行到argvConverted[argc] = NULL;这一句。 继续按F10,来到代码段: // need to save argvConverted copy for later deletion. char **deleteUs = new char*[argc+1]; if (!deleteUs) { FreeAllocStrings(argc, argvConverted); return 127; } for (int i = 0; i < argc; i++) deleteUs[i] = argvConverted[i]; int result = main(argc, argvConverted); 这段代码最后一句result = main(argc, argvConverted);是前面下第一个断点的地方,在这里程序调用一个main函数,打开了firefox的profilemanager,然后选择profile之后就打开了firefox,页面显示的是火狐主页。 Firefox打开之后,可以在变量监视窗口看到,result的值为0,上面的代码之后的代码是: delete[] argvConverted; FreeAllocStrings(argc, deleteUs); return result; 删除前面分配的内存并返回0.一直按F10,直到该函数(wmain)返回,该函数执行完毕,按F10时程序来到了D:\Program Files\Microsoft Visual Studio 10.0\VC\crt\src\crtexe.c文件,其中有代码(550-553行): #ifdef WPRFLAG __winitenv = envp; mainret = wmain(argc, argv, envp); #else /* WPRFLAG */ 直接一个F10又到了下面的地方(565行): if ( !managedapp ) exit(mainret); 这里再按F10就执行了exit(mainret);调试过程结束(firefox没有关闭的话还在运行着)。这一遍跟踪只搞清楚了一个问题,那就是firefox运行的时候启动的入口函数是wmain,这个函数就相当于普通C程序的main函数。下面是wmain函数的代码: int wmain(int argc, WCHAR **argv) { #ifndef XRE_DONT_PROTECT_DLL_LOAD mozilla::SanitizeEnvironmentVariables(); mozilla::NS_SetDllDirectory(L""); #endif #ifdef XRE_WANT_DLL_BLOCKLIST SetupDllBlocklist(); #endif char **argvConverted = new char*[argc + 1]; if (!argvConverted) return 127; for (int i = 0; i < argc; ++i) { argvConverted[i] = AllocConvertUTF16toUTF8(argv[i]); if (!argvConverted[i]) { return 127; } } argvConverted[argc] = NULL; // need to save argvConverted copy for later deletion. char **deleteUs = new char*[argc+1]; if (!deleteUs) { FreeAllocStrings(argc, argvConverted); return 127; } for (int i = 0; i < argc; i++) deleteUs[i] = argvConverted[i]; int result = main(argc, argvConverted); delete[] argvConverted; FreeAllocStrings(argc, deleteUs); return result; } 将这个wmain函数看一遍,发现没有执行多少有意义的事情,所有与打开firefox有关的看起来都在 int result = main(argc, argvConverted);这一句。这个函数就是重点考察对象,在此,我还没得到我想要的过程,因此我选择继续跟进这个main函数。按照前面的方法,对这个main函数进行跟踪调试。 这个main函数是~firefox-8.0.source\browser\app\nsBrowserApp.cpp文件中的一个main函数,下面就不一步一步的描述了(这样的调试纯属体力活),我将重要的信息记录下来便是。main函数: int main(int argc, char* argv[]) { char exePath[MAXPATHLEN]; nsresult rv = mozilla::BinaryPath::Get(argv[0], exePath); char *lastSlash = strrchr(exePath, XPCOM_FILE_PATH_SEPARATOR[0]); strcpy(++lastSlash, XPCOM_DLL); int gotCounters; IO_COUNTERS ioCounters; gotCounters = GetProcessIoCounters(GetCurrentProcess(), &ioCounters); rv = XPCOMGlueStartup(exePath); rv = XPCOMGlueLoadXULFunctions(kXULFuncs); #ifdef XRE_HAS_DLL_BLOCKLIST XRE_SetupDllBlocklist(); #endif if (gotCounters) { #if defined(XP_WIN) XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_OPS, int(ioCounters.ReadOperationCount)); XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_TRANSFER, int(ioCounters.ReadTransferCount / 1024)); IO_COUNTERS newIoCounters; int result; { ScopedLogging log; result = do_main(exePath, argc, argv); } XPCOMGlueShutdown(); return result; } 这个函数获取了firefox.exe的路径,执行了XPCOMGlueStartup(exePath);和XPCOMGlueLoadXULFunctions(kXULFuncs);并运行了XRE_SetupDllBlocklist();最后调用函数do_main(exePath, argc, argv);其参数exePath是在这个main函数中获取的,而argc和argv则是wmain函数传过来的。 继续跟进do_main函数。该函数内容如下: static int do_main(const char *exePath, int argc, char* argv[]) { nsCOMPtr<nsILocalFile> appini; #ifdef XP_WIN nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(exePath), PR_FALSE, getter_AddRefs(appini)); appini->SetNativeLeafName(NS_LITERAL_CSTRING("application.ini")); const char *appDataFile = getenv("XUL_APP_FILE"); nsXREAppData *appData; rv = XRE_CreateAppData(appini, &appData); int result = XRE_main(argc, argv, appData); XRE_FreeAppData(appData); return result; } 这个函数做的事情是根据前面传过来的exepath获得一个appini,并根据这个appini生成一个nsXREAppData 结构体appData,作为调用函数XRE_main(argc, argv, appData);的参数,其参数argc和argv依然是最开始那个。下面根进XRE_main。 这个函数在~\firefox-8.0.source\toolkit\xre\nsAppRunner.cpp文件里面,有一千多行(2612-3618),按F10实际执行了的代码: Int XRE_main(int argc, char* argv[], const nsXREAppData* aAppData) { NS_TIME_FUNCTION; gXRE_mainTimestamp = PR_Now(); nsresult rv; ArgResult ar; #ifdef XP_WIN // Vista API. Mozilla is DPI Aware. typedef BOOL (*SetProcessDPIAwareFunc)(VOID); SetProcessDPIAwareFunc setDPIAware = (SetProcessDPIAwareFunc) GetProcAddress(LoadLibraryW(L"user32.dll"), "SetProcessDPIAware"); if (setDPIAware) setDPIAware(); #endif SetupErrorHandling(argv[0]); #ifdef CAIRO_HAS_DWRITE_FONT OSVERSIONINFO vinfo; vinfo.dwOSVersionInfoSize = sizeof(vinfo); #endif gArgc = argc; gArgv = argv; NS_ENSURE_TRUE(aAppData, 2); const char* override = nsnull; ar = CheckArg("override", PR_TRUE, &override); ScopedAppData appData(aAppData); gAppData = &appData; ScopedLogging log; if (!appData.xreDirectory) { nsCOMPtr<nsILocalFile> lf; rv = XRE_GetBinaryPath(gArgv[0], getter_AddRefs(lf)); nsCOMPtr<nsIFile> greDir; rv = lf->GetParent(getter_AddRefs(greDir)); rv = CallQueryInterface(greDir, &appData.xreDirectory); } nsXREDirProvider dirProvider; rv = dirProvider.Initialize(gAppData->directory, gAppData->xreDirectory); #ifdef MOZ_CRASHREPORTER if (EnvHasValue("MOZ_CRASHREPORTER")) { appData.flags |= NS_XRE_ENABLE_CRASH_REPORTER; } #endif SaveToEnv("MOZ_LAUNCHED_CHILD="); gRestartArgc = gArgc; gRestartArgv = (char**) malloc(sizeof(char*) * (gArgc + 1 + (override ? 2 : 0))); int i; for (i = 0; i < gArgc; ++i) { gRestartArgv[i] = gArgv[i]; } gRestartArgv[gRestartArgc] = nsnull; ar = CheckArg("safe-mode", PR_TRUE); ar = CheckArg("no-remote", PR_TRUE); rv = XRE_InitCommandLine(gArgc, gArgv); NS_ENSURE_SUCCESS(rv, 1); { ar = CheckArg("register", PR_TRUE); if (ar == ARG_BAD) { } else if (ar == ARG_FOUND) { } nsCOMPtr<nsINativeAppSupport> nativeApp; rv = NS_CreateNativeAppSupport(getter_AddRefs(nativeApp)); PRBool canRun = PR_FALSE; rv = nativeApp->Start(&canRun); #if defined(MOZ_UPDATER) && !defined(ANDROID) nsCOMPtr<nsIFile> updRoot; PRBool persistent; rv = dirProvider.GetFile(XRE_UPDATE_ROOT_DIR, &persistent, getter_AddRefs(updRoot)); ProcessUpdates(dirProvider.GetGREDir(), dirProvider.GetAppDir(), updRoot, gRestartArgc, gRestartArgv, appData.version); #endif nsCOMPtr<nsIProfileLock> profileLock; PRBool startOffline = PR_FALSE; nsCAutoString profileName; rv = SelectProfile(getter_AddRefs(profileLock), nativeApp, &startOffline, &profileName); if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) return 0; } 该函数对系统信息和版本信息等进行了进一步的获取,利用参数aAppData创建了ScopedAppData 类的对象 appData,两个nsCOMPtr结构profileLock和nativeApp。定义了类nsCAutoString 的对象profileName和PRBool值startOffline其值为PR_FALSE。然后调用函数SelectProfile来让用户选择profile。其中第一个参数为getter_AddRefs(profileLock)的返回值,后面三个参数为之前定义的nativeApp,&startOffline和&profileName。该函数执行完毕,返回值rv= NS_ERROR_LAUNCHED_CHILD_PROCESS,于是XRE_main函数可以返回了。。 跟到这里,我认为SelectProfile函数在这样的运行条件下,如果用户选择正确的profile返回值必然是NS_ERROR_LAUNCHED_CHILD_PROCESS,如果取消profile的选择返回值必然是NS_ERROR_ABORT,因此我觉得这里没有必要再跟下去了,我对如何打开profilemanager的过程暂时不感兴趣。要搞清楚firefox是如何打开的必须跟到新打开的一个线程中区,前面已经跟踪到了那个CreateProcessW函数的入口处了。那么下一步就是进入新线程中进行跟踪了。 Wait,profilemanager打开用户选择profile之后,到创建新进程之间还有一些过程,我得去看看这些过程干了些什么。 楼下继续, |
|
|
12楼#
发布于:2011-12-05 21:54
Firefox打开空白页的跟踪
跟踪第一遍,直接按F10,程序开始执行,出现了一个控制台窗口,同时程序运行到nsWindowsWMain.cpp文件中的vmain函数处,如下图所示: 一直按F10,直到firefox打开,这个过程中,控制台输出了很多内容(估计这就是debug版的好处吧)。Firefox打开的是火狐主页,如下图所示: Firefox打开的时候程序运行到如下图所示的地方,我在这里设置断点,然后结束这一遍调试 跟踪第二遍,按F5运行,程序断在上图设置断点的地方。然后按F11跟进。来到了nsBrowserApp.cpp文件中的函数main如图: 接着一直按F10,直到firefox打开,这个过程中,控制台依然输出很多内容。Firefox打开了火狐主页,此时程序运行到了该文件中如下图所示的地方,在这里设置断点,然后结束这一遍调试: 跟踪第三遍,按F5运行,程序断在第一个断点处,再按F5,程序在上图设置断点的地方断下。然后按F11跟进。依然在nsBrowserApp.cpp文件中,来到了函数do_main如图: 接着一直按F10,直到firefox打开,此时程序运行到了该文件中如下图所示的地方,在这里设置断点,然后结束这一遍调试: 跟踪第四遍,按F5运行,此时已经设置了三个断点了,总共按三次F5。程序在上图设置断点的地方断下。然后按F11跟进。来到了nsAppRunner.cpp文件中的函数XRE_main处,如图: 接着一直按F10,直到firefox打开,此时程序运行到了该文件中如下图所示的地方,在这里设置断点,然后结束这一遍调试: 跟踪第五遍,按F5运行,此时已经设置了四个断点了,总共按四次F5。程序在上图设置断点的地方断下。然后按F11跟进。来到了nsCOMPtr.h文件中如下图所示的地方: 同时控制台输出: 接着继续按F10,按了几次又回到的刚才下断点的地方,经过多次在这里摸索,我发现该断点是SelectProfile函数,其第一个参数是函数getter_AddRefs(profileLock)的返回值,因此我感觉前面输出的控制台提示就是这个函数执行的时候产生的,于是在回到这个断点的位置时,我继续按F11,这次来到了nsCOMPtr.h文件中getter_AddRefs函数处,如下图: 接着继续按F10,按了两次再次回到第四个断点处。在这个断点处按F10必定会打开profilemanager,选择之后就打开了firefox,有了前面的经历,这次我按F11,运行到了nsCOMPtr.h文件中如下图所示的地方: 接着继续按了两次F10,又回到第四个断点处。我还按F11,这次来到了nsAppRunner.cpp文件中的函数SelectProfile函数的实现处: 在该函数的结尾处,调用了函数ShowProfileManager,从其名称可知,该函数要打开ProfileManager了。再F10则打开ProfileManager。选择profile之后,经过一番运行就打开了firefox。在这里设置第五个断点,待会儿在这里按F11跟进去: 跟踪第六遍,按F5运行,此时已经设置了五个断点了,总共按五次F5。程序在上图设置断点的地方断下。然后按F11跟进。又来到了nsCOMPtr.h文件中,如下图: 这里按两次F10,依然返回到了第五个断点处。在第五个断点处再次按F11跟进,来到函数ShowProfileManager的实现代码处,如下图: 在这里一直按F10,直到如下图所示的函数windowWatcher->OpenWindow处,这个函数运行之后,控制台输出更多内容,并弹出profilemanager,选择profile之后暂时没打开firefox: 函数ShowProfileManager的最后一句为:return LaunchChild(aNative);这里按F10的话就会打开firefox了,在这里设置第六个断点,待会儿F11跟进,如下图: 跟踪第七遍,按F5运行,此时已经设置了六个断点了,总共按六次F5。程序在上图设置断点的地方断下。然后按F11跟进。又来到了文件nsAppRunner.cpp的LaunchChild函数的实现代码处,如下图: 一直按F10,直到如下图所示的地方,这一句执行F10的话会打开firefox,在这里设置第七个断点,待会儿F11跟进: 跟踪第八遍,按F5运行,此时已经设置了七个断点了,总共按七次F5。程序在上图设置断点的地方断下。然后按F11跟进。又来到了文件nsTString.h文件中如下图所示的位置: 直接F10回到了第七个断点处,再按F11,来到nsWindowsRestart.cpp文件中WinLaunchChild函数实现代码处,如下图: 一直按F10,到如下图所示的地方: 发现调用的函数又是WinLaunchChild,于是F11跟进,如下图又有一个WinLaunchChild的实现,应该是函数重载: 这里继续按F10,直到下图所示的位置,CreateProcessW运行之后firefox就打开了,在这里设置第八个断点: 跟踪第九遍,按F5运行,此时已经设置了八个断点了,总共按八次F5。程序在上图设置断点的地方断下。然后按F11,依然打开了firefox。也就是说,在这里不管是按F10还是F11都会打开firefox。即无法继续跟踪其调用过程了。函数CreateProcessW看起来是windows 的API,查看一下MSDN,我发现了CreateProcess是属于Kernel32.lib库,该函数在Winbase.h中声明,使用这个函数需要包含windows.h头文件。因此确定这个函数是windows API函数。 经过这一番的跟踪,发现firefox运行过程大体上是先选择用户profile,然后打开相关网页,其实这个过程不进行跟踪都知道,但是经过这番跟踪,对这个过程有了深刻的印象。这里firefox打开的是firefox主页,相当于打开一个空白页。因此下次调试打算让firefox打开主页,如:百度。 以上文字是我一边跟踪一边写下来的。当然实际上不止跟踪了9遍,也不止下了8个断点,但是我感觉这几遍跟踪和这几个断点是比较重要的,而其他我感觉不重要的断点就直接用完就删除了,也没有在文中提及。 这里还有一些悬而未决的疑问,首先:环境设置是否正确,我没有十足的把握,我怀疑不设置那个MOZ_NO_REMOTE=1也可以进行调试,但我不确定;另外,某些资料中说可以把需要设置断点的文件添加进来,然后设置断点进行调试,但是这里我是直接单步运行,然后当程序运行到某个文件的时候,有源代码的文件都由VS自动打开了,我没有手动添加任何源代码进来。 最后想说这个大型的源代码跟踪起来就是累啊~捣鼓了这么久跟踪了N多遍,才走完程序的一个小分支。。。 |
|
|