1 /*
2 * Copyright (c) 2021 BayLibre, SAS
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <errno.h>
8 #include <stdarg.h>
9 #include <stdint.h>
10 #include <string.h>
11 #include <sys/cbprintf.h>
12 #include <sys/types.h>
13 #include <sys/util.h>
14 #include <sys/__assert.h>
15
16
17 /**
18 * @brief Check if address is in read only section.
19 *
20 * @param addr Address.
21 *
22 * @return True if address identified within read only section.
23 */
ptr_in_rodata(const char * addr)24 static inline bool ptr_in_rodata(const char *addr)
25 {
26 #if defined(CBPRINTF_VIA_UNIT_TEST)
27 /* Unit test is X86 (or other host) but not using Zephyr
28 * linker scripts.
29 */
30 #define RO_START 0
31 #define RO_END 0
32 #elif defined(CONFIG_ARC) || defined(CONFIG_ARM) || defined(CONFIG_X86) \
33 || defined(CONFIG_RISCV) || defined(CONFIG_ARM64) \
34 || defined(CONFIG_NIOS2)
35 extern char __rodata_region_start[];
36 extern char __rodata_region_end[];
37 #define RO_START __rodata_region_start
38 #define RO_END __rodata_region_end
39 #elif defined(CONFIG_XTENSA)
40 extern char _rodata_start[];
41 extern char _rodata_end[];
42 #define RO_START _rodata_start
43 #define RO_END _rodata_end
44 #else
45 #define RO_START 0
46 #define RO_END 0
47 #endif
48
49 return ((addr >= (const char *)RO_START) &&
50 (addr < (const char *)RO_END));
51 }
52
53 /*
54 * va_list creation
55 */
56
57 #if defined(__aarch64__)
58 /*
59 * Reference:
60 *
61 * Procedure Call Standard for the ARM 64-bit Architecture
62 */
63
64 struct __va_list {
65 void *__stack;
66 void *__gr_top;
67 void *__vr_top;
68 int __gr_offs;
69 int __vr_offs;
70 };
71
72 BUILD_ASSERT(sizeof(va_list) == sizeof(struct __va_list),
73 "architecture specific support is wrong");
74
cbprintf_via_va_list(cbprintf_cb out,void * ctx,const char * fmt,void * buf)75 static int cbprintf_via_va_list(cbprintf_cb out, void *ctx,
76 const char *fmt, void *buf)
77 {
78 union {
79 va_list ap;
80 struct __va_list __ap;
81 } u;
82
83 /* create a valid va_list with our buffer */
84 u.__ap.__stack = buf;
85 u.__ap.__gr_top = NULL;
86 u.__ap.__vr_top = NULL;
87 u.__ap.__gr_offs = 0;
88 u.__ap.__vr_offs = 0;
89
90 return cbvprintf(out, ctx, fmt, u.ap);
91 }
92
93 #elif defined(__x86_64__)
94 /*
95 * Reference:
96 *
97 * System V Application Binary Interface
98 * AMD64 Architecture Processor Supplement
99 */
100
101 struct __va_list {
102 unsigned int gp_offset;
103 unsigned int fp_offset;
104 void *overflow_arg_area;
105 void *reg_save_area;
106 };
107
108 BUILD_ASSERT(sizeof(va_list) == sizeof(struct __va_list),
109 "architecture specific support is wrong");
110
cbprintf_via_va_list(cbprintf_cb out,void * ctx,const char * fmt,void * buf)111 static int cbprintf_via_va_list(cbprintf_cb out, void *ctx,
112 const char *fmt, void *buf)
113 {
114 union {
115 va_list ap;
116 struct __va_list __ap;
117 } u;
118
119 /* create a valid va_list with our buffer */
120 u.__ap.overflow_arg_area = buf;
121 u.__ap.reg_save_area = NULL;
122 u.__ap.gp_offset = (6 * 8);
123 u.__ap.fp_offset = (6 * 8 + 16 * 16);
124
125 return cbvprintf(out, ctx, fmt, u.ap);
126 }
127
128 #elif defined(__xtensa__)
129 /*
130 * Reference:
131 *
132 * gcc source code (gcc/config/xtensa/xtensa.c)
133 * xtensa_build_builtin_va_list(), xtensa_va_start(),
134 * xtensa_gimplify_va_arg_expr()
135 */
136
137 struct __va_list {
138 void *__va_stk;
139 void *__va_reg;
140 int __va_ndx;
141 };
142
143 BUILD_ASSERT(sizeof(va_list) == sizeof(struct __va_list),
144 "architecture specific support is wrong");
145
cbprintf_via_va_list(cbprintf_cb out,void * ctx,const char * fmt,void * buf)146 static int cbprintf_via_va_list(cbprintf_cb out, void *ctx,
147 const char *fmt, void *buf)
148 {
149 union {
150 va_list ap;
151 struct __va_list __ap;
152 } u;
153
154 /* create a valid va_list with our buffer */
155 u.__ap.__va_stk = (char *)buf - 32;
156 u.__ap.__va_reg = NULL;
157 u.__ap.__va_ndx = (6 + 2) * 4;
158
159 return cbvprintf(out, ctx, fmt, u.ap);
160 }
161
162 #else
163 /*
164 * Default implementation shared by many architectures like
165 * 32-bit ARM and Intel.
166 *
167 * We assume va_list is a simple pointer.
168 */
169
170 BUILD_ASSERT(sizeof(va_list) == sizeof(void *),
171 "architecture specific support is needed");
172
cbprintf_via_va_list(cbprintf_cb out,void * ctx,const char * fmt,void * buf)173 static int cbprintf_via_va_list(cbprintf_cb out, void *ctx,
174 const char *fmt, void *buf)
175 {
176 union {
177 va_list ap;
178 void *ptr;
179 } u;
180
181 u.ptr = buf;
182
183 return cbvprintf(out, ctx, fmt, u.ap);
184 }
185
186 #endif
187
cbvprintf_package(void * packaged,size_t len,uint32_t flags,const char * fmt,va_list ap)188 int cbvprintf_package(void *packaged, size_t len, uint32_t flags,
189 const char *fmt, va_list ap)
190 {
191 /* Internally, byte is used to store location of a string argument within a
192 * package. MSB bit is set if string is read-only so effectively 7 bits are
193 * used for index, which should be enough.
194 */
195 #define CBPRINTF_STR_POS_RO_FLAG BIT(7)
196 #define CBPRINTF_STR_POS_MASK BIT_MASK(7)
197
198 char *buf = packaged, *buf0 = buf;
199 unsigned int align, size, i, s_idx = 0, s_rw_cnt = 0, s_ro_cnt = 0;
200 uint8_t str_ptr_pos[16];
201 const char *s;
202 bool parsing = false;
203
204 /* Buffer must be aligned at least to size of a pointer. */
205 if ((uintptr_t)packaged & (sizeof(void *) - 1)) {
206 return -EFAULT;
207 }
208
209 #if defined(__xtensa__)
210 /* Xtensa requires package to be 16 bytes aligned. */
211 if ((uintptr_t)packaged & (CBPRINTF_PACKAGE_ALIGNMENT - 1)) {
212 return -EFAULT;
213 }
214 #endif
215
216 /*
217 * Make room to store the arg list size and the number of
218 * appended strings. They both occupy 1 byte each.
219 *
220 * Given the next value to store is the format string pointer
221 * which is guaranteed to be at least 4 bytes, we just reserve
222 * a pointer size for the above to preserve alignment.
223 */
224 buf += sizeof(char *);
225
226 /*
227 * When buf0 is NULL we don't store anything.
228 * Instead we count the needed space to store the data.
229 * In this case, incoming len argument indicates the anticipated
230 * buffer "misalignment" offset.
231 */
232 if (!buf0) {
233 #if defined(__xtensa__)
234 if (len % CBPRINTF_PACKAGE_ALIGNMENT) {
235 return -EFAULT;
236 }
237 #endif
238 buf += len % CBPRINTF_PACKAGE_ALIGNMENT;
239 len = -(len % CBPRINTF_PACKAGE_ALIGNMENT);
240 }
241
242 /*
243 * Otherwise we must ensure we can store at least
244 * thepointer to the format string itself.
245 */
246 if (buf0 && buf - buf0 + sizeof(char *) > len) {
247 return -ENOSPC;
248 }
249
250 /*
251 * Then process the format string itself.
252 * Here we branch directly into the code processing strings
253 * which is in the middle of the following while() loop. That's the
254 * reason for the post-decrement on fmt as it will be incremented
255 * prior to the next (actually first) round of that loop.
256 */
257 s = fmt--;
258 align = VA_STACK_ALIGN(char *);
259 size = sizeof(char *);
260 goto process_string;
261
262 /* Scan the format string */
263 while (*++fmt) {
264 if (!parsing) {
265 if (*fmt == '%') {
266 parsing = true;
267 align = VA_STACK_ALIGN(int);
268 size = sizeof(int);
269 }
270 continue;
271 }
272 switch (*fmt) {
273 case '%':
274 parsing = false;
275 continue;
276
277 case '#':
278 case '-':
279 case '+':
280 case ' ':
281 case '0':
282 case '1':
283 case '2':
284 case '3':
285 case '4':
286 case '5':
287 case '6':
288 case '7':
289 case '8':
290 case '9':
291 case '.':
292 case 'h':
293 case 'l':
294 case 'L':
295 continue;
296
297 case '*':
298 break;
299
300 case 'j':
301 align = VA_STACK_ALIGN(intmax_t);
302 size = sizeof(intmax_t);
303 continue;
304
305 case 'z':
306 align = VA_STACK_ALIGN(size_t);
307 size = sizeof(size_t);
308 continue;
309
310 case 't':
311 align = VA_STACK_ALIGN(ptrdiff_t);
312 size = sizeof(ptrdiff_t);
313 continue;
314
315 case 'c':
316 case 'd':
317 case 'i':
318 case 'o':
319 case 'u':
320 case 'x':
321 case 'X':
322 if (fmt[-1] == 'l') {
323 if (fmt[-2] == 'l') {
324 align = VA_STACK_ALIGN(long long);
325 size = sizeof(long long);
326 } else {
327 align = VA_STACK_ALIGN(long);
328 size = sizeof(long);
329 }
330 }
331 parsing = false;
332 break;
333
334 case 's':
335 case 'p':
336 case 'n':
337 align = VA_STACK_ALIGN(void *);
338 size = sizeof(void *);
339 parsing = false;
340 break;
341
342 case 'a':
343 case 'A':
344 case 'e':
345 case 'E':
346 case 'f':
347 case 'F':
348 case 'g':
349 case 'G': {
350 /*
351 * Handle floats separately as they may be
352 * held in a different register set.
353 */
354 union { double d; long double ld; } v;
355
356 if (fmt[-1] == 'L') {
357 v.ld = va_arg(ap, long double);
358 align = VA_STACK_ALIGN(long double);
359 size = sizeof(long double);
360 } else {
361 v.d = va_arg(ap, double);
362 align = VA_STACK_ALIGN(double);
363 size = sizeof(double);
364 }
365 /* align destination buffer location */
366 buf = (void *) ROUND_UP(buf, align);
367 if (buf0) {
368 /* make sure it fits */
369 if (buf - buf0 + size > len) {
370 return -ENOSPC;
371 }
372 if (Z_CBPRINTF_VA_STACK_LL_DBL_MEMCPY) {
373 memcpy(buf, &v, size);
374 } else if (fmt[-1] == 'L') {
375 *(long double *)buf = v.ld;
376 } else {
377 *(double *)buf = v.d;
378 }
379 }
380 buf += size;
381 parsing = false;
382 continue;
383 }
384
385 default:
386 parsing = false;
387 continue;
388 }
389
390 /* align destination buffer location */
391 buf = (void *) ROUND_UP(buf, align);
392
393 /* make sure the data fits */
394 if (buf0 && buf - buf0 + size > len) {
395 return -ENOSPC;
396 }
397
398 /* copy va_list data over to our buffer */
399 if (*fmt == 's') {
400 s = va_arg(ap, char *);
401 process_string:
402 if (buf0) {
403 *(const char **)buf = s;
404 }
405
406 /* Bother about read only strings only if storing
407 * string indexes is requested.
408 */
409 bool is_ro = ptr_in_rodata(s);
410 bool str_idxs = flags & CBPRINTF_PACKAGE_ADD_STRING_IDXS;
411 bool need_ro = is_ro && str_idxs;
412
413 if (ptr_in_rodata(s) && !str_idxs) {
414 /* do nothing special */
415 } else if (buf0) {
416
417 /*
418 * Remember string pointer location.
419 * We will append it later.
420 */
421 if (s_idx >= ARRAY_SIZE(str_ptr_pos)) {
422 __ASSERT(false, "str_ptr_pos[] too small");
423 return -EINVAL;
424 }
425
426 if ((buf - buf0) > CBPRINTF_STR_POS_MASK) {
427 __ASSERT(false, "String with too many arguments");
428 return -EINVAL;
429 }
430
431 /* Add marking to identify if read only string. */
432 uint8_t ro_flag = need_ro ?
433 CBPRINTF_STR_POS_RO_FLAG : 0;
434
435 if (ro_flag) {
436 s_ro_cnt++;
437 } else {
438 s_rw_cnt++;
439 }
440
441 /* Use same multiple as the arg list size. */
442 str_ptr_pos[s_idx++] = ro_flag |
443 (buf - buf0) / sizeof(int);
444 } else {
445 if (!is_ro) {
446 /*
447 * Add the string length, the final '\0'
448 * and size of the pointer position prefix.
449 */
450 len += strlen(s) + 1 + 1;
451 } else if (need_ro) {
452 /*
453 * Add only pointer position prefix for
454 * read only string is requested.
455 */
456 len += 1;
457 }
458 }
459 buf += sizeof(char *);
460 } else if (size == sizeof(int)) {
461 int v = va_arg(ap, int);
462
463 if (buf0) {
464 *(int *)buf = v;
465 }
466 buf += sizeof(int);
467 } else if (size == sizeof(long)) {
468 long v = va_arg(ap, long);
469
470 if (buf0) {
471 *(long *)buf = v;
472 }
473 buf += sizeof(long);
474 } else if (size == sizeof(long long)) {
475 long long v = va_arg(ap, long long);
476
477 if (buf0) {
478 if (Z_CBPRINTF_VA_STACK_LL_DBL_MEMCPY) {
479 memcpy(buf, &v, sizeof(long long));
480 } else {
481 *(long long *)buf = v;
482 }
483 }
484 buf += sizeof(long long);
485 } else {
486 __ASSERT(false, "unexpected size %u", size);
487 return -EINVAL;
488 }
489 }
490
491 /*
492 * We remember the size of the argument list as a multiple of
493 * sizeof(int) and limit it to a 8-bit field. That means 1020 bytes
494 * worth of va_list, or about 127 arguments on a 64-bit system
495 * (twice that on 32-bit systems). That ought to be good enough.
496 */
497 if ((buf - buf0) / sizeof(int) > 255) {
498 __ASSERT(false, "too many format args");
499 return -EINVAL;
500 }
501
502 /*
503 * If all we wanted was to count required buffer size
504 * then we have it now.
505 */
506 if (!buf0) {
507 return len + buf - buf0;
508 }
509
510 /* Clear our buffer header. We made room for it initially. */
511 *(char **)buf0 = NULL;
512
513 /* Record end of argument list and number of appended strings. */
514 buf0[0] = (buf - buf0) / sizeof(int);
515 buf0[1] = s_rw_cnt;
516 buf0[2] = s_ro_cnt;
517
518 /* Store strings pointer locations of read only strings. */
519 if (s_ro_cnt) {
520 for (i = 0; i < s_idx; i++) {
521 if (!(str_ptr_pos[i] & CBPRINTF_STR_POS_RO_FLAG)) {
522 continue;
523 }
524
525 uint8_t pos = str_ptr_pos[i] & CBPRINTF_STR_POS_MASK;
526
527 /* make sure it fits */
528 if (buf - buf0 + 1 > len) {
529 return -ENOSPC;
530 }
531 /* store the pointer position prefix */
532 *buf++ = pos;
533 }
534 }
535
536 /* Store strings prefixed by their pointer location. */
537 for (i = 0; i < s_idx; i++) {
538 /* Process only RW strings. */
539 if (str_ptr_pos[i] & CBPRINTF_STR_POS_RO_FLAG) {
540 continue;
541 }
542
543 /* retrieve the string pointer */
544 s = *(char **)(buf0 + str_ptr_pos[i] * sizeof(int));
545 /* clear the in-buffer pointer (less entropy if compressed) */
546 *(char **)(buf0 + str_ptr_pos[i] * sizeof(int)) = NULL;
547 /* find the string length including terminating '\0' */
548 size = strlen(s) + 1;
549 /* make sure it fits */
550 if (buf - buf0 + 1 + size > len) {
551 return -ENOSPC;
552 }
553 /* store the pointer position prefix */
554 *buf++ = str_ptr_pos[i];
555 /* copy the string with its terminating '\0' */
556 memcpy(buf, s, size);
557 buf += size;
558 }
559
560 /*
561 * TODO: remove pointers for appended strings since they're useless.
562 * TODO: explore leveraging same mechanism to remove alignment padding
563 */
564
565 return buf - buf0;
566
567 #undef CBPRINTF_STR_POS_RO_FLAG
568 #undef CBPRINTF_STR_POS_MASK
569 }
570
cbprintf_package(void * packaged,size_t len,uint32_t flags,const char * format,...)571 int cbprintf_package(void *packaged, size_t len, uint32_t flags,
572 const char *format, ...)
573 {
574 va_list ap;
575 int ret;
576
577 va_start(ap, format);
578 ret = cbvprintf_package(packaged, len, flags, format, ap);
579 va_end(ap);
580 return ret;
581 }
582
cbpprintf(cbprintf_cb out,void * ctx,void * packaged)583 int cbpprintf(cbprintf_cb out, void *ctx, void *packaged)
584 {
585 char *buf = packaged, *fmt, *s, **ps;
586 unsigned int i, args_size, s_nbr, ros_nbr, s_idx;
587
588 if (!buf) {
589 return -EINVAL;
590 }
591
592 /* Retrieve the size of the arg list and number of strings. */
593 args_size = ((uint8_t *)buf)[0] * sizeof(int);
594 s_nbr = ((uint8_t *)buf)[1];
595 ros_nbr = ((uint8_t *)buf)[2];
596
597 /* Locate the string table */
598 s = buf + args_size + ros_nbr;
599
600 /*
601 * Patch in string pointers.
602 */
603 for (i = 0; i < s_nbr; i++) {
604 /* Locate pointer location for this string */
605 s_idx = *(uint8_t *)s++;
606 ps = (char **)(buf + s_idx * sizeof(int));
607 /* update the pointer with current string location */
608 *ps = s;
609 /* move to next string */
610 s += strlen(s) + 1;
611 }
612
613 /* Retrieve format string */
614 fmt = ((char **)buf)[1];
615
616 /* skip past format string pointer */
617 buf += sizeof(char *) * 2;
618
619 /* Turn this into a va_list and print it */
620 return cbprintf_via_va_list(out, ctx, fmt, buf);
621 }
622
cbprintf_fsc_package(void * in_packaged,size_t in_len,void * packaged,size_t len)623 int cbprintf_fsc_package(void *in_packaged,
624 size_t in_len,
625 void *packaged,
626 size_t len)
627 {
628 uint8_t *buf = in_packaged, *out = packaged;
629 char **ps;
630 unsigned int args_size, s_nbr, ros_nbr, s_idx;
631 size_t out_len;
632 size_t slen;
633
634 if (!buf) {
635 return -EINVAL;
636 }
637
638 if (packaged && (len < in_len)) {
639 return -ENOSPC;
640 }
641
642 /* Retrieve the size of the arg list and number of strings. */
643 args_size = buf[0] * sizeof(int);
644 s_nbr = buf[1];
645 ros_nbr = buf[2];
646
647 out_len = in_len;
648
649 if (packaged) {
650 unsigned int rw_strs_len = in_len - (args_size + ros_nbr);
651
652 memcpy(out, buf, args_size);
653 out[1] = s_nbr + ros_nbr;
654 out[2] = 0;
655 out += args_size;
656
657 /* Append all strings that were already part of the package. */
658 memcpy(out, &buf[args_size + ros_nbr], rw_strs_len);
659 out += rw_strs_len;
660 }
661
662 for (unsigned int i = 0; i < ros_nbr; i++) {
663 /* Get string address location */
664 s_idx = buf[args_size + i];
665 ps = (char **)(buf + s_idx * sizeof(int));
666
667 /* Get string length */
668 slen = strlen(*ps) + 1;
669 out_len += slen;
670
671 /* Copy string into the buffer (if provided) and enough space. */
672 if (packaged) {
673 if (out_len > len) {
674 return -ENOSPC;
675 }
676 *out++ = s_idx;
677 memcpy(out, *ps, slen);
678 out += slen;
679 }
680 }
681
682 return out_len;
683 }
684