This patch has no influence if you don't have libcwd installed. Note that libcwd (http://libcwd.sourceforge.net/) is only available for linux. A default compile of libcwd does memory allocation tracking, which is too slow for everyday usage of the viewer (usable, but notably slower) and we don't need that. Configure your libcwd as follows: ./configure --prefix=/sl/usr --disable-alloc --enable-optimize Or whatever prefix you prefer (add --enable-maintainer-mode if you're compiling it from the SVN repository), add --disable-nonthreading to compile twice as fast. If you have it installed you can activate it's use by setting a few environment variables: CXXFLAGS="$(pkg-config --cflags libcwd_r)" LDFLAGS="$(pkg-config --libs libcwd_r) -lpthread" and then reconfiguring the viewer. The -lpthread is needed when using ld.gold, however, if you leave it out you might get an LDFLAGS that ends on trailing whitespaces, which doesn't work for some reason. Also, if you installed it in a non-standard place (/sl/usr above) then you need this to run the viewer (and tests): export LD_LIBRARY_PATH="/sl/usr/lib"
404 lines
11 KiB
C++
404 lines
11 KiB
C++
/**
|
|
* @file llcrashlogger.cpp
|
|
* @brief Crash logger implementation
|
|
*
|
|
* $LicenseInfo:firstyear=2003&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2003-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <sstream>
|
|
#include <map>
|
|
|
|
#include "llcrashlogger.h"
|
|
#include "llstring.h"
|
|
#include "indra_constants.h" // CRASH_BEHAVIOR_ASK, CRASH_SETTING_NAME
|
|
#include "llerror.h"
|
|
#include "lltimer.h"
|
|
#include "lldir.h"
|
|
#include "llsdserialize.h"
|
|
#include "lliopipe.h"
|
|
#include "llpumpio.h"
|
|
#include "llhttpclient.h"
|
|
#include "llsdserialize.h"
|
|
|
|
LLPumpIO* gServicePump;
|
|
BOOL gBreak = false;
|
|
BOOL gSent = false;
|
|
|
|
class LLCrashLoggerResponder : public LLHTTPClient::Responder
|
|
{
|
|
public:
|
|
LLCrashLoggerResponder()
|
|
{
|
|
}
|
|
|
|
virtual void error(U32 status, const std::string& reason)
|
|
{
|
|
gBreak = true;
|
|
}
|
|
|
|
virtual void result(const LLSD& content)
|
|
{
|
|
gBreak = true;
|
|
gSent = true;
|
|
}
|
|
};
|
|
|
|
bool LLCrashLoggerText::mainLoop()
|
|
{
|
|
std::cout << "Entering main loop" << std::endl;
|
|
sendCrashLogs();
|
|
return true;
|
|
}
|
|
|
|
void LLCrashLoggerText::updateApplication(const std::string& message)
|
|
{
|
|
LLCrashLogger::updateApplication(message);
|
|
std::cout << message << std::endl;
|
|
}
|
|
|
|
LLCrashLogger::LLCrashLogger() :
|
|
mCrashBehavior(CRASH_BEHAVIOR_ASK),
|
|
mCrashInPreviousExec(false),
|
|
mSentCrashLogs(false),
|
|
mCrashHost("")
|
|
{
|
|
|
|
}
|
|
|
|
LLCrashLogger::~LLCrashLogger()
|
|
{
|
|
|
|
}
|
|
|
|
// TRIM_SIZE must remain larger than LINE_SEARCH_SIZE.
|
|
const int TRIM_SIZE = 128000;
|
|
const int LINE_SEARCH_DIST = 500;
|
|
const std::string SKIP_TEXT = "\n ...Skipping... \n";
|
|
void trimSLLog(std::string& sllog)
|
|
{
|
|
if(sllog.length() > TRIM_SIZE * 2)
|
|
{
|
|
std::string::iterator head = sllog.begin() + TRIM_SIZE;
|
|
std::string::iterator tail = sllog.begin() + sllog.length() - TRIM_SIZE;
|
|
std::string::iterator new_head = std::find(head, head - LINE_SEARCH_DIST, '\n');
|
|
if(new_head != head - LINE_SEARCH_DIST)
|
|
{
|
|
head = new_head;
|
|
}
|
|
|
|
std::string::iterator new_tail = std::find(tail, tail + LINE_SEARCH_DIST, '\n');
|
|
if(new_tail != tail + LINE_SEARCH_DIST)
|
|
{
|
|
tail = new_tail;
|
|
}
|
|
|
|
sllog.erase(head, tail);
|
|
sllog.insert(head, SKIP_TEXT.begin(), SKIP_TEXT.end());
|
|
}
|
|
}
|
|
|
|
std::string getStartupStateFromLog(std::string& sllog)
|
|
{
|
|
std::string startup_state = "STATE_FIRST";
|
|
std::string startup_token = "Startup state changing from ";
|
|
|
|
int index = sllog.rfind(startup_token);
|
|
if (index < 0 || index + startup_token.length() > sllog.length()) {
|
|
return startup_state;
|
|
}
|
|
|
|
// find new line
|
|
char cur_char = sllog[index + startup_token.length()];
|
|
std::string::size_type newline_loc = index + startup_token.length();
|
|
while(cur_char != '\n' && newline_loc < sllog.length())
|
|
{
|
|
newline_loc++;
|
|
cur_char = sllog[newline_loc];
|
|
}
|
|
|
|
// get substring and find location of " to "
|
|
std::string state_line = sllog.substr(index, newline_loc - index);
|
|
std::string::size_type state_index = state_line.find(" to ");
|
|
startup_state = state_line.substr(state_index + 4, state_line.length() - state_index - 4);
|
|
|
|
return startup_state;
|
|
}
|
|
|
|
void LLCrashLogger::gatherFiles()
|
|
{
|
|
|
|
/*
|
|
//TODO:This function needs to be reimplemented somewhere in here...
|
|
if(!previous_crash && is_crash_log)
|
|
{
|
|
// Make sure the file isn't too old.
|
|
double age = difftime(gLaunchTime, stat_data.st_mtimespec.tv_sec);
|
|
|
|
// llinfos << "age is " << age << llendl;
|
|
|
|
if(age > 60.0)
|
|
{
|
|
// The file was last modified more than 60 seconds before the crash reporter was launched. Assume it's stale.
|
|
llwarns << "File " << mFilename << " is too old!" << llendl;
|
|
return;
|
|
}
|
|
}
|
|
*/
|
|
|
|
updateApplication("Gathering logs...");
|
|
|
|
// Figure out the filename of the debug log
|
|
std::string db_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"debug_info.log");
|
|
std::ifstream debug_log_file(db_file_name.c_str());
|
|
|
|
// Look for it in the debug_info.log file
|
|
if (debug_log_file.is_open())
|
|
{
|
|
LLSDSerialize::fromXML(mDebugLog, debug_log_file);
|
|
|
|
mCrashInPreviousExec = mDebugLog["CrashNotHandled"].asBoolean();
|
|
|
|
mFileMap["SecondLifeLog"] = mDebugLog["SLLog"].asString();
|
|
mFileMap["SettingsXml"] = mDebugLog["SettingsFilename"].asString();
|
|
if(mDebugLog.has("CAFilename"))
|
|
{
|
|
LLCurl::setCAFile(mDebugLog["CAFilename"].asString());
|
|
}
|
|
else
|
|
{
|
|
LLCurl::setCAFile(gDirUtilp->getCAFile());
|
|
}
|
|
|
|
llinfos << "Using log file from debug log " << mFileMap["SecondLifeLog"] << llendl;
|
|
llinfos << "Using settings file from debug log " << mFileMap["SettingsXml"] << llendl;
|
|
}
|
|
else
|
|
{
|
|
// Figure out the filename of the second life log
|
|
LLCurl::setCAFile(gDirUtilp->getCAFile());
|
|
mFileMap["SecondLifeLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log");
|
|
mFileMap["SettingsXml"] = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,"settings.xml");
|
|
}
|
|
|
|
#if !LL_DARWIN
|
|
if(mCrashInPreviousExec)
|
|
#else
|
|
#endif
|
|
{
|
|
// Replace the log file ext with .old, since the
|
|
// instance that launched this process has overwritten
|
|
// SecondLife.log
|
|
std::string log_filename = mFileMap["SecondLifeLog"];
|
|
log_filename.replace(log_filename.size() - 4, 4, ".old");
|
|
mFileMap["SecondLifeLog"] = log_filename;
|
|
}
|
|
|
|
gatherPlatformSpecificFiles();
|
|
|
|
//Use the debug log to reconstruct the URL to send the crash report to
|
|
if(mDebugLog.has("CurrentSimHost"))
|
|
{
|
|
mCrashHost = "https://";
|
|
mCrashHost += mDebugLog["CurrentSimHost"].asString();
|
|
mCrashHost += ":12043/crash/report";
|
|
}
|
|
else if(mDebugLog.has("GridName"))
|
|
{
|
|
// This is a 'little' hacky, but its the best simple solution.
|
|
std::string grid_host = mDebugLog["GridName"].asString();
|
|
LLStringUtil::toLower(grid_host);
|
|
|
|
mCrashHost = "https://login.";
|
|
mCrashHost += grid_host;
|
|
mCrashHost += ".lindenlab.com:12043/crash/report";
|
|
}
|
|
|
|
// Use login servers as the alternate, since they are already load balanced and have a known name
|
|
mAltCrashHost = "https://login.agni.lindenlab.com:12043/crash/report";
|
|
|
|
mCrashInfo["DebugLog"] = mDebugLog;
|
|
mFileMap["StatsLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stats.log");
|
|
mFileMap["StackTrace"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stack_trace.log");
|
|
|
|
updateApplication("Encoding files...");
|
|
|
|
for(std::map<std::string, std::string>::iterator itr = mFileMap.begin(); itr != mFileMap.end(); ++itr)
|
|
{
|
|
std::ifstream f((*itr).second.c_str());
|
|
if(!f.is_open())
|
|
{
|
|
std::cout << "Can't find file " << (*itr).second << std::endl;
|
|
continue;
|
|
}
|
|
std::stringstream s;
|
|
s << f.rdbuf();
|
|
|
|
std::string crash_info = s.str();
|
|
if(itr->first == "SecondLifeLog")
|
|
{
|
|
if(!mCrashInfo["DebugLog"].has("StartupState"))
|
|
{
|
|
mCrashInfo["DebugLog"]["StartupState"] = getStartupStateFromLog(crash_info);
|
|
}
|
|
trimSLLog(crash_info);
|
|
}
|
|
|
|
mCrashInfo[(*itr).first] = rawstr_to_utf8(crash_info);
|
|
}
|
|
}
|
|
|
|
LLSD LLCrashLogger::constructPostData()
|
|
{
|
|
LLSD ret;
|
|
return mCrashInfo;
|
|
}
|
|
|
|
S32 LLCrashLogger::loadCrashBehaviorSetting()
|
|
{
|
|
std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE);
|
|
|
|
mCrashSettings.loadFromFile(filename);
|
|
|
|
S32 value = mCrashSettings.getS32(CRASH_BEHAVIOR_SETTING);
|
|
|
|
if (value < CRASH_BEHAVIOR_ASK || CRASH_BEHAVIOR_NEVER_SEND < value) return CRASH_BEHAVIOR_ASK;
|
|
|
|
return value;
|
|
}
|
|
|
|
bool LLCrashLogger::saveCrashBehaviorSetting(S32 crash_behavior)
|
|
{
|
|
if (crash_behavior != CRASH_BEHAVIOR_ASK && crash_behavior != CRASH_BEHAVIOR_ALWAYS_SEND) return false;
|
|
|
|
mCrashSettings.setS32(CRASH_BEHAVIOR_SETTING, crash_behavior);
|
|
std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE);
|
|
|
|
mCrashSettings.saveToFile(filename, FALSE);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LLCrashLogger::runCrashLogPost(std::string host, LLSD data, std::string msg, int retries, int timeout)
|
|
{
|
|
gBreak = false;
|
|
std::string status_message;
|
|
for(int i = 0; i < retries; ++i)
|
|
{
|
|
status_message = llformat("%s, try %d...", msg.c_str(), i+1);
|
|
LLHTTPClient::post(host, data, new LLCrashLoggerResponder(), timeout);
|
|
while(!gBreak)
|
|
{
|
|
updateApplication(status_message);
|
|
}
|
|
if(gSent)
|
|
{
|
|
return gSent;
|
|
}
|
|
}
|
|
return gSent;
|
|
}
|
|
|
|
bool LLCrashLogger::sendCrashLogs()
|
|
{
|
|
gatherFiles();
|
|
|
|
LLSD post_data;
|
|
post_data = constructPostData();
|
|
|
|
updateApplication("Sending reports...");
|
|
|
|
std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
|
|
"SecondLifeCrashReport");
|
|
std::string report_file = dump_path + ".log";
|
|
|
|
std::ofstream out_file(report_file.c_str());
|
|
LLSDSerialize::toPrettyXML(post_data, out_file);
|
|
out_file.close();
|
|
|
|
bool sent = false;
|
|
|
|
//*TODO: Translate
|
|
if(mCrashHost != "")
|
|
{
|
|
sent = runCrashLogPost(mCrashHost, post_data, std::string("Sending to server"), 3, 5);
|
|
}
|
|
|
|
if(!sent)
|
|
{
|
|
sent = runCrashLogPost(mAltCrashHost, post_data, std::string("Sending to alternate server"), 3, 5);
|
|
}
|
|
|
|
mSentCrashLogs = sent;
|
|
|
|
return true;
|
|
}
|
|
|
|
void LLCrashLogger::updateApplication(const std::string& message)
|
|
{
|
|
gServicePump->pump();
|
|
gServicePump->callback();
|
|
}
|
|
|
|
bool LLCrashLogger::init()
|
|
{
|
|
// We assume that all the logs we're looking for reside on the current drive
|
|
gDirUtilp->initAppDirs("SecondLife");
|
|
|
|
// Default to the product name "Second Life" (this is overridden by the -name argument)
|
|
mProductName = "Second Life";
|
|
|
|
mCrashSettings.declareS32(CRASH_BEHAVIOR_SETTING, CRASH_BEHAVIOR_ASK, "Controls behavior when viewer crashes "
|
|
"(0 = ask before sending crash report, 1 = always send crash report, 2 = never send crash report)");
|
|
|
|
llinfos << "Loading crash behavior setting" << llendl;
|
|
mCrashBehavior = loadCrashBehaviorSetting();
|
|
|
|
// If user doesn't want to send, bail out
|
|
if (mCrashBehavior == CRASH_BEHAVIOR_NEVER_SEND)
|
|
{
|
|
llinfos << "Crash behavior is never_send, quitting" << llendl;
|
|
return false;
|
|
}
|
|
|
|
gServicePump = new LLPumpIO;
|
|
LLHTTPClient::setPump(*gServicePump);
|
|
|
|
//If we've opened the crash logger, assume we can delete the marker file if it exists
|
|
if( gDirUtilp )
|
|
{
|
|
std::string marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.exec_marker");
|
|
LLAPRFile::remove( marker_file );
|
|
}
|
|
|
|
return true;
|
|
}
|