iBoot/tools/code_sizer

184 lines
4.2 KiB
Tcl

#!/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