370 lines
10 KiB
C++
370 lines
10 KiB
C++
/**
|
|
* @file llcrashlogger.cpp
|
|
* @brief Crash logger implementation
|
|
*
|
|
* $LicenseInfo:firstyear=2003&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "llcrashlogger.h"
|
|
#include "linden_common.h"
|
|
#include "llstring.h"
|
|
#include "indra_constants.h" // CRASH_BEHAVIOR_...
|
|
#include "llerror.h"
|
|
#include "llerrorcontrol.h"
|
|
#include "lltimer.h"
|
|
#include "lldir.h"
|
|
#include "llfile.h"
|
|
#include "llsdserialize.h"
|
|
#include "lliopipe.h"
|
|
#include "llpumpio.h"
|
|
#include "llhttpclient.h"
|
|
#include "llsdserialize.h"
|
|
#include "llproxy.h"
|
|
#include "aistatemachine.h"
|
|
|
|
class AIHTTPTimeoutPolicy;
|
|
extern AIHTTPTimeoutPolicy crashLoggerResponder_timeout;
|
|
|
|
class LLCrashLoggerResponder : public LLHTTPClient::ResponderWithResult
|
|
{
|
|
public:
|
|
LLCrashLoggerResponder()
|
|
{
|
|
}
|
|
|
|
virtual void error(U32 status, const std::string& reason)
|
|
{
|
|
llwarns << "Crash report sending failed: " << reason << llendl;
|
|
}
|
|
|
|
virtual void result(const LLSD& content)
|
|
{
|
|
std::string msg = "Crash report successfully sent";
|
|
if (content.has("message"))
|
|
{
|
|
msg += ": " + content["message"].asString();
|
|
}
|
|
llinfos << msg << llendl;
|
|
}
|
|
|
|
virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const
|
|
{
|
|
return crashLoggerResponder_timeout;
|
|
}
|
|
|
|
virtual char const* getName(void) const
|
|
{
|
|
return "LLCrashLoggerResponder";
|
|
}
|
|
};
|
|
|
|
LLCrashLogger::LLCrashLogger() :
|
|
mCrashBehavior(CRASH_BEHAVIOR_ALWAYS_SEND),
|
|
mCrashInPreviousExec(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;
|
|
}
|
|
|
|
bool LLCrashLogger::readDebugFromXML(LLSD& dest, const std::string& filename )
|
|
{
|
|
std::string db_file_name = gDirUtilp->getExpandedFilename(LL_PATH_DUMP,filename);
|
|
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(dest, debug_log_file);
|
|
debug_log_file.close();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void LLCrashLogger::mergeLogs( LLSD src_sd )
|
|
{
|
|
LLSD::map_iterator iter = src_sd.beginMap();
|
|
LLSD::map_iterator end = src_sd.endMap();
|
|
for( ; iter != end; ++iter)
|
|
{
|
|
mDebugLog[iter->first] = iter->second;
|
|
}
|
|
}
|
|
|
|
bool LLCrashLogger::readMinidump(std::string minidump_path)
|
|
{
|
|
size_t length=0;
|
|
|
|
std::ifstream minidump_stream(minidump_path.c_str(), std::ios_base::in | std::ios_base::binary);
|
|
if(minidump_stream.is_open())
|
|
{
|
|
minidump_stream.seekg(0, std::ios::end);
|
|
length = (size_t)minidump_stream.tellg();
|
|
minidump_stream.seekg(0, std::ios::beg);
|
|
|
|
LLSD::Binary data;
|
|
data.resize(length);
|
|
|
|
minidump_stream.read(reinterpret_cast<char *>(&(data[0])),length);
|
|
minidump_stream.close();
|
|
|
|
mCrashInfo["Minidump"] = data;
|
|
}
|
|
return (length>0?true:false);
|
|
}
|
|
|
|
void LLCrashLogger::gatherFiles()
|
|
{
|
|
llinfos << "Gathering logs..." << llendl;
|
|
|
|
LLSD static_sd;
|
|
LLSD dynamic_sd;
|
|
|
|
bool has_logs = readDebugFromXML( static_sd, "static_debug_info.log" );
|
|
has_logs |= readDebugFromXML( dynamic_sd, "dynamic_debug_info.log" );
|
|
|
|
if ( has_logs )
|
|
{
|
|
mDebugLog = static_sd;
|
|
mergeLogs(dynamic_sd);
|
|
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(mCrashInPreviousExec)
|
|
{
|
|
// Restarting after freeze.
|
|
// 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();
|
|
|
|
mCrashInfo["DebugLog"] = mDebugLog;
|
|
mFileMap["StatsLog"] = gDirUtilp->getExpandedFilename(LL_PATH_DUMP,"stats.log");
|
|
// Singu Note: we have just started again, log has been renamed
|
|
mFileMap["SecondLifeLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "Singularity.old");
|
|
|
|
llinfos << "Encoding files..." << llendl;
|
|
|
|
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())
|
|
{
|
|
llinfos << "Can't find file " << (*itr).second << llendl;
|
|
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] = LLStringFn::strip_invalid_xml(rawstr_to_utf8(crash_info));
|
|
}
|
|
|
|
std::string minidump_path;
|
|
|
|
// Add minidump as binary.
|
|
bool has_minidump = mDebugLog.has("MinidumpPath");
|
|
|
|
if (has_minidump)
|
|
minidump_path = mDebugLog["MinidumpPath"].asString();
|
|
|
|
|
|
if (has_minidump)
|
|
{
|
|
has_minidump = readMinidump(minidump_path);
|
|
}
|
|
|
|
if (!has_minidump) //Viewer was probably so hosed it couldn't write remaining data. Try brute force.
|
|
{
|
|
//Look for a filename at least 30 characters long in the dump dir which contains the characters MDMP as the first 4 characters in the file.
|
|
typedef std::vector<std::string> vec;
|
|
|
|
std::string pathname = gDirUtilp->getExpandedFilename(LL_PATH_DUMP,"");
|
|
vec file_vec = gDirUtilp->getFilesInDir(pathname);
|
|
for(vec::const_iterator iter=file_vec.begin(); iter!=file_vec.end(); ++iter)
|
|
{
|
|
if ( ( iter->length() > 30 ) && (iter->rfind(".log") != (iter->length()-4) ) )
|
|
{
|
|
std::string fullname = pathname + *iter;
|
|
std::ifstream fdat( fullname.c_str(), std::ifstream::binary);
|
|
if (fdat)
|
|
{
|
|
char buf[5];
|
|
fdat.read(buf,4);
|
|
fdat.close();
|
|
if (!strncmp(buf,"MDMP",4))
|
|
{
|
|
minidump_path = *iter;
|
|
has_minidump = readMinidump(fullname);
|
|
mDebugLog["MinidumpPath"] = fullname;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LLSD LLCrashLogger::constructPostData()
|
|
{
|
|
return mCrashInfo;
|
|
}
|
|
|
|
|
|
bool LLCrashLogger::sendCrashLog(std::string dump_dir)
|
|
{
|
|
gDirUtilp->setDumpDir( dump_dir );
|
|
|
|
std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
|
|
"SingularityCrashReport");
|
|
std::string report_file = dump_path + ".log";
|
|
|
|
gatherFiles();
|
|
|
|
LLSD post_data;
|
|
post_data = constructPostData();
|
|
|
|
llinfos << "Sending reports..." << llendl;
|
|
|
|
std::ofstream out_file(report_file.c_str());
|
|
LLSDSerialize::toPrettyXML(post_data, out_file);
|
|
out_file.close();
|
|
|
|
LLHTTPClient::post(mCrashHost, post_data, new LLCrashLoggerResponder());
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void LLCrashLogger::checkCrashDump()
|
|
{
|
|
mCrashHost = gSavedSettings.getString("CrashHostUrl");
|
|
|
|
std::string dumpDir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "") + "singularity-debug";
|
|
if (gDirUtilp->fileExists(dumpDir))
|
|
{
|
|
#if LL_SEND_CRASH_REPORTS
|
|
if (!mCrashHost.empty() && gSavedSettings.getS32("CrashSubmitBehavior") != 2)
|
|
{
|
|
sendCrashLog(dumpDir);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
llinfos << "No crash dump found frome previous run, not sending report" << LL_ENDL;
|
|
}
|
|
}
|