Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Hacky script to check for the set_fast_math constructor in an executable/shared library using objdump
#!/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")
@nickalcock
Copy link

nickalcock commented Oct 28, 2022

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).

@moyix
Copy link
Author

moyix commented Oct 29, 2022

You might be interested in this even faster version :) https://gist.github.com/moyix/2154125d0cb9947ec0525fb49449fab7

@nickalcock
Copy link

nickalcock commented Oct 29, 2022

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment