-
Laura Abbott authored
After a crash, ramdumps may be collected for analysis. The ram parser is a tool to aid in analysis of these dumps. The script extracts useful information from the dumps to help with debugging. Please see the README for full details. Change-Id: I7795686a96bbfe246edf47d09e3f616131688429
9133968f
ramdump.py 44.94 KiB
# Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 and
# only version 2 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
import sys
import re
import os
import struct
import gzip
import functools
from tempfile import NamedTemporaryFile
import gdbmi
from print_out import print_out_str
from mmu import Armv7MMU, Armv7LPAEMMU
FP = 11
SP = 13
LR = 14
PC = 15
THREAD_SIZE = 8192
HARDWARE_ID_IDX = 0
MEMORY_START_IDX = 1
PHYS_OFFSET_IDX = 2
WATCHDOG_BARK_OFFSET_IDX = 3
IMEM_START_IDX = 4
CPU_TYPE = 5
IMEM_FILENAME = 6
VERSION_COMPARE = 7
smem_offsets = [
0, # 8960/9x15 family and earlier
0x0FA00000, # 8974
0x00100000,
0x0D900000, # 8610
0x01100000, # 9635
]
hw_ids = [
(8660, 0x40000000, 0x40200000, 0x2a05f658,
0x2a05f000, 'SCORPION', 'IMEM_C.BIN', None),
(8960, 0x80000000, 0x80200000, 0x2a03f658,
0x2a03f000, 'KRAIT', 'IMEM_C.BIN', None),
(8064, 0x80000000, 0x80200000, 0x2a03f658,
0x2a03f000, 'KRAIT', 'IMEM_C.BIN', None),
(9615, 0x40000000, 0x40800000, 0x0,
0x0, 'CORTEXA5', None, None),
(8974, 0x0, 0x0, 0xfe805658,
0xfe800000, 'KRAIT', 'OCIMEM.BIN', None),
(9625, 0x0, 0x00200000, 0xfc42b658,
0xfc428000, 'CORTEXA5', 'MSGRAM.BIN', 1),
(9625, 0x0, 0x00200000, 0xfe805658,
0xfe800000, 'CORTEXA5', 'OCIMEM.BIN', 2),
(8625, 0x0, 0x00200000, 0x0,
0x0, 'SCORPION', None, None),
(8226, 0x0, 0x00000000, 0xfe805658,
0xfe800000, 'CORTEXA7', 'OCIMEM.BIN', None),
(8610, 0x0, 0x00000000, 0xfe805658,
0xfe800000, 'CORTEXA7', 'OCIMEM.BIN', None),
(8084, 0x0, 0x0, 0xfe805658,
0xfe800000, 'KRAIT', 'OCIMEM.BIN', None),
(9635, 0x0, 0x00000000, 0xfe805658,
0xfe800000, 'CORTEXA7', 'OCIMEM.BIN', None),
(8092, 0x0, 0x0, 0xfe805658,
0xfe800000, 'KRAIT', 'OCIMEM.BIN', None),
]
MSM_CPU_UNKNOWN = 0
MSM_CPU_7X01 = -1
MSM_CPU_7X25 = -1
MSM_CPU_7X27 = -1
MSM_CPU_8X50 = -1
MSM_CPU_8X50A = -1
MSM_CPU_7X30 = -1
MSM_CPU_8X55 = -1
MSM_CPU_8X60 = 8660
MSM_CPU_8960 = 8960
MSM_CPU_8960AB = 8960
MSM_CPU_7X27A = 8625
FSM_CPU_9XXX = -1
MSM_CPU_7X25A = 8625
MSM_CPU_7X25AA = 8625
MSM_CPU_7X25AB = 8625
MSM_CPU_8064 = 8064
MSM_CPU_8064AB = 8064
MSM_CPU_8930 = 8960
MSM_CPU_8930AA = 8960
MSM_CPU_8930AB = 8960
MSM_CPU_7X27AA = -1
MSM_CPU_9615 = 9615
MSM_CPU_8974 = 8974
MSM_CPU_8974PRO_AA = 8974
MSM_CPU_8974PRO_AB = 8974
MSM_CPU_8974PRO_AC = 8974
MSM_CPU_8627 = 8960
MSM_CPU_8625 = 9615
MSM_CPU_9625 = 9625
MSM_CPU_8226 = 8226
MSM_CPU_8610 = 8610
MSM_CPU_8084 = 8084
MSM_CPU_KRYPTON = 9635
MSM_CPU_8092 = 8092
# id, cpu, cpuname
cpu_of_id = [
# 7x01 IDs
(1, MSM_CPU_7X01, 'MSM_CPU_7X01'),
(16, MSM_CPU_7X01, 'MSM_CPU_7X01'),
(17, MSM_CPU_7X01, 'MSM_CPU_7X01'),
(18, MSM_CPU_7X01, 'MSM_CPU_7X01'),
(19, MSM_CPU_7X01, 'MSM_CPU_7X01'),
(23, MSM_CPU_7X01, 'MSM_CPU_7X01'),
(25, MSM_CPU_7X01, 'MSM_CPU_7X01'),
(26, MSM_CPU_7X01, 'MSM_CPU_7X01'),
(32, MSM_CPU_7X01, 'MSM_CPU_7X01'),
(33, MSM_CPU_7X01, 'MSM_CPU_7X01'),
(34, MSM_CPU_7X01, 'MSM_CPU_7X01'),
(35, MSM_CPU_7X01, 'MSM_CPU_7X01'),
# 7x25 IDs
(20, MSM_CPU_7X25, 'MSM_CPU_7X25'),
(21, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7225
(24, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7525
(27, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7625
(39, MSM_CPU_7X25, 'MSM_CPU_7X25'),
(40, MSM_CPU_7X25, 'MSM_CPU_7X25'),
(41, MSM_CPU_7X25, 'MSM_CPU_7X25'),
(42, MSM_CPU_7X25, 'MSM_CPU_7X25'),
(62, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7625-1
(63, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7225-1
(66, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7225-2
# 7x27 IDs
(43, MSM_CPU_7X27, 'MSM_CPU_7X27'),
(44, MSM_CPU_7X27, 'MSM_CPU_7X27'),
(61, MSM_CPU_7X27, 'MSM_CPU_7X27'),
(67, MSM_CPU_7X27, 'MSM_CPU_7X27'), # 7227-1
(68, MSM_CPU_7X27, 'MSM_CPU_7X27'), # 7627-1
(69, MSM_CPU_7X27, 'MSM_CPU_7X27'), # 7627-2
# 8x50 IDs
(30, MSM_CPU_8X50, 'MSM_CPU_8X50'),
(36, MSM_CPU_8X50, 'MSM_CPU_8X50'),
(37, MSM_CPU_8X50, 'MSM_CPU_8X50'),
(38, MSM_CPU_8X50, 'MSM_CPU_8X50'),
# 7x30 IDs
(59, MSM_CPU_7X30, 'MSM_CPU_7X30'),
(60, MSM_CPU_7X30, 'MSM_CPU_7X30'),
# 8x55 IDs
(74, MSM_CPU_8X55, 'MSM_CPU_8X55'),
(75, MSM_CPU_8X55, 'MSM_CPU_8X55'),
(85, MSM_CPU_8X55, 'MSM_CPU_8X55'),
# 8x60 IDs
(70, MSM_CPU_8X60, 'MSM_CPU_8X60'),
(71, MSM_CPU_8X60, 'MSM_CPU_8X60'),
(86, MSM_CPU_8X60, 'MSM_CPU_8X60'),
# 8960 IDs
(87, MSM_CPU_8960, 'MSM_CPU_8960'),
# 7x25A IDs
(88, MSM_CPU_7X25A, 'MSM_CPU_7X25A'),
(89, MSM_CPU_7X25A, 'MSM_CPU_7X25A'),
(96, MSM_CPU_7X25A, 'MSM_CPU_7X25A'),
# 7x27A IDs
(90, MSM_CPU_7X27A, 'MSM_CPU_7X27A'),
(91, MSM_CPU_7X27A, 'MSM_CPU_7X27A'),
(92, MSM_CPU_7X27A, 'MSM_CPU_7X27A'),
(97, MSM_CPU_7X27A, 'MSM_CPU_7X27A'),
# FSM9xxx ID
(94, FSM_CPU_9XXX, 'FSM_CPU_9XXX'),
(95, FSM_CPU_9XXX, 'FSM_CPU_9XXX'),
# 7x25AA ID
(98, MSM_CPU_7X25AA, 'MSM_CPU_7X25AA'),
(99, MSM_CPU_7X25AA, 'MSM_CPU_7X25AA'),
(100, MSM_CPU_7X25AA, 'MSM_CPU_7X25AA'),
# 7x27AA ID
(101, MSM_CPU_7X27AA, 'MSM_CPU_7X27AA'),
(102, MSM_CPU_7X27AA, 'MSM_CPU_7X27AA'),
(103, MSM_CPU_7X27AA, 'MSM_CPU_7X27AA'),
# 9x15 ID
(104, MSM_CPU_9615, 'MSM_CPU_9615'),
(105, MSM_CPU_9615, 'MSM_CPU_9615'),
(106, MSM_CPU_9615, 'MSM_CPU_9615'),
(107, MSM_CPU_9615, 'MSM_CPU_9615'),
# 8064 IDs
(109, MSM_CPU_8064, 'MSM_CPU_8064'),
(130, MSM_CPU_8064, 'MSM_CPU_8064'),
# 8930 IDs
(116, MSM_CPU_8930, 'MSM_CPU_8930'),
(117, MSM_CPU_8930, 'MSM_CPU_8930'),
(118, MSM_CPU_8930, 'MSM_CPU_8930'),
(119, MSM_CPU_8930, 'MSM_CPU_8930'),
# 8627 IDs
(120, MSM_CPU_8627, 'MSM_CPU_8627'),
(121, MSM_CPU_8627, 'MSM_CPU_8627'),
# 8660A ID
(122, MSM_CPU_8960, 'MSM_CPU_8960'),
# 8260A ID
(123, MSM_CPU_8960, '8260A'),
# 8060A ID
(124, MSM_CPU_8960, '8060A'),
# Copper IDs
(126, MSM_CPU_8974, 'MSM_CPU_8974'),
(184, MSM_CPU_8974, 'MSM_CPU_8974'),
(185, MSM_CPU_8974, 'MSM_CPU_8974'),
(186, MSM_CPU_8974, 'MSM_CPU_8974'),
# 8974 PRO AA IDs
(208, MSM_CPU_8974PRO_AA, 'MSM_CPU_8974PRO_AA'),
(211, MSM_CPU_8974PRO_AA, 'MSM_CPU_8974PRO_AA'),
(214, MSM_CPU_8974PRO_AA, 'MSM_CPU_8974PRO_AA'),
(217, MSM_CPU_8974PRO_AA, 'MSM_CPU_8974PRO_AA'),
# 8974 PRO AB IDs
(209, MSM_CPU_8974PRO_AB, 'MSM_CPU_8974PRO_AB'),
(212, MSM_CPU_8974PRO_AB, 'MSM_CPU_8974PRO_AB'),
(215, MSM_CPU_8974PRO_AB, 'MSM_CPU_8974PRO_AB'),
(218, MSM_CPU_8974PRO_AB, 'MSM_CPU_8974PRO_AB'),
# 8974 PRO AC IDs
(194, MSM_CPU_8974PRO_AC, 'MSM_CPU_8974PRO_AC'),
(210, MSM_CPU_8974PRO_AC, 'MSM_CPU_8974PRO_AC'),
(213, MSM_CPU_8974PRO_AC, 'MSM_CPU_8974PRO_AC'),
(216, MSM_CPU_8974PRO_AC, 'MSM_CPU_8974PRO_AC'),
# 8625 IDs
(127, MSM_CPU_8625, 'MSM_CPU_8625'),
(128, MSM_CPU_8625, 'MSM_CPU_8625'),
(129, MSM_CPU_8625, 'MSM_CPU_8625'),
# 8064 MPQ ID */
(130, MSM_CPU_8064, 'MSM_CPU_8064'),
# 7x25AB IDs
(131, MSM_CPU_7X25AB, 'MSM_CPU_7X25AB'),
(132, MSM_CPU_7X25AB, 'MSM_CPU_7X25AB'),
(133, MSM_CPU_7X25AB, 'MSM_CPU_7X25AB'),
(135, MSM_CPU_7X25AB, 'MSM_CPU_7X25AB'),
# 9625 IDs
(134, MSM_CPU_9625, 'MSM_CPU_9625'),
(148, MSM_CPU_9625, 'MSM_CPU_9625'),
(149, MSM_CPU_9625, 'MSM_CPU_9625'),
(150, MSM_CPU_9625, 'MSM_CPU_9625'),
(151, MSM_CPU_9625, 'MSM_CPU_9625'),
(152, MSM_CPU_9625, 'MSM_CPU_9625'),
(173, MSM_CPU_9625, 'MSM_CPU_9625'),
(174, MSM_CPU_9625, 'MSM_CPU_9625'),
(175, MSM_CPU_9625, 'MSM_CPU_9625'),
# 8960AB IDs
(138, MSM_CPU_8960AB, 'MSM_CPU_8960AB'),
(139, MSM_CPU_8960AB, 'MSM_CPU_8960AB'),
(140, MSM_CPU_8960AB, 'MSM_CPU_8960AB'),
(141, MSM_CPU_8960AB, 'MSM_CPU_8960AB'),
# 8930AA IDs
(142, MSM_CPU_8930AA, 'MSM_CPU_8930AA'),
(143, MSM_CPU_8930AA, 'MSM_CPU_8930AA'),
(144, MSM_CPU_8930AA, 'MSM_CPU_8930AA'),
# 8226 IDx
(145, MSM_CPU_8226, 'MSM_CPU_8226'),
(158, MSM_CPU_8226, 'MSM_CPU_8226'),
(159, MSM_CPU_8226, 'MSM_CPU_8226'),
(198, MSM_CPU_8226, 'MSM_CPU_8226'),
(199, MSM_CPU_8226, 'MSM_CPU_8226'),
(200, MSM_CPU_8226, 'MSM_CPU_8226'),
(205, MSM_CPU_8226, 'MSM_CPU_8226'),
(219, MSM_CPU_8226, 'MSM_CPU_8226'),
(220, MSM_CPU_8226, 'MSM_CPU_8226'),
(221, MSM_CPU_8226, 'MSM_CPU_8226'),
(222, MSM_CPU_8226, 'MSM_CPU_8226'),
(223, MSM_CPU_8226, 'MSM_CPU_8226'),
(224, MSM_CPU_8226, 'MSM_CPU_8226'),
# 8610 IDx
(147, MSM_CPU_8610, 'MSM_CPU_8610'),
(161, MSM_CPU_8610, 'MSM_CPU_8610'),
(162, MSM_CPU_8610, 'MSM_CPU_8610'),
(163, MSM_CPU_8610, 'MSM_CPU_8610'),
(164, MSM_CPU_8610, 'MSM_CPU_8610'),
(165, MSM_CPU_8610, 'MSM_CPU_8610'),
(166, MSM_CPU_8610, 'MSM_CPU_8610'),
# 8064AB IDs
(153, MSM_CPU_8064AB, 'MSM_CPU_8064AB'),
# 8930AB IDs
(154, MSM_CPU_8930AB, 'MSM_CPU_8930AB'),
(155, MSM_CPU_8930AB, 'MSM_CPU_8930AB'),
(156, MSM_CPU_8930AB, 'MSM_CPU_8930AB'),
(157, MSM_CPU_8930AB, 'MSM_CPU_8930AB'),
(160, MSM_CPU_8930AA, 'MSM_CPU_8930AA'),
# 8084 IDs
(178, MSM_CPU_8084, 'MSM_CPU_8084'),
# 9635 IDs
(187, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'),
(227, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'),
(228, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'),
(229, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'),
(230, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'),
(231, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'),
(146, MSM_CPU_8092, 'MSM_CPU_8092'),
# Uninitialized IDs are not known to run Linux.
# MSM_CPU_UNKNOWN is set to 0 to ensure these IDs are
# considered as unknown CPU.
]
socinfo_v1 = functools.reduce(lambda x, y: x + y, [
'I', # format
'I', # id
'I', # version
])
launch_config_str = 'OS=\nID=T32_1000002\nTMP=C:\\TEMP\nSYS=C:\\T32\nHELP=C:\\T32\\pdf\n\nPBI=SIM\nSCREEN=\nFONT=SMALL\nHEADER=Trace32-ScorpionSimulator\nPRINTER=WINDOWS'
# The smem code is very stable and unlikely to go away or be changed.
# Rather than go through the hassel of parsing the id through gdb,
# just hard code it
SMEM_HW_SW_BUILD_ID = 0x89
BUILD_ID_LENGTH = 32
first_mem_file_names = ['EBICS0.BIN',
'EBI1.BIN', 'DDRCS0.BIN', 'ebi1_cs0.bin', 'DDRCS0_0.BIN']
extra_mem_file_names = ['EBI1CS1.BIN', 'DDRCS1.BIN', 'ebi1_cs1.bin', 'DDRCS0_1.BIN']
class RamDump():
class Unwinder ():
class Stackframe ():
def __init__(self, fp, sp, lr, pc):
self.fp = fp
self.sp = sp
self.lr = lr
self.pc = pc
class UnwindCtrlBlock ():
def __init__(self):
self.vrs = 16 * [0]
self.insn = 0
self.entries = -1
self.byte = -1
self.index = 0
def __init__(self, ramdump):
start = ramdump.addr_lookup('__start_unwind_idx')
end = ramdump.addr_lookup('__stop_unwind_idx')
if (start is None) or (end is None):
print_out_str('!!! Could not lookup unwinding information')
return None
# addresses
self.start_idx = start
self.stop_idx = end
self.unwind_table = []
self.ramdump = ramdump
i = 0
for addr in range(start, end, 8):
(a, b) = ramdump.read_string(addr, '<II')
self.unwind_table.append((a, b, start + 8 * i))
i += 1
ver = ramdump.version
if re.search('3.0.\d', ver) is not None:
self.search_idx = self.search_idx_3_0
else:
self.search_idx = self.search_idx_3_4
# index into the table
self.origin = self.unwind_find_origin()
def unwind_find_origin(self):
start = 0
stop = len(self.unwind_table)
while (start < stop):
mid = start + ((stop - start) >> 1)
if (self.unwind_table[mid][0] >= 0x40000000):
start = mid + 1
else:
stop = mid
return stop
def unwind_frame_generic(self, frame):
high = 0
fp = frame.fp
low = frame.sp
mask = (THREAD_SIZE) - 1
high = (low + mask) & (~mask) # ALIGN(low, THREAD_SIZE)
# /* check current frame pointer is within bounds */
if (fp < (low + 12) or fp + 4 >= high):
return -1
fp_is_at = self.ramdump.read_word(frame.fp - 12)
sp_is_at = self.ramdump.read_word(frame.fp - 8)
pc_is_at = self.ramdump.read_word(frame.fp - 4)
frame.fp = fp_is_at
frame.sp = sp_is_at
frame.pc = pc_is_at
return 0
def walk_stackframe_generic(self, frame):
while True:
symname = self.ramdump.addr_to_symbol(frame.pc)
print_out_str(symname)
ret = self.unwind_frame_generic(frame)
if ret < 0:
break
def unwind_backtrace_generic(self, sp, fp, pc):
frame = self.Stackframe()
frame.fp = fp
frame.pc = pc
frame.sp = sp
walk_stackframe_generic(frame)
def search_idx_3_4(self, addr):
start = 0
stop = len(self.unwind_table)
orig = addr
if (addr < self.start_idx):
stop = self.origin
else:
start = self.origin
if (start >= stop):
return None
addr = (addr - self.unwind_table[start][2]) & 0x7fffffff
while (start < (stop - 1)):
mid = start + ((stop - start) >> 1)
dif = (self.unwind_table[mid][2]
- self.unwind_table[start][2])
if ((addr - dif) < self.unwind_table[mid][0]):
stop = mid
else:
addr = addr - dif
start = mid
if self.unwind_table[start][0] <= addr:
return self.unwind_table[start]
else:
return None
def search_idx_3_0(self, addr):
first = 0
last = len(self.unwind_table)
while (first < last - 1):
mid = first + ((last - first + 1) >> 1)
if (addr < self.unwind_table[mid][0]):
last = mid
else:
first = mid
return self.unwind_table[first]
def unwind_get_byte(self, ctrl):
if (ctrl.entries <= 0):
print_out_str('unwind: Corrupt unwind table')
return 0
val = self.ramdump.read_word(ctrl.insn)
ret = (val >> (ctrl.byte * 8)) & 0xff
if (ctrl.byte == 0):
ctrl.insn += 4
ctrl.entries -= 1
ctrl.byte = 3
else:
ctrl.byte -= 1
return ret
def unwind_exec_insn(self, ctrl, trace=False):
insn = self.unwind_get_byte(ctrl)
if ((insn & 0xc0) == 0x00):
ctrl.vrs[SP] += ((insn & 0x3f) << 2) + 4
if trace:
print_out_str(
' add {0} to stack'.format(((insn & 0x3f) << 2) + 4))
elif ((insn & 0xc0) == 0x40):
ctrl.vrs[SP] -= ((insn & 0x3f) << 2) + 4
if trace:
print_out_str(
' subtract {0} from stack'.format(((insn & 0x3f) << 2) + 4))
elif ((insn & 0xf0) == 0x80):
vsp = ctrl.vrs[SP]
reg = 4
insn = (insn << 8) | self.unwind_get_byte(ctrl)
mask = insn & 0x0fff
if (mask == 0):
print_out_str("unwind: 'Refuse to unwind' instruction")
return -1
# pop R4-R15 according to mask */
load_sp = mask & (1 << (13 - 4))
while (mask):
if (mask & 1):
ctrl.vrs[reg] = self.ramdump.read_word(vsp)
if trace:
print_out_str(
' pop r{0} from stack'.format(reg))
if ctrl.vrs[reg] is None:
return -1
vsp += 4
mask >>= 1
reg += 1
if not load_sp:
ctrl.vrs[SP] = vsp
elif ((insn & 0xf0) == 0x90 and (insn & 0x0d) != 0x0d):
if trace:
print_out_str(
' set SP with the value from {0}'.format(insn & 0x0f))
ctrl.vrs[SP] = ctrl.vrs[insn & 0x0f]
elif ((insn & 0xf0) == 0xa0):
vsp = ctrl.vrs[SP]
a = list(range(4, 4 + (insn & 7)))
a.append(4 + (insn & 7))
# pop R4-R[4+bbb] */
for reg in (a):
ctrl.vrs[reg] = self.ramdump.read_word(vsp)
if trace:
print_out_str(' pop r{0} from stack'.format(reg))
if ctrl.vrs[reg] is None:
return -1
vsp += 4
if (insn & 0x80):
if trace:
print_out_str(' set LR from the stack')
ctrl.vrs[14] = self.ramdump.read_word(vsp)
if ctrl.vrs[14] is None:
return -1
vsp += 4
ctrl.vrs[SP] = vsp
elif (insn == 0xb0):
if trace:
print_out_str(' set pc = lr')
if (ctrl.vrs[PC] == 0):
ctrl.vrs[PC] = ctrl.vrs[LR]
ctrl.entries = 0
elif (insn == 0xb1):
mask = self.unwind_get_byte(ctrl)
vsp = ctrl.vrs[SP]
reg = 0
if (mask == 0 or mask & 0xf0):
print_out_str('unwind: Spare encoding')
return -1
# pop R0-R3 according to mask
while mask:
if (mask & 1):
ctrl.vrs[reg] = self.ramdump.read_word(vsp)
if trace:
print_out_str(
' pop r{0} from stack'.format(reg))
if ctrl.vrs[reg] is None:
return -1
vsp += 4
mask >>= 1
reg += 1
ctrl.vrs[SP] = vsp
elif (insn == 0xb2):
uleb128 = self.unwind_get_byte(ctrl)
if trace:
print_out_str(
' Adjust sp by {0}'.format(0x204 + (uleb128 << 2)))
ctrl.vrs[SP] += 0x204 + (uleb128 << 2)
else:
print_out_str('unwind: Unhandled instruction')
return -1
return 0
def prel31_to_addr(self, addr):
value = self.ramdump.read_word(addr)
# offset = (value << 1) >> 1
# C wants this sign extended. Python doesn't do that.
# Sign extend manually.
if (value & 0x40000000):
offset = value | 0x80000000
else:
offset = value
# This addition relies on integer overflow
# Emulate this behavior
temp = addr + offset
return (temp & 0xffffffff) + ((temp >> 32) & 0xffffffff)
def unwind_frame(self, frame, trace=False):
low = frame.sp
high = ((low + (THREAD_SIZE - 1)) & ~(THREAD_SIZE - 1)) + \
THREAD_SIZE
idx = self.search_idx(frame.pc)
if (idx is None):
if trace:
print_out_str("can't find %x" % frame.pc)
return -1
ctrl = self.UnwindCtrlBlock()
ctrl.vrs[FP] = frame.fp
ctrl.vrs[SP] = frame.sp
ctrl.vrs[LR] = frame.lr
ctrl.vrs[PC] = 0
if (idx[1] == 1):
return -1
elif ((idx[1] & 0x80000000) == 0):
ctrl.insn = self.prel31_to_addr(idx[2] + 4)
elif (idx[1] & 0xff000000) == 0x80000000:
ctrl.insn = idx[2] + 4
else:
print_out_str('not supported')
return -1
val = self.ramdump.read_word(ctrl.insn)
if ((val & 0xff000000) == 0x80000000):
ctrl.byte = 2
ctrl.entries = 1
elif ((val & 0xff000000) == 0x81000000):
ctrl.byte = 1
ctrl.entries = 1 + ((val & 0x00ff0000) >> 16)
else:
return -1
while (ctrl.entries > 0):
urc = self.unwind_exec_insn(ctrl, trace)
if (urc < 0):
return urc
if (ctrl.vrs[SP] < low or ctrl.vrs[SP] >= high):
return -1
if (ctrl.vrs[PC] == 0):
ctrl.vrs[PC] = ctrl.vrs[LR]
# check for infinite loop */
if (frame.pc == ctrl.vrs[PC]):
return -1
frame.fp = ctrl.vrs[FP]
frame.sp = ctrl.vrs[SP]
frame.lr = ctrl.vrs[LR]
frame.pc = ctrl.vrs[PC]
return 0
def unwind_backtrace(self, sp, fp, pc, lr, extra_str='', out_file=None, trace=False):
offset = 0
frame = self.Stackframe(fp, sp, lr, pc)
frame.fp = fp
frame.sp = sp
frame.lr = lr
frame.pc = pc
while True:
where = frame.pc
offset = 0
r = self.ramdump.unwind_lookup(frame.pc)
if r is None:
symname = 'UNKNOWN'
offset = 0x0
else:
symname, offset = r
pstring = (
extra_str + '[<{0:x}>] {1}+0x{2:x}'.format(frame.pc, symname, offset))
if out_file:
out_file.write(pstring + '\n')
else:
print_out_str(pstring)
urc = self.unwind_frame(frame, trace)
if urc < 0:
break
def __init__(self, vmlinux_path, nm_path, gdb_path, ebi, file_path, phys_offset, outdir, hw_id=None, hw_version=None):
self.ebi_files = []
self.phys_offset = None
self.tz_start = 0
self.ebi_start = 0
self.cpu_type = None
self.hw_id = hw_id
self.hw_version = hw_version
self.offset_table = []
self.vmlinux = vmlinux_path
self.nm_path = nm_path
self.gdb_path = gdb_path
self.outdir = outdir
self.imem_fname = None
self.gdbmi = gdbmi.GdbMI(self.gdb_path, self.vmlinux)
self.gdbmi.open()
if ebi is not None:
# TODO sanity check to make sure the memory regions don't overlap
for file_path, start, end in ebi:
fd = open(file_path, 'rb')
if not fd:
print_out_str(
'Could not open {0}. Will not be part of dump'.format(file_path))
continue
self.ebi_files.append((fd, start, end, file_path))
else:
if not self.auto_parse(file_path):
return None
if self.ebi_start == 0:
self.ebi_start = self.ebi_files[0][1]
if self.phys_offset is None:
self.get_hw_id()
if phys_offset is not None:
print_out_str(
'[!!!] Phys offset was set to {0:x}'.format(phys_offset))
self.phys_offset = phys_offset
self.lookup_table = []
self.page_offset = 0xc0000000
self.config = []
self.setup_symbol_tables()
# The address of swapper_pg_dir can be used to determine
# whether or not we're running with LPAE enabled since an
# extra 4k is needed for LPAE. If it's 0x5000 below
# PAGE_OFFSET + TEXT_OFFSET then we know we're using LPAE. For
# non-LPAE it should be 0x4000 below PAGE_OFFSET + TEXT_OFFSET
swapper_pg_dir_addr = self.addr_lookup('swapper_pg_dir')
kernel_text_offset = 0x8000
pg_dir_size = kernel_text_offset - \
(swapper_pg_dir_addr - self.page_offset)
if pg_dir_size == 0x4000:
print_out_str('Using non-LPAE MMU')
self.mmu = Armv7MMU(self)
elif pg_dir_size == 0x5000:
print_out_str('Using LPAE MMU')
self.mmu = Armv7LPAEMMU(self)
else:
print_out_str(
"!!! Couldn't determine whether or not we're using LPAE!")
print_out_str(
'!!! This is a BUG in the parser and should be reported.')
sys.exit(1)
if not self.get_version():
print_out_str('!!! Could not get the Linux version!')
print_out_str(
'!!! Your vmlinux is probably wrong for these dumps')
print_out_str('!!! Exiting now')
sys.exit(1)
if not self.get_config():
print_out_str('!!! Could not get saved configuration')
print_out_str(
'!!! This is really bad and probably indicates RAM corruption')
print_out_str('!!! Some features may be disabled!')
self.unwind = self.Unwinder(self)
def __del__(self):
self.gdbmi.close()
def open_file(self, file_name, mode='wb'):
file_path = os.path.join(self.outdir, file_name)
f = None
try:
f = open(file_path, mode)
except:
print_out_str('Could not open path {0}'.format(file_path))
print_out_str('Do you have write/read permissions on the path?')
sys.exit(1)
return f
def get_config(self):
kconfig_addr = self.addr_lookup('kernel_config_data')
if kconfig_addr is None:
return
kconfig_size = self.sizeof('kernel_config_data')
# size includes magic, offset from it
kconfig_size = kconfig_size - 16 - 1
zconfig = NamedTemporaryFile(mode='wb', delete=False)
# kconfig data starts with magic 8 byte string, go past that
s = self.read_cstring(kconfig_addr, 8)
if s != 'IKCFG_ST':
return
kconfig_addr = kconfig_addr + 8
for i in range(0, kconfig_size):
val = self.read_byte(kconfig_addr + i)
zconfig.write(struct.pack('<B', val))
zconfig.close()
zconfig_in = gzip.open(zconfig.name, 'rb')
try:
t = zconfig_in.readlines()
except:
return False
zconfig_in.close()
os.remove(zconfig.name)
for l in t:
self.config.append(l.rstrip().decode('ascii', 'ignore'))
return True
def is_config_defined(self, config):
s = config + '=y'
return s in self.config
def get_version(self):
banner_addr = self.addr_lookup('linux_banner')
if banner_addr is not None:
# Don't try virt to phys yet, compute manually
banner_addr = banner_addr - 0xc0000000 + self.phys_offset
b = self.read_cstring(banner_addr, 256, False)
if b is None:
print_out_str('!!! Could not read banner address!')
return False
v = re.search('Linux version (\d{0,2}\.\d{0,2}\.\d{0,2})', b)
if v is None:
print_out_str('!!! Could not match version! {0}'.format(b))
return False
self.version = v.group(1)
print_out_str('Linux Banner: ' + b.rstrip())
print_out_str('version = {0}'.format(self.version))
return True
else:
print_out_str('!!! Could not lookup banner address')
return False
def print_command_line(self):
command_addr = self.addr_lookup('saved_command_line')
if command_addr is not None:
command_addr = self.read_word(command_addr)
b = self.read_cstring(command_addr, 2048)
if b is None:
print_out_str('!!! could not read saved command line address')
return False
print_out_str('Command Line: ' + b)
return True
else:
print_out_str('!!! Could not lookup saved command line address')
return False
def auto_parse(self, file_path):
first_mem_path = None
for f in first_mem_file_names:
test_path = file_path + '/' + f
if os.path.exists(test_path):
first_mem_path = test_path
break
if first_mem_path is None:
print_out_str('!!! Could not open a memory file. I give up')
sys.exit(1)
first_mem = open(first_mem_path, 'rb')
# put some dummy data in for now
self.ebi_files = [(first_mem, 0, 0xffff0000, first_mem_path)]
if not self.get_hw_id():
return False
first_mem_end = self.ebi_start + os.path.getsize(first_mem_path) - 1
self.ebi_files = [
(first_mem, self.ebi_start, first_mem_end, first_mem_path)]
print_out_str(
'Adding {0} {1:x}--{2:x}'.format(first_mem_path, self.ebi_start, first_mem_end))
for f in extra_mem_file_names:
extra_path = file_path + '/' + f
if os.path.exists(extra_path):
extra = open(extra_path, 'rb')
extra_start = self.ebi_start + os.path.getsize(first_mem_path)
extra_end = extra_start + os.path.getsize(extra_path) - 1
print_out_str(
'Adding {0} {1:x}--{2:x}'.format(extra_path, extra_start, extra_end))
self.ebi_files.append(
(extra, extra_start, extra_end, extra_path))
if self.imem_fname is not None:
imemc_path = file_path + '/' + self.imem_fname
if os.path.exists(imemc_path):
imemc = open(imemc_path, 'rb')
imemc_start = self.tz_start
imemc_end = imemc_start + os.path.getsize(imemc_path) - 1
print_out_str(
'Adding {0} {1:x}--{2:x}'.format(imemc_path, imemc_start, imemc_end))
self.ebi_files.append(
(imemc, imemc_start, imemc_end, imemc_path))
return True
# TODO support linux launcher, for when linux T32 actually happens
def create_t32_launcher(self):
out_path = self.outdir
launch_config = open(out_path + '/t32_config.t32', 'wb')
launch_config.write(launch_config_str.encode('ascii', 'ignore'))
launch_config.close()
startup_script = open(out_path + '/t32_startup_script.cmm', 'wb')
startup_script.write(
'sys.cpu {0}\n'.format(self.cpu_type).encode('ascii', 'ignore'))
startup_script.write('sys.up\n'.encode('ascii', 'ignore'))
for ram in self.ebi_files:
ebi_path = os.path.abspath(ram[3])
startup_script.write('data.load.binary {0} 0x{1:x}\n'.format(
ebi_path, ram[1]).encode('ascii', 'ignore'))
startup_script.write(
'PER.S.F C15:0x2 %L 0x{0:x}\n'.format(self.mmu.ttbr).encode('ascii', 'ignore'))
if isinstance(self.mmu, Armv7LPAEMMU):
# TTBR1. This gets setup once and never change again even if TTBR0
# changes
startup_script.write('PER.S.F C15:0x102 %L 0x{0:x}\n'.format(
self.mmu.ttbr + 0x4000).encode('ascii', 'ignore'))
# TTBCR with EAE and T1SZ set approprately
startup_script.write(
'PER.S.F C15:0x202 %L 0x80030000\n'.encode('ascii', 'ignore'))
startup_script.write('mmu.on\n'.encode('ascii', 'ignore'))
startup_script.write('mmu.scan\n'.encode('ascii', 'ignore'))
startup_script.write(
('data.load.elf ' + os.path.abspath(self.vmlinux) + ' /nocode\n').encode('ascii', 'ignore'))
startup_script.write(
'task.config c:\\t32\\demo\\arm\\kernel\\linux\\linux.t32\n'.encode('ascii', 'ignore'))
startup_script.write(
'menu.reprogram c:\\t32\\demo\\arm\\kernel\\linux\\linux.men\n'.encode('ascii', 'ignore'))
startup_script.write('task.dtask\n'.encode('ascii', 'ignore'))
startup_script.write(
'v.v %ASCII %STRING linux_banner\n'.encode('ascii', 'ignore'))
if os.path.exists(out_path + '/regs_panic.cmm'):
startup_script.write(
'do {0}\n'.format(out_path + '/regs_panic.cmm').encode('ascii', 'ignore'))
elif os.path.exists(out_path + '/core0_regs.cmm'):
startup_script.write(
'do {0}\n'.format(out_path + '/core0_regs.cmm').encode('ascii', 'ignore'))
startup_script.close()
t32_bat = open(out_path + '/launch_t32.bat', 'wb')
t32_bat.write(('start c:\\t32\\t32MARM.exe -c ' + out_path + '/t32_config.t32, ' +
out_path + '/t32_startup_script.cmm').encode('ascii', 'ignore'))
t32_bat.close()
print_out_str(
'--- Created a T32 Simulator launcher (run {0}/launch_t32.bat)'.format(out_path))
def read_tz_offset(self):
if self.tz_addr == 0:
print_out_str(
'No TZ address was given, cannot read the magic value!')
return None
else:
return self.read_word(self.tz_addr, False)
def find_hw_id(self, socinfo_id, version):
if self.hw_version is not None:
version = self.hw_version
for cpuid in cpu_of_id:
if socinfo_id == cpuid[0]:
for hwid in hw_ids:
if cpuid[1] == hwid[HARDWARE_ID_IDX]:
if hwid[VERSION_COMPARE] is not None and hwid[VERSION_COMPARE] != version:
continue
return hwid
return None
def get_hw_id(self):
heap_toc_offset = self.field_offset('struct smem_shared', 'heap_toc')
if heap_toc_offset is None:
print_out_str(
'!!!! Could not get a necessary offset for auto detection!')
print_out_str(
'!!!! Please check the gdb path which is used for offsets!')
print_out_str('!!!! Also check that the vmlinux is not stripped')
print_out_str('!!!! Exiting...')
sys.exit(1)
smem_heap_entry_size = self.sizeof('struct smem_heap_entry')
offset_offset = self.field_offset('struct smem_heap_entry', 'offset')
socinfo_format = -1
socinfo_id = -1
socinfo_version = 0
socinfo_build_id = 'DUMMY'
hwid = None
if (self.hw_id is None):
for smem_offset in smem_offsets:
socinfo_start_addr = self.ebi_files[0][
1] + smem_offset + heap_toc_offset + smem_heap_entry_size * SMEM_HW_SW_BUILD_ID + offset_offset
soc_start = self.read_word(socinfo_start_addr, False)
if soc_start is None:
continue
socinfo_start = self.ebi_files[0][1] + smem_offset + soc_start
socinfo_format = self.read_word(socinfo_start, False)
socinfo_id = self.read_word(socinfo_start + 4, False)
socinfo_version = self.read_word(socinfo_start + 8, False)
socinfo_build_id = self.read_cstring(
socinfo_start + 12, BUILD_ID_LENGTH, False)
if socinfo_id is not None and socinfo_version is not None:
hwid = self.find_hw_id(socinfo_id, socinfo_version >> 16)
if (hwid is not None):
break
if (hwid is None):
print_out_str('!!!! Could not find hardware')
print_out_str("!!!! The SMEM didn't match anything")
print_out_str(
'!!!! You can use --force-hardware to use a specific set of values')
sys.exit(1)
else:
hwid = None
for a in hw_ids:
if self.hw_id == a[HARDWARE_ID_IDX] and self.hw_version == a[VERSION_COMPARE]:
print_out_str(
'!!! Hardware id found! The socinfo values given are bogus')
print_out_str('!!! Proceed with caution!')
hwid = a
break
if hwid is None:
print_out_str(
'!!! A bogus hardware id was specified: {0}'.format(self.hw_id))
print_out_str(
'!!! Try passing one of these to --force-hardware.')
print_out_str(
'!!! If a version is specified, pass the version with --force-version')
for a in hw_ids:
if a[VERSION_COMPARE] is not None:
v = 'v{0}'.format(a[VERSION_COMPARE])
else:
v = ''
print_out_str(
'!!! {0}{1}'.format(a[HARDWARE_ID_IDX], v))
sys.exit(1)
print_out_str('\nHardware match: {0}'.format(hwid[HARDWARE_ID_IDX]))
print_out_str('Socinfo id = {0}, version {1:x}.{2:x}'.format(
socinfo_id, socinfo_version >> 16, socinfo_version & 0xFFFF))
print_out_str('Socinfo build = {0}'.format(socinfo_build_id))
print_out_str(
'Now setting phys_offset to {0:x}'.format(hwid[PHYS_OFFSET_IDX]))
print_out_str(
'TZ address: {0:x}'.format(hwid[WATCHDOG_BARK_OFFSET_IDX]))
self.phys_offset = hwid[PHYS_OFFSET_IDX]
self.tz_addr = hwid[WATCHDOG_BARK_OFFSET_IDX]
self.ebi_start = hwid[MEMORY_START_IDX]
self.tz_start = hwid[IMEM_START_IDX]
self.hw_id = hwid[HARDWARE_ID_IDX]
self.cpu_type = hwid[CPU_TYPE]
self.imem_fname = hwid[IMEM_FILENAME]
return True
def virt_to_phys(self, virt):
return self.mmu.virt_to_phys(virt)
def setup_symbol_tables(self):
stream = os.popen(self.nm_path + ' -n ' + self.vmlinux)
symbols = stream.readlines()
for line in symbols:
s = line.split(' ')
if len(s) == 3:
self.lookup_table.append((int(s[0], 16), s[2].rstrip()))
stream.close()
def addr_lookup(self, symbol):
try:
return self.gdbmi.address_of(symbol)
except gdbmi.GdbMIException:
pass
def symbol_lookup(self, addr):
try:
return self.gdbmi.symbol_at(addr).symbol
except gdbmi.GdbMIException:
pass
def sizeof(self, the_type):
try:
return self.gdbmi.sizeof(the_type)
except gdbmi.GdbMIException:
pass
def array_index(self, addr, the_type, index):
"""Index into the array of type `the_type' located at `addr'.
I.e.:
Given:
int my_arr[3];
my_arr[2] = 42;
The following:
my_arr_addr = dump.addr_lookup("my_arr")
dump.read_word(dump.array_index(my_arr_addr, "int", 2))
will return 42.
"""
offset = self.gdbmi.sizeof(the_type) * index
return addr + offset
def field_offset(self, the_type, field):
try:
return self.gdbmi.field_offset(the_type, field)
except gdbmi.GdbMIException:
pass
def unwind_lookup(self, addr, symbol_size=0):
if (addr is None):
return ('(Invalid address)', 0x0)
# modules are not supported so just print out an address
# instead of a confusing symbol
if (addr < self.page_offset):
return ('(No symbol for address {0:x})'.format(addr), 0x0)
low = 0
high = len(self.lookup_table)
# Python now complains about division producing floats
mid = (low + high) >> 1
premid = 0
while(not(addr >= self.lookup_table[mid][0] and addr < self.lookup_table[mid + 1][0])):
if(addr < self.lookup_table[mid][0]):
high = mid - 1
if(addr > self.lookup_table[mid][0]):
low = mid + 1
mid = (high + low) >> 1
if(mid == premid):
return None
if (mid + 1) >= len(self.lookup_table) or mid < 0:
return None
premid = mid
if symbol_size == 0:
return (self.lookup_table[mid][1], addr - self.lookup_table[mid][0])
else:
return (self.lookup_table[mid][1], self.lookup_table[mid + 1][0] - self.lookup_table[mid][0])
def read_physical(self, addr, length, trace=False):
ebi = (-1, -1, -1)
for a in self.ebi_files:
fd, start, end, path = a
if addr >= start and addr <= end:
ebi = a
break
if ebi[0] is -1:
return None
if trace:
print_out_str('reading from {0}'.format(ebi[0]))
print_out_str('start = {0:x}'.format(ebi[1]))
print_out_str('end = {0:x}'.format(ebi[2]))
print_out_str('length = {0:x}'.format(length))
offset = addr - ebi[1]
if trace:
print_out_str('offset = {0:x}'.format(offset))
ebi[0].seek(offset)
a = ebi[0].read(length)
if trace:
print_out_str('result = {0}'.format(a))
print_out_str('lenght = {0}'.format(len(a)))
return a
def read_dword(self, address, virtual=True, trace=False, cpu=None):
if trace:
print_out_str('reading {0:x}'.format(address))
s = self.read_string(address, '<Q', virtual, trace, cpu)
if s is None:
return None
else:
return s[0]
# returns the 4 bytes read from the specified virtual address
# return None on error
def read_word(self, address, virtual=True, trace=False, cpu=None):
if trace:
print_out_str('reading {0:x}'.format(address))
s = self.read_string(address, '<I', virtual, trace, cpu)
if s is None:
return None
else:
return s[0]
def read_halfword(self, address, virtual=True, trace=False, cpu=None):
if trace:
print_out_str('reading {0:x}'.format(address))
s = self.read_string(address, '<H', virtual, trace, cpu)
if s is None:
return None
else:
return s[0]
def read_byte(self, address, virtual=True, trace=False, cpu=None):
if trace:
print_out_str('reading {0:x}'.format(address))
s = self.read_string(address, '<B', virtual, trace, cpu)
if s is None:
return None
else:
return s[0]
def read_cstring(self, address, max_length, virtual=True, cpu=None):
addr = address
if virtual:
if cpu is not None:
address += pcpu_offset + self.per_cpu_offset(cpu)
addr = self.virt_to_phys(address)
s = self.read_physical(addr, max_length)
if s is not None:
a = s.decode('ascii', 'ignore')
return a.split('\0')[0]
else:
return s
# returns a tuple of the result from reading from the specified fromat string
# return None on failure
def read_string(self, address, format_string, virtual=True, trace=False, cpu=None):
addr = address
per_cpu_string = ''
if virtual:
if cpu is not None:
pcpu_offset = self.per_cpu_offset(cpu)
address += pcpu_offset
per_cpu_string = ' with per-cpu offset of ' + hex(pcpu_offset)
addr = self.virt_to_phys(address)
if trace:
print_out_str('reading from phys {0:x}{1}'.format(addr,
per_cpu_string))
s = self.read_physical(addr, struct.calcsize(format_string), trace)
if (s is None) or (s == ''):
if trace:
print_out_str(
'address {0:x} failed hard core (v {1} t{2})'.format(addr, virtual, trace))
return None
return struct.unpack(format_string, s)
def per_cpu_offset(self, cpu):
per_cpu_offset_addr = self.addr_lookup('__per_cpu_offset')
if per_cpu_offset_addr is None:
return 0
per_cpu_offset_addr_indexed = self.array_index(
per_cpu_offset_addr, 'unsigned long', cpu)
return self.read_word(per_cpu_offset_addr_indexed)
def get_num_cpus(self):
cpu_present_bits_addr = self.addr_lookup('cpu_present_bits')
cpu_present_bits = self.read_word(cpu_present_bits_addr)
return bin(cpu_present_bits).count('1')
def iter_cpus(self):
return xrange(self.get_num_cpus())