MCU code fault analysis

记录在MCU出现未按照预期工作的解决方案或思路

Bootloader正常运行,APP无法无法正常运行

检查是否APP本身问题

  1. 单独运行APP代码,不需要Bootloader,检查代码是否正常运行

检查APP配置问题

  1. 检查中断向量偏移地址
  2. 检查APP flash起始地址
  3. 检查APP flash栈地址
  4. 检查APP flash复位地址

检查Bootloader跳转APP时,设置的栈地址和复位地址是否正确

IAP/ISP/OTA烧录完APP后的等待1s左右,重新读取FLASH,检查固件和烧录的是否一致

检查调试器HID灯是否闪烁,曾看到PC端出现错误到时pyocd等烧录擦除进程被周期调用,最终结果擦出了MCU固件

  1. windows脚本检测pyocd是否周期性调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# monitor_pyocd.py
import psutil
import time
import datetime
import win32api
import win32con
import win32process
import win32security
from collections import defaultdict
import os
import sys

def get_process_tree(pid):
"""获取进程树信息"""
try:
proc = psutil.Process(pid)
tree_info = {
'pid': pid,
'name': proc.name(),
'exe': proc.exe() if proc.exe() else '',
'cmdline': ' '.join(proc.cmdline()) if proc.cmdline() else '',
'create_time': datetime.datetime.fromtimestamp(proc.create_time()),
'username': proc.username(),
'parent': None,
'children': []
}

# 获取父进程
try:
parent = proc.parent()
if parent:
tree_info['parent'] = parent.pid
except:
pass

return tree_info
except (psutil.NoSuchProcess, psutil.AccessDenied):
return None

def get_detailed_process_info(pid):
"""获取详细的进程信息(Windows API)"""
try:
# 获取进程句柄
handle = win32api.OpenProcess(
win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ,
False, pid
)

# 获取进程令牌信息
token = win32security.OpenProcessToken(handle, win32con.TOKEN_QUERY)
user_sid = win32security.GetTokenInformation(token, win32security.TokenUser)[0]
username = win32security.LookupAccountSid(None, user_sid)[0]

# 获取进程路径
exe_path = win32process.GetModuleFileNameEx(handle, 0)

# 获取命令行(需要更高级的权限)
cmdline = ""
try:
import ctypes
from ctypes import wintypes

kernel32 = ctypes.windll.kernel32

class PROCESS_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [
("ExitStatus", wintypes.DWORD),
("PebBaseAddress", ctypes.POINTER(ctypes.c_void_p)),
("AffinityMask", ctypes.POINTER(ctypes.c_size_t)),
("BasePriority", wintypes.DWORD),
("UniqueProcessId", ctypes.POINTER(ctypes.c_size_t)),
("InheritedFromUniqueProcessId", ctypes.POINTER(ctypes.c_size_t)),
]

GetProcessInformation = kernel32.NtQueryInformationProcess
GetProcessInformation.argtypes = [
wintypes.HANDLE,
wintypes.DWORD,
ctypes.POINTER(PROCESS_BASIC_INFORMATION),
wintypes.ULONG,
ctypes.POINTER(wintypes.ULONG)
]

pbi = PROCESS_BASIC_INFORMATION()
size = wintypes.ULONG()
GetProcessInformation(
handle, 0,
ctypes.byref(pbi),
ctypes.sizeof(PROCESS_BASIC_INFORMATION),
ctypes.byref(size)
)

# 读取PEB获取命令行
# 这里简化处理,实际需要更复杂的PEB解析
except:
pass

win32api.CloseHandle(handle)

return {
'username': username,
'exe_path': exe_path,
'cmdline': cmdline
}

except Exception as e:
return {'error': str(e)}

def monitor_pyocd_processes(monitor_duration=60, check_interval=1):
"""监控pyocd进程"""
print(f"[{datetime.datetime.now()}] 开始监控pyocd进程,持续{monitor_duration}秒...")
print("按 Ctrl+C 停止监控")
print("=" * 80)

seen_pids = set()
event_log = []

try:
start_time = time.time()
while time.time() - start_time < monitor_duration:
# 查找所有pyocd进程
for proc in psutil.process_iter(['pid', 'name', 'create_time']):
try:
if 'pyocd' in proc.info['name'].lower():
pid = proc.info['pid']

# 如果是新发现的进程
if pid not in seen_pids:
seen_pids.add(pid)

# 获取详细信息
timestamp = datetime.datetime.now()
tree_info = get_process_tree(pid)

if tree_info:
# 记录事件
event = {
'timestamp': timestamp,
'pid': pid,
'process_name': tree_info['name'],
'exe_path': tree_info['exe'],
'cmdline': tree_info['cmdline'],
'username': tree_info['username'],
'parent_pid': tree_info['parent'],
'create_time': tree_info['create_time']
}

event_log.append(event)

# 打印信息
print(f"[{timestamp}] 发现新的pyocd进程!")
print(f" PID: {pid}")
print(f" 进程名: {tree_info['name']}")
print(f" 路径: {tree_info['exe']}")
print(f" 命令行: {tree_info['cmdline'][:100]}..." if len(tree_info['cmdline']) > 100 else f" 命令行: {tree_info['cmdline']}")
print(f" 用户名: {tree_info['username']}")
print(f" 创建时间: {tree_info['create_time']}")
print(f" 父进程PID: {tree_info['parent']}")

# 获取父进程信息
if tree_info['parent']:
try:
parent_proc = psutil.Process(tree_info['parent'])
print(f" 父进程: {parent_proc.name()} (PID: {tree_info['parent']})")
print(f" 父进程路径: {parent_proc.exe()}")
except:
print(f" 父进程: 无法获取信息")

print("-" * 60)
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue

time.sleep(check_interval)

except KeyboardInterrupt:
print("\n用户中断监控")

# 生成报告
print("\n" + "=" * 80)
print(f"监控报告 ({datetime.datetime.now()}):")
print(f"监控时长: {monitor_duration}秒")
print(f"发现pyocd进程次数: {len(event_log)}")

if event_log:
print("\n详细事件记录:")
for i, event in enumerate(event_log, 1):
print(f"\n事件 #{i}:")
print(f" 时间: {event['timestamp']}")
print(f" PID: {event['pid']}")
print(f" 进程: {event['process_name']}")
print(f" 路径: {event['exe_path']}")
print(f" 用户: {event['username']}")
if event['parent_pid']:
print(f" 父进程PID: {event['parent_pid']}")

return event_log

def analyze_trigger_patterns(events):
"""分析触发模式"""
if not events:
return

print("\n" + "=" * 80)
print("触发模式分析:")

# 按分钟统计触发频率
minute_counts = defaultdict(int)
for event in events:
minute_key = event['timestamp'].strftime("%Y-%m-%d %H:%M")
minute_counts[minute_key] += 1

print("\n按分钟统计触发频率:")
for minute, count in sorted(minute_counts.items()):
print(f" {minute}: {count}次")

# 分析父进程
parent_counts = defaultdict(int)
parent_names = {}
for event in events:
if event['parent_pid']:
parent_counts[event['parent_pid']] += 1
try:
parent_proc = psutil.Process(event['parent_pid'])
parent_names[event['parent_pid']] = parent_proc.name()
except:
parent_names[event['parent_pid']] = "未知"

if parent_counts:
print("\n父进程统计:")
for pid, count in sorted(parent_counts.items(), key=lambda x: x[1], reverse=True):
print(f" 父进程: {parent_names.get(pid, '未知')} (PID: {pid}): {count}次")

def save_report(events, filename="pyocd_monitor_report.txt"):
"""保存监控报告"""
with open(filename, 'w', encoding='utf-8') as f:
f.write(f"pyocd进程监控报告\n")
f.write(f"生成时间: {datetime.datetime.now()}\n")
f.write("=" * 80 + "\n\n")

f.write(f"监控到的事件总数: {len(events)}\n\n")

for i, event in enumerate(events, 1):
f.write(f"事件 #{i}:\n")
f.write(f" 时间: {event['timestamp']}\n")
f.write(f" PID: {event['pid']}\n")
f.write(f" 进程名: {event['process_name']}\n")
f.write(f" 路径: {event['exe_path']}\n")
f.write(f" 命令行: {event['cmdline']}\n")
f.write(f" 用户名: {event['username']}\n")
f.write(f" 父进程PID: {event['parent_pid']}\n")
f.write("-" * 60 + "\n")

if __name__ == "__main__":
# 检查是否是管理员权限
try:
is_admin = os.getuid() == 0
except AttributeError:
import ctypes
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0

if not is_admin:
print("警告: 建议以管理员身份运行此脚本以获取完整信息")
print("右键点击脚本 -> 以管理员身份运行\n")

# 开始监控
events = monitor_pyocd_processes(monitor_duration=120, check_interval=1)

# 分析模式
analyze_trigger_patterns(events)

# 保存报告
if events:
save_report(events)
print(f"\n详细报告已保存到: pyocd_monitor_report.txt")