【问题标题】:Are there good tips or tools for removing third party C and C++ libraries from a codebase? (OS X or Linux)是否有从代码库中删除第三方 C 和 C++ 库的好技巧或工具? (OS X 或 Linux)
【发布时间】:2011-10-21 05:39:00
【问题描述】:

我正在减少和隔离我对某些库的使用。我编写的许多现有程序直接使用这些库。我想要编译器(在这种情况下是 GCC 和/或 Clang)或一些工具来帮助我在我的代码库中识别这些用途。简而言之,我想在整个代码库中毒化这些库的使用,但它们将被一个库使用,并且一个库将对我的代码库中的其他模块可见。

问题:

1) 你知道可以帮助我解决这个问题的工具吗?

2) 或者您能否推荐一些策略来简化此过程?

条件和详情:

  • 不能删除它们的包含。
  • 由于我的代码库大小和要隔离的符号数量,搜索无效。
  • 考虑到代码库的复杂性和要删除的符号数量,使用重构工具将过于乏味。
  • 由于第三方库中声明的数量,不能单独弃用符号。
  • 第三方库接口大多用 C 编写。
  • 翻译将是 C++ 和 Objective-C++。
  • 对于我的构建配置方式而言,预处理器的诡计并不优雅,它会更改太多文件。
  • 不需要取消每次最后一次使用。理想情况下,它们会是,但大多数用途是令人满意的。这不是一项要求,因为要更新的内容太多。
  • 在这种情况下,将它们从链接阶段移除并不是一个好的选择(在更新 #3 中有详细说明)。
  • 理想情况下,此工具或策略可以在 OS X 上使用,但我也可以构建大量针对 Linux 的程序。

想到的一些策略:

到目前为止,对于这种情况,我想出的最好办法是重新声明库使用的类型,并用不推荐使用的属性来装饰它们:

typedef IHREType IHREType __attribute__((__deprecated__));

但这不会涵盖所有情况,经过几次迭代后信噪比会很高。

另一种方法是在我使用的根命名空间中重新声明这些类型:

namespace MON {
typedef t_poisoned IHREType;
}

但这会变得有点混乱。

所以我想我将从弃用的属性策略开始,但在我这样做之前,我想其他人已经解决了这个问题并且会知道更好的解决方案。

更新 #1

  • K-ballo 提到了一个很好的策略(通过包含中毒)。不幸的是,在我的情况下它不起作用,我想隔离的 API 也可以在系统框架中找到,这些框架是通过我不想隔离的 API 包含的。

更新 #2

由于响应数量少而添加了 Linux。

更新 #3

> > Justin: Removing them from the link stage is not a good option in this case.
> thiton: Why not? 

详细说明这一点:我喜欢此时库和项目的布局方式。有静态库和动态库的组合。更改该结构并同步依赖项非常耗时(尽管对于某些库来说,孤立的情况可能会很好地利用时间......)。链接器还解决了由于依赖关系(例如在系统库中)而我想要删除的大量符号。

我正在制定的计划

代码库中有数百个 Xcode 项目(为其他构建器/IDE 添加到该项目中)。

我将在这里关注这些更新几天,那里几天;在这个时间范围内,100% 的覆盖率不是一个现实的目标,目前也不是一个要求。由于任务的规模和代码库的当前状态,我现在想专注于按数量删除出现。按数字删除也是可取的,因为它最终会减少构建时间(构建这一切需要一段时间)。一旦减少,我将转向完全消除——至少,这是我目前的计划。在这种情况下,我有时间执行更新,但还不紧急。如果您的建议偏离此模型,我确实有灵活性。

【问题讨论】:

  • "在这种情况下,将它们从链接阶段移除并不是一个好的选择。"为什么不?我认为链接器命令行中正确放置的毒药库可以让您在这里度过没有人做内联或宏魔术的一天。如果您可以更清楚地说明您的详细信息的原因,那么回答会容易得多。截至目前,我只看到一个“非选项”声明列表,当我回答时会扩展:-)。
  • @thiton 很高兴提供详细信息:请参阅更新 #3。如果您需要更多信息,请询问 - 我试图让它远离“tl;dr”。

标签: c++ c deprecated dependency-management


【解决方案1】:

我将提供带有 #error#warning 指令的包含的浅版本,以便预处理器让我知道谁在使用这些文件。

【讨论】:

  • +1 总体上是一种出色的策略,我过去曾有效地使用过这种策略。我不确定我的问题是对细节太重还是太轻,但不幸的是,这种方法是不可能的在我的特定情况下,因为我要隔离的 API 包括在系统框架中声明的 API ;因此我真的无法控制它们的包含,因为它们也是我不想隔离的 API 的依赖项。我会将这一点添加到问题中。谢谢。
  • @Justin:您可以通过提供一个简单(临时)包装系统包含将定义一个宏,然后有条件地基于宏的存在警告包含标题.完成该过程后,取下包装纸。 (顺便说一句:删除系统使用的库有什么意义,也就是说,您没有删除依赖项,您无法删除它,系统已经依赖于该库!)
  • @DavidRodríguez-dribeas 我看不出这在这种特殊情况下会有什么帮助,因为我要删除的 API 的标头也可以包含在翻译的其他依赖项中。如果困了我而我错过了重点,请打电话给我=)我将提供一个依赖示例:假设我想在我的程序中删除对 libA 的使用,但 libA 也通过 libB 包含在内,并且 libB 在许多 TU 中使用,我不想改变我对 libB 的使用 - 警告将等同于很多噪音。 (续)
  • (cont) 破解一份 libB 的头文件以删除这些依赖项大约需要一天的时间(因为代码库很大,而且我想隔离的库确实不止一个)。邮件中有一个“重点”的例子。
  • @Justin 您可以创建触发警告的pthread_wrapper.h(但它也包括pthread.h),然后将代码库中的#include <pthread.h> 自动替换为#include "pthread_wrapper.h"。使用pthread.h 的系统库不会更改,也不会触发警告。编译,检测警告来自哪里,然后在需要时手动更改任何您希望允许使用直接包含 pthread.h 的位置。
【解决方案2】:

您可以使用 #pragma GCC poison identifier 指令要求 GCC 就给定 identifier 的进一步使用发出警告

您也可以将__attribute__((deprecated))(在 GCC 中)用于类似的目标。

如果你的代码库足够大,值得付出努力,你可以开发一个 GCC 4.6 插件(或GCC MELT 扩展,做你想做的事。(MELT 是一种高级领域特定语言,用于扩展 GCC )。

而 GCC 插件(痛苦地用 C 编码)或 MELT 扩展(更容易用 MELT 编码)可以为您插入这些属性或 #pragma。

但仅对不太小的代码库才值得自动化此类任务。

【讨论】:

  • +1 感谢 Basile - 这里有一些很好的建议。我也不知道 MELT。
【解决方案3】:

详细说明这一点:我喜欢此时库和项目的布局方式。有静态库和动态库的组合。更改该结构并同步依赖项是耗时的(尽管对于某些库来说,孤立的情况可能会很好地利用时间......)。链接器还解决了由于依赖关系(例如在系统库中)而我想要删除的大量符号。

感谢您的详细说明。我将描述一种基于链接器的方法,因为我不认为这个原因是完全的阻碍,但这当然由你来决定。

您可以编写一个非常小的库,其中包含所有已弃用函数的平衡版本,并将其注入到应弃用您的函数的库的链接器调用中。由于 99.99% 的链接器行看起来像:

ld $(FLAGS) a.o b.o c.o -la -lb -lc

您应该能够以这种方式插入您的库:

ld $(FLAGS) a.o b.o c.o -lpoison -la -lb -lc

实际上没有改变你的链接结构。

优点:

  • 这种方法也适用于 libtool。
  • 无需更改任何来源。
  • 适用于系统库。
  • 捕获 100% 的函数调用。
  • 当 -lpoison 链接到真实对象时,您可以在运行时发出警告而不是错误。

缺点:

  • 如果不使用一些链接器命令行魔法(我对此一无所知,但链接器具有所有必要的信息),您将不得不求助于运行时错误和运行时堆栈跟踪来获取实际通话地点。
  • 无法捕获宏或内联函数。

示例:要使用 pthread_create,您必须编写如下文件:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) {
     /* Print a backtrace and exit */
}

将该文件编译成静态库 libpoison.a 并将其添加到您的包含路径中。

假设 libA 是 pthread 的接口库,libB 和 progc 使用它。然后,将链接器路径修改为:

 # Leave that one unmodified
 ld -o libA.a libA-foo.o libA-bar.o -lpthread
 # Poison the rest
 ld -o libB.a libB-foo.o libB-bar.o -lpoison -lA
 ld -o progc progc-foo.o progc-bar.o -lpoison -lB -lA

【讨论】:

  • +1 有运行时选项也很好。谢谢你。
【解决方案4】:

代码库大小真的是不使用findgrep 之类的好理由吗?这些将比编译项目运行得更快。

如果您只担心链接的库,您可以限制自己通过构建配置文件进行 grepping。你说的是 OS X,所以它可能只是 xcode 配置文件——否则你会添加 make 文件或其他任何东西。无论哪种方式,搜索新的配置文件类型都可能比修改相同的配置文件以特殊方式构建输出要快。

如果它是纯头文件,那么您可能可以在配置文件中查找相应的包含路径。

如果您试图阻止仅使用系统标头库,那么最大的问题就是。在这种情况下,您必须 grep 源代码。

如果您真的反对搜索,您可以为您的编译器(和链接器等)创建包装器,以查找所需的参数,如果找到则发出警告或错误,否则将它们传递给真正的编译器。

【讨论】:

  • +1 谢谢迈克尔。是的,grepping 对我想要删除的 一些 东西很有效。就我而言,我想删除的符号太多了,我可以用 grep 查找一些我想替换的类型名称和一些前缀,然后执行这些更改。这也是我在某些领域应用于代码库的一种方法,因此这项任务很快就会变得乏味,因为每次搜索的结果数量相当少时,我会有很多符号要 grep。 (续)
  • (cont) 更好的是能够对nm 或标签数据库(例如ctags)的输出进行grep 以获得更好的覆盖率。这是我最终可能会编写的程序/脚本。
【解决方案5】:

我建议查看doxygen。它可以生成 CALL_GRAPH 和 CALLER_GRAPH (example)。

这样您就可以从您的代码生成文档并查找第 3 方标头。您可以确定谁调用了该函数。

不幸的是,你需要知道你调用的是什么类型的函数。

【讨论】:

  • +1 感谢 Jakozaur。这是一个很好的实用程序(我也用于此任务)。
【解决方案6】:

以下是我最终使用的代码库中使用的详细转储。

我编写了一个 bash 脚本,它获取 nm 的输出(转储图像的符号),修复并过滤符号,然后 grepped 在整个代码库中匹配符号的结果。

当心:我的脚本能力太可怕了。

#!/usr/bin/env bash

# TODO enter your source root to search here:
source_root=SOME_PATH_TO_SOURCE_CODE

# TODO enter the path to your binary to extract symbols from here:
binary=SOME_PATH_TO_BINARY

# a list of the symbols in binary
nm_symbols=$(nm -g -U -j $binary)

invalid_symbol="INVALID"

function trim_and_filter_symbol() {

    # note: input expects osx binaries
    # you may also want to disable some filters. this is the filter set I used:

    sym=${1}

    if [[
        "_" == ${sym:0:1} &&
        "_" == ${sym:1:1} &&
        "Z" == ${sym:2:1}
        ]]; then
        # ignore c++ symbols
        echo $invalid_symbol
    else
        sym=${sym#_}
        sym=${sym#_}
        sym=${sym#_}
    fi

    char_zero=${sym:0:1}
    char_last=${sym:${#at}-1:1}

    if [[ $char_zero == "$" ]]; then
        echo $invalid_symbol
    elif [[
        $char_zero == "+" ||
        $char_zero == "-" ||
        $char_zero == "[" ||
        $char_last == "]" ||
        $sym == *OBJC_METACLASS_* ||
        $sym == *OBJC_EHTYPE_* ||
        $sym == *OBJC_CLASS_* ||
        $sym == *OBJC_IVAR_*
        ]]; then
        # ignore objc symbols
        echo $invalid_symbol
    elif [[
        $sym == *PRETTY_FUNCTION* ||
        $sym == *func__.* ||
        $sym == *lock.* ||
        $sym == s.* ||
        $sym == *dyfunc.* ||
        $sym == *static_init.* ||
        $sym == *destroy_helper_block* ||
        $sym == *copy_helper_block* ||
        $sym == *block_holder_tmp* ||
        $sym == *block_descriptor_tmp* ||
        $sym == *_block_invoke_*
        ]]; then
        # ignore other miscellaneous symbols
        echo $invalid_symbol

    else
            # return the symbol
        echo $sym
    fi
}

function dump_grep_results() {
    symbol=${1}
    grep_result=${2}

    # filter or format to taste
    echo "*** Output for symbol '$symbol' :"
    echo ${grep_result}
    echo
    echo
    echo
}

echo Grepping source tree $source_root
echo for symbols in binary: $binary...
echo
echo
echo

for symbol_at in $nm_symbols;
do
    trimmed=$(trim_and_filter_symbol ${symbol_at})
    if [[ $invalid_symbol != $trimmed ]]; then

        grep_result=$(grep -r -n -I -H ${trimmed} ${source_root})

        if [[ "0" != ${#grep_result} ]]; then
            dump_grep_results ${trimmed} "$grep_result"
        fi
    fi
done

我将把赏金奖励给迈克尔·安德森(Michael Anderson),因为他朝着正确的方向推动了最接近我的问题所要求的解决方案(见评论)。感谢大家的帮助和回答 - 我赞成你所有的回答 =)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-20
    • 1970-01-01
    • 2020-06-04
    • 2011-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-27
    相关资源
    最近更新 更多