- UID
- 2
- 精华
- 积分
- 7736
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
发表于 2015-2-28 22:07:51
|
显示全部楼层
我用vc6做了个实验,为了去掉CRT库,需要在工程设置里将入口函数重置,并使用Release编译,
- #include <windows.h>
- static bool mainexist=false;
- DWORD WINAPI callback(LPVOID param)
- {
- while(mainexist)//保证主线程退出
- {
- Sleep(100);
- }
- MessageBox(NULL,(LPCTSTR)param,(LPCTSTR)param,MB_OK);
- MessageBox(NULL,(LPCTSTR)param,(LPCTSTR)param,MB_OK);
- return 0;
- }
- void start()
- {
- mainexist=true;
- CreateThread(NULL,0,callback,"another thread",0,NULL);
- mainexist=false;
- }
复制代码
运行后,可以看到弹出对话框,只要弹出对话框就说明确实是“主线程退出,而子线程运行”的情况
msdn上介绍的有关进程线程的基础知识:https://msdn.microsoft.com/en-us/library/windows/desktop/ms681917(v=vs.85).aspx
进程拥有:虚拟地址空间、执行代码、系统资源句柄、安全context、进程id、环境变量、优先级、最大最小工作集,且最少有一个在执行的线程
线程拥有:异常处理、调度优先级、tls、线程id、一些用于保存context的系统结构
我上面vc6的程序,用windbg载入并在入口下断点,停下后看调用栈,得到:
0018ff94 772cb5af image00400000+0x1040
0018ffdc 772cb57a ntdll!__RtlUserThreadStart+0x2f
0018ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
我们在最后函数里下断点,重新来过,现在进去看看这2系统函数执行情况:
- ntdll:772CB580 ntdll___RtlUserThreadStart proc near
- ntdll:772CB580
- ntdll:772CB580 ; FUNCTION CHUNK AT ntdll:7731F3C3 SIZE 00000011 BYTES
- ntdll:772CB580
- ntdll:772CB580 push 1Ch
- ntdll:772CB582 push offset unk_772CB5C0
- ntdll:772CB587 call near ptr ntdll__SEH_prolog4_GS
- ntdll:772CB58C mov edi, ecx
- ntdll:772CB58E and dword ptr [ebp-4], 0
- ntdll:772CB592 mov esi, ntdll_Kernel32ThreadInitThunkFunction
- ntdll:772CB598 push edx
- ntdll:772CB599 test esi, esi
- ntdll:772CB59B jz loc_7731F3C3
- ntdll:772CB5A1 mov ecx, esi
- ntdll:772CB5A3 call ntdll___guard_check_icall_fptr
- ntdll:772CB5A9 mov edx, edi
- ntdll:772CB5AB xor ecx, ecx
- ntdll:772CB5AD call esi ; kernel32_BaseThreadInitThunk
- ntdll:772CB5AF mov dword ptr [ebp-4], 0FFFFFFFEh
- ntdll:772CB5B6 call near ptr ntdll__SEH_epilog4_GS
- ntdll:772CB5BB retn
- ntdll:772CB5BB ntdll___RtlUserThreadStart endp
- ntdll:772CB55F ntdll__RtlUserThreadStart proc near
- ntdll:772CB55F
- ntdll:772CB55F var_8 = byte ptr -8
- ntdll:772CB55F arg_0 = dword ptr 8
- ntdll:772CB55F arg_4 = dword ptr 0Ch
- ntdll:772CB55F
- ntdll:772CB55F mov edi, edi
- ntdll:772CB561 push ebp
- ntdll:772CB562 mov ebp, esp
- ntdll:772CB564 push ecx
- ntdll:772CB565 push ecx
- ntdll:772CB566 lea eax, [ebp+var_8]
- ntdll:772CB569 push eax
- ntdll:772CB56A call near ptr ntdll_RtlInitializeExceptionChain
- ntdll:772CB56F mov edx, [ebp+arg_4]
- ntdll:772CB572 mov ecx, [ebp+arg_0]
- ntdll:772CB575 call ntdll___RtlUserThreadStart
- ntdll:772CB57A int 3 ; Trap to Debugger
- ntdll:772CB57A ntdll__RtlUserThreadStart endp
复制代码
分析出的代码大概如下:
- struct _EXCEPTION_REGISTRATION
- {
- struc EXCEPTION_REGISTRATION *Prev; //前一个_EXCEPTION_REGISTRATION结构
- DWORD Handler; //异常处理过程地址
- };
- void _stdcall _RtlUserThreadStart(PTHREAD_START_ROUTINE pfnStartAddr,PVOID pvParam)
- {
- ExceptionChain chain;
- RtlInitializeExceptionChain(&chain);
- RtlUserThreadStart(pfnStartAddr,pvParam);
- }
- void _fastcall RtlInitializeExceptionChain(ExceptionChain* chain)
- {
- if(RtlpProcessECVPolicy == 1)
- return;
- _TEB* teb=getTeb();
- chain->Prev = -1;
- chain->Handler = RtlpFinalExceptionHandler;
- if(teb->NtTib->Self->ExceptionList != -1)
- return;
- teb->NtTib->ExceptionList=chain;
- teb->NtTib->SameTebFlags |= 0x200;
- }
- void _fastcall RtlUserThreadStart(PTHREAD_START_ROUTINE pfnStartAddr,PVOID pvParam)
- {
- __try
- {
- if(!Kernel32ThreadInitThunkFunction)
- {
- pfnStartAddr(pvParam);//同步调用用户态入口函数,我们的程序分支走到这里
- }
- else
- {
- Kernel32ThreadInitThunkFunction(0,pfn);
- }
-
- }
- __finally
- {
- RtlExitUserThread();//最终主线程运行到这里,用户入口的部分运行结束,而用户态代码仍处主线程中,在等待所有子线程结束后才执行完毕。
- }
- }
复制代码
经过试验后发现,在RtlUserThreadStart执行完之前,会等待子线程运行完毕。
根据实验结果,可以总结出出如下结论:
1.进程启动时,系统会为之创建一个线程,该线程通常称主线程,所有其他线程都通过主线程创建
2.主线程结束前,如果用默认的crt库,会结束所有其他子线程
3.系统启动进程的方式是使用ntdll!RtlUserThreadStart调用程序入口,调用入口的线程是主线程
4.主线程结束后,系统调用者ntdll!RtlUserThreadStart最终会调用RtlExitUserThread等待所有线程结束,所有线程结束之后,进程便结束,该函数执行完毕。
在某种程度上说,ntdll!RtlUserThreadStart才是主线程,因为他调用exe入口是个同步过程,而不是异步过程,举例来说,如果exe用户总入口是start(),那么RtlUserThreadStart中的调用方式是call start,等到start执行完毕,他才能继续运行,而此时的确不是用户空间了,但是此时主线程却不应该认为“结束”,我所认为的主线程结束,应该是RtlUserThreadStart结束,而从上面系统汇编代码可以看出,该函数结束前必然会等待所有线程结束。(另外RtlUserThreadStart在ntdll里,其地址<0x80000000,应该也算“用户态”,ntdll.dll也是该exe的地址空间。)
因此经常说的:主线程结束,其他子线程也会结束,这句话是有道理的。 |
|