Windows核心编程笔记(四) 进程相关

1、Windwos程序分为GUI程序和Console程序,通过设置VS的链接器开关/SUBSYSTEM:XX来设置,/SUBSYSTEM:CONSOLE控制台程序,基于命令行,/SUBSYSTEM:

WINDOWS GUI界面程序,基于窗口。


2、使用VS编写Windows程序会在程序的开始是C/C++运行库启动函数,它初始化了一些全局变量,参见http://blog.csdn.net/wangpengk7788/article/details/53914526


3、实例句柄,实例句柄的本质是一个模块被加载到进程地址空间中的基地址,例如,通常我们写的EXE程序在运行时候被加载到(32位环境下)以0x00400000开始的地方,DLL文件被加载到0x00100000开始地址的地方,这些基地址就是实例句柄,EXE文件的实例句柄是HINSTANCE通过main函数的参数传递进来,  DLL文件的是HMOUDLE在DllMain中做为参数传递进来,他们在本质上是一样的因此可以互相转换

HMODULE WINAPI GetModuleHandle(
  __in_opt  LPCTSTR lpModuleName
);
该函数可以检查某一个名字的模块是否被加载到了进程中,如果已加载返回加载的基地址HMODULE,如果给该API传递NULL参数,返回的是主调进程的可执行文件的基地址


4、在_tWinMain中进程的前一个实例句柄,是为了兼容16位系统,已废弃,我们可以不为其制定参数名,并用UNREFERENCE_PARAMETER宏消除编译器警告


5、可以调用GetCommandLine函数获取进程的命令行参数,C运行库的代码会将去掉可执行文件路径以后的命令参数传递给WinMain函数。

在Shell32.dll中导出的CommandLineToArgvW函数可以将命令行参数转换为,参数数组的形式

#include <windows.h>
#include <stdio.h>
#include <shellapi.h>

int __cdecl main()
{
   LPWSTR *szArglist;
   int nArgs;
   int i;

   szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
   if( NULL == szArglist )
   {
      wprintf(L"CommandLineToArgvW failed\n");
      return 0;
   }
   else for( i=0; i<nArgs; i++) printf("%d: %ws\n", i, szArglist[i]);

// Free memory allocated for CommandLineToArgvW arguments.

   LocalFree(szArglist);

   return(1);
}




6、每个进程都有一个与它相关联的环境块。用以定义工作环境、保存有用信息,使系统获得相关设置。应用程序经常利用环境变量让用户精细其行为。用户创建一个环境变量并进行初始化,此后应用程序运行时会正在环境块中查找变量,如果找到变量就会解析变量的值,并调整自己的行为。它所占用的内存是在进程地址空间内分配的。同样调用GetEnvironmentStrings函数可以获得完整的环境块。通常子进程会继承一组环境变量,这些环境变量和父进程的环境变量相同,父进程可以控制那些环境变量允许子进程继承。注意子进程继承的仅仅是父进程环境变量的副本,它们不共享同一个环境块。GetEnvironmentVariable函数可以用来判断一个环境变量是否存在。
在下图中我们可以看到形如%USERPROFILE%的字符串,它表示两个%之间的这部分内容是一个可替换的变量。该变量在环境变量中已经被定义。
 
 
    可以使用SetEnvironmentVariable来添加、删除或修改一个变量。
Windows不建议使用入口函数的参数来访问命令行或是环境变量,而应该使用以上介绍的各种函数。应该将它们当做只读变量,不要对它们进行修改。
     在多处理器的系统中,可以强迫线程在某个cpu上运行,这成为处理器关联性。子进程继承了其父进程的关联性。
     如果不提供完整的路径名,windows函数就会在当前驱动器的当前目录查找文件和目录。如:调用CreateFile打开一个文件时,如果仅指定文件名,系统将在当前驱动器和目录查找该文件。
 
系统在内部跟踪记录这一个进程当前驱动器和目录,这些信息是以进程为单位来维护的,如果该进程的一个线程更改了当前驱动器和目录,则只影响本进程的所有线程。
 
     一个线程可以使用GetCurrentDirectory和SetCurrentDirectory来获得和设置当前驱动器和目录。子进程的当前目录默认为每个驱动器的根目录。如果父进程希望子进程继承它的当前目录,就必须在生成子进程之前,添加环境变量。
 
使用GetVersionEx可以获得window系统的版本号。




7、进程的错误模式,每个进程都关联的了一组标志,这些标志的作用是让系统知道进程是如何响应严重错误的。

Value Meaning 
0 Use the system default, which is to display all error dialog boxes.
 
SEM_FAILCRITICALERRORS
0x0001 The system does not display the critical-error-handler message box. Instead, the system sends the error to the calling process.
                    系统不显示错误对话框,相反,系统返回错误代码给主调进程
SEM_NOALIGNMENTFAULTEXCEPT
0x0004 The system automatically fixes memory alignment faults and makes them invisible to the application. It does this for the calling process and any descendant processes. This feature is only supported by certain processor architectures. For more information, see the Remarks section. 
系统自动修复内存对齐,并使应用程序看不到这些错误
After this value is set for a process, subsequent attempts to clear the value are ignored.
 
SEM_NOGPFAULTERRORBOX
0x0002 The system does not display the general-protection-fault message box. This flag should be set only by debugging applications that handle general protection (GP) faults themselves with an exception handler.

系统不显示常规保护错误对话框,这个标志应该由调试中的程序来设置,该调试程序用一个异常处理例程来处理这些常规错误。
 
SEM_NOOPENFILEERRORBOX
0x8000 The system does not display a message box when it fails to find a file. Instead, the error is returned to the calling process.
                 系统在查找文件失败市不显示错误对话框


8、当调用CreateFile来打开一个文件是,提供的是一个不完整的路径名,那么系统会在当期驱动器和目录中查找,系统在内部跟踪记录着每一个进程的当前驱动器和目录。

DWORD WINAPI GetCurrentDirectory(
  __in   DWORD nBufferLength,
  __out  LPTSTR lpBuffer
);

驱动器的当前目录保存在环境变量块中。


DWORD WINAPI GetFullPathName(
  __in   LPCTSTR lpFileName,
  __in   DWORD nBufferLength,
  __out  LPTSTR lpBuffer,
  __out  LPTSTR *lpFilePart
);


9、 GetVersion和GetVersionEx获取系统版本信息,参见MSDN




CreateProcess 创建进程的API



BOOL WINAPI CreateProcess(
  __in_opt     LPCTSTR lpApplicationName,
  __inout_opt  LPTSTR lpCommandLine,
  __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
  __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in         BOOL bInheritHandles,
  __in         DWORD dwCreationFlags,
  __in_opt     LPVOID lpEnvironment,
  __in_opt     LPCTSTR lpCurrentDirectory,
  __in         LPSTARTUPINFO lpStartupInfo,
  __out        LPPROCESS_INFORMATION lpProcessInformation
);

一个线程调用CreateProcess时,系统将创建一个进程内核对象(一个存在于内核的结构体对象实例,系统用它来管理进程,该结构存放于进行相关的一些信息),并将引起计数设置为,然后系统为新创建的进程分配进程虚拟地址空间,并将可执行文件(EXE和其所依赖的DLL)的代码和数据加载到进程的地址空间中。

系统为新进程的主线程创建一个线程内核对象(同样是一个内核结构体的对象实例,系统用来管理线程,该结构存放于线程相关的一些信息),并将引用计数设置为1,这个主线程一个开始就会运行,线程地起始地址就是EXE文件PE结构中的入口点对应的虚拟内存地址,如果是VS编写的EXE,那么入口点地址就是C/C++运行时库的启动代码,它在初始化工作完成后,调用我们程序的Main函数。


参数:


1、lpCommandLine,字符串类型,作为新进程的命令行参数,在实现中系统会修改这个传入的字符串,因此应该避免使用放在PE静态区的字符串做为此参数的实参。

可以使用该参数指定一个完整的命令行,这样函数会以该参数来创建一个新进程,系统会检查字符串的第一个标记,并假定次标识使我们想运行的可以执行文件的名称,然后按照以下顺序搜索可执行文件

(1)主调进程EXE文件所在的目录

(2)主调进程的当前目录

(3)Windows目录

(4)PATH环境变量中列出的目录

如果包含的是一个完整的绝对路径则不会进程搜索,只要lpApplicationName参数为NULL就会发生这样的情况。


2、lpApplicationName,如果该参数不是一个完整路径,则在当期目录中查找,否则应该指定一个完整的路径名,该参数必须指定可执行文件的扩展名如.exe

当该参数不为空时lpCommandLine做为新进程的参数。

3、lpProcessAttributes,和lpThreadAttributes  bInheritHandles      关于返回的进程句柄是否可以被 再次创建的进程 继承,不做讨论


4、dwCreationFlags  ,参见MSDN,比较重要的有,CREATE_SUSPENDED  创建后挂起主线程,DEBUG_PROCESS创建一个用于调试的进程


5、lpEnvironment,为新进程指定环境变量块,如果为NULL则继承父进程的环境变量,


6、lpCurrentDirectory ,如果为NULL ,新进程使用跟父进程一样的当前驱动器和目录。


7、lpStartupInfo,STARTUPINFO 或者 STARTUPINFOEX 结构体指针,用来设置创建新的新进程的一些信息,可以再新进程中调用GetStartupInfo来获取该结构。

typedef struct _STARTUPINFO {
  DWORD  cb;  结构体大小
  LPTSTR lpReserved;  保留
  LPTSTR lpDesktop;   在哪个桌面启动程序,如果桌面存在则于进程关联,不存在则创建桌面,如果为NULL与当前桌面关联
  LPTSTR lpTitle;      控制窗口标题,为NULL则 窗口标题为EXE的名字
  DWORD  dwX;          窗口位置的X坐标
  DWORD  dwY; 		窗口位置的Y坐标
  DWORD  dwXSize;	窗口的宽度
  DWORD  dwYSize;	窗口的高度    这两个在创建窗口时使用CW_USERDEFAULE时起作用
  DWORD  dwXCountChars;
  DWORD  dwYCountChars;	控制台窗口的高度和宽度,用字符数表示	
  DWORD  dwFillAttribute;	控制台的文本和背景色	
  DWORD  dwFlags;          
  WORD   wShowWindow;          在dwFlags为 STARTF_USESHOWWINDOWS是起作用,在调用ShowWindows时用SW_SHOWDEFAULET作参数时 用该值标识显示模式
  WORD   cbReserved2;
  LPBYTE lpReserved2;          保留,在C运行库初始化时有用到
  HANDLE hStdInput;            控制台的输入缓冲区句柄
  HANDLE hStdOutput;           缓冲区句柄	
  HANDLE hStdError;            缓冲区句柄
} STARTUPINFO, *LPSTARTUPINFO;


dwFlags 指示哪些结构中的内容起作用,参见MSDN



typedef struct _STARTUPINFOEX {
  STARTUPINFO                 StartupInfo;
  PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
} STARTUPINFOEX
第二项是一个指针  可已用InitializeProcThreadAttributeList来申请
OOL WINAPI InitializeProcThreadAttributeList(
  __out_opt   LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,   在返回中填充该指针
  __in        DWORD dwAttributeCount,  个数
  __reserved  DWORD dwFlags,  保留的,为0
  __inout     PSIZE_T lpSize    返回实际需要的字节数
);

在申请好以后用UpdateProcThreadAttribute填空内容


BOOL WINAPI UpdateProcThreadAttribute(
  __inout    LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
  __in       DWORD dwFlags,  保留 为NULL
  __in       DWORD_PTR Attribute,
  __in       PVOID lpValue,   值  保留直到DeleteProcThreadAttributeList释放
  __in       SIZE_T cbSize,  值得大小
  __out_opt  PVOID lpPreviousValue, 保留 为NULL
  __in_opt   PSIZE_T lpReturnSize  保留 为NULL
);
其中 Attribute  用来指示 lpValue实际需要指向的数据的类型,书上介绍了两种 ,其他的参考MSDN

PROC_THREAD_ATTRIBUTE_HANDLE_LIST    lpValue指向一组句柄(子进程只能继承这组句柄),该组句柄必须是可继承的,而且不能是伪句柄


PROC_THREAD_ATTRIBUTE_PARENT_PROCESS      lpValue指向一个进程句柄,那么新的进程以该句柄标识的进程为父进程,同时新进程从该进程继承 句柄、处理器关联性、优先级、配额、用户令牌、关联的作业。’

在使用完这个申请到的空间之后 调用 DeleteProcThreadAttributeList  释放资源。


8、lpProcessInformation,PROCESS_INFORMATION结构体指针,CreateProcess函数返回过程中该结构被填充。

typedef struct _PROCESS_INFORMATION {
  HANDLE hProcess;   //填充为新进程的句柄
  HANDLE hThread;    //填充为新进程的主线程句柄
  DWORD  dwProcessId;//新进程ID
  DWORD  dwThreadId; //主线程ID
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
在不对子进程进行操作的情况下,经过关闭获取到进程和线程句柄。


结束进程的4中方式

1、没有创建新线程的情况下,主线程的入口函数返回

2、进程中的某一个线程调用ExitProcess

3、任何进程中的任何线程调用TerminateProcess,传递给它需要关心的进程的句柄,前提是需要有足够的权限。

4、进程中的所有线程“自然死亡”,线程执行完成退出



主线程的入口函数返回,应该做一下工作:该线程创建的所有对象由对象的析构函数销毁,操作系统释放线程栈使用的内存,系统将退出代码设为入口函数的返回值,系统递减进程内核对象的使用计数。

应避免使用TerminateProcess  ExitProcess  因为这两个函数在不通知进程中任何线程做退出工作的情况下,结束进程。



当一个进程终止时系统做一下操作:  

(一)终止进程中的所有线程

(二)释放所有用户对象,GDI对象,关闭所有内核对象(如果该内核对象没有在其他进程中被打开)

(三)进程的退出代码从STILL_CATIVE变为传给TerminateProcess  ExitProcess 的退出码

(四)进程内核对象变为触发状态

(五)进程内核对象使用计数递减1


进程内核对象生命周期可能长于进程,如果一个进程内核对象被其他进程打开,那么及时进程终止,进程地址空间被释放,进程内核对象仍然存在,直到内核对象引用计数为0

我们可以调用GetExitCodeProcess来获取进程的退出码,如果退出码是STILL_ACTIVE表示进程还在运行,否则表示进程已终止

BOOL WINAPI GetExitCodeProcess(
  __in   HANDLE hProcess,
  __out  LPDWORD lpExitCode
);


UAC 用户账户控制


在VISTA及之后的系统中引入了UAC,系统会使用一个经过筛选后的令牌与每个由资源管理创建的进程关联在一起,使用这个筛选后的令牌没有访问受限制的资源的权限,必须选择以管理员身份运行,如果程序的可执行文件中嵌入了RT_MAINIFEST资源,

<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
        <requestedPrivileges>
            <requestedExecutionLevel
                level="highestAvailable"              //需要执行的权限
                uiAccess="false"
            />
        </requestedPrivileges>
    </security>
</trustInfo>
</assembly>


level="highestAvailable"     以当前最高权限运行,如果系统以管理员身份登录运行弹出提升权限对话框,如果以普通用户身份登录,程序以标准权限启动

level="requireAdministrator"  程序必须以管理员权限启动,弹出提升权限对话框

 level="isInvoker"  程序使用与主调进程同样的权限运行


使用 ShellExecuteEx 要求提升子进程权限


typedef struct _SHELLEXECUTEINFO {
  DWORD     cbSize;
  ULONG     fMask;
  HWND      hwnd;
  LPCTSTR   lpVerb;
  LPCTSTR   lpFile;
  LPCTSTR   lpParameters;
  LPCTSTR   lpDirectory;
  int       nShow;
  HINSTANCE hInstApp;
  LPVOID    lpIDList;
  LPCTSTR   lpClass;
  HKEY      hkeyClass;
  DWORD     dwHotKey;
  union {
    HANDLE hIcon;
    HANDLE hMonitor;
  } DUMMYUNIONNAME;
  HANDLE    hProcess;
} SHELLEXECUTEINFO, *LPSHELLEXECUTEINFO;

如果 lpverb为runas  lpFile为可执行文件路径,弹出提升权限对话框,要求以管理员身份运行程序


#include <Windows.h>
#include <ShellAPI.h>

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	SHELLEXECUTEINFO sei = {0};
	sei.cbSize = sizeof(SHELLEXECUTEINFO);
	sei.lpVerb = TEXT("runas");
	sei.lpFile = TEXT("notepad.exe");
	sei.nShow = SW_SHOWNORMAL;
	if (!ShellExecuteEx(&sei))
	{
		DWORD erCode = GetLastError();
		if (erCode == ERROR_CANCELLED)
		{
			MessageBox(NULL,TEXT("User selected cancel!"),TEXT("Notes!"),NULL);
		}
		else
		{
			if (erCode == ERROR_FILE_NOT_FOUND)
			{
				MessageBox(NULL,TEXT("File to  executed don't found"),TEXT("Notes!"),NULL);
			}
		}

	}


	return 0;



使用GetTokenInformation获取进程当前令牌信息。


BOOL WINAPI GetTokenInformation(
  __in       HANDLE TokenHandle,
  __in       TOKEN_INFORMATION_CLASS TokenInformationClass,
  __out_opt  LPVOID TokenInformation,
  __in       DWORD TokenInformationLength,
  __out      PDWORD ReturnLength
);


查看进程是否处于被提升权限下

 BOOL GetProcessElevation(TOKEN_ELEVATION_TYPE * pElevationType,BOOL * pIsAdmin)
{
	DWORD dwLength;
	HANDLE hToken;
	BOOL ret= FALSE;
	if(!OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&hToken))
	{
		return ret;
	}
	if (GetTokenInformation(hToken,TokenElevationType,pElevationType,sizeof(TOKEN_ELEVATION_TYPE),&dwLength))
	{
		BYTE adminSID[SECURITY_MAX_SID_SIZE];
		DWORD dwSize = sizeof(adminSID);
		CreateWellKnownSid(WinBuiltinAdministratorsSid,NULL,&adminSID,&dwSize);
		if (*pElevationType == TokenElevationTypeLimited)
		{
			HANDLE hLinkedToken;
			GetTokenInformation(hToken,TokenLinkedToken,&hLinkedToken,sizeof(HANDLE),&dwLength);
			if (CheckTokenMembership(hLinkedToken,&adminSID,pIsAdmin))
			{
				ret = TRUE;
			}
		}
		else
		{
			*pIsAdmin = IsUserAnAdmin();
			ret = TRUE;
		}

	}
	CloseHandle(hToken);

	return ret;
	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值