parselinkmap.py源码分析

作者: 肠粉白粥_Hoben | 来源:发表于2019-10-22 19:44 被阅读0次

    LinkMap初探提到了自动分析LinkMap的开源项目,今天来分析一下。

    其实源码里面的核心思想挺简单的,就是用一个字典{symbol:size},计算总体积的时候,枚举每一个key,相加即可。

    一. 读取文件

    1. 确保字段完整

    def read_base_link_map_file(base_link_map_file, base_link_map_result_file):
        try:
            link_map_file = open(base_link_map_file)
        except IOError:
            print "Read file " + base_link_map_file + " failed!"
            return
        else:
            try:
                content = link_map_file.read()
            except IOError:
                print "Read file " + base_link_map_file + " failed!"
                return
            else:
                obj_file_tag_index = content.find("# Object files:")
                # +15的意思是读取下一行
                sub_obj_file_symbol_str = content[obj_file_tag_index + 15:]
                symbols_index = sub_obj_file_symbol_str.find("# Symbols:")
                if obj_file_tag_index == -1 or symbols_index == -1 or content.find("# Path:") == -1:
                    print "The Content of File " + base_link_map_file + " is Invalid."
                    pass
    

    上述代码是为了确保该文件中存在File段和Symbol段,这样才能开始进行下面的读取文件的操作。

    2. 读取Files字段

    Files字段长这样:

    # Object files:
    [  0] linker synthesized
    [  1] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/TestSuperDemo.app-Simulated.xcent
    [  2] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/ViewController.o
    [  3] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/SonViewController.o
    [  4] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/main.o
    [  5] /Users/huanghongbin/Library/Developer/Xcode/DerivedData/TestSuperDemo-bbczgzluwrthgjbpluoujrgxekqa/Build/Intermediates.noindex/TestSuperDemo.build/Debug-iphonesimulator/TestSuperDemo.build/Objects-normal/x86_64/AppDelegate.o
    [  6] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/System/Library/Frameworks//Foundation.framework/Foundation.tbd
    [  7] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/usr/lib/libobjc.tbd
    [  8] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/usr/lib/libSystem.tbd
    [  9] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.1.sdk/System/Library/Frameworks//UIKit.framework/UIKit.tbd
    

    我们可以通过这一段来找到序号和文件名的对应,后面分析size的时候就可以以文件名为key,size为value去分析了。

    link_map_file_tmp = open(base_link_map_file)
    reach_files = 0
    reach_sections = 0
    reach_symbols = 0
    size_map = {}
    while 1:
        line = link_map_file_tmp.readline()
        if not line:
            break
        if line.startswith("#"):
            if line.startswith("# Object files:"):
                reach_files = 1
                pass
            if line.startswith("# Sections"):
                reach_sections = 1
                pass
            if line.startswith("# Symbols"):
                reach_symbols = 1
                pass
            pass
        else:
            if reach_files == 1 and reach_sections == 0 and reach_symbols == 0:
                index = line.find("]")
                if index != -1:
                    symbol = {"file": line[index + 2:-1]}
                    key = int(line[1: index])
                    size_map[key] = symbol
                pass
    

    上面的代码就是把序号取出来,作为key,把路径取出来,作为value一一对应。

    3. Symbols字段

    Symbols字段长这样:

    # Symbols:
    # Address   Size        File  Name
    0x100001660 0x00000460  [  2] -[ViewController init]
    0x100001AC0 0x00000040  [  2] -[ViewController viewDidLoad]
    0x100001B00 0x000000B0  [  2] -[ViewController push]
    x100001BB0  0x00000030  [  2] -[ViewController shouldNotPrint]
    0x100001BE0 0x00000020  [  2] -[ViewController button]
    0x100001C00 0x00000040  [  2] -[ViewController setButton:]
    0x100001C40 0x00000020  [  2] -[ViewController allPlate]
    0x100001C60 0x00000040  [  2] -[ViewController setAllPlate:]
    0x100001CA0 0x00000020  [  2] -[ViewController currentPlateInfo]
    0x100001CC0 0x00000040  [  2] -[ViewController setCurrentPlateInf
    

    可以看到,Symbols字段有十六进制的Size,每一个序号对应一个文件,只要我们把这个序号的Size相加,即可得到这个文件的大小了。

    elif reach_files == 1 and reach_sections == 1 and reach_symbols == 1:
        symbols_array = line.split("\t")
        if len(symbols_array) == 3:
            file_key_and_name = symbols_array[2]
            size = int(symbols_array[1], 16)
            index = file_key_and_name.find("]")
            if index != -1:
                key = file_key_and_name[1:index]
                key = int(key)
                # 根据symbols字段里面的key,回到files字段找到对应的.o文件
                symbol = size_map[key]
                if symbol:
                    if "size" in symbol:
                        symbol["size"] += size
                        pass
                    else:
                        symbol["size"] = size
                    pass
                pass
            pass
        pass
    

    二. 体积计算

    1. 计算文件体积

    上面读取文件完毕后,已经将所有.o文件的大小都获取到,并存在了size_map里面,size_map里的内容如下所示:

    size_map = {
        "100" : {                                   # 序号
            "symbol" : {
                "size" : 1024B,                     # 大小
                "file" : "User/Document/File.o"     # 路径
            }
        }
    }
    

    其中,路径中的文件不仅仅是.o文件,也有可能是.a文件(包含若干个.o文件),要分析.a文件的大小,则需要把.a文件里面所有的.o文件体积加起来。

    .a文件的名字像这样:libAFNetworking.a(AFNetworking-dummy.o),所以要获取.a文件名字,就需要用(分开。

    这里我额外加了白名单机制,因为有些文件加入了#ifDebug就会没有size字段,过滤掉免得显示一些警告信息。

    for key in size_map:
        symbol = size_map[key]
        if "size" in symbol:
            total_size += symbol["size"]
            # .o文件和.a文件,如libAFNetworking.a(AFNetworking-dummy.o)就可以区分开
            # 普通的.o文件就会按.o计算
            # .a文件会统一计算
            o_file_name = symbol["file"].split("/")[-1]
            a_file_name = o_file_name.split("(")[0]
            if a_file_name in a_file_map:
                a_file_map[a_file_name] += symbol["size"]
                pass
            else:
                a_file_map[a_file_name] = symbol["size"]
                pass
            pass
    
        else:
            o_file_name = symbol["file"].split("/")[-1]
            if o_file_name in debug_white_list:
                pass
            else:
                print "WARN : some error occurred for [file, key] : ",
                print "[" + o_file_name + ", " + key.__str__() + "]"
    

    2. 体积大小排序

    # 根据size从大到小排序
    a_file_sorted_list = sorted(a_file_map.items(), key=lambda x: x[1], reverse=True)
    

    这个排序的意思是,从大到小,根据a_file_map的value(即size)排序。

    3. 打印文件

    print "%s" % "=".ljust(80, '=')
    print "%s" % (base_link_map_file+"各模块体积汇总").center(87)
    print "%s" % "=".ljust(80, '=')
    if os.path.exists(base_link_map_result_file):
        os.remove(base_link_map_result_file)
        pass
    print "Creating Result File : %s" % base_link_map_result_file
    output_file = open(base_link_map_result_file, "w")
    for item in a_file_sorted_list:
        print "%s%.2fM" % (item[0].ljust(50), item[1]/1024.0/1024.0)
        output_file.write("%s \t\t\t%.2fM\n" % (item[0].ljust(50), item[1]/1024.0/1024.0))
        pass
    print "%s%.2fM" % ("总体积:".ljust(53), total_size / 1024.0/1024.0)
    print "\n\n\n\n\n"
    output_file.write("%s%.2fM" % ("总体积:".ljust(53), total_size / 1024.0/1024.0))
    link_map_file_tmp.close()
    output_file.close()
    

    现在获得的size单位是B,要转化为MB,则需要除以1024的平方,最后输出打印即可(他还顺便生成了一个结果文件)。

    三. 调用

    1. 分析单个文件

    直接调用了read_base_link_map_file函数,输入为LinkMap文件路径、输出结果路径。

    2. 对比分析两个文件

    对比的文件也调用了read_base_link_map_file方法,输出两个output_file,格式类似于:

    ================================================================================
                         demoData/BaseLinkMap.txt各模块体积汇总
    ================================================================================
    Creating Result File : demoData/BaseLinkMapResult.txt
    AppDelegate.o                                     0.01M
    ViewController.o                                  0.00M
    TestCleanPackage.app.xcent                        0.00M
    UnUsedClass.o                                     0.00M
    main.o                                            0.00M
    libobjc.tbd                                       0.00M
    linker synthesized                                0.00M
    Foundation.tbd                                    0.00M
    UIKit.tbd                                         0.00M
    总体积:                                           0.01M
    

    然后对各个文件体积和总体积进行分词(其他字段忽略),并存入了字典:

    def parse_result_file(result_file_name):
        base_bundle_list = []
        result_file = open(result_file_name)
        while 1:
            line = result_file.readline()
            if not line:
                break
            bundle_and_size = line.split()
            if len(bundle_and_size) == 2 and line.find(":") == -1:
                bundle_and_size_map = {"name": bundle_and_size[0], "size": bundle_and_size[1]}
                base_bundle_list += [bundle_and_size_map]
                pass
        return base_bundle_list
    

    最后compare方法将两个文件的字典的size取出来,进行对比,如果后者文件更大点则输出,如果后者有而前者没有也会输出。

    相关文章

      网友评论

        本文标题:parselinkmap.py源码分析

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