Last active
May 14, 2019 10:35
-
-
Save 0x1306a94/a5cf44ca5802914ecab91a827326b46e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"flag" | |
"fmt" | |
"io" | |
"os" | |
"sort" | |
"strconv" | |
"strings" | |
) | |
type ObjectInfo struct { | |
Num int64 // 文件编号 | |
Path string // 文件全路径 | |
Name string // 文件名 | |
LibName string | |
Size int64 | |
} | |
type LibraryInfo struct { | |
Name string | |
Objects []*ObjectInfo | |
Size int64 | |
} | |
type SectionInfo struct { | |
Segment string | |
Section string | |
Size int64 | |
} | |
type SortWrapper struct { //注意此处 | |
libs []*LibraryInfo | |
by func(p, q *LibraryInfo) bool | |
} | |
func (pw SortWrapper) Len() int { // 重写 Len() 方法 | |
return len(pw.libs) | |
} | |
func (pw SortWrapper) Swap(i, j int) { // 重写 Swap() 方法 | |
pw.libs[i], pw.libs[j] = pw.libs[j], pw.libs[i] | |
} | |
func (pw SortWrapper) Less(i, j int) bool { // 重写 Less() 方法 | |
return pw.by(pw.libs[i], pw.libs[j]) | |
} | |
var ( | |
filePath string // linkMap 文件路径 | |
verbose bool // 打印过程 | |
objectMaps map[int64]*ObjectInfo = make(map[int64]*ObjectInfo) | |
librarys map[string]*LibraryInfo = make(map[string]*LibraryInfo) | |
sections []*SectionInfo = make([]*SectionInfo, 0) | |
currParser = ParserUnknown | |
deadCodeSize int64 = 0 | |
) | |
const ( | |
ParserUnknown = iota | |
ParserObject | |
ParserSection | |
ParserSymbols | |
ParserDeadStrippedSymbols | |
) | |
func main() { | |
flag.StringVar(&filePath, "path", "", "LinkMap file path") | |
flag.BoolVar(&verbose, "verbose", true, "打印日志过程") | |
flag.Parse() | |
if filePath == "" { | |
flag.Usage() | |
os.Exit(0) | |
} | |
if !pathExist(filePath) { | |
fmt.Println("LinkMap file does not exist") | |
os.Exit(0) | |
} | |
file, err := os.Open(filePath) | |
if err != nil { | |
fmt.Println(err) | |
os.Exit(0) | |
} | |
defer file.Close() | |
reader := bufio.NewReader(file) | |
skipNextLine := false | |
for { | |
line, err := reader.ReadString('\n') | |
if err != nil && err == io.EOF { | |
logger("The Parse finished") | |
break | |
} | |
if err != nil { | |
fmt.Println(err) | |
os.Exit(0) | |
} | |
if skipNextLine { | |
skipNextLine = false | |
continue | |
} | |
line = strings.TrimSuffix(line, "\n") | |
if len(line) == 0 { | |
continue | |
} | |
if strings.HasPrefix(line, "# Object files:") { | |
currParser = ParserObject | |
continue | |
} else if strings.HasPrefix(line, "# Sections:") { | |
currParser = ParserSection | |
skipNextLine = true | |
logger("Parser object files complete library count: ", len(librarys)) | |
continue | |
} else if strings.HasPrefix(line, "# Symbols:") { | |
currParser = ParserSymbols | |
skipNextLine = true | |
logger("Parser Sections complete ") | |
showSectionSize() | |
continue | |
} else if strings.HasPrefix(line, "# Dead Stripped Symbols:") { | |
currParser = ParserDeadStrippedSymbols | |
skipNextLine = true | |
logger("Parser Symbols complete ") | |
continue | |
} | |
switch currParser { | |
case ParserObject: | |
parserObject(line) | |
case ParserSection: | |
parserSection(line) | |
case ParserSymbols, ParserDeadStrippedSymbols: | |
parserSymbols(line) | |
} | |
} | |
logger("dead code size: ", sizeFormat(deadCodeSize)) | |
libs := make([]*LibraryInfo, 0) | |
for _, v := range librarys { | |
//logger("lib: ", k, " size: ", sizeFormat(v.Size)) | |
libs = append(libs, v) | |
} | |
sort.Sort(SortWrapper{libs: libs, by: func(p, q *LibraryInfo) bool { | |
return p.Size > q.Size | |
}}) | |
for _, val := range libs { | |
logger("lib: ", val.Name, " size: ", sizeFormat(val.Size)) | |
} | |
} | |
func parserObject(line string) { | |
fields := strings.Fields(line) | |
if len(fields) != 2 { | |
return | |
} | |
numBeginIdx := strings.Index(line, "[") | |
numEndIdx := strings.Index(line, "]") | |
numStr := string(line[numBeginIdx : numEndIdx+1]) | |
numStr = strings.ReplaceAll(numStr, "[", "") | |
numStr = strings.ReplaceAll(numStr, "]", "") | |
numStr = strings.ReplaceAll(numStr, " ", "") | |
num, err := strconv.ParseInt(numStr, 0, 64) | |
if err != nil { | |
logger(err) | |
return | |
} | |
nameFields := strings.FieldsFunc(fields[1], func(r rune) bool { | |
return r == '/' | |
}) | |
name := nameFields[len(nameFields)-1] | |
var lib *LibraryInfo | |
libName := "" | |
if strings.HasSuffix(name, ")") { | |
libNames := strings.Split(name, "(") | |
libName = libNames[0] | |
name = strings.TrimRight(libNames[1], ")") | |
} else if strings.Contains(line, ".app/Contents/Developer/Platforms") { | |
libNames := strings.Split(name, "/") | |
libName = libNames[len(libNames)-1] | |
name = libName | |
} | |
if len(libName) > 0 { | |
lib = librarys[libName] | |
if lib == nil { | |
lib = &LibraryInfo{ | |
Name: libName, | |
Objects: make([]*ObjectInfo, 0), | |
Size: 0, | |
} | |
librarys[libName] = lib | |
} | |
} | |
object := &ObjectInfo{ | |
Num: num, | |
Path: fields[1], | |
Name: name, | |
LibName: libName, | |
} | |
objectMaps[num] = object | |
if lib != nil { | |
lib.Objects = append(lib.Objects, object) | |
} | |
} | |
func parserSection(line string) { | |
fields := strings.Fields(line) | |
if len(fields) != 4 { | |
return | |
} | |
size, err := strconv.ParseInt(fields[1], 0, 64) | |
if err != nil { | |
logger(err) | |
return | |
} | |
section := &SectionInfo{ | |
Segment: fields[2], | |
Section: fields[3], | |
Size: size, | |
} | |
sections = append(sections, section) | |
} | |
func showSectionSize() { | |
if len(sections) == 0 { | |
return | |
} | |
logger("# Sections:") | |
logger("Size\t\tSegment\tSection") | |
for _, val := range sections { | |
logger(sizeFormat(val.Size), "\t\t", val.Segment, "\t", val.Section) | |
} | |
} | |
func parserSymbols(line string) { | |
if strings.HasPrefix(line, "<<dead>>") { | |
line = strings.Replace(line, "<<dead>>", "dead", 1) | |
} | |
fields := strings.Fields(line) | |
if len(fields) < 4 { | |
return | |
} | |
size, err := strconv.ParseInt(fields[1], 0, 64) | |
if err != nil { | |
logger(err) | |
return | |
} | |
numBeginIdx := strings.Index(line, "[") | |
numEndIdx := strings.Index(line, "]") | |
numStr := string(line[numBeginIdx : numEndIdx+1]) | |
numStr = strings.ReplaceAll(numStr, "[", "") | |
numStr = strings.ReplaceAll(numStr, "]", "") | |
numStr = strings.ReplaceAll(numStr, " ", "") | |
num, err := strconv.ParseInt(numStr, 0, 64) | |
if err != nil { | |
logger(err) | |
return | |
} | |
if object, ok := objectMaps[num]; ok { | |
object.Size += size | |
if len(object.LibName) > 0 { | |
if lib, ok := librarys[object.LibName]; ok { | |
lib.Size += size | |
} | |
} | |
} | |
if fields[0] == "dead" { | |
deadCodeSize += size | |
} | |
} | |
func sizeFormat(size int64) string { | |
_size := float64(size) | |
if size > (1024 << 10) { | |
return fmt.Sprintf("%.2f MB", _size/float64(1024<<10)) | |
} | |
if size > 1024 { | |
return fmt.Sprintf("%.2f KB", _size/1024.0) | |
} | |
return fmt.Sprintf("%v B", size) | |
} | |
func pathExist(path string) bool { | |
_, err := os.Stat(path) | |
if err != nil && os.IsNotExist(err) { | |
return false | |
} | |
return true | |
} | |
func logger(a ...interface{}) { | |
if verbose { | |
fmt.Println(a...) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment