text: import subprocess
  import sys
  import re
  
  function_intro_re = re.compile(r'^(?P<addr>[0-9a-fA-F]{8}) <(?P<name>[a-zA-Z0-9\._]+)>:$')
  insn_re = re.compile(r'^\s+(?P<addr>[0-9a-fA-F]+):\s+(?P<insn>[0-9a-fA-F ]+)\s+\t(?P<op>.*)$')
  
  class Instruction:
      def __init__(self, addr, insn, op):
          self.addr = long(addr, 16)
          self.insn = insn
  
          args = op.split('\t', 1)
          
          self.op = args[0].strip()
          if len(args) == 2:
              comment = args[1].strip().split(';', 1)
          else:
              comment = args
  
          self.args = comment[0].strip()
  
          if len(comment) == 2:
              self.comment = comment[1].strip()
          else:
              self.comment = ''
      
      def __repr__(self):
          return '<insn %r>' % (self.__dict__)
  
  
  def literal_branch_target(t):
      return ' <' in t
  
  class Function:
      def __init__(self, addr, name):
          self.name = name
          self.addr = long(addr, 16)
          self.insns = []
          self.calls = []
  
      def __repr__(self):
          return '<%s %d instructions>' % (self.name, len(self.insns))
  
      def add_insn(self, insn):
          self.insns.append(Instruction(**insn))
  
      def contains_addr(self, addr):
          if self.insns:
              return addr >= self.addr and addr <= self.insns[-1].addr
          else:
              return addr == self.addr
  
      def dump(self):
          print self.name + ':'
          for insn in self.insns:
              print '  ', '%04x' % insn.addr + ':', insn.op, insn.args, '\t;', insn.comment
  
      def get_literal_word(self, addr):
          for insn in self.insns:
              if insn.addr == addr and insn.op == '.word':
                  w = int(insn.args, 16)
                  if w & 0x80000000:
                      w = -(w ^ 0xffffffff) + 1
                  return w
          return None
  
      def analyse(self, prog):
          self.stack_guess = None
          regs = {}
  
          for insn in self.insns:
              # stack adjustment with literal
              if insn.op == 'sub' and insn.args.startswith('sp, ') and self.stack_guess is None:
                  sz = int(insn.args.split('#', 1)[1])
                  self.stack_guess = sz
  
              # literal pool loads
              if insn.op == 'ldr' and ', [pc, #' in insn.args:
                  reg, offset = insn.args.split(', [pc, #')
                  offset = int(offset.replace(']', ''))
                  word = self.get_literal_word(insn.addr + offset + 2)
                  if word is not None:
                      regs[reg] = word
  
              if insn.op == 'add' and insn.args.startswith('sp, r') and self.stack_guess is None:
                  reg = insn.args.split(', ')[1]
                  if reg in regs:
                      self.stack_guess = regs[reg]
  
              # static branches
              if insn.op[0] == 'b' and literal_branch_target(insn.args):
                  target = long(insn.args.split(' <', 1)[0], 16)
  
                  targetf = prog.function_at_addr(target)
  
                  if targetf and targetf != self:
                      self.calls.append(targetf)
  
          if self.stack_guess is None:
              self.stack_guess = 0
  
      def stack_usage(self, hints, warns, prog, depth = 0):
          hinted_calls = []
          if self.stack_guess:
              print '    ' * depth, 'stack:', self.name, self.stack_guess, 'bytes'
  
          our_hints = [h for h in hints if h and h[0] == self.name]
          if our_hints:
              hints = [h[1:] for h in our_hints]
              hinted_calls = [prog.function_by_name(h[0]) for h in hints if h]
          else:
              if self.name in warns:
                  print '    WARN: no calls hints for fn-ptr caller', self.name
  
          if self.calls + hinted_calls:
              call_usage = max([f.stack_usage(hints, warns, prog, depth + 1) for f in self.calls + hinted_calls])
          else:
              call_usage = 0
          return self.stack_guess + call_usage
  
  class Program:
      def __init__(self):
          self.functions = []
  
          # sequence of tuples naming a call sequence known to occur
          # this allows working out calls through pointers
          self.call_hints = []
  
          # function names to warn on if we don't have callees
          self.call_warns = set()
  
      def read_elf(self, elf):
          current_fn = None
  
          for x in subprocess.Popen(['arm-none-eabi-objdump', '-d', elf],
                  stdout = subprocess.PIPE).stdout:
              x = x.rstrip('\n')
              m = function_intro_re.match(x)
              if m:
                  fn = Function(**m.groupdict())
                  current_fn = fn
                  self.functions.append(fn)
  
              m = insn_re.match(x)
              if m:
                  assert current_fn
                  current_fn.add_insn(m.groupdict())
  
      def analyse(self):
          for f in self.functions:
              f.analyse(self)
  
      def function_by_name(self, name):
          fns = [fn for fn in self.functions if fn.name == name]
          if len(fns) == 0:
              return None
          elif len(fns) == 1:
              return fns[0]
          else:
              print 'warn: more than one function named', name
              return None
  
      def function_at_addr(self, addr):
          for f in self.functions:
              if f.addr == addr:
                  return f
          return None
  
      def add_call_hint(self, *seq):
          self.call_hints.append(seq)
  
      def add_call_warn(self, fn):
          self.call_warns.add(fn)
  
      def measure_stack(self, name):
          fn = self.function_by_name(name)
          if fn is None:
              return 0
  
          return fn.stack_usage(self.call_hints, self.call_warns, self)
  
  _, exe, fn = sys.argv
  
  p = Program()
  p.read_elf(exe)
  
  p.analyse()
  
  # calls which indirect through fn ptrs
  p.add_call_warn('cf_blockwise_accumulate')
  p.add_call_warn('cf_blockwise_accumulate_final')
  
  # hints to resolve those
  p.add_call_hint('cf_sha224_update', 'cf_blockwise_accumulate', 'cf_blockwise_accumulate_final', 'sha256_update_block')
  p.add_call_hint('cf_sha256_update', 'cf_blockwise_accumulate', 'cf_blockwise_accumulate_final', 'sha256_update_block')
  p.add_call_hint('cf_sha384_update', 'cf_blockwise_accumulate', 'cf_blockwise_accumulate_final', 'sha512_update_block')
  p.add_call_hint('cf_sha512_update', 'cf_blockwise_accumulate', 'cf_blockwise_accumulate_final', 'sha512_update_block')
  p.add_call_hint('cf_norx32_encrypt', 'input', 'cf_blockwise_accumulate', 'cf_blockwise_accumulate_final', 'input_block')
  p.add_call_hint('cf_norx32_decrypt', 'input', 'cf_blockwise_accumulate', 'cf_blockwise_accumulate_final', 'input_block')
  p.add_call_hint('cf_cbcmac_stream_update', 'cf_blockwise_accumulate', 'cf_blockwise_accumulate_final', 'cbcmac_process')
  p.add_call_hint('cf_cmac_stream_update', 'cf_blockwise_accumulate', 'cf_blockwise_accumulate_final', 'cmac_process_final_pad')
  p.add_call_hint('cf_cmac_stream_update', 'cf_blockwise_accumulate_final', 'cmac_process')
  p.add_call_hint('cf_cmac_stream_update', 'cf_blockwise_accumulate_final', 'cmac_process_final_nopad')
  
  
  print 'stack', fn, '=', p.measure_stack(fn)
  