Last active
October 29, 2022 11:42
-
-
Save moyix/6ebe542affd555218bdb82b40ec49291 to your computer and use it in GitHub Desktop.
Hacky script to check for the set_fast_math constructor in an executable/shared library using objdump
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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).