zoukankan      html  css  js  c++  java
  • 【VS开发】Caffelib中出现的问题:强制链接静态库所有符号(包括未被使用的)

    C++程序在链接一个静态库时,如果该静态库里的某些方法没有任何地方调用到,最终这些没有被调用到的方法或变量将会被丢弃掉,不会被链接到目标程序中。这样做大大减小生成二进制文件的体积。但是,某些时候,即使静态库里的某些方法没有任何地方使用到,我们也希望将这些没有使用到的代码编译进最终的二进制文件中。

    为什么会有这样的需求?的确,存在这种需求的是少数情况,但是一旦你遇到下面的需求,就变得必须了。比如:

    1. 动态插件机制。代码中没有直接调用某方法,但是希望能在运行时动态加载执行某方法。
    2. 执行代码覆盖率统计。需要统计静态库所有代码的覆盖情况,而不只是被使用到的代码覆盖情况。

    如果是gcc编译,比较好办,只需要加上--whole-archive链接选项。但是在Windows平台,微软的编译器没有这样的选项,一个最接近的选项是/OPT:NOREF

    文档见:https://msdn.microsoft.com/en-us/library/bxwfs976.aspx
    说明:/OPT:REF eliminates functions and data that are never referenced; /OPT:NOREF keeps functions and data that are never referenced.

    /OPT:NOREF在Debug下是默认打开的,而且只能强制保留本工程未被使用到的函数和变量。对于引用的静态库的未被使用的函数和变量是不生效的。甚至有人认为这是微软的BUG在这个帖子里热烈讨论过:LINK.EXE BUG: /OPT:NOREF option doesn't work!

    遇到同样问题的可不止我一个人,比如StackOverFlow里就有人问:What is the Visual studio equivalent to GNU ld option --whole-archive

    有人建议他用/INCLUDE 选项强制链接未使用的符号,也有人说使用/OPT:NOREF(显然不行)。

    使用/INCLUDE 指定某个符号强制链接是可以的。但是,假如静态库中有成百上千个符号需要强制/INCLUDE,怎么办?

    所以,最好的方法,也是上面讨论/OPT:NOREF BUG的帖子里有人提到的方法,就是在代码中使用:

    #pragma comment(linker, "/include:?emptyreference@Noisy@@QAEXXZ")

    通过上面的方法,可以让链接器强制include一个符号,include:后面的是符号名称。如果要强制include静态库中所有符号,需要把静态库中的所有符号找出来,然后通过上面的方法强制include。

    人手工找出所有Symbols,然后添加上面的代码是不太靠谱的。一方面Symbols的格式可读性太差不好维护,另一方面假如静态库符号信息修改了,这个维护代价就更大了。所以,必须让这个过程自动完成。

    查看静态库所有符号列表,Linux里可以使用nm ,Windows平台可以使用dumpbin

    执行dumbin.exe需要注意,必须在Visual Studio的开发命令行环境才能执行。不过有个小技巧可以让你不必在Developer Command Prompt执行,就是假如是VS2013环境,建一个批处理,在开头加上:

    @echo off
    if defined VS120COMNTOOLS (
        call "%VS120COMNTOOLS%vsvars32.bat")

    我们使用dumpbin /LINKERMEMBER xxx.lib,可以列出所有的符号名字,比如查看静态库MyLib.lib所有符号:

    d:CodeCppLinkAllSymbolsDebug>dumpbin.exe /linkermember:1 MyLib.lib
    Microsoft (R) COFF/PE Dumper Version 12.00.30501.0
    Copyright (C) Microsoft Corporation.  All rights reserved.
     
     
    Dump of file MyLib.lib
     
    File Type: LIBRARY
     
    Archive member name at 8: /
    557D4C17 time/date Sun Jun 14 17:40:39 2015
             uid
             gid
           0 mode
          ED size
    correct header end
     
        9 public symbols
     
          328 ??4Turtle@@QAEAAV0@ABV0@@Z
          328 ??_C@_0M@KEAKLOKJ@Turtle?5run?4?$AA@
          328 ?Download@@YAHXZ
          328 ?Run@Turtle@@QAEXXZ
         19CE ?FishRun@@YAXXZ
         19CE ?Run@Fish@@QAEXXZ
         2D16 ??_C@_08EMEDHABH@Dog?5run?4?$AA@
         2D16 ?Foo@@YAHXZ
         2D16 ?Run@Dog@@QAEXXZ
     
      Summary
     
            28B4 .debug$S
              F0 .debug$T
             102 .drectve
              15 .rdata
               C .rtc$IMZ
               C .rtc$TMZ
             15A .text$mn

    因此,只需要执行dumpbin,并且在输出结果中抽取出所有的符号名称,然后自动生成#pragma comment(linker, "/include:xxx")代码即可。

    于是,我写了一个Python脚本,执行dumpbin,然后通过正则表达式拿到所有符号名称,自动生成强制include了所有符号的头文件。关键代码如下:

    import re
     
    regex = re.compile(r"s+.*s([?_]+.*)")
     
    exclude = []
     
    def gen_header_file_for_lib(lib_path, header_path):
        cmd = ['dumpbin.exe','/linkermember:1', lib_path]
        lines = execute_command(cmd)
        symbols = find_matches(lines, regex, exclude)
     
        with open(header_path, 'w') as f:
            header_guard = "LINK_ALL_SYMBOLS_H_"
            f.write("#ifndef " + header_guard + '
    ')
            f.write("#define " + header_guard + '
    ')
            f.write("// Generated by GenLinkerSymbols.py. Do not modify! 
    
    ")
     
            for symbol in symbols:
                pragma_line = '#pragma comment(linker, "/include:' + symbol + '")'
                f.write(pragma_line + '
    ')
            f.write("
    #endif // " + header_guard + '
    ')
     
        print("Link symbols count: %s" % len(symbols))
     
    def find_matches(lines, regex, exclude_list):
        def match(line):
            m = regex.match(line)
            if m:
                return m.group(1).split()[0]
            return None
     
        def exclude_filter(line):
            if not line:
                return False
     
            for exclude in exclude_list:
                if line.find(exclude) >= 0:
                    return False
            return True
     
        matches = filter(exclude_filter, map(match, lines))
        return list(set(matches))

    结合Visual Studio工程配置里的Post-Build Event,就可以在编译静态库之后自动更新头文件了。比如:

    python ..GenSymbolsHeader.py $(OutDir)$(TargetName)$(TargetExt) ..IncludeLinkAllSymbols.h

    在使用该静态库的工程代码中,只需要#include "LinkAllSymbols.h" 就可以了。

    对比

    使用OpenCppCoverage进行代码覆盖率测试,对比如下:

    正常情况下,不强制在linker时include静态库所有符号时,代码覆盖率结果为:

    noinclude

    通过上面的方法,自动生成LinkAllSymbols.h并#include "LinkAllSymbols.h",覆盖率结果为:

    included

    github

    所有代码见:https://github.com/coderzh/LinkAllSymbols

  • 相关阅读:
    Proj THUDBFuzz Paper Reading: PMFuzz: Test Case Generation for Persistent Memory Programs
    入围 WF 后训练记
    算法竞赛历程
    2021 多校 杭电 第十场
    2021 多校 杭电 第九场
    2021 多校 牛客 第十场
    2021 多校 牛客 第九场
    2021 多校 杭电 第八场
    2021 多校 杭电 第六场
    2021 多校 杭电 第七场
  • 原文地址:https://www.cnblogs.com/huty/p/8518101.html
Copyright © 2011-2022 走看看