summaryrefslogtreecommitdiffstats
path: root/linker/linker_format.c
diff options
context:
space:
mode:
Diffstat (limited to 'linker/linker_format.c')
-rw-r--r--linker/linker_format.c703
1 files changed, 703 insertions, 0 deletions
diff --git a/linker/linker_format.c b/linker/linker_format.c
new file mode 100644
index 0000000..4d00bd9
--- /dev/null
+++ b/linker/linker_format.c
@@ -0,0 +1,703 @@
+/*
+ * 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 */