Files
SingularityViewer/indra/cwdebug/debug.cc
Aleric Inglewood f6b57d956d Added base class AIStateMachine.
This is the skeleton needed to implement classes that can be reused and
work together, which can perform asynchronous tasks (read: need to wait
for certain events before they can continue).

An example would be the task of waiting for a given inventory folder to
be read. This could then be used to improve the builtin AO
(automatically reading that folder when a notecard is dropped, and
continuing when the whole folder is read).

It's first use will be communication with a filepicker that runs
in a plugin.
2011-05-08 17:08:48 +02:00

414 lines
14 KiB
C++

// slviewer -- Second Life Viewer Source Code
//
//! @file debug.cc
//! @brief This file contains the definitions of debug related objects and functions.
//
// Copyright (C) 2008, by
//
// Carlo Wood, Run on IRC <carlo@alinoe.com>
// RSA-1024 0x624ACAD5 1997-01-26 Sign & Encrypt
// Fingerprint16 = 32 EC A7 B6 AC DB 65 A6 F6 F6 55 DD 1C DC FF 61
//
// 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, see <http://www.gnu.org/licenses/>.
#ifndef USE_PCH
#include "sys.h" // Needed for platform-specific code
#endif
#ifdef CWDEBUG
#ifndef USE_PCH
#include <cctype> // Needed for std::isprint
#include <iomanip> // Needed for setfill and setw
#include <map>
#include <string>
#include <sstream>
#include <fstream>
#include "debug.h"
#ifdef USE_LIBCW
#include <libcw/memleak.h> // memleak_filter
#endif
#include <sys/types.h>
#include <unistd.h>
#endif // USE_PCH
#define BACKTRACE_AQUIRE_LOCK libcwd::_private_::mutex_tct<libcwd::_private_::backtrace_instance>::lock()
#define BACKTRACE_RELEASE_LOCK libcwd::_private_::mutex_tct<libcwd::_private_::backtrace_instance>::unlock()
namespace debug {
#if CWDEBUG_LOCATION
void BackTrace::dump_backtrace(void) const
{
for (int frame = 0; frame < frames(); ++frame)
{
libcwd::location_ct loc((char*)buffer()[frame] + libcwd::builtin_return_address_offset);
Dout(dc::notice|continued_cf, '#' << std::left << std::setw(3) << frame <<
std::left << std::setw(16) << buffer()[frame] << ' ' << loc << "\n in ");
char const* mangled_function_name = loc.mangled_function_name();
if (mangled_function_name != libcwd::unknown_function_c)
{
std::string demangled_function_name;
libcwd::demangle_symbol(mangled_function_name, demangled_function_name);
Dout(dc::finish, demangled_function_name);
}
else
Dout(dc::finish, mangled_function_name);
}
}
#endif // CWDEBUG_LOCATION
#if CWDEBUG_ALLOC && CWDEBUG_LOCATION
typedef std::map<BackTrace, int, std::less<BackTrace>, libcwd::_private_::internal_allocator> backtrace_map_t;
backtrace_map_t* backtrace_map;
static int total_calls = 0;
static int number_of_stack_traces = 0;
void my_backtrace_hook(void** buffer, int frames LIBCWD_COMMA_TSD_PARAM)
{
++total_calls;
backtrace_map_t::iterator iter;
set_alloc_checking_off(__libcwd_tsd);
{
BackTrace backtrace(buffer, frames);
std::pair<backtrace_map_t::iterator, bool> res = backtrace_map->insert(backtrace_map_t::value_type(backtrace, 0));
if (res.second)
++number_of_stack_traces;
++res.first->second;
iter = res.first;
}
set_alloc_checking_on(__libcwd_tsd);
#if 0
// Dump the stack trace.
iter->first.dump_backtrace();
#endif
}
void start_recording_backtraces(void)
{
BACKTRACE_AQUIRE_LOCK;
libcwd::backtrace_hook = my_backtrace_hook;
BACKTRACE_RELEASE_LOCK;
//Debug(dc::malloc.on());
LIBCWD_TSD_DECLARATION;
set_alloc_checking_off(__libcwd_tsd);
backtrace_map = new backtrace_map_t;
set_alloc_checking_on(__libcwd_tsd);
}
struct Compare {
bool operator()(backtrace_map_t::const_iterator const& iter1, backtrace_map_t::const_iterator const& iter2)
{
return iter1->second > iter2->second;
}
};
void stop_recording_backtraces(void)
{
//Debug(dc::malloc.off());
BACKTRACE_AQUIRE_LOCK;
libcwd::backtrace_hook = NULL;
if (!backtrace_map)
{
Dout(dc::notice, "Not recording; call cwdebug_start() first.");
return;
}
Dout(dc::notice, "Total number of calls: " << total_calls);
Dout(dc::notice, "Number of different stack traces: " << number_of_stack_traces);
Dout(dc::notice, "First 10 stack traces:");
std::list<backtrace_map_t::const_iterator> entries;
for (backtrace_map_t::const_iterator iter = backtrace_map->begin(); iter != backtrace_map->end(); ++iter)
entries.push_back(iter);
entries.sort(Compare());
int count = 0;
for (std::list<backtrace_map_t::const_iterator>::iterator iter = entries.begin(); iter != entries.end(); ++iter, ++count)
{
Dout(dc::notice, "Used: " << (*iter)->second);
// Dump the stack trace.
(*iter)->first.dump_backtrace();
if (count == 10)
break;
}
// Clear all data.
LIBCWD_TSD_DECLARATION;
set_alloc_checking_off(__libcwd_tsd);
delete backtrace_map;
set_alloc_checking_on(__libcwd_tsd);
backtrace_map = NULL;
total_calls = 0;
number_of_stack_traces = 0;
BACKTRACE_RELEASE_LOCK;
}
#endif // CWDEBUG_ALLOC && CWDEBUG_LOCATION
namespace channels { // namespace DEBUGCHANNELS
namespace dc {
#ifndef DOXYGEN
#define DDCN(x) (x)
#endif
// Add new debug channels here.
channel_ct viewer DDCN("VIEWER"); //!< This debug channel is used for the normal debugging out of the viewer.
channel_ct primbackup DDCN("PRIMBACKUP"); //!< This debug channel is used for output related to primbackup.
channel_ct gtk DDCN("GTK"); //!< This debug channel is used for output related to gtk.
channel_ct sdl DDCN("SDL"); //!< This debug channel is used for output related to sdl locking.
channel_ct backtrace DDCN("BACKTRACE"); //!< This debug channel is used for backtraces.
channel_ct statemachine DDCN("STATEMACHINE"); //!< This debug channel is used class AIStateMachine.
} // namespace dc
} // namespace DEBUGCHANNELS
// Anonymous namespace, this map and its initialization functions are private to this file
// for Thead-safeness reasons.
namespace {
/*! @brief The type of rcfile_dc_states.
* @internal
*/
typedef std::map<std::string, bool> rcfile_dc_states_type;
/*! @brief Map containing the default debug channel states used at the start of each new thread.
* @internal
*
* The first thread calls main, which calls debug::init which will initialize this
* map with all debug channel labels and whether or not they were turned on in the
* rcfile or not.
*/
rcfile_dc_states_type rcfile_dc_states;
/*! @brief Set the default state of debug channel \a dc_label.
* @internal
*
* This function is called once for each debug channel.
*/
void set_state(char const* dc_label, bool is_on)
{
std::pair<rcfile_dc_states_type::iterator, bool> res =
rcfile_dc_states.insert(rcfile_dc_states_type::value_type(std::string(dc_label), is_on));
if (!res.second)
Dout(dc::warning, "Calling set_state() more than once for the same label!");
return;
}
/*! @brief Save debug channel states.
* @internal
*
* One time initialization function of rcfile_dc_state.
* This must be called from debug::init after reading the rcfile.
*/
void save_dc_states(void)
{
// We may only call this function once: it reflects the states as stored
// in the rcfile and that won't change. Therefore it is not needed to
// lock `rcfile_dc_states', it is only written to by the first thread
// (once, via main -> init) when there are no other threads yet.
static bool second_time = false;
if (second_time)
{
Dout(dc::warning, "Calling save_dc_states() more than once!");
return;
}
second_time = true;
ForAllDebugChannels( set_state(debugChannel.get_label(), debugChannel.is_on()) );
}
} // anonymous namespace
/*! @brief Returns the the original state of a debug channel.
* @internal
*
* For a given \a dc_label, which must be the exact name (<tt>channel_ct::get_label</tt>) of an
* existing debug channel, this function returns \c true when the corresponding debug channel was
* <em>on</em> at the startup of the application, directly after reading the libcwd runtime
* configuration file (.libcwdrc).
*
* If the label/channel did not exist at the start of the application, it will return \c false
* (note that libcwd disallows adding debug channels to modules - so this would probably
* a bug).
*/
bool is_on_in_rcfile(char const* dc_label)
{
rcfile_dc_states_type::const_iterator iter = rcfile_dc_states.find(std::string(dc_label));
if (iter == rcfile_dc_states.end())
{
Dout(dc::warning, "is_on_in_rcfile(\"" << dc_label << "\"): \"" << dc_label << "\" is an unknown label!");
return false;
}
return iter->second;
}
#if LIBCWD_THREAD_SAFE
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// I can cause I'm the maintainer of libcwd ;).
libcwd::_private_::pthread_lock_interface_ct cout_mutex(&mutex);
libcwd::_private_::lock_interface_base_ct* cout_mutex_ptr(&cout_mutex);
#endif
/*! @brief Initialize debugging code from new threads.
*
* This function needs to be called at the start of each new thread,
* because a new thread starts in a completely reset state.
*
* The function turns on all debug channels that were turned on
* after reading the rcfile at the start of the application.
* Furthermore it initializes the debug ostream, its mutex and the
* margin of the default debug object (Dout).
*/
void init_thread(void)
{
// Turn on all debug channels that are turned on as per rcfile configuration.
ForAllDebugChannels(
if (!debugChannel.is_on() && is_on_in_rcfile(debugChannel.get_label()))
debugChannel.on();
);
// Turn on debug output.
Debug( libcw_do.on() );
static std::ofstream debug_file;
static bool debug_out_opened = false;
if (!debug_out_opened)
{
debug_out_opened = true; // Thread-safe, cause the main thread calls this first while it's still alone.
std::ostringstream os;
os << "debug.out." << getpid();
debug_file.open(os.str().c_str());
}
static debug::TeeStream debug_stream(std::cout, debug_file);
#if LIBCWD_THREAD_SAFE
Debug( libcw_do.set_ostream(&debug_stream, cout_mutex_ptr) );
#else
Debug( libcw_do.set_ostream(&debug_stream) );
#endif
static bool first_thread = true;
if (!first_thread) // So far, the application has only one thread. So don't add a thread id.
{
// Set the thread id in the margin.
char margin[18];
sprintf(margin, "0x%-14lx ", pthread_self());
Debug( libcw_do.margin().assign(margin, 17) );
}
first_thread = false;
}
/*! @brief Initialize debugging code from main.
*
* This function initializes the debug code.
*/
void init(void)
{
#if CWDEBUG_ALLOC && defined(USE_LIBCW)
// Tell the memory leak detector which parts of the code are
// expected to leak so that we won't get an alarm for those.
{
std::vector<std::pair<std::string, std::string> > hide_list;
hide_list.push_back(std::pair<std::string, std::string>("libdl.so.2", "_dlerror_run"));
hide_list.push_back(std::pair<std::string, std::string>("libstdc++.so.6", "__cxa_get_globals"));
// The following is actually necessary because of a bug in glibc
// (see http://sources.redhat.com/bugzilla/show_bug.cgi?id=311).
hide_list.push_back(std::pair<std::string, std::string>("libc.so.6", "dl_open_worker"));
memleak_filter().hide_functions_matching(hide_list);
}
{
std::vector<std::string> hide_list;
// Also because of http://sources.redhat.com/bugzilla/show_bug.cgi?id=311
hide_list.push_back(std::string("ld-linux.so.2"));
memleak_filter().hide_objectfiles_matching(hide_list);
}
memleak_filter().set_flags(libcwd::show_objectfile|libcwd::show_function);
#endif
// The following call allocated the filebuf's of cin, cout, cerr, wcin, wcout and wcerr.
// Because this causes a memory leak being reported, make them invisible.
Debug(set_invisible_on());
// You want this, unless you mix streams output with C output.
// Read http://gcc.gnu.org/onlinedocs/libstdc++/27_io/howto.html#8 for an explanation.
//std::ios::sync_with_stdio(false);
// Cancel previous call to set_invisible_on.
Debug(set_invisible_off());
// This will warn you when you are using header files that do not belong to the
// shared libcwd object that you linked with.
Debug( check_configuration() );
Debug(
libcw_do.on(); // Show which rcfile we are reading!
ForAllDebugChannels(
while (debugChannel.is_on())
debugChannel.off() // Print as little as possible though.
);
read_rcfile(); // Put 'silent = on' in the rcfile to suppress most of the output here.
libcw_do.off()
);
save_dc_states();
init_thread();
}
#if CWDEBUG_LOCATION
/*! @brief Return call location.
*
* @param return_addr The return address of the call.
*/
std::string call_location(void const* return_addr)
{
libcwd::location_ct loc((char*)return_addr + libcwd::builtin_return_address_offset);
std::ostringstream convert;
convert << loc;
return convert.str();
}
std::vector<BackTrace> __attribute__ ((visibility("default"))) backtraces;
pthread_mutex_t __attribute__ ((visibility("default"))) backtrace_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
} // namespace debug
#if CWDEBUG_ALLOC
// These can be called from gdb.
void cwdebug_start()
{
debug::start_recording_backtraces();
}
void cwdebug_stop()
{
debug::stop_recording_backtraces();
}
#endif
#if CWDEBUG_LOCATION
void cwdebug_backtrace(int n)
{
if (0 < n && n <= debug::backtraces.size())
{
Dout(dc::backtrace, "Backtrace #" << n << ":");
debug::backtraces[n - 1].dump_backtrace();
}
else
std::cout << "No such backtrace nr. Max is " << debug::backtraces.size() << std::endl;
}
#endif
#endif // CWDEBUG