2010-07-20 7 views
9

Estoy tratando de escribir una herramienta que tomará como entrada algunos códigos C que contienen estructuras. Compilará el código, luego buscará y dará salida al tamaño y al desplazamiento de cualquier relleno que el compilador decida agregar a las estructuras dentro de él. Esto es bastante sencillo de hacer a mano para una estructura conocida que utiliza offsetof, sizeof y alguna adición, pero no puedo encontrar una manera fácil de hacerlo automáticamente para cualquier estructura de entrada.¿Una forma de encontrar el tamaño y la ubicación del relleno en una estructura?

Si supiera cómo iterar a través de todos los elementos en una estructura, creo que podría obtener la herramienta escrita sin problemas, pero hasta donde sé, no hay forma de hacerlo. Espero que algunas personas de StackOverflow conozcan una forma. Sin embargo, no estoy atascado en mi enfoque, y ciertamente estoy abierto a cualquier enfoque alternativo para encontrar relleno en una estructura.

+1

I don' Entiendo lo que estás pidiendo. ¿Desea construir un sistema de reflexión genérico para el lenguaje C? Querer el reflejo en C es como querer cruzar el Océano Atlántico con una motocicleta ... –

+0

Disculpe si no estaba claro en mi pregunta. No estoy buscando un sistema de reflexión genérico (¡espero!). Pensé que mi solución implicaría analizar el código fuente C con Perl y generar algún código C modificado con un tamaño y una llamada de compensación para cada elemento en la estructura.Eso proporcionaría el tamaño y la ubicación de todos los elementos en la estructura, y de ahí es trivial encontrar e informar cualquier relleno que contenga la estructura. ¿Parece un enfoque razonable o abordarías el problema de otra manera? –

Respuesta

6

¿No es esto lo que pahole hace?

+1

De hecho, es * exactamente * lo que hace 'phohole', aunque opera en binarios compilados con información de depuración en lugar de fuente. – caf

+0

+1 Esto es lo que estaba buscando pero no encontré ... – bstpierre

-1

No creo que exista ninguna función de propósito general para la introspección/reflexión en C. Para eso están Java o C#.

+0

Esta respuesta parece ser correcta, ¿por qué se downvoted? –

+0

Probablemente fue una votación negativa porque no contribuyó a la pregunta. El hecho de que no haya introspección no significa que no haya forma de encontrar el relleno, como lo demuestran las muchas respuestas útiles (y mi programa de trabajo). –

+0

@Desert: Eso no es el caso. Fue una de las trece respuestas downvoted en segundos el uno del otro por un usuario que ofendí. –

-1

No hay una característica de lenguaje C++ para iterar a través de los miembros de una estructura, así que creo que no tiene suerte.

Es posible que pueda reducir parte de la placa de la caldera con un macro, pero creo que está atascado especificando todos los miembros explícitamente.

+0

Esta respuesta parece ser correcta, ¿por qué fue downvoted? –

3

Prefiero leer y escribir en un búfer, luego tener una función cargar los miembros de la estructura del búfer. Esto es más portátil que leer directamente en una estructura o usar memcpy. Además, este algoritmo libera cualquier preocupación sobre el relleno del compilador y también se puede ajustar para manejar Endianess.

Un programa correcto y robusto vale más que cualquier tiempo dedicado a la compactación de datos binarios.

+0

Lamentablemente, no tengo voz en el programa existente que está diseñado para ayudar, por lo que no puedo usar búferes. Mi objetivo es minimizar los errores introducidos cuando cambian las estructuras de datos. Actualmente, un cambio en la estructura de datos cambia el desplazamiento de bytes de todos los siguientes miembros de la estructura de una manera no predecible. Solo estoy tratando de agregar cierta predictibilidad allí. –

1

Haga que su herramienta analice la definición de estructura para encontrar los nombres de los campos, luego genere el código C que imprime una descripción del relleno de la estructura, y finalmente compile y ejecute ese código C. Ejemplo de código Perl para la segunda 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 es muy difícil de analizar, pero sólo está interesado en una parte muy pequeña de la lengua, y suena como que tiene un cierto control sobre los archivos de origen, entonces un analizador simple debería hacer el truco. Una búsqueda de CPAN da como resultado Devel::Tokenizer::C y algunos módulos C:: como candidatos (no sé nada de ellos más que sus nombres). Si realmente necesita un analizador C preciso, existe Cil, pero debe escribir su análisis en Ocaml.

2

Puede usar Exuberant Ctags para analizar sus archivos fuente en lugar de usar un módulo CPAN o hackear algo usted mismo. Por ejemplo, por el siguiente código:

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

ctags emite los siguientes:

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

la primera, cuarta y quinta columnas debe ser suficiente para que pueda determinar qué estructura existen tipos y lo que sus miembros son. Puede usar esa información para generar un programa en C que determine la cantidad de relleno que tiene cada tipo de estructura.

2

Puede probar pstruct.

Nunca lo he usado, pero estaba buscando alguna forma en la que puedas usar puñaladas, y esto suena a que encaja en la cuenta.

Si no es así, sugeriría buscar otras formas de analizar la información de las estacas.

+0

@daxim - gracias por la corrección – bstpierre

+0

pstruct es una buena sugerencia, y funciona para algunos archivos básicos de C que he lanzado. También tiene la ventaja de estar ya instalado en la mayoría de las máquinas. Sin embargo, está teniendo algunos problemas con las estructuras anidadas. –

1

Si usted tiene acceso a Visual C++, se puede añadir la siguiente pragma tener el compilador escupió dónde y cómo se añadió relleno tanto:

#pragma warning(enable : 4820) 

En ese momento es probable que sólo puede consumir la salida de cl.exe e ir de fiesta.

4

Digamos que tiene la siguiente module.h:

programa
typedef void (*handler)(void); 

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

struct bar { 
    float y; 
    short z; 
}; 

Un Perl para generar unpack plantillas comienza con el asunto delante habitual:

#! /usr/bin/perl 

use warnings; 
use strict; 

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

Con structs, que alimentan la cabecera para ctags y desde su salida recolecta miembros de la estructura. El resultado es un hash cuyas claves son nombres de estructuras y cuyos valores son matrices de pares del formulario [$member_name, $type].

Tenga en cuenta que maneja solo unos pocos tipos de 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; 
} 

Suponiendo que la cabecera se puede incluir por sí misma, generate_source genera un programa C que imprime las compensaciones a la salida estándar, llena estructuras con valores ficticios, y escribe las estructuras primas a la salida estándar precedido por sus respectivos tamaños en bytes

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

Generar una plantilla para unpack donde el parámetro $members es un valor en el hash devuelto por structs que ha sido aumentada con offsets (es decir, arrayrefs de la 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; 
} 

Finalmente , llegamos al programa principal donde la primera tarea es generar y compilar el programa 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"; 

Ahora leemos la salida del programa generado y decodificar la estructuras:

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

Salida:

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

Como referencia, el programa de C generado para module.h es

#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

Gracias por una gran explicación y un programa independiente sin muchas dependencias. ¿Maneja esto las estructuras anidadas? –

+0

@Desert ed Como está escrito, maneja estructuras planas con miembros de solo unos pocos tipos intrínsecos. El manejo de estructuras anidadas no tomaría mucho: 'template' necesitaría llamarse a sí mismo cuando vea un tipo que sea otra estructura, y por supuesto' structs' necesitaría ser más indulgente en los tipos que acepta. –

+0

guau, la respuesta más larga en una pregunta que no es una de esas "¿Cuál es tu favorita ___?" – mrk

Cuestiones relacionadas