如何在MSVC中获取指向二进制部分的指针?

时间:2010-09-27 21:31:24

标签: c visual-c++ gcc linker

我正在编写一些代码,这些代码将一些数据结构存储在一个特殊的命名二进制部分中。这些是同一结构的所有实例,它们分散在许多C文件中,并且不在彼此的范围内。通过将它们全部放在命名区域中,我可以迭代它们。

在GCC中,我使用_ 属性 _((section(...))加上一些特别命名的extern指针,这些指针由链接器神奇地填充。这是一个简单的例子:

#include <stdio.h>

extern int __start___mysection[];
extern int __stop___mysection[];

static int x __attribute__((section("__mysection"))) = 4;
static int y __attribute__((section("__mysection"))) = 10;
static int z __attribute__((section("__mysection"))) = 22;

#define SECTION_SIZE(sect) \
    ((size_t)((__stop_##sect - __start_##sect)))

int main(void)
{
    size_t sz = SECTION_SIZE(__mysection);
    int i;

    printf("Section size is %u\n", sz);

    for (i=0; i < sz; i++) {
        printf("%d\n", __start___mysection[i]);
    }

    return 0;
}

我正在试图弄清楚如何在MSVC中做到这一点,但我正在画一个空白。我从编译器文档中看到我可以使用__pragma(section(...))声明该部分,并使用__declspec(allocate(...))声明数据在该部分中,但我看不出我怎么能得到在运行时指向该部分的开始和结束的指针。

我在网上看到了一些关于在MSVC中执行_ 属性 _((构造函数))的例子,但它似乎是特定于CRT的黑客攻击而不是获取指针的一般方法到一节的开头/结尾。有人有什么想法吗?

4 个答案:

答案 0 :(得分:7)

还有一种方法可以在不使用汇编文件的情况下执行此操作。

#pragma section(".init$a")
#pragma section(".init$u")
#pragma section(".init$z")

__declspec(allocate(".init$a")) int InitSectionStart = 0;
__declspec(allocate(".init$z")) int InitSectionEnd   = 0;

__declspec(allocate(".init$u")) int token1 = 0xdeadbeef;
__declspec(allocate(".init$u")) int token2 = 0xdeadc0de;

第3行定义了细分。这些定义了部分并取代了程序集文件。与data_seg pragma不同,section pragma仅创建节。 __declspec(allocate())行告诉编译器将该项放在该段中。

从微软页面: 这里的顺序很重要。部分名称不得超过8个字符。 $之前具有相同名称的部分合并为一个部分。它们合并的顺序是通过对$后面的字符进行排序来确定的。

要记住的另一个要点是段填充为256字节。 START和END指针不会像您期望的那样直接在前后。

如果将表设置为指向函数或其他非NULL值的指针,则应该很容易在表之前和之后跳过NULL条目,因为部分填充

有关详细信息,请参阅this msdn page

答案 1 :(得分:4)

首先,您需要创建一个包含您感兴趣的所有部分的ASM文件(例如,section.asm):

.686
.model flat

PUBLIC C __InitSectionStart
PUBLIC C __InitSectionEnd

INIT$A SEGMENT DWORD PUBLIC FLAT alias(".init$a")
        __InitSectionStart EQU $
INIT$A ENDS

INIT$Z SEGMENT DWORD PUBLIC FLAT alias(".init$z")
        __InitSectionEnd EQU $
INIT$Z ENDS

END

接下来,在您的代码中,您可以使用以下内容:

#pragma data_seg(".init$u")
int token1 = 0xdeadbeef;
int token2 = 0xdeadc0de;
#pragma data_seg()

这给出了这样一个MAP文件:

 Start         Length     Name                   Class
 0003:00000000 00000000H .init$a                 DATA
 0003:00000000 00000008H .init$u                 DATA
 0003:00000008 00000000H .init$z                 DATA

  Address         Publics by Value              Rva+Base       Lib:Object
 0003:00000000       ?token1@@3HA               10005000     dllmain.obj
 0003:00000000       ___InitSectionStart        10005000     section.obj
 0003:00000004       ?token2@@3HA               10005004     dllmain.obj
 0003:00000008       ___InitSectionEnd          10005008     section.obj

因此,正如您所看到的,名称为.init$u的部分位于.init$a.init$z之间,这使您能够获取指向数据开头的指针通过__InitSectionStart符号,并通过__InitSectionEnd符号结束数据。

答案 2 :(得分:0)

我在这里进行了一些实验并试图在没有汇编文件的情况下实现该版本,但是在部分之间的随机数量的填充字节很难实现,这使得几乎不可能找到.init $ u部分的开头part如果内容不仅仅是指针或其他可以检查NULL或其他已知模式的简单项目。 是否插入填充似乎与调试选项Zi的使用相关。给定时,插入填充,而不是,所有部分都完全按照人们想要的方式显示。

答案 3 :(得分:0)

ML64可以减少很多装配噪音:

public foo_start
public foo_stop

.code foo$a
foo_start:

.code foo$z
foo_stop:

end