Mercurial > flash_v2
diff host/infra/trace.cxx @ 76:435cced73e2f ecos-v1_3_1-release
eCos v1.3.1 merged from eCos master repository on 2000-03-27-23:22:51-BST
| author | jlarmour |
|---|---|
| date | Tue, 28 Mar 2000 14:10:45 +0000 |
| parents | |
| children | f58bc1b2c2bc |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/host/infra/trace.cxx @@ -0,0 +1,683 @@ +//{{{ Banner + +//============================================================================ +// +// trace.cxx +// +// Host side implementation of the infrastructure trace facilities. +// +//============================================================================ +//####COPYRIGHTBEGIN#### +// +// ---------------------------------------------------------------------------- +// Copyright (C) 1998, 1999, 2000 Red Hat, Inc. +// +// This file is part of the eCos host tools. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the Free Software Foundation, Inc., +// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// ---------------------------------------------------------------------------- +// +//####COPYRIGHTEND#### +//============================================================================ +//#####DESCRIPTIONBEGIN#### +// +// Author(s): bartv +// Contact(s): bartv +// Date: 1998/12/07 +// Version: 0.01 +// Purpose: To provide a host-side implementation of the eCos tracing +// facilities. +// +//####DESCRIPTIONEND#### +//============================================================================ + +//}}} +//{{{ #include's + +// Make sure that the host-side extensions get prototyped +// as well. Note that the tracing code needs to interact +// with the assertion facilities to set up an appropriate +// callback. +#define CYG_DECLARE_HOST_ASSERTION_SUPPORT +#include "pkgconf/infra.h" +#include "cyg/infra/cyg_type.h" +#include "cyg/infra/cyg_ass.h" + +// Without this #define the tracing enums and prototypes are +// not visible. +#define CYGDBG_USE_TRACING +#include "cyg/infra/cyg_trac.h" + +// The standard C++ string class is used extensively +#include <string> + +// Add a few C headers +#include <cctype> +#include <cstring> +#include <cstdio> + +//}}} + +//{{{ Description + +// ------------------------------------------------------------------------- +// The tracing macros end up calling one of the following routines: +// +// void cyg_tracenomsg(cyg_uint32 what, char* fn, char* file, cyg_uint32 line) +// void cyg_tracemsg( ..., char* msg) +// void cyg_tracemsg2( ..., CYG_ADDRWORD arg0, CYG_ADDRWORD arg1 ) +// void cyg_tracemsg4( ..., CYG_ADDRWORD arg0, CYG_ADDRWORD arg1, ... ) +// void cyg_tracemsg6( ..., CYG_ADDRWORD arg0, CYG_ADDRWORD arg1, ... ) +// void cyg_tracemsg8( ..., CYG_ADDRWORD arg0, CYG_ADDRWORD arg1, ... ) +// +// For the 2/4/6/8 variants the msg argument is essentially a printf() +// style format string. However the intention is that the implementation +// of the trace code can delay doing the formatting until the trace +// information is actually needed (with obvious consequences for +// generated strings). Such an implementation would significantly +// reduce the overheads associated with tracing, and is what is implemented +// here. +// +// CYG_ADDRWORD is likely to be either "int" or the platform-specific +// 64 bit data type: it should be big enough to hold either a pointer +// or any normal integral type. This causes problems on machines which +// have e.g. 32 bit int and 64 bit long: any 32 bit quantities will +// have been converted to 64 bit quantities in the calling code, and +// it is no longer possible to just pass the format string to sprintf(). +// Instead what amounts to a re-implementation of sprintf() is needed +// here. +// +// The basic implementation of this trace code is as follows: +// +// 1) a static array of data structures to hold the trace data. The +// size can be configured. There is a current index into this +// array. +// +// 2) the various trace functions simply update this array and the +// counter. +// +// 3) all of the trace functions also check a static to see whether +// or not it is necessary to install a trace handler. This cannot +// be done by means of a static object due to constructor priority +// ordering problems. +// +// 4) the callback function does all the hardware of the formatting +// etc. + +//}}} +//{{{ Types and statics + +// ---------------------------------------------------------------------------- +// A data structure rather than a class is used to hold the trace data. +// This guarantees that the array gets put in the bss section and is properly +// zeroed. A "valid" field in the structure can be checked when dumping the +// array. + +typedef struct trace_entry { + bool valid; + cyg_uint32 what; + cyg_uint32 line; + char* fn; + char* file; + char* msg; + CYG_ADDRWORD data[8]; +} trace_entry; + +#ifndef CYGNUM_INFRA_TRACE_VECTOR_SIZE +# define CYGNUM_INFRA_TRACE_VECTOR_SIZE 2048 +#endif + +static trace_entry tracevec[CYGNUM_INFRA_TRACE_VECTOR_SIZE]; +static volatile int trace_index = 0; + +// Forward declaration of the callback function, for convenience. +static void trace_callback(void (*)(const char*)); + +// Has the callback been installed yet? +static bool callback_installed = false; + +//}}} +//{{{ The trace functions themselves + +// ---------------------------------------------------------------------------- +// The functions that get called by the trace macros. Typically these work +// as follows: +// +// 1) read and increment the trace index. This makes tracing marginally usable +// in multi-threaded systems. +// +// 2) invalidate the entry that is about to be updated. Again this helps a bit +// with multi-threaded systems. +// +// 3) fill in all the fields as per the command-line arguments, zeroing +// unused fields. +// +// 4) set the valid flag to true, which means the contents can now be output. +// +// This is by no means sufficient to guarantee that a call to dump the trace +// vector in some other thread can work safely, but it may help a little bit. + +extern "C" void +cyg_tracenomsg(char* fn, char* file, cyg_uint32 line) +{ + int i = trace_index; + tracevec[i].valid = false; + trace_index = (trace_index + 1) % CYGNUM_INFRA_TRACE_VECTOR_SIZE; + + tracevec[i].what = cyg_trace_trace; + tracevec[i].fn = fn; + tracevec[i].file = file; + tracevec[i].line = line; + tracevec[i].msg = 0; + tracevec[i].data[0] = 0; + tracevec[i].data[1] = 0; + tracevec[i].data[2] = 0; + tracevec[i].data[3] = 0; + tracevec[i].data[4] = 0; + tracevec[i].data[5] = 0; + tracevec[i].data[6] = 0; + tracevec[i].data[7] = 0; + tracevec[i].valid = true; + + if (!callback_installed) { + cyg_assert_install_failure_callback("Trace", &trace_callback); + callback_installed = true; + } +} + +extern "C" void +cyg_tracemsg(cyg_uint32 what, char* fn, char* file, cyg_uint32 line, char* msg) +{ + int i = trace_index; + tracevec[i].valid = false; + trace_index = (trace_index + 1) % CYGNUM_INFRA_TRACE_VECTOR_SIZE; + + tracevec[i].what = what; + tracevec[i].fn = fn; + tracevec[i].file = file; + tracevec[i].line = line; + tracevec[i].msg = msg; + tracevec[i].data[0] = 0; + tracevec[i].data[1] = 0; + tracevec[i].data[2] = 0; + tracevec[i].data[3] = 0; + tracevec[i].data[4] = 0; + tracevec[i].data[5] = 0; + tracevec[i].data[6] = 0; + tracevec[i].data[7] = 0; + tracevec[i].valid = true; + + if (!callback_installed) { + cyg_assert_install_failure_callback("Trace", &trace_callback); + callback_installed = true; + } +} + +extern "C" void +cyg_tracemsg2(cyg_uint32 what, char *fn, char* file, cyg_uint32 line, char *msg, + CYG_ADDRWORD arg0, CYG_ADDRWORD arg1) +{ + int i = trace_index; + tracevec[i].valid = false; + trace_index = (trace_index + 1) % CYGNUM_INFRA_TRACE_VECTOR_SIZE; + + tracevec[i].what = what; + tracevec[i].fn = fn; + tracevec[i].file = file; + tracevec[i].line = line; + tracevec[i].msg = msg; + tracevec[i].data[0] = arg0; + tracevec[i].data[1] = arg1; + tracevec[i].data[2] = 0; + tracevec[i].data[3] = 0; + tracevec[i].data[4] = 0; + tracevec[i].data[5] = 0; + tracevec[i].data[6] = 0; + tracevec[i].data[7] = 0; + tracevec[i].valid = true; + + if (!callback_installed) { + cyg_assert_install_failure_callback("Trace", &trace_callback); + callback_installed = true; + } +} + +extern "C" void +cyg_tracemsg4(cyg_uint32 what, char *fn, char* file, cyg_uint32 line, char *msg, + CYG_ADDRWORD arg0, CYG_ADDRWORD arg1, + CYG_ADDRWORD arg2, CYG_ADDRWORD arg3) +{ + int i = trace_index; + tracevec[i].valid = false; + trace_index = (trace_index + 1) % CYGNUM_INFRA_TRACE_VECTOR_SIZE; + + tracevec[i].what = what; + tracevec[i].fn = fn; + tracevec[i].file = file; + tracevec[i].line = line; + tracevec[i].msg = msg; + tracevec[i].data[0] = arg0; + tracevec[i].data[1] = arg1; + tracevec[i].data[2] = arg2; + tracevec[i].data[3] = arg3; + tracevec[i].data[4] = 0; + tracevec[i].data[5] = 0; + tracevec[i].data[6] = 0; + tracevec[i].data[7] = 0; + tracevec[i].valid = true; + + if (!callback_installed) { + cyg_assert_install_failure_callback("Trace", &trace_callback); + callback_installed = true; + } +} + +extern "C" void +cyg_tracemsg6(cyg_uint32 what, char *fn, char* file, cyg_uint32 line, char *msg, + CYG_ADDRWORD arg0, CYG_ADDRWORD arg1, + CYG_ADDRWORD arg2, CYG_ADDRWORD arg3, + CYG_ADDRWORD arg4, CYG_ADDRWORD arg5) +{ + int i = trace_index; + tracevec[i].valid = false; + trace_index = (trace_index + 1) % CYGNUM_INFRA_TRACE_VECTOR_SIZE; + + tracevec[i].what = what; + tracevec[i].fn = fn; + tracevec[i].file = file; + tracevec[i].line = line; + tracevec[i].msg = msg; + tracevec[i].data[0] = arg0; + tracevec[i].data[1] = arg1; + tracevec[i].data[2] = arg2; + tracevec[i].data[3] = arg3; + tracevec[i].data[4] = arg4; + tracevec[i].data[5] = arg5; + tracevec[i].data[6] = 0; + tracevec[i].data[7] = 0; + tracevec[i].valid = true; + + if (!callback_installed) { + cyg_assert_install_failure_callback("Trace", &trace_callback); + callback_installed = true; + } +} + +extern "C" void +cyg_tracemsg8(cyg_uint32 what, char *fn, char* file, cyg_uint32 line, char *msg, + CYG_ADDRWORD arg0, CYG_ADDRWORD arg1, + CYG_ADDRWORD arg2, CYG_ADDRWORD arg3, + CYG_ADDRWORD arg4, CYG_ADDRWORD arg5, + CYG_ADDRWORD arg6, CYG_ADDRWORD arg7) +{ + int i = trace_index; + tracevec[i].valid = false; + trace_index = (trace_index + 1) % CYGNUM_INFRA_TRACE_VECTOR_SIZE; + + tracevec[i].what = what; + tracevec[i].fn = fn; + tracevec[i].file = file; + tracevec[i].line = line; + tracevec[i].msg = msg; + tracevec[i].data[0] = arg0; + tracevec[i].data[1] = arg1; + tracevec[i].data[2] = arg2; + tracevec[i].data[3] = arg3; + tracevec[i].data[4] = arg4; + tracevec[i].data[5] = arg5; + tracevec[i].data[6] = arg6; + tracevec[i].data[7] = arg7; + tracevec[i].valid = true; + + if (!callback_installed) { + cyg_assert_install_failure_callback("Trace", &trace_callback); + callback_installed = true; + } +} + +//}}} +//{{{ Output callback + +// ---------------------------------------------------------------------------- +// Dumping the output. The assertion code will invoke a single callback +// function, cyg_trace_dummy::trace_callback(), with a function pointer +// that can be used for the actual output. +// +// The trace_callback() function loops through the various entries in the +// vector, ignoring invalid ones, and invoking output_entry() for the +// valid ones. +// +// There are a number of utility routines: +// +// trim_file() is used to take a full pathname and return just the +// final part of it as a C++ string. There is an upper bound on the +// length of this string. +// +// trim_linenum() formats the linenumber sensibly. +// +// trim_function() is used to parse a __PRETTY_FUNCTION__ value +// and produce something more manageable. +// +// parse_msg() is used to construct the full trace message. +// Because of possible 32/64 bit confusion it is not possible +// to just use sprintf() for this. + +static std::string +trim_file(char* file) +{ + // If the output is to look reasonable then the result should be a + // fixed length. 20 characters is reasonable for now. + const int max_filename_len = 20; + + if (0 == file) { + return std::string(max_filename_len, ' '); + } + + // Move to the end of the string, and then back again until + // a directory separator is found. Given the number of levels + // in a typical eCos directory hierarchy it is probably not + // worthwhile outputting any of that information. + char * pEnd = file + strlen(file); + while ((pEnd > file) && ('/' != *pEnd) && ('\\' != *pEnd)) { + pEnd--; + } + if (pEnd != file) + pEnd++; + + std::string result = ""; + int i = 0; + for ( ;(*pEnd != '\0') && (i < max_filename_len); i++, pEnd++) { + result += *pEnd; + } + for ( ; i < max_filename_len; i++) { + result += ' '; + } + + return result; +} + +// The linenumber output should be up to four digits, right-padded +// with spaces. sprintf() will do the trick nicely. + +static std::string +trim_linenum(cyg_uint32 line) +{ + char buf[32]; + sprintf(buf, "%-4d", (int) line); + return buf; +} + +// Extract a function name. On the target side function names +// are usually obtained via __PRETTY_FUNCTION__, and the resulting +// output is a bit on the large side: return value, arguments, etc +// are all included. On the host side the function name is normally +// supplied explicitly and should not be trimmed at all. +// +// Padding is not appropriate since the function name is likely +// to be followed immediately by the argument list. No maximum +// length is imposed - arguably that is a bad idea. +static std::string +trim_function(char* fn) +{ + if (0 == fn) { + return "<unknown>"; + } + +#if 1 + return fn; +#else + // This implements the target-side behaviour. + // + // First locate the opening bracket. The function name can + // be identified by walking backwards from that. + char *s; + for (s = fn; ('\0' != *s) && ('(' != *s); s++); + for ( ; (s > fn) && (*s != ' '); s--); + if ( s > fn) s++; + + std::string result = ""; + while ( ('\0' != *s) && ('(' != *s) ) + result += *s++; + + return result; +#endif +} + +// The trace format string contained a %s. It is necessary to check +// whether the argument is still valid, and return a suitable +// approximation to the actual data. +static std::string +trim_string(char * arg) +{ + const int max_string_len = 20; + + std::string result = ""; + if (0 == arg) { + return result; + } + int i; + for ( i = 0; (i < max_string_len) && ('\0' != *arg) && isprint(*arg); i++, arg++) { + result += *arg; + } + return result; +} + +// ---------------------------------------------------------------------------- +// Parse a printf() style format string and do the appropriate expansions. +// Because of possible confusion between 32 and 64 bit integers it is not +// possible to use sprintf() itself. +// +// It is assumed that the format string is valid, as are most of the +// arguments. The possible exception is %s arguments where a little bit of +// checking happens first. + +static std::string +parse_msg(char* msg, trace_entry& entry) +{ + if (0 == msg) { + return ""; + } + // Keep track of the number of arguments in the trace_entry + // that have been processed. + int args_index = 0; + + // A utility buffer for sprintf(), e.g. for integer-> string conversions. + char util_buf[64]; + + std::string result = ""; + for ( ; '\0' != *msg; msg++) { + + if ('%' != *msg) { + result += *msg; + continue; + } + + // We have a format string. Extract all of it. + std::string format = "%"; + msg++; + + // The first part of the format string may be one or more flags. + while ( ('-' == *msg) || ('+' == *msg) || (' ' == *msg) || + ('#' == *msg) || ('0' == *msg) ) { + format += *msg++; + } + + // Next comes the width. If this is an asterix it is necessary to + // substitute in an actual argument. + if ('*' == *msg) { + int width = (args_index < 8) ? (int) entry.data[args_index++] : 0; + sprintf(util_buf, "%d", width); + format += util_buf; + msg++; + } else { + // Otherwise the width should be one or more digits + while( isdigit(*msg) ) { + format += *msg++; + } + } + + // Look for a precision, again coping with an asterix. + if ('.' == *msg) { + format += *msg++; + if ('*' == *msg) { + int precision = (args_index < 8) ? (int) entry.data[args_index++] : 0; + sprintf(util_buf, "%d", precision); + format += util_buf; + msg++; + } else { + // The precision should be one or more digits, with an optional - + if ('-' == *msg) { + format += *msg++; + } + while (isdigit(*msg)) { + format += *msg++; + } + } + } + + // Now look for h,l and L. These have to be remembered. + bool short_version = false; + bool long_version = false; + if ('h' == *msg) { + format += *msg++; + short_version = true; + } else if (('l' == *msg) || ('L' == *msg)) { + format += *msg++; + long_version = true; + } + + // The end of the format string has been reached. + int format_ch = *msg; + format += *msg; + + // If we have already formatted too many arguments, there is no point + // in trying to do the actual formatting. + if ( 8 <= args_index ) { + continue; + } + CYG_ADDRWORD val = entry.data[args_index++]; + + switch( format_ch ) { + case '%' : + result += '%'; + break; + + case 'd' : + case 'i' : + case 'o' : + case 'u' : + case 'x' : + case 'X' : + // "format" contains the appropriate format string. + // Invoke sprintf() using util_buf, doing the + // appropriate cast, and then append the output + // of util_buf. + // + // This is not totally robust. If a ridiculous + // precision has been specified then util_buf may + // overflow. + if (long_version) { + sprintf(util_buf, format.c_str(), (long) val); + } else { + // The implicit cast rules mean that shorts do not + // require any special attention. + sprintf(util_buf, format.c_str(), (int) val); + } + result += util_buf; + break; + + case 'c' : + sprintf(util_buf, format.c_str(), (int) val); + result += util_buf; + break; + + case 'p' : + sprintf(util_buf, format.c_str(), (void *) val); + result += util_buf; + break; + + case 's' : + { + std::string data = trim_string((char *) val); + sprintf(util_buf, format.c_str(), data.c_str()); + result += util_buf; + break; + } + + default : + // Any attempt to do floating point conversions would be + // rather tricky given the casts that have been applied. + // There is no point in doing anything for unrecognised + // sequences. + break; + } + } + return result; +} + +// ---------------------------------------------------------------------------- + + +static void +output_entry(void (*pOutputFn)(const char*), trace_entry& entry) +{ + std::string output = trim_file(entry.file) + " " + + trim_linenum(entry.line) + " " + + trim_function(entry.fn) + " "; + if (0 != entry.msg) { + + switch( entry.what) { + case cyg_trace_trace : output += " '"; break; + case cyg_trace_enter : output += "{{"; break; + case cyg_trace_args : output += "(("; break; + case cyg_trace_return : output += "}}"; break; + default : output += " ?"; + } + output += parse_msg(entry.msg, entry); + switch( entry.what) { + case cyg_trace_trace : output += "' "; break; + case cyg_trace_enter : break; + case cyg_trace_args : output += "))"; break; + case cyg_trace_return : break; + default : output += "? "; + } + } + output += "\n"; + (*pOutputFn)(output.c_str()); +} + +static void +trace_callback( void (*pOutputFn)(const char*)) +{ + if ((trace_index < 0) || (trace_index >= CYGNUM_INFRA_TRACE_VECTOR_SIZE)) + return; + + // Start at the last entry and work back down to zero, skipping + // invalid ones. Then go to the top and work back to the current index. + int i; + for (i = trace_index - 1; i >= 0; i--) { + if (tracevec[i].valid) { + output_entry(pOutputFn, tracevec[i]); + } + } + for (i = (CYGNUM_INFRA_TRACE_VECTOR_SIZE - 1); i >= trace_index; i--) { + if (tracevec[i].valid) { + output_entry(pOutputFn, tracevec[i]); + } + } +} + +//}}}
