1 | // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. |
2 | // |
3 | // SPDX-License-Identifier: GPL-3.0-or-later |
4 | |
5 | #include <assert.h> |
6 | #include <unistd.h> |
7 | #include <fcntl.h> |
8 | #include <string.h> |
9 | #include <errno.h> |
10 | #include <stdio.h> |
11 | #include <stdlib.h> |
12 | #include <limits.h> |
13 | #include <elf.h> |
14 | #include <link.h> |
15 | #include <unistd.h> |
16 | #include <zstd.h> |
17 | #include <iomanip> |
18 | |
19 | #include "trace_writer.h" |
20 | #include "easylogging++.h" |
21 | #include "trace_reader.h" |
22 | #include "md_types.h" |
23 | |
24 | using namespace std; |
25 | |
26 | static double timespec2ms(const struct timespec* spec) |
27 | { |
28 | return (spec->tv_sec*1000.0 + spec->tv_nsec/(1000.0*1000.0)); |
29 | } |
30 | |
31 | static long get_file_size(const char* filename) |
32 | { |
33 | struct stat st; |
34 | stat(filename, &st); |
35 | return st.st_size; |
36 | } |
37 | |
38 | static bool decompress_file(FILE* fin, FILE* fout) |
39 | { |
40 | bool ret = false; |
41 | size_t buffInSize = 0; |
42 | void* buffIn = nullptr; |
43 | /* Guarantee to successfully flush at least one complete compressed block in all |
44 | * circumstances. */ |
45 | size_t buffOutSize = 0; |
46 | void* buffOut = nullptr; |
47 | ZSTD_DStream* dstream = nullptr; |
48 | size_t initResult = 0; |
49 | size_t read = 0, toRead = 0; |
50 | |
51 | buffInSize = ZSTD_DStreamInSize(); |
52 | buffIn = malloc(buffInSize); |
53 | /* Guarantee to successfully flush at least one complete compressed block in all |
54 | * circumstances. */ |
55 | buffOutSize = ZSTD_DStreamOutSize(); |
56 | buffOut = malloc(buffOutSize); |
57 | |
58 | dstream = ZSTD_createDStream(); |
59 | if (dstream==NULL) { |
60 | LOG(ERROR) << "ZSTD_createDStream() error" ; |
61 | goto error_found; |
62 | } |
63 | |
64 | /* In more complex scenarios, a file may consist of multiple appended frames (ex : pzstd). |
65 | * The following example decompresses only the first frame. |
66 | * It is compatible with other provided streaming examples */ |
67 | initResult = ZSTD_initDStream(dstream); |
68 | if (ZSTD_isError(initResult)) { |
69 | LOG(ERROR) << "ZSTD_initDStream() error : " |
70 | << ZSTD_getErrorName(initResult); |
71 | } |
72 | |
73 | toRead = initResult; |
74 | while ( (read = fread(buffIn, 1, toRead, fin)) ) { |
75 | ZSTD_inBuffer input = {buffIn, read, 0}; |
76 | |
77 | while (input.pos < input.size) { |
78 | ZSTD_outBuffer output = { buffOut, buffOutSize, 0 }; |
79 | /* toRead : size of next compressed block */ |
80 | toRead = ZSTD_decompressStream(dstream, &output , &input); |
81 | if (ZSTD_isError(toRead)) { |
82 | LOG(ERROR) << "ZSTD_decompressStream() error : " |
83 | << ZSTD_getErrorName(toRead); |
84 | goto error_found; |
85 | } |
86 | if (fwrite(buffOut, 1, output.pos, fout) != output.pos) { |
87 | LOG(ERROR) << "failed to write error : " << errno; |
88 | goto error_found; |
89 | } |
90 | } |
91 | } |
92 | |
93 | ret = true; |
94 | |
95 | error_found: |
96 | if (dstream) ZSTD_freeDStream(dstream); |
97 | if (buffIn) free(buffIn); |
98 | if (buffOut) free(buffOut); |
99 | |
100 | return ret; |
101 | } |
102 | |
103 | static FILE* decompress(const char* filename, long* fsize) |
104 | { |
105 | char path[NAME_MAX]; |
106 | char path2[NAME_MAX]; |
107 | FILE* ret = nullptr; |
108 | FILE* fout = nullptr; |
109 | FILE* fin = nullptr; |
110 | |
111 | snprintf(path, sizeof(path), "/tmp/%s.dec" , basename(filename)); |
112 | if (access(path, F_OK) == 0) { |
113 | // has decompressed ! |
114 | *fsize = get_file_size(path); |
115 | if (*fsize > 0) return fopen(path, "rb" ); |
116 | } |
117 | |
118 | fin = fopen(filename, "rb" ); |
119 | if (nullptr == fin) { |
120 | LOG(ERROR) << "failed to open " << filename << ", errno=" << errno; |
121 | goto error_found; |
122 | } |
123 | |
124 | fout = fopen(path, "wb" ); |
125 | if (nullptr == fout) { |
126 | LOG(ERROR) << "failed to create " << path << ", errno=" << errno; |
127 | goto error_found; |
128 | } |
129 | |
130 | decompress_file (fin, fout); |
131 | |
132 | // test xxx.clone is exist |
133 | snprintf(path2, sizeof(path2), "%s.clone" , filename); |
134 | if (access(path2, F_OK) == 0) { |
135 | fclose(fin); |
136 | fin = fopen(path2, "rb" ); |
137 | if (nullptr != fin) { |
138 | decompress_file (fin, fout); |
139 | } |
140 | } |
141 | |
142 | error_found: |
143 | if (fin) fclose(fin); |
144 | if (fout) fclose(fout); |
145 | |
146 | *fsize = get_file_size(path); |
147 | if (*fsize > 0) ret = fopen(path, "rb" ); |
148 | |
149 | return ret; |
150 | } |
151 | |
152 | bool TraceReader::Open(const char* filename, bool zstd) |
153 | { |
154 | if (!zstd) { |
155 | fsize = get_file_size(filename); |
156 | if (fsize > 0) fin = fopen(filename, "rb" ); |
157 | } else { |
158 | fin = decompress(filename, &fsize); |
159 | } |
160 | |
161 | return fin != nullptr; |
162 | } |
163 | |
164 | TraceReader::TraceReader(void) |
165 | { |
166 | fin = nullptr; |
167 | fsize = 0; |
168 | } |
169 | |
170 | TraceReader::~TraceReader(void) |
171 | { |
172 | fclose(fin); |
173 | fin = nullptr; |
174 | } |
175 | |
176 | int TraceReader::ReadBlock(string& out) |
177 | { |
178 | /*NOTE: block-size-field occupied 4bytes*/ |
179 | int size = 0; |
180 | if (!ReadValue<int>(&size)) { |
181 | out.clear(); |
182 | return 0; //file reach end! |
183 | } |
184 | |
185 | if (size > 0) { |
186 | out.resize(size, '\0'); |
187 | int bytes = fread((void*)out.data(), 1, size, fin); |
188 | return bytes; |
189 | } |
190 | |
191 | out.clear(); |
192 | out.insert(0, "empty or invalid block size" ); // return empty will cause the caller crash |
193 | return 0; |
194 | } |
195 | |
196 | int TraceReader::ReadBlock(void* buf, int size) |
197 | { |
198 | /*NOTE: no block-size-field field*/ |
199 | int bytes = fread(buf, 1, size, fin); |
200 | return bytes; |
201 | } |
202 | |
203 | static bool update_syscall_result(std::vector<EventEntry>* table, EventEntry& event) |
204 | { |
205 | uint16_t syscall = event.type - DUMP_REASON_syscall_exit; |
206 | for (auto i = table->rbegin(); i != table->rend(); ++i) { |
207 | if (event.tid == i->tid && i->type == syscall) { |
208 | assert(i->time <= event.time); |
209 | i->syscall_result = event.syscall_result; |
210 | i->duration = event.time - i->time; |
211 | LOG(DEBUG) << " " << get_event_name(event.type) |
212 | << " duration:" << i->duration; |
213 | return true; |
214 | } |
215 | } |
216 | |
217 | LOG(ERROR) << "Not found syscall enter for tracee " << event.tid |
218 | << " syscall=" << syscall << " at " << event.offset; |
219 | return false; |
220 | } |
221 | |
222 | bool TraceReader::BuildEventTable(std::vector<EventEntry>* table) |
223 | { |
224 | EventEntry event; |
225 | int size = 0; |
226 | uint32_t heap_count = 0; |
227 | uint32_t tid = 0; |
228 | long stack_addr = 0; |
229 | struct timespec event_time; |
230 | |
231 | for (;;) |
232 | { |
233 | event.extra_size = 0; |
234 | event.syscall_result = 0; |
235 | event.offset = ftell(fin); |
236 | |
237 | if (event.offset >= fsize) { |
238 | LOG(DEBUG) << "reach end at " << event.offset << ">=" << fsize; |
239 | break; |
240 | } |
241 | |
242 | if (!ReadBlock(&event_time, sizeof(event_time)) || |
243 | (0 == event_time.tv_sec)){ |
244 | LOG(ERROR) << "invalid time stamp at " |
245 | << event.offset << "/" << fsize; |
246 | break; // reach end, zero bytes padding to 128K! |
247 | } |
248 | event.time = timespec2ms(&event_time); |
249 | |
250 | if (!ReadValue<int16_t>(&event.type) || (event.type >= DUMP_REASON_ptrace)) { |
251 | LOG(ERROR) << "invalid event type at " |
252 | << event.offset << "/" << fsize; |
253 | break; |
254 | } |
255 | if (!ReadValue<uint16_t>(&event.thread_num)) { |
256 | LOG(ERROR) << "invalid thread count at " |
257 | << event.offset << "/" << fsize; |
258 | break; // reach end, zero bytes padding to 128K! |
259 | } |
260 | if (!ReadValue<int>(&size)) { |
261 | LOG(ERROR) << "invalid tid at " |
262 | << event.offset << "/" << fsize; |
263 | break; |
264 | } |
265 | event.tid = size & 0xffff; |
266 | size >>= 16; |
267 | if (event.type < __NR_Linux) { |
268 | if (sizeof(long) != size) { |
269 | LOG(ERROR) << "invalid syscall result at " |
270 | << event.offset << "/" << fsize; |
271 | break; |
272 | } |
273 | |
274 | ReadValue<long>(&event.syscall_result); |
275 | update_syscall_result(table, event); |
276 | |
277 | LOG(DEBUG) << " " << get_event_name(event.type) |
278 | << " exit for tracee " << event.tid |
279 | << " ret=" << event.syscall_result |
280 | << " time=" << event.time |
281 | << " at [" << event.offset |
282 | << ", " << ftell(fin) << "]" ; |
283 | continue; |
284 | } |
285 | else { |
286 | // skip event extra data, e.g. DBusMessage, XEvent. |
287 | event.extra_size = size; |
288 | if (size == sizeof(long)) { |
289 | // load syscall result recorded in fast mode! |
290 | ReadValue<long>(&event.syscall_result); |
291 | } |
292 | else { |
293 | fseek(fin, size, SEEK_CUR); |
294 | } |
295 | } |
296 | |
297 | LOG(DEBUG) << " thread_num:" << event.thread_num; |
298 | for (uint16_t i = 0; i < event.thread_num; ++i) { |
299 | tid = 0; |
300 | ReadValue<uint32_t>(&tid); |
301 | |
302 | fseek(fin, (tid>>16), SEEK_CUR); |
303 | |
304 | size = 0; |
305 | ReadValue<int>(&size); |
306 | if (size > 0) { |
307 | LOG(DEBUG) << " tls size:" << size; |
308 | fseek(fin, size, SEEK_CUR); |
309 | } |
310 | |
311 | // skip stack address |
312 | ReadValue<long>(&stack_addr); |
313 | size = 0; |
314 | ReadValue<int>(&size); |
315 | // skip stack data |
316 | if (size > 0) { |
317 | LOG(DEBUG) << " stack size:" << size << ", addr:" << HEX(stack_addr); |
318 | fseek(fin, size, SEEK_CUR); |
319 | } |
320 | } |
321 | |
322 | heap_count = 0; |
323 | ReadValue<uint32_t>(&heap_count); |
324 | LOG(DEBUG) << " heap count:" << heap_count << ", at " << ftell(fin); |
325 | for (uint32_t i = 0; i < heap_count; ++i) { |
326 | uintptr_t addr = 0; |
327 | ReadValue<uintptr_t>(&addr); |
328 | size = 0; |
329 | ReadValue<int>(&size); |
330 | LOG(DEBUG) << " address:" << HEX(addr) << ", size:" << size; |
331 | // skip heap data |
332 | fseek(fin, size, SEEK_CUR); |
333 | } |
334 | |
335 | event.duration = 0; |
336 | table->push_back(event); |
337 | |
338 | LOG(DEBUG) << table->size() << ":" << get_event_name(event.type) |
339 | << " for tracee " << event.tid |
340 | << " time=" << event.time |
341 | << " at [" << event.offset |
342 | << ", " << ftell(fin) << "]" ; |
343 | } |
344 | |
345 | return true; |
346 | } |
347 | |
348 | bool TraceReader::BuildMapsTable(std::vector<MapsEntry>* table) |
349 | { |
350 | bool valid = false; |
351 | int size = 0; |
352 | MapsEntry entry; |
353 | MDRawDebug debug; |
354 | struct timespec entry_time; |
355 | |
356 | // see TraceSession::dump_maps, dump_proc_file, dump_auxv |
357 | |
358 | ReadBlock(&entry_time, sizeof(entry_time)); |
359 | entry.offset = ftell(fin); |
360 | entry.time = timespec2ms(&entry_time); |
361 | |
362 | // skip auxv |
363 | size = 0; |
364 | ReadValue<int>(&size); |
365 | fseek(fin, size, SEEK_CUR); |
366 | |
367 | // skip cmdline |
368 | size = 0; |
369 | ReadValue<int>(&size); |
370 | fseek(fin, size, SEEK_CUR); |
371 | |
372 | // skip environ |
373 | size = 0; |
374 | ReadValue<int>(&size); |
375 | fseek(fin, size, SEEK_CUR); |
376 | |
377 | // skip vdso |
378 | size = 0; |
379 | ReadValue<int>(&size); |
380 | fseek(fin, size, SEEK_CUR); |
381 | |
382 | table->push_back(entry); |
383 | |
384 | for (;;) |
385 | { |
386 | entry.offset = ftell(fin); |
387 | if (entry.offset >= fsize) |
388 | break; |
389 | |
390 | ReadBlock(&entry_time, sizeof(entry_time)); |
391 | if (0 == entry_time.tv_sec) { |
392 | break; // reach end, zero bytes padding to 128K! |
393 | } |
394 | entry.offset += sizeof(entry_time); |
395 | entry.time = timespec2ms(&entry_time); |
396 | |
397 | // skip maps |
398 | size = 0; |
399 | ReadValue<int>(&size); |
400 | fseek(fin, size, SEEK_CUR); |
401 | |
402 | // skip module list |
403 | size = 0; |
404 | ReadValue<int>(&size); |
405 | if (size > 0) { |
406 | fseek(fin, size * sizeof(MDRawModule), SEEK_CUR); |
407 | } |
408 | |
409 | // skip dso link map |
410 | size = 0; |
411 | ReadValue<int>(&size); |
412 | if (size == sizeof(debug)) { |
413 | ReadBlock(&debug, sizeof(debug)); |
414 | if (debug.dso_count > 0) { |
415 | valid = true; |
416 | fseek(fin, debug.dso_count * sizeof(MDRawLinkMap), SEEK_CUR); |
417 | } |
418 | |
419 | // skip dynamic data |
420 | size = 0; |
421 | ReadValue<int>(&size); |
422 | fseek(fin, size, SEEK_CUR); |
423 | } |
424 | |
425 | if (valid) { |
426 | valid = false; |
427 | table->push_back(entry); |
428 | } |
429 | else { |
430 | LOG(DEBUG) << "ignore empty dso entry:" << entry.offset |
431 | << "," << entry_time.tv_sec |
432 | << " " << entry_time.tv_nsec; |
433 | } |
434 | } |
435 | |
436 | return true; |
437 | } |
438 | |
439 | bool TraceReader::Seek(long offset) |
440 | { |
441 | if (fin) { |
442 | fseek(fin, offset, SEEK_SET); |
443 | } |
444 | return true; |
445 | } |
446 | |
447 | /*end*/ |
448 | |