184 lines
4.2 KiB
Plaintext
184 lines
4.2 KiB
Plaintext
|
#!/usr/bin/tclsh
|
||
|
################################################################################
|
||
|
#
|
||
|
# Given a mach-o binary, use nm and gdb to size all the functions it contains
|
||
|
#
|
||
|
|
||
|
|
||
|
proc main { } {
|
||
|
global argv
|
||
|
|
||
|
set binfile [lindex $argv 0]
|
||
|
if {![file readable $binfile]} {
|
||
|
puts "can't read '$binfile'"
|
||
|
exit 1
|
||
|
}
|
||
|
|
||
|
# run nm and parse the output
|
||
|
do_nm $binfile
|
||
|
|
||
|
# compute text symbol sizes
|
||
|
do_sizing
|
||
|
|
||
|
# now ask GDB for the file(s) containing each symbol
|
||
|
do_gdb $binfile
|
||
|
|
||
|
# and emit results
|
||
|
do_emit_sized
|
||
|
do_emit_files
|
||
|
}
|
||
|
|
||
|
|
||
|
proc do_nm {binfile} {
|
||
|
|
||
|
global symbol_addresses
|
||
|
global text_symbols
|
||
|
|
||
|
set lines [exec nm -n $binfile]
|
||
|
|
||
|
foreach {addr type _sym} $lines {
|
||
|
# record the fact that there is a symbol boundary at this address
|
||
|
lappend addresses "0x$addr"
|
||
|
|
||
|
# if the symbol is a text symbol, record its address
|
||
|
# this could be "tds" to get data symbols too..
|
||
|
if {[string match \[tT\] $type]} {
|
||
|
# ignore the first leading underscore in the symbol name
|
||
|
set sym [string range $_sym 1 end]
|
||
|
|
||
|
# save the text symbol and its address
|
||
|
set text_symbols($sym) "0x$addr"
|
||
|
}
|
||
|
}
|
||
|
set symbol_addresses [lsort -integer -unique $addresses]
|
||
|
puts "[llength [array names text_symbols]] text symbols"
|
||
|
}
|
||
|
|
||
|
|
||
|
proc do_sizing {} {
|
||
|
|
||
|
global symbol_addresses
|
||
|
global text_symbols
|
||
|
global text_symbol_sizes
|
||
|
global text_size_symbols
|
||
|
|
||
|
# walk the array of text symbols
|
||
|
foreach sym [array names text_symbols] {
|
||
|
|
||
|
# get the address of the symbol
|
||
|
set addr $text_symbols($sym)
|
||
|
|
||
|
# find the address in the global list of symbol addresses
|
||
|
set index [lsearch $symbol_addresses $addr]
|
||
|
|
||
|
# find the next symbol boundary
|
||
|
set nextaddr [lindex $symbol_addresses [expr $index + 1]]
|
||
|
|
||
|
# size is the distance between the two
|
||
|
set size [expr $nextaddr - $addr]
|
||
|
set text_symbol_sizes($sym) $size
|
||
|
|
||
|
# index symbols by size
|
||
|
lappend text_size_symbols($size) $sym
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
proc do_gdb {binfile} {
|
||
|
|
||
|
global symbol_filenames
|
||
|
global file_symbols
|
||
|
|
||
|
# ask GDB for the output of "info functions"
|
||
|
set lines [exec echo "info functions" | xcrun -sdk iphoneos.internal gdb --quiet $binfile 2>/dev/null]
|
||
|
|
||
|
# Output from GDB includes lines announcing files, and lines describing functions
|
||
|
# within those files.
|
||
|
foreach line [split $lines "\n"] {
|
||
|
# new file announcement
|
||
|
if {[string match "File *" $line]} {
|
||
|
set current_file [string range $line 5 end-1]
|
||
|
}
|
||
|
|
||
|
# function within a file
|
||
|
set paren [string first "(" $line]
|
||
|
if {$paren > 0} {
|
||
|
|
||
|
# function name is last token before ( in the line
|
||
|
set frag [string range $line 0 [expr $paren - 1]]
|
||
|
set sym [string trimleft [lindex [split $frag] end] "*"]
|
||
|
|
||
|
# index filesnames by symbol
|
||
|
lappend symbol_filenames($sym) $current_file
|
||
|
|
||
|
# index symbols by filename
|
||
|
lappend file_symbols($current_file) $sym
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
proc do_emit_sized {} {
|
||
|
|
||
|
global text_size_symbols
|
||
|
global symbol_filenames
|
||
|
|
||
|
puts "Symbols by size"
|
||
|
puts "==============="
|
||
|
foreach size [lsort -integer [array names text_size_symbols]] {
|
||
|
foreach sym $text_size_symbols($size) {
|
||
|
if {[llength [array names symbol_filenames -exact $sym]] > 0} {
|
||
|
puts [format "%8d %-50s %s" $size $sym $symbol_filenames($sym)]
|
||
|
} else {
|
||
|
puts [format "%8d %-50s" $size $sym]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
puts ""
|
||
|
}
|
||
|
|
||
|
proc do_emit_files {} {
|
||
|
|
||
|
global file_symbols
|
||
|
global text_symbol_sizes
|
||
|
|
||
|
puts "Symbols by file size"
|
||
|
puts "===================="
|
||
|
foreach fn [array names file_symbols] {
|
||
|
set size 0
|
||
|
foreach sym $file_symbols($fn) {
|
||
|
if {[array names text_symbol_sizes $sym] != ""} {
|
||
|
incr size $text_symbol_sizes($sym)
|
||
|
} else {
|
||
|
puts "WARNING: no size information for '$sym' - nm didn't see it."
|
||
|
}
|
||
|
}
|
||
|
lappend size_files($size) $fn
|
||
|
}
|
||
|
|
||
|
foreach size [lsort -integer [array names size_files]] {
|
||
|
foreach fn $size_files($size) {
|
||
|
puts "$fn: ($size)"
|
||
|
set lines [list]
|
||
|
foreach sym $file_symbols($fn) {
|
||
|
if {[array names text_symbol_sizes $sym] != ""} {
|
||
|
lappend lines [list $text_symbol_sizes($sym) $sym]
|
||
|
}
|
||
|
}
|
||
|
foreach ent [lsort -index 0 -integer $lines] {
|
||
|
puts [format "%8d %-50s" [lindex $ent 0] [lindex $ent 1]]
|
||
|
}
|
||
|
puts ""
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
################################################################################
|
||
|
main
|