一种在结构中查找填充大小和位置的方法?

Posted

技术标签:

【中文标题】一种在结构中查找填充大小和位置的方法?【英文标题】:A way to find the size and location of padding in a struct? 【发布时间】:2011-03-18 15:57:39 【问题描述】:

我正在尝试编写一个工具,它将一些包含结构的 C 代码作为输入。它将编译代码,然后查找并输出编译器决定添加到其中的结构的任何填充的大小和偏移量。对于使用 offsetof、sizeof 和一些附加项的已知结构,这非常简单,但我无法找到一种简单的方法来自动为任何输入结构执行此操作。

如果我知道如何遍历结构中的所有元素,我想我可以毫无问题地编写该工具,但据我所知,没有办法做到这一点。我希望一些 *** 的人会知道一种方法。但是,我并没有拘泥于自己的方法,而且我当然愿意接受任何在结构中查找填充的替代方法。

【问题讨论】:

我不明白你在问什么。您想为 C 语言构建一个通用的反射系统吗?想要在 C 中进行反射有点像想要骑摩托车穿越大西洋...... 对不起,如果我的问题不清楚。我不是在寻找通用的反射系统(我希望!)。我认为我的解决方案将涉及使用 Perl 解析 C 源代码,并为结构中的每个元素生成一些带有 sizeof 和 offsetof 调用的修改后的 C 代码。这将提供结构中所有元素的大小和位置,并且从中查找和报告结构包含的任何填充都是微不足道的。这似乎是一种合理的方法,还是您会以不同的方式解决问题? 【参考方案1】:

你可以试试pstruct。

我从未使用过它,但我一直在寻找可以使用 stabs 的方法,这听起来很合适。

如果没有,我建议寻找其他方法来解析刺伤信息。

【讨论】:

pstruct 是一个很好的建议,适用于我提供的一些基本 C 文件。它还具有已安装在大多数机器上的优点。不过嵌套结构有一些问题。【参考方案2】:

假设你有以下module.h

typedef void (*handler)(void);

struct foo 
  char a;
  double b;
  int c;
;

struct bar 
  float y;
  short z;
;

生成unpack 模板的Perl 程序从惯用的前端开始:

#! /usr/bin/perl

use warnings;
use strict;

sub usage  "Usage: $0 header\n" 

使用structs,我们将标头提供给ctags,并从其输出中收集结构成员。结果是一个哈希,其键是结构的名称,其值是 [$member_name, $type] 形式的对数组。

请注意,它只处理少数 C 类型。

sub structs 
  my($header) = @_;

  open my $fh, "-|", "ctags", "-f", "-", $header
    or die "$0: could not start ctags";

  my %struct;
  while (<$fh>) 
    chomp;
    my @f = split /\t/;
    next unless @f >= 5 &&
                $f[3] eq "m" &&
                $f[4] =~ /^struct:(.+)/;

    my $struct = $1;
    die "$0: unknown type in $f[2]"
      unless $f[2] =~ m!/\^\s*(float|char|int|double|short)\b!;

    # [ member-name => type ]
    push @ $struct$struct  => [ $f[0] => $1 ];
  

  wantarray ? %struct : \%struct;

假设标头可以单独包含,generate_source 生成一个 C 程序,该程序将偏移量打印到标准输出,用虚拟值填充结构,并将原始结构写入标准输出,前面加上它们各自的字节大小.

sub generate_source 
  my($struct,$header) = @_;

  my $path = "/tmp/my-offsets.c";
  open my $fh, ">", $path
    or die "$0: open $path: $!";

  print $fh <<EOStart;
#include <stdio.h>
#include <stddef.h>
#include <$header>
void print_buf(void *b, size_t n) 
  char *c = (char *) b;
  printf("%zd\\n", n);
  while (n--) 
    fputc(*c++, stdout);
  


int main(void) 
EOStart

  my $id = "a1";
  my %id;
  foreach my $s (sort keys %$struct) 
    $id$s = $id++;
    print $fh "struct $s $id$s;\n";
  

  my $value = 0;
  foreach my $s (sort keys %$struct) 
    for (@ $struct->$s ) 
      print $fh <<EOLine;
printf("%lu\\n", offsetof(struct $s,$_->[0]));
$id$s.$_->[0] = $value;
EOLine
      ++$value;
    
  

  print $fh qqprintf("----\\n");\n;

  foreach my $s (sort keys %$struct) 
    print $fh "print_buf(&$id$s, sizeof($id$s));\n";
  
  print $fh <<EOEnd;
  return 0;

EOEnd

  close $fh or warn "$0: close $path: $!";
  $path;

unpack 生成一个模板,其中参数$membersstructs 返回的哈希值中的一个值,该值已增加了偏移量([$member_name, $type, $offset] 形式的arrayrefs :

sub template 
  my($members) = @_;

  my %type2tmpl = (
    char => "c",
    double => "d",
    float => "f",
    int => "i!",
    short => "s!",
  );

  join " " =>
  map '@![' . $_->[2] . ']' . $type2tmpl $_->[1]  =>
  @$members;

最后,我们到达主程序,第一个任务是生成和编译 C 程序:

die usage unless @ARGV == 1;
my $header = shift;

my $struct = structs $header;
my $src    = generate_source $struct, $header;

(my $cmd = $src) =~ s/\.c$//;
system("gcc -I`pwd` -o $cmd $src") == 0
  or die "$0: gcc failed";

现在我们读取生成程序的输出并解码结构:

my @todo = map @ $struct->$_  => sort keys %$struct;

open my $fh, "-|", $cmd
  or die "$0: start $cmd failed: $!";
while (<$fh>) 
  last if /^-+$/;
  chomp;
  my $m = shift @todo;
  push @$m => $_;


if (@todo) 
  die "$0: unfilled:\n" .
      join "" => map "  - $_->[0]\n", @todo;


foreach my $s (sort keys %$struct) 
  chomp(my $length = <$fh> || die "$0: unexpected end of input");
  my $bytes = read $fh, my($buf), $length;
  if (defined $bytes) 
    die "$0: unexpected end of input" unless $bytes;
    print "$s: @[unpack template($struct->$s), $buf]\n";
  
  else 
    die "$0: read: $!";
  

输出:

$ ./unpack module.h
酒吧:0 1
富:2 3 4

供参考,为module.h生成的C程序为

#include <stdio.h>
#include <stddef.h>
#include <module.h>
void print_buf(void *b, size_t n) 
  char *c = (char *) b;
  printf("%zd\n", n);
  while (n--) 
    fputc(*c++, stdout);
  


int main(void) 
struct bar a1;
struct foo a2;
printf("%lu\n", offsetof(struct bar,y));
a1.y = 0;
printf("%lu\n", offsetof(struct bar,z));
a1.z = 1;
printf("%lu\n", offsetof(struct foo,a));
a2.a = 2;
printf("%lu\n", offsetof(struct foo,b));
a2.b = 3;
printf("%lu\n", offsetof(struct foo,c));
a2.c = 4;
printf("----\n");
print_buf(&a1, sizeof(a1));
print_buf(&a2, sizeof(a2));
  return 0;

【讨论】:

感谢您的精彩解释和没有太多依赖的独立程序。这是否处理嵌套结构? @Desert ed 正如所写,它处理只有少数内在类型成员的平面结构。处理嵌套结构不需要太多:template 在看到另一个结构的类型时需要调用自己,当然structs 需要在它接受的类型上更加宽松。 哇,在一个问题中最长的答案不是“你最喜欢什么___?”【参考方案3】:

如果您可以访问 Visual C++,则可以添加以下 pragma 以使编译器吐出添加的填充位置和数量:

#pragma warning(enable : 4820) 

到那时,您可能只需使用 cl.exe 的输出并参加派对。

【讨论】:

【参考方案4】:

这不是pahole 所做的吗?

【讨论】:

这确实是完全 pahole 所做的,尽管它对使用调试信息而不是源代码编译的二进制文件进行操作。【参考方案5】:

您可以使用Exuberant Ctags 来解析您的源文件,而不是使用 CPAN 模块或自己破解一些东西。比如下面的代码:

typedef 结构 _foo 诠释一个; 诠释 b; 富;

ctags 发出以下内容:

_foo x.c /^typedef struct _foo $/;" 的文件: a x.c /^ int a;$/;" m struct:_foo 文件: b x.c /^ int b;$/;" m struct:_foo 文件: foo x.c /^ foo;$/;" t typeref:struct:_foo 文件:

第一、第四和第五列应该足以让您确定存在哪些结构类型以及它们的成员是什么。您可以使用该信息生成一个 C 程序,以确定每种结构类型具有多少填充。

【讨论】:

【参考方案6】:

破解Convert::Binary::C

【讨论】:

【参考方案7】:

让您的工具解析结构定义以查找字段名称,然后生成打印结构填充描述的 C 代码,最后编译并运行该 C 代码。第二部分的示例 Perl 代码:

printf "const char *const field_names[] = %s;\n",
       join(", ", map "\"$_\"" @field_names);
printf "const size_t offsets[] = %s, %s;\n",
       join(", ", map "offsetof(struct $struct_name, $_)" @field_names),
       "sizeof(struct $struct_name)";
print <<'EOF'
for (i = 0; i < sizeof(field_names)/sizeof(*field_names); i++) 
    size_t padding = offsets[i+1] - offsets[i];
    printf("After %s: %zu bytes of padding\n", field_names[i], padding);

EOF

C 很难解析,但您只对语言的一小部分感兴趣,而且听起来您对源文件有一定的控制权,所以一个简单的解析器应该可以解决问题。搜索 CPAN 会发现 Devel::Tokenizer::C 和一些 C:: 模块作为候选模块(除了它们的名字,我对它们一无所知)。如果你真的需要一个准确的 C 解析器,有Cil,但你必须用 Ocaml 编写你的分析。

【讨论】:

【参考方案8】:

我更喜欢读取和写入缓冲区,然后有一个函数从缓冲区加载结构成员。这比直接读入结构或使用memcpy 更便携。此外,该算法消除了对编译器填充的任何担忧,并且还可以进行调整以处理 Endianess。

一个正确且健壮的程序比花费在压缩二进制数据上的任何时间都更有价值。

【讨论】:

不幸的是,我在现有程序中没有任何发言权,这是为了提供帮助,所以我不能只使用缓冲区。我的目标是尽量减少数据结构更改时引入的错误。目前,对数据结构的更改会以不可预测的方式更改所有后续结构成员的字节偏移量。我只是想在那里添加一些可预测性。【参考方案9】:

没有 C++ 语言功能可以遍历结构的成员,所以我认为你不走运。

您也许可以使用宏来减少一些样板,但我认为您无法明确指定所有成员。

【讨论】:

【参考方案10】:

我认为 C 中不存在任何用于自省/反射的通用工具。这就是 Java 或 C# 的用途。

【讨论】:

它可能被否决了,因为它对问题没有贡献。正如许多有用的答案(和我的工作程序)所显示的那样,仅仅因为没有自省并不意味着无法找到填充。 @Desert:事实并非如此。这是我冒犯的用户在几秒钟内投票否决的 13 个答案之一。

以上是关于一种在结构中查找填充大小和位置的方法?的主要内容,如果未能解决你的问题,请参考以下文章

折半查找(C语言)

二分查找算法(JAVA)

21年考研王道数据结构2-9算法:通过折半查找指定值X并根据结果进行操作(两种方法)

数据结构之顺序表

二分查找

20172302 《Java软件结构与数据结构》第五周学习总结