From 940cc213aecb692c9d9462ebb116d8b51e8ad813 Mon Sep 17 00:00:00 2001 From: zy Date: Mon, 20 Nov 2023 05:31:39 -0500 Subject: [PATCH] user / kernel stack output --- source/ucli_py/lib.py | 68 ++++++++ source/ucli_py/ucli.py | 342 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 410 insertions(+) create mode 100644 source/ucli_py/lib.py create mode 100644 source/ucli_py/ucli.py diff --git a/source/ucli_py/lib.py b/source/ucli_py/lib.py new file mode 100644 index 0000000..ca53652 --- /dev/null +++ b/source/ucli_py/lib.py @@ -0,0 +1,68 @@ +import bisect +import os +import re +import subprocess + +# __pattern = re.compile(r"([0-9a-f]+)-([0-9a-f]+) [\w-]+ [\w:]+ [\w:]+ \S+ (.*?)\n") + + +class ProcMapsParser: + __pattern = re.compile(r"([0-9a-f]+)-([0-9a-f]+) [\w-]+ [\w:]+ [\w:]+ \S+ (.*?)\n") + + def __init__(self, pid): + self.ranges = [] + self.names = [] + try: + with open(f"/proc/{pid}/maps", "r") as f: + for line in f: + m = self.__pattern.match(line) + if m is not None: + start, end, name = m.groups() + # remove " " + name = name.strip() + start = int(start, 16) + end = int(end, 16) + if name == "" or name == None: + name = None + elif not os.path.isabs(name): + name = None + self.ranges.append((start, end)) + self.names.append(name) + except FileNotFoundError: + return None + + def lookup(self, addr): + # 内核空间地址通常在高内存区域,我们设定一个阈值为 0x7fffffffffff + if addr > 0x7FFFFFFFFFFF: + return "kernel", addr + i = bisect.bisect(self.ranges, (addr, addr)) - 1 + if i >= 0 and self.ranges[i][0] <= addr < self.ranges[i][1]: + offset = addr - self.ranges[i][0] + return self.names[i], offset + return None, None + + +__pattern = re.compile(r"([0-9a-f]+) ([Tt]) (.*)") + + +def addr_to_symbol(path, offset): + output = subprocess.check_output(["nm", "-D", path]) + lines = output.decode().splitlines() + lines = [line for line in lines if " " in line] + lines.sort() # Sort by address + prev_sym = None + for line in lines: + match = __pattern.match(line) + if match is None: # Skip if the line does not match the pattern + continue + addr_str, type_, sym = match.groups() + addr = int(addr_str, 16) + if addr > offset: + return prev_sym + prev_sym = sym + return prev_sym + + +# 使用示例: +# p = ProcMapsParser(1234) # 请将 1234 替换成你想要查询的进程 ID +# print(p.lookup(0x7FF6C4D2D000)) # 将这里的地址替换成你想要查询的地址 diff --git a/source/ucli_py/ucli.py b/source/ucli_py/ucli.py new file mode 100644 index 0000000..0d41125 --- /dev/null +++ b/source/ucli_py/ucli.py @@ -0,0 +1,342 @@ +import os +import ctypes +from ctypes import * +import fcntl +import pickle +import bisect + +from lib import * + +DEVICE = "/dev/variable_monitor" +file_desc = None + + +def open_device(): + global file_desc + if file_desc is None: + try: + file_desc = os.open(DEVICE, os.O_RDWR) + except OSError: + print(f"Can't open device file: {DEVICE}") + return -1 + return 0 + + +def close_device(): + global file_desc + if file_desc is None: + print(f"Device not open: {DEVICE}, {file_desc}") + return file_desc + + os.close(file_desc) + file_desc = None + return 0 + + +class ioctl_dump_param(Structure): + _fields_ = [ + ("user_ptr_len", POINTER(c_int)), + ("user_buf_len", c_size_t), + ("user_buf", c_void_p), + ] + + +variant_buf = create_string_buffer(50 * 1024 * 1024) +len_temp = c_int() + + +def do_dump(arg): + dump_param = ioctl_dump_param( + user_ptr_len=pointer(len_temp), + user_buf_len=50 * 1024 * 1024, + user_buf=addressof(variant_buf), + ) + ret = fcntl.ioctl(file_desc, 1, dump_param) + + if ret == 0: + buf_bytes = bytes(variant_buf) + with open("buff", "wb") as f: + pickle.dump(buf_bytes, f) + + with open("len", "wb") as f: + pickle.dump(len_temp, f) + # do_extract(variant_buf, len_temp.value) + + +def do_extract(buf, len): + extract_variant_buffer(buf, len, load_monitor_extract, None) + + +class diag_variant_buffer_head(Structure): + _fields_ = [ + ("magic", c_ulong), + ("len", c_ulong), + ] + + +DIAG_VARIANT_BUFFER_HEAD_MAGIC_SEALED = 197612031122 + + +def extract_variant_buffer(buf, len, func, arg): + pos = 0 + dir = os.getcwd() + + while pos < len: + head = diag_variant_buffer_head.from_buffer(buf, pos) + if pos + ctypes.sizeof(diag_variant_buffer_head) >= len: + break + if head.magic != DIAG_VARIANT_BUFFER_HEAD_MAGIC_SEALED: + break + if head.len < ctypes.sizeof(diag_variant_buffer_head): + break + + rec = buf[pos + ctypes.sizeof(diag_variant_buffer_head) : pos + head.len] + rec_len = head.len - ctypes.sizeof(diag_variant_buffer_head) + func(rec, rec_len, arg) + + pos += head.len + + os.chdir(dir) + + +MAX_NAME_LEN = 15 +TIMER_MAX_WATCH_NUM = 32 +CGROUP_NAME_LEN = 32 +TASK_COMM_LEN = 16 +BACKTRACE_DEPTH = 30 +PROCESS_CHAINS_COUNT = 10 +PROCESS_ARGV_LEN = 128 + + +class threshold(ctypes.Structure): + _fields_ = [ + ("task_id", c_int), + ("name", c_char * (MAX_NAME_LEN + 1)), + ("ptr", c_void_p), + ("threshold", c_longlong), + ] + + +class variable_monitor_record(ctypes.Structure): + _fields_ = [ + ("et_type", c_int), + ("id", c_ulong), + ("tv", c_ulonglong), + ("threshold_num", c_int), + ("threshold_record", threshold * TIMER_MAX_WATCH_NUM), + ] + + +class task_detail(ctypes.Structure): + _fields_ = [ + ("cgroup_buf", c_char * CGROUP_NAME_LEN), + ("cgroup_cpuset", c_char * CGROUP_NAME_LEN), + ("pid", c_int), + ("tgid", c_int), + ("container_pid", c_int), + ("container_tgid", c_int), + ("state", c_long), + ("task_type", c_int), + ("syscallno", c_ulong), + ("sys_task", c_ulong), + ("user_mode", c_ulong), + ("comm", c_char * TASK_COMM_LEN), + ] + + +class kern_stack_detail(ctypes.Structure): + _fields_ = [ + ("stack", c_ulong * BACKTRACE_DEPTH), + ] + + +class user_stack_detail(ctypes.Structure): + _fields_ = [ + ("regs", c_ulong), # Replace with actual type + ("ip", c_ulong), + ("bp", c_ulong), + ("sp", c_ulong), + ("stack", c_ulong * BACKTRACE_DEPTH), + ] + + +class proc_chains_detail(ctypes.Structure): + _fields_ = [ + ("full_argv", c_uint * PROCESS_CHAINS_COUNT), + ("chains", c_char * PROCESS_CHAINS_COUNT * PROCESS_ARGV_LEN), + ("tgid", c_uint * PROCESS_CHAINS_COUNT), + ] + + +class variable_monitor_task(ctypes.Structure): + _fields_ = [ + ("et_type", c_int), + ("id", c_ulong), + ("tv", c_ulonglong), + ("task", task_detail), + ("user_stack", user_stack_detail), + ("kern_stack", kern_stack_detail), + ("proc_chains", proc_chains_detail), + ] + + +def parse_file(file_path): + result = {} + with open(file_path, "r") as file: + for line in file: + parts = line.split() + if len(parts) == 3 and (parts[1] == "t" or parts[1] == "T"): + address = int(parts[0], 16) + # Convert address from string to c_ulong + name = parts[2] + result[address] = name + return result + + +def parse_kallsyms(filename): + symbols = [] + with open(filename, "r") as f: + for line in f: + parts = line.split() + if len(parts) < 3: + continue + if parts[1] in ("t", "T"): # only consider text symbols + addr = int(parts[0], 16) + name = parts[2] + symbols.append((addr, name)) + # sort by address + symbols.sort() + return symbols + + +def print_stack_trace(symbols, address): + symbol_addrs = [addr for addr, _ in symbols] + # find the nearest symbol that is less than or equal to the addr + i = bisect.bisect_right(symbol_addrs, address) + if i: + nearest_addr, nearest_symbol = symbols[i - 1] + offset = address - nearest_addr + return (nearest_symbol, offset) + print(f"{nearest_symbol}+0x{offset:x}") + else: + return (None, None) + + +# kallsymsMap = parse_file("/proc/kallsyms") +# parse /proc/kallsyms +symbols = parse_kallsyms("/proc/kallsyms") + + +def diag_printf_kern_stack(kern_stack: kern_stack_detail): + print(" 内核态堆栈:") + for i in range(BACKTRACE_DEPTH - 1, -1, -1): + if kern_stack.stack[i] == ctypes.c_ulong(-1).value or kern_stack.stack[i] == 0: + continue + # temp = kern_stack.stack[i] + nearest_symbol, offset = print_stack_trace(symbols, kern_stack.stack[i]) + # sym = kallsymsMap.get(kern_stack.stack[i]) + if nearest_symbol != None: + print( + "#@ 0x%lx %s ([kernel.kallsyms])" + % (kern_stack.stack[i], nearest_symbol) + ) + else: + print("#@ 0x%lx %s" % (kern_stack.stack[i], "UNKNOWN")) + + +def print_structure(structure, indent=0, struct_name=""): + indent_spaces = " " * indent + for field_name, field_type in structure._fields_: + if field_name == "chains": # 跳过 'chains' 字段 + continue + value = getattr(structure, field_name) + if hasattr(value, "_fields_"): # 如果是嵌套的结构体 + print(f"{indent_spaces}{struct_name} {field_name}:") + print_structure(value, indent + 4, field_name) # 递归打印,增加缩进 + elif isinstance(value, ctypes.Array): # 如果是数组 + print(f"{indent_spaces}{struct_name} {field_name}: {list(value)}") + else: + print(f"{indent_spaces}{struct_name} {field_name}: {value}") + + +def diag_printf_user_stack(pid: int, user_stack: user_stack_detail): + p = None + print(" 用户态堆栈:") + for i in range(BACKTRACE_DEPTH - 1, -1, -1): + if user_stack.stack[i] == ctypes.c_ulong(-1).value or user_stack.stack[i] == 0: + continue + if not p: + p = ProcMapsParser(pid) + if not p: + print("#~ 0x%lx NO /proc/pid/maps" % (user_stack.stack[i])) + continue + path, addr = p.lookup(user_stack.stack[i]) + if path != None: + if path == "kernel": + nearest_symbol, offset = print_stack_trace(symbols, addr) + print( + "#~ 0x%lx %s ([kernel.kallsyms])" + % (user_stack.stack[i], nearest_symbol) + ) + else: + symbol = addr_to_symbol(path, addr) + print("#~ 0x%lx %s ([symbol])" % (user_stack.stack[i], symbol)) + else: + print("#~ 0x%lx UNKNOWN" % (user_stack.stack[i])) + + +def load_monitor_extract(buf, len, _): + seq = 0 + + if len == 0: + return 0 + + et_type = ctypes.cast(buf, ctypes.POINTER(ctypes.c_int)).contents.value + + if et_type == 0: + if len < ctypes.sizeof(variable_monitor_record): + return 0 + vm_record = ctypes.cast(buf, ctypes.POINTER(variable_monitor_record)).contents + + print(f"超出阈值:{vm_record.tv}") + + for i in range(vm_record.threshold_num): + print( + f"\t: pid:{vm_record.threshold_record[i].task_id}, name:{vm_record.threshold_record[i].name.decode()}, ptr:{vm_record.threshold_record[i].ptr}, threshold:{vm_record.threshold_record[i].threshold}" + ) + + elif et_type == 1: + if len < ctypes.sizeof(variable_monitor_task): + return 0 + tsk_info = ctypes.cast(buf, ctypes.POINTER(variable_monitor_task)).contents + seq += 1 + + print( + f"##CGROUP:[{tsk_info.task.cgroup_buf.decode()}] {tsk_info.task.pid} [{seq:03d}] 采样命中[{'R' if tsk_info.task.state == 0 else 'D'}]" + ) + # 打印 tsk_info + # print_structure(tsk_info, struct_name="variable_monitor_task") + diag_printf_user_stack(tsk_info.task.pid, tsk_info.user_stack) + diag_printf_kern_stack(tsk_info.kern_stack) + print(f"#* 0xffffffffffffff {tsk_info.task.comm.decode()} (UNKNOWN)") + # diag_printf_proc_chains(&tsk_info->proc_chains, 0, process_chains); + + print("##") + + return 0 + + +if __name__ == "__main__": + # open_device() + # do_dump(None) + # close_device() + + with open("buff", "rb") as f: + buf_bytes = pickle.load(f) + + buf2 = ctypes.create_string_buffer(len(buf_bytes)) + buf2.raw = buf_bytes + + with open("len", "rb") as f: + len2 = pickle.load(f) + do_extract(buf2, len2.value)