《Windows via C/C++》的原文摘录
垮进程边界共享内核对象的第二个办法是为对象命名。许多(但不是全部)内核对象都可以进行命名。
CreateMutex,CreateEvent,CreateSemaphore,CreateWaitableTimer,CreateFileMapping,CreateObject这些API都可以创建命名的内核对象。
用于创建内核对象的函数总是返回具有完全访问权限的句柄。如果想限制一个句柄的访问权限,可以使用这些函数带Ex后缀的扩展版本。
通过名称来创建内核对象共享时,请注意:进程B调用CreateMutex时,它向函数传递安全属性信息和第二个参数。如果已经存在一个指定名称的对象,这些参数就会被忽略。事实上,可以用GetLastError来判断自己刚才是真的创建一个新的内核对象还是仅仅打开了一个现有的。
调用CreateXX函数和调用OpenXX函数的主要区别在于,如果对象不存在,CreateXX会创建它;OpenXX函数则不同,如果对象不存在,它只是简单以调用失败告终。所以,微软没有提供任何专门机制来保证我们创建独一无二的对象名。所以如果来自两个不同公司的两个程序都试图创建名为“MyObejct”的对象,那么就会出现问题,所以一般建议是创建一个GUID,并将这个GUID的字符串形式作为自己的对象名使用。 (查看原文 )
在正在运行终端服务的计算机中,有多个用于内核对象的命名空间。其中一个是全局命名空间,所有客户端都能访问的内核对象要放在这个命名空间中。这个命名空间主要由服务使用。此外每个客户端会话(Client Session)都有自己的一个命名空间
一个服务的命名内核对象始终位于全局命名空间内的。默认情况下,在终端服务中,引用程序自己的命名内核对象在会话的命名空间内。我们可以强制把一个命名对象放入全局命名空间,做法是在其名称前加入“Global\”前缀,同理,可以把一个命名对象放入当前会话的命名空间,做法是在其名称前加入“Local\”前缀。
CreateEvent(NULL,FALSE,FALSE,TEXT("Global\\MyName"))
CreateEvent(NULL,FALSE,FALSE,TEXT("Local\\MyName")) (查看原文 )
创建一个内核对象时,可以传递一个安全描述符指针,从而保护该对象的访问。不过在windows Vista以前,不可能防止一个共享对象的名称被“劫持”。任何进程,即使是最低权限的进程都能用任何指定的名称来创建一个对象。这样就很容易另外写一个应用程序来创建一个同名的内核对象。如果它先于单实例应用程序启动,那么单实例的应用程序就变成了无实例的应用程序-------始终一启动就退出,错误地认为它自己的另一个实例已经在运行。这就是DOS攻击的基本机制。但是注意,未命名的内核对象不会遭到DOS攻击。未命名的对象是不能在进程之间共享的。
CreatePrivateNamespace创建一个专有的命名空间。 (查看原文 )
跨进程边界共享内核对象的最后一招是使用DuplicateHandle,简单的说,这个函数获得一个进程的句柄表中的一个记录项,然后在另一个进程的句柄表中创建这个记录的一个副本。 (查看原文 )
第1章 错误处理
P3 常见Windows函数返回值数据类型:VOID, BOOL, HANDLE, PVOID, LONG/DWORD
P4 DWORD GetLastError(); 返回由上一个函数调用设置的线程的32位错误代码 (WinError.h)
在Visual Studio 的Watch窗口中使用$err,hr来查看当前线程的“上一个错误代码”
P6 FormatMessage
第2章 字符和字符串处理
P21 _countof //errno_t result = _tcscpy_s(szBuffer, _countof(szBuffer), TEXT("0123456789"));
P24 CompareString(); CompareStringOrdinal();
P25 修改与字符串有关的计算:传入的是缓冲区大小的字符数,而不是字节数,即_countof(szBuffer)非sizeof(szBuffer);如需要为一个字符串分配一个内存块,而且知道字符串中的字符数,那么内存是以字节来分配的,即malloc(nCharacters*sizeof(TCHAR)),非malloc(nCharacters)
P28 ANSI与UNICODE之间的相互转化,一般是把ANSI版本函数中的字符串转换为UNICODE,之后调用UNICODE函数,把结果再转换为ANSI
P30 用IsTextUnicode函数判断文本是否Unicode编码有可能返回错误结果。测试缓冲区内的字节数越多,结果越精确。
第3章 内核对象 kernel object
P32 内核对象的几种类型:访问令牌对象,事件对象,文件对象,文件映射对象,I/O完成端口对象,作业对象,邮件槽对象,互斥量对象,管道对象,进程对象,信号量对象,线程对象,可等待计时器对... (查看原文 )
P36 进程内核对象句柄表:只是由数据结构组成的数组,每个数据结构包含指向一个内核对象的指针,一个访问掩码和一些标志。
P38 注意创建一个新内核对象后返回的句柄,有可能是NULL,也有可能是INVALID_HANDLE_VALUE
P40 假定忘记调用CloseHandle,会发生对象泄漏的情况吗?嗯,不一定。
P42 3种不同的机制允许进程共享内核对象:1使用对象句柄继承,2为对象命名,3复制对象句柄
P44 对象句柄的继承只会在生成子进程的时候发生。之后父进程又有什么新的句柄被创建跟子进程无关,不会被继承。
子进程并不知道自己继承了任何句柄。
P46 改变句柄的标志 SetHandleInformation 其中HANDLE_FLAG_PROTECT_FROM_CLOSE是为了防止子进程在生成孙进程之前就已经先把句柄关闭了,保护孙进程有更大的机会继承到子进程的句柄,可以让孙进程与父进程通信
P48 可以在创建一个新内核对象后,马上调用GetLastError,判断是真创建还是仅打开了一个现有的。
P51 专有命名空间
P58 跨进程边界共享内核对象,使用DuplicateHandle
第4章 进程
P65 线程的定义:一个内核对象,一个地址空间
P66 链接器设置,对于GUI程序是/SUBSYSTEM:WINDOWS;对于CUI程序是/SUBSYSTEM:CONSOLE
P71 HMODULE和HINSTANCE完全是一回事。
P72 GetModuleHandle函数的两大重要特征,1只检查主调进程的地址空间。2调用GetModuleHandle并向其传递NULL值,会返回进程的地址空间中的可执行文件的基地址。
P74 获得进程的环境变量GetEnvironmentStrings() ; CUI专用,利用TCHAR* e... (查看原文 )
P101 调用ExitProcess或ExitThread会导致进程或线程直接终止运行——再也不会返回当前函数调用。
P101 只有在无法通过其他方法来强制进程退出时,才应使用TerminateProcess
一旦进程终止,系统会保证不留它的任何部分。绝对没有任何办法知道那个进程是否运行过。进程在终止后绝对不会泄漏任何东西。
P104 Windows提供了几种方式在不同进程之间传递数据,DDE,OLE,管道,邮件槽;共享数据最方便的方式之一就是使用内存映射文件。
P105 独立的进程:一旦进程创建并开始执行,父进程就不再与新进程通信,或者不用等它完成工作之后才继续自己的工作。
P111 CreateProcess没有专门提供标记或参数来指定对这种权限提升的需求。相反,我们需要调用ShellExecuteEx函数。
第5章 作业
P121 可以将作业对象想象成一个进程容器。限制进程能够做什么
P124 创建一个新的作业内核对象HANDLE CreateJobObject(PSECURITY_ATTRIBUTES psa, PCTSTR pszName); 最后一个参数对此作业对象进行命名,使其能够由另一个进程通过OpenJobObject进行访问
确定在自己的代码中不再访问作业对象,就必须调用CloseHandle来关闭它的句柄。关闭一个作业对象,不会迫使作业中的所有进程都终止运行。作业对象实际知识加了一个删除标记,只有在作业中的所有进程都已终止运行之后,才会自动销毁
关闭作业的句柄 CloseHandle(hJob)会导致所有进程都不可访问此作业,即使这个作业仍然存在。
P124 ~基本限额和扩展基本限额(用于防止作业中的进程独占系统资源)
~基本UI限制(用于防止作业内的进程更改用户界面)
... (查看原文 )
P131 作业中的进程可以调用QueryInformationJobObject获得其所属作业的相关信息
第6章 线程基础
理解线程跟进程的区别( 进程:一个内核对象,一个地址空间 )
理解如何用线程内核对象来管理线程
P140 每个进程至少都有一个线程
线程也有2个部分组成:1线程的内核对象,2线程栈。几乎不涉及记录活动,不需要占用多少内存
线程要在其进程的地址空间内执行代码和处理数据。共享内核对象句柄
P144 创建新线程CreateThread (查看原文 )
新线程可以访问进程内核对象的所有句柄、进程中的所有内存以及同一个进程中其他所以线程的栈。同一个进程中的多个线程可以很容易地相互通信。
如果写C/C++代码,正确选择应该是Microsoft C++运行库函数_beginthreadex
psa 指向SECURITY_ATTRIBUTES结构指针,一般传入NULL。
cbStackSize 指定线程可以为其线程栈使用多少地址空间。每个线程都拥有自己的栈。
pfnStartAddr 指定新线程执行的线程函数的地址。
pvParam 与最初传给CreateThread函数的pvParam参数是一样的。Windows是一个抢占式的多线程系统,这意味着新的线程和调用CreateThread函数的线程可以同时执行。
dwCreateFlags : 0 立即执行; CREATE_SUSPENDED 初始化悬挂
pdwThreadID 必须是DWORD的一个有效地址。可以是NULL,表示用户对线程ID无兴趣
P148 4中方式终止运行线程:1线程函数返回 2线程通过调用ExitThread函数Kill自己 3同一个进程或另一个进程中的线程调用TerminateThread函数 4包含线程的进程终止运行
ExitThread函数没有返回值。总是kill主调线程,C/C++使用_endthreadex
TerminateThread能kill任何线程。 (查看原文 )
hThread参数标识了要终止的那个线程的句柄。线程终于运行时,其退出代码将变成dwExitCode参数传递值。同时,线程的内核对象的使用计数会递减。
此函数是异步的。一旦线程不再运行,系统中就没有别的线程再用该线程的句柄了。但是,其他线程可以调用GetExitCodeThread来检查hThread所标识的那个线程是否已终止运行。
····线程内幕
_beginthread函数,不能创建具有安全属性的线程,不能创建让线程立即挂起,也不能获得线程ID值。
_endthread函数在调用ExitThread前,会调用CloseHandle,向其传入新线程句柄。
GetProcessTime函数,一个线程可以查询其所在进程的使用时间。
P162 HANDLE GetCurrentProcess();
HANDLE GetCurrentThread(); 这2个函数都返回到主调线程的进程内核对象或线程内核对象的一个伪句柄。
DuplicateHandle函数可以将伪句柄转换为一个真正的句柄。正常情况下,利用这个函数,可以根据与进程A相关的一个内核对象句柄来创建一个新句柄,并让它同进程B相关。另外,可以把进程的伪句柄转换为真正的进程句柄。
第7章 线程调度、优先级和关联性
P167 当线程挂起计数为0时,线程就成为可调度的了,除非还在等待某个事件发生。
ResumeThread函数成功,将返回线程的前一个挂起计数;否则返回0xFFFFFFFF
调用SuspendThread必须小心,因为试图挂起一个线程时,不知道线程在做什么。例如,线程正在分配堆中的内存,线程将锁定堆。
P168 Windows没有提供其他方式挂起进程中所有线程,因为存在竞态条件问题。
ToolHelp函数来枚举系统中的线程列表。
P169 关于S... (查看原文 )
By default, UAC forces applications to run in a restricted context for security safety even though the current user is part of the Administrators group. (查看原文 )
Let's say that you forget to call CloseHandle – will there be an object leak? Well, yes and no. It is possible for a process to leak resource (such as kernel objects) while the process runs. However, when the process terminates, the operating system ensures that all resources used by the process are freed – this is guaranteed. (查看原文 )
If you are leaking lots of objects, they are probably not named because you can create only one instance of a named object – other attempts just open it. (查看原文 )
Different mechanisms that allow process to share kernel object: (1) using object handle inheritance; (2) naming objects; (3) duplicating object handles. (查看原文 )
Sometimes I hear people use the term object inheritance. However, there is no such thing as object inheritance; Windows supports object handle inheritance. In other words, it is the handles that are inheritable, not the object themselves. (查看原文 )
The content of kernel objects is stored in the kernel address space that is shared by all processes running on the system. (查看原文 )
Be aware that object handle inheritance applies only at the time the child process is spawned. If the parent process were to create any new kernel objects with inheritable handles, an already-running child process would not inherit these new handles. (查看原文 )
Object handle inheritance has one very strange characteristic: when you use it, the child has no idea that it has inherited any handles. (查看原文 )
By far, the most common way for a child process to determine the handle value of the kernel object that it's expecting is to have the handle value passed as a command-line arguments to the child process. (查看原文 )
Unfortunately, Microsoft offers no guidance for assigning unique names to kernel objects. To make matters worse, all these objects share a single namespace even though they don't share the same type. (查看原文 )