/* * Copyright (C) 2010 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include <stdarg.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <stdint.h> #include <stddef.h> #include "linker_format.h" #include "linker_debug.h" /* define UNIT_TESTS to build this file as a single executable that runs * the formatter's unit tests */ #define xxUNIT_TESTS /*** Generic output sink ***/ typedef struct { void *opaque; void (*send)(void *opaque, const char *data, int len); } Out; static void out_send(Out *o, const void *data, size_t len) { o->send(o->opaque, data, (int)len); } static void out_send_repeat(Out *o, char ch, int count) { char pad[8]; const int padSize = (int)sizeof(pad); memset(pad, ch, sizeof(pad)); while (count > 0) { int avail = count; if (avail > padSize) { avail = padSize; } o->send(o->opaque, pad, avail); count -= avail; } } /* forward declaration */ static void out_vformat(Out *o, const char *format, va_list args); /*** Bounded buffer output ***/ typedef struct { Out out[1]; char *buffer; char *pos; char *end; int total; } BufOut; static void buf_out_send(void *opaque, const char *data, int len) { BufOut *bo = opaque; if (len < 0) len = strlen(data); bo->total += len; while (len > 0) { int avail = bo->end - bo->pos; if (avail == 0) break; if (avail > len) avail = len; memcpy(bo->pos, data, avail); bo->pos += avail; bo->pos[0] = '\0'; len -= avail; } } static Out* buf_out_init(BufOut *bo, char *buffer, size_t size) { if (size == 0) return NULL; bo->out->opaque = bo; bo->out->send = buf_out_send; bo->buffer = buffer; bo->end = buffer + size - 1; bo->pos = bo->buffer; bo->pos[0] = '\0'; bo->total = 0; return bo->out; } static int buf_out_length(BufOut *bo) { return bo->total; } static int vformat_buffer(char *buff, size_t buffsize, const char *format, va_list args) { BufOut bo; Out *out; out = buf_out_init(&bo, buff, buffsize); if (out == NULL) return 0; out_vformat(out, format, args); return buf_out_length(&bo); } int format_buffer(char *buff, size_t buffsize, const char *format, ...) { va_list args; int ret; va_start(args, format); ret = vformat_buffer(buff, buffsize, format, args); va_end(args); return ret; } /* The __stack_chk_fail() function calls __libc_android_log_print() * which calls vsnprintf(). * * We define our version of the function here to avoid dragging * about 25 KB of C library routines related to formatting. */ int vsnprintf(char *buff, size_t bufsize, const char *format, va_list args) { return format_buffer(buff, bufsize, format, args); } #if LINKER_DEBUG #if !LINKER_DEBUG_TO_LOG /*** File descriptor output ***/ typedef struct { Out out[1]; int fd; int total; } FdOut; static void fd_out_send(void *opaque, const char *data, int len) { FdOut *fdo = opaque; if (len < 0) len = strlen(data); while (len > 0) { int ret = write(fdo->fd, data, len); if (ret < 0) { if (errno == EINTR) continue; break; } data += ret; len -= ret; fdo->total += ret; } } static Out* fd_out_init(FdOut *fdo, int fd) { fdo->out->opaque = fdo; fdo->out->send = fd_out_send; fdo->fd = fd; fdo->total = 0; return fdo->out; } static int fd_out_length(FdOut *fdo) { return fdo->total; } int format_fd(int fd, const char *format, ...) { FdOut fdo; Out* out; va_list args; out = fd_out_init(&fdo, fd); if (out == NULL) return 0; va_start(args, format); out_vformat(out, format, args); va_end(args); return fd_out_length(&fdo); } #else /* LINKER_DEBUG_TO_LOG */ /*** Log output ***/ /* We need our own version of __libc_android_log_vprint, otherwise * the log output is completely broken. Probably due to the fact * that the C library is not initialized yet. * * You can test that by setting CUSTOM_LOG_VPRINT to 0 */ #define CUSTOM_LOG_VPRINT 1 #if CUSTOM_LOG_VPRINT #include <unistd.h> #include <fcntl.h> #include <sys/uio.h> static int log_vprint(int prio, const char *tag, const char *fmt, va_list args) { char buf[1024]; int result; static int log_fd = -1; result = vformat_buffer(buf, sizeof buf, fmt, args); if (log_fd < 0) { log_fd = open("/dev/log/main", O_WRONLY); if (log_fd < 0) return result; } { ssize_t ret; struct iovec vec[3]; vec[0].iov_base = (unsigned char *) &prio; vec[0].iov_len = 1; vec[1].iov_base = (void *) tag; vec[1].iov_len = strlen(tag) + 1; vec[2].iov_base = (void *) buf; vec[2].iov_len = strlen(buf) + 1; do { ret = writev(log_fd, vec, 3); } while ((ret < 0) && (errno == EINTR)); } return result; } #define __libc_android_log_vprint log_vprint #else /* !CUSTOM_LOG_VPRINT */ extern int __libc_android_log_vprint(int prio, const char* tag, const char* format, va_list ap); #endif /* !CUSTOM_LOG_VPRINT */ int format_log(int prio, const char *tag, const char *format, ...) { int ret; va_list args; va_start(args, format); ret = __libc_android_log_vprint(prio, tag, format, args); va_end(args); return ret; } #endif /* LINKER_DEBUG_TO_LOG */ #endif /* LINKER_DEBUG */ /*** formatted output implementation ***/ /* Parse a decimal string from 'format + *ppos', * return the value, and writes the new position past * the decimal string in '*ppos' on exit. * * NOTE: Does *not* handle a sign prefix. */ static unsigned parse_decimal(const char *format, int *ppos) { const char* p = format + *ppos; unsigned result = 0; for (;;) { int ch = *p; unsigned d = (unsigned)(ch - '0'); if (d >= 10U) break; result = result*10 + d; p++; } *ppos = p - format; return result; } /* write an octal/decimal/number into a bounded buffer. * assumes that bufsize > 0, and 'digits' is a string of * digits of at least 'base' values. */ static void format_number(char *buffer, size_t bufsize, uint64_t value, int base, const char *digits) { char *pos = buffer; char *end = buffer + bufsize - 1; /* generate digit string in reverse order */ while (value) { unsigned d = value % base; value /= base; if (pos < end) { *pos++ = digits[d]; } } /* special case for 0 */ if (pos == buffer) { if (pos < end) { *pos++ = '0'; } } pos[0] = '\0'; /* now reverse digit string in-place */ end = pos - 1; pos = buffer; while (pos < end) { int ch = pos[0]; pos[0] = end[0]; end[0] = (char) ch; pos++; end--; } } /* Write an integer (octal or decimal) into a buffer, assumes buffsize > 2 */ static void format_integer(char *buffer, size_t buffsize, uint64_t value, int base, int isSigned) { if (isSigned && (int64_t)value < 0) { buffer[0] = '-'; buffer += 1; buffsize -= 1; value = (uint64_t)(-(int64_t)value); } format_number(buffer, buffsize, value, base, "0123456789"); } /* Write an octal into a buffer, assumes buffsize > 2 */ static void format_octal(char *buffer, size_t buffsize, uint64_t value, int isSigned) { format_integer(buffer, buffsize, value, 8, isSigned); } /* Write a decimal into a buffer, assumes buffsize > 2 */ static void format_decimal(char *buffer, size_t buffsize, uint64_t value, int isSigned) { format_integer(buffer, buffsize, value, 10, isSigned); } /* Write an hexadecimal into a buffer, isCap is true for capital alphas. * Assumes bufsize > 2 */ static void format_hex(char *buffer, size_t buffsize, uint64_t value, int isCap) { const char *digits = isCap ? "0123456789ABCDEF" : "0123456789abcdef"; format_number(buffer, buffsize, value, 16, digits); } /* Perform formatted output to an output target 'o' */ static void out_vformat(Out *o, const char *format, va_list args) { int nn = 0, mm; int padZero = 0; int padLeft = 0; char sign = '\0'; int width = -1; int prec = -1; size_t bytelen = sizeof(int); const char* str; int slen; char buffer[32]; /* temporary buffer used to format numbers */ for (;;) { char c; /* first, find all characters that are not 0 or '%' */ /* then send them to the output directly */ mm = nn; do { c = format[mm]; if (c == '\0' || c == '%') break; mm++; } while (1); if (mm > nn) { out_send(o, format+nn, mm-nn); nn = mm; } /* is this it ? then exit */ if (c == '\0') break; /* nope, we are at a '%' modifier */ nn++; // skip it /* parse flags */ for (;;) { c = format[nn++]; if (c == '\0') { /* single trailing '%' ? */ c = '%'; out_send(o, &c, 1); return; } else if (c == '0') { padZero = 1; continue; } else if (c == '-') { padLeft = 1; continue; } else if (c == ' ' || c == '+') { sign = c; continue; } break; } /* parse field width */ if ((c >= '0' && c <= '9')) { nn --; width = (int)parse_decimal(format, &nn); c = format[nn++]; } /* parse precision */ if (c == '.') { prec = (int)parse_decimal(format, &nn); c = format[nn++]; } /* length modifier */ switch (c) { case 'h': bytelen = sizeof(short); if (format[nn] == 'h') { bytelen = sizeof(char); nn += 1; } c = format[nn++]; break; case 'l': bytelen = sizeof(long); if (format[nn] == 'l') { bytelen = sizeof(long long); nn += 1; } c = format[nn++]; break; case 'z': bytelen = sizeof(size_t); c = format[nn++]; break; case 't': bytelen = sizeof(ptrdiff_t); c = format[nn++]; break; case 'p': bytelen = sizeof(void*); c = format[nn++]; default: ; } /* conversion specifier */ if (c == 's') { /* string */ str = va_arg(args, const char*); } else if (c == 'c') { /* character */ /* NOTE: char is promoted to int when passed through the stack */ buffer[0] = (char) va_arg(args, int); buffer[1] = '\0'; str = buffer; } else if (c == 'p') { uint64_t value = (uint64_t)(ptrdiff_t) va_arg(args, void*); buffer[0] = '0'; buffer[1] = 'x'; format_hex(buffer + 2, sizeof buffer-2, value, 0); str = buffer; } else { /* integers - first read value from stack */ uint64_t value; int isSigned = (c == 'd' || c == 'i' || c == 'o'); /* NOTE: int8_t and int16_t are promoted to int when passed * through the stack */ switch (bytelen) { case 1: value = (uint8_t) va_arg(args, int); break; case 2: value = (uint16_t) va_arg(args, int); break; case 4: value = va_arg(args, uint32_t); break; case 8: value = va_arg(args, uint64_t); break; default: return; /* should not happen */ } /* sign extension, if needed */ if (isSigned) { int shift = 64 - 8*bytelen; value = (uint64_t)(((int64_t)(value << shift)) >> shift); } /* format the number properly into our buffer */ switch (c) { case 'i': case 'd': format_integer(buffer, sizeof buffer, value, 10, isSigned); break; case 'o': format_integer(buffer, sizeof buffer, value, 8, isSigned); break; case 'x': case 'X': format_hex(buffer, sizeof buffer, value, (c == 'X')); break; default: buffer[0] = '\0'; } /* then point to it */ str = buffer; } /* if we are here, 'str' points to the content that must be * outputted. handle padding and alignment now */ slen = strlen(str); if (slen < width && !padLeft) { char padChar = padZero ? '0' : ' '; out_send_repeat(o, padChar, width - slen); } out_send(o, str, slen); if (slen < width && padLeft) { char padChar = padZero ? '0' : ' '; out_send_repeat(o, padChar, width - slen); } } } #ifdef UNIT_TESTS #include <stdio.h> static int gFails = 0; #define MARGIN 40 #define UTEST_CHECK(condition,message) \ printf("Checking %-*s: ", MARGIN, message); fflush(stdout); \ if (!(condition)) { \ printf("KO\n"); \ gFails += 1; \ } else { \ printf("ok\n"); \ } static void utest_BufOut(void) { char buffer[16]; BufOut bo[1]; Out* out; int ret; buffer[0] = '1'; out = buf_out_init(bo, buffer, sizeof buffer); UTEST_CHECK(buffer[0] == '\0', "buf_out_init clears initial byte"); out_send(out, "abc", 3); UTEST_CHECK(!memcmp(buffer, "abc", 4), "out_send() works with BufOut"); out_send_repeat(out, 'X', 4); UTEST_CHECK(!memcmp(buffer, "abcXXXX", 8), "out_send_repeat() works with BufOut"); buffer[sizeof buffer-1] = 'x'; out_send_repeat(out, 'Y', 2*sizeof(buffer)); UTEST_CHECK(buffer[sizeof buffer-1] == '\0', "overflows always zero-terminates"); out = buf_out_init(bo, buffer, sizeof buffer); out_send_repeat(out, 'X', 2*sizeof(buffer)); ret = buf_out_length(bo); UTEST_CHECK(ret == 2*sizeof(buffer), "correct size returned on overflow"); } static void utest_expect(const char* result, const char* format, ...) { va_list args; BufOut bo[1]; char buffer[256]; Out* out = buf_out_init(bo, buffer, sizeof buffer); printf("Checking %-*s: ", MARGIN, format); fflush(stdout); va_start(args, format); out_vformat(out, format, args); va_end(args); if (strcmp(result, buffer)) { printf("KO. got '%s' expecting '%s'\n", buffer, result); gFails += 1; } else { printf("ok. got '%s'\n", result); } } int main(void) { utest_BufOut(); utest_expect("", ""); utest_expect("a", "a"); utest_expect("01234", "01234", ""); utest_expect("01234", "%s", "01234"); utest_expect("aabbcc", "aa%scc", "bb"); utest_expect("a", "%c", 'a'); utest_expect("1234", "%d", 1234); utest_expect("-8123", "%d", -8123); utest_expect("16", "%hd", 0x7fff0010); utest_expect("16", "%hhd", 0x7fffff10); utest_expect("68719476736", "%lld", 0x1000000000); utest_expect("70000", "%ld", 70000); utest_expect("0xb0001234", "%p", (void*)0xb0001234); utest_expect("12ab", "%x", 0x12ab); utest_expect("12AB", "%X", 0x12ab); utest_expect("00123456", "%08x", 0x123456); utest_expect("01234", "0%d", 1234); utest_expect(" 1234", "%5d", 1234); utest_expect("01234", "%05d", 1234); utest_expect(" 1234", "%8d", 1234); utest_expect("1234 ", "%-8d", 1234); utest_expect("abcdef ", "%-11s", "abcdef"); utest_expect("something:1234", "%s:%d", "something", 1234); return gFails != 0; } #endif /* UNIT_TESTS */