user / kernel stack output
This commit is contained in:
68
source/ucli_py/lib.py
Normal file
68
source/ucli_py/lib.py
Normal file
@@ -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)) # 将这里的地址替换成你想要查询的地址
|
||||
342
source/ucli_py/ucli.py
Normal file
342
source/ucli_py/ucli.py
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user