1 /*
2 * Copyright (c) 2018 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/kernel.h>
8 #include <stdio.h>
9 #include <stdint.h>
10 #include <errno.h>
11 #include <string.h>
12 #include <zephyr/posix/fnmatch.h>
13 #include "coverage.h"
14 #include <zephyr/arch/common/semihost.h>
15 #include <sys/types.h>
16
17 K_HEAP_DEFINE(gcov_heap, CONFIG_COVERAGE_GCOV_HEAP_SIZE);
18
19 static struct gcov_info *gcov_info_head;
20
21 /**
22 * Is called by gcc-generated constructor code for each object file compiled
23 * with -fprofile-arcs.
24 */
__gcov_init(struct gcov_info * info)25 void __gcov_init(struct gcov_info *info)
26 {
27 info->next = gcov_info_head;
28 gcov_info_head = info;
29 }
30
__gcov_merge_add(gcov_type * counters,unsigned int n_counters)31 void __gcov_merge_add(gcov_type *counters, unsigned int n_counters)
32 {
33 /* Unused. */
34 }
35
__gcov_exit(void)36 void __gcov_exit(void)
37 {
38 /* Unused. */
39 }
40
41 /**
42 * print_u8 - Print 8 bit of gcov data
43 */
print_u8(uint8_t v)44 static inline void print_u8(uint8_t v)
45 {
46 printk("%02x", v);
47 }
48
49 /**
50 * print_u32 - Print 32 bit of gcov data
51 */
print_u32(uint32_t v)52 static inline void print_u32(uint32_t v)
53 {
54 uint8_t *ptr = (uint8_t *)&v;
55
56 print_u8(*ptr);
57 print_u8(*(ptr+1));
58 print_u8(*(ptr+2));
59 print_u8(*(ptr+3));
60 }
61
62 /**
63 * write_u64 - Store 64 bit data on a buffer and return the size
64 */
65
write_u64(void * buffer,size_t * off,uint64_t v)66 static inline void write_u64(void *buffer, size_t *off, uint64_t v)
67 {
68 if (buffer != NULL) {
69 memcpy((uint8_t *)buffer + *off, (uint8_t *)&v, sizeof(v));
70 } else {
71 print_u32(*((uint32_t *)&v));
72 print_u32(*(((uint32_t *)&v) + 1));
73 }
74 *off = *off + sizeof(uint64_t);
75 }
76
77 /**
78 * write_u32 - Store 32 bit data on a buffer and return the size
79 */
write_u32(void * buffer,size_t * off,uint32_t v)80 static inline void write_u32(void *buffer, size_t *off, uint32_t v)
81 {
82 if (buffer != NULL) {
83 memcpy((uint8_t *)buffer + *off, (uint8_t *)&v, sizeof(v));
84 } else {
85 print_u32(v);
86 }
87 *off = *off + sizeof(uint32_t);
88 }
89
gcov_calculate_buff_size(struct gcov_info * info)90 size_t gcov_calculate_buff_size(struct gcov_info *info)
91 {
92 uint32_t iter;
93 uint32_t iter_1;
94 uint32_t iter_2;
95
96 /* Few fixed values at the start: magic number,
97 * version, stamp, and checksum.
98 */
99 #ifdef GCOV_12_FORMAT
100 uint32_t size = sizeof(uint32_t) * 4;
101 #else
102 uint32_t size = sizeof(uint32_t) * 3;
103 #endif
104
105 for (iter = 0U; iter < info->n_functions; iter++) {
106 /* space for TAG_FUNCTION and FUNCTION_LENGTH
107 * ident
108 * lineno_checksum
109 * cfg_checksum
110 */
111 size += (sizeof(uint32_t) * 5);
112
113 for (iter_1 = 0U; iter_1 < GCOV_COUNTERS; iter_1++) {
114 if (!info->merge[iter_1]) {
115 continue;
116 }
117
118 /* for function counter and number of values */
119 size += (sizeof(uint32_t) * 2);
120
121 for (iter_2 = 0U;
122 iter_2 < info->functions[iter]->ctrs->num;
123 iter_2++) {
124
125 /* Iter for values which is uint64_t */
126 size += (sizeof(uint64_t));
127 }
128
129 }
130
131
132 }
133
134 return size;
135 }
136
137 /**
138 * gcov_populate_buffer - convert from gcov data set (info) to
139 * .gcda file format.
140 * This buffer will now have info similar to a regular gcda
141 * format.
142 */
gcov_populate_buffer(uint8_t * buffer,struct gcov_info * info)143 size_t gcov_populate_buffer(uint8_t *buffer, struct gcov_info *info)
144 {
145 struct gcov_fn_info *functions;
146 struct gcov_ctr_info *counters_per_func;
147 uint32_t iter_functions;
148 uint32_t iter_counts;
149 uint32_t iter_counter_values;
150 size_t buffer_write_position = 0;
151
152 /* File header. */
153 write_u32(buffer,
154 &buffer_write_position,
155 GCOV_DATA_MAGIC);
156
157 write_u32(buffer,
158 &buffer_write_position,
159 info->version);
160
161 write_u32(buffer,
162 &buffer_write_position,
163 info->stamp);
164
165 #ifdef GCOV_12_FORMAT
166 write_u32(buffer,
167 &buffer_write_position,
168 info->checksum);
169 #endif
170
171 for (iter_functions = 0U;
172 iter_functions < info->n_functions;
173 iter_functions++) {
174
175 functions = info->functions[iter_functions];
176
177
178 write_u32(buffer,
179 &buffer_write_position,
180 GCOV_TAG_FUNCTION);
181
182 write_u32(buffer,
183 &buffer_write_position,
184 GCOV_TAG_FUNCTION_LENGTH);
185
186 write_u32(buffer,
187 &buffer_write_position,
188 functions->ident);
189
190 write_u32(buffer,
191 &buffer_write_position,
192 functions->lineno_checksum);
193
194 write_u32(buffer,
195 &buffer_write_position,
196 functions->cfg_checksum);
197
198 counters_per_func = functions->ctrs;
199
200 for (iter_counts = 0U;
201 iter_counts < GCOV_COUNTERS;
202 iter_counts++) {
203
204 if (!info->merge[iter_counts]) {
205 continue;
206 }
207
208 write_u32(buffer,
209 &buffer_write_position,
210 GCOV_TAG_FOR_COUNTER(iter_counts));
211
212 #ifdef GCOV_12_FORMAT
213 /* GCOV 12 counts the length by bytes */
214 write_u32(buffer,
215 &buffer_write_position,
216 counters_per_func->num * 2U * 4);
217 #else
218 write_u32(buffer,
219 &buffer_write_position,
220 counters_per_func->num * 2U);
221 #endif
222
223 for (iter_counter_values = 0U;
224 iter_counter_values < counters_per_func->num;
225 iter_counter_values++) {
226
227 write_u64(buffer,
228 &buffer_write_position,
229 counters_per_func->values[iter_counter_values]);
230 }
231
232 counters_per_func++;
233 }
234 }
235 return buffer_write_position;
236 }
237
gcov_reset_counts(struct gcov_info * info)238 void gcov_reset_counts(struct gcov_info *info)
239 {
240 struct gcov_fn_info *functions;
241 struct gcov_ctr_info *counters_per_func;
242 uint32_t iter_functions;
243 uint32_t iter_counts;
244 uint32_t iter_counter_values;
245
246 for (iter_functions = 0U;
247 iter_functions < info->n_functions;
248 iter_functions++) {
249
250 functions = info->functions[iter_functions];
251 counters_per_func = functions->ctrs;
252
253 for (iter_counts = 0U;
254 iter_counts < GCOV_COUNTERS;
255 iter_counts++) {
256 for (iter_counter_values = 0U;
257 iter_counter_values < counters_per_func->num;
258 iter_counter_values++) {
259 counters_per_func->values[iter_counter_values] = 0;
260 }
261 }
262 }
263 }
264
gcov_reset_all_counts(void)265 void gcov_reset_all_counts(void)
266 {
267 struct gcov_info *gcov_list = NULL;
268
269 #ifdef CONFIG_MULTITHREADING
270 k_sched_lock();
271 #endif
272
273 gcov_list = gcov_get_list_head();
274
275 while (gcov_list) {
276 gcov_reset_counts(gcov_list);
277 gcov_list = gcov_list->next;
278 }
279
280 #ifdef CONFIG_MULTITHREADING
281 k_sched_unlock();
282 #endif
283 }
284
285 #ifdef CONFIG_COVERAGE_DUMP
dump_on_console_start(const char * filename)286 void dump_on_console_start(const char *filename)
287 {
288 printk("\n%c", FILE_START_INDICATOR);
289 while (*filename != '\0') {
290 printk("%c", *filename++);
291 }
292 printk("%c", GCOV_DUMP_SEPARATOR);
293 }
294
dump_on_console_data(char * ptr,size_t len)295 void dump_on_console_data(char *ptr, size_t len)
296 {
297 if (ptr != NULL) {
298 for (size_t iter = 0U; iter < len; iter++) {
299 print_u8((uint8_t)*ptr++);
300 }
301 }
302 }
303
304
305 /**
306 * Retrieves gcov coverage data and sends it over the given interface.
307 */
gcov_coverage_dump(void)308 void gcov_coverage_dump(void)
309 {
310 uint8_t *buffer;
311 size_t size;
312 size_t written_size;
313 struct gcov_info *gcov_list_first = gcov_info_head;
314 struct gcov_info *gcov_list = gcov_info_head;
315
316 #ifdef CONFIG_MULTITHREADING
317 if (!k_is_in_isr()) {
318 k_sched_lock();
319 }
320 #endif
321 printk("\nGCOV_COVERAGE_DUMP_START");
322 while (gcov_list) {
323 if ((strlen(CONFIG_COVERAGE_DUMP_PATH_EXCLUDE) > 0) &&
324 (fnmatch(CONFIG_COVERAGE_DUMP_PATH_EXCLUDE, gcov_list->filename, 0) == 0)) {
325 /* Don't print a note here, it would be interpreted as dump data */
326 goto file_dump_end;
327 }
328
329 dump_on_console_start(gcov_list->filename);
330 size = gcov_calculate_buff_size(gcov_list);
331
332 buffer = k_heap_alloc(&gcov_heap, size, K_NO_WAIT);
333 if (CONFIG_COVERAGE_GCOV_HEAP_SIZE > 0 && !buffer) {
334 printk("No memory available to continue dump\n");
335 k_heap_free(&gcov_heap, buffer);
336 goto coverage_dump_end;
337 }
338
339 written_size = gcov_populate_buffer(buffer, gcov_list);
340 if (written_size != size) {
341 printk("Write Error on buffer\n");
342 k_heap_free(&gcov_heap, buffer);
343 goto coverage_dump_end;
344 }
345
346 dump_on_console_data(buffer, size);
347
348 k_heap_free(&gcov_heap, buffer);
349 file_dump_end:
350 gcov_list = gcov_list->next;
351 if (gcov_list_first == gcov_list) {
352 goto coverage_dump_end;
353 }
354 }
355 coverage_dump_end:
356 printk("\nGCOV_COVERAGE_DUMP_END\n");
357 #ifdef CONFIG_MULTITHREADING
358 if (!k_is_in_isr()) {
359 k_sched_unlock();
360 }
361 #endif
362 return;
363 }
364
365 #elif CONFIG_COVERAGE_SEMIHOST
366 /**
367 * Retrieves gcov coverage data and sends it over the given interface.
368 */
gcov_coverage_semihost(void)369 void gcov_coverage_semihost(void)
370 {
371 uint8_t *buffer;
372 size_t size;
373 size_t written_size;
374 struct gcov_info *gcov_list_first = gcov_info_head;
375 struct gcov_info *gcov_list = gcov_info_head;
376
377 #ifdef CONFIG_MULTITHREADING
378 if (!k_is_in_isr()) {
379 k_sched_lock();
380 }
381 #endif
382 while (gcov_list) {
383
384 int fd = semihost_open(gcov_list->filename, SEMIHOST_OPEN_WB);
385
386 if (fd < 0) {
387 printk("Failed to open file: %s\n", gcov_list->filename);
388 goto coverage_dump_end;
389 }
390
391 size = gcov_calculate_buff_size(gcov_list);
392
393 buffer = k_heap_alloc(&gcov_heap, size, K_NO_WAIT);
394 if (CONFIG_COVERAGE_GCOV_HEAP_SIZE > 0 && !buffer) {
395 printk("No memory available to continue dump\n");
396 semihost_close(fd);
397 k_heap_free(&gcov_heap, buffer);
398 goto coverage_dump_end;
399 }
400
401 written_size = gcov_populate_buffer(buffer, gcov_list);
402 if (written_size != size) {
403 printk("Write Error on buffer\n");
404 semihost_close(fd);
405 k_heap_free(&gcov_heap, buffer);
406 goto coverage_dump_end;
407 }
408
409 int ret = semihost_write(fd, (const void *)buffer, size);
410
411 if (ret < 0) {
412 printk("Failed to write data to file: %s\n", gcov_list->filename);
413 semihost_close(fd);
414 k_heap_free(&gcov_heap, buffer);
415 goto coverage_dump_end;
416 }
417
418 k_heap_free(&gcov_heap, buffer);
419 gcov_list = gcov_list->next;
420 if (gcov_list_first == gcov_list) {
421 semihost_close(fd);
422 goto coverage_dump_end;
423 }
424 }
425 coverage_dump_end:
426 #ifdef CONFIG_MULTITHREADING
427 if (!k_is_in_isr()) {
428 k_sched_unlock();
429 }
430 #endif
431 }
432 #endif
433
gcov_get_list_head(void)434 struct gcov_info *gcov_get_list_head(void)
435 {
436 /* Locking someway before getting this is recommended. */
437 return gcov_info_head;
438 }
439
440 /* Initialize the gcov by calling the required constructors */
gcov_static_init(void)441 void gcov_static_init(void)
442 {
443 extern uintptr_t __init_array_start, __init_array_end;
444 uintptr_t func_pointer_start = (uintptr_t) &__init_array_start;
445 uintptr_t func_pointer_end = (uintptr_t) &__init_array_end;
446
447 while (func_pointer_start < func_pointer_end) {
448 void (**p)(void);
449 /* get function pointer */
450 p = (void (**)(void)) func_pointer_start;
451 (*p)();
452 func_pointer_start += sizeof(p);
453 }
454 }
455