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
24using namespace std;
25
26static double timespec2ms(const struct timespec* spec)
27{
28 return (spec->tv_sec*1000.0 + spec->tv_nsec/(1000.0*1000.0));
29}
30
31static long get_file_size(const char* filename)
32{
33 struct stat st;
34 stat(filename, &st);
35 return st.st_size;
36}
37
38static 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
95error_found:
96 if (dstream) ZSTD_freeDStream(dstream);
97 if (buffIn) free(buffIn);
98 if (buffOut) free(buffOut);
99
100 return ret;
101}
102
103static 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
142error_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
152bool 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
164TraceReader::TraceReader(void)
165{
166 fin = nullptr;
167 fsize = 0;
168}
169
170TraceReader::~TraceReader(void)
171{
172 fclose(fin);
173 fin = nullptr;
174}
175
176int 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
196int 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
203static 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
222bool 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
348bool 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
439bool TraceReader::Seek(long offset)
440{
441 if (fin) {
442 fseek(fin, offset, SEEK_SET);
443 }
444 return true;
445}
446
447/*end*/
448