#!/usr/bin/env python | |
import subprocess | |
import re | |
import sys | |
def get_init_array(filename): | |
# Call objdump -s -j .init_array <filename> to get the contents of the .init_array section | |
try: | |
objdump_output = subprocess.check_output(['objdump', '-s', '-j', '.init_array', filename], stderr=subprocess.STDOUT) | |
except subprocess.CalledProcessError as e: | |
return [] | |
objdump_output = objdump_output.decode('utf-8') | |
objdump_output = objdump_output.split('\n') | |
found_contents = False | |
constructors = [] | |
for line in objdump_output: | |
if line.startswith("Contents of section .init_array:"): | |
found_contents = True | |
continue | |
if found_contents: | |
if not line.strip(): | |
break | |
line_re = re.compile(r'^ *[0-9a-f]+ (([0-9a-f]+) ([0-9a-f]+)) ?(([0-9a-f]+) ([0-9a-f]+))?') | |
m = line_re.match(line) | |
if not m: continue | |
addr = m.group(1).replace(' ', '') | |
addr = int.from_bytes(bytes.fromhex(addr), 'little') | |
constructors.append(addr) | |
try: | |
addr = m.group(4) | |
if not addr: continue | |
addr = addr.replace(' ', '') | |
addr = int.from_bytes(bytes.fromhex(addr), 'little') | |
constructors.append(addr) | |
except IndexError: | |
pass | |
return constructors | |
def check_for_ffast_math(filename, addr): | |
# Call objdump to disassemble filename at addr | |
objdump_process = subprocess.Popen( | |
['objdump', f'--start-address={hex(addr)}', '-d', filename], | |
stdout=subprocess.PIPE | |
) | |
found_stmxcsr, found_8040, found_rdmxcsr = False, False, False | |
for line in iter(lambda: objdump_process.stdout.readline(), b""): | |
line = line.decode('utf-8') | |
if "stmxcsr" in line: | |
found_stmxcsr = True | |
if "0x8040" in line: | |
found_8040 = True | |
if "ldmxcsr" in line: | |
found_rdmxcsr = True | |
if "retq" in line: | |
objdump_process.kill() | |
break | |
return found_stmxcsr and found_8040 and found_rdmxcsr | |
for filename in sys.argv[1:]: | |
print(f"{filename} ", end="", flush=True) | |
constructors = get_init_array(filename) | |
i = len(constructors) | |
print(f"({i} constructor{'s'[:i^1]}) ", end="", flush=True) | |
for addr in constructors: | |
if check_for_ffast_math(filename, addr): | |
print(f"contains ffast-math constructor at {hex(addr)}") | |
break | |
else: | |
print("is clean") |
You might be interested in this even faster version :) https://gist.github.com/moyix/2154125d0cb9947ec0525fb49449fab7
Yeah, I fell back to that because just doing byte matching isn't going to work if your GCC decided to do things slightly differently -- in particular, in the crtfastmath.o I have here right now I'm getting vstmxcsr / vldmxcsr rather than straight stmxcsr/ldmxcsr (actually I'm not sure how you could ever get anything else on x86-64, what with sse being the default and that being what GCC has long used when SSE is on). This leads to the first insn, the vstmxcsr, having the encoding c5 f8 ae 5c 24 fc, which doesn't match what you're looking for in that script. The vldmxcsr at the end is similarly modified.
In general byte matching insns as wildly variable as the x86's has always struck me as highly likely to fail, particularly when as I am right now you are faced with a pile of binaries of very different ages built by very different compilers (but many of them with -ffast-math, sigh).
But modified this way I can check ten thousand or so binaries in only ten minutes or so with a proper disassembler in the loop, even on a not-terribly-fast quad-core Athlon (on a machine with a decent number of cores it would be much faster, of course).
FYI, I just forked this to add multiprocessing support (invoking multiple objdumps in parallel) and to avoid disassembling the entire binary from the point of the ELF constructor up to the end (!). It's much faster now, but we lost some of the nice output (counts of constructors, addresses etc).