代码拉取完成,页面将自动刷新
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()
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。