Я пытаюсь записать инструмент, который возьмет в качестве входа некоторый код C, содержащий структуры. Это скомпилирует код, затем найти и произвести размер, и смещение любого дополнения компилятора решает добавить к структурам в нем. Это довольно просто, чтобы сделать вручную для известной структуры с помощью offsetof, sizeof, и некоторого дополнения, но я не могу выяснить простой способ сделать это автоматически для любой входной структуры.
Если я знал, как выполнить итерации через все элементы в структуре, я думаю, что мог записать инструмент без проблем, но насколько я знаю, что нет никакого способа сделать это. Я надеюсь, что некоторые люди StackOverflow будут знать путь. Однако я не застреваю в своем подходе, и я, конечно, открыт для любых альтернативных подходов к нахождению дополнения в структуре.
Я не верю, что существует какое-либо средство общего назначения для самоанализа / рефлексии в C. Это то, для чего предназначены Java или C #.
Попросите ваш инструмент проанализировать определение структуры, чтобы найти имена полей, затем сгенерировать код 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.
В языке C++ нет функции итерации по членам структуры, поэтому я думаю, что вам не повезло.
Возможно, вы сможете сократить часть кодовой таблицы с помощью макроса, но я думаю, что вы застряли на явном указании всех членов.
Вы можете использовать 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, которая определяет, сколько заполнения имеет каждый тип структуры.
Вы можете попробовать pstruct .
Я никогда не использовал это, но я искал способ, которым вы могли бы использовать удары, и это звучит так, как будто это отвечало всем требованиям.
Если это не так, я бы посоветовал поискать другие способы разобрать информацию о ударах.
Я предпочитаю читать и писать в буфер, а затем использовать функцию, загружающую элементы структуры из буфера . Это более переносимо, чем чтение непосредственно в структуру или использование memcpy
. Кроме того, этот алгоритм избавляет от беспокойства о заполнении компилятора и также может быть настроен для обработки Endianess.
Правильная и надежная программа стоит больше, чем время, потраченное на сжатие двоичных данных.
Допустим, у вас есть следующий module.h
:
typedef void (*handler)(void);
struct foo {
char a;
double b;
int c;
};
struct bar {
float y;
short z;
};
Программа Perl для генерации распаковки
шаблонов начинается с обычного front matter:
#! /usr/bin/perl
use warnings;
use strict;
sub usage { "Usage: $0 header\n" }
С structs
мы подаем заголовок на ctags
и из его вывода собираем члены struct. В результате получается хэш, ключами которого являются имена структур, а значениями - массивы пар вида [$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
генерирует программу на Си, которая выводит смещения на стандартный вывод, заполняет структуры фиктивными значениями и записывает необработанные структуры на стандартный вывод, предваряя их соответствующими размерами в байтах.
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
, дополненное смещениями (i.e. 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;
}
Наконец, мы добрались до основной программы, где первой задачей является генерация и компиляция программы на языке Си:
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
Для справки, программа на языке Си, сгенерированная для модуля. h
is
#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;
}
Если у вас есть доступ к Visual C ++, вы можете добавить следующую директиву, чтобы компилятор сообщал, где и сколько было добавлено отступов:
#pragma warning(enable : 4820)
В этот момент вы, вероятно, можете просто использовать вывод cl.exe и перейти вечеринка.