2010-07-20 9 views
9

Sto provando a scrivere uno strumento che prenderà come input del codice C contenente le strutture. Compilerà il codice, quindi troverà e produrrà la dimensione e l'offset di qualsiasi padding che il compilatore decide di aggiungere alle strutture al suo interno. Questo è abbastanza semplice da fare a mano per una struttura conosciuta usando offsetof, sizeof e qualche aggiunta, ma non riesco a capire un modo semplice per farlo automaticamente per qualsiasi struttura di input.Un modo per trovare la dimensione e la posizione del riempimento in una struttura?

Se sapessi iterare attraverso tutti gli elementi di una struttura, penso che potrei ottenere lo strumento scritto senza problemi, ma per quanto ne so non c'è modo di farlo. Spero che alcune persone di StackOverflow conoscano un modo. Tuttavia, non sono bloccato nel mio approccio, e sono certamente aperto a qualsiasi approccio alternativo per trovare il padding in una struttura.

+1

I don' t capire cosa stai chiedendo. Vuoi costruire un sistema di riflessione generico per il linguaggio C? La riflessione in C è un po 'come voler attraversare l'Oceano Atlantico con una moto ... –

+0

Scusa se non sono stato chiaro nella mia domanda. Non sto cercando un sistema di riflessione generico (spero!). Ho pensato che la mia soluzione avrebbe comportato l'analisi del codice sorgente C con Perl e la generazione di qualche codice C modificato con una dimensione e una chiamata offset per ogni elemento nella struttura.Ciò fornirebbe la dimensione e la posizione di tutti gli elementi nella struct, e da quello è banale trovare e riportare qualsiasi padding che la struct contenga. Sembra un approccio ragionevole, o andresti sul problema in un altro modo? –

risposta

6

Non è questo che fa pahole?

+1

È esattamente * esattamente * ciò che fa 'pahole', sebbene funzioni su binari compilati con informazioni di debug piuttosto che sorgenti. – caf

+0

+1 Questo è quello che stavo cercando ma non ho trovato ... – bstpierre

-1

Non credo che esista una funzione generica per l'introspezione/la riflessione in C. Ecco a cosa servono Java o C#.

+0

Questa risposta sembra essere corretta, quindi perché è stata downvoted? –

+0

Probabilmente è stato downvoted perché non ha contribuito alla domanda. Solo perché non c'è introspezione non significa che non c'è modo di trovare il padding, come mostrano molte risposte utili (e il mio programma di lavoro). –

+0

@Desert: questo risulta non essere il caso. Era una delle tredici risposte downvoted a pochi secondi l'una dall'altra da un utente che ho offeso. –

-1

Non esiste una funzione di linguaggio C++ per scorrere i membri di una struttura, quindi penso che tu sia sfortunato.

Potresti riuscire a ridurre parte della piastra della caldaia con una macro, ma penso che tu sia bloccato specificando esplicitamente tutti i membri.

+0

Questa risposta sembra essere corretta, quindi perché è stata downvoted? –

3

Preferisco leggere e scrivere in un buffer, quindi disporre di una funzione per caricare i membri della struttura dal buffer. Questo è più portatile di leggere direttamente in una struttura o utilizzando memcpy. Anche questo algoritmo libera qualsiasi preoccupazione sul riempimento del compilatore e può anche essere regolato per gestire Endianess.

Un programma corretto e affidabile vale più di qualsiasi altro tempo impiegato per compattare i dati binari.

+0

Purtroppo non ho alcuna voce in capitolo nel programma esistente progettato per aiutare, quindi non posso usare solo i buffer. Il mio obiettivo è minimizzare gli errori introdotti quando cambiano le strutture dei dati. Attualmente, una modifica alla struttura dei dati modifica l'offset di byte di tutti i membri della struct seguenti in un modo non prevedibile. Sto solo cercando di aggiungere un po 'di prevedibilità lì. –

1

Il proprio strumento analizza la definizione della struct per trovare i nomi dei campi, quindi genera il codice C che stampa una descrizione del riempimento della struct e infine compila ed esegue tale codice. Esempio di codice Perl per la seconda parte:

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 è molto difficile da analizzare, ma siete interessati solo a una piccola parte del linguaggio, e sembra che tu abbia un certo controllo sui file di origine, quindi un semplice parser dovrebbe fare il trucco. Una ricerca di CPAN mostra Devel::Tokenizer::C e alcuni moduli C:: come candidati (non ne so nulla se non i loro nomi). Se hai davvero bisogno di un parser C accurato, esiste lo Cil, ma devi scrivere la tua analisi in Ocaml.

2

È possibile utilizzare Exuberant Ctags per analizzare i file di origine anziché utilizzare un modulo CPAN o hackerare qualcosa. Per esempio, per il seguente codice:

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

ctags emette i seguenti:

 
_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: 

I primi, quarto, e quinta colonna dovrebbe essere sufficiente per voi per determinare che cosa struct esistono tipi e quali i loro membri siamo. È possibile utilizzare tali informazioni per generare un programma C che determina la quantità di riempimento di ciascun tipo di struttura.

2

Si potrebbe provare pstruct.

Non l'ho mai usato, ma stavo cercando un modo in cui potreste essere in grado di usare pugnalate e questo suona come sarebbe adatto al conto.

In caso contrario, suggerirei altri modi per analizzare le informazioni sui coltelli.

+0

@daxim - grazie per la correzione – bstpierre

+0

pstruct è un buon suggerimento, e funziona per alcuni file C di base che ho gettato su di esso. Ha anche il vantaggio di essere già installato sulla maggior parte delle macchine. Comunque sta avendo qualche problema con le strutture nidificate. –

1

Se si ha accesso a Visual C++, è possibile aggiungere il seguente pragma di avere il compilatore sputare fuori dove e quanto imbottitura è stato aggiunto:

#pragma warning(enable : 4820) 

A quel punto probabilmente si può solo consumare l'uscita del cl.exe e vai alla festa.

4

Diciamo che avete il seguente module.h:

programma
typedef void (*handler)(void); 

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

struct bar { 
    float y; 
    short z; 
}; 

Un Perl per generare unpack modelli inizia con la materia anteriore consueto:

#! /usr/bin/perl 

use warnings; 
use strict; 

sub usage { "Usage: $0 header\n" } 

Con structs, alimentiamo l'intestazione a ctags e da il suo output raccoglie membri struct. Il risultato è un hash le cui chiavi sono nomi di strutture e i cui valori sono array di coppie del modulo [$member_name, $type].

Nota che gestisce solo alcuni tipi di 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; 
} 

Supponendo che l'intestazione può essere incluso per sé, generate_source genera un programma C che stampa offset per l'uscita standard, riempie le strutture con valori fittizi, e scrive le strutture prime per l'uscita standard preceduto da rispettivi formati in byte.

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; 
} 

generare un modello per unpack in cui il parametro $members è un valore nel hash restituito da structs che è stato convertito con offset (cioè, arrayrefs della forma [$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; 
} 

Infine , raggiungiamo il programma principale in cui il primo compito è generare e compilare il programma 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"; 

Ora leggiamo l'output del programma generato e decodificare lo struct:

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: $!"; 
    } 
} 

uscita:

$ ./unpack module.h 
bar: 0 1 
foo: 2 3 4

Per riferimento, il programma C generato per 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; 
} 
+0

Grazie per l'ottima spiegazione e un programma stand alone senza molte dipendenze. Questo gestisce le strutture annidate? –

+0

@Desertato Come scritto, gestisce strutture piatte con membri di pochi tipi intrinseci. Gestire le strutture annidate non richiederebbe molto: 'template' avrebbe bisogno di chiamarsi quando vedesse un tipo che è un'altra struttura, e naturalmente' structs' avrebbe bisogno di essere più clemente nei tipi che accetta. –

+0

wow, la risposta più lunga in una domanda che non è una di quelle "Qual è il tuo preferito ___?" – mrk

Problemi correlati