Coverage for /opt/cloud/slavespace/usr1/096471637100f3de0fcfc01072822a80/ut/src/asys/analyze/coredump_analyze.py: 88%

253 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-14 17:42 +0800

1#!/usr/bin/env python3 

2# -*- coding: utf-8 -*- 

3# ---------------------------------------------------------------------------- 

4# Copyright (c) 2025 Huawei Technologies Co., Ltd. 

5# 

6# Licensed under the Apache License, Version 2.0 (the "License"); 

7# you may not use this file except in compliance with the License. 

8# You may obtain a copy of the License at 

9# 

10# http://www.apache.org/licenses/LICENSE-2.0 

11# 

12# Unless required by applicable law or agreed to in writing, software 

13# distributed under the License is distributed on an "AS IS" BASIS, 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

15# See the License for the specific language governing permissions and 

16# limitations under the License. 

17# ---------------------------------------------------------------------------- 

18 

19import re 

20import subprocess 

21from platform import machine 

22from multiprocessing import Pool, cpu_count, Manager 

23 

24from common import log_error, log_warning 

25from common.task_common import is_hexadecimal, int_to_hex 

26from common.const import REG_OFF, REG_THREAD, REG_STACK, ADDR_LEN_HEX, ADDR_BIT_LEN, GDB_LAYER_MAX 

27from params import ParamDict 

28 

29 

30def _get_reg_info_cmd(): 

31 sys_type = machine() 

32 if sys_type == "x86_64": 

33 reg_cmd = f"info reg rbp rsp rip\n" 

34 elif sys_type == "aarch64": 

35 reg_cmd = f"info reg x29 sp pc\n" 

36 else: 

37 reg_cmd = "" 

38 return reg_cmd 

39 

40 

41def thread_stacks_reg_info(cmd, thread, stacks, queue_reg_info): 

42 """ 

43 get thread all stacks reg value 

44 """ 

45 thread_id = thread.split(" ")[1] 

46 gdb_process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 

47 bufsize=1, encoding='utf-8', errors='ignore', text=True) 

48 _reg_info = {thread: dict()} 

49 gdb_process.stdin.write(f"thread {thread_id}\n") 

50 

51 reg_cmd = _get_reg_info_cmd() 

52 if not reg_cmd: 

53 return False 

54 

55 for stack in stacks: 

56 stack_id = stack.split(" ")[0][1:] 

57 reg_list = [None, None, None] 

58 gdb_process.stdin.write(f"frame {stack_id}\n") 

59 gdb_process.stdin.write(reg_cmd) 

60 while True: 

61 line = gdb_process.stdout.readline().rstrip() 

62 gdb_process.stdout.flush() 

63 data_list = line.strip("\n").strip(" ").split() 

64 if line.startswith("(gdb) rbp ") or line.startswith("(gdb) x29 "): 

65 reg_list[0] = data_list[2] 

66 continue 

67 if line.startswith("rsp ") or line.startswith("sp "): 

68 reg_list[1] = data_list[1] 

69 continue 

70 if line.startswith("rip ") or line.startswith("pc "): 

71 reg_list[2] = data_list[1] 

72 

73 if all(reg_list): 

74 line_num = f"#{stack_id}" 

75 # #0 -> #00 

76 if len(line_num) == 2: 

77 line_num = f"#0{stack_id}" 

78 _reg_info[thread][line_num] = reg_list 

79 break 

80 gdb_process.stdin.write("quit\n") 

81 gdb_process.stdin.write("y\n") 

82 

83 queue_reg_info.put(_reg_info) 

84 return True 

85 

86 

87class CoreDump: 

88 def __init__(self, exe_file, core_file, symbol, output): 

89 self.exe_file = exe_file 

90 self.core_file = core_file 

91 self.symbol = symbol 

92 self.output = output 

93 self.bt_info = dict() 

94 self.map_info = list() 

95 self.map_str = "[maps]\n" 

96 self.reg_level = ParamDict().get_arg("reg") 

97 

98 @staticmethod 

99 def check_map_line(data_list): 

100 """ 

101 Check whether the data row is in the mapping format. 

102 """ 

103 if len(data_list) < 5: 

104 return False 

105 if not (is_hexadecimal(data_list[0]) and is_hexadecimal(data_list[1]) and is_hexadecimal(data_list[2])): 

106 return False 

107 return True 

108 

109 @staticmethod 

110 def _get_gdb_cmd(exe_file, core_file): 

111 return ["gdb", exe_file, core_file] 

112 

113 def collect_info(self, thread_name, data_list, line, before_line): 

114 """ 

115 Collect stack information and map table information. 

116 """ 

117 if re.match(r'#(\d+)', data_list[0]) and thread_name: 

118 if self.bt_info.get(thread_name): 

119 self.bt_info[thread_name].append(line) 

120 else: 

121 self.bt_info[thread_name] = [line] 

122 elif data_list[0] == "Start" and data_list[-1] == "objfile": 

123 self.map_str += (line[6:] + "\n") 

124 elif self.check_map_line(data_list): 

125 self.map_str += (line[6:] + "\n") 

126 if data_list[-1] != before_line: 

127 self.map_info.append([int_to_hex(data_list[0]), int_to_hex(data_list[1]), data_list[-1]]) 

128 else: 

129 if int_to_hex(data_list[0]) < self.map_info[-1][0]: 

130 self.map_info[-1][0] = int_to_hex(data_list[0]) 

131 if int_to_hex(data_list[1]) > self.map_info[-1][1]: 

132 self.map_info[-1][1] = int_to_hex(data_list[1]) 

133 before_line = data_list[-1] 

134 return before_line 

135 

136 def view_map(self, stack_txt, bt_line, reg_info): 

137 """ 

138 Obtain the stack start address and dynamic library from the mapping table. 

139 """ 

140 bt_list = bt_line.strip("\n").strip(" ").split() 

141 # #0 -> #00 

142 if len(bt_list[0]) == 2: 

143 bt_list[0] = f"#0{bt_list[0][1]}" 

144 address = int_to_hex(bt_list[1]) 

145 for values in self.map_info: 

146 start_address, end_address, key = values 

147 if int(start_address, ADDR_BIT_LEN) < int(address, ADDR_BIT_LEN) < int(end_address, ADDR_BIT_LEN): 

148 stack_txt += f"{bt_list[0]} 0x{address[2:].rjust(ADDR_BIT_LEN, '0')} " \ 

149 f"0x{start_address[2:].rjust(ADDR_BIT_LEN, '0')} {key}\n" 

150 stack_txt = self._stack_add_reg(stack_txt, bt_list[0], reg_info) 

151 return stack_txt 

152 if self.symbol: 

153 stack_txt += " ".join(bt_list) 

154 stack_txt += "\n" 

155 stack_txt = self._stack_add_reg(stack_txt, bt_list[0], reg_info) 

156 

157 return stack_txt 

158 

159 @staticmethod 

160 def _get_reg_str(start_str, reg_values): 

161 if not reg_values or len(reg_values) != 3: 

162 return "" 

163 

164 reg_cmd = _get_reg_info_cmd() 

165 line_start = " " * (len(start_str) + 1) 

166 if "x29 sp pc" in reg_cmd: 

167 reg_str = f"{line_start}fp = {reg_values[0]} sp = {reg_values[1]}\n{line_start}pc = {reg_values[2]}\n" 

168 elif "rbp rsp rip" in reg_cmd: 

169 reg_str = f"{line_start}rbp = {reg_values[0]} rsp = {reg_values[1]}\n{line_start}rip = {reg_values[2]}\n" 

170 else: 

171 reg_str = "" 

172 return reg_str 

173 

174 def _stack_add_reg(self, stack_txt, stack_id, reg_info): 

175 # stack_txt add stack reg_data 

176 if not reg_info or isinstance(reg_info, list): 

177 return stack_txt 

178 reg_values = reg_info.get(stack_id) 

179 stack_txt += self._get_reg_str(stack_id, reg_values) 

180 

181 return stack_txt 

182 

183 def parse_stackcore(self, stack_txt, bt_lines, reg_info=None): 

184 if self.reg_level == REG_THREAD: 

185 stack_txt += self._get_reg_str("", reg_info) 

186 for bt_line in bt_lines: 

187 bt_list = bt_line.strip("\n").strip(" ").split() 

188 # #0 -> #00 

189 if len(bt_list[0]) == 2: 

190 bt_list[0] = f"#0{bt_list[0][1]}" 

191 if self.symbol and "in ??" not in bt_line: 

192 stack_txt += " ".join(bt_list) 

193 stack_txt += "\n" 

194 stack_txt = self._stack_add_reg(stack_txt, bt_list[0], reg_info) 

195 continue 

196 if not is_hexadecimal(bt_list[1]): 

197 stack_txt += f"{bt_list[0]} {' ' * ADDR_LEN_HEX} {' ' * ADDR_LEN_HEX} Ignore\n" 

198 continue 

199 stack_txt = self.view_map(stack_txt, bt_line, reg_info) 

200 return stack_txt 

201 

202 def _get_reg_info_level_stack(self): 

203 queue_reg_info = Manager().Queue() 

204 cmd = self._get_gdb_cmd(self.exe_file, self.core_file) 

205 p = Pool(cpu_count() - 1) 

206 for thread, stacks in self.bt_info.items(): 

207 p.apply_async(thread_stacks_reg_info, args=(cmd, thread, stacks, queue_reg_info)) 

208 p.close() 

209 p.join() 

210 

211 threads_stacks_reg_info = dict() 

212 while queue_reg_info.qsize() > 0: 

213 for thread, stacks in queue_reg_info.get().items(): 

214 threads_stacks_reg_info[thread] = stacks 

215 return threads_stacks_reg_info 

216 

217 def _get_reg_info_level_thread(self): 

218 gdb_process = subprocess.Popen(self._get_gdb_cmd(self.exe_file, self.core_file), 

219 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 

220 encoding='utf-8', errors='ignore', text=True, bufsize=1) 

221 _reg_info = dict() 

222 reg_cmd = _get_reg_info_cmd() 

223 if not reg_cmd: 

224 return _reg_info 

225 

226 for thread in self.bt_info.keys(): 

227 thread_id = thread.split(" ")[1] 

228 gdb_process.stdin.write(f"thread {thread_id}\n") 

229 gdb_process.stdin.write(reg_cmd) 

230 reg_list = [None, None, None] 

231 while True: 

232 line = gdb_process.stdout.readline().rstrip() 

233 gdb_process.stdout.flush() 

234 data_list = line.strip("\n").strip(" ").split() 

235 if line.startswith("(gdb) rbp ") or line.startswith("(gdb) x29 "): 

236 reg_list[0] = data_list[2] 

237 continue 

238 if line.startswith("rsp ") or line.startswith("sp "): 

239 reg_list[1] = data_list[1] 

240 continue 

241 if line.startswith("rip ") or line.startswith("pc "): 

242 reg_list[2] = data_list[1] 

243 

244 if all(reg_list): 

245 _reg_info[thread] = reg_list 

246 break 

247 gdb_process.stdin.write("quit\n") 

248 gdb_process.stdin.write("y\n") 

249 

250 return _reg_info 

251 

252 def get_threads_stacks_reg_info(self): 

253 if self.reg_level == REG_OFF: 

254 return {} 

255 elif self.reg_level == REG_THREAD: 

256 return self._get_reg_info_level_thread() 

257 elif self.reg_level == REG_STACK: 

258 return self._get_reg_info_level_stack() 

259 else: 

260 return {} 

261 

262 def start_gdb(self, stack_txt): 

263 try: 

264 gdb_process = subprocess.Popen(self._get_gdb_cmd(self.exe_file, self.core_file), 

265 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 

266 encoding='utf-8', errors='ignore', text=True) 

267 except FileNotFoundError: 

268 log_error("Failed to obtain the core dump information.") 

269 return stack_txt, 0 

270 gdb_process.stdin.write("info inferiors\n") 

271 gdb_process.stdin.write("info sharedlibrary\n") 

272 gdb_process.stdin.write(f"thread apply all bt {GDB_LAYER_MAX}\n") 

273 gdb_process.stdin.write("info proc mappings\n") 

274 gdb_process.stdin.write("quit\n") 

275 gdb_process.stdin.write("y\n") 

276 console_output, _ = gdb_process.communicate() 

277 out_lines = console_output.split("\n") 

278 thread_name = None 

279 pid = 0 

280 before_line = "" 

281 crash_info = {"crash reason": "", "crash pid": "", "crash tid": ""} 

282 for line in out_lines: 

283 if f"{self.exe_file}: No such file or directory" in line: 

284 log_error(line) 

285 return stack_txt, pid 

286 if "core file may not match specified executable file" in line: 

287 log_warning("Core file may not match specified executable file") 

288 continue 

289 if line.startswith("Program terminated"): 

290 crash_info["crash reason"] = f"{line.split('signal')[-1].strip(' ').split(',')[0]}" 

291 continue 

292 data_list = line.strip("\n").strip(" ").split() 

293 if "Current thread" in line: 

294 crash_info["crash tid"] = re.match(r"(\d+)", data_list[-1])[0] 

295 continue 

296 if data_list and data_list[0] == "No": 

297 log_warning(f'Could not load shared library symbols for "{data_list[-1]}", parsing errors may occur.') 

298 continue 

299 if len(data_list) < 2: 

300 continue 

301 if line.startswith("*") and "process" == data_list[2]: 

302 pid = data_list[3] 

303 continue 

304 if line.startswith("Thread") and "LWP" in line: 

305 tid = re.search(r"LWP (\d+)", line).group(1) 

306 thread_name = f"Thread {data_list[1]} ({tid})" 

307 continue 

308 before_line = self.collect_info(thread_name, data_list, line, before_line) 

309 stack_txt, pid = self._process_stack_txt(crash_info, pid, stack_txt) 

310 return stack_txt, pid 

311 

312 def _process_stack_txt(self, crash_info, pid, stack_txt): 

313 if not self.map_info or not self.bt_info: 

314 log_error("Failed to obtain the core dump information.") 

315 return stack_txt, pid 

316 

317 crash_info["crash pid"] = pid 

318 

319 for crash_key, crash_value in crash_info.items(): 

320 stack_txt += f"{crash_key}: {crash_value}\n" 

321 

322 stack_txt += "\n" 

323 stack_txt += "[stack]\n" 

324 threads_stacks_reg_info = self.get_threads_stacks_reg_info() 

325 for stack_name, bt_lines in self.bt_info.items(): 

326 stack_txt += (stack_name + "\n") 

327 stacks_reg_info = threads_stacks_reg_info.get(stack_name) 

328 stack_txt = self.parse_stackcore(stack_txt, bt_lines, stacks_reg_info) 

329 stack_txt += "\n" 

330 stack_txt += self.map_str 

331 return stack_txt, pid