Breakpad3: WIP, minidump files created

TODO: re-work sending crash logs
get rid of standalone loggers
This commit is contained in:
Latif Khalifa
2013-10-01 13:43:45 +02:00
parent 6c45bf0353
commit c87f7b0576
29 changed files with 1745 additions and 1460 deletions

View File

@@ -2,62 +2,58 @@
* @file llcrashlogger.cpp
* @brief Crash logger implementation
*
* $LicenseInfo:firstyear=2003&license=viewergpl$
*
* Copyright (c) 2003-2009, Linden Research, Inc.
*
* $LicenseInfo:firstyear=2003&license=viewerlgpl$
* 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
* Copyright (C) 2010, Linden Research, Inc.
*
* 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
* 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.
*
* 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.
* 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.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* 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 "linden_common.h"
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include <map>
#include "llcrashlogger.h"
#include "llcrashlock.h"
#include "linden_common.h"
#include "llstring.h"
#include "indra_constants.h" // CRASH_BEHAVIOR_ASK, CRASH_SETTING_NAME
#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 "llcurl.h"
#include "llproxy.h"
#include "aistatemachine.h"
LLPumpIO* gServicePump;
BOOL gBreak = false;
BOOL gSent = false;
extern void startEngineThread(void);
class AIHTTPTimeoutPolicy;
extern AIHTTPTimeoutPolicy crashLoggerResponder_timeout;
extern void startEngineThread(void);
class LLCrashLoggerResponder : public LLHTTPClient::ResponderWithResult
{
@@ -66,42 +62,35 @@ public:
{
}
/*virtual*/ void error(U32 status, const std::string& reason)
virtual void error(U32 status, const std::string& reason)
{
gBreak = true;
gBreak = true;
}
/*virtual*/ void result(const LLSD& content)
{
virtual void result(const LLSD& content)
{
gBreak = true;
gSent = true;
}
/*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return crashLoggerResponder_timeout; }
/*virtual*/ char const* getName(void) const { return "LLCrashLoggerResponder"; }
virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const
{
return crashLoggerResponder_timeout;
}
virtual char const* getName(void) const
{
return "LLCrashLoggerResponder";
}
};
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),
mCrashBehavior(CRASH_BEHAVIOR_ALWAYS_SEND),
mCrashInPreviousExec(false),
mCrashSettings("CrashSettings"),
mSentCrashLogs(false),
mCrashHost(""),
mCrashSettings("CrashSettings")
mCrashHost("")
{
}
LLCrashLogger::~LLCrashLogger()
@@ -163,38 +152,67 @@ std::string getStartupStateFromLog(std::string& sllog)
return startup_state;
}
void LLCrashLogger::gatherFiles()
bool LLCrashLogger::readDebugFromXML(LLSD& dest, const std::string& filename )
{
/*
//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());
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(mDebugLog, debug_log_file);
{
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()
{
updateApplication("Gathering logs...");
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();
@@ -219,11 +237,9 @@ void LLCrashLogger::gatherFiles()
mFileMap["SettingsXml"] = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,"settings.xml");
}
#if !LL_DARWIN
if(mCrashInPreviousExec)
#else
#endif
{
// Restarting after freeze.
// Replace the log file ext with .old, since the
// instance that launched this process has overwritten
// SecondLife.log
@@ -235,7 +251,12 @@ void LLCrashLogger::gatherFiles()
gatherPlatformSpecificFiles();
//Use the debug log to reconstruct the URL to send the crash report to
if(mDebugLog.has("CurrentSimHost"))
if(mDebugLog.has("CrashHostUrl"))
{
// Crash log receiver has been manually configured.
mCrashHost = mDebugLog["CrashHostUrl"].asString();
}
else if(mDebugLog.has("CurrentSimHost"))
{
mCrashHost = "https://";
mCrashHost += mDebugLog["CurrentSimHost"].asString();
@@ -256,8 +277,7 @@ void LLCrashLogger::gatherFiles()
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");
mFileMap["StatsLog"] = gDirUtilp->getExpandedFilename(LL_PATH_DUMP,"stats.log");
updateApplication("Encoding files...");
@@ -282,52 +302,115 @@ void LLCrashLogger::gatherFiles()
trimSLLog(crash_info);
}
mCrashInfo[(*itr).first] = rawstr_to_utf8(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()
{
LLSD ret;
return mCrashInfo;
}
// Singu Note, defiend in indra_constants.h # const char* const CRASH_SETTINGS_FILE = "settings_crash_behavior.xml";
S32 LLCrashLogger::loadCrashBehaviorSetting()
{
// First check user_settings (in the user's home dir)
std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE);
if (! mCrashSettings.loadFromFile(filename))
{
// Next check app_settings (in the SL program dir)
std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, CRASH_SETTINGS_FILE);
mCrashSettings.loadFromFile(filename);
}
mCrashSettings.loadFromFile(filename);
S32 value = mCrashSettings.getS32(CRASH_BEHAVIOR_SETTING);
if (value < CRASH_BEHAVIOR_ASK || CRASH_BEHAVIOR_NEVER_SEND < value) return CRASH_BEHAVIOR_ASK;
// If we didn't load any files above, this will return the default
S32 value = mCrashSettings.getS32("CrashSubmitBehavior");
return value;
// Whatever value we got, make sure it's valid
switch (value)
{
case CRASH_BEHAVIOR_NEVER_SEND:
return CRASH_BEHAVIOR_NEVER_SEND;
case CRASH_BEHAVIOR_ALWAYS_SEND:
return CRASH_BEHAVIOR_ALWAYS_SEND;
}
return CRASH_BEHAVIOR_ASK;
}
bool LLCrashLogger::saveCrashBehaviorSetting(S32 crash_behavior)
{
if (crash_behavior != CRASH_BEHAVIOR_ASK && crash_behavior != CRASH_BEHAVIOR_ALWAYS_SEND) return false;
switch (crash_behavior)
{
case CRASH_BEHAVIOR_ASK:
case CRASH_BEHAVIOR_NEVER_SEND:
case CRASH_BEHAVIOR_ALWAYS_SEND:
break;
default:
return false;
}
mCrashSettings.setS32(CRASH_BEHAVIOR_SETTING, crash_behavior);
mCrashSettings.setS32("CrashSubmitBehavior", 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)
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);
updateApplication(llformat("%s, try %d...", msg.c_str(), i+1));
LLHTTPClient::post(host, data, new LLCrashLoggerResponder());
while(!gBreak)
{
updateApplication(status_message);
updateApplication(); // No new message, just pump the IO
}
if(gSent)
{
@@ -337,89 +420,194 @@ bool LLCrashLogger::runCrashLogPost(std::string host, LLSD data, std::string msg
return gSent;
}
bool LLCrashLogger::sendCrashLogs()
bool LLCrashLogger::sendCrashLog(std::string dump_dir)
{
gDirUtilp->setDumpDir( dump_dir );
std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
"SecondLifeCrashReport");
std::string report_file = dump_path + ".log";
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
//*TODO: Translate
if(mCrashHost != "")
{
sent = runCrashLogPost(mCrashHost, post_data, std::string("Sending to server"), 3);
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);
sent = runCrashLogPost(mAltCrashHost, post_data, std::string("Sending to alternate server"), 3, 5);
}
mSentCrashLogs = sent;
return sent;
}
return true;
bool LLCrashLogger::sendCrashLogs()
{
//pertinent code from below moved into a subroutine.
LLSD locks = mKeyMaster.getProcessList();
LLSD newlocks = LLSD::emptyArray();
LLSD opts = getOptionData(PRIORITY_COMMAND_LINE);
LLSD rec;
if ( opts.has("pid") && opts.has("dumpdir") && opts.has("procname") )
{
rec["pid"]=opts["pid"];
rec["dumpdir"]=opts["dumpdir"];
rec["procname"]=opts["procname"];
#if LL_WINDOWS
locks.append(rec);
#endif
}
if (locks.isArray())
{
for (LLSD::array_iterator lock=locks.beginArray();
lock !=locks.endArray();
++lock)
{
if ( (*lock).has("pid") && (*lock).has("dumpdir") && (*lock).has("procname") )
{
if ( mKeyMaster.isProcessAlive( (*lock)["pid"].asInteger(), (*lock)["procname"].asString() ) )
{
newlocks.append(*lock);
}
else
{
//TODO: This is a hack but I didn't want to include boost in another file or retest everything related to lldir
if (LLCrashLock::fileExists((*lock)["dumpdir"].asString()))
{
//the viewer cleans up the log directory on clean shutdown
//but is ignorant of the locking table.
if (!sendCrashLog((*lock)["dumpdir"].asString()))
{
newlocks.append(*lock); //Failed to send log so don't delete it.
}
else
{
mCrashInfo["DebugLog"].erase("MinidumpPath");
mKeyMaster.cleanupProcess((*lock)["dumpdir"].asString());
}
}
}
}
else
{
llwarns << "Discarding corrupted entry from lock table." << llendl;
}
}
}
#if !LL_WINDOWS
if (rec)
{
newlocks.append(rec);
}
#endif
mKeyMaster.putProcessList(newlocks);
return true;
}
void LLCrashLogger::updateApplication(const std::string& message)
{
/* Sing TODO
gServicePump->pump();
gServicePump->callback();
gMainThreadEngine.mainloop();
*/
if (!message.empty()) llinfos << message << llendl;
}
bool LLCrashLogger::init()
{
// Initialize curl
AICurlInterface::initCurl();
// Initialize state machine engines.
AIEngine::setMaxCount(100); // StateMachineMaxTime
// Start state machine thread.
startEngineThread();
LLCurl::initCurl();
AIEngine::setMaxCount(100);
// We assume that all the logs we're looking for reside on the current drive
gDirUtilp->initAppDirs("SecondLife");
LLError::initForApplication(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, ""));
// 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)");
// Handle locking
bool locked = mKeyMaster.requestMaster(); //Request maser locking file. wait time is defaulted to 300S
while (!locked && mKeyMaster.isWaiting())
{
#if LL_WINDOWS
Sleep(1000);
#else
sleep(1);
#endif
locked = mKeyMaster.checkMaster();
}
if (!locked)
{
llwarns << "Unable to get master lock. Another crash reporter may be hung." << llendl;
return false;
}
// Rename current log file to ".old"
std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "crashreport.log.old");
std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "crashreport.log");
#if LL_WINDOWS
LLAPRFile::remove(old_log_file);
#endif
LLFile::rename(log_file.c_str(), old_log_file.c_str());
// Set the log file to crashreport.log
LLError::logToFile(log_file);
mCrashSettings.declareS32("CrashSubmitBehavior", CRASH_BEHAVIOR_ALWAYS_SEND,
"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;
}
// Start curl thread.
AICurlInterface::startCurlThread(&mCrashSettings);
gServicePump = new LLPumpIO;
//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 );
}
startEngineThread();
/* Singu Note: not needed for AICurl
gServicePump = new LLPumpIO(gAPRPoolp);
gServicePump->prime(gAPRPoolp);
LLHTTPClient::setPump(*gServicePump);
*/
return true;
}
// For cleanup code common to all platforms.
void LLCrashLogger::commonCleanup()
{
LLProxy::cleanupClass();
}