分析 lib 库中的符号冲突

这篇文章主要是自己这周的学习成果,传播性不是很高,供自己日后查阅。文章主要涉及到一些编译原理的基础、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']
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容