394 lines
12 KiB
Python
Executable file
394 lines
12 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
import sys
|
|
import argparse
|
|
|
|
verbose = False
|
|
|
|
|
|
class NotFDE(Exception):
|
|
pass
|
|
|
|
|
|
def func_name(infos, symtb):
|
|
for sym in symtb:
|
|
if infos["beg"] == symtb[sym][0]:
|
|
return sym
|
|
return None
|
|
|
|
|
|
def parse_fde_head(line):
|
|
spl = line.strip().split()
|
|
assert len(spl) >= 2
|
|
if spl[1] == "ZERO":
|
|
raise NotFDE
|
|
assert len(spl) >= 4
|
|
typ = spl[3]
|
|
if typ != "FDE":
|
|
raise NotFDE
|
|
assert len(spl) == 6
|
|
pc_range = spl[5][3:]
|
|
pc_beg, pc_end = map(lambda x: int(x, 16), pc_range.split(".."))
|
|
|
|
return pc_beg, pc_end
|
|
|
|
|
|
def detect_clang_flat_to_pyramid(rows):
|
|
""" Artificially repair clang flat callee-saved saving to a gcc pyramid-like shape.
|
|
|
|
Eg., clang will generate
|
|
LOC CFA rbx rbp ra
|
|
0000000000007180 rsp+8 u u c-8
|
|
0000000000007181 rsp+16 u u c-8
|
|
0000000000007182 rsp+24 u u c-8
|
|
0000000000007189 rsp+7632 c-24 c-16 c-8
|
|
|
|
|
|
while we would wish to have
|
|
LOC CFA rbx rbp ra
|
|
0000000000007180 rsp+8 u u c-8
|
|
0000000000007181 rsp+16 u c-16 c-8
|
|
0000000000007182 rsp+24 c-24 c-16 c-8
|
|
0000000000007189 rsp+7632 c-24 c-16 c-8
|
|
|
|
The repair strategy is as follows:
|
|
- ignore the implicit first row
|
|
- find the first k lines such that only CFA changes from line to line, with a delta
|
|
of +8, with address delta of 2. (push is 2 bytes long)
|
|
- for every callee-saved R that concerns us and that is defined at line k+1 with
|
|
offset c-x, while rsp+x is the CFA of line k' <= k, redefine R as c-k in lines
|
|
[k'; k[
|
|
"""
|
|
|
|
def is_flatness_row(row, prev_cfa, prev_loc, first_row=False):
|
|
for reg in row:
|
|
if reg not in ["LOC", "CFA", "ra"] and row[reg] != "u":
|
|
return prev_cfa, prev_loc, True
|
|
cfa = row["CFA"]
|
|
if cfa[:4] != "rsp+":
|
|
return prev_cfa, prev_loc, True
|
|
cfa_offset = int(cfa[4:])
|
|
if cfa_offset != prev_cfa + 8:
|
|
return prev_cfa, prev_loc, True
|
|
prev_cfa += 8
|
|
loc = row["LOC"]
|
|
if not first_row and loc > prev_loc + 2:
|
|
return prev_cfa, prev_loc, True
|
|
prev_loc = loc
|
|
|
|
return prev_cfa, prev_loc, False
|
|
|
|
def try_starting_at(start_row):
|
|
if len(rows) < start_row + 1: # Ensure we have at least the start row
|
|
return rows, False
|
|
|
|
flatness_row_id = start_row
|
|
if rows[1]["CFA"][:4] != "rsp+":
|
|
return rows, False
|
|
first_cfa = int(rows[start_row]["CFA"][4:])
|
|
prev_cfa = first_cfa
|
|
prev_loc = rows[start_row]["LOC"]
|
|
first_row = True
|
|
|
|
for row in rows[start_row + 1 :]:
|
|
prev_cfa, prev_loc, flatness = is_flatness_row(
|
|
row, prev_cfa, prev_loc, first_row
|
|
)
|
|
first_row = False
|
|
if flatness:
|
|
break
|
|
flatness_row_id += 1
|
|
|
|
flatness_row_id += 1
|
|
if flatness_row_id - start_row <= 1 or flatness_row_id >= len(rows):
|
|
return rows, False # nothing to change
|
|
flatness_row = rows[flatness_row_id]
|
|
|
|
reg_changes = {}
|
|
for reg in flatness_row:
|
|
if reg in ["LOC", "CFA", "ra"]:
|
|
continue
|
|
rule = flatness_row[reg]
|
|
if rule[:2] != "c-":
|
|
return rows, False # Not a flat_to_pyramid after all
|
|
rule_offset = int(rule[2:])
|
|
rule_offset_rectified = rule_offset - first_cfa
|
|
if rule_offset_rectified % 8 != 0:
|
|
return rows, False
|
|
row_change_id = rule_offset_rectified // 8 + start_row
|
|
reg_changes[reg] = (row_change_id, rule)
|
|
|
|
for reg in reg_changes:
|
|
change_from, rule = reg_changes[reg]
|
|
for row in rows[change_from:flatness_row_id]:
|
|
row[reg] = rule
|
|
|
|
return rows, True
|
|
|
|
for start_row in [1, 2]:
|
|
mod_rows, modified = try_starting_at(start_row)
|
|
if modified:
|
|
return mod_rows
|
|
return rows
|
|
|
|
|
|
def parse_fde_row(line, reg_cols):
|
|
vals = list(map(lambda x: x.strip(), line.split()))
|
|
assert len(vals) > reg_cols["ra"] # ra is the rightmost useful column
|
|
|
|
out = {"LOC": int(vals[0], 16), "CFA": vals[1]}
|
|
|
|
for reg in reg_cols:
|
|
col_id = reg_cols[reg]
|
|
out[reg] = vals[col_id]
|
|
|
|
if "rbp" not in out:
|
|
out["rbp"] = "u"
|
|
|
|
return out
|
|
|
|
|
|
def clean_rows(rows):
|
|
# Merge equivalent contiguous rows
|
|
if not rows:
|
|
return rows
|
|
assert len(rows) > 0
|
|
out_rows = [rows[0]]
|
|
for row in rows[1:]:
|
|
if not row == out_rows[-1]:
|
|
filtered_row = row
|
|
filter_out = []
|
|
for reg in filtered_row:
|
|
if reg not in ["LOC", "CFA", "rbp", "ra"]:
|
|
filter_out.append(reg)
|
|
for reg in filter_out:
|
|
filtered_row.pop(reg)
|
|
out_rows.append(filtered_row)
|
|
return out_rows
|
|
|
|
|
|
def parse_fde(lines):
|
|
assert len(lines) > 0
|
|
try:
|
|
pc_beg, pc_end = parse_fde_head(lines[0])
|
|
except NotFDE:
|
|
return
|
|
|
|
rows = [{"LOC": 0, "CFA": "rsp+8", "rbp": "u", "ra": "c-8"}] # Implicit CIE row
|
|
|
|
if len(lines) >= 2: # Has content
|
|
head_row = list(map(lambda x: x.strip(), lines[1].split()))
|
|
reg_cols = {}
|
|
for pos, reg in enumerate(head_row):
|
|
if reg not in ["LOC", "CFA"]:
|
|
reg_cols[reg] = pos
|
|
|
|
for line in lines[2:]:
|
|
rows.append(parse_fde_row(line, reg_cols))
|
|
|
|
rows = detect_clang_flat_to_pyramid(rows)
|
|
return {"beg": pc_beg, "end": pc_end, "rows": clean_rows(rows)}
|
|
|
|
|
|
def parse_eh_frame(handle, symtb):
|
|
output = []
|
|
cur_lines = []
|
|
for line in handle:
|
|
line = line.strip()
|
|
if line == "===":
|
|
return output
|
|
if line.startswith("Contents of"):
|
|
continue
|
|
if line == "":
|
|
if cur_lines != []:
|
|
infos = parse_fde(cur_lines)
|
|
if infos:
|
|
symname = func_name(infos, symtb)
|
|
if symname not in ["_start", "__libc_csu_init"]:
|
|
# These functions have weird instructions
|
|
output.append(infos)
|
|
cur_lines = []
|
|
else:
|
|
cur_lines.append(line)
|
|
return sorted(output, key=lambda x: x["beg"])
|
|
|
|
|
|
def match_segments(orig_eh, synth_eh):
|
|
out = []
|
|
matches = [[False] * len(orig_eh), [False] * len(synth_eh)]
|
|
for orig_id, orig_fde in enumerate(orig_eh):
|
|
is_plt = False
|
|
for row in orig_fde["rows"]:
|
|
if row["CFA"] == "exp":
|
|
is_plt = True
|
|
|
|
for synth_id, synth_fde in enumerate(synth_eh):
|
|
if orig_fde["beg"] == synth_fde["beg"]:
|
|
if is_plt:
|
|
matches[1][synth_id] = True # PLT -- fake match
|
|
continue
|
|
if matches[1][synth_id]:
|
|
if verbose:
|
|
print("Multiple matches (synth)")
|
|
if matches[0][orig_id]:
|
|
if verbose:
|
|
print(
|
|
"Multiple matches (orig) {}--{}".format(
|
|
hex(orig_fde["beg"]), hex(orig_fde["end"])
|
|
)
|
|
)
|
|
else:
|
|
matches[0][orig_id] = True
|
|
matches[1][synth_id] = True
|
|
out.append((orig_fde, synth_fde))
|
|
elif (
|
|
is_plt
|
|
and orig_fde["beg"] <= synth_fde["beg"]
|
|
and synth_fde["end"] <= orig_fde["end"]
|
|
):
|
|
matches[1][synth_id] = True # PLT -- fake match
|
|
if is_plt:
|
|
matches[0][orig_id] = True # plt -- fake match
|
|
|
|
unmatched_orig, unmatched_synth = [], []
|
|
for orig_id, orig_match in enumerate(matches[0]):
|
|
if not orig_match:
|
|
unmatched_orig.append(orig_eh[orig_id])
|
|
for synth_id, synth_match in enumerate(matches[1]):
|
|
if not synth_match:
|
|
unmatched_synth.append(synth_eh[synth_id])
|
|
return out, unmatched_orig, unmatched_synth
|
|
|
|
|
|
def fde_pos(fde):
|
|
return "{}--{}".format(hex(fde["beg"]), hex(fde["end"]))
|
|
|
|
|
|
def dump_light_fdes(fdes):
|
|
for fde in fdes:
|
|
print("FDE: {}".format(fde_pos(fde)))
|
|
|
|
|
|
def match_fde(orig, synth):
|
|
def vals_of(row):
|
|
return {"CFA": row["CFA"], "ra": row["ra"], "rbp": row["rbp"]}
|
|
|
|
def loc_of(rch):
|
|
return rch[1]["LOC"]
|
|
|
|
rows = [orig["rows"], synth["rows"]]
|
|
cur_val = [vals_of(rows[0][0]), vals_of(rows[1][0])]
|
|
|
|
rowchanges = []
|
|
for typ in [0, 1]:
|
|
for row in rows[typ]:
|
|
rowchanges.append((typ, row))
|
|
rowchanges.sort(key=loc_of)
|
|
|
|
mismatch_count = 0
|
|
match_count = 0
|
|
for rowid, rowch in enumerate(rowchanges):
|
|
typ, row = rowch[0], rowch[1]
|
|
cur_val[typ] = vals_of(row)
|
|
if len(rowchanges) > rowid + 1 and loc_of(rowch) == loc_of(
|
|
rowchanges[rowid + 1]
|
|
):
|
|
continue
|
|
if cur_val[0] != cur_val[1]:
|
|
if verbose:
|
|
print(
|
|
"Mismatch {}: {} ; {}".format(
|
|
hex(row["LOC"]), cur_val[0], cur_val[1]
|
|
)
|
|
)
|
|
mismatch_count += 1
|
|
else:
|
|
match_count += 1
|
|
|
|
return mismatch_count, match_count
|
|
|
|
|
|
def parse_sym_table(handle):
|
|
def readint(x):
|
|
if x.startswith("0x"):
|
|
return int(x[2:], 16)
|
|
return int(x)
|
|
|
|
out_map = {}
|
|
for line in handle:
|
|
line = line.strip()
|
|
if line == "===":
|
|
break
|
|
|
|
spl = list(map(lambda x: x.strip(), line.split()))
|
|
loc = int(spl[1], 16)
|
|
size = readint(spl[2])
|
|
name = spl[7]
|
|
out_map[name] = (loc, size)
|
|
return out_map
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"-v", "--verbose", action="store_true", help="Display verbose results"
|
|
)
|
|
parser.add_argument(
|
|
"test_name",
|
|
action="store",
|
|
help="Base path of the test case (eg. some_test/01)",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
global verbose
|
|
parser_args = parse_args()
|
|
test_name = parser_args.test_name
|
|
verbose = parser_args.verbose
|
|
symtb = parse_sym_table(sys.stdin)
|
|
orig_eh = parse_eh_frame(sys.stdin, symtb)
|
|
synth_eh = parse_eh_frame(sys.stdin, symtb)
|
|
matched, unmatched_orig, unmatched_synth = match_segments(orig_eh, synth_eh)
|
|
# dump_light_fdes(unmatched_orig)
|
|
# dump_light_fdes(unmatched_synth)
|
|
|
|
mismatches = 0
|
|
good_match = 0
|
|
for (orig, synth) in matched:
|
|
cur_mismatch, cur_match = match_fde(orig, synth)
|
|
mismatches += cur_mismatch
|
|
good_match += cur_match
|
|
reports = []
|
|
if mismatches > 0:
|
|
reports.append("{} mismatches - {} well matched".format(mismatches, good_match))
|
|
if unmatched_orig:
|
|
worth_reporting = False
|
|
for unmatched in unmatched_orig:
|
|
if len(unmatched["rows"]) > 1:
|
|
worth_reporting = True
|
|
break
|
|
if worth_reporting:
|
|
unmatched_addrs = [fde_pos(fde) for fde in unmatched_orig]
|
|
reports.append(
|
|
"{} unmatched (orig): {}".format(
|
|
len(unmatched_orig), ", ".join(unmatched_addrs)
|
|
)
|
|
)
|
|
if unmatched_synth:
|
|
unmatched_addrs = [fde_pos(fde) for fde in unmatched_synth]
|
|
reports.append(
|
|
"{} unmatched (synth): {}".format(
|
|
len(unmatched_synth), ", ".join(unmatched_addrs)
|
|
)
|
|
)
|
|
|
|
if reports:
|
|
# If we had some errors to report, let's report positive data too
|
|
reports.append("{} matched".format(len(matched)))
|
|
print("{}: {}".format(test_name, "; ".join(reports)))
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|