summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorzy <[email protected]>2023-11-20 05:31:39 -0500
committerzy <[email protected]>2023-11-20 05:31:39 -0500
commit940cc213aecb692c9d9462ebb116d8b51e8ad813 (patch)
treea6d307f13f6000b5cdfc5ec69567147400b05909 /source
parent21aae0b89e70a348b597771c62f19ea0d5dc5edd (diff)
user / kernel stack output
Diffstat (limited to 'source')
-rw-r--r--source/ucli_py/lib.py68
-rw-r--r--source/ucli_py/ucli.py342
2 files changed, 410 insertions, 0 deletions
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)