c++ - 64 位 Windows API 结构对齐导致命名管道上的访问被拒绝错误

标签 c++ winapi struct

我花了两天时间,终于缩小了ERROR_ACCESS_DENIED的来源范围。 (5) 尝试 CallNamedPipe 时出错作为结构对齐的问题。我们有一个 32 位服务和一个 32 位应用程序,我正在尝试将该服务更新为 64 位服务。奇怪的是,在 32 位模式下一切正常,但在 64 位模式下 CallNamedPipe 32 位应用程序报告访问被拒绝错误。

该服务已设置 SECURITY_ATTRIBUTES结构并填充lpSecurityDescriptor具有正确初始化的成员 PSECURITY_DESCRIPTOR 。当传递给 CreateNamedPipe 时,它没有报告任何错误。 。我仍然不知道为什么它没有报告错误;也许不良的安全属性会默默地回退到默认值而不是失败。

经过多次反复(包括早期一些不完整/不正确的更改结构对齐的尝试 - 调试服务启动代码并不容易),我开始意识到将默认结构对齐设置为 1 字节 (/Zp1) 的项目设置是造成问题。当我最终使用#pragma pack(push,8)时在所有出现 #include <windows.h> 之前和#pragma pack(pop)之后,事情就开始起作用了。

我现在的问题是为什么这是必要的。我发现 Windows API 中有许多头文件通过包含 pshpack1.h 来显式设置结构对齐。 , pshpack2.h , pshpack4.h , pshpack8.hpoppack.h 。我如何知道 Windows API 何时控制其自身的打包以及何时由我负责设置正确的打包级别?难道不是每个关心结构对齐的 Windows API 声明都应该设置正确的打包,这样我就不必筛选系统中的所有代码来查找包括 Windows API 头文件在内的任何内容吗?一种替代方法是更改​​项目设置以使用默认结构对齐,但我必须假设这样做是因为我们系统中依赖 1 字节结构对齐的代码比依赖 Windows API 的代码还要多。

这是服务器端代码的样子:

BOOL OpenMyPipe()
{
   SECURITY_ATTRIBUTES sa;
   PSECURITY_DESCRIPTOR pSD;

   printf("sizeof(SECURITY_ATTRIBUTES) == %d\n", sizeof(SECURITY_ATTRIBUTES));
   pSD = (PSECURITY_DESCRIPTOR)GlobalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
   if (pSD == NULL)
      return FALSE;

   if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION))
      return FALSE;

   if (!SetSecurityDescriptorDacl(pSD, TRUE, (PACL)NULL, FALSE))
      return FALSE;

   sa.nLength = sizeof(sa);
   sa.lpSecurityDescriptor = pSD;
   sa.bInheritHandle = FALSE;

   char szPipeName[MAX_PATH];
   sprintf(szPipeName, "\\\\.\\pipe\\%s%s", "__SQLTST_",
      "MAINMR");

   hPipe = CreateNamedPipe(szPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
      PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
      1, 0, 0, NMPWAIT_WAIT_FOREVER, &sa);

   if (hPipe == INVALID_HANDLE_VALUE)
      return FALSE;
   return TRUE;
}

为了简单起见,我使用一个小型 VB.NET 客户端验证了这一点:

Sub Main()
  Dim pipes = System.IO.Directory.GetFiles("\\.\pipe\")

  Using pipe As New System.IO.Pipes.NamedPipeClientStream(".", "__SQLTST_MAINMR")
     Dim message(16) As Byte
     pipe.Connect(3000)
     Array.Copy(BitConverter.GetBytes(Process.GetCurrentProcess().Id), message, 4)
     pipe.Write(message, 0, 16)
  End Using
End Sub

我相信只有当服务器端代码在不同的帐户(例如系统帐户)下运行时才会发生该错误。但我不知道如何轻松地测试它。我所知道的是,即使没有 SECURITY_ATTRIBUTES,上面的代码也不会失败。当它全部在与常规应用程序代码相同的帐户下运行时进行设置。当然,您还必须将服务器代码中的结构对齐设置为 1 字节才能看到错误。

最佳答案

Windows SDK 期望打包为 8 字节。来自 Using the Windows Headers

Projects should be compiled to use the default structure packing, which is currently 8 bytes because the largest integral type is 8 bytes. Doing so ensures that all structure types within the header files are compiled into the application with the same alignment the Windows API expects. It also ensures that structures with 8-byte values are properly aligned and will not cause alignment faults on processors that enforce data alignment.

这对于确保数据结构按照系统预期对齐是必要的。我怀疑不明确执行此操作的原因是他们想要默认值,因此 why ask for anything else 。更换包装的情况相对较少,应仅限于特定情况。如果微软将#pragma pack(push,8)添加到每个头文件中,他们就会隐含地表示改变对齐方式是正常的。

未对齐的结构可以节省空间,但会降低性能,如 alignment faults访问未对齐成员时生成。

出于多种原因,Windows SDK 确实会更改结构的对齐方式。一种可能是需要读取 32 位或 64 位数据结构的文件格式。例如,可以使用 IMAGE_THUNK_DATA64IMAGE_THUNK_DATA32 读取 PE 文件格式。前者需要 8 字节填充,而后者需要 4 字节填充。同样,Wininet.h 将以不同方式打包数据结构,具体取决于它是针对 32 位代码还是针对 64 位代码进行编译。这些是包装上的合法更改,但有特定的原因。

关于c++ - 64 位 Windows API 结构对齐导致命名管道上的访问被拒绝错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29518184/

相关文章:

c++ - C++中delete和delete[]的区别

c++ - 我正在创建一个 C++ 程序,其中用户有 2 次尝试密码用户名组合的尝试。如果他们不能得到它,他们的程序就会停止

c++ - 64 位 SendMessage API

c++ - 读取进程的进程内存不会返回所有内容

c - 如何从打开的句柄中获取目录名称/路径

Swift:闭包如何捕获值类型的变量?

c++ - 什么是多重虚拟继承?

C++ 类设计为每个不同的行为提供多个接口(interface)

arrays - 在结构体的数组字段中转义 HTML

c++ - C动态成员结构