在可执行文件或DLL的多个实例之间共享静态数据

来源:互联网 发布:java encodeuri 解码 编辑:程序博客网 时间:2024/06/13 22:41

全局数据和静态数据不能被同一个. e x e或D L L文件的多个映像共享,这是个安全的默认设置。但是,在某些情况下,让一个. e x e文件的多个映像共享一个变量的实例是非常有用和方便的。例如,Wi n d o w s没有提供任何简便的方法来确定用户是否在运行应用程序的多个实例。但是,如果能够让所有实例共享单个全局变量,那么这个全局变量就能够反映正在运行的实例的数量。当用户启动应用程序的一个实例时,新实例的线程能够简单地查看全局变量的值(它已经被另一个实例更新);如果这个数量大于1,那么第二个实例就能够通知用户,该应用程序只有一个实例可以运行,而第二个实例将终止运行。

 

每个. e x e或D L L文件的映像都由许多节组成。按照规定,每个标准节的名字均以圆点开头。例如,当编译你的程序时,编译器会将所有代码放入一个名叫. t e x t的节中。该编译器还将所有未经初始化的数据放入一个. b s s节,而已经初始化的所有数据则放入. d a t a节中。

每一节都拥有与其相关的一组属性,这些属性如表1 7 - 1所示。

表17-1 .exe或D L L文件各节的属性

属性含义R E A D该节中的字节可以读取W R I T E该节中的字节可以写入E X E C U T E该节中的字节可以执行S H A R E D该节中的字节可以被多个实例共享(本属性能够有效地关闭c o p y - o n - w r i t e机制)

使用M i c r o s o f t的Visual Studio的D u m p B i n实用程序(带有/ H e a d e r s开关),可以查看. e x e或D L L映射文件中各个节的列表。下面选录的代码是在一个可执行文件上运行D u m p B i n程序而生成的:

SECTION HEADER #1   .text name   11A70 virtual size    1000 virtual address   12000 size of raw data    1000 file pointer to raw data       0 file pointer to relocation table       0 file pointer to line numbers       0 number of relocations       0 number of line numbers60000020 flags         Code         Execute ReadSECTION HEADER #2  .rdata name     1F6 virtual size   13000 virtual address    1000 size of raw data   13000 file pointer to raw data       0 file pointer to relocation table       0 file pointer to line numbers       0 number of relocations       0 number of line numbers40000040 flags         Initialized Data         Read OnlySECTION HEADER #3   .data name     560 virtual size   14000 virtual address    1000 size of raw data   14000 file pointer to raw data       0 file pointer to relocation table       0 file pointer to line numbers       0 number of relocations       0 number of line numbersC0000040 flags         Initialized Data         Read WriteSECTION HEADER #4  .idata name     58D virtual size   15000 virtual address    1000 size of raw data   15000 file pointer to raw data       0 file pointer to relocation table       0 file pointer to line numbers       0 number of relocations       0 number of line numbersC0000040 flags         Initialized Data         Read WriteSECTION HEADER #5  .didat name     7A2 virtual size   16000 virtual address    1000 size of raw data   16000 file pointer to raw data       0 file pointer to relocation table       0 file pointer to line numbers       0 number of relocations       0 number of line numbersC0000040 flags         Initialized Data         Read WriteSECTION HEADER #6  .reloc name     26D virtual size   17000 virtual address    1000 size of raw data   17000 file pointer to raw data       0 file pointer to relocation table       0 file pointer to line numbers       0 number of relocations       0 number of line numbers42000040 flags         Initialized Data         Discardable         Read Only   Summary        1000 .data        1000 .didat        1000 .idata        1000 .rdata        1000 .reloc       12000 .text

表1 7 - 2显示了比较常见的一些节的名字,并且说明了每一节的作用。

除了编译器和链接程序创建的标准节外,也可以在使用下面的命令进行编译时创建自己的节:

表17-2 常见的节名及作用

节名作用. b s s未经初始化的数据. C RTC运行期只读数据. d a t a已经初始化的数据. d e b u g调试信息. d i d a t a延迟输入文件名表. e d a t a输出文件名表. i d a t a输入文件名表. r d a t a运行期只读数据. r e l o c重定位表信息. r s r c资源. t e x t. e x e或D L L文件的代码. t l s线程的本地存储器. x d a t a异常处理表

 

#pragma data_seg("sectionname")

我可以创建一个称为“S h a r e d”的节,它包含单个L O N G值,如下所示:

#pragma data_seg("Shared")LONG g_lInstanceCount = 0;#pragma data_seg()

当编译器对这个代码进行编译时,它创建一个新节,称为S h a r e d,并将它在编译指示后面看到的所有已经初始化(i n i t i a l i z e d)的数据变量放入这个新节中。在上面这个例子中,变量放入S h a r e d节中。该变量后面的#pragma dataseg()一行告诉编译器停止将已经初始化的变量放入S h a r e d节,并且开始将它们放回到默认数据节中。需要记住的是,编译器只将已经初始化的变量放入新节中。例如,如果我从前面的代码段中删除初始化变量(如下面的代码所示),那么编译器将把该变量放入S h a r e d节以外的节中。

#pragma data_seg("Shared")LONG g_lInstanceCount;#pragma data_seg()

Microsoft 的Visual C++编译器提供了一个A l l o c a t e说明符,使你可以将未经初始化的数据放入你希望的任何节中。请看下面的代码:

// Create Shared section & have compiler place initialized data in it.#pragma data_seg("Shared")// Initialized, in Shared sectionint a = 0;// Uninitialized, not in Shared sectionint b;// Have compiler stop placing initialized data in Shared section.#pragma data_seg()// Initialized, in Shared section__declspec(allocate("Shared")) int c = 0;// Uninitialized, in Shared section__declspec(allocate("Shared")) int d;// Initialized, not in Shared sectionint e = 0;// Uninitialized, not in Shared sectionint f;        

上面的注释清楚地指明了指定的变量将被放入哪一节。若要使A l l o c a t e声明的规则正确地起作用,那么首先必须创建节。如果删除前面这个代码中的第一行#pragma data_seg,上面的代码将不进行编译。

之所以将变量放入它们自己的节中,最常见的原因也许是要在. e x e或D L L文件的多个映像之间共享这些变量。按照默认设置, . e x e或D L L文件的每个映像都有它自己的一组变量。然而,可以将你想在该模块的所有映像之间共享的任何变量组合到它自己的节中去。当给变量分组时,系统并不为. e x e或D L L文件的每个映像创建新实例。

仅仅告诉编译器将某些变量放入它们自己的节中,是不足以实现对这些变量的共享的。还必须告诉链接程序,某个节中的变量是需要加以共享的。若要进行这项操作,可以使用链接程序的命令行上的/ S E C T I O N开关:

/SECTION:name,attributes

在冒号的后面,放入你想要改变其属性的节的名字。在我们的例子中,我们想要改变S h a r e d节的属性。因此应该创建下面的链接程序开关:

/SECTION:Shared,RWS

在逗号的后面,我们设定了需要的属性。用R代表R E A D ,W代表W E I T E,E代表E X E C U T E,S代表S H A R E D。上面的开关用于指明位于S h a r e d节中的数据是可以读取、写入和共享的数据。如果想要改变多个节的属性,必须多次设定/ S E C T I O N开关,也就是为你要改变属性的每个节设定一个/ S E C T I O N开关。

也可以使用下面的句法将链接程序开关嵌入你的源代码中:

#pragma comment(linker, "/SECTION:Shared,RWS")

这一行代码告诉编译器将上面的字符串嵌入名字为“ . d r e c t v e”的节。当链接程序将所有的. o b j模块组合在一起时,链接程序就要查看每个. o b j模块的“ . d r e c t v e”节,并且规定所有的字符串均作为命令行参数传递给该链接程序。我一直使用这种方法,因为它非常方便。如果将源代码文件移植到一个新项目中,不必记住在Visual C++的Project Settings(项目设置)对话框中设置链接程序开关。

虽然可以创建共享节,但是,由于两个原因, M i c r o s o f t并不鼓励你使用共享节。第一,用这种方法共享内存有可能破坏系统的安全。第二,共享变量意味着一个应用程序中的错误可能影响另一个应用程序的运行,因为它没有办法防止某个应用程序将数据随机写入一个数据块。

假设你编写了两个应用程序,每个应用程序都要求用户输入一个口令。然而你又决定给应用程序添加一些特性,使用户操作起来更加方便些:如果在第二个应用程序启动运行时,用户正在运行其中的一个应用程序,那么第二个应用程序就可以查看共享内存的内容,以便获得用户的口令。这样,如果程序中的某一个已经被使用,那么用户就不必重新输入他的口令。

这听起来没有什么问题。毕竟没有别的应用程序而只有你自己的应用程序加载了D L L,并且知道到什么地方去查找包含在共享节中的口令。但是,黑客正在窥视着你的行动,如果他们想要得到你的口令,只需要编写一段很短的程序,加载到你的公司的D L L文件中,然后监控共享内存块。当用户输入口令时,黑客的程序就能知道该用户的口令。

黑客精心编制的程序也可能试图反复猜测用户的口令并将它们写入共享内存。一旦该程序猜测到正确的口令,它就能够将各种命令发送给两个应用程序中的一个。如果有一种办法只为某些应用程序赋予访问权,以便加载一个特定的D L L,那么这个问题也许是可以解决的。但是目前还不行,因为任何程序都能够调用L o a d L i b r a r y函数来显式加载D L L。

 

原创粉丝点击