minidumpserver:support thread awareness
If we have a full memory raw dump, we can parse g_tcbinfo, g_npidhash, and g_pidhash from the ELF to get NuttX thread info, regardless of the crash dump log file. support new command in gdb: info threads thread id Signed-off-by: anjiahao <anjiahao@xiaomi.com>
This commit is contained in:
parent
65ef1f72c1
commit
f52a3d314e
@ -41,6 +41,9 @@ SHF_ALLOC_EXEC = SHF_ALLOC | SHF_EXEC
|
|||||||
|
|
||||||
GDB_SIGNAL_DEFAULT = 7
|
GDB_SIGNAL_DEFAULT = 7
|
||||||
|
|
||||||
|
UINT16_MAX = 65535
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_GDB_INIT_CMD = "-ex 'bt full' -ex 'info reg' -ex 'display /40i $pc-40'"
|
DEFAULT_GDB_INIT_CMD = "-ex 'bt full' -ex 'info reg' -ex 'display /40i $pc-40'"
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
@ -307,7 +310,20 @@ class DumpELFFile:
|
|||||||
if segment.header.p_flags & 1 and not self.__text:
|
if segment.header.p_flags & 1 and not self.__text:
|
||||||
self.__text = segment.header.p_vaddr
|
self.__text = segment.header.p_vaddr
|
||||||
|
|
||||||
|
symtab = elf.get_section_by_name(".symtab")
|
||||||
|
self.symbol = {}
|
||||||
|
for symbol in symtab.iter_symbols():
|
||||||
|
if symbol["st_info"]["type"] != "STT_OBJECT":
|
||||||
|
continue
|
||||||
|
|
||||||
|
if symbol.name in ("g_tcbinfo", "g_pidhash", "g_npidhash"):
|
||||||
|
self.symbol[symbol.name] = symbol
|
||||||
|
logger.debug(
|
||||||
|
f"name:{symbol.name} size:{symbol['st_size']} value:{hex(symbol['st_value'])}"
|
||||||
|
)
|
||||||
|
|
||||||
elf.close()
|
elf.close()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def merge(self, other):
|
def merge(self, other):
|
||||||
@ -454,19 +470,25 @@ class GDBStub:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, logfile: DumpLogFile, elffile: DumpELFFile, rawfile: RawMemoryFile
|
self, logfile: DumpLogFile, elffile: DumpELFFile, rawfile: RawMemoryFile
|
||||||
):
|
):
|
||||||
self.logfile = logfile
|
self.registers = logfile.registers
|
||||||
self.elffile = elffile
|
self.elffile = elffile
|
||||||
self.socket = None
|
self.socket = None
|
||||||
self.gdb_signal = GDB_SIGNAL_DEFAULT
|
self.gdb_signal = GDB_SIGNAL_DEFAULT
|
||||||
self.mem_regions = (
|
self.mem_regions = (
|
||||||
self.elffile.get_memories()
|
self.elffile.get_memories()
|
||||||
+ self.logfile.get_memories()
|
+ logfile.get_memories()
|
||||||
+ rawfile.get_memories()
|
+ rawfile.get_memories()
|
||||||
)
|
)
|
||||||
self.reg_digits = elffile.xlen() // 4
|
self.reg_digits = elffile.xlen() // 4
|
||||||
self.reg_fmt = "<I" if elffile.xlen() <= 32 else "<Q"
|
self.reg_fmt = "<I" if elffile.xlen() <= 32 else "<Q"
|
||||||
|
|
||||||
self.mem_regions.sort(key=lambda x: x["start"])
|
self.mem_regions.sort(key=lambda x: x["start"])
|
||||||
|
self.threadinfo = []
|
||||||
|
self.current_thread = 0
|
||||||
|
try:
|
||||||
|
self.parse_thread()
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
def get_gdb_packet(self):
|
def get_gdb_packet(self):
|
||||||
socket = self.socket
|
socket = self.socket
|
||||||
@ -536,9 +558,11 @@ class GDBStub:
|
|||||||
self.put_gdb_packet(pkt)
|
self.put_gdb_packet(pkt)
|
||||||
|
|
||||||
def handle_register_group_read_packet(self):
|
def handle_register_group_read_packet(self):
|
||||||
|
|
||||||
|
def put_register_packet(regs):
|
||||||
pkt = b""
|
pkt = b""
|
||||||
|
|
||||||
for reg in self.logfile.registers:
|
for reg in regs:
|
||||||
if reg != b"x":
|
if reg != b"x":
|
||||||
bval = struct.pack(self.reg_fmt, reg)
|
bval = struct.pack(self.reg_fmt, reg)
|
||||||
pkt += binascii.hexlify(bval)
|
pkt += binascii.hexlify(bval)
|
||||||
@ -549,16 +573,34 @@ class GDBStub:
|
|||||||
|
|
||||||
self.put_gdb_packet(pkt)
|
self.put_gdb_packet(pkt)
|
||||||
|
|
||||||
|
if not self.threadinfo:
|
||||||
|
put_register_packet(self.registers)
|
||||||
|
else:
|
||||||
|
for thread in self.threadinfo:
|
||||||
|
if thread["tcb"]["pid"] == self.current_thread:
|
||||||
|
put_register_packet(thread["gdb_regs"])
|
||||||
|
break
|
||||||
|
|
||||||
def handle_register_single_read_packet(self, pkt):
|
def handle_register_single_read_packet(self, pkt):
|
||||||
logger.debug(f"pkt: {pkt}")
|
logger.debug(f"pkt: {pkt}")
|
||||||
|
|
||||||
reg = int("0x" + pkt[1:].decode("utf8"), 16)
|
def put_one_register_packet(regs):
|
||||||
if reg < len(self.logfile.registers) and self.logfile.registers[reg] != b"x":
|
|
||||||
bval = struct.pack(self.reg_fmt, self.logfile.registers[reg])
|
reg = int(pkt[1:].decode("utf8"), 16)
|
||||||
|
if reg < len(regs) and regs[reg] != b"x":
|
||||||
|
bval = struct.pack(self.reg_fmt, regs[reg])
|
||||||
self.put_gdb_packet(binascii.hexlify(bval))
|
self.put_gdb_packet(binascii.hexlify(bval))
|
||||||
else:
|
else:
|
||||||
self.put_gdb_packet(b"x" * self.reg_digits)
|
self.put_gdb_packet(b"x" * self.reg_digits)
|
||||||
|
|
||||||
|
if not self.threadinfo:
|
||||||
|
put_one_register_packet(self.registers)
|
||||||
|
else:
|
||||||
|
for thread in self.threadinfo:
|
||||||
|
if thread["tcb"]["pid"] == self.current_thread:
|
||||||
|
put_one_register_packet(thread["gdb_regs"])
|
||||||
|
break
|
||||||
|
|
||||||
def handle_register_group_write_packet(self):
|
def handle_register_group_write_packet(self):
|
||||||
# the 'G' packet for writing to a group of registers
|
# the 'G' packet for writing to a group of registers
|
||||||
#
|
#
|
||||||
@ -572,27 +614,20 @@ class GDBStub:
|
|||||||
reg_val = 0
|
reg_val = 0
|
||||||
for i in range(0, len(value), 2):
|
for i in range(0, len(value), 2):
|
||||||
data = value[i : i + 2]
|
data = value[i : i + 2]
|
||||||
reg_val = reg_val + (int("0x" + data.decode("utf8"), 16) << (i * 4))
|
reg_val = reg_val + (int(data.decode("utf8"), 16) << (i * 4))
|
||||||
|
|
||||||
reg = int("0x" + index.decode("utf8"), 16)
|
reg = int(index.decode("utf8"), 16)
|
||||||
if reg < len(self.logfile.registers):
|
if reg < len(self.registers):
|
||||||
self.logfile.registers[reg] = reg_val
|
self.registers[reg] = reg_val
|
||||||
|
|
||||||
self.put_gdb_packet(b"OK")
|
self.put_gdb_packet(b"OK")
|
||||||
|
|
||||||
def handle_memory_read_packet(self, pkt):
|
def get_mem_region(self, addr):
|
||||||
# the 'm' packet for reading memory: m<addr>,<len>
|
|
||||||
|
|
||||||
def get_mem_region(addr):
|
|
||||||
left = 0
|
left = 0
|
||||||
right = len(self.mem_regions) - 1
|
right = len(self.mem_regions) - 1
|
||||||
while left <= right:
|
while left <= right:
|
||||||
mid = (left + right) // 2
|
mid = (left + right) // 2
|
||||||
if (
|
if self.mem_regions[mid]["start"] <= addr <= self.mem_regions[mid]["end"]:
|
||||||
self.mem_regions[mid]["start"]
|
|
||||||
<= addr
|
|
||||||
<= self.mem_regions[mid]["end"]
|
|
||||||
):
|
|
||||||
return self.mem_regions[mid]
|
return self.mem_regions[mid]
|
||||||
elif addr < self.mem_regions[mid]["start"]:
|
elif addr < self.mem_regions[mid]["start"]:
|
||||||
right = mid - 1
|
right = mid - 1
|
||||||
@ -601,6 +636,9 @@ class GDBStub:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def handle_memory_read_packet(self, pkt):
|
||||||
|
# the 'm' packet for reading memory: m<addr>,<len>
|
||||||
|
|
||||||
# extract address and length from packet
|
# extract address and length from packet
|
||||||
# and convert them into usable integer values
|
# and convert them into usable integer values
|
||||||
addr, length = pkt[1:].split(b",")
|
addr, length = pkt[1:].split(b",")
|
||||||
@ -610,7 +648,7 @@ class GDBStub:
|
|||||||
remaining = length
|
remaining = length
|
||||||
addr = s_addr
|
addr = s_addr
|
||||||
barray = b""
|
barray = b""
|
||||||
r = get_mem_region(addr)
|
r = self.get_mem_region(addr)
|
||||||
while remaining > 0:
|
while remaining > 0:
|
||||||
if r is None:
|
if r is None:
|
||||||
barray = None
|
barray = None
|
||||||
@ -634,7 +672,153 @@ class GDBStub:
|
|||||||
# We don't support writing so return error
|
# We don't support writing so return error
|
||||||
self.put_gdb_packet(b"E02")
|
self.put_gdb_packet(b"E02")
|
||||||
|
|
||||||
|
def handle_is_thread_active(self, pkt):
|
||||||
|
self.current_thread = int(pkt[1:]) - 1
|
||||||
|
self.put_gdb_packet(b"OK")
|
||||||
|
|
||||||
|
def handle_thread_context(self, pkt):
|
||||||
|
if b"g" == pkt[1:2]:
|
||||||
|
self.current_thread = int(pkt[2:]) - 1
|
||||||
|
elif b"c" == pkt[1:2]:
|
||||||
|
self.current_thread = int(pkt[3:]) - 1
|
||||||
|
|
||||||
|
if self.current_thread == -1:
|
||||||
|
self.current_thread = 0
|
||||||
|
self.put_gdb_packet(b"OK")
|
||||||
|
|
||||||
|
def parse_thread(self):
|
||||||
|
def unpack_data(addr, size, fmt):
|
||||||
|
r = self.get_mem_region(addr)
|
||||||
|
offset = addr - r["start"]
|
||||||
|
data = r["data"][offset : offset + size]
|
||||||
|
return struct.unpack(fmt, data)
|
||||||
|
|
||||||
|
TCBINFO_FMT = "<8HQ"
|
||||||
|
|
||||||
|
# uint16_t pid_off; /* Offset of tcb.pid */
|
||||||
|
# uint16_t state_off; /* Offset of tcb.task_state */
|
||||||
|
# uint16_t pri_off; /* Offset of tcb.sched_priority */
|
||||||
|
# uint16_t name_off; /* Offset of tcb.name */
|
||||||
|
# uint16_t stack_off; /* Offset of tcb.stack_alloc_ptr */
|
||||||
|
# uint16_t stack_size_off; /* Offset of tcb.adj_stack_size */
|
||||||
|
# uint16_t regs_off; /* Offset of tcb.regs */
|
||||||
|
# uint16_t regs_num; /* Num of general regs */
|
||||||
|
# union
|
||||||
|
# {
|
||||||
|
# uint8_t u[8];
|
||||||
|
# FAR const uint16_t *p;
|
||||||
|
# }
|
||||||
|
|
||||||
|
unpacked_data = unpack_data(
|
||||||
|
self.elffile.symbol["g_tcbinfo"]["st_value"],
|
||||||
|
self.elffile.symbol["g_tcbinfo"]["st_size"],
|
||||||
|
TCBINFO_FMT,
|
||||||
|
)
|
||||||
|
tcbinfo = {
|
||||||
|
"pid_off": int(unpacked_data[0]),
|
||||||
|
"state_off": int(unpacked_data[1]),
|
||||||
|
"pri_off": int(unpacked_data[2]),
|
||||||
|
"name_off": int(unpacked_data[3]),
|
||||||
|
"stack_off": int(unpacked_data[4]),
|
||||||
|
"stack_size_off": int(unpacked_data[5]),
|
||||||
|
"regs_off": int(unpacked_data[6]),
|
||||||
|
"regs_num": int(unpacked_data[7]),
|
||||||
|
"reg_off": int(unpacked_data[8]),
|
||||||
|
}
|
||||||
|
|
||||||
|
unpacked_data = unpack_data(
|
||||||
|
self.elffile.symbol["g_npidhash"]["st_value"],
|
||||||
|
self.elffile.symbol["g_npidhash"]["st_size"],
|
||||||
|
"<I",
|
||||||
|
)
|
||||||
|
npidhash = int(unpacked_data[0])
|
||||||
|
unpacked_data = unpack_data(
|
||||||
|
self.elffile.symbol["g_pidhash"]["st_value"],
|
||||||
|
self.elffile.symbol["g_pidhash"]["st_size"],
|
||||||
|
"<I",
|
||||||
|
)
|
||||||
|
pidhash = int(unpacked_data[0])
|
||||||
|
|
||||||
|
tcbptr_list = []
|
||||||
|
for i in range(0, npidhash):
|
||||||
|
unpacked_data = unpack_data(pidhash + i * 4, 4, "<I")
|
||||||
|
tcbptr_list.append(int(unpacked_data[0]))
|
||||||
|
|
||||||
|
def parse_tcb(tcbptr):
|
||||||
|
tcb = {}
|
||||||
|
tcb["pid"] = int(unpack_data(tcbptr + tcbinfo["pid_off"], 4, "<I")[0])
|
||||||
|
tcb["state"] = int(unpack_data(tcbptr + tcbinfo["state_off"], 1, "<B")[0])
|
||||||
|
tcb["pri"] = int(unpack_data(tcbptr + tcbinfo["pri_off"], 1, "<B")[0])
|
||||||
|
tcb["stack"] = int(unpack_data(tcbptr + tcbinfo["stack_off"], 4, "<I")[0])
|
||||||
|
tcb["stack_size"] = int(
|
||||||
|
unpack_data(tcbptr + tcbinfo["stack_size_off"], 4, "<I")[0]
|
||||||
|
)
|
||||||
|
tcb["regs"] = int(unpack_data(tcbptr + tcbinfo["regs_off"], 4, "<I")[0])
|
||||||
|
i = 0
|
||||||
|
tcb["name"] = ""
|
||||||
|
while True:
|
||||||
|
c = int(unpack_data(tcbptr + tcbinfo["name_off"] + i, 1, "<B")[0])
|
||||||
|
if c == 0:
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
tcb["name"] += chr(c)
|
||||||
|
|
||||||
|
return tcb
|
||||||
|
|
||||||
|
def parse_tcb_regs_to_gdb(tcb):
|
||||||
|
regs = []
|
||||||
|
for i in range(0, tcbinfo["regs_num"]):
|
||||||
|
reg_off = int(unpack_data(tcbinfo["reg_off"] + i * 2, 2, "<H")[0])
|
||||||
|
if reg_off == UINT16_MAX:
|
||||||
|
regs.append(b"x")
|
||||||
|
else:
|
||||||
|
regs.append(int(unpack_data(tcb["regs"] + reg_off, 4, "<I")[0]))
|
||||||
|
return regs
|
||||||
|
|
||||||
|
for tcbptr in tcbptr_list:
|
||||||
|
if tcbptr == 0:
|
||||||
|
break
|
||||||
|
thread_dict = {}
|
||||||
|
tcb = parse_tcb(tcbptr)
|
||||||
|
thread_dict["tcb"] = tcb
|
||||||
|
thread_dict["gdb_regs"] = parse_tcb_regs_to_gdb(tcb)
|
||||||
|
self.threadinfo.append(thread_dict)
|
||||||
|
|
||||||
def handle_general_query_packet(self, pkt):
|
def handle_general_query_packet(self, pkt):
|
||||||
|
if b"Rcmd" == pkt[1:5]:
|
||||||
|
self.put_gdb_packet(b"OK")
|
||||||
|
elif b"qfThreadInfo" == pkt[: len(b"qfThreadInfo")]:
|
||||||
|
reply_str = "m"
|
||||||
|
for thread in self.threadinfo:
|
||||||
|
pid = thread["tcb"]["pid"]
|
||||||
|
reply_str += "," + str(pid + 1) # pid + 1 for gdb index
|
||||||
|
|
||||||
|
reply = reply_str.encode("utf-8")
|
||||||
|
self.put_gdb_packet(reply)
|
||||||
|
|
||||||
|
elif b"qsThreadInfo" == pkt[: len(b"qsThreadInfo")]:
|
||||||
|
self.put_gdb_packet(b"l")
|
||||||
|
|
||||||
|
elif b"qThreadExtraInfo" == pkt[: len(b"qThreadExtraInfo")]:
|
||||||
|
cmd, pid = pkt[1:].split(b",")
|
||||||
|
pid = int(pid) - 1
|
||||||
|
|
||||||
|
for thread in self.threadinfo:
|
||||||
|
if thread["tcb"]["pid"] == pid:
|
||||||
|
|
||||||
|
pkt_str = "Name: %s, State: %d, Pri: %d, Stack: %x, Size: %d" % (
|
||||||
|
thread["tcb"]["name"],
|
||||||
|
thread["tcb"]["state"],
|
||||||
|
thread["tcb"]["pri"],
|
||||||
|
thread["tcb"]["stack"],
|
||||||
|
thread["tcb"]["stack_size"],
|
||||||
|
)
|
||||||
|
pkt = pkt_str.encode()
|
||||||
|
pkt_str = pkt.hex()
|
||||||
|
pkt = pkt_str.encode()
|
||||||
|
self.put_gdb_packet(pkt)
|
||||||
|
break
|
||||||
|
else:
|
||||||
self.put_gdb_packet(b"")
|
self.put_gdb_packet(b"")
|
||||||
|
|
||||||
def handle_vkill_packet(self, pkt):
|
def handle_vkill_packet(self, pkt):
|
||||||
@ -676,6 +860,10 @@ class GDBStub:
|
|||||||
elif pkt.startswith(b"vKill") or pkt_type == b"k":
|
elif pkt.startswith(b"vKill") or pkt_type == b"k":
|
||||||
# GDB quits
|
# GDB quits
|
||||||
self.handle_vkill_packet(pkt)
|
self.handle_vkill_packet(pkt)
|
||||||
|
elif pkt_type == b"H":
|
||||||
|
self.handle_thread_context(pkt)
|
||||||
|
elif pkt_type == b"T":
|
||||||
|
self.handle_is_thread_active(pkt)
|
||||||
else:
|
else:
|
||||||
self.put_gdb_packet(b"")
|
self.put_gdb_packet(b"")
|
||||||
|
|
||||||
@ -686,7 +874,7 @@ def arg_parser():
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-e", "--elffile", required=True, action="append", help="elffile"
|
"-e", "--elffile", required=True, action="append", help="elffile"
|
||||||
)
|
)
|
||||||
parser.add_argument("-l", "--logfile", required=True, help="logfile")
|
parser.add_argument("-l", "--logfile", help="logfile")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-a",
|
"-a",
|
||||||
"--arch",
|
"--arch",
|
||||||
@ -785,15 +973,16 @@ def main(args):
|
|||||||
logger.error(f"Cannot find file {name}, exiting...")
|
logger.error(f"Cannot find file {name}, exiting...")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
if args.logfile:
|
||||||
if not os.path.isfile(args.logfile):
|
if not os.path.isfile(args.logfile):
|
||||||
logger.error(f"Cannot find file {args.logfile}, exiting...")
|
logger.error(f"Cannot find file {args.logfile}, exiting...")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not args.rawfile and not args.logfile:
|
||||||
|
logger.error("Must have a input file log or rawfile, exiting...")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
config_log(args.debug)
|
config_log(args.debug)
|
||||||
|
|
||||||
selected_log = auto_parse_log_file(args.logfile)
|
|
||||||
|
|
||||||
# parse ELF fisrt to get arch
|
|
||||||
elf = DumpELFFile(args.elffile[0])
|
elf = DumpELFFile(args.elffile[0])
|
||||||
elf.parse()
|
elf.parse()
|
||||||
elf_texts = [elf.text()]
|
elf_texts = [elf.text()]
|
||||||
@ -803,7 +992,13 @@ def main(args):
|
|||||||
elf_texts.append(other.text())
|
elf_texts.append(other.text())
|
||||||
elf.merge(other)
|
elf.merge(other)
|
||||||
|
|
||||||
|
if args.logfile is not None:
|
||||||
|
selected_log = auto_parse_log_file(args.logfile)
|
||||||
log = DumpLogFile(selected_log)
|
log = DumpLogFile(selected_log)
|
||||||
|
else:
|
||||||
|
log = DumpLogFile(None)
|
||||||
|
|
||||||
|
if args.logfile is not None:
|
||||||
if args.arch:
|
if args.arch:
|
||||||
log.parse(args.arch)
|
log.parse(args.arch)
|
||||||
elif elf.arch() in reg_table.keys():
|
elif elf.arch() in reg_table.keys():
|
||||||
@ -811,11 +1006,10 @@ def main(args):
|
|||||||
else:
|
else:
|
||||||
logger.error("Architecture unknown, exiting...")
|
logger.error("Architecture unknown, exiting...")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
elf.parse_addr2line(args.arch, args.addr2line, log.stack_data)
|
||||||
|
|
||||||
raw = RawMemoryFile(args.rawfile)
|
raw = RawMemoryFile(args.rawfile)
|
||||||
|
|
||||||
gdb_stub = GDBStub(log, elf, raw)
|
gdb_stub = GDBStub(log, elf, raw)
|
||||||
|
|
||||||
gdbserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
gdbserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
# Reuse address so we don't have to wait for socket to be
|
# Reuse address so we don't have to wait for socket to be
|
||||||
|
Loading…
x
Reference in New Issue
Block a user