summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChe-yu Wu <cheyuw@google.com>2017-08-18 09:01:24 +0800
committerchrome-bot <chrome-bot@chromium.org>2017-08-24 01:25:51 -0700
commit4f21ee309c63db2084dd0c0122c5311636a3627e (patch)
tree0bcce8c66c312d93dde4d7e6954ef9272de8814e
parentf9a2ef234703a632aadef8ba637487881cabc843 (diff)
downloadchrome-ec-4f21ee309c63db2084dd0c0122c5311636a3627e.tar.gz
extra/stack_analyzer: Show indirect calls.
Show the indirect calls found in disassembly. BUG=chromium:648840 BRANCH=none TEST=extra/stack_analyzer/stack_analyzer_unittest.py make BOARD=elm && extra/stack_analyzer/stack_analyzer.py \ --objdump=arm-none-eabi-objdump \ --addr2line=arm-none-eabi-addr2line \ --export_taskinfo=./build/elm/util/export_taskinfo.so \ --section=RW \ --annotation=./extra/stack_analyzer/example_annotation.yaml \ ./build/elm/RW/ec.RW.elf make BOARD=elm SECTION=RW \ ANNOTATION=./extra/stack_analyzer/example_annotation.yaml \ analyzestack Change-Id: Ib82e68e0bc6c4be56ab679c297f256cbfe94bbb7 Signed-off-by: Che-yu Wu <cheyuw@google.com> Reviewed-on: https://chromium-review.googlesource.com/628048 Reviewed-by: Nicolas Boichat <drinkcat@chromium.org>
-rw-r--r--extra/stack_analyzer/README.md2
-rwxr-xr-xextra/stack_analyzer/stack_analyzer.py65
-rwxr-xr-xextra/stack_analyzer/stack_analyzer_unittest.py12
3 files changed, 62 insertions, 17 deletions
diff --git a/extra/stack_analyzer/README.md b/extra/stack_analyzer/README.md
index 9bb37656f3..73e980e610 100644
--- a/extra/stack_analyzer/README.md
+++ b/extra/stack_analyzer/README.md
@@ -47,3 +47,5 @@ but it is inlined to the current function and you can follow the trace:
`usb_pd_protocol.c:1808 -> usb_pd_protocol.c:1191 -> usb_pd_protocol.c:798` to
find the callsite. The second callsite is at `usb_pd_protocol.c:2672`. And the
third one is added by annotation.
+
+The unresolved indirect callsites have the similar format to the above.
diff --git a/extra/stack_analyzer/stack_analyzer.py b/extra/stack_analyzer/stack_analyzer.py
index 4b10bbfe2d..2d84ccec60 100755
--- a/extra/stack_analyzer/stack_analyzer.py
+++ b/extra/stack_analyzer/stack_analyzer.py
@@ -136,7 +136,7 @@ class Callsite(object):
Attributes:
address: Address of callsite location. None if it is unknown.
- target: Callee address.
+ target: Callee address. None if it is unknown.
is_tail: A bool indicates that it is a tailing call.
callee: Resolved callee function. None if it hasn't been resolved.
"""
@@ -145,12 +145,14 @@ class Callsite(object):
"""Constructor.
Args:
- address: Address of callsite location.
- target: Callee address.
+ address: Address of callsite location. None if it is unknown.
+ target: Callee address. None if it is unknown.
is_tail: A bool indicates that it is a tailing call. (function jump to
another function without restoring the stack frame)
callee: Resolved callee function.
"""
+ # It makes no sense that both address and target are unknown.
+ assert not (address is None and target is None)
self.address = address
self.target = target
self.is_tail = is_tail
@@ -281,8 +283,13 @@ class ArmAnalyzer(object):
CBZ_CBNZ_OPCODE_RE = re.compile(r'^(cbz|cbnz)(\.\w)?$')
# Example: "r0, 1009bcbe <host_cmd_motion_sense+0x1d2>"
CBZ_CBNZ_OPERAND_RE = re.compile(r'^[^,]+,\s+{}$'.format(IMM_ADDRESS_RE))
+ # Ignore lr register because it's for return.
+ INDIRECT_CALL_OPERAND_RE = re.compile(r'^r\d+|sb|sl|fp|ip|sp|pc$')
# TODO(cheyuw): Handle conditional versions of following
# instructions.
+ LDR_OPCODE_RE = re.compile(r'^ldr(\.\w)?$')
+ # Example: "pc, [sp], #4"
+ LDR_PC_OPERAND_RE = re.compile(r'^pc, \[([^\]]+)\]')
# TODO(cheyuw): Handle other kinds of stm instructions.
PUSH_OPCODE_RE = re.compile(r'^push$')
STM_OPCODE_RE = re.compile(r'^stmdb$')
@@ -298,7 +305,7 @@ class ArmAnalyzer(object):
instructions: Instruction list.
Returns:
- (stack_frame, callsites): Size of stack frame and callsite list.
+ (stack_frame, callsites): Size of stack frame, callsite list.
"""
stack_frame = 0
callsites = []
@@ -307,22 +314,38 @@ class ArmAnalyzer(object):
is_call_opcode = self.CALL_OPCODE_RE.match(opcode) is not None
is_cbz_cbnz_opcode = self.CBZ_CBNZ_OPCODE_RE.match(opcode) is not None
if is_jump_opcode or is_call_opcode or is_cbz_cbnz_opcode:
+ is_tail = is_jump_opcode or is_cbz_cbnz_opcode
+
if is_cbz_cbnz_opcode:
result = self.CBZ_CBNZ_OPERAND_RE.match(operand_text)
else:
result = self.CALL_OPERAND_RE.match(operand_text)
- if result is not None:
+ if result is None:
+ # Failed to match immediate address, maybe it is an indirect call.
+ # CBZ and CBNZ can't be indirect calls.
+ if (not is_cbz_cbnz_opcode and
+ self.INDIRECT_CALL_OPERAND_RE.match(operand_text) is not None):
+ # Found an indirect call.
+ callsites.append(Callsite(address, None, is_tail))
+
+ else:
target_address = int(result.group(1), 16)
# Filter out the in-function target (branches and in-function calls,
# which are actually branches).
if not (function_symbol.size > 0 and
function_symbol.address < target_address <
(function_symbol.address + function_symbol.size)):
- # Maybe it's a callsite.
- callsites.append(Callsite(address,
- target_address,
- is_jump_opcode or is_cbz_cbnz_opcode))
+ # Maybe it is a callsite.
+ callsites.append(Callsite(address, target_address, is_tail))
+
+ elif self.LDR_OPCODE_RE.match(opcode) is not None:
+ result = self.LDR_PC_OPERAND_RE.match(operand_text)
+ if result is not None:
+ # Ignore "ldr pc, [sp], xx" because it's usually a return.
+ if result.group(1) != 'sp':
+ # Found an indirect call.
+ callsites.append(Callsite(address, None, True))
elif self.PUSH_OPCODE_RE.match(opcode) is not None:
# Example: "{r4, r5, r6, r7, lr}"
@@ -593,8 +616,9 @@ class StackAnalyzer(object):
# Resolve callees of functions.
for function in function_map.values():
for callsite in function.callsites:
- # Remain the callee as None if we can't resolve it.
- callsite.callee = function_map.get(callsite.target)
+ if callsite.target is not None:
+ # Remain the callee as None if we can't resolve it.
+ callsite.callee = function_map.get(callsite.target)
return function_map
@@ -951,10 +975,21 @@ class StackAnalyzer(object):
curr_func = succ_func
- if len(failed_sigs) > 0:
- print('Failed to resolve some annotation signatures:')
- for sig, error in failed_sigs:
- print(' {}: {}'.format(sig, error))
+ print('Unresolved indirect callsites:')
+ for function in function_map.values():
+ indirect_callsites = []
+ for callsite in function.callsites:
+ if callsite.target is None:
+ indirect_callsites.append(callsite.address)
+
+ if len(indirect_callsites) > 0:
+ print(' {}'.format(function.name))
+ for address in indirect_callsites:
+ PrintInlineStack(address, ' ')
+
+ print('Unresolved annotation signatures:')
+ for sig, error in failed_sigs:
+ print(' {}: {}'.format(sig, error))
def ParseArgs():
diff --git a/extra/stack_analyzer/stack_analyzer_unittest.py b/extra/stack_analyzer/stack_analyzer_unittest.py
index 03144fda32..0f62e1a3be 100755
--- a/extra/stack_analyzer/stack_analyzer_unittest.py
+++ b/extra/stack_analyzer/stack_analyzer_unittest.py
@@ -123,12 +123,16 @@ class ArmAnalyzerTest(unittest.TestCase):
(0x2c, 'stmdb', 'sp!, {r4}'),
(0x30, 'stmdb', 'sp, {r4}'),
(0x34, 'bx.n', '10 <foo>'),
+ (0x36, 'bx.n', 'r3'),
+ (0x38, 'ldr', 'pc, [r10]'),
]
(size, callsites) = analyzer.AnalyzeFunction(symbol, instructions)
self.assertEqual(size, 72)
expect_callsites = [sa.Callsite(0x1e, 0xdeadbeef, False),
sa.Callsite(0x22, 0x0, False),
- sa.Callsite(0x34, 0x10, True)]
+ sa.Callsite(0x34, 0x10, True),
+ sa.Callsite(0x36, None, True),
+ sa.Callsite(0x38, None, True)]
self.assertEqual(callsites, expect_callsites)
@@ -459,6 +463,7 @@ class StackAnalyzerTest(unittest.TestCase):
' 2000: b508\t\tpush {r3, lr}\n'
' 2002: f00e fcc5\tbl 1000 <hook_task>\n'
' 2006: f00e bd3b\tb.w 53968 <get_program_memory_addr>\n'
+ ' 200a: 1234 5678\tb.w sl\n'
)
addrtoline_mock.return_value = [('??', '??', 0)]
@@ -478,7 +483,10 @@ class StackAnalyzerTest(unittest.TestCase):
mock.call(' console_task (8) [??:0] 2000'),
mock.call(' -> ?? [??:0] 2002'),
mock.call(' hook_task (8) [??:0] 1000'),
- mock.call('Failed to resolve some annotation signatures:'),
+ mock.call('Unresolved indirect callsites:'),
+ mock.call(' console_task'),
+ mock.call(' -> ?? [??:0] 200a'),
+ mock.call('Unresolved annotation signatures:'),
mock.call(' fake_func: function is not found'),
])