summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author David Srbecky <dsrbecky@google.com> 2022-01-12 13:31:00 +0000
committer Treehugger Robot <treehugger-gerrit@google.com> 2022-04-07 17:50:17 +0000
commit8bb486a78e350ce6072d71e15cc4f01a142e80d4 (patch)
treece4a98de895bb081c67417271bd560523364704c
parent608a9158b06598c6403dea766822534a347b1f27 (diff)
Add CFI-checking script and fix found CFI issues.
It is essential for the unwinder to know the stack size at any point. Our assembly is manually annotated with this information, but it is easy to make mistakes (e.g. cfi_restore_state handles register spills, but does not restore CFI offset as one might easily assume). Add python script which compares the CFI information to disassembly and checks that they match (that is, it is checking that push/pop instructions result in the expected CFI offset increment/decrement). In general, this is unfeasible (as we would not need CFI otherwise), but even conservative checks can catch many bugs. Test: ./art/test.py -b -r Change-Id: I232fc0a31fa6f28381e18fdf6aceaf0b8323f550
-rw-r--r--runtime/arch/arm/jni_entrypoints_arm.S2
-rw-r--r--runtime/arch/arm/quick_entrypoints_arm.S16
-rw-r--r--runtime/arch/arm64/quick_entrypoints_arm64.S6
-rw-r--r--runtime/interpreter/mterp/arm64ng/control_flow.S1
-rw-r--r--runtime/interpreter/mterp/arm64ng/main.S2
-rw-r--r--runtime/interpreter/mterp/armng/control_flow.S1
-rw-r--r--runtime/interpreter/mterp/armng/main.S13
-rwxr-xr-xtools/check_cfi.py247
8 files changed, 282 insertions, 6 deletions
diff --git a/runtime/arch/arm/jni_entrypoints_arm.S b/runtime/arch/arm/jni_entrypoints_arm.S
index 96b6241a20..fc57df76d3 100644
--- a/runtime/arch/arm/jni_entrypoints_arm.S
+++ b/runtime/arch/arm/jni_entrypoints_arm.S
@@ -114,11 +114,13 @@ ENTRY art_jni_dlsym_lookup_stub
add sp, #12 @ restore stack pointer
.cfi_adjust_cfa_offset -12
cbz r0, 1f @ is method code null?
+ .cfi_remember_state
pop {r0, r1, r2, r3, lr} @ restore regs
.cfi_adjust_cfa_offset -20
.cfi_restore lr
bx r12 @ if non-null, tail call to method's code
1:
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, 20
pop {r0, r1, r2, r3, pc} @ restore regs and return to caller to handle exception
END art_jni_dlsym_lookup_stub
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 7e11d32656..d6f129be50 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -686,6 +686,7 @@ ENTRY art_quick_aput_obj
mov r0, r3
bl artIsAssignableFromCode
cbz r0, .Lthrow_array_store_exception
+ .cfi_remember_state
pop {r0-r2, lr}
.cfi_restore r0
.cfi_restore r1
@@ -700,8 +701,13 @@ ENTRY art_quick_aput_obj
strb r3, [r3, r0]
blx lr
.Lthrow_array_store_exception:
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, 16
pop {r0-r2, lr}
- /* No need to repeat restore cfi directives, the ones above apply here. */
+ .cfi_restore r0
+ .cfi_restore r1
+ .cfi_restore r2
+ .cfi_restore lr
+ .cfi_adjust_cfa_offset -16
SETUP_SAVE_ALL_CALLEE_SAVES_FRAME r3
mov r1, r2
mov r2, rSELF @ pass Thread::Current
@@ -780,7 +786,7 @@ ENTRY \name
RESTORE_SAVE_EVERYTHING_FRAME_KEEP_R0
REFRESH_MARKING_REGISTER
bx lr
- .cfi_restore_state
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
1:
DELIVER_PENDING_EXCEPTION_FRAME_READY
END \name
@@ -1356,12 +1362,14 @@ ENTRY art_quick_resolution_trampoline
mov r3, sp @ pass SP
blx artQuickResolutionTrampoline @ (Method* called, receiver, Thread*, SP)
cbz r0, 1f @ is code pointer null? goto exception
+ .cfi_remember_state
mov r12, r0
ldr r0, [sp, #0] @ load resolved method in r0
RESTORE_SAVE_REFS_AND_ARGS_FRAME
REFRESH_MARKING_REGISTER
bx r12 @ tail-call into actual code
1:
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_REFS_AND_ARGS
RESTORE_SAVE_REFS_AND_ARGS_FRAME
DELIVER_PENDING_EXCEPTION
END art_quick_resolution_trampoline
@@ -1502,6 +1510,7 @@ ENTRY art_quick_instrumentation_entry
mov r3, sp @ pass SP
blx artInstrumentationMethodEntryFromCode @ (Method*, Object*, Thread*, SP)
cbz r0, .Ldeliver_instrumentation_entry_exception
+ .cfi_remember_state
@ Deliver exception if we got nullptr as function.
mov r12, r0 @ r12 holds reference to code
ldr r0, [sp, #4] @ restore r0
@@ -1511,6 +1520,7 @@ ENTRY art_quick_instrumentation_entry
REFRESH_MARKING_REGISTER
bx r12 @ call method with lr set to art_quick_instrumentation_exit
.Ldeliver_instrumentation_entry_exception:
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_REFS_AND_ARGS
@ Deliver exception for art_quick_instrumentation_entry placed after
@ art_quick_instrumentation_exit so that the fallthrough works.
RESTORE_SAVE_REFS_AND_ARGS_FRAME
@@ -1529,6 +1539,7 @@ ENTRY art_quick_instrumentation_exit
cbz r0, .Ldo_deliver_instrumentation_exception
@ Deliver exception if we got nullptr as function.
+ .cfi_remember_state
cbnz r1, .Ldeoptimize
// Normal return.
str r0, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 4]
@@ -1537,6 +1548,7 @@ ENTRY art_quick_instrumentation_exit
REFRESH_MARKING_REGISTER
bx lr
.Ldo_deliver_instrumentation_exception:
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
DELIVER_PENDING_EXCEPTION_FRAME_READY
.Ldeoptimize:
str r1, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 4]
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index a20d558240..d8c91e11b9 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -1645,11 +1645,13 @@ ENTRY art_quick_proxy_invoke_handler
bl artQuickProxyInvokeHandler // (Method* proxy method, receiver, Thread*, SP)
ldr x2, [xSELF, THREAD_EXCEPTION_OFFSET]
cbnz x2, .Lexception_in_proxy // success if no exception is pending
+ .cfi_remember_state
RESTORE_SAVE_REFS_AND_ARGS_FRAME // Restore frame
REFRESH_MARKING_REGISTER
fmov d0, x0 // Store result in d0 in case it was float or double
ret // return on success
.Lexception_in_proxy:
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_REFS_AND_ARGS
RESTORE_SAVE_REFS_AND_ARGS_FRAME
DELIVER_PENDING_EXCEPTION
END art_quick_proxy_invoke_handler
@@ -1692,12 +1694,14 @@ ENTRY art_quick_resolution_trampoline
mov x3, sp
bl artQuickResolutionTrampoline // (called, receiver, Thread*, SP)
cbz x0, 1f
+ .cfi_remember_state
mov xIP0, x0 // Remember returned code pointer in xIP0.
ldr x0, [sp, #0] // artQuickResolutionTrampoline puts called method in *SP.
RESTORE_SAVE_REFS_AND_ARGS_FRAME
REFRESH_MARKING_REGISTER
br xIP0
1:
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_REFS_AND_ARGS
RESTORE_SAVE_REFS_AND_ARGS_FRAME
DELIVER_PENDING_EXCEPTION
END art_quick_resolution_trampoline
@@ -1924,6 +1928,7 @@ ENTRY art_quick_instrumentation_exit
bl artInstrumentationMethodExitFromCode // (Thread*, SP, gpr_res*, fpr_res*)
cbz x0, .Ldo_deliver_instrumentation_exception
+ .cfi_remember_state
// Handle error
cbnz x1, .Ldeoptimize
// Normal return.
@@ -1933,6 +1938,7 @@ ENTRY art_quick_instrumentation_exit
REFRESH_MARKING_REGISTER
br lr
.Ldo_deliver_instrumentation_exception:
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
DELIVER_PENDING_EXCEPTION_FRAME_READY
.Ldeoptimize:
str x1, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 8]
diff --git a/runtime/interpreter/mterp/arm64ng/control_flow.S b/runtime/interpreter/mterp/arm64ng/control_flow.S
index f2d0559064..7873ca6b7c 100644
--- a/runtime/interpreter/mterp/arm64ng/control_flow.S
+++ b/runtime/interpreter/mterp/arm64ng/control_flow.S
@@ -168,6 +168,7 @@
RESTORE_ALL_CALLEE_SAVES
ret
.cfi_restore_state
+ CFI_DEF_CFA_BREG_PLUS_UCONST CFI_REFS, -8, CALLEE_SAVES_SIZE
%def op_return_object():
% op_return(is_object="1", is_void="0", is_wide="0")
diff --git a/runtime/interpreter/mterp/arm64ng/main.S b/runtime/interpreter/mterp/arm64ng/main.S
index 8ada63c03e..89de81f5e4 100644
--- a/runtime/interpreter/mterp/arm64ng/main.S
+++ b/runtime/interpreter/mterp/arm64ng/main.S
@@ -1917,6 +1917,8 @@ artNterpAsmInstructionStart = .L_op_nop
% return "nterp_"
%def opcode_start():
NAME_START nterp_${opcode}
+ # Explicitly restore CFA, just in case the previous opcode clobbered it (by .cfi_def_*).
+ CFI_DEF_CFA_BREG_PLUS_UCONST CFI_REFS, -8, CALLEE_SAVES_SIZE
%def opcode_end():
NAME_END nterp_${opcode}
// Advance to the end of this handler. Causes error if we are past that point.
diff --git a/runtime/interpreter/mterp/armng/control_flow.S b/runtime/interpreter/mterp/armng/control_flow.S
index ab05228c2c..689b245729 100644
--- a/runtime/interpreter/mterp/armng/control_flow.S
+++ b/runtime/interpreter/mterp/armng/control_flow.S
@@ -168,6 +168,7 @@
.cfi_def_cfa sp, CALLEE_SAVES_SIZE
RESTORE_ALL_CALLEE_SAVES lr_to_pc=1
.cfi_restore_state
+ CFI_DEF_CFA_BREG_PLUS_UCONST CFI_REFS, -4, CALLEE_SAVES_SIZE
%def op_return_object():
% op_return(is_object="1", is_void="0", is_wide="0")
diff --git a/runtime/interpreter/mterp/armng/main.S b/runtime/interpreter/mterp/armng/main.S
index 5a086f597d..310a3fd8f1 100644
--- a/runtime/interpreter/mterp/armng/main.S
+++ b/runtime/interpreter/mterp/armng/main.S
@@ -488,6 +488,7 @@ END \name
.cfi_adjust_cfa_offset -4
.if \lr_to_pc
pop {r9-r11, pc} @ 9 words of callee saves and args.
+ .cfi_adjust_cfa_offset -16
.else
pop {r9-r11, lr} @ 9 words of callee saves and args.
.cfi_adjust_cfa_offset -16
@@ -1908,7 +1909,7 @@ EndExecuteNterpImpl:
* expected common case is a "reasonable" value that converts directly
* to modest integer. The EABI convert function isn't doing this for us.
*/
-nterp_d2l_doconv:
+ENTRY nterp_d2l_doconv
ubfx r2, r1, #20, #11 @ grab the exponent
movw r3, #0x43e
cmp r2, r3 @ MINLONG < x > MAXLONG?
@@ -1931,6 +1932,7 @@ d2l_maybeNaN:
mov r0, #0
mov r1, #0
bx lr @ return 0 for NaN
+END nterp_d2l_doconv
/*
* Convert the float in r0 to a long in r0/r1.
@@ -1939,7 +1941,7 @@ d2l_maybeNaN:
* expected common case is a "reasonable" value that converts directly
* to modest integer. The EABI convert function isn't doing this for us.
*/
-nterp_f2l_doconv:
+ENTRY nterp_f2l_doconv
ubfx r2, r0, #23, #8 @ grab the exponent
cmp r2, #0xbe @ MININT < x > MAXINT?
bhs f2l_special_cases
@@ -1960,6 +1962,7 @@ f2l_maybeNaN:
mov r0, #0
mov r1, #0
bx lr @ return 0 for NaN
+END nterp_f2l_doconv
// Entrypoints into runtime.
NTERP_TRAMPOLINE nterp_get_static_field, NterpGetStaticField
@@ -1975,7 +1978,7 @@ NTERP_TRAMPOLINE nterp_load_object, NterpLoadObject
// within [ExecuteNterpImpl, EndExecuteNterpImpl).
%def instruction_end():
- .type artNterpAsmInstructionEnd, #object
+ .type artNterpAsmInstructionEnd, #function
.hidden artNterpAsmInstructionEnd
.global artNterpAsmInstructionEnd
artNterpAsmInstructionEnd:
@@ -1986,7 +1989,7 @@ artNterpAsmInstructionEnd:
%def instruction_start():
- .type artNterpAsmInstructionStart, #object
+ .type artNterpAsmInstructionStart, #function
.hidden artNterpAsmInstructionStart
.global artNterpAsmInstructionStart
artNterpAsmInstructionStart = .L_op_nop
@@ -1996,6 +1999,8 @@ artNterpAsmInstructionStart = .L_op_nop
% return "nterp_"
%def opcode_start():
NAME_START nterp_${opcode}
+ # Explicitly restore CFA, just in case the previous opcode clobbered it (by .cfi_def_*).
+ CFI_DEF_CFA_BREG_PLUS_UCONST CFI_REFS, -4, CALLEE_SAVES_SIZE
%def opcode_end():
NAME_END nterp_${opcode}
// Advance to the end of this handler. Causes error if we are past that point.
diff --git a/tools/check_cfi.py b/tools/check_cfi.py
new file mode 100755
index 0000000000..ac258c28aa
--- /dev/null
+++ b/tools/check_cfi.py
@@ -0,0 +1,247 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Checks dwarf CFI (unwinding) information by comparing it to disassembly.
+It is only a simple heuristic check of stack pointer adjustments.
+Fully inferring CFI from disassembly is not possible in general.
+"""
+
+import os, re, subprocess, collections, pathlib, bisect, collections
+from typing import List, Optional, Set, Tuple
+
+Source = collections.namedtuple("Source", ["addr", "file", "line", "flag"])
+
+def get_source(lib: pathlib.Path) -> List[Source]:
+ """ Get source-file and line-number for all hand-written assembly code. """
+
+ proc = subprocess.run(["llvm-dwarfdump", "--debug-line", lib],
+ encoding='utf-8',
+ capture_output=True,
+ check=True)
+
+ section_re = re.compile("^debug_line\[0x[0-9a-f]+\]$", re.MULTILINE)
+ filename_re = re.compile('file_names\[ *(\d)+\]:\n\s*name: "(.*)"', re.MULTILINE)
+ line_re = re.compile('0x([0-9a-f]{16}) +(\d+) +\d+ +(\d+)' # addr, line, column, file
+ ' +\d+ +\d +(.*)') # isa, discriminator, flag
+
+ results = []
+ for section in section_re.split(proc.stdout):
+ files = {m[1]: m[2] for m in filename_re.finditer(section)}
+ if not any(f.endswith(".S") for f in files.values()):
+ continue
+ lines = line_re.findall(section)
+ results.extend([Source(int(a, 16), files[fn], l, fg) for a, l, fn, fg in lines])
+ return sorted(filter(lambda line: "end_sequence" not in line.flag, results))
+
+Fde = collections.namedtuple("Fde", ["addr", "end", "data"])
+
+def get_fde(lib: pathlib.Path) -> List[Fde]:
+ """ Get all unwinding FDE blocks (in dumped text-based format) """
+
+ proc = subprocess.run(["llvm-dwarfdump", "--debug-frame", lib],
+ encoding='utf-8',
+ capture_output=True,
+ check=True)
+
+ section_re = re.compile("\n(?! |\n)", re.MULTILINE) # New-line not followed by indent.
+ fda_re = re.compile(".* FDE .* pc=([0-9a-f]+)...([0-9a-f]+)")
+
+ results = []
+ for section in section_re.split(proc.stdout):
+ m = fda_re.match(section)
+ if m:
+ fde = Fde(int(m[1], 16), int(m[2], 16), section)
+ if fde.addr != 0:
+ results.append(fde)
+ return sorted(results)
+
+Asm = collections.namedtuple("Asm", ["addr", "name", "data"])
+
+def get_asm(lib: pathlib.Path) -> List[Asm]:
+ """ Get disassembly for all methods (in dumped text-based format) """
+
+ proc = subprocess.run(["llvm-objdump", "--disassemble", lib],
+ encoding='utf-8',
+ capture_output=True,
+ check=True)
+
+ section_re = re.compile("\n(?! |\n)", re.MULTILINE) # New-line not followed by indent.
+ sym_re = re.compile("([0-9a-f]+) <(.+)>:")
+
+ results = []
+ for section in section_re.split(proc.stdout):
+ sym = sym_re.match(section)
+ if sym:
+ results.append(Asm(int(sym[1], 16), sym[2], section))
+ return sorted(results)
+
+Cfa = collections.namedtuple("Cfa", ["addr", "cfa"])
+
+def get_cfa(fde: Fde) -> List[Cfa]:
+ """ Extract individual CFA (SP+offset) entries from the FDE block """
+
+ cfa_re = re.compile("0x([0-9a-f]+): CFA=([^\s:]+)")
+ return [Cfa(int(addr, 16), cfa) for addr, cfa in cfa_re.findall(fde.data)]
+
+Inst = collections.namedtuple("Inst", ["addr", "inst", "symbol"])
+
+def get_instructions(asm: Asm) -> List[Inst]:
+ """ Extract individual instructions from disassembled code block """
+
+ data = re.sub(r"[ \t]+", " ", asm.data)
+ inst_re = re.compile(r"([0-9a-f]+): +(?:[0-9a-f]{2} +)*(.*)")
+ return [Inst(int(addr, 16), inst, asm.name) for addr, inst in inst_re.findall(data)]
+
+CfaOffset = collections.namedtuple("CfaOffset", ["addr", "offset"])
+
+def get_dwarf_cfa_offsets(cfas: List[Cfa]) -> List[CfaOffset]:
+ """ Parse textual CFA entries into integer stack offsets """
+
+ result = []
+ for addr, cfa in cfas:
+ if cfa == "WSP" or cfa == "SP":
+ result.append(CfaOffset(addr, 0))
+ elif cfa.startswith("WSP+") or cfa.startswith("SP+"):
+ result.append(CfaOffset(addr, int(cfa.split("+")[1])))
+ else:
+ result.append(CfaOffset(addr, None))
+ return result
+
+def get_infered_cfa_offsets(insts: List[Inst]) -> List[CfaOffset]:
+ """ Heuristic to convert disassembly into stack offsets """
+
+ # Regular expressions which find instructions that adjust stack pointer.
+ rexprs = []
+ def add(rexpr, adjust_offset):
+ rexprs.append((re.compile(rexpr), adjust_offset))
+ add(r"sub sp,(?: sp,)? #(\d+)", lambda m: int(m[1]))
+ add(r"add sp,(?: sp,)? #(\d+)", lambda m: -int(m[1]))
+ add(r"str \w+, \[sp, #-(\d+)\]!", lambda m: int(m[1]))
+ add(r"ldr \w+, \[sp\], #(\d+)", lambda m: -int(m[1]))
+ add(r"stp \w+, \w+, \[sp, #-(\d+)\]!", lambda m: int(m[1]))
+ add(r"ldp \w+, \w+, \[sp\], #(\d+)", lambda m: -int(m[1]))
+ add(r"vpush \{([d0-9, ]*)\}", lambda m: 8 * len(m[1].split(",")))
+ add(r"vpop \{([d0-9, ]*)\}", lambda m: -8 * len(m[1].split(",")))
+ add(r"v?push(?:\.w)? \{([\w+, ]*)\}", lambda m: 4 * len(m[1].split(",")))
+ add(r"v?pop(?:\.w)? \{([\w+, ]*)\}", lambda m: -4 * len(m[1].split(",")))
+
+ # Regular expression which identifies branches.
+ jmp_re = re.compile(r"cb\w* \w+, 0x(\w+)|(?:b|bl|b\w\w) 0x(\w+)")
+
+ offset, future_offset = 0, {}
+ result = [CfaOffset(insts[0].addr, offset)]
+ for addr, inst, symbol in insts:
+ # Previous code branched here, so us that offset instead.
+ # This likely identifies slow-path which is after return.
+ if addr in future_offset:
+ offset = future_offset[addr]
+
+ # Add entry to output (only if the offset changed).
+ if result[-1].offset != offset:
+ result.append(CfaOffset(addr, offset))
+
+ # Adjust offset if the instruction modifies stack pointer.
+ for rexpr, adjust_offset in rexprs:
+ m = rexpr.match(inst)
+ if m:
+ offset += adjust_offset(m)
+ break # First matched pattern wins.
+
+ # Record branches. We only support forward edges for now.
+ m = jmp_re.match(inst)
+ if m:
+ future_offset[int(m[m.lastindex], 16)] = offset
+ return result
+
+def check_fde(fde: Fde, insts: List[Inst], srcs, verbose: bool = False) -> Tuple[str, Set[int]]:
+ """ Compare DWARF offsets to assembly-inferred offsets. Report differences. """
+
+ error, seen_addrs = None, set()
+ cfas = get_cfa(fde)
+ i, dwarf_cfa = 0, get_dwarf_cfa_offsets(cfas)
+ j, infered_cfa = 0, get_infered_cfa_offsets(insts)
+ for inst in insts:
+ seen_addrs.add(inst.addr)
+ while i+1 < len(dwarf_cfa) and dwarf_cfa[i+1].addr <= inst.addr:
+ i += 1
+ while j+1 < len(infered_cfa) and infered_cfa[j+1].addr <= inst.addr:
+ j += 1
+ if verbose:
+ print("{:08x}: dwarf={:4} infered={:4} {:40} // {}".format(
+ inst.addr, str(dwarf_cfa[i].offset), str(infered_cfa[j].offset),
+ inst.inst.strip(), srcs.get(inst.addr, "")))
+ if dwarf_cfa[i].offset is not None and dwarf_cfa[i].offset != infered_cfa[j].offset:
+ if inst.addr in srcs: # Only report if it maps to source code (not padding or literals).
+ error = error or "{:08x} {}".format(inst.addr, srcs.get(inst.addr, ""))
+ return error, seen_addrs
+
+def check_lib(lib: pathlib.Path):
+ assert lib.exists()
+ IGNORE = [
+ "art_quick_throw_null_pointer_exception_from_signal", # Starts with non-zero offset.
+ "art_quick_generic_jni_trampoline", # Saves/restores SP in other register.
+ "nterp_op_", # Uses calculated CFA due to dynamic stack size.
+ "$d.", # Data (literals) interleaved within code.
+ ]
+ fdes = get_fde(lib)
+ asms = collections.deque(get_asm(lib))
+ srcs = {src.addr: src.file + ":" + src.line for src in get_source(lib)}
+ seen = set() # Used to verify the we have covered all assembly source lines.
+
+ for fde in fdes:
+ if fde.addr not in srcs:
+ continue # Ignore if it is not hand-written assembly.
+
+ # Assembly instructions (one FDE can cover several assembly chunks).
+ all_insts, name = [], None
+ while asms and asms[0].addr < fde.end:
+ asm = asms.popleft()
+ if asm.addr < fde.addr:
+ continue
+ insts = get_instructions(asm)
+ if any(asm.name.startswith(i) for i in IGNORE):
+ seen.update([inst.addr for inst in insts])
+ continue
+ all_insts.extend(insts)
+ name = name or asm.name
+ if not all_insts:
+ continue # No assembly
+
+ # Compare DWARF data to assembly instructions
+ error, seen_addrs = check_fde(fde, all_insts, srcs)
+ if error:
+ print("ERROR at " + name + " " + error)
+ check_fde(fde, all_insts, srcs, True)
+ print("")
+ seen.update(seen_addrs)
+ for addr in sorted(set(srcs.keys()) - seen):
+ print("Missing CFI for {:08x}: {}".format(addr, srcs[addr]))
+
+
+def main(argv):
+ """ Check libraries provided on the command line, or use the default build output """
+
+ libs = argv[1:]
+ if not libs:
+ out = os.environ["OUT"]
+ libs.append(out + "/symbols/apex/com.android.art/lib/libart.so")
+ libs.append(out + "/symbols/apex/com.android.art/lib64/libart.so")
+ for lib in libs:
+ check_lib(pathlib.Path(lib))
+
+if __name__ == "__main__":
+ main(os.sys.argv)