fastlane-plugin-analyze_ios_linkmap 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +56 -17
- data/lib/fastlane/plugin/analyze_ios_linkmap/actions/analyze_ios_linkmap_action.rb +56 -47
- data/lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb +299 -0
- data/lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_dead_stripped_symbol.rb +38 -0
- data/lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_helper.rb +7 -0
- data/lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_library.rb +55 -0
- data/lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_object_file.rb +143 -0
- data/lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_section.rb +71 -0
- data/lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_segment.rb +31 -0
- data/lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_symbol.rb +41 -0
- data/lib/fastlane/plugin/analyze_ios_linkmap/version.rb +1 -1
- metadata +11 -4
- data/lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_helper.rb +0 -493
@@ -0,0 +1,38 @@
|
|
1
|
+
module Fastlane
|
2
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
3
|
+
module Helper
|
4
|
+
module LinkMap
|
5
|
+
require_relative 'analyze_ios_file_helper'
|
6
|
+
|
7
|
+
# Dead Stripped Symbols:
|
8
|
+
# Size File Name
|
9
|
+
# <<dead>> 0x00000030 [ 26] ___destroy_helper_block_e8_32s40s48s
|
10
|
+
# <<dead>> 0x00000008 [ 26] ___copy_helper_block_e8_32s
|
11
|
+
# <<dead>> 0x00000008 [ 26] ___destroy_helper_block_e8_32s
|
12
|
+
|
13
|
+
class DeadStrippedSymbol
|
14
|
+
attr_accessor(:size, :file, :name, :invalid) #=> file 并不是【文件名】, 而是【文件 id】
|
15
|
+
|
16
|
+
def initialize(line)
|
17
|
+
if line =~ %r(^<<dead>>\s+0x(.+?)\s+\[(.+?)\]\w*)
|
18
|
+
@size = $1.to_i(16)
|
19
|
+
@file = $2.to_i
|
20
|
+
@invalid = false
|
21
|
+
else
|
22
|
+
@invalid = true
|
23
|
+
# UI.error "#{line.inspect} can not match symbol regular"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
{
|
29
|
+
size: @size,
|
30
|
+
format_size: FileHelper.format_size(@size),
|
31
|
+
file: @file,
|
32
|
+
name: @name
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
require_relative 'analyze_ios_file_helper'
|
2
|
+
require_relative 'linkmap_library'
|
3
|
+
require_relative 'linkmap_segment'
|
4
|
+
require_relative 'linkmap_section'
|
5
|
+
require_relative 'linkmap_object_file'
|
6
|
+
require_relative 'linkmap_dead_stripped_symbol'
|
7
|
+
require_relative 'linkmap_symbol'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Fastlane
|
2
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
3
|
+
module Helper
|
4
|
+
module LinkMap
|
5
|
+
require_relative 'analyze_ios_file_helper'
|
6
|
+
|
7
|
+
#
|
8
|
+
# Linkmap.txt 结构中, 并没有这种结构, 自己抽象出的
|
9
|
+
#
|
10
|
+
|
11
|
+
class Library
|
12
|
+
attr_accessor(:name, :size, :object_files, :dead_symbol_size, :podspec_name)
|
13
|
+
|
14
|
+
def initialize(options = {})
|
15
|
+
@name = options[:name]
|
16
|
+
@size = options[:size]
|
17
|
+
@object_files = options[:object_files]
|
18
|
+
@dead_symbol_size = options[:dead_symbol_size]
|
19
|
+
@podspec_name = options[:podspec_name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_hash(all_objects)
|
23
|
+
#
|
24
|
+
# ─── FIX pod spec name ───────────────────────────────────────────────────────────
|
25
|
+
#
|
26
|
+
# <podspec name> ==> <library name>
|
27
|
+
#
|
28
|
+
# "AsyncSwift": "Async",
|
29
|
+
# "Light-Untar": "Light_Untar",
|
30
|
+
# "UIAlertView-Blocks": "UIAlertView_Blocks",
|
31
|
+
# "UIDevice-Hardware": "UIDevice_Hardware",
|
32
|
+
# "Yoga": "yoga",
|
33
|
+
# "lottie-ios": "Lottie"
|
34
|
+
#
|
35
|
+
podspec_name = if @podspec_name
|
36
|
+
@podspec_name # => 优先使用 podspec_name
|
37
|
+
else
|
38
|
+
@name # => 如果【没有】podspec_name, 就使用 library_name 作为 podspec_name
|
39
|
+
end
|
40
|
+
|
41
|
+
h = {
|
42
|
+
name: @name,
|
43
|
+
size: @size,
|
44
|
+
format_size: FileHelper.format_size(@size),
|
45
|
+
dead_symbol_size: @dead_symbol_size,
|
46
|
+
format_dead_symbol_size: FileHelper.format_size(@dead_symbol_size),
|
47
|
+
podspec_name: podspec_name
|
48
|
+
}
|
49
|
+
h[:object_files] = @object_files if all_objects
|
50
|
+
h
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Fastlane
|
2
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
3
|
+
module Helper
|
4
|
+
module LinkMap
|
5
|
+
require_relative 'analyze_ios_file_helper'
|
6
|
+
|
7
|
+
# Object files:
|
8
|
+
# ..........
|
9
|
+
# [ 12] /Users/xiongzenghui/App/Pods/BangcleCryptoTool/BangcleCryptoTool/libs/libbangcle_crypto_tool.a(lsm4.o)
|
10
|
+
# ..........
|
11
|
+
# [ 25] /Users/xiongzenghui/App/Pods/AFNetworking/AFNetworking.framework/AFNetworking(AFHTTPSessionManager.o)
|
12
|
+
# ------------------------------------------------------------------------------
|
13
|
+
# @param index : 25、26、...、31
|
14
|
+
# @param filename : AFHTTPSessionManager.o
|
15
|
+
# @param library : 所属 静态库 文件名
|
16
|
+
# @param framework : 如果 静态库 是 xx.framework 形态的, 则获取其 framework 名字
|
17
|
+
# @param symbols : 这个 AFHTTPSessionManager.o 包含的所有的 symbol 对象
|
18
|
+
# @param size : 所有符号的总大小
|
19
|
+
# @param dead_symbol_size: strip 掉的符号总大小
|
20
|
+
#
|
21
|
+
|
22
|
+
OBJECT_FILE_TYPE_USER_LIBRARY = 0
|
23
|
+
OBJECT_FILE_TYPE_OTHERS = 1
|
24
|
+
OBJECT_FILE_TYPE_SYSTEM = 2
|
25
|
+
|
26
|
+
class ObjectFile
|
27
|
+
attr_accessor(:index, :file_name, :library, :framework, :symbols, :size, :dead_symbol_size)
|
28
|
+
alias framework? framework
|
29
|
+
|
30
|
+
def initialize(line, &blk)
|
31
|
+
if line =~ /\[(.*)\].*\/(.*)\((.*)\)/
|
32
|
+
# [ 5] /Users/xiongzenghui/App/Pods/BangcleCryptoTool/BangcleCryptoTool/libs/libbangcle_crypto_tool.a(aes.o)
|
33
|
+
# [ 6] /Users/xiongzenghui/App/Pods/BangcleCryptoTool/BangcleCryptoTool/libs/libbangcle_crypto_tool.a(crypto.o)
|
34
|
+
# [ 7] /Users/xiongzenghui/App/Pods/BangcleCryptoTool/BangcleCryptoTool/libs/libbangcle_crypto_tool.a(des.o)
|
35
|
+
# ...............
|
36
|
+
# [ 23] /Users/xiongzenghui/App/Pods/AFNetworking/AFNetworking.framework/AFNetworking(AFAutoPurgingImageCache.o)
|
37
|
+
# [ 24] /Users/xiongzenghui/App/Pods/AFNetworking/AFNetworking.framework/AFNetworking(AFHTTPSessionManager.o)
|
38
|
+
# [ 25] /Users/xiongzenghui/App/Pods/AFNetworking/AFNetworking.framework/AFNetworking(AFImageDownloader.o)
|
39
|
+
|
40
|
+
index = $1.to_i #=> 6 , 23
|
41
|
+
library = $2 #=> libbangcle_crypto_tool.a , AFNetworking
|
42
|
+
file_name = $3 #=> crypto.o , AFAutoPurgingImageCache.o
|
43
|
+
# puts "index: #{index}"
|
44
|
+
# puts "library: #{library}"
|
45
|
+
# puts "file_name: #{file_name}"
|
46
|
+
|
47
|
+
@index = index
|
48
|
+
@file_name = file_name
|
49
|
+
@library = library
|
50
|
+
@framework = if line.include?('.framework')
|
51
|
+
true
|
52
|
+
else
|
53
|
+
false
|
54
|
+
end
|
55
|
+
@symbols = Array.new
|
56
|
+
@size = 0
|
57
|
+
@dead_symbol_size = 0
|
58
|
+
|
59
|
+
blk.call(self, OBJECT_FILE_TYPE_USER_LIBRARY)
|
60
|
+
elsif line =~ /\[(.*)\].*\/(.*)/
|
61
|
+
# [ 3] /path/to/Release-iphoneos/App.build/Objects-normal/arm64/main.o
|
62
|
+
# [100] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/UIKit.framework/UIKit.tbd
|
63
|
+
# [9742] /path/to/Pods/du.framework/du
|
64
|
+
# [8659] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/libswiftDispatch.dylib
|
65
|
+
|
66
|
+
index = $1.to_i
|
67
|
+
file_name = $2
|
68
|
+
# puts "index: #{index}"
|
69
|
+
# puts "file_name: #{file_name}"
|
70
|
+
|
71
|
+
@index = index
|
72
|
+
@file_name = file_name
|
73
|
+
@library = if line.include?('.framework') && !file_name.include?('.') #=> /path/to/du.framework/du 【用户】动态库
|
74
|
+
file_name
|
75
|
+
else
|
76
|
+
if line.end_with?('.a') #=> \.a$ xcode 内置的 静态库
|
77
|
+
file_name
|
78
|
+
else
|
79
|
+
if file_name.end_with?('.tbd') #=> \.tbd$ iOS 系统 动态库 软链接
|
80
|
+
'tdb'
|
81
|
+
elsif file_name.end_with?('.dylib') #=> \.dylib$ iOS 系统 动态库
|
82
|
+
'dylib'
|
83
|
+
elsif file_name.end_with?('.o') #=> \.o$ 用户 目标文件
|
84
|
+
'main' #=> main.o、ZHUIAutoTest.o、swift.o ... 散落在 App 工程中的 xxx.o
|
85
|
+
else
|
86
|
+
'system'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
@framework = if line.include?('.framework') && !file_name.include?('.')
|
91
|
+
true
|
92
|
+
else
|
93
|
+
false
|
94
|
+
end
|
95
|
+
@symbols = Array.new
|
96
|
+
@size = 0
|
97
|
+
@dead_symbol_size = 0
|
98
|
+
|
99
|
+
blk.call(self, OBJECT_FILE_TYPE_OTHERS)
|
100
|
+
elsif line =~ /\[(.*)\]\s*([\w\s]+)/
|
101
|
+
# [ 0] linker synthesized
|
102
|
+
# [ 1] dtrace
|
103
|
+
|
104
|
+
index = $1.to_i
|
105
|
+
file_name = $2
|
106
|
+
# puts "index: #{index}"
|
107
|
+
# puts "file_name: #{file_name}"
|
108
|
+
|
109
|
+
@index = index
|
110
|
+
@file_name = file_name
|
111
|
+
@library = nil
|
112
|
+
@framework = false
|
113
|
+
@symbols = Array.new
|
114
|
+
@size = 0
|
115
|
+
@dead_symbol_size = 0
|
116
|
+
|
117
|
+
blk.call(self, OBJECT_FILE_TYPE_SYSTEM)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_hash(all_symbols)
|
122
|
+
h = {
|
123
|
+
index: @index,
|
124
|
+
file_name: @file_name,
|
125
|
+
library: @library,
|
126
|
+
size: @size,
|
127
|
+
format_size: FileHelper.format_size(@size),
|
128
|
+
dead_symbol_size: @dead_symbol_size,
|
129
|
+
format_dead_symbol_size: FileHelper.format_size(@dead_symbol_size),
|
130
|
+
}
|
131
|
+
h[:symbols] = @symbols.map { |e| e.to_hash } if all_symbols
|
132
|
+
h
|
133
|
+
end
|
134
|
+
|
135
|
+
# ObjectFile 追加 Symbol
|
136
|
+
def add_symbol(symbol)
|
137
|
+
@symbols.push(symbol)
|
138
|
+
@size += symbol.size #=> 累加 ObjectFile 所有 Symbol 大小
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Fastlane
|
2
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
3
|
+
module Helper
|
4
|
+
module LinkMap
|
5
|
+
require_relative 'analyze_ios_file_helper'
|
6
|
+
|
7
|
+
# Sections:
|
8
|
+
# Address Size Segment Section
|
9
|
+
# 0x1000048A0 0x055656A8 __TEXT __text
|
10
|
+
# 0x105569F48 0x000090E4 __TEXT __stubs
|
11
|
+
# 0x10557302C 0x000079D4 __TEXT __stub_helper
|
12
|
+
# 0x10557AA00 0x002D4E1A __TEXT __cstring
|
13
|
+
|
14
|
+
class Section
|
15
|
+
attr_accessor(:name, :symbol_size, :residual_size, :start_addr, :end_addr, :segment)
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
{
|
19
|
+
name: @name,
|
20
|
+
symbol_size: @symbol_size,
|
21
|
+
format_symbol_size: FileHelper.format_size(@symbol_size),
|
22
|
+
residual_size: @residual_size,
|
23
|
+
format_residual_size: FileHelper.format_size(@residual_size),
|
24
|
+
start_addr: @start_addr,
|
25
|
+
end_addr: @end_addr,
|
26
|
+
segment: @segment
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(line)
|
31
|
+
lines = line.split(' ').map(&:strip)
|
32
|
+
|
33
|
+
address = lines[0]
|
34
|
+
size = lines[1]
|
35
|
+
segment = lines[2]
|
36
|
+
section = lines[3]
|
37
|
+
# puts "address: #{address}"
|
38
|
+
# puts "size: #{size}"
|
39
|
+
# puts "segment: #{segment}"
|
40
|
+
# puts "section: #{section}"
|
41
|
+
|
42
|
+
start_addr = address.to_i(16)
|
43
|
+
residual_size = size.to_i(16)
|
44
|
+
end_addr = start_addr + residual_size
|
45
|
+
# puts "start_addr: #{start_addr}"
|
46
|
+
# puts "residual_size: #{residual_size}"
|
47
|
+
# puts "end_addr: #{end_addr}"
|
48
|
+
|
49
|
+
@name = section
|
50
|
+
@symbol_size = 0
|
51
|
+
@residual_size = residual_size
|
52
|
+
@start_addr = start_addr
|
53
|
+
@end_addr = end_addr
|
54
|
+
@segment = segment
|
55
|
+
end
|
56
|
+
|
57
|
+
# 【注意】
|
58
|
+
# `section name` may be dulicate in different segment
|
59
|
+
# 所以使用 <segment_name + section_name> 作为 map 的 key 存储
|
60
|
+
# @section_map[section.key] = section
|
61
|
+
def key
|
62
|
+
"#{segment}:#{name}".to_sym
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_segment
|
66
|
+
key.to_s.split(':')[0].to_sym
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Fastlane
|
2
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
3
|
+
module Helper
|
4
|
+
module LinkMap
|
5
|
+
require_relative 'analyze_ios_file_helper'
|
6
|
+
|
7
|
+
#
|
8
|
+
# Linkmap.txt 结构中, 没有直接给出, 只能由【所有的 Section】累加计算得到
|
9
|
+
#
|
10
|
+
class Segment
|
11
|
+
attr_accessor(:name, :size, :residual_size)
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
@name = options[:name]
|
15
|
+
@size = options[:size]
|
16
|
+
@residual_size = options[:residual_size]
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hash
|
20
|
+
{
|
21
|
+
name: @name,
|
22
|
+
size: @size,
|
23
|
+
format_size: FileHelper.format_size(@size),
|
24
|
+
residual_size: @residual_size,
|
25
|
+
format_residual_size: FileHelper.format_size(@residual_size)
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Fastlane
|
2
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
3
|
+
module Helper
|
4
|
+
module LinkMap
|
5
|
+
require_relative 'analyze_ios_file_helper'
|
6
|
+
|
7
|
+
# Symbols:
|
8
|
+
# Address Size File Name
|
9
|
+
# 0x1000048A0 0x000000A4 [ 2] _main
|
10
|
+
# 0x100004944 0x00000028 [ 5] _Bangcle_WB_AES_encrypt
|
11
|
+
#
|
12
|
+
|
13
|
+
class Symbol
|
14
|
+
attr_accessor(:address, :size, :file, :name, :invalid) #=> file 并不是【文件名】, 而是【文件 id】
|
15
|
+
|
16
|
+
def initialize(line, &blk)
|
17
|
+
if line =~ %r(^0x(.+?)\s+0x(.+?)\s+\[(.+?)\]\s(.*))
|
18
|
+
@address = $1.to_i(16) #=> Address
|
19
|
+
@size = $2.to_i(16) #=> Size
|
20
|
+
@file = $3.to_i #=> File
|
21
|
+
@name = $4 #=> Name
|
22
|
+
@invalid = false
|
23
|
+
else
|
24
|
+
@invalid = true
|
25
|
+
# UI.error "#{line.inspect} can not match symbol regular"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_hash
|
30
|
+
{
|
31
|
+
address: @address,
|
32
|
+
size: @size,
|
33
|
+
format_size: FileHelper.format_size(@size),
|
34
|
+
file: @file,
|
35
|
+
name: @name
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-analyze_ios_linkmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- xiongzenghui
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-12-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -147,7 +147,14 @@ files:
|
|
147
147
|
- lib/fastlane/plugin/analyze_ios_linkmap.rb
|
148
148
|
- lib/fastlane/plugin/analyze_ios_linkmap/actions/analyze_ios_linkmap_action.rb
|
149
149
|
- lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_file_helper.rb
|
150
|
-
- lib/fastlane/plugin/analyze_ios_linkmap/helper/
|
150
|
+
- lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb
|
151
|
+
- lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_dead_stripped_symbol.rb
|
152
|
+
- lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_helper.rb
|
153
|
+
- lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_library.rb
|
154
|
+
- lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_object_file.rb
|
155
|
+
- lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_section.rb
|
156
|
+
- lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_segment.rb
|
157
|
+
- lib/fastlane/plugin/analyze_ios_linkmap/helper/linkmap_symbol.rb
|
151
158
|
- lib/fastlane/plugin/analyze_ios_linkmap/version.rb
|
152
159
|
homepage: https://github.com/xzhhe/fastlane-plugin-analyze_ios_linkmap
|
153
160
|
licenses:
|
@@ -168,7 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
175
|
- !ruby/object:Gem::Version
|
169
176
|
version: '0'
|
170
177
|
requirements: []
|
171
|
-
rubygems_version: 3.0.
|
178
|
+
rubygems_version: 3.0.6
|
172
179
|
signing_key:
|
173
180
|
specification_version: 4
|
174
181
|
summary: iOS parse linkmap.txt to ruby Hash
|
@@ -1,493 +0,0 @@
|
|
1
|
-
require 'fastlane_core/ui/ui'
|
2
|
-
|
3
|
-
module Fastlane
|
4
|
-
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
5
|
-
module Helper
|
6
|
-
module LinkMap
|
7
|
-
require 'pp'
|
8
|
-
|
9
|
-
# Symbols:
|
10
|
-
# Address Size File Name
|
11
|
-
# 0x100001710 0x00000039 [ 2] -[ViewController viewDidLoad]
|
12
|
-
Symbol = Struct.new(:address, :size, :object_file_id, :name)
|
13
|
-
|
14
|
-
# Dead Stripped Symbols:
|
15
|
-
# Size File Name
|
16
|
-
# <<dead>> 0x00000018 [ 2] CIE
|
17
|
-
DeadStrippedSymbol = Struct.new(:size, :object_file_id, :name)
|
18
|
-
|
19
|
-
# Sections:
|
20
|
-
# Address Size Segment Section
|
21
|
-
# 0x100001710 0x00000333 __TEXT __text
|
22
|
-
Section = Struct.new(:section, :segment, :start_addr, :end_addr, :symbol_size, :residual_size) do
|
23
|
-
def key
|
24
|
-
"#{segment}:#{section}".to_sym
|
25
|
-
end
|
26
|
-
|
27
|
-
def parse_segment
|
28
|
-
key.to_s.split(':')[0].to_sym
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Linkmap.txt 中, 没有直接给出, 只能由【所有的 Section】统计得出
|
33
|
-
Segment = Struct.new(:name, :symbol_size, :residual_size)
|
34
|
-
|
35
|
-
#
|
36
|
-
#<FastlaneCore::Helper::LinkMap::ObjectFile:0x007ff93ec4fc90
|
37
|
-
# @file_id=39,
|
38
|
-
# object="ViewController.o",
|
39
|
-
# @framework=false,
|
40
|
-
# @library="libbangcle_crypto_tool.a",
|
41
|
-
# @symbols=[
|
42
|
-
# <struct FastlaneCore::Helper::LinkMap::Symbol
|
43
|
-
# address=4294976749,
|
44
|
-
# size=15,
|
45
|
-
# object_file_id=2,
|
46
|
-
# name="literal string: ViewController"
|
47
|
-
# >,
|
48
|
-
# <struct FastlaneCore::Helper::LinkMap::Symbol
|
49
|
-
# address=4294974150,
|
50
|
-
# size=12,
|
51
|
-
# object_file_id=2,
|
52
|
-
# name="literal string: viewDidLoad"
|
53
|
-
# >
|
54
|
-
# ]
|
55
|
-
# >
|
56
|
-
ObjectFile = Struct.new(:file_id, :object, :library, :framework, :symbols, :size, :dead_symbol_size)
|
57
|
-
|
58
|
-
#
|
59
|
-
# "AFNetworking"=>
|
60
|
-
# <struct FastlaneCore::Helper::LinkMap::Library
|
61
|
-
# name="AFNetworking",
|
62
|
-
# size=0,
|
63
|
-
# object_file_ids=[23,24,25,26,27,28,29,30],
|
64
|
-
# dead_symbol_size=0,
|
65
|
-
# pod_name="AFNetworking"
|
66
|
-
# >
|
67
|
-
#
|
68
|
-
Library = Struct.new(:name, :size, :object_file_ids, :dead_symbol_size, :pod_name)
|
69
|
-
|
70
|
-
class Parser
|
71
|
-
attr_accessor(:object_map, :library_map, :section_map, :segment_map)
|
72
|
-
|
73
|
-
def initialize(options)
|
74
|
-
@filepath = options[:filepath]
|
75
|
-
@all_symbols = options[:all_symbols]
|
76
|
-
|
77
|
-
UI.user_error!("❌ #{@filepath} not pass") unless @filepath
|
78
|
-
UI.user_error!("❌ #{@filepath} not exist") unless File.exist?(@filepath)
|
79
|
-
|
80
|
-
@object_map = {}
|
81
|
-
@library_map = {}
|
82
|
-
@section_map = {}
|
83
|
-
@segment_map = {} # 根据 @section_map 统计【所有的 section】得出
|
84
|
-
|
85
|
-
parse
|
86
|
-
end
|
87
|
-
|
88
|
-
def pretty_json
|
89
|
-
JSON.pretty_generate(pretty_hash)
|
90
|
-
end
|
91
|
-
|
92
|
-
def pretty_hash
|
93
|
-
return @result if @result
|
94
|
-
|
95
|
-
# sort object_map[i].ObjectFile.symbols
|
96
|
-
@object_map.each do |ofid, object|
|
97
|
-
next unless object.symbols
|
98
|
-
|
99
|
-
object.symbols.sort! do |sym1, sym2|
|
100
|
-
sym2[:size] <=> sym1[:size]
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
# 计算 linkmap.txt 所有的 symbol 总大小
|
105
|
-
total_size = @library_map.values.map(&:size).inject(:+)
|
106
|
-
total_dead_size = @library_map.values.map(&:dead_symbol_size).inject(:+)
|
107
|
-
|
108
|
-
# sort object_map[i]
|
109
|
-
library_map_values = @library_map.values.sort do |a, b|
|
110
|
-
b.size <=> a.size
|
111
|
-
end
|
112
|
-
library_map_values.compact!
|
113
|
-
|
114
|
-
library_maps = library_map_values.map do |lib|
|
115
|
-
pod_name = lib.name
|
116
|
-
unless lib.pod_name.empty?
|
117
|
-
pod_name = lib.pod_name
|
118
|
-
end
|
119
|
-
|
120
|
-
if @all_symbols
|
121
|
-
{
|
122
|
-
library: lib.name,
|
123
|
-
pod: pod_name,
|
124
|
-
total: lib.size,
|
125
|
-
format_total: Fastlane::Helper::LinkMap::FileHelper.format_size(lib.size),
|
126
|
-
total_dead: lib.dead_symbol_size,
|
127
|
-
format_total_dead: Fastlane::Helper::LinkMap::FileHelper.format_size(lib.dead_symbol_size),
|
128
|
-
objects: lib.object_file_ids.map do |object_file_id|
|
129
|
-
# Struct.new(:file_id, :object, :library, :framework, :symbols, :size, :dead_symbol_size)
|
130
|
-
object_file = @object_map[object_file_id]
|
131
|
-
if object_file
|
132
|
-
{
|
133
|
-
object: object_file.object,
|
134
|
-
symbols: object_file.symbols.map do |symb|
|
135
|
-
{
|
136
|
-
name: symb.name,
|
137
|
-
total: symb.size,
|
138
|
-
format_total: Fastlane::Helper::LinkMap::FileHelper.format_size(symb.size),
|
139
|
-
}
|
140
|
-
end
|
141
|
-
}
|
142
|
-
else
|
143
|
-
nil
|
144
|
-
end
|
145
|
-
end
|
146
|
-
}
|
147
|
-
else
|
148
|
-
{
|
149
|
-
library: lib.name,
|
150
|
-
pod: pod_name,
|
151
|
-
total: lib.size,
|
152
|
-
format_total: Fastlane::Helper::LinkMap::FileHelper.format_size(lib.size),
|
153
|
-
total_dead: lib.dead_symbol_size,
|
154
|
-
format_total_dead: Fastlane::Helper::LinkMap::FileHelper.format_size(lib.dead_symbol_size)
|
155
|
-
}
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
@result = {
|
160
|
-
total_count: library_maps.count,
|
161
|
-
total_size: total_size,
|
162
|
-
format_total_size: Fastlane::Helper::LinkMap::FileHelper.format_size(total_size),
|
163
|
-
total_dead_size: total_dead_size,
|
164
|
-
format_total_dead_size: Fastlane::Helper::LinkMap::FileHelper.format_size(total_dead_size),
|
165
|
-
librarys: library_maps
|
166
|
-
}
|
167
|
-
|
168
|
-
@result
|
169
|
-
end
|
170
|
-
|
171
|
-
def pretty_merge_by_pod_hash
|
172
|
-
return @merge_by_pod_result if @merge_by_pod_result
|
173
|
-
|
174
|
-
@merge_by_pod_result = {
|
175
|
-
total_count: pretty_hash[:total_count],
|
176
|
-
total_size: pretty_hash[:total_size],
|
177
|
-
format_total_size: pretty_hash[:format_total_size],
|
178
|
-
total_dead_size: pretty_hash[:total_dead_size],
|
179
|
-
format_total_dead_size: pretty_hash[:format_total_dead_size]
|
180
|
-
}
|
181
|
-
|
182
|
-
pods = Hash.new
|
183
|
-
pretty_hash[:librarys].each do |lib|
|
184
|
-
apod_librarys = pods[lib[:pod]]
|
185
|
-
apod_librarys ||= Array.new
|
186
|
-
apod_librarys << lib
|
187
|
-
pods[lib[:pod]] = apod_librarys
|
188
|
-
end
|
189
|
-
@merge_by_pod_result[:pods] = pods
|
190
|
-
|
191
|
-
@merge_by_pod_result
|
192
|
-
end
|
193
|
-
|
194
|
-
def pretty_merge_by_pod_json
|
195
|
-
JSON.pretty_generate(pretty_merge_by_pod_hash)
|
196
|
-
end
|
197
|
-
|
198
|
-
def parse
|
199
|
-
# 读取 Linkmap.txt 【每一行】进行解析
|
200
|
-
File.foreach(@filepath).with_index do |line, line_num|
|
201
|
-
begin
|
202
|
-
unless line.valid_encoding?
|
203
|
-
line = line.encode("UTF-16", :invalid => :replace, :replace => "?").encode('UTF-8')
|
204
|
-
end
|
205
|
-
|
206
|
-
if line.start_with? "#"
|
207
|
-
if line.start_with? "# Object files:" #=> 初始化 @object_map
|
208
|
-
@subparser = :parse_object_files
|
209
|
-
elsif line.start_with? "# Sections:" #=> 初始化 @section_map
|
210
|
-
@subparser = :parse_sections
|
211
|
-
elsif line.start_with? "# Symbols:" #=> 解析得到每一个 symbol 【占用】大小
|
212
|
-
@subparser = :parse_symbols
|
213
|
-
elsif line.start_with? '# Dead Stripped Symbols:' #=> 解析得到 dead strpped 【废弃】大小
|
214
|
-
@subparser = :parse_dead
|
215
|
-
end
|
216
|
-
else
|
217
|
-
send(@subparser, line) #=> self.func(line)
|
218
|
-
end
|
219
|
-
rescue => e
|
220
|
-
UI.error "Exception on Link map file line #{line_num}"
|
221
|
-
UI.message "Content is: "
|
222
|
-
UI.message line
|
223
|
-
end
|
224
|
-
end
|
225
|
-
# puts "There are #{@section_map.values.map{|value| value[:residual_size]}.inject(:+)} Byte in some section can not be analyze"
|
226
|
-
end
|
227
|
-
|
228
|
-
def parse_object_files(line)
|
229
|
-
if line =~ %r(\[(.*)\].*\/(.*)\((.*)\))
|
230
|
-
# Object files:
|
231
|
-
# [ 5] /Users/xiongzenghui/Desktop/launching_time/osee2unified/osee2unified/Pods/BangcleCryptoTool/BangcleCryptoTool/libs/libbangcle_crypto_tool.a(aes.o)
|
232
|
-
# [ 6] /Users/xiongzenghui/Desktop/launching_time/osee2unified/osee2unified/Pods/BangcleCryptoTool/BangcleCryptoTool/libs/libbangcle_crypto_tool.a(crypto.o)
|
233
|
-
# [ 7] /Users/xiongzenghui/Desktop/launching_time/osee2unified/osee2unified/Pods/BangcleCryptoTool/BangcleCryptoTool/libs/libbangcle_crypto_tool.a(des.o)
|
234
|
-
# ...............
|
235
|
-
# [ 23] /Users/xxx/ci-jenkins/workspace/xxx-iOS-module/VenomShellProject/osee2unified/Pods/AFNetworking/AFNetworking.framework/AFNetworking(AFAutoPurgingImageCache.o)
|
236
|
-
# [ 24] /Users/xxx/ci-jenkins/workspace/xxx-iOS-module/VenomShellProject/osee2unified/Pods/AFNetworking/AFNetworking.framework/AFNetworking(AFHTTPSessionManager.o)
|
237
|
-
# [ 25] /Users/xxx/ci-jenkins/workspace/xxx-iOS-module/VenomShellProject/osee2unified/Pods/AFNetworking/AFNetworking.framework/AFNetworking(AFImageDownloader.o)
|
238
|
-
# ...........
|
239
|
-
|
240
|
-
# 1.
|
241
|
-
objc_file_id = $1.to_i #=> 6 , 23
|
242
|
-
library_name = $2 #=> libbangcle_crypto_tool.a , AFNetworking
|
243
|
-
object_file = $3 #=> crypto.o , AFAutoPurgingImageCache.o
|
244
|
-
|
245
|
-
# 2.
|
246
|
-
of = ObjectFile.new(
|
247
|
-
objc_file_id,
|
248
|
-
object_file,
|
249
|
-
library_name,
|
250
|
-
if line.include?('.framework')
|
251
|
-
true
|
252
|
-
else
|
253
|
-
false
|
254
|
-
end,
|
255
|
-
Array.new,
|
256
|
-
0,
|
257
|
-
0
|
258
|
-
)
|
259
|
-
|
260
|
-
# 3. 保存解析 xx.o (object file) 的数据
|
261
|
-
@object_map[objc_file_id] = of
|
262
|
-
|
263
|
-
# 4. 创建【静态库 library】对应的实体对象
|
264
|
-
library = @library_map[library_name]
|
265
|
-
library ||= Library.new(library_name, 0, [], 0, '')
|
266
|
-
|
267
|
-
# 5. 【追加】 xx.o 文件位于 ``# Object Files`` 后面的 [ n] 标号
|
268
|
-
library.object_file_ids << objc_file_id
|
269
|
-
|
270
|
-
# 6. 确认 library 的 pod_name 名字
|
271
|
-
if line.include?('/Pods/')
|
272
|
-
# [ 23] /Users/xxx/ci-jenkins/workspace/xxx-iOS-module/VenomShellProject/osee2unified/Pods/AFNetworking/AFNetworking.framework/AFNetworking(AFAutoPurgingImageCache.o)
|
273
|
-
divstr = line.split('/Pods/').last #=> AFNetworking/AFNetworking.framework/AFNetworking(AFAutoPurgingImageCache.o
|
274
|
-
pod_name = divstr.split('/').first #=> AFNetworking
|
275
|
-
library.pod_name = pod_name
|
276
|
-
else
|
277
|
-
library.pod_name = ''
|
278
|
-
end
|
279
|
-
|
280
|
-
# 7.
|
281
|
-
@library_map[library_name] = library
|
282
|
-
elsif line =~ %r(\[(.*)\].*\/(.*))
|
283
|
-
# [ 3] /SomePath/Release-iphoneos/CrashDemo.build/Objects-normal/arm64/main.o
|
284
|
-
# [100] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/UIKit.framework/UIKit.tbd
|
285
|
-
# [9742] /SomePath/Pods/du.framework/du
|
286
|
-
# [8659] /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/libswiftDispatch.dylib
|
287
|
-
|
288
|
-
# 1.
|
289
|
-
objc_file_id = $1.to_i #=> 3
|
290
|
-
object_file = $2 #=> main.o
|
291
|
-
|
292
|
-
# 2.
|
293
|
-
library_name = ''
|
294
|
-
if line.include?('.framework') && !object_file.include?('.') #=> /path/to/du.framework/du 【用户】动态库
|
295
|
-
library_name = object_file
|
296
|
-
else
|
297
|
-
if line.end_with?('.a')
|
298
|
-
library_name = object_file
|
299
|
-
else
|
300
|
-
library_name = if object_file.end_with?('.tbd')
|
301
|
-
'tdb'
|
302
|
-
elsif object_file.end_with?('.dylib')
|
303
|
-
'dylib'
|
304
|
-
elsif object_file.end_with?('.o')
|
305
|
-
'main'
|
306
|
-
else
|
307
|
-
'system'
|
308
|
-
end
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
# 3.
|
313
|
-
of = ObjectFile.new(
|
314
|
-
objc_file_id,
|
315
|
-
object_file,
|
316
|
-
library_name,
|
317
|
-
if line.include?('.framework') && !object_file.include?('.')
|
318
|
-
true
|
319
|
-
else
|
320
|
-
false
|
321
|
-
end,
|
322
|
-
Array.new,
|
323
|
-
0,
|
324
|
-
0
|
325
|
-
)
|
326
|
-
|
327
|
-
# 4.
|
328
|
-
@object_map[objc_file_id] = of
|
329
|
-
# puts "#{objc_file_id} -- #{library_name}"
|
330
|
-
|
331
|
-
# 5.
|
332
|
-
library = @library_map[library_name]
|
333
|
-
library ||= Library.new(library_name, 0, [], 0, '')
|
334
|
-
|
335
|
-
# 6.
|
336
|
-
library.object_file_ids << objc_file_id
|
337
|
-
|
338
|
-
# 7.
|
339
|
-
@library_map[library_name] = library
|
340
|
-
elsif line =~ /\[(.*)\]\s*([\w\s]+)/
|
341
|
-
# Sample:
|
342
|
-
# [ 0] linker synthesized
|
343
|
-
# [ 1] dtrace
|
344
|
-
|
345
|
-
# 1.
|
346
|
-
objc_file_id = $1.to_i
|
347
|
-
|
348
|
-
# 2.
|
349
|
-
of = ObjectFile.new(
|
350
|
-
objc_file_id,
|
351
|
-
$2,
|
352
|
-
'',
|
353
|
-
false,
|
354
|
-
Array.new,
|
355
|
-
0,
|
356
|
-
0
|
357
|
-
)
|
358
|
-
|
359
|
-
# 3.
|
360
|
-
@object_map[objc_file_id] = of
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
def parse_sections(line)
|
365
|
-
# Sections:
|
366
|
-
# Address Size Segment Section
|
367
|
-
# 0x1000048A0 0x055656A8 __TEXT __text
|
368
|
-
# 0x105569F48 0x000090E4 __TEXT __stubs
|
369
|
-
# 0x10557302C 0x000079D4 __TEXT __stub_helper
|
370
|
-
# 0x10557AA00 0x002D4E1A __TEXT __cstring
|
371
|
-
#
|
372
|
-
|
373
|
-
lines = line.split(' ').each(&:strip)
|
374
|
-
section_name = lines[3]
|
375
|
-
segment_name = lines[2]
|
376
|
-
start_addr = lines.first.to_i(16)
|
377
|
-
end_addr = start_addr + lines[1].to_i(16)
|
378
|
-
residual_size = lines[1].to_i(16)
|
379
|
-
|
380
|
-
section = Section.new(
|
381
|
-
section_name,
|
382
|
-
segment_name,
|
383
|
-
start_addr,
|
384
|
-
end_addr,
|
385
|
-
0,
|
386
|
-
residual_size
|
387
|
-
)
|
388
|
-
|
389
|
-
# 【section name】may be dulicate in different segment
|
390
|
-
# 所以使用 segment_name + section_name 作为 map 的 key 存储
|
391
|
-
@section_map[section.key] = section
|
392
|
-
end
|
393
|
-
|
394
|
-
def parse_symbols(line)
|
395
|
-
# Symbols:
|
396
|
-
# Address Size File Name
|
397
|
-
# 0x1000048A0 0x000000A4 [ 2] _main
|
398
|
-
# 0x100004944 0x00000028 [ 5] _Bangcle_WB_AES_encrypt
|
399
|
-
|
400
|
-
if line =~ %r(^0x(.+?)\s+0x(.+?)\s+\[(.+?)\]\s(.*))
|
401
|
-
# 1.
|
402
|
-
symbol_address = $1.to_i(16) #=> Address
|
403
|
-
symbol_size = $2.to_i(16) #=> Size
|
404
|
-
object_file_id = $3.to_i #=> File
|
405
|
-
symbol_name = $4 #=> Name
|
406
|
-
|
407
|
-
# 2.
|
408
|
-
object_file = @object_map[object_file_id]
|
409
|
-
|
410
|
-
# 3.
|
411
|
-
unless object_file
|
412
|
-
UI.error "#{line.inspect} can not found object file"
|
413
|
-
return
|
414
|
-
end
|
415
|
-
|
416
|
-
# 4.
|
417
|
-
symbol = Symbol.new(
|
418
|
-
symbol_address,
|
419
|
-
symbol_size,
|
420
|
-
object_file_id,
|
421
|
-
symbol_name
|
422
|
-
)
|
423
|
-
|
424
|
-
# 5. 追加【symbol】符号
|
425
|
-
object_file.symbols.push(symbol)
|
426
|
-
|
427
|
-
# 6. 统计【Object File】总大小
|
428
|
-
object_file.size += symbol_size
|
429
|
-
|
430
|
-
# 7. 统计【library/framework】总大小
|
431
|
-
library = @library_map[object_file.library]
|
432
|
-
library.size += symbol_size if library
|
433
|
-
|
434
|
-
# 8. 统计【segment】总大小
|
435
|
-
sections = @section_map.detect do |seg_sec_name, sec|
|
436
|
-
if sec
|
437
|
-
(sec.start_addr...sec.end_addr).include?(symbol_address)
|
438
|
-
else
|
439
|
-
false
|
440
|
-
end
|
441
|
-
end
|
442
|
-
# pp "⚠️ seg_sec_names=#{seg_sec_names}"
|
443
|
-
|
444
|
-
if sections
|
445
|
-
section = sections[1]
|
446
|
-
segment_name = section.parse_segment
|
447
|
-
segment = @segment_map[segment_name]
|
448
|
-
# pp "⚠️ segment_name=#{segment_name}"
|
449
|
-
|
450
|
-
unless segment
|
451
|
-
segment = Segment.new(segment_name, symbol_size, symbol_size)
|
452
|
-
else
|
453
|
-
segment.symbol_size += symbol_size
|
454
|
-
segment.residual_size += symbol_size
|
455
|
-
end
|
456
|
-
# pp "⚠️ #{segment.name} #{segment.symbol_size} - #{segment.residual_size}"
|
457
|
-
@segment_map[segment_name] = segment
|
458
|
-
end
|
459
|
-
else
|
460
|
-
UI.error "#{line.inspect} can not match symbol regular"
|
461
|
-
end
|
462
|
-
end
|
463
|
-
|
464
|
-
def parse_dead(line)
|
465
|
-
# Dead Stripped Symbols:
|
466
|
-
# Size File Name
|
467
|
-
# <<dead>> 0x00000028 [ 2] literal string: com.xxx.audioBook.notifications.start
|
468
|
-
# <<dead>> 0x00000029 [ 2] literal string: com.xxx.audioBook.notifications.stoped
|
469
|
-
# <<dead>> 0x0000002A [ 2] literal string: com.xxx.audioBook.notificaitons.loading
|
470
|
-
# <<dead>> 0x0000002A [ 2] literal string: com.xxx.audioBook.notificaitons.palying
|
471
|
-
# <<dead>> 0x0000002D [ 2] literal string: com.xxx.audioBook.notificaitons.paySuccess
|
472
|
-
# <<dead>> 0x00000006 [ 2] literal string: appId
|
473
|
-
# <<dead>> 0x00000008 [ 2] literal string: fakeURL
|
474
|
-
# <<dead>> 0x00000007 [ 2] literal string: 300300
|
475
|
-
|
476
|
-
if line =~ %r(^<<dead>>\s+0x(.+?)\s+\[(.+?)\]\w*)
|
477
|
-
size = $1.to_i(16)
|
478
|
-
file = $2.to_i
|
479
|
-
|
480
|
-
object_file = @object_map[file]
|
481
|
-
return unless object_file
|
482
|
-
|
483
|
-
# 累加 xx.o 的 dead symbol size
|
484
|
-
object_file.dead_symbol_size += size
|
485
|
-
|
486
|
-
# 累加 library(xx.o) 的 dead symbol size
|
487
|
-
@library_map[object_file.library].dead_symbol_size += size
|
488
|
-
end
|
489
|
-
end
|
490
|
-
end
|
491
|
-
end
|
492
|
-
end
|
493
|
-
end
|