Способ найти размер и расположение отступов в структуре? - PullRequest
9 голосов
/ 21 июля 2010

Я пытаюсь написать инструмент, который будет принимать в качестве входных данных некоторый C-код, содержащий структуры. Он скомпилирует код, затем найдет и выведет размер и смещение любого отступа, который компилятор решит добавить к структурам внутри него. Это довольно просто сделать вручную для известной структуры, используя offsetof, sizeof и некоторые дополнения, но я не могу найти простой способ сделать это автоматически для любой входной структуры.

Если бы я знал, как перебирать все элементы в структуре, я думаю, что мог бы написать инструмент без проблем, но, насколько я знаю, нет способа сделать это. Я надеюсь, что некоторые люди из StackOverflow найдут способ. Тем не менее, я не застрял в своем подходе, и я, безусловно, открыт для любых альтернативных подходов к поиску заполнения в структуре.

Ответы [ 10 ]

6 голосов
/ 21 июля 2010

Разве это не то, что делает pahole ?

4 голосов
/ 21 июля 2010

Допустим, у вас есть следующие module.h:

typedef void (*handler)(void);

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

struct bar {
  float y;
  short z;
};

Программа Perl для генерации unpack шаблонов начинается с обычного текста:

#! /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 qq{printf("----\\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, где параметр $members - это значение в хеш-функции, возвращаемое structs, которое было дополнено смещениями ( т.е. , arrayrefs вида[$member_name, $type, $offset]:

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 
bar: 0 1
foo: 2 3 4

Для справки, программа C, сгенерированная для module.h, равна

#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;
}
3 голосов
/ 21 июля 2010

Я предпочитаю читать и записывать в буфер, затем иметь функцию загрузки элементов структуры из буфера.Это более переносимо, чем чтение непосредственно в структуру или использование memcpy.Кроме того, этот алгоритм освобождает вас от беспокойства по поводу заполнения компилятором и также может быть настроен для обработки Endianess.

Правильная и надежная программа стоит больше, чем любое время, потраченное на сжатие двоичных данных.

2 голосов
/ 21 июля 2010

Вы можете попробовать pstruct .

Я никогда не использовал его, но я искал какой-то способ, которым вы могли бы использовать удары, и это звучит так, как будто это будет соответствовать всем требованиям..

Если этого не произойдет, я бы посоветовал поискать другие способы анализа информации о ударах.

2 голосов
/ 21 июля 2010

Вы можете использовать Exuberant Ctags для анализа ваших исходных файлов вместо использования модуля CPAN или взлома чего-либо самостоятельно.Например, для следующего кода:

typedef struct _foo {
    int a;
    int b;
} foo;

ctags выдает следующее:

_foo    x.c     /^typedef struct _foo {$/;"     s                               file:
a       x.c     /^    int a;$/;"                m       struct:_foo             file:
b       x.c     /^    int b;$/;"                m       struct:_foo             file:
foo     x.c     /^} foo;$/;"                    t       typeref:struct:_foo     file:

Первого, четвертого и пятого столбцов должно быть достаточно, чтобы определить, какие существуют типы структури каковы их члены.Вы можете использовать эту информацию для генерации C-программы, которая определяет, сколько отступов имеет каждый тип структуры.

2 голосов
/ 21 июля 2010

взломать Convert::Binary::C.

1 голос
/ 21 июля 2010

Если у вас есть доступ к Visual C ++, вы можете добавить следующую прагму, чтобы компилятор показывал, где и сколько было добавлено отступов:

#pragma warning(enable : 4820) 

В этот момент вы, вероятно, можете просто использовать вывод cl.exe и перейти на party.

1 голос
/ 21 июля 2010

Попросите ваш инструмент проанализировать определение структуры, чтобы найти имена полей, затем сгенерировать код 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.

0 голосов
/ 21 июля 2010

Нет функции языка C ++ для перебора элементов структуры, поэтому я думаю, что вам не повезло.

Возможно, вам удастся урезать некоторую часть шаблона с помощью макроса, но я думаю, что вы застряли, указав все члены явно.

0 голосов
/ 21 июля 2010

Я не верю, что существует какой-либо универсальный инструмент для самоанализа / отражения в C. Это то, для чего нужны Java или C #.

...