8

我目前正在开发一个灵活的 C/C++ 构建框架,我将(希望)很快开源。(有关一些背景信息,请参阅问题)。

我正在使用以下命令为源/头文件生成#include 文件依赖项。

gcc -M -MM -MF

有没有办法以与上述类似的方式使用 gcc/GNU 实用程序巧妙地推断可执行文件的链接器(.o 文件)依赖项(在我的情况下,单元测试 + 目标平台的主要可执行文件)?目前,该框架做出了很多假设,并且在确定这些依赖关系方面非常愚蠢。

我听说过一种方法,可以使用nm命令在目标文件中列出未定义符号的列表。例如,在目标文件(使用 gcc -c 编译)上运行 nm 会出现这样的结果 -

nm -o module.o

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

然后寻找其他目标文件,其中定义了这些未定义的符号以提供成功链接文件所需的目标文件依赖项列表。

这是否被认为是确定可执行文件的链接器依赖关系的最佳实践?有没有其他方法可以推断这些依赖关系?在提出您的解决方案时,假设所有目标文件都已经存在(即已经使用 gcc -c 编译过)。

4

3 回答 3

8

如果有多个可执行文件(甚至单个可执行文件)需要不同的依赖集,那么处理这种情况的正常经典方法是使用库——静态.a或共享.so(或等效)——来保存可以被由多个程序使用,并将程序与该库链接。链接器会自动从静态存档中提取正确的目标文件。共享库过程略有不同,但最终结果是相同的:可执行文件在运行时具有可用的正确目标文件。

对于任何程序,至少有一个程序唯一的文件(通常是包含该main()程序的文件)。该程序可能有一些文件。这些文件可能是已知的并且可以很容易地列出。根据配置和编译选项,您可能需要的那些可能在程序之间共享,并且可以通过库机制轻松处理。

您必须决定是要使用静态库还是共享库。创建共享库比创建静态库更难。另一方面,您可以更新共享库并立即影响所有使用它的程序,而静态库可以更改,但只有与新库重新链接的程序才能从更改中受益。

于 2012-09-23T07:30:27.000 回答
5

以下 Python 脚本可用于收集和处理nm当前目录中所有目标文件的输出:

#! /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))

该脚本在当前目录和所有子目录中搜索.o文件,调用nm找到的每个文件并剖析结果输出。在一个文件中未定义而在另一个文件中定义的符号.o被解释为两个文件之间的依赖关系。无处定义的符号(通常由外部库提供)被忽略。最后,脚本打印所有目标文件的直接依赖项列表。

如果一个符号由多个目标文件提供,则此脚本任意假定依赖于具有最短文件名的目标文件(并*在输出中用 a 标记所选文件)。这种行为可以通过修改函数来改变pick

该脚本适用于我在 Linux 和 MacOS 上,我没有尝试过任何其他操作系统,并且该脚本只是经过轻微测试。

于 2013-05-29T17:01:08.723 回答
4

nm 实用程序使用 libbfd 读取目标文件(和档案,例如 .a 库)。我在想你真正想做的是处理你知道的库中定义的公共符号的数据库,以及这个项目的一部分的目标文件,这样当你生成每个新的目标文件时,你可以查看其中未定义的符号并确定您需要链接哪个对象(普通对象或库中的对象)以解析引用。本质上,您正在做与链接器相同的工作,但有点相反,这样您就可以找到可以定位的符号。

如果您正在使用 GCC,您可以随时查看您的 'binutils' 的源代码包以找到 nm 的源代码,如果需要,甚至可以找到 ld 的源代码。当它只是在后台使用 libbfd 时,您当然不想运行 nm 并解析输出,只需自己调用 libbfd 即可。

于 2012-09-25T22:43:57.493 回答