You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

178 lines
6.8 KiB

#!/usr/bin/env python
import os
import sys
import subprocess
# This script gets two arguments: a binary file, and a name of a function - and returns
# the size in bytes of the function which name matches the input.
# If no function with that name was found then the script returns with error.
def read_func_size_linux(bin, func_name):
# Use nm --print-size.
# Output for functions has 4 columns: [ Address, Size, "T" for text segment, Name]
command = "nm --print-size --radix=d {0} | c++filt --no-params | grep {1}".format(bin, func_name)
popen_obj = subprocess.Popen( command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
(out, err) = popen_obj.communicate()
if (popen_obj.returncode == 0):
out = out.decode().rstrip().lstrip()
for line in out.split('\n'):
tokens = line.split()
if len(tokens) != 4:
print ("ERROR: unexpected input \"{0}\"".format(line))
return None
if tokens[3] != func_name: continue
try:
size = int(tokens[1])
return size
except ValueError:
print ("ERROR: Failed to convert \"{0}\" to int".format(tokens[1]))
return None
if (err): print("ERROR {0}".format(err.decode().rstrip().lstrip()))
print ("ERROR: Failed to find function \"{0}\" in {1}".format(func_name, bin))
return None
def read_func_size_osx(bin, func_name):
# Use nm --print-size.
# Output for functions has 4 columns: [ Address, Size, "T" for text segment, Name]
# On MAC, the Size will appear as 0.
# For inlined assembly functions, we insert another symbol right after the function with extension _endfunc,
# for example, if we have function "bar" then using macros for MAC we will have another symbol "bar_endfunc".
# The size if the difference between the addresses of the two symbols.
osx_func_name = "_{0}".format(func_name)
osx_func_name_endfunc = "{0}_endfunc".format(osx_func_name)
command = "nm --print-size --radix=d {0} | c++filt --no-params | grep {1}".format(bin, osx_func_name)
popen_obj = subprocess.Popen( command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
(out, err) = popen_obj.communicate()
if (popen_obj.returncode == 0):
addr_func = None
addr_endfunc = None
out = out.decode().rstrip().lstrip()
for line in out.split('\n'):
tokens = line.split()
if len(tokens) != 4:
print ("ERROR: unexpected input \"{0}\"".format(line))
return None
if tokens[3] == osx_func_name:
try:
addr_func = int(tokens[0])
except ValueError:
print ("ERROR: Failed to convert \"{0}\" to int".format(tokens[0]))
return None
elif tokens[3] == osx_func_name_endfunc:
try:
addr_endfunc = int(tokens[0])
except ValueError:
print ("ERROR: Failed to convert \"{0}\" to int".format(tokens[0]))
return None
if (addr_func and addr_endfunc):
return addr_endfunc - addr_func
if (err): print("ERROR {0}".format(err.decode().rstrip().lstrip()))
print ("ERROR: Failed to find function \"{0}\" in {1}".format(bin, func_name))
return None
def read_func_size_windows(bin, func_name):
# Use dumpbin /DISASM:NOBYTES
# This will print the disassembly of the binary with addresses.
# We will be searching for a label of the func_name, and then RET instruction,
# and calculate the function size as the number of bytes between the two addresses.
command = "dumpbin /DISASM:NOBYTES {0}".format(bin)
popen_obj = subprocess.Popen( command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
(out, err) = popen_obj.communicate()
if (popen_obj.returncode == 0):
out = out.decode("utf-8", "ignore")
lines = out.split('\n')
lines1 = ""
for line in lines:
line.strip()
if line.startswith(("{0}:".format(func_name), "_{0}:".format(func_name))):
# record the line with the first instruction after the function label
idx = lines.index(line)+1
lines1 = lines[idx:]
break
lines2 = None
for line in lines1:
if " ret" in line:
# record the line with RET instruction
idx = lines1.index(line)+1
lines2 = lines1[:idx]
break
if (lines2):
# address of the first instruction after the function label
addr1 = lines2[0].lstrip().split(':')[0]
# address of RET instruction in this function
addr2 = lines2[-1].lstrip().split(':')[0]
try:
addr1 = int(addr1, 16)
except ValueError:
print ("ERROR: Failed to convert \"{0}\" to int".format(addr1))
return None
try:
addr2 = int(addr2, 16)
except ValueError:
print ("ERROR: Failed to convert \"{0}\" to int".format(addr2))
return None
# assume size of RET instruction is 1
return (addr2-addr1+1)
if (err): print("ERROR {0}".format(err.decode().rstrip().lstrip()))
print ("ERROR: Failed to find function \"{0}\" in {1}".format(func_name, bin))
return None
def main():
if (sys.platform != 'linux') and (sys.platform != 'darwin') and (sys.platform != 'win32'):
print("This script does not support {0}".format(sys.platform))
return 1
if len(sys.argv) != 3:
print("ERROR: Please provide 2 arguments: path to binary and name of function")
return 1
path_to_bin = sys.argv[1]
func_name = sys.argv[2]
if not os.path.exists(path_to_bin):
print("ERROR: File does not exist [{0}]".format(path_to_bin))
return 1
if (sys.platform == 'linux'):
size = read_func_size_linux(path_to_bin, func_name)
elif (sys.platform == 'darwin'):
size = read_func_size_osx(path_to_bin, func_name)
elif (sys.platform == 'win32'):
size = read_func_size_windows(path_to_bin, func_name)
if size:
print(size)
return 0
return 1
if __name__ == "__main__":
exit(main())