使用C Struct成员的连续内存

时间:2016-09-13 02:25:38

标签: c unix struct system elf

在将此标记为重复之前,请先阅读此问题。

所以这可能是一个非常愚蠢的问题,但它困扰着我。我知道,从阅读,以及许多其他SO问题,由于编译器添加了填充,C中的结构中的字段不能保证是连续的。例如,根据C标准:

  

13 / 在结构对象中,非位字段成员和位字所在的单位具有按声明顺序增加的地址。指向适当转换的结构对象的指针指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。在结构对象中可能有未命名的填充,但不是在它的开头。

我正在编写一个类似于unix readelfnm的程序,只是为了好玩,它涉及很多工作,将特定偏移处的字节处理到文件中以读取某些值。例如,目标文件的前62个字节包含"文件头"。文件头的字节0x00-0x04编码一个int,而0x20-0x28编码一个指针等。但是,我注意到在readelf.c的原始实现中程序员做了这样的事情:

首先,他们使用与文件头中的东西相对应的字段声明一个struct(让我们称之为ELF_H)(即第一个字段是int,就像文件头中的前4个字节一样,第二个字符是char因为elf头中的字节0x04-0x05编码char等。然后他们做的是将整个elf文件复制到内存中,并将指向此内存开头的指针键入ELF_H类型。类似的东西:

FILE *file = fopen('filename', rb);
void *start_of_file = malloc(/* size_of_file */);
fread(start_of_file, 1, /* size_of_file */,file);  // copies entire file into memory
ELF_H hdr = *(ELF_H) start_of_file;               // type case pointer to be of type struct and dereference

并且在执行此操作之后,只需使用struct的成员变量访问标头的每个部分。因此,不是使用指针算法获得应该在字节0x04处的内容,而是使用hdr.member2(在struct中是第二个成员,后面是第一个成为int的成员)。

如果结构中的字段不保证是连续的,这意味着如何工作?

我能找到的最接近的答案是here,但在该示例中,结构的成员属于同一类型。在ELF_H中,它们的类型不同。

提前谢谢你:)

5 个答案:

答案 0 :(得分:3)

如果文件中的数据是从正在读取的表单的填充结构中写入的,那么填充是无关紧要的;该文件包含填充,内存表示也是如此。

确实标准不是特别限制,编译器可以在ELF阅读器结构中插入随机填充,编写ELF的工具不匹配。但在实践中,"未命名的填充"用于对齐目的,并且所有主要编译器都具有可预测的行为;他们填充对齐字段以匹配其类型。因此int字段(在具有四个字节int的系统上)前面有1-3个填充字节,如果前一个字段没有在四字节边界上结束,char字段得到没有填充等。在这种情况下,我知道的编译器不会在前导int字段和后面的char[2]之间插入填充,因为char无论如何都没有必需的对齐。

也可以使用非标准编译器扩展来防止填充对齐结构中的字段,但如果你的结构定义永远不会有一个未对齐的字段,那么它是不必要的(因为你总是在较大的字段之后放置较小的字段,或者因为您总是将较小的字段组合在一起以维持后续较大字段的对齐要求。)

答案 1 :(得分:2)

  

如果结构中的字段不能保证是连续的,那么这是如何工作的?

该标准不要求结构是连续的,但这并不意味着结构是随机布置或以不可预测的方式布局的。正在使用的特定编译器和链接器将始终以指定的方式生成二进制文件,如应用程序二进制接口或ABI所指示的那样。恰好在GNU / Linux机器上,ELF ABI完全对应于GCC将如何布局和访问该结构。

换句话说,您可以预测您描述的方法是否适用于任何给定的ABI /编译器/链接器组合。它不能保证按标准工作,但可以保证通过ABI的兼容性工作。

答案 2 :(得分:1)

您可以通过禁用struct padding使结构的字段连续。对于gcc,它应该是:

typedef struct Port
{
    uint32_t reg0;
    uint32_t reg1;
    uint32_t reg2;
} __attribute__((__packed__));

对于VS C ++:

#pragma pack(push, 1)

typedef struct Port
{
    uint32_t reg0;
    uint32_t reg1;
    uint32_t reg2;
};

#pragma pop();

答案 3 :(得分:1)

有趣的是,我在精灵的reference specification(第2页)中找到了答案。

根据它:

  

目标文件格式定义的所有数据结构都遵循"自然''相关课程的大小和对齐准则。如有必要,数据结构包含显式填充,以确保4字节对象的4字节对齐,强制结构大小为4的倍数,依此类推。数据也从文件的开头有适当的对齐方式。因此,例如,包含Elf32_Addr成员的结构将在文件中的4字节边界上对齐。

这是专门针对32位架构的,但我确信同样的概念适用于64位系统。所以看来,为ELF定义的所有数据结构都是以允许对齐的方式进行的,这样结构就可以代表它们。

谢谢大家的回答;他们非常有帮助!

答案 4 :(得分:0)

为了加快速度,只添加填充以获得32/64位对齐值,并通过查看elf.h中的结构(Elf头结构,程序头结构,节头结构),您将请注意,这些值已根据其体系结构进行了对齐,因此,您可以将内容从文件复制到内存,并将缓冲区强制转换为您想要的任何结构。然后从内部访问值。

我正在开发一种工具(并且巧合)我正在开发一种工具(我正在尝试制作一种结合了ReadelfObjdump的功能的工具。我取得了重大进展,我愿意在GitHub上分享这个项目 您可能希望与我一起进一步开发(在afr0ck.bin@gmail.com上与我联系)。