分析 lib 库中的符号冲突

作者: Joy___ | 来源:发表于2016-08-21 09:51 被阅读509次

    这篇文章主要是自己这周的学习成果,传播性不是很高,供自己日后查阅。文章主要涉及到一些编译原理的基础、PythonLinux命令。

    从编译链接说起

    一个工程变成可执行文件,有预处理、编译、汇编、链接等过程,经过编译汇编之后输出的文件是.o文件,然后对这些.o文件进行链接,就是最终的可执行文件。静态链接库一般是一个.a 压缩文件,解压后可以得到众多的.o文件,静态库也会在链接过程中连接到你的可执行文件中去。我们在工程编译的时候遇到的Duplicate Symbol错误,一般都是引用的静态库中存在符号冲突。

    啥是符号

    我们在源码中写的全局变量名、函数名、类名在生成的.o文件中都可以叫做符号,存在一个符号表中。下面可以查看一个最简单程序的符号

    #include <stdio.h>  
    #include "test.h"  
    void print()  
    {  
        printf("rainy days\n");  
    } 
    
    #include "test.h"  
    int main()  
    {  
        print();  
        return 0;  
    }
    

    通过以下命令进行查询

    192:test Joy$ nm main.o
    0000000000000000 T _main
                     U _print
    192:test Joy$ nm test.o
    0000000000000000 T _print
                     U _printf
    

    用到的几个Linux命令

    nm

    nm 用来列出一个目标文件中的各种符号,列举几个常见的符号类型

    • T:Text段的符号,比如文件中实现了一个函数function,则function就是这种符号
    • U:未定义的符号。如果文件中引用了不存在的函数,则这些未定义的函数符号就是这种类型
    • S:未初始化的符号,比如全局变量int s;则s的符号就是此类型

    nm 中还有几个常搭配使用的关键字,比如-A、-n 、-u等,我这里使用了-A来列出符号名的时候同时显示来自于哪个文件

    192:test Joy$ nm -A libQYReaderLib.a
    
    (for architecture arm64):/Users/wjl/Downloads/Pods/QYReader_debug/Debug/libQYReaderLib.a:SkRegion.o: 00000000000001e8 T __ZN8SkRegionC1Ev
    

    ar

    ar 用来创建、修改库,也可以从库中提出单个模块。例如下面可以提取.a文件的.o文件

    192:test Joy$ ar -t /tmp/libQYReaderLib.a
    
    __.SYMDEF
    QRChargeHistoryViewController.o
    QRFontChooseCell.o
    CGFontToFontData.o
    QRSearchCard-9C0FBD175825148E.o
    QRBookViewerBrightView.o
    QRBaseModel-9CF48C494CFB4381.o
    QRProgressButton.o
    QROrderModel.o
    blowfish.o
    QRSearchCard-9C0FBD175825148E.o
    QRSelectedBarExView.o
    QRSegementController.o
    

    lipo

    后面通过查看符号路径的时候,发现一个.a文件会同时存在不同的处理器指令集上,也就是说这个库可能支持i386,也支持arm64。那么lipo可以对通用静态库做一些操作,比如“瘦身”,只支持单一处理器指令集。详情可以通过 man lipo查看

    第一行 Python

    当听到写一个脚本,分析下所有的符号冲突,并列举冲突发生的.o.a文件是哪个的时候,我是懵比的,因为我从来没写过Python

    第一次开发所使用的到的Python模块有 ossyssubprocessargparse,作为初次入门篇,我就介绍一下他们的用途吧

    os 模块

    os 模块是一个Python的系统编程的操作模块,提供了操作文件和目录等方法。 下面简单列举所使用到的 API

    os.getcwd()命令来获取当前工作目录

    >>> import os
    >>> os.getcwd()
    '/Users/wjl/Desktop/test/test'
    

    os.path.join(path,name)连接目录与文件名或目录

    >>> dir = os.getcwd()
    >>> result =  os.path.join(dir,test.c)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'test' is not defined
    >>> result =  os.path.join(dir,"test.c")
    >>> print result
    /Users/wjl/Desktop/test/test/test.c
    

    os.path.split()函数返回一个路径的目录名和文件名

    >>> print result
    /Users/wjl/Desktop/test/test/test.c
    >>> (d, lib_name) = os.path.split(result)
    >>> print d
    /Users/wjl/Desktop/test/test
    >>> print lib_name
    test.c
    

    sys 模块

    sys 模块提供了访问由解释器使用和维护的一些变量和与解释器强烈交互的函数

    比如sys.exit()退出程序

    subprocess 模块

    顾名思义,与子进程有关系的一个模块。subprocess 模块允许你产生新的进程,然后连接它们的输入/输出/错误管道,并获取返回值。该模块中进程的创建和管理底层是通过Popen类处理的。

    class subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=())
    

    args 应该是一个程序参数的序列或者一个单一的字符串。我这里使用的是字符串,对于是字符串的情况,要指定shellTrue,也就是使用shell来执行程序。

    >>> import subprocess
    >>> cmd = "nm -o /Users/wjl/Downloads/Pods/QYReader_debug/Debug/libQYReaderLib.a"
    >>> symbols = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()
    >>> print symbols
    

    运行的结果是:列举libQYReaderLib.a文件中的所有符号,部分如下

    (for architecture arm64):/Users/wjl/Downloads/Pods/QYReader_debug/Debug/libQYReaderLib.a:SkRegion.o: 00000000000001e8 T __ZN8SkRegionC1Ev
    (for architecture armv7):/Users/wjl/Downloads/Pods/QYReader_debug/Debug/libQYReaderLib.a:HtmlNode.o: 0000055a T __ZNK8HtmlNode12getNodeStyleEv
    (for architecture i386):/Users/wjl/Downloads/Pods/QYReader_debug/Debug/libQYReaderLib.a:ftbase.ios_i386.o: 000067b0 T _HY_FT_List_Up
    

    如果 shell 不设置为 true,程序将无法执行。

    stdinstdoutstderr 分别指定要执行的程序的标准输入、标准输出和标准错误文件句柄。一般会使用文件、PIPENonePIPE表示为子进程建立管道,管道是子、父进程通信的一种方式。

    symbols = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()
    

    后面的 communicate()主要是为了保持可以及时的读出内容,防止程序堵死

    argparse

    argparse 是 python 命令行解析工具,比如下面这个简单的例子

    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("square", help="display a square of a given number",
                        type=int)
    args = parser.parse_args()
    print args.square**2
    

    在终端的运行结果:

    $ python prog.py 4
    16
    
    

    还可以使用它来查看符号

    
       parser = argparse.ArgumentParser()
       parser.add_argument("-n", "--nm", dest="nm", help="set the ndk nm path")
       parser.add_argument("-l", "--libs", dest="libs", help="set the libs library")
       args = parser.parse_args()
       
       # 根据nm 指令,以及传进来的 libs 路径 来查看其路径下所有 .o文件或者符号
       # checklibs(args.libs, args.nm)
    

    详情可以学习这篇文章:Argparse Tutorial¶

    成果

    这三天就写了这么点代码,初步可以实现查看重复函数符号,并列举出所对应的处理器指令集,以及发生冲突的.a.o文件名称。可以把它放到项目目录下运行起来,查看输出结果

    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    import os
    import sys
    import subprocess
    
    def main():
        
        cmd = "find . -name *.a"
        output = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()[0]
        libs = output.split("\n")
        
        # get .a and path
        lib_names = {}  # {"name":"dir"}
        for lib in libs:
            if lib.find('.a') == -1:
                continue
            (d, lib_name) = os.path.split(lib)
            lib = os.path.join(os.getcwd(), lib[2:])
            lib_names[lib_name] = lib
        
        # get symbols
        results = {} # {"symbol":[lib1,lib2]}
        for lib_name in lib_names.keys():
            cmd = "nm -A %s" % lib_names[lib_name]
            symbols = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()
            if symbols and len(symbols) != 0:
                symbols = symbols[0].strip().strip('\n').split('\n')
            # add symbols
            for symbol in symbols:
    
                # is define?
                pos = symbol.find(" T ")
                if pos == -1:
                    continue
                symbolArray = symbol.split(":")
                
                symbol = symbolArray[0] + symbolArray[len(symbolArray)-1]
                
                # get symbol name
                #symbol = symbol[pos + 3:]
    
                # get the symbol libs
                symbollibs = results.get(symbol)
                if not symbollibs:
                    symbollibs = []
    
                symbollibKey = lib_name + "." + symbolArray[2]
                # append library 
                symbollibs.append(symbollibKey)
    
                # update the symbol info
                results[symbol] = symbollibs
    
        # add symbols
        for symbol, symbollibs in results.iteritems():
    
            # the repeat count
            count = len(symbollibs)
            if count > 1:
                print symbol
                print symbollibs
    
    # entry
    if __name__ == "__main__":
        sys.exit(main())
    

    测试结果如下:

    (for architecture i386) 000000f0 T _png_set_read_fn
    ['libQYReaderLib.a.pngrio.o', 'libQYReaderLib.a.pngrio.o']
    (for architecture armv7) 0000285c T __ZN4JsonlsERNSt3__113basic_ostreamIcNS0_11char_traitsIcEEEERKNS_5ValueE
    ['libvodnet.a.json_writer.o', 'liblivenet6.a.json_writer.o']
    (for architecture arm64) 0000000000003020 T __ZN22SkRGB16_Shader_Blitter8blitRectEiiii
    ['libQYReaderLib.a.SkBlitter_RGB16.o', 'libQYReaderLib.a.SkBlitter_RGB16.o']
    

    相关文章

      网友评论

      本文标题:分析 lib 库中的符号冲突

      本文链接:https://www.haomeiwen.com/subject/wqaisttx.html