⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄

📁 兼容内核漫谈 适合想将Windows上的程序移植到其它平台上的朋友研究查看
💻
📖 第 1 页 / 共 4 页
字号:
}[/code]

    先看调用参数。其中UnsafeConnectedPortHandle是用来返回建立连接以后的通信端口Handle的。之所以说是“Unsafe”是为了强调这可能是在用户空间,因而可能需要把它复制到内核空间的副本ConnectedPortHandle中。其实这也并非特殊,多数系统调用都是这样的。参数PortName是个Unicode字符串,就是目标端口的对象名。另一个参数UnsafeMaximumMessageSize为允许通过本连接(如果成功的话)发送的报文长度设置了一个上限。此外,在请求建立连接的过程中也可以附带着向服务线程发送一些数据,而服务线程在接受或拒绝连接请求时也可以发回来一些数据,参数UnsafeConnectData就是指向为此而设的缓冲区,而UnsafeConnectDataLength则为数据长度。
    指针UnsafeWriteMap和UnsafeReadMap就是与共享内存区有关的参数。如果所有需要传递的报文都是短报文,那么这两个指针也可以是NULL。
    参数Qos就要费些口舌了。这是个指针,指向一个SECURITY_QUALITY_OF_SERVICE数据结构。QoS字面上的意义是服务质量保证,具体则与一种称为“临时身份(Impersonate)”的机制有关。Impersonate在字典上的解释是“人格化、扮演”,实际上是“身份、代表、授权”的意思。在LPC机制中,服务进程应客户进程的请求而执行某些操作。换言之,服务进程是代表着客户进程、以客户进程的名义在执行这些操作,因而服务进程在执行这些操作的过程中所具有的各种权限理应与具体的客户进程相同,这既不是服务进程本身的权限,也不是某种一成不变的权限。不妨拿日常生活中当事人与律师的关系作个对比:律师有律师的法定权限,不管当事人是谁,律师的权限都是一样的,并且一般都是高于当事人所具有的权限,例如律师可以查阅法庭的卷宗,而当事人显然不可以,所以律师并不是以当事人的身份在工作,这样才能维护社会公正。但是,LPC则与此相反,服务进程应该以客户进程的身份操作,否则就有可能被权限较低的进程利用来达到本来不能达到的目的,或者本身权限较高的客户却不能达到应该可以达到的目的。操作系统要保证的恰恰是一套等级森严的体制,而不是社会公正。所以,服务进程在为客户提供本地过程调用时就应该临时转换到客户的身份,不过这是可以选择的。显然,这与访问权限有关,因而与系统的安全机制有关。这方面一则说来话长,二则目前的ReactOS才刚开始触及安全机制的实现,所以我们以后再来专题讨论。在调用NtConnectPort()的时候,指针Qos也可以是NULL,那就表示采用默认的方式,就是服务方不采用客户的身份。
    继续看NtConnectPort()其余的调用参数。如前所述,通过NtConnectPort()建立端口连接时,还可以随同连接请求发送一些数据,而对方也可以回复一些数据。这里的UnsafeConnectData就是个指针,而UnsafeConnectDataLength则是数据的长度。请求连接的进程在NtListenPort()中受阻而进入睡眠,直到对方接受(或拒绝)了连接请求时才被唤醒并且再次受调度运行。这样,当客户进程从NtConnectPort()返回时,缓冲区中的内容已经是从服务进程返回的数据。

    NtConnectPort()的开头一段代码都是为了把用户空间的参数复制到内核中,这里就略去了,我们把注意力集中在实质性的操作。显然,这里的核心是EiConnectPort()。注意这里把WriteMap中ViewBase和TargetViewBase字段的地址,以及ReadMap中ViewSize和ViewBase字段的地址传了下去,这说明EiConnectPort()有可能修改这些字段的数值。

[code][NtConnectPort() > EiConnectPort()]

NTSTATUS STDCALL
EiConnectPort(IN PEPORT* ConnectedPort,
       IN PEPORT NamedPort,
       IN PSECTION_OBJECT Section,
       IN LARGE_INTEGER SectionOffset,
       IN ULONG ViewSize,
       OUT PVOID* ClientSendViewBase,
       OUT PVOID* ServerSendViewBase,
       OUT PULONG ReceiveViewSize,
       OUT PVOID* ReceiveViewBase,
       OUT PULONG MaximumMessageSize,
       IN OUT PVOID ConnectData,
       IN OUT PULONG ConnectDataLength)
{
  . . . . . .

  /* Create a port to represent our side of the connection */
  Status = ObCreateObject (KernelMode, LpcPortObjectType, NULL,
                     KernelMode, NULL, sizeof(EPORT), 0, 0, (PVOID*)&OurPort);
  . . . . . .
  LpcpInitializePort(OurPort, EPORT_TYPE_CLIENT_COMM_PORT, NamedPort);

  /* Allocate a request message. */
  RequestMessage = ExAllocatePool(NonPagedPool,
                  sizeof(EPORT_CONNECT_REQUEST_MESSAGE) +
                  RequestConnectDataLength);
  . . . . . .

  /* Initialize the request message. */
  RequestMessage->MessageHeader.DataSize =
    sizeof(EPORT_CONNECT_REQUEST_MESSAGE) + RequestConnectDataLength -
    sizeof(LPC_MESSAGE);
  RequestMessage->MessageHeader.MessageSize =
    sizeof(EPORT_CONNECT_REQUEST_MESSAGE) + RequestConnectDataLength;
  . . . . . .
  RequestMessage->MessageHeader.SectionSize = 0;
  RequestMessage->ConnectingProcess = PsGetCurrentProcess();
  ObReferenceObjectByPointer(RequestMessage->ConnectingProcess,
                    PROCESS_VM_OPERATION, NULL, KernelMode);
  RequestMessage->SendSectionObject = (struct _SECTION_OBJECT*)Section;
  RequestMessage->SendSectionOffset = SectionOffset;
  RequestMessage->SendViewSize = ViewSize;
  RequestMessage->ConnectDataLength = RequestConnectDataLength;
  if (RequestConnectDataLength > 0)
  {
     memcpy(RequestMessage->ConnectData, ConnectData, RequestConnectDataLength);
  }

  /* Queue the message to the named port */
  EiReplyOrRequestPort(NamedPort, &RequestMessage->MessageHeader,
                            LPC_CONNECTION_REQUEST, OurPort);
  KeReleaseSemaphore(&NamedPort->Semaphore, IO_NO_INCREMENT, 1, FALSE);
  ExFreePool(RequestMessage);

  /* Wait for them to accept our connection */
  KeWaitForSingleObject(&OurPort->Semaphore, UserRequest, UserMode,
                                                    FALSE, NULL);

  /* Dequeue the response */
  KeAcquireSpinLock (&OurPort->Lock, &oldIrql);
  Reply = EiDequeueMessagePort(OurPort);
  KeReleaseSpinLock (&OurPort->Lock, oldIrql);
  CReply = (PEPORT_CONNECT_REPLY_MESSAGE)&Reply->Message;

  /* Do some initial cleanup. */
  ObDereferenceObject(PsGetCurrentProcess());

  /* Check for connection refusal. */
  if (CReply->MessageHeader.MessageType == LPC_CONNECTION_REFUSED)
  {
      . . . . . .
      return(STATUS_PORT_CONNECTION_REFUSED);
  }

  /* Otherwise we are connected. Copy data back to the client. */
  *ServerSendViewBase = CReply->SendServerViewBase;
  *ReceiveViewSize = CReply->ReceiveClientViewSize;
  *ReceiveViewBase = CReply->ReceiveClientViewBase;
  *MaximumMessageSize = CReply->MaximumMessageSize;
  if (ConnectDataLength != NULL)
  {
      *ConnectDataLength = CReply->ConnectDataLength;
      memcpy(ConnectData, CReply->ConnectData, CReply->ConnectDataLength);
  }

  /* Create our view of the send section object. */
  if (Section != NULL)
  {
      *ClientSendViewBase = 0;
      Status = MmMapViewOfSection(Section, PsGetCurrentProcess(),
                  ClientSendViewBase, 0, ViewSize, &SectionOffset,
                  &ViewSize, ViewUnmap, 0, PAGE_READWRITE);
      . . . . . .
  }

  /* Do the final initialization of our port. */
  OurPort->State = EPORT_CONNECTED_CLIENT;

  /* Cleanup. */
  ExFreePool(Reply);
  *ConnectedPort = OurPort;
  return(STATUS_SUCCESS);
}[/code]

    参数ConnectedPort的方向说是IN,看来倒应该是OUT,用来返回新创建的通信端口(见下)的Handle。参数Section是上面传下来的SECTION_OBJECT结构指针。至于其余参数的作用,读者不妨对比一下前面调用这个函数时所使用的实参。
    这个函数的执行大致上可以分成四个阶段。
    第一个阶段为将来(连接请求被接受以后)的通信创建一个“通信端口”,这就是ObCreateObject()和随后的LpcpInitializePort()所做的事。其结果就是代码中的指针OurPort。
    第二个阶段先准备好“连接请求”报文的数据结构,包括通过参数ConnectData传递下来的附加数据,然后就通过EiReplyOrRequestPort()把报文的容器挂入目标端口的接收队列。注意这里的目标端口是个“连接端口”而不是“通信端口”。接着,还要通过KeReleaseSemaphore()对目标端口的“信号量”实行一次V操作,把正在NtListenPort()中睡眠等待的服务线程唤醒。
    第三个阶段就是通过KeWaitForSingleObject()睡眠等待对方是否接受连接请求的答复了。
    第四个阶段,得到了服务方线程的答复以后,客户线程从睡眠中被唤醒,通过EiDequeueMessagePort()从其通信端口的队列中获取对方的应答报文。如果对方接受连接请求的话,就把原先准备下用于发送数据的共享内存区映射到自己的用户空间,并返回映射的地址。
    注意这里映射的只是供客户方写入的区间。还有,这里的指针ClientSendViewBase就是前面的&WriteMap.ViewBase。反向的区间、即供客户方读出的区间是由服务方代为映射的,实际映射的地址通过应答报文中的ReceiveClientViewBase字段返回过来,这里代码中的指针ReceiveViewBase就是前面的&ReadMap.ViewBase。应答报文中的SendServerViewBase字段是服务方读出区间的映射地址。为什么需要知道对方读出区间的映射地址地址呢?这是因为在发往对方的数据中可能会有起着指针作用的数据,对这些数据的值需要在发送出去之前根据共享内存区在对方的映射位置进行“重定位”。那为什么客户方用于读出的区间要由对方代为映射呢?客户方自己当然也可以映射,但是那样的话又得把映射的地址告知对方,那就又得再发送一个报文了。

    再看服务方线程。它在NtListenPort()中被唤醒,以后就从其连接端口的接收队列中获取由客户方发来的报文。如果这不是“连接请求”报文就又回过去睡眠等待,直至接收到“连接请求”报文才从NtListenPort()返回。服务方线程在接收到连接请求以后要作出决定,并通过系统调用NtAcceptConnectPort()加以接受或拒绝。

[code]NTSTATUS STDCALL
NtAcceptConnectPort (PHANDLE  ServerPortHandle,  HANDLE  NamedPortHandle,
            PLPC_MESSAGE  LpcMessage,  BOOLEAN  AcceptIt,
            PLPC_SECTION_WRITE  WriteMap,  PLPC_SECTION_READ  ReadMap)
{
  . . . . . .

  Size = sizeof(EPORT_CONNECT_REPLY_MESSAGE);
  if (LpcMessage)
  {
     Size += LpcMessage->DataSize;
  }

  CReply = ExAllocatePool(NonPagedPool, Size);
  if (CReply == NULL)
    {
      return(STATUS_NO_MEMORY);
    }

  Status = ObReferenceObjectByHandle(NamedPortHandle, PORT_ALL_ACCESS,
                   LpcPortObjectType, UserMode, (PVOID*)&NamedPort, NULL);
  . . . . . .

  /* Create a port object for our side of the connection */
  if (AcceptIt)
  {
      Status = ObCreateObject(ExGetPreviousMode(), LpcPortObjectType, NULL,
                  ExGetPreviousMode(),NULL, sizeof(EPORT), 0, 0, (PVOID*)&OurPort);
      if (!NT_SUCCESS(Status))
      {
        ExFreePool(CReply);
        ObDereferenceObject(NamedPort);
        return(Status);
      }

      Status = ObInsertObject ((PVOID)OurPort, NULL,
                           PORT_ALL_ACCESS, 0, NULL, ServerPortHandle);
      . . . . . .
      LpcpInitializePort(OurPort, EPORT_TYPE_SERVER_COMM_PORT, NamedPort);
  }

  /* Dequeue the connection request */
  KeAcquireSpinLock(&NamedPort->Lock, &oldIrql);
  ConnectionRequest = EiDequeueConnectMessagePort(NamedPort);
  KeReleaseSpinLock(&NamedPort->Lock, oldIrql);
  CRequest =
       (PEPORT_CONNECT_REQUEST_MESSAGE)(&ConnectionRequest->Message);

  /* Prepare the reply. */
  if (LpcMessage != NULL)
  {
      memcpy(&CReply->MessageHeader, LpcMessage, sizeof(LPC_MESSAGE));
      memcpy(&CReply->ConnectData, (PVOID)(LpcMessage + 1),
                                                  LpcMessage->DataSize);
      CReply->MessageHeader.MessageSize =
          sizeof(EPORT_CONNECT_REPLY_MESSAGE) + LpcMessage->DataSize;
      CReply->MessageHeader.DataSize = CReply->MessageHeader.MessageSize -
                CReply->MessageHeader.MessageSize - sizeof(LPC_MESSAGE);
      CReply->ConnectDataLength = LpcMessage->DataSize;
  }
  else
  {
      CReply->MessageHeader.MessageSize =
                                    sizeof(EPORT_CONNECT_REPLY_MESSAGE);
      CReply->MessageHeader.DataSize =
              sizeof(EPORT_CONNECT_REPLY_MESSAGE) - sizeof(LPC_MESSAGE);
      CReply->ConnectDataLength = 0;
  }
  if (!AcceptIt)
  { 
      EiReplyOrRequestPort(ConnectionRequest->Sender,
      &CReply->MessageHeader,
      LPC_CONNECTION_REFUSED,
      NamedPort);
      KeReleaseSemaphore(&ConnectionRequest->Sender->Semaphore,
    IO_NO_INCREMENT,
    1,
    FALSE);
      ObDereferenceObject(ConnectionRequest->Sender);
      ExFreePool(ConnectionRequest); 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -