假设你有以下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 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 返回的哈希值中的一个值,该值已增加了偏移量(即,[$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;
}