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:
anjiahao 2024-08-13 10:53:23 +08:00 committed by Xiang Xiao
parent 65ef1f72c1
commit f52a3d314e

View File

@ -41,6 +41,9 @@ SHF_ALLOC_EXEC = SHF_ALLOC | SHF_EXEC
GDB_SIGNAL_DEFAULT = 7
UINT16_MAX = 65535
DEFAULT_GDB_INIT_CMD = "-ex 'bt full' -ex 'info reg' -ex 'display /40i $pc-40'"
logger = logging.getLogger()
@ -307,7 +310,20 @@ class DumpELFFile:
if segment.header.p_flags & 1 and not self.__text:
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()
return True
def merge(self, other):
@ -454,19 +470,25 @@ class GDBStub:
def __init__(
self, logfile: DumpLogFile, elffile: DumpELFFile, rawfile: RawMemoryFile
):
self.logfile = logfile
self.registers = logfile.registers
self.elffile = elffile
self.socket = None
self.gdb_signal = GDB_SIGNAL_DEFAULT
self.mem_regions = (
self.elffile.get_memories()
+ self.logfile.get_memories()
+ logfile.get_memories()
+ rawfile.get_memories()
)
self.reg_digits = elffile.xlen() // 4
self.reg_fmt = "<I" if elffile.xlen() <= 32 else "<Q"
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):
socket = self.socket
@ -536,28 +558,48 @@ class GDBStub:
self.put_gdb_packet(pkt)
def handle_register_group_read_packet(self):
pkt = b""
for reg in self.logfile.registers:
if reg != b"x":
bval = struct.pack(self.reg_fmt, reg)
pkt += binascii.hexlify(bval)
else:
# Register not in coredump -> unknown value
# Send in "xxxxxxxx"
pkt += b"x" * self.reg_digits
def put_register_packet(regs):
pkt = b""
self.put_gdb_packet(pkt)
for reg in regs:
if reg != b"x":
bval = struct.pack(self.reg_fmt, reg)
pkt += binascii.hexlify(bval)
else:
# Register not in coredump -> unknown value
# Send in "xxxxxxxx"
pkt += b"x" * self.reg_digits
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):
logger.debug(f"pkt: {pkt}")
reg = int("0x" + pkt[1:].decode("utf8"), 16)
if reg < len(self.logfile.registers) and self.logfile.registers[reg] != b"x":
bval = struct.pack(self.reg_fmt, self.logfile.registers[reg])
self.put_gdb_packet(binascii.hexlify(bval))
def put_one_register_packet(regs):
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))
else:
self.put_gdb_packet(b"x" * self.reg_digits)
if not self.threadinfo:
put_one_register_packet(self.registers)
else:
self.put_gdb_packet(b"x" * self.reg_digits)
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):
# the 'G' packet for writing to a group of registers
@ -572,35 +614,31 @@ class GDBStub:
reg_val = 0
for i in range(0, len(value), 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)
if reg < len(self.logfile.registers):
self.logfile.registers[reg] = reg_val
reg = int(index.decode("utf8"), 16)
if reg < len(self.registers):
self.registers[reg] = reg_val
self.put_gdb_packet(b"OK")
def get_mem_region(self, addr):
left = 0
right = len(self.mem_regions) - 1
while left <= right:
mid = (left + right) // 2
if self.mem_regions[mid]["start"] <= addr <= self.mem_regions[mid]["end"]:
return self.mem_regions[mid]
elif addr < self.mem_regions[mid]["start"]:
right = mid - 1
else:
left = mid + 1
return None
def handle_memory_read_packet(self, pkt):
# the 'm' packet for reading memory: m<addr>,<len>
def get_mem_region(addr):
left = 0
right = len(self.mem_regions) - 1
while left <= right:
mid = (left + right) // 2
if (
self.mem_regions[mid]["start"]
<= addr
<= self.mem_regions[mid]["end"]
):
return self.mem_regions[mid]
elif addr < self.mem_regions[mid]["start"]:
right = mid - 1
else:
left = mid + 1
return None
# extract address and length from packet
# and convert them into usable integer values
addr, length = pkt[1:].split(b",")
@ -610,7 +648,7 @@ class GDBStub:
remaining = length
addr = s_addr
barray = b""
r = get_mem_region(addr)
r = self.get_mem_region(addr)
while remaining > 0:
if r is None:
barray = None
@ -634,8 +672,154 @@ class GDBStub:
# We don't support writing so return error
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):
self.put_gdb_packet(b"")
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"")
def handle_vkill_packet(self, pkt):
self.put_gdb_packet(b"OK")
@ -676,6 +860,10 @@ class GDBStub:
elif pkt.startswith(b"vKill") or pkt_type == b"k":
# GDB quits
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:
self.put_gdb_packet(b"")
@ -686,7 +874,7 @@ def arg_parser():
parser.add_argument(
"-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(
"-a",
"--arch",
@ -785,15 +973,16 @@ def main(args):
logger.error(f"Cannot find file {name}, exiting...")
sys.exit(1)
if not os.path.isfile(args.logfile):
logger.error(f"Cannot find file {args.logfile}, exiting...")
if args.logfile:
if not os.path.isfile(args.logfile):
logger.error(f"Cannot find file {args.logfile}, exiting...")
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)
selected_log = auto_parse_log_file(args.logfile)
# parse ELF fisrt to get arch
elf = DumpELFFile(args.elffile[0])
elf.parse()
elf_texts = [elf.text()]
@ -803,19 +992,24 @@ def main(args):
elf_texts.append(other.text())
elf.merge(other)
log = DumpLogFile(selected_log)
if args.arch:
log.parse(args.arch)
elif elf.arch() in reg_table.keys():
log.parse(elf.arch())
if args.logfile is not None:
selected_log = auto_parse_log_file(args.logfile)
log = DumpLogFile(selected_log)
else:
logger.error("Architecture unknown, exiting...")
sys.exit(2)
log = DumpLogFile(None)
if args.logfile is not None:
if args.arch:
log.parse(args.arch)
elif elf.arch() in reg_table.keys():
log.parse(elf.arch())
else:
logger.error("Architecture unknown, exiting...")
sys.exit(2)
elf.parse_addr2line(args.arch, args.addr2line, log.stack_data)
raw = RawMemoryFile(args.rawfile)
gdb_stub = GDBStub(log, elf, raw)
gdbserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Reuse address so we don't have to wait for socket to be