Home | History | Annotate | Download | only in coregrind
      1 /* -*- mode: C; c-basic-offset: 3; -*- */
      2 
      3 /*--------------------------------------------------------------------*/
      4 /*--- Libc printing.                                 m_libcprint.c ---*/
      5 /*--------------------------------------------------------------------*/
      6 
      7 /*
      8    This file is part of Valgrind, a dynamic binary instrumentation
      9    framework.
     10 
     11    Copyright (C) 2000-2015 Julian Seward
     12       jseward (at) acm.org
     13 
     14    This program is free software; you can redistribute it and/or
     15    modify it under the terms of the GNU General Public License as
     16    published by the Free Software Foundation; either version 2 of the
     17    License, or (at your option) any later version.
     18 
     19    This program is distributed in the hope that it will be useful, but
     20    WITHOUT ANY WARRANTY; without even the implied warranty of
     21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     22    General Public License for more details.
     23 
     24    You should have received a copy of the GNU General Public License
     25    along with this program; if not, write to the Free Software
     26    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
     27    02111-1307, USA.
     28 
     29    The GNU General Public License is contained in the file COPYING.
     30 */
     31 
     32 #include "pub_core_basics.h"
     33 #include "pub_core_vki.h"
     34 #include "pub_core_debuglog.h"
     35 #include "pub_core_gdbserver.h"  // VG_(gdb_printf)
     36 #include "pub_core_libcbase.h"
     37 #include "pub_core_libcassert.h"
     38 #include "pub_core_libcfile.h"   // VG_(write)(), VG_(write_socket)()
     39 #include "pub_core_libcprint.h"
     40 #include "pub_core_libcproc.h"   // VG_(getpid)(), VG_(read_millisecond_timer()
     41 #include "pub_core_mallocfree.h" // VG_(malloc)
     42 #include "pub_core_options.h"
     43 #include "pub_core_clreq.h"      // For RUNNING_ON_VALGRIND
     44 
     45 
     46 /* ---------------------------------------------------------------------
     47    Writing to file or a socket
     48    ------------------------------------------------------------------ */
     49 
     50 /* The destination sinks for normal and XML output.  These have their
     51    initial values here; they are set to final values by
     52    m_main.main_process_cmd_line_options().  See comment at the top of
     53    that function for the associated logic.
     54    After startup, the gdbserver monitor command might temporarily
     55    set the fd of log_output_sink to -2 to indicate that output is
     56    to be given to gdb rather than output to the startup fd */
     57 OutputSink VG_(log_output_sink) = {  2, False }; /* 2 = stderr */
     58 OutputSink VG_(xml_output_sink) = { -1, False }; /* disabled */
     59 
     60 /* Do the low-level send of a message to the logging sink. */
     61 static
     62 void send_bytes_to_logging_sink ( OutputSink* sink, const HChar* msg, Int nbytes )
     63 {
     64    if (sink->is_socket) {
     65       Int rc = VG_(write_socket)( sink->fd, msg, nbytes );
     66       if (rc == -1) {
     67          // For example, the listener process died.  Switch back to stderr.
     68          sink->is_socket = False;
     69          sink->fd = 2;
     70          VG_(write)( sink->fd, msg, nbytes );
     71       }
     72    } else {
     73       /* sink->fd could have been set to -1 in the various
     74          sys-wrappers for sys_fork, if --child-silent-after-fork=yes
     75          is in effect.  That is a signal that we should not produce
     76          any more output. */
     77       if (sink->fd >= 0)
     78          VG_(write)( sink->fd, msg, nbytes );
     79       else if (sink->fd == -2 && nbytes > 0)
     80          /* send to gdb the provided data, which must be
     81             a null terminated string with len >= 1 */
     82          VG_(gdb_printf)("%s", msg);
     83    }
     84 }
     85 
     86 
     87 /* ---------------------------------------------------------------------
     88    printf() and friends
     89    ------------------------------------------------------------------ */
     90 
     91 /* --------- printf --------- */
     92 
     93 typedef
     94    struct {
     95       HChar       buf[512];
     96       Int         buf_used;
     97       OutputSink* sink;
     98    }
     99    printf_buf_t;
    100 
    101 // Adds a single char to the buffer.  When the buffer gets sufficiently
    102 // full, we write its contents to the logging sink.
    103 static void add_to__printf_buf ( HChar c, void *p )
    104 {
    105    printf_buf_t *b = (printf_buf_t *)p;
    106 
    107    if (b->buf_used > sizeof(b->buf) - 2 ) {
    108       send_bytes_to_logging_sink( b->sink, b->buf, b->buf_used );
    109       b->buf_used = 0;
    110    }
    111    b->buf[b->buf_used++] = c;
    112    b->buf[b->buf_used]   = 0;
    113    vg_assert(b->buf_used < sizeof(b->buf));
    114 }
    115 
    116 static UInt vprintf_to_buf ( printf_buf_t* b,
    117                              const HChar *format, va_list vargs )
    118 {
    119    UInt ret = 0;
    120    if (b->sink->fd >= 0 || b->sink->fd == -2) {
    121       ret = VG_(debugLog_vprintf)
    122                ( add_to__printf_buf, b, format, vargs );
    123    }
    124    return ret;
    125 }
    126 
    127 static UInt vprintf_WRK ( OutputSink* sink,
    128                           const HChar *format, va_list vargs )
    129 {
    130    printf_buf_t myprintf_buf
    131       = { "", 0, sink };
    132    UInt ret
    133       = vprintf_to_buf(&myprintf_buf, format, vargs);
    134    // Write out any chars left in the buffer.
    135    if (myprintf_buf.buf_used > 0) {
    136       send_bytes_to_logging_sink( myprintf_buf.sink,
    137                                   myprintf_buf.buf,
    138                                   myprintf_buf.buf_used );
    139    }
    140    return ret;
    141 }
    142 
    143 UInt VG_(vprintf) ( const HChar *format, va_list vargs )
    144 {
    145    return vprintf_WRK( &VG_(log_output_sink), format, vargs );
    146 }
    147 
    148 UInt VG_(printf) ( const HChar *format, ... )
    149 {
    150    UInt ret;
    151    va_list vargs;
    152    va_start(vargs, format);
    153    ret = VG_(vprintf)(format, vargs);
    154    va_end(vargs);
    155    return ret;
    156 }
    157 
    158 UInt VG_(vprintf_xml) ( const HChar *format, va_list vargs )
    159 {
    160    return vprintf_WRK( &VG_(xml_output_sink), format, vargs );
    161 }
    162 
    163 UInt VG_(printf_xml) ( const HChar *format, ... )
    164 {
    165    UInt ret;
    166    va_list vargs;
    167    va_start(vargs, format);
    168    ret = VG_(vprintf_xml)(format, vargs);
    169    va_end(vargs);
    170    return ret;
    171 }
    172 
    173 static UInt emit_WRK ( const HChar* format, va_list vargs )
    174 {
    175    if (VG_(clo_xml)) {
    176       return VG_(vprintf_xml)(format, vargs);
    177    } else if (VG_(log_output_sink).fd == -2) {
    178       return VG_(vprintf) (format, vargs);
    179    } else {
    180       return VG_(vmessage)(Vg_UserMsg, format, vargs);
    181    }
    182 }
    183 UInt VG_(emit) ( const HChar* format, ... )
    184 {
    185    UInt ret;
    186    va_list vargs;
    187    va_start(vargs, format);
    188    ret = emit_WRK(format, vargs);
    189    va_end(vargs);
    190    return ret;
    191 }
    192 
    193 /* --------- sprintf --------- */
    194 
    195 /* If we had an explicit buf structure here, it would contain only one
    196    field, indicating where the next char is to go.  So use p directly
    197    for that, rather than having it be a pointer to a structure. */
    198 
    199 static void add_to__sprintf_buf ( HChar c, void *p )
    200 {
    201    HChar** b = p;
    202    *(*b)++ = c;
    203 }
    204 
    205 UInt VG_(vsprintf) ( HChar* buf, const HChar *format, va_list vargs )
    206 {
    207    Int ret;
    208    HChar* sprintf_ptr = buf;
    209 
    210    ret = VG_(debugLog_vprintf)
    211             ( add_to__sprintf_buf, &sprintf_ptr, format, vargs );
    212    add_to__sprintf_buf('\0', &sprintf_ptr);
    213 
    214    vg_assert(VG_(strlen)(buf) == ret);
    215 
    216    return ret;
    217 }
    218 
    219 UInt VG_(sprintf) ( HChar* buf, const HChar *format, ... )
    220 {
    221    UInt ret;
    222    va_list vargs;
    223    va_start(vargs,format);
    224    ret = VG_(vsprintf)(buf, format, vargs);
    225    va_end(vargs);
    226    return ret;
    227 }
    228 
    229 
    230 /* --------- snprintf --------- */
    231 
    232 /* The return value of VG_(snprintf) and VG_(vsnprintf) differs from
    233    what is defined in C99. Let S be the size of the buffer as given in
    234    the 2nd argument.
    235    Return value R:
    236      R < S:  The output string was successfully written to the buffer.
    237              It is null-terminated and R == strlen( output string )
    238      R == S: The supplied buffer was too small to hold the output string.
    239              The first S-1 characters of the output string were written
    240              to the buffer followed by the terminating null character.
    241 */
    242 
    243 typedef
    244    struct {
    245       HChar* buf;
    246       Int    buf_size;
    247       Int    buf_used;
    248    }
    249    snprintf_buf_t;
    250 
    251 static void add_to__snprintf_buf ( HChar c, void* p )
    252 {
    253    snprintf_buf_t* b = p;
    254    if (b->buf_size > 0 && b->buf_used < b->buf_size) {
    255       b->buf[b->buf_used++] = c;
    256       if (b->buf_used < b->buf_size)
    257          b->buf[b->buf_used] = 0;
    258       else
    259          b->buf[b->buf_size-1] = 0; /* pre: b->buf_size > 0 */
    260    }
    261 }
    262 
    263 UInt VG_(vsnprintf) ( HChar* buf, Int size, const HChar *format, va_list vargs )
    264 {
    265    snprintf_buf_t b;
    266    b.buf      = buf;
    267    b.buf_size = size < 0 ? 0 : size;
    268    b.buf_used = 0;
    269    if (b.buf_size > 0)
    270       b.buf[0] = 0; // ensure to null terminate buf if empty format
    271    (void) VG_(debugLog_vprintf)
    272              ( add_to__snprintf_buf, &b, format, vargs );
    273 
    274    return b.buf_used;
    275 }
    276 
    277 UInt VG_(snprintf) ( HChar* buf, Int size, const HChar *format, ... )
    278 {
    279    UInt ret;
    280    va_list vargs;
    281    va_start(vargs,format);
    282    ret = VG_(vsnprintf)(buf, size, format, vargs);
    283    va_end(vargs);
    284    return ret;
    285 }
    286 
    287 
    288 /* --------- vcbprintf --------- */
    289 
    290 void VG_(vcbprintf)( void(*char_sink)(HChar, void* opaque),
    291                      void* opaque,
    292                      const HChar* format, va_list vargs )
    293 {
    294    (void) VG_(debugLog_vprintf)
    295              ( char_sink, opaque, format, vargs );
    296 }
    297 
    298 
    299 /* --------- fprintf ---------- */
    300 
    301 /* This is like [v]fprintf, except it writes to a file handle using
    302    VG_(write). */
    303 
    304 #define VGFILE_BUFSIZE  8192
    305 
    306 struct _VgFile {
    307    HChar buf[VGFILE_BUFSIZE];
    308    UInt  num_chars;   // number of characters in buf
    309    Int   fd;          // file descriptor to write to
    310 };
    311 
    312 
    313 static void add_to__vgfile ( HChar c, void *p )
    314 {
    315    VgFile *fp = p;
    316 
    317    fp->buf[fp->num_chars++] = c;
    318 
    319    if (fp->num_chars == VGFILE_BUFSIZE) {
    320       VG_(write)(fp->fd, fp->buf, fp->num_chars);
    321       fp->num_chars = 0;
    322    }
    323 }
    324 
    325 VgFile *VG_(fopen)(const HChar *name, Int flags, Int mode)
    326 {
    327    SysRes res = VG_(open)(name, flags, mode);
    328 
    329    if (sr_isError(res))
    330       return NULL;
    331 
    332    VgFile *fp = VG_(malloc)("fopen", sizeof(VgFile));
    333 
    334    fp->fd = sr_Res(res);
    335    fp->num_chars = 0;
    336 
    337    return fp;
    338 }
    339 
    340 
    341 UInt VG_(vfprintf) ( VgFile *fp, const HChar *format, va_list vargs )
    342 {
    343    return VG_(debugLog_vprintf)(add_to__vgfile, fp, format, vargs);
    344 }
    345 
    346 UInt VG_(fprintf) ( VgFile *fp, const HChar *format, ... )
    347 {
    348    UInt ret;
    349    va_list vargs;
    350    va_start(vargs,format);
    351    ret = VG_(vfprintf)(fp, format, vargs);
    352    va_end(vargs);
    353    return ret;
    354 }
    355 
    356 void VG_(fclose)( VgFile *fp )
    357 {
    358    // Flush the buffer.
    359    if (fp->num_chars)
    360       VG_(write)(fp->fd, fp->buf, fp->num_chars);
    361 
    362    VG_(free)(fp);
    363 }
    364 
    365 
    366 /* ---------------------------------------------------------------------
    367    elapsed_wallclock_time()
    368    ------------------------------------------------------------------ */
    369 
    370 /* Get the elapsed wallclock time since startup into buf, which must
    371    16 chars long.  This is unchecked.  It also relies on the
    372    millisecond timer having been set to zero by an initial read in
    373    m_main during startup. */
    374 
    375 void VG_(elapsed_wallclock_time) ( /*OUT*/HChar* buf, SizeT bufsize )
    376 {
    377    UInt t, ms, s, mins, hours, days;
    378 
    379    vg_assert(bufsize > 20);
    380 
    381    t  = VG_(read_millisecond_timer)(); /* milliseconds */
    382 
    383    ms = t % 1000;
    384    t /= 1000; /* now in seconds */
    385 
    386    s = t % 60;
    387    t /= 60; /* now in minutes */
    388 
    389    mins = t % 60;
    390    t /= 60; /* now in hours */
    391 
    392    hours = t % 24;
    393    t /= 24; /* now in days */
    394 
    395    days = t;
    396 
    397    VG_(sprintf)(buf, "%02u:%02u:%02u:%02u.%03u ", days, hours, mins, s, ms);
    398 }
    399 
    400 
    401 /* ---------------------------------------------------------------------
    402    message()
    403    ------------------------------------------------------------------ */
    404 
    405 /* A buffer for accumulating VG_(message) style output.  This is
    406    pretty much the same as VG_(printf)'s scheme, with two differences:
    407 
    408    * The message buffer persists between calls, so that multiple
    409      calls to VG_(message) can build up output.
    410 
    411    * Whenever the first character on a line is emitted, the
    412      ==PID== style preamble is stuffed in before it.
    413 */
    414 typedef
    415    struct {
    416       HChar buf[512+128];
    417       Int   buf_used;
    418       Bool  atLeft; /* notionally, is the next char position at the
    419                        leftmost column? */
    420       /* Current message kind - changes from call to call */
    421       VgMsgKind kind;
    422       /* destination */
    423       OutputSink* sink;
    424    }
    425    vmessage_buf_t;
    426 
    427 static vmessage_buf_t vmessage_buf
    428    = { "", 0, True, Vg_UserMsg, &VG_(log_output_sink) };
    429 
    430 
    431 // Adds a single char to the buffer.  We aim to have at least 128
    432 // bytes free in the buffer, so that it's always possible to emit
    433 // the preamble into the buffer if c happens to be the character
    434 // following a \n.  When the buffer gets too full, we write its
    435 // contents to the logging sink.
    436 static void add_to__vmessage_buf ( HChar c, void *p )
    437 {
    438    HChar tmp[64];
    439    vmessage_buf_t* b = (vmessage_buf_t*)p;
    440 
    441    vg_assert(b->buf_used >= 0 && b->buf_used < sizeof(b->buf)-128);
    442 
    443    if (UNLIKELY(b->atLeft)) {
    444       // insert preamble
    445       HChar ch;
    446       Int   i, depth;
    447 
    448       // Print one '>' in front of the messages for each level of
    449       // self-hosting being performed.
    450       // Do not print such '>' if sim hint "no-inner-prefix" given
    451       // (useful to run regression tests in an outer/inner setup
    452       // and avoid the diff failing due to these unexpected '>').
    453       depth = RUNNING_ON_VALGRIND;
    454       if (depth > 0
    455           && !SimHintiS(SimHint_no_inner_prefix, VG_(clo_sim_hints))) {
    456          if (depth > 10)
    457             depth = 10; // ?!?!
    458          for (i = 0; i < depth; i++) {
    459             b->buf[b->buf_used++] = '>';
    460          }
    461       }
    462 
    463       if (Vg_FailMsg == b->kind) {
    464          // "valgrind: " prefix.
    465          b->buf[b->buf_used++] = 'v';
    466          b->buf[b->buf_used++] = 'a';
    467          b->buf[b->buf_used++] = 'l';
    468          b->buf[b->buf_used++] = 'g';
    469          b->buf[b->buf_used++] = 'r';
    470          b->buf[b->buf_used++] = 'i';
    471          b->buf[b->buf_used++] = 'n';
    472          b->buf[b->buf_used++] = 'd';
    473          b->buf[b->buf_used++] = ':';
    474          b->buf[b->buf_used++] = ' ';
    475       } else {
    476          switch (b->kind) {
    477             case Vg_UserMsg:       ch = '='; break;
    478             case Vg_DebugMsg:      ch = '-'; break;
    479             case Vg_ClientMsg:     ch = '*'; break;
    480             default:               ch = '?'; break;
    481          }
    482 
    483          b->buf[b->buf_used++] = ch;
    484          b->buf[b->buf_used++] = ch;
    485 
    486          if (VG_(clo_time_stamp)) {
    487             VG_(elapsed_wallclock_time)(tmp, sizeof tmp);
    488             for (i = 0; tmp[i]; i++)
    489                b->buf[b->buf_used++] = tmp[i];
    490          }
    491 
    492          VG_(sprintf)(tmp, "%d", VG_(getpid)());
    493          tmp[sizeof(tmp)-1] = 0;
    494          for (i = 0; tmp[i]; i++)
    495             b->buf[b->buf_used++] = tmp[i];
    496 
    497          b->buf[b->buf_used++] = ch;
    498          b->buf[b->buf_used++] = ch;
    499          b->buf[b->buf_used++] = ' ';
    500       }
    501 
    502       /* We can't possibly have stuffed 96 chars in merely as a result
    503          of making the preamble (can we?) */
    504       vg_assert(b->buf_used < sizeof(b->buf)-32);
    505    }
    506 
    507    b->buf[b->buf_used++] = c;
    508    b->buf[b->buf_used]   = 0;
    509 
    510    if (b->buf_used >= sizeof(b->buf) - 128) {
    511       send_bytes_to_logging_sink( b->sink, b->buf, b->buf_used );
    512       b->buf_used = 0;
    513    }
    514 
    515    b->atLeft = c == '\n';
    516 }
    517 
    518 
    519 UInt VG_(vmessage) ( VgMsgKind kind, const HChar* format, va_list vargs )
    520 {
    521    UInt ret;
    522 
    523    /* Note (carefully) that the buf persists from call to call, unlike
    524       with the other printf variants in earlier parts of this file. */
    525    vmessage_buf_t* b = &vmessage_buf; /* shorthand for convenience */
    526 
    527    /* We have to set this each call, so that the correct flavour
    528       of preamble is emitted at each \n. */
    529    b->kind = kind;
    530 
    531    ret = VG_(debugLog_vprintf) ( add_to__vmessage_buf,
    532                                  b, format, vargs );
    533 
    534    /* If the message finished exactly with a \n, then flush it at this
    535       point.  If not, assume more bits of the same line will turn up
    536       in later messages, so don't bother to flush it right now. */
    537 
    538    if (b->atLeft && b->buf_used > 0) {
    539       send_bytes_to_logging_sink( b->sink, b->buf, b->buf_used );
    540       b->buf_used = 0;
    541    }
    542 
    543    return ret;
    544 }
    545 
    546 /* Send a simple single-part message. */
    547 UInt VG_(message) ( VgMsgKind kind, const HChar* format, ... )
    548 {
    549    UInt count;
    550    va_list vargs;
    551    va_start(vargs,format);
    552    count = VG_(vmessage) ( kind, format, vargs );
    553    va_end(vargs);
    554    return count;
    555 }
    556 
    557 static void revert_to_stderr ( void )
    558 {
    559    VG_(log_output_sink).fd = 2; /* stderr */
    560    VG_(log_output_sink).is_socket = False;
    561 }
    562 
    563 /* VG_(message) variants with hardwired first argument. */
    564 
    565 UInt VG_(fmsg) ( const HChar* format, ... )
    566 {
    567    UInt count;
    568    va_list vargs;
    569    va_start(vargs,format);
    570    count = VG_(vmessage) ( Vg_FailMsg, format, vargs );
    571    va_end(vargs);
    572    return count;
    573 }
    574 
    575 void VG_(fmsg_bad_option) ( const HChar* opt, const HChar* format, ... )
    576 {
    577    va_list vargs;
    578    va_start(vargs,format);
    579    revert_to_stderr();
    580    VG_(message) (Vg_FailMsg, "Bad option: %s\n", opt);
    581    VG_(vmessage)(Vg_FailMsg, format, vargs );
    582    VG_(message) (Vg_FailMsg, "Use --help for more information or consult the user manual.\n");
    583    va_end(vargs);
    584    VG_(exit)(1);
    585 }
    586 
    587 void VG_(fmsg_unknown_option) ( const HChar* opt)
    588 {
    589    revert_to_stderr();
    590    VG_(message) (Vg_FailMsg, "Unknown option: %s\n", opt);
    591    VG_(message) (Vg_FailMsg, "Use --help for more information or consult the user manual.\n");
    592    VG_(exit)(1);
    593 }
    594 
    595 UInt VG_(umsg) ( const HChar* format, ... )
    596 {
    597    UInt count;
    598    va_list vargs;
    599    va_start(vargs,format);
    600    count = VG_(vmessage) ( Vg_UserMsg, format, vargs );
    601    va_end(vargs);
    602    return count;
    603 }
    604 
    605 UInt VG_(dmsg) ( const HChar* format, ... )
    606 {
    607    UInt count;
    608    va_list vargs;
    609    va_start(vargs,format);
    610    count = VG_(vmessage) ( Vg_DebugMsg, format, vargs );
    611    va_end(vargs);
    612    return count;
    613 }
    614 
    615 /* Flush any output that has accumulated in vmessage_buf as a
    616    result of previous calls to VG_(message) et al. */
    617 void VG_(message_flush) ( void )
    618 {
    619    vmessage_buf_t* b = &vmessage_buf;
    620    send_bytes_to_logging_sink( b->sink, b->buf, b->buf_used );
    621    b->buf_used = 0;
    622 }
    623 
    624 __attribute__((noreturn))
    625 void VG_(err_missing_prog) ( void  )
    626 {
    627    revert_to_stderr();
    628    VG_(fmsg)("no program specified\n");
    629    VG_(fmsg)("Use --help for more information.\n");
    630    VG_(exit)(1);
    631 }
    632 
    633 __attribute__((noreturn))
    634 void VG_(err_config_error) ( const HChar* format, ... )
    635 {
    636    va_list vargs;
    637    va_start(vargs,format);
    638    revert_to_stderr();
    639    VG_(message) (Vg_FailMsg, "Startup or configuration error:\n   ");
    640    VG_(vmessage)(Vg_FailMsg, format, vargs );
    641    VG_(message) (Vg_FailMsg, "Unable to start up properly.  Giving up.\n");
    642    va_end(vargs);
    643    VG_(exit)(1);
    644 }
    645 
    646 /* ---------------------------------------------------------------------
    647    VG_(sr_as_string)()
    648    ------------------------------------------------------------------ */
    649 
    650 #if defined(VGO_linux)
    651 // FIXME: Does this function need to be adjusted for MIPS's _valEx ?
    652 const HChar *VG_(sr_as_string) ( SysRes sr )
    653 {
    654    static HChar buf[7+1+2+16+1+1];   // large enough
    655 
    656    if (sr_isError(sr))
    657       VG_(sprintf)(buf, "Failure(0x%lx)", sr_Err(sr));
    658    else
    659       VG_(sprintf)(buf, "Success(0x%lx)", sr_Res(sr));
    660    return buf;
    661 }
    662 
    663 #elif defined(VGO_darwin) || (VGO_solaris)
    664 
    665 const HChar *VG_(sr_as_string) ( SysRes sr )
    666 {
    667    static HChar buf[7+1+2+16+1+2+16+1+1];   // large enough
    668 
    669    if (sr_isError(sr))
    670       VG_(sprintf)(buf, "Failure(0x%lx)", sr_Err(sr));
    671    else
    672       VG_(sprintf)(buf, "Success(0x%lx:0x%lx)", sr_ResHI(sr), sr_Res(sr));
    673    return buf;
    674 }
    675 
    676 #else
    677 
    678 #error unknown OS
    679 
    680 #endif
    681 
    682 /*--------------------------------------------------------------------*/
    683 /*--- end                                                          ---*/
    684 /*--------------------------------------------------------------------*/
    685