mirror of
https://github.com/Zenithsiz/ftmemsim-valgrind.git
synced 2026-02-10 13:40:25 +00:00
This patch changes the interface and behaviour of VG_(demangle) and VG_(maybe_Z_demangle). Instead of copying the demangled name into a fixed sized buffer that is passed in from the caller (HChar *buf, Int n_buf), the demangling functions will now return a pointer to the full-length demangled name (HChar **result). It is the caller's responsiblilty to make a copy if needed. This change in function parameters ripples upward - first: to get_sym_name - then to the convenience wrappers - VG_(get_fnname) - VG_(get_fnname_w_offset) - VG_(get_fnname_if_entry) - VG_(get_fnname_raw) - VG_(get_fnname_no_cxx_demangle) - VG_(get_datasym_and_offset) The changes in foComplete then forces the arguments of - VG_(get_objname) to be changed as well There are some issues regarding the ownership and persistence of character strings to consider. In general, the returned character string is owned by "somebody else" which means the caller must not free it. Also, the caller must not modify the returned string as it possibly points to read only memory. Additionally, the returned string is not necessarily persistent. Here are the scenarios: - the returned string is a demangled function name in which case the memory holding the string will be freed when the demangler is called again. - the returned string hangs off of a DebugInfo structure in which case it will be freed when the DebugInfo is discarded - the returned string hangs off of a segment in the address space manager in which case it may be overwritten when the segment is merged with another segment So the rule of thunb here is: if in doubt strdup the string. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@14664
648 lines
18 KiB
C
648 lines
18 KiB
C
|
|
/* This demonstrates a stack overrun bug that exp-ptrcheck found while
|
|
running Valgrind itself (self hosting). As at 12 Sept 08 this bug
|
|
is still in Valgrind. */
|
|
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
|
|
typedef unsigned long long int ULong;
|
|
typedef signed long long int Long;
|
|
typedef unsigned int UInt;
|
|
typedef signed int Int;
|
|
typedef signed char Char;
|
|
typedef char HChar;
|
|
typedef unsigned long UWord;
|
|
typedef signed long Word;
|
|
|
|
|
|
|
|
typedef unsigned char Bool;
|
|
#define True ((Bool)1)
|
|
#define False ((Bool)0)
|
|
|
|
#define VG_(_str) VG_##_str
|
|
|
|
|
|
/* ---------------------------------------------------------------------
|
|
vg_sprintf, copied from m_libcprint.c
|
|
------------------------------------------------------------------ */
|
|
UInt
|
|
VG_(debugLog_vprintf) (
|
|
void(*send)(HChar,void*),
|
|
void* send_arg2,
|
|
const HChar* format,
|
|
va_list vargs
|
|
);
|
|
|
|
/* ---------------------------------------------------------------------
|
|
printf() and friends
|
|
------------------------------------------------------------------ */
|
|
typedef
|
|
struct { Int fd; Bool is_socket; }
|
|
OutputSink;
|
|
|
|
|
|
OutputSink VG_(log_output_sink) = { 2, False }; /* 2 = stderr */
|
|
|
|
/* Do the low-level send of a message to the logging sink. */
|
|
static
|
|
void send_bytes_to_logging_sink ( OutputSink* sink, HChar* msg, Int nbytes )
|
|
{
|
|
fwrite(msg, 1, nbytes, stdout);
|
|
fflush(stdout);
|
|
}
|
|
|
|
|
|
/* --------- printf --------- */
|
|
|
|
typedef
|
|
struct {
|
|
HChar buf[512];
|
|
Int buf_used;
|
|
OutputSink* sink;
|
|
}
|
|
printf_buf_t;
|
|
|
|
// Adds a single char to the buffer. When the buffer gets sufficiently
|
|
// full, we write its contents to the logging sink.
|
|
static void add_to__printf_buf ( HChar c, void *p )
|
|
{
|
|
printf_buf_t *b = (printf_buf_t *)p;
|
|
|
|
if (b->buf_used > sizeof(b->buf) - 2 ) {
|
|
send_bytes_to_logging_sink( b->sink, b->buf, b->buf_used );
|
|
b->buf_used = 0;
|
|
}
|
|
b->buf[b->buf_used++] = c;
|
|
b->buf[b->buf_used] = 0;
|
|
assert(b->buf_used < sizeof(b->buf));
|
|
}
|
|
|
|
__attribute__((noinline))
|
|
static UInt vprintf_to_buf ( printf_buf_t* b,
|
|
const HChar *format, va_list vargs )
|
|
{
|
|
UInt ret = 0;
|
|
if (b->sink->fd >= 0 || b->sink->fd == -2) {
|
|
ret = VG_(debugLog_vprintf)
|
|
( add_to__printf_buf, b, format, vargs );
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
__attribute__((noinline))
|
|
static UInt vprintf_WRK ( OutputSink* sink,
|
|
const HChar *format, va_list vargs )
|
|
{
|
|
printf_buf_t myprintf_buf
|
|
= { "", 0, sink };
|
|
UInt ret;
|
|
ret = vprintf_to_buf(&myprintf_buf, format, vargs);
|
|
// Write out any chars left in the buffer.
|
|
if (myprintf_buf.buf_used > 0) {
|
|
send_bytes_to_logging_sink( myprintf_buf.sink,
|
|
myprintf_buf.buf,
|
|
myprintf_buf.buf_used );
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
__attribute__((noinline))
|
|
UInt VG_(vprintf) ( const HChar *format, va_list vargs )
|
|
{
|
|
return vprintf_WRK( &VG_(log_output_sink), format, vargs );
|
|
}
|
|
|
|
__attribute__((noinline))
|
|
UInt VG_(printf) ( const HChar *format, ... )
|
|
{
|
|
UInt ret;
|
|
va_list vargs;
|
|
va_start(vargs, format);
|
|
ret = VG_(vprintf)(format, vargs);
|
|
va_end(vargs);
|
|
return ret;
|
|
}
|
|
|
|
static Bool toBool ( Int x ) {
|
|
Int r = (x == 0) ? False : True;
|
|
return (Bool)r;
|
|
}
|
|
|
|
__attribute__((noinline))
|
|
static Int local_strlen ( const HChar* str )
|
|
{
|
|
Int i = 0;
|
|
while (str[i] != 0) i++;
|
|
return i;
|
|
}
|
|
|
|
__attribute__((noinline))
|
|
static HChar local_toupper ( HChar c )
|
|
{
|
|
if (c >= 'a' && c <= 'z')
|
|
return c + ('A' - 'a');
|
|
else
|
|
return c;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- A simple, generic, vprintf implementation. ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* -----------------------------------------------
|
|
Distantly derived from:
|
|
|
|
vprintf replacement for Checker.
|
|
Copyright 1993, 1994, 1995 Tristan Gingold
|
|
Written September 1993 Tristan Gingold
|
|
Tristan Gingold, 8 rue Parmentier, F-91120 PALAISEAU, FRANCE
|
|
|
|
(Checker itself was GPL'd.)
|
|
----------------------------------------------- */
|
|
|
|
/* Some flags. */
|
|
#define VG_MSG_SIGNED 1 /* The value is signed. */
|
|
#define VG_MSG_ZJUSTIFY 2 /* Must justify with '0'. */
|
|
#define VG_MSG_LJUSTIFY 4 /* Must justify on the left. */
|
|
#define VG_MSG_PAREN 8 /* Parenthesize if present (for %y) */
|
|
#define VG_MSG_COMMA 16 /* Add commas to numbers (for %d, %u) */
|
|
#define VG_MSG_ALTFORMAT 32 /* Convert the value to alternate format */
|
|
|
|
/* Copy a string into the buffer. */
|
|
static __attribute__((noinline))
|
|
UInt myvprintf_str ( void(*send)(HChar,void*),
|
|
void* send_arg2,
|
|
Int flags,
|
|
Int width,
|
|
HChar* str,
|
|
Bool capitalise )
|
|
{
|
|
# define MAYBE_TOUPPER(ch) (capitalise ? local_toupper(ch) : (ch))
|
|
UInt ret = 0;
|
|
Int i, extra;
|
|
Int len = local_strlen(str);
|
|
|
|
if (width == 0) {
|
|
ret += len;
|
|
for (i = 0; i < len; i++)
|
|
send(MAYBE_TOUPPER(str[i]), send_arg2);
|
|
return ret;
|
|
}
|
|
|
|
if (len > width) {
|
|
ret += width;
|
|
for (i = 0; i < width; i++)
|
|
send(MAYBE_TOUPPER(str[i]), send_arg2);
|
|
return ret;
|
|
}
|
|
|
|
extra = width - len;
|
|
if (flags & VG_MSG_LJUSTIFY) {
|
|
ret += extra;
|
|
for (i = 0; i < extra; i++)
|
|
send(' ', send_arg2);
|
|
}
|
|
ret += len;
|
|
for (i = 0; i < len; i++)
|
|
send(MAYBE_TOUPPER(str[i]), send_arg2);
|
|
if (!(flags & VG_MSG_LJUSTIFY)) {
|
|
ret += extra;
|
|
for (i = 0; i < extra; i++)
|
|
send(' ', send_arg2);
|
|
}
|
|
|
|
# undef MAYBE_TOUPPER
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Copy a string into the buffer, escaping bad XML chars. */
|
|
static
|
|
UInt myvprintf_str_XML_simplistic ( void(*send)(HChar,void*),
|
|
void* send_arg2,
|
|
HChar* str )
|
|
{
|
|
UInt ret = 0;
|
|
Int i;
|
|
Int len = local_strlen(str);
|
|
HChar* alt;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
switch (str[i]) {
|
|
case '&': alt = "&"; break;
|
|
case '<': alt = "<"; break;
|
|
case '>': alt = ">"; break;
|
|
default: alt = NULL;
|
|
}
|
|
|
|
if (alt) {
|
|
while (*alt) {
|
|
send(*alt, send_arg2);
|
|
ret++;
|
|
alt++;
|
|
}
|
|
} else {
|
|
send(str[i], send_arg2);
|
|
ret++;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Write P into the buffer according to these args:
|
|
* If SIGN is true, p is a signed.
|
|
* BASE is the base.
|
|
* If WITH_ZERO is true, '0' must be added.
|
|
* WIDTH is the width of the field.
|
|
*/
|
|
static
|
|
UInt myvprintf_int64 ( void(*send)(HChar,void*),
|
|
void* send_arg2,
|
|
Int flags,
|
|
Int base,
|
|
Int width,
|
|
Bool capitalised,
|
|
ULong p )
|
|
{
|
|
HChar buf[40];
|
|
Int ind = 0;
|
|
Int i, nc = 0;
|
|
Bool neg = False;
|
|
HChar* digits = capitalised ? "0123456789ABCDEF" : "0123456789abcdef";
|
|
UInt ret = 0;
|
|
|
|
if (base < 2 || base > 16)
|
|
return ret;
|
|
|
|
if ((flags & VG_MSG_SIGNED) && (Long)p < 0) {
|
|
p = - (Long)p;
|
|
neg = True;
|
|
}
|
|
|
|
if (p == 0)
|
|
buf[ind++] = '0';
|
|
else {
|
|
while (p > 0) {
|
|
if (flags & VG_MSG_COMMA && 10 == base &&
|
|
0 == (ind-nc) % 3 && 0 != ind)
|
|
{
|
|
buf[ind++] = ',';
|
|
nc++;
|
|
}
|
|
buf[ind++] = digits[p % base];
|
|
p /= base;
|
|
}
|
|
}
|
|
|
|
if (neg)
|
|
buf[ind++] = '-';
|
|
|
|
if (width > 0 && !(flags & VG_MSG_LJUSTIFY)) {
|
|
for(; ind < width; ind++) {
|
|
/* assert(ind < 39); */
|
|
if (ind > 39) {
|
|
buf[39] = 0;
|
|
break;
|
|
}
|
|
buf[ind] = (flags & VG_MSG_ZJUSTIFY) ? '0': ' ';
|
|
}
|
|
}
|
|
|
|
/* Reverse copy to buffer. */
|
|
ret += ind;
|
|
for (i = ind -1; i >= 0; i--) {
|
|
send(buf[i], send_arg2);
|
|
}
|
|
if (width > 0 && (flags & VG_MSG_LJUSTIFY)) {
|
|
for(; ind < width; ind++) {
|
|
ret++;
|
|
/* Never pad with zeroes on RHS -- changes the value! */
|
|
send(' ', send_arg2);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* A simple vprintf(). */
|
|
/* EXPORTED */
|
|
__attribute__((noinline))
|
|
UInt
|
|
VG_(debugLog_vprintf) (
|
|
void(*send)(HChar,void*),
|
|
void* send_arg2,
|
|
const HChar* format,
|
|
va_list vargs
|
|
)
|
|
{
|
|
UInt ret = 0;
|
|
Int i;
|
|
Int flags;
|
|
Int width;
|
|
Int n_ls = 0;
|
|
Bool is_long, caps;
|
|
|
|
/* We assume that vargs has already been initialised by the
|
|
caller, using va_start, and that the caller will similarly
|
|
clean up with va_end.
|
|
*/
|
|
|
|
for (i = 0; format[i] != 0; i++) {
|
|
if (format[i] != '%') {
|
|
send(format[i], send_arg2);
|
|
ret++;
|
|
continue;
|
|
}
|
|
i++;
|
|
/* A '%' has been found. Ignore a trailing %. */
|
|
if (format[i] == 0)
|
|
break;
|
|
if (format[i] == '%') {
|
|
/* '%%' is replaced by '%'. */
|
|
send('%', send_arg2);
|
|
ret++;
|
|
continue;
|
|
}
|
|
flags = 0;
|
|
n_ls = 0;
|
|
width = 0; /* length of the field. */
|
|
while (1) {
|
|
switch (format[i]) {
|
|
case '(':
|
|
flags |= VG_MSG_PAREN;
|
|
break;
|
|
case ',':
|
|
case '\'':
|
|
/* If ',' or '\'' follows '%', commas will be inserted. */
|
|
flags |= VG_MSG_COMMA;
|
|
break;
|
|
case '-':
|
|
/* If '-' follows '%', justify on the left. */
|
|
flags |= VG_MSG_LJUSTIFY;
|
|
break;
|
|
case '0':
|
|
/* If '0' follows '%', pads will be inserted. */
|
|
flags |= VG_MSG_ZJUSTIFY;
|
|
break;
|
|
case '#':
|
|
/* If '#' follows '%', alternative format will be used. */
|
|
flags |= VG_MSG_ALTFORMAT;
|
|
break;
|
|
default:
|
|
goto parse_fieldwidth;
|
|
}
|
|
i++;
|
|
}
|
|
parse_fieldwidth:
|
|
/* Compute the field length. */
|
|
while (format[i] >= '0' && format[i] <= '9') {
|
|
width *= 10;
|
|
width += format[i++] - '0';
|
|
}
|
|
while (format[i] == 'l') {
|
|
i++;
|
|
n_ls++;
|
|
}
|
|
|
|
// %d means print a 32-bit integer.
|
|
// %ld means print a word-size integer.
|
|
// %lld means print a 64-bit integer.
|
|
if (0 == n_ls) { is_long = False; }
|
|
else if (1 == n_ls) { is_long = ( sizeof(void*) == sizeof(Long) ); }
|
|
else { is_long = True; }
|
|
|
|
switch (format[i]) {
|
|
case 'o': /* %o */
|
|
if (flags & VG_MSG_ALTFORMAT) {
|
|
ret += 2;
|
|
send('0',send_arg2);
|
|
}
|
|
if (is_long)
|
|
ret += myvprintf_int64(send, send_arg2, flags, 8, width, False,
|
|
(ULong)(va_arg (vargs, ULong)));
|
|
else
|
|
ret += myvprintf_int64(send, send_arg2, flags, 8, width, False,
|
|
(ULong)(va_arg (vargs, UInt)));
|
|
break;
|
|
case 'd': /* %d */
|
|
flags |= VG_MSG_SIGNED;
|
|
if (is_long)
|
|
ret += myvprintf_int64(send, send_arg2, flags, 10, width, False,
|
|
(ULong)(va_arg (vargs, Long)));
|
|
else
|
|
ret += myvprintf_int64(send, send_arg2, flags, 10, width, False,
|
|
(ULong)(va_arg (vargs, Int)));
|
|
break;
|
|
case 'u': /* %u */
|
|
if (is_long)
|
|
ret += myvprintf_int64(send, send_arg2, flags, 10, width, False,
|
|
(ULong)(va_arg (vargs, ULong)));
|
|
else
|
|
ret += myvprintf_int64(send, send_arg2, flags, 10, width, False,
|
|
(ULong)(va_arg (vargs, UInt)));
|
|
break;
|
|
case 'p':
|
|
if (format[i+1] == 'S') {
|
|
i++;
|
|
/* %pS, like %s but escaping chars for XML safety */
|
|
/* Note: simplistic; ignores field width and flags */
|
|
char *str = va_arg (vargs, char *);
|
|
if (str == (char*) 0)
|
|
str = "(null)";
|
|
ret += myvprintf_str_XML_simplistic(send, send_arg2, str);
|
|
} else {
|
|
/* %p */
|
|
ret += 2;
|
|
send('0',send_arg2);
|
|
send('x',send_arg2);
|
|
ret += myvprintf_int64(send, send_arg2, flags, 16, width, True,
|
|
(ULong)((UWord)va_arg (vargs, void *)));
|
|
}
|
|
break;
|
|
case 'x': /* %x */
|
|
case 'X': /* %X */
|
|
caps = toBool(format[i] == 'X');
|
|
if (flags & VG_MSG_ALTFORMAT) {
|
|
ret += 2;
|
|
send('0',send_arg2);
|
|
send('x',send_arg2);
|
|
}
|
|
if (is_long)
|
|
ret += myvprintf_int64(send, send_arg2, flags, 16, width, caps,
|
|
(ULong)(va_arg (vargs, ULong)));
|
|
else
|
|
ret += myvprintf_int64(send, send_arg2, flags, 16, width, caps,
|
|
(ULong)(va_arg (vargs, UInt)));
|
|
break;
|
|
case 'c': /* %c */
|
|
ret++;
|
|
send(va_arg (vargs, int), send_arg2);
|
|
break;
|
|
case 's': case 'S': { /* %s */
|
|
char *str = va_arg (vargs, char *);
|
|
if (str == (char*) 0) str = "(null)";
|
|
ret += myvprintf_str(send, send_arg2,
|
|
flags, width, str, format[i]=='S');
|
|
break;
|
|
}
|
|
|
|
// case 'y': { /* %y - print symbol */
|
|
// Addr a = va_arg(vargs, Addr);
|
|
//
|
|
//
|
|
//
|
|
// HChar *name;
|
|
// if (VG_(get_fnname_w_offset)(a, &name)) {
|
|
// HChar buf[1 + VG_strlen(name) + 1 + 1];
|
|
// if (flags & VG_MSG_PAREN) {
|
|
// VG_(sprintf)(str, "(%s)", name):
|
|
// } else {
|
|
// VG_(sprintf)(str, "%s", name):
|
|
// }
|
|
// ret += myvprintf_str(send, flags, width, buf, 0);
|
|
// }
|
|
// break;
|
|
// }
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void add_to__sprintf_buf ( HChar c, void *p )
|
|
{
|
|
HChar** b = p;
|
|
*(*b)++ = c;
|
|
}
|
|
|
|
UInt VG_(vsprintf) ( HChar* buf, const HChar *format, va_list vargs )
|
|
{
|
|
Int ret;
|
|
HChar* sprintf_ptr = buf;
|
|
|
|
ret = VG_(debugLog_vprintf)
|
|
( add_to__sprintf_buf, &sprintf_ptr, format, vargs );
|
|
add_to__sprintf_buf('\0', &sprintf_ptr);
|
|
|
|
assert(local_strlen(buf) == ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
UInt VG_(sprintf) ( HChar* buf, const HChar *format, ... )
|
|
{
|
|
UInt ret;
|
|
va_list vargs;
|
|
va_start(vargs,format);
|
|
ret = VG_(vsprintf)(buf, format, vargs);
|
|
va_end(vargs);
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------
|
|
percentify()
|
|
------------------------------------------------------------------ */
|
|
|
|
/* This part excerpted from coregrind/m_libcbase.c */
|
|
|
|
// Percentify n/m with d decimal places. Includes the '%' symbol at the end.
|
|
// Right justifies in 'buf'.
|
|
__attribute__((noinline))
|
|
void VG_percentify(ULong n, ULong m, UInt d, Int n_buf, HChar buf[])
|
|
{
|
|
Int i, len, space;
|
|
ULong p1;
|
|
HChar fmt[32];
|
|
|
|
if (m == 0) {
|
|
// Have to generate the format string in order to be flexible about
|
|
// the width of the field.
|
|
VG_(sprintf)(fmt, "%%-%ds", n_buf);
|
|
// fmt is now "%<n_buf>s" where <d> is 1,2,3...
|
|
VG_(sprintf)(buf, fmt, "--%");
|
|
return;
|
|
}
|
|
|
|
p1 = (100*n) / m;
|
|
|
|
if (d == 0) {
|
|
VG_(sprintf)(buf, "%lld%%", p1);
|
|
} else {
|
|
ULong p2;
|
|
UInt ex;
|
|
switch (d) {
|
|
case 1: ex = 10; break;
|
|
case 2: ex = 100; break;
|
|
case 3: ex = 1000; break;
|
|
default: assert(0);
|
|
/* was: VG_(tool_panic)("Currently can only handle 3 decimal places"); */
|
|
}
|
|
p2 = ((100*n*ex) / m) % ex;
|
|
// Have to generate the format string in order to be flexible about
|
|
// the width of the post-decimal-point part.
|
|
VG_(sprintf)(fmt, "%%lld.%%0%dlld%%%%", d);
|
|
// fmt is now "%lld.%0<d>lld%%" where <d> is 1,2,3...
|
|
VG_(sprintf)(buf, fmt, p1, p2);
|
|
}
|
|
|
|
len = local_strlen(buf);
|
|
space = n_buf - len;
|
|
if (space < 0) space = 0; /* Allow for v. small field_width */
|
|
i = len;
|
|
|
|
/* Right justify in field */
|
|
for ( ; i >= 0; i--) buf[i + space] = buf[i];
|
|
for (i = 0; i < space; i++) buf[i] = ' ';
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Stats ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* This part excerpted from coregrind/m_translate.c */
|
|
|
|
static UInt n_SP_updates_fast = 0;
|
|
static UInt n_SP_updates_generic_known = 0;
|
|
static UInt n_SP_updates_generic_unknown = 0;
|
|
|
|
__attribute__((noinline))
|
|
void VG_print_translation_stats ( void )
|
|
{
|
|
HChar buf[6];
|
|
UInt n_SP_updates = n_SP_updates_fast + n_SP_updates_generic_known
|
|
+ n_SP_updates_generic_unknown;
|
|
VG_percentify(n_SP_updates_fast, n_SP_updates, 1, 6, buf);
|
|
VG_(printf)(
|
|
"translate: fast SP updates identified: %'u (%s)\n",
|
|
n_SP_updates_fast, buf );
|
|
|
|
VG_percentify(n_SP_updates_generic_known, n_SP_updates, 1, 6, buf);
|
|
VG_(printf)(
|
|
"translate: generic_known SP updates identified: %'u (%s)\n",
|
|
n_SP_updates_generic_known, buf );
|
|
|
|
VG_percentify(n_SP_updates_generic_unknown, n_SP_updates, 1, 6, buf);
|
|
VG_(printf)(
|
|
"translate: generic_unknown SP updates identified: %'u (%s)\n",
|
|
n_SP_updates_generic_unknown, buf );
|
|
}
|
|
|
|
|
|
|
|
int main ( void )
|
|
{
|
|
VG_print_translation_stats();
|
|
return 0;
|
|
}
|