代码拉取完成,页面将自动刷新
同步操作将从 yedapeng/Analysis tool for mcu 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import scrolledtext, font
import threading
import queue
import os
import io
import sys
import jlink_operations
from stack_tracer import StackTracer # 确保正确导入你的类
class AnalysisApp:
def __init__(self, root_var):
self.root = root_var
root.minsize(1024, 500) # 根据您的需求设置合适的宽高
# 创建菜单栏
self.create_menu()
# 配置根窗口,使其能够自动调整大小
root.columnconfigure(0, weight=0) # 标签不动
root.columnconfigure(1, weight=1) # 输入框扩展
root.columnconfigure(2, weight=0) # 浏览按钮不动
root.rowconfigure(0, weight=0)
root.rowconfigure(1, weight=0)
root.rowconfigure(2, weight=0)
root.rowconfigure(3, weight=0)
root.rowconfigure(4, weight=0)
root.rowconfigure(5, weight=0)
root.rowconfigure(6, weight=1) # 使日志输出区域自适应扩展
# DLL文件选择
self.dll_path = tk.StringVar()
tk.Label(root, text="选择DLL文件:", anchor="w").grid(row=0, column=0, sticky="w", padx=10, pady=5)
tk.Entry(root, textvariable=self.dll_path, width=50).grid(row=0, column=1, sticky="we", padx=10, pady=5)
tk.Button(root, text="浏览", command=self.load_dll).grid(row=0, column=2, padx=10, pady=5)
self.prj_path = tk.StringVar()
tk.Label(root, text="选择工程目录:", anchor="w").grid(row=1, column=0, sticky="w", padx=10, pady=5)
tk.Entry(root, textvariable=self.prj_path, width=50).grid(row=1, column=1, sticky="we", padx=10, pady=5)
tk.Button(root, text="打开", command=self.load_prj).grid(row=1, column=2, padx=10, pady=5)
"""
# AXF文件选择
self.axf_path = tk.StringVar()
tk.Label(root, text="选择.AXF文件:", anchor="w").grid(row=1, column=0, sticky="w", padx=10, pady=5)
tk.Entry(root, textvariable=self.axf_path, width=50).grid(row=1, column=1, sticky="we", padx=10, pady=5)
tk.Button(root, text="浏览", command=self.load_axf).grid(row=1, column=2, padx=10, pady=5)
# MAP文件选择
self.map_path = tk.StringVar()
tk.Label(root, text="选择.MAP文件:", anchor="w").grid(row=2, column=0, sticky="w", padx=10, pady=5)
tk.Entry(root, textvariable=self.map_path, width=50).grid(row=2, column=1, sticky="we", padx=10, pady=5)
tk.Button(root, text="浏览", command=self.load_map).grid(row=2, column=2, padx=10, pady=5)
"""
# 起始地址和分析长度输入
self.start_address = tk.StringVar(value="0x20000000")
self.length = tk.StringVar(value="0x4000")
# 起始地址标签和输入框
tk.Label(root, text="分析起始地址:", anchor="w").grid(row=3, column=0, sticky="w", padx=10, pady=5)
tk.Entry(root, textvariable=self.start_address, width=15).grid(row=3, column=1, sticky="w", padx=10, pady=5)
# 分析长度标签和输入框
tk.Label(root, text="分析长度:", anchor="w").grid(row=4, column=0, sticky="w", padx=10, pady=5)
tk.Entry(root, textvariable=self.length, width=15).grid(row=4, column=1, sticky="w", padx=10, pady=5)
# 将提取和分析按钮放在同一列中,使用padx和pady设置偏移量
tk.Button(root, text="提取", command=self.start_analysis).grid(row=3, column=1, padx=(130, 0), pady=(5, 0), sticky="w")
tk.Button(root, text="分析", command=self.analyze_stack_info).grid(row=4, column=1, padx=(130, 0), pady=(5, 5), sticky="w")
# 日志窗口
tk.Label(root, text="日志输出:", anchor="w").grid(row=5, column=0, sticky="nw", padx=10, pady=5)
tk.Button(root, text="清空", command=self.clear_log).grid(row=5, column=2, padx=10, pady=5, sticky="e")
custom_font = font.Font(family="Consolas", size=11) # 设置字体样式和大小
self.log_window = scrolledtext.ScrolledText(root, width=60, height=15, state="disabled", font=custom_font)
self.log_window.grid(row=6, column=0, columnspan=3, sticky="nsew", padx=10, pady=10)
# 设置日志窗口的纵向权重,以便在窗口调整大小时自适应
root.rowconfigure(6, weight=1)
# 用于接收子线程日志的队列
self.log_queue = queue.Queue()
# 定时检查日志队列并更新日志窗口
self.root.after(100, self.process_log_queue)
# 在关闭窗口时调用 on_closing 方法
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
# 用于管理子进程
self.processes = [] # 用于保存子进程
self.root.title("分析程序")
if getattr(sys, 'frozen', False):
# 如果是打包后的可执行文件,使用sys._MEIPASS获取文件路径
current_dir = sys._MEIPASS # type: ignore
else:
# 如果是源代码,使用os.path获取文件路径
current_dir = os.path.dirname(os.path.abspath(__file__))
icon_path = os.path.join(current_dir, 'favicon.ico')
self.root.iconbitmap(icon_path)
self.show_about() # 在程序启动时显示关于信息
def create_menu(self):
menubar = tk.Menu(self.root)
menubar.add_command(label="关于", command=self.show_about) # 直接添加关于选项
self.root.config(menu=menubar)
def show_about(self):
messagebox.showinfo("关于", "版本: 1.0\n作者: 叶大鹏, 谭继鑫\n版权: PN学堂\n网址:www.pnxuetang.cn")
def load_prj(self):
folder_dir = filedialog.askdirectory()
if dir: # 如果选择了目录
self.prj_path.set(folder_dir) # 将目录路径设置到文本框中
def load_dll(self):
path = filedialog.askopenfilename(filetypes=[("DLL files", "*.dll")])
if path:
self.dll_path.set(path)
self.log("加载DLL文件: " + path)
"""
def load_axf(self):
path = filedialog.askopenfilename(filetypes=[("AXF files", "*.axf")])
if path:
self.axf_path.set(path)
self.log("加载AXF文件: " + path)
def load_map(self):
path = filedialog.askopenfilename(filetypes=[("map files", "*.map")])
if path:
self.map_path.set(path)
self.log("加载MAP文件: " + path)
"""
def find_files_with_extension(self, directory, extension):
if not os.path.isdir(directory):
print(f"目录不存在: {directory}")
return
found = False # 用于跟踪是否找到文件
for rooter, dirs, files in os.walk(directory):
for file in files:
if file.endswith(extension):
# print(os.path.join(rooter, file))
self.log(os.path.join(rooter, file))
found = True
return os.path.join(rooter, file)
if not found:
print(f"没有找到扩展名为 {extension} 的文件。")
def start_analysis(self):
# 获取用户输入的值
dll_path = self.dll_path.get()
# axf_path = self.axf_path.get()
axf_path = self.find_files_with_extension(self.prj_path.get(), '.axf')
# map_path = self.map_path.get()
map_path = self.find_files_with_extension(self.prj_path.get(), '.map')
start_address = self.start_address.get()
length = self.length.get()
# 添加设备名称输入
# device_name = self.device_name.get() # 获取设备名称
device_name = "GD32F303ZE"
# 输入验证
if not all([dll_path, axf_path, map_path, start_address, length]):
messagebox.showwarning("警告", "所有字段都必须填写。")
return
# 创建线程来运行分析脚本并捕获输出
thread = threading.Thread(target=self.run_analysis_script,
args=(dll_path, axf_path, map_path, start_address, length, device_name))
thread.start()
# jlink_operations.run_jlink_script(dll_path, axf_path, start_address, length, device_name)
def run_analysis_script(self, dll_path, axf_path, map_path, start_address, length, device_name):
# 使用 StringIO 重定向 stdout 和 stderr
output_buffer = io.StringIO()
error_buffer = io.StringIO()
sys.stdout = output_buffer # 重定向 stdout
sys.stderr = error_buffer # 重定向 stderr
try:
# 调用 jlink_operations 中的方法
jlink_operations.run_jlink_script(dll_path, axf_path, start_address, length, device_name)
# 获取输出并放入日志队列
output = output_buffer.getvalue()
# error_output = error_buffer.getvalue()
# 处理输出,去掉每行开头的"Error:"前缀
for line in output.splitlines():
if line.startswith("Error:"):
line = line[len("Error:"):].lstrip() # 去掉"Error:"前缀和任何前导空格
self.log(line)
# self.log_queue.put(output + error_output) # 合并输出
self.log("提取完成")
except Exception as e:
self.log(f"运行分析脚本时出错: {str(e)}")
finally:
sys.stdout = sys.__stdout__ # 恢复 stdout
sys.stderr = sys.__stderr__ # 恢复 stderr
def analyze_stack_info(self):
"""执行分析栈信息的 Python 脚本"""
self.log("\nStack trace analysis begins")
dll_path = self.dll_path.get()
# axf_file = self.axf_path.get() # 从用户输入获取 AXF 文件路径
axf_file = self.find_files_with_extension(self.prj_path.get(), '.axf')
axf_directory = os.path.dirname(axf_file)
# 将 bin_file_path 指向 axf_directory 同目录下的 ram.bin
bin_file = os.path.join(axf_directory, 'ram.bin')
reg_dump_file = os.path.join(axf_directory, 'registers.txt')
# map_file = self.map_path.get() # 从用户输入获取 MAP 文件路径
map_file = self.find_files_with_extension(self.prj_path.get(), '.map')
if not all([dll_path, axf_file, map_file]):
messagebox.showwarning("警告", "前三行字段都必须填写。")
return
# 创建线程来运行分析脚本
thread = threading.Thread(target=self.execute_python_script, args=(axf_file, bin_file),
kwargs={'reg_dump': reg_dump_file, 'map_file': map_file})
thread.start()
def execute_python_script(self, axf_file, bin_file, reg_dump=None, map_file=None):
# 使用 StringIO 重定向 stdout 和 stderr
output_buffer = io.StringIO()
error_buffer = io.StringIO()
sys.stdout = output_buffer # 重定向 stdout
sys.stderr = error_buffer # 重定向 stderr
try:
tracer = StackTracer(axf_file, bin_file, reg_dump, map_file)
tracer.analyze() # 调用分析方法
output = output_buffer.getvalue()
error_output = error_buffer.getvalue()
self.log_queue.put(output + error_output) # 合并输出
self.log_queue.put("分析完成")
except Exception as e:
self.log("分析出错: " + str(e))
finally:
sys.stdout = sys.__stdout__ # 恢复 stdout
sys.stderr = sys.__stderr__ # 恢复 stderr
def read_process_output(self, process):
for line in process.stdout:
self.log_queue.put(line.strip())
for line in process.stderr:
self.log_queue.put(line.strip())
# 子进程结束后从列表中移除
self.processes.remove(process)
def process_log_queue(self):
"""定时检查日志队列并更新日志窗口"""
try:
while True: # 从队列中获取所有消息
message = self.log_queue.get_nowait()
self.log(message)
except queue.Empty:
pass
self.root.after(100, self.process_log_queue)
def log(self, message):
"""在日志窗口中添加信息"""
# 开启日志窗口的可编辑状态
self.log_window.config(state="normal")
# 按原样插入子程序输出内容,确保格式保持不变
self.log_window.insert(tk.END, message + "\n")
# 禁用编辑状态并保持滚动到最底部
self.log_window.config(state="disabled")
self.log_window.yview(tk.END)
def on_closing(self):
"""关闭窗口时,终止所有子进程并退出程序"""
for process in self.processes:
process.terminate() # 尝试优雅终止
process.wait() # 等待进程结束
self.root.destroy() # 销毁主窗口
def clear_log(self):
"""清空日志窗口内容"""
self.log_window.config(state="normal")
self.log_window.delete("1.0", tk.END)
self.log_window.config(state="disabled")
if __name__ == "__main__":
root = tk.Tk()
app = AnalysisApp(root)
# root.iconbitmap("favicon.ico") # 设置窗口图标(.ico格式)
root.mainloop()
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。