Допустим, у вас есть следующие 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;
}