2012-05-20 14 views
7

Actualmente estoy trabajando en un marco de compilación de C/C++ flexible que (con suerte) abriré fuente bastante pronto. (Consulte la pregunta this para obtener más información).Genera automáticamente dependencias de archivo de objeto (vinculador) para ejecutables de C/C++

Estoy utilizando el siguiente comando para generar las dependencias del archivo #include para los archivos fuente/encabezado.

gcc -M -MM -MF 

¿Hay una manera de inferir inteligentemente dependencias enlazador (.o archivos ejecutables) para (pruebas unitarias + ejecutable principal para la plataforma de destino en mi caso) utilizando gcc utilidades de GNU/de una manera similar a la anterior? Actualmente, el marco hace muchas suposiciones y es bastante tonto al determinar estas dependencias.

He oído hablar de un enfoque donde el comando nm se puede utilizar para obtener una lista de símbolos indefinidos en un archivo de objeto. Por ejemplo, correr nm en un archivo objeto (compilado usando gcc -c) se le ocurre algo como esto -

nm -o module.o 

module.o:   U _undefinedSymbol1 
module.o:   U _undefinedSymbol2 
module.o:0000386f T _definedSymbol 

uno podría buscar otros archivos de objetos, donde estos símbolos no definidos son definido para llegar a una lista de las dependencias de archivos objeto requeridas para vincular exitosamente el archivo.

¿Se considera esto como la mejor práctica para determinar las dependencias del vinculador para los ejecutables? ¿Hay alguna otra forma de inferir estas dependencias? Suponga que todos los archivos de objeto ya existen (es decir, que ya se han compilado utilizando gcc -c) al proponer su solución.

+0

¿Cuál sería el punto de partida para este enfoque? Inicialmente no tiene archivos de objeto y, por lo tanto, no hay nada que examinar con 'nm' ... –

+0

Suponga que todos los archivos se han compilado (es decir, justo antes del proceso de vinculación) y que ya existen sus correspondientes archivos de objeto. – thegreendroid

+0

En cuyo caso, no hay dependencias para resolver. Si B es una dependencia de A, significa que B es necesario para * crear * A. –

Respuesta

8

Si hay varios ejecutables (o incluso un solo ejecutable) que necesitan diferentes conjuntos de dependencias, la forma normal y clásica de manejar eso es usar una biblioteca - estática .a o compartida (o equivalente) - para contener los archivos objeto que pueden ser utilizados por más de un programa y para vincular los programas con esa biblioteca. El vinculador extrae automáticamente los archivos de objeto correctos de un archivo estático. El proceso de biblioteca compartida es un poco diferente, pero el resultado neto es el mismo: el ejecutable tiene los archivos de objeto correctos disponibles en tiempo de ejecución.

Para cualquier programa, hay al menos un archivo exclusivo del programa (normalmente, ese es el archivo que contiene el programa main()). Puede haber algunos archivos para ese programa. Esos archivos son probablemente conocidos y se pueden enumerar fácilmente. Los que posiblemente necesite según las opciones de configuración y compilación probablemente se compartan entre programas y se manejen fácilmente a través del mecanismo de la biblioteca.

Tiene que decidir si desea usar bibliotecas estáticas o compartidas. Crear bibliotecas compartidas es más difícil que crear bibliotecas estáticas. Por otro lado, puede actualizar una biblioteca compartida e inmediatamente afectar a todos los programas que la utilizan, mientras que una biblioteca estática puede cambiarse, pero solo los programas que se vuelven a vincular con la nueva biblioteca se benefician de los cambios.

4

La utilidad nm lee archivos de objeto (y archivos, como bibliotecas .a) utilizando libbfd. Estoy pensando que lo que realmente querrás hacer es procesar una base de datos de los símbolos públicos definidos en las bibliotecas que conoces, y en los archivos de objetos que forman parte de este proyecto, de modo que a medida que generes cada nuevo archivo de objeto, Puede ver los símbolos indefinidos en él y determinar qué objeto (simple o en una biblioteca) necesita vincular para resolver las referencias. Básicamente, estás haciendo el mismo trabajo que el vinculador, pero al revés, para que puedas ver qué símbolos puedes localizar.

Si está trabajando con GCC, siempre puede buscar en los paquetes fuente de sus 'binutils' para encontrar las fuentes en nm, e incluso en ld si así lo desea. Ciertamente no desea ejecutar nm y analizar el resultado cuando solo está utilizando libbfd bajo el capó, simplemente llame a libbfd usted mismo.

+0

¡No lo sabía, excelente consejo! Gracias @Wexxor! – thegreendroid

5

La siguiente secuencia de comandos de Python se puede utilizar para recoger y procesar la salida nm para todos los archivos de objeto en el directorio actual:

#! /usr/bin/env python 

import collections 
import os 
import re 
import subprocess 

addr_re = r"(?P<address>[0-9a-f]{1,16})?" 
code_re = r"(?P<code>[a-z])" 
symbol_re = r"(?P<symbol>[a-z0-9_.$]+)" 
nm_line_re = re.compile(r"\s+".join([addr_re, code_re, symbol_re]) + "\s*$", 
         re.I) 

requires = collections.defaultdict(set) 
provides = collections.defaultdict(set) 

def get_symbols(fname): 
    lines = subprocess.check_output(["nm", "-g", fname]) 
    for l in lines.splitlines(): 
     m = nm_line_re.match(l) 
     symbol = m.group('symbol') 
     if m.group('code') == 'U': 
      requires[fname].add(symbol) 
     else: 
      provides[symbol].add(fname) 

for dirpath, dirnames, filenames in os.walk("."): 
    for f in filenames: 
     if f.endswith(".o"): 
      get_symbols(f) 

def pick(symbols): 
    # If several files provide a symbol, choose the one with the shortest name. 
    best = None 
    for s in symbols: 
     if best is None or len(s) < len(best): 
      best = s 
    if len(symbols) > 1: 
     best = "*" + best 
    return best 

for fname, symbols in requires.items(): 
    dependencies = set(pick(provides[s]) for s in symbols if s in provides) 
    print fname + ': ' + ' '.join(sorted(dependencies)) 

El script busca en el directorio actual y todos los subdirectorios para .o archivos, llamadas nm de cada archivo encontrado y disecciona la salida resultante. Los símbolos que no están definidos en un archivo .o y definidos en otro se interpretan como una dependencia entre los dos archivos. Los símbolos definidos en ninguna parte (normalmente proporcionados por bibliotecas externas) se ignoran. Finalmente, el script imprime una lista de dependencias directas para todos los archivos de objetos.

Si varios archivos de objeto proporcionan un símbolo, este script supone arbitrariamente una dependencia en el archivo de objeto con el nombre de archivo más corto (y marca el archivo elegido con un * en la salida). Este comportamiento se puede modificar modificando la función pick.

La secuencia de comandos funciona para mí en Linux y MacOS, no he probado ningún otro sistema operativo y el script se prueba solo ligeramente.

+1

Gracias por publicar esto. Me tropecé aquí tratando de responder a una pregunta ligeramente diferente: en una aplicación que no maneja juiciosamente sus dependencias, ¿cómo puedo averiguar si algo realmente NECESITA vincular contra una biblioteca o un archivo de objeto en particular? Probablemente tenga que modificar tu script, pero siempre es más fácil construir sobre algo que crearlo de nuevo. –

+0

Además, al menos con Python 2.7, esto no funcionó sin modificaciones. En el ciclo sobre el retorno de os.walk() tuve que unirme a 'dirpath' y' f' para obtener el filepath, y pasar el filepath como argumento a 'get_symbols'. –

Cuestiones relacionadas