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
|
||||
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user