commit
97cee1b6b0
@ -1,55 +0,0 @@ |
||||
/*
|
||||
* UBImportVirtualPrinter.h |
||||
* |
||||
* Created on: 18 mars 2009 |
||||
* Author: Julien |
||||
*/ |
||||
|
||||
#ifndef UBIMPORTVIRTUALPRINTER_H_ |
||||
#define UBIMPORTVIRTUALPRINTER_H_ |
||||
|
||||
#include <QtGui> |
||||
|
||||
#include "UBImportAdaptor.h" |
||||
|
||||
#include "document/UBDocumentProxy.h" |
||||
|
||||
/**
|
||||
* This class import ini file that are generated by Uniboard Printer. |
||||
*/ |
||||
class UBImportVirtualPrinter: public UBImportAdaptor |
||||
{ |
||||
Q_OBJECT; |
||||
|
||||
public: |
||||
UBImportVirtualPrinter(QObject *parent = 0); |
||||
virtual ~UBImportVirtualPrinter(); |
||||
|
||||
// this is the name of the default printer that must be reset after importing from virtual printer.
|
||||
// this allow other import adaptor to backup default printer, then set Uniboard printer as default printer and launch the
|
||||
// print. Then when uniboard will import the file from virtual printer, it will reset the default printer to this one.
|
||||
static QString sOriginalDefaultPrintername; |
||||
|
||||
|
||||
/**
|
||||
* The document expecting to be merged with incoming data |
||||
*/ |
||||
static QPointer<UBDocumentProxy> pendingDocument; |
||||
|
||||
virtual QStringList supportedExtentions(); |
||||
virtual QString importFileFilter(); |
||||
virtual UBDocumentProxy* importFile(const QFile& pFile, const QString& pGroup); |
||||
|
||||
virtual bool addFileToDocument(UBDocumentProxy* pDocument, const QFile& pFile); |
||||
|
||||
|
||||
private: |
||||
|
||||
QString pdfFileName(const QFile& pFile); |
||||
QStringList emfFileNames(const QFile& pFile); |
||||
|
||||
void cleanUp(const QFile& pFile, const QString& pPdfFileName, QStringList pEmfFilenames); |
||||
}; |
||||
|
||||
|
||||
#endif /* UBIMPORTVIRTUALPRINTER_H_ */ |
@ -1,13 +0,0 @@ |
||||
|
||||
#ifndef UBPOWERPOINTAPPLICATION_H_ |
||||
#define UBPOWERPOINTAPPLICATION_H_ |
||||
|
||||
#if defined(Q_OS_WIN) |
||||
#include "UBPowerPointApplication_win.h" |
||||
#elif defined(Q_OS_MAC) |
||||
#include "UBPowerPointApplication_mac.h" |
||||
#else |
||||
//TODO Linux
|
||||
#endif |
||||
|
||||
#endif /* UBPOWERPOINTAPPLICATION_H_ */ |
@ -1,28 +0,0 @@ |
||||
|
||||
#ifndef UBPOWERPOINTAPPLICATIONMAC_H_ |
||||
#define UBPOWERPOINTAPPLICATIONMAC_H_ |
||||
|
||||
#include <QtCore> |
||||
#include "UBImportAdaptor.h" |
||||
|
||||
class UBPowerPointApplication : public UBImportAdaptor |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
UBPowerPointApplication(QObject* parent = 0); |
||||
virtual ~UBPowerPointApplication(); |
||||
|
||||
virtual QStringList supportedExtentions(); |
||||
virtual QString importFileFilter(); |
||||
virtual UBDocumentProxy* importFile(const QFile& pFile, const QString& pGroup); |
||||
|
||||
virtual bool addFileToDocument(UBDocumentProxy* pDocument, const QFile& pFile); |
||||
|
||||
private: |
||||
bool generatePdfFromPptFile(const QString& pptFile, const QString& pOutputFile); |
||||
|
||||
|
||||
}; |
||||
|
||||
#endif /* UBPOWERPOINTAPPLICATIONMAC_H_ */ |
@ -1,227 +0,0 @@ |
||||
|
||||
#include "UBPowerPointApplication_mac.h" |
||||
|
||||
#include "core/UBApplication.h" |
||||
#include "core/UBDocumentManager.h" |
||||
#include "frameworks/UBPlatformUtils.h" |
||||
#include "frameworks/UBFileSystemUtils.h" |
||||
|
||||
#import <Foundation/NSAutoreleasePool.h> |
||||
#import <Carbon/Carbon.h> |
||||
|
||||
|
||||
|
||||
UBPowerPointApplication::UBPowerPointApplication(QObject* parent) |
||||
: UBImportAdaptor(parent) |
||||
{ |
||||
// NOOP |
||||
} |
||||
|
||||
UBPowerPointApplication::~UBPowerPointApplication() |
||||
{ |
||||
// NOOP |
||||
} |
||||
|
||||
class AppleScriptThread : public QThread |
||||
{ |
||||
public: |
||||
AppleScriptThread(NSAppleScript *appleScript, QObject *parent = 0) |
||||
: QThread(parent) |
||||
, mAppleScript(appleScript) |
||||
, mError(nil) |
||||
{ |
||||
// NOOP |
||||
} |
||||
|
||||
~AppleScriptThread() |
||||
{ |
||||
if (mError) |
||||
{ |
||||
[mError release]; |
||||
} |
||||
} |
||||
|
||||
void run() |
||||
{ |
||||
NSDictionary *error = nil; |
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
||||
[mAppleScript executeAndReturnError:&error]; |
||||
mError = error ? [[NSDictionary alloc] initWithDictionary:error] : nil; |
||||
[pool release]; |
||||
} |
||||
|
||||
NSDictionary* error() |
||||
{ |
||||
return mError; |
||||
} |
||||
|
||||
private: |
||||
NSAppleScript *mAppleScript; |
||||
NSDictionary *mError; |
||||
}; |
||||
|
||||
|
||||
NSString* escapePath(const QString &filePath) |
||||
{ |
||||
QString escapedFilePath(filePath); |
||||
escapedFilePath.replace("\"", "\\\""); |
||||
QByteArray pathRepresentation = QFile::encodeName(escapedFilePath); |
||||
return [[NSFileManager defaultManager] stringWithFileSystemRepresentation:pathRepresentation.constData() length:pathRepresentation.length()]; |
||||
} |
||||
|
||||
|
||||
bool UBPowerPointApplication::generatePdfFromPptFile(const QString& pptFile, const QString& pOutputFile) |
||||
{ |
||||
bool result = true; |
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
||||
QResource scriptResource(":/PowerPointImport.applescript"); |
||||
NSString *script = [[NSString alloc] initWithBytes:scriptResource.data() length:scriptResource.size() encoding:NSUTF8StringEncoding]; |
||||
QFileInfo pptFileInfo(pptFile); |
||||
NSString *scriptSource = [NSString stringWithFormat:script, escapePath(pptFileInfo.fileName()), escapePath(pptFile), escapePath(pOutputFile)]; |
||||
[script release]; |
||||
NSAppleScript *pdfExportScript = [[NSAppleScript alloc] initWithSource:scriptSource]; |
||||
AppleScriptThread appleScriptThread(pdfExportScript); |
||||
appleScriptThread.start(); |
||||
while (appleScriptThread.isRunning()) |
||||
{ |
||||
qApp->processEvents(); |
||||
} |
||||
if (appleScriptThread.error()) |
||||
{ |
||||
const char* errorDescription = [[appleScriptThread.error() description] UTF8String]; |
||||
qWarning() << "PowerPoint import error:" << QString::fromUtf8(errorDescription, strlen(errorDescription)); |
||||
result = false; |
||||
} |
||||
[pdfExportScript release]; |
||||
[pool drain]; |
||||
return result; |
||||
} |
||||
|
||||
|
||||
QStringList UBPowerPointApplication::supportedExtentions() |
||||
{ |
||||
QStringList result; |
||||
CFURLRef powerPointURL = NULL; |
||||
|
||||
if (LSFindApplicationForInfo(kLSUnknownCreator, CFSTR("com.microsoft.Powerpoint"), NULL, NULL, &powerPointURL) == noErr) |
||||
{ |
||||
CFBundleRef powerPointBundle = CFBundleCreate(kCFAllocatorDefault, powerPointURL); |
||||
if (powerPointBundle) |
||||
{ |
||||
CFStringRef buildNumber = (CFStringRef)CFBundleGetValueForInfoDictionaryKey(powerPointBundle, CFSTR("MicrosoftBuildNumber")); |
||||
if (buildNumber && (CFGetTypeID(buildNumber) == CFStringGetTypeID())) |
||||
{ |
||||
int buildValue = CFStringGetIntValue(buildNumber); |
||||
if (buildValue >= 80409) |
||||
{ |
||||
// PowerPoint 2008 |
||||
result << "ppt" << "pptx" << "pptm" << "pps" << "ppsx" << "ppsm"; |
||||
} |
||||
/* |
||||
else if (buildValue >= `Office 2004 MicrosoftBuildNumber`) |
||||
{ |
||||
result << "ppt" << "pptm" << "pps" << "ppsm"; |
||||
} |
||||
*/ |
||||
else |
||||
{ |
||||
qWarning("Unsupported Microsoft PowerPoint version: %d", buildValue); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
qWarning("Invalid PowerPoint MicrosoftBuildNumber"); |
||||
} |
||||
CFRelease(powerPointBundle); |
||||
} |
||||
else |
||||
{ |
||||
qWarning("Microsoft PowerPoint bundle was not found"); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
qWarning("Microsoft PowerPoint was not found"); |
||||
} |
||||
|
||||
if (powerPointURL) |
||||
{ |
||||
CFRelease(powerPointURL); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
|
||||
QString UBPowerPointApplication::importFileFilter() |
||||
{ |
||||
QStringList extentions = supportedExtentions(); |
||||
if (extentions.count() > 0) |
||||
{ |
||||
QString filter = "PowerPoint ("; |
||||
foreach (const QString ext, extentions) |
||||
{ |
||||
filter.append("*."); |
||||
filter.append(ext); |
||||
filter.append(" "); |
||||
} |
||||
filter = filter.trimmed(); |
||||
filter.append(")"); |
||||
return filter; |
||||
} |
||||
else |
||||
{ |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
|
||||
UBDocumentProxy* UBPowerPointApplication::importFile(const QFile& pFile, const QString& pGroup) |
||||
{ |
||||
UBApplication::showMessage(tr("Converting PowerPoint file ..."), true); |
||||
UBDocumentProxy* result = 0; |
||||
|
||||
QString tempDir = UBFileSystemUtils::createTempDir(); |
||||
QFileInfo sourceFileInfo(pFile); |
||||
QString tempFile = tempDir + "/" + sourceFileInfo.baseName() + ".pdf"; |
||||
|
||||
if (generatePdfFromPptFile(pFile.fileName(), tempFile)) |
||||
{ |
||||
UBApplication::showMessage(tr("PowerPoint import successful.")); |
||||
QFile tmp(tempFile); |
||||
result = UBDocumentManager::documentManager()->importFile(tmp, pGroup); |
||||
} |
||||
else |
||||
{ |
||||
UBApplication::showMessage(tr("PowerPoint import failed.")); |
||||
} |
||||
|
||||
UBFileSystemUtils::deleteDir(tempDir); |
||||
return result; |
||||
} |
||||
|
||||
|
||||
bool UBPowerPointApplication::addFileToDocument(UBDocumentProxy* pDocument, const QFile& pFile) |
||||
{ |
||||
UBApplication::showMessage(tr("Converting PowerPoint file ..."), true); |
||||
bool result = false; |
||||
|
||||
QString tempDir = UBFileSystemUtils::createTempDir(); |
||||
QFileInfo sourceFileInfo(pFile); |
||||
QString tempFile = tempDir + "/" + sourceFileInfo.baseName() + ".pdf"; |
||||
|
||||
if (generatePdfFromPptFile(pFile.fileName(), tempFile)) |
||||
{ |
||||
UBApplication::showMessage(tr("PowerPoint import successful.")); |
||||
QFile tmp(tempFile); |
||||
result = UBDocumentManager::documentManager()->addFileToDocument(pDocument, tmp); |
||||
} |
||||
else |
||||
{ |
||||
UBApplication::showMessage(tr("PowerPoint import failed.")); |
||||
} |
||||
|
||||
UBFileSystemUtils::deleteDir(tempDir); |
||||
return result; |
||||
} |
||||
|
@ -1,269 +0,0 @@ |
||||
/*
|
||||
* UBPowerPointApplication.cpp |
||||
* |
||||
* Created on: Dec 4, 2008 |
||||
* Author: Luc |
||||
*/ |
||||
|
||||
#include "UBPowerPointApplication_win.h" |
||||
|
||||
#include "frameworks/UBFileSystemUtils.h" |
||||
|
||||
#include "core/UBApplication.h" |
||||
#include "core/UBDocumentManager.h" |
||||
#include "core/UBPersistenceManager.h" |
||||
|
||||
#include "UBImportVirtualPrinter.h" |
||||
#include "msppt.h" |
||||
#include "mso.h" |
||||
#include "windows.h" |
||||
|
||||
UBPowerPointApplication::UBPowerPointApplication(QObject* parent) |
||||
: UBImportAdaptor(parent) |
||||
, mInit(false) |
||||
, mHasException(false) |
||||
, mSupportPpt(false) |
||||
, mSupportPptX(false) |
||||
{ |
||||
// NOOP
|
||||
} |
||||
|
||||
|
||||
UBPowerPointApplication::~UBPowerPointApplication() |
||||
{ |
||||
// NOOP
|
||||
} |
||||
|
||||
|
||||
void UBPowerPointApplication::init() |
||||
{ |
||||
PowerPoint::Application ppt; |
||||
qDebug() << "PPT version :" << ppt.Version().toFloat(); |
||||
mSupportPpt = !ppt.isNull(); |
||||
mSupportPptX = ppt.Version().toFloat() >= 12; |
||||
mInit = true; |
||||
} |
||||
|
||||
|
||||
bool UBPowerPointApplication::isPowerPointInstalled() |
||||
{ |
||||
if (!mInit) |
||||
{ |
||||
init(); |
||||
} |
||||
return mSupportPpt; |
||||
} |
||||
|
||||
|
||||
bool UBPowerPointApplication::supportPptx() |
||||
{ |
||||
if (!mInit) |
||||
{ |
||||
init(); |
||||
} |
||||
|
||||
return mSupportPptX; |
||||
} |
||||
|
||||
|
||||
bool UBPowerPointApplication::generatePdfFromPptFile(const QString& pptFile, const QString& outputDir) |
||||
{ |
||||
Q_UNUSED(pptFile); |
||||
Q_UNUSED(outputDir); |
||||
return false; |
||||
} |
||||
|
||||
|
||||
bool UBPowerPointApplication::generateImagesFromPptFile(const QString& pptFile, const QString& outputDir, const QString& imageFormat, const QSize& imageSize) |
||||
{ |
||||
mHasException = false; |
||||
|
||||
PowerPoint::Application ppt; |
||||
|
||||
connect(&ppt, SIGNAL(exception ( int , const QString & , const QString & , const QString & )) |
||||
, this, SLOT(exception ( int , const QString & , const QString & , const QString & ))); |
||||
|
||||
if (ppt.isNull()) |
||||
return false; |
||||
|
||||
ppt.Activate(); |
||||
ppt.SetWindowState(PowerPoint::ppWindowMinimized); |
||||
|
||||
UBApplication::processEvents(); |
||||
|
||||
int previouslyOpenPresentations = ppt.Presentations()->Count(); |
||||
|
||||
PowerPoint::Presentation *presentation = |
||||
ppt.Presentations()->Open(QDir::toNativeSeparators(pptFile)); |
||||
|
||||
int currentOpenPresentations = ppt.Presentations()->Count(); |
||||
|
||||
if(!presentation) |
||||
return false; |
||||
|
||||
if (ppt.Version().toFloat() >= 12) // PPT 2007 is broken with high res exports : https://trac.assembla.com/uniboard/ticket/297#comment:16
|
||||
{ |
||||
presentation->Export(QDir::toNativeSeparators(outputDir), imageFormat); |
||||
} |
||||
else |
||||
{ |
||||
presentation->Export(QDir::toNativeSeparators(outputDir), imageFormat, imageSize.width(), imageSize.height()); |
||||
} |
||||
|
||||
if(mHasException) |
||||
return false; |
||||
|
||||
if (currentOpenPresentations != previouslyOpenPresentations) |
||||
presentation->Close(); |
||||
|
||||
if (ppt.Presentations()->Count() == 0) |
||||
ppt.Quit(); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
QStringList UBPowerPointApplication::supportedExtentions() |
||||
{ |
||||
QStringList result; |
||||
if (UBPowerPointApplication::isPowerPointInstalled()) |
||||
{ |
||||
result << QStringList("ppt") << "pps"; |
||||
if (UBPowerPointApplication::supportPptx()) |
||||
{ |
||||
result << "pptx" << "pptm" << "ppsx" << "ppsm"; |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
QString UBPowerPointApplication::importFileFilter() |
||||
{ |
||||
if (UBPowerPointApplication::isPowerPointInstalled()) |
||||
{ |
||||
if (UBPowerPointApplication::supportPptx()) |
||||
{ |
||||
return "PowerPoint (*.ppt *.pptx *.pptm *.pps *.ppsx *.ppsm)"; |
||||
} |
||||
else |
||||
{ |
||||
return "PowerPoint (*.ppt *.pps)"; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
return ""; |
||||
} |
||||
} |
||||
|
||||
UBDocumentProxy* UBPowerPointApplication::importFile(const QFile& pFile, const QString& pGroup) |
||||
{ |
||||
|
||||
UBDocumentProxy* document = 0; |
||||
// print by changing default printer and use shell execute to print
|
||||
LPTSTR wDefaultPrinterName = new TCHAR[255]; |
||||
LPTSTR virtualPrinter = new TCHAR[255]; |
||||
|
||||
int i = QString("Uniboard").toWCharArray(virtualPrinter); // TODO UB 4.x make me configurable ....
|
||||
virtualPrinter[i] = 0; |
||||
DWORD bufferSize = 1000; |
||||
GetDefaultPrinter(wDefaultPrinterName, &bufferSize); |
||||
UBImportVirtualPrinter::sOriginalDefaultPrintername = QString::fromWCharArray(wDefaultPrinterName); |
||||
|
||||
if (!SetDefaultPrinter(virtualPrinter)) |
||||
{ |
||||
QMessageBox msgBox(0); |
||||
msgBox.setText(tr("Uniboard printer is not installed. Import will be done in jpg format.")); |
||||
msgBox.setStandardButtons(QMessageBox::Ok); |
||||
msgBox.setDefaultButton(QMessageBox::Ok); |
||||
msgBox.exec(); |
||||
QString tempLocation = UBFileSystemUtils::createTempDir(); |
||||
QDir tempDir(tempLocation); |
||||
|
||||
bool pptSuccess = generateImagesFromPptFile(pFile.fileName(), tempLocation, "jpg", QSize(3000, 2250)); |
||||
|
||||
if (pptSuccess) |
||||
{ |
||||
document = UBDocumentManager::documentManager()->importDir(tempDir, pGroup); |
||||
|
||||
if (document) |
||||
{ |
||||
UBFileSystemUtils::deleteDir(tempLocation); |
||||
} |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
document = UBPersistenceManager::persistenceManager()->createDocument(pGroup); |
||||
|
||||
QFileInfo sourceFileInfo(pFile); |
||||
|
||||
document->setMetaData(UBSettings::documentName, sourceFileInfo.baseName()); |
||||
UBPersistenceManager::persistenceManager()->persistDocumentMetadata(document); |
||||
|
||||
UBImportVirtualPrinter::pendingDocument = document; |
||||
|
||||
int result = (int)ShellExecute(NULL, QString("print").utf16() , pFile.fileName().utf16(), NULL, NULL, SW_HIDE); |
||||
|
||||
qDebug() << "PPT shellexec print result" << result; |
||||
} |
||||
|
||||
delete[] wDefaultPrinterName; |
||||
delete[] virtualPrinter; |
||||
|
||||
return document; |
||||
} |
||||
|
||||
bool UBPowerPointApplication::addFileToDocument(UBDocumentProxy* pDocument, const QFile& pFile) |
||||
{ |
||||
bool result = false; |
||||
// print by changing default printer and use shell execute to print
|
||||
LPTSTR wDefaultPrinterName = new TCHAR[255]; |
||||
LPTSTR virtualPrinter = new TCHAR[255]; |
||||
|
||||
int i = QString("Uniboard").toWCharArray(virtualPrinter); // TODO UB 4.x make me configurable ....
|
||||
virtualPrinter[i] = 0; |
||||
DWORD bufferSize = 1000; |
||||
GetDefaultPrinter(wDefaultPrinterName, &bufferSize); |
||||
UBImportVirtualPrinter::sOriginalDefaultPrintername = QString::fromWCharArray(wDefaultPrinterName); |
||||
|
||||
if (!SetDefaultPrinter(virtualPrinter)) |
||||
{ |
||||
QMessageBox msgBox(0); |
||||
msgBox.setText(tr("Uniboard printer is not installed. Import will be done in jpg format.")); |
||||
msgBox.setStandardButtons(QMessageBox::Ok); |
||||
msgBox.setDefaultButton(QMessageBox::Ok); |
||||
msgBox.exec(); |
||||
QString tempLocation = UBFileSystemUtils::createTempDir(); |
||||
QDir tempDir(tempLocation); |
||||
|
||||
bool pptSuccess = generateImagesFromPptFile(pFile.fileName(), tempLocation, "jpg", QSize(3000, 2250)); |
||||
|
||||
if (pptSuccess) |
||||
{ |
||||
if (UBDocumentManager::documentManager()->addImageDirToDocument(tempDir, pDocument)) |
||||
{ |
||||
UBFileSystemUtils::deleteDir(tempLocation); |
||||
result = true; |
||||
} |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
UBImportVirtualPrinter::pendingDocument = pDocument; |
||||
result = ShellExecute(NULL, QString("print").utf16() , pFile.fileName().utf16(), NULL, NULL, SW_HIDE); |
||||
} |
||||
|
||||
delete[] wDefaultPrinterName; |
||||
delete[] virtualPrinter; |
||||
return result; |
||||
|
||||
} |
||||
|
||||
void UBPowerPointApplication::exception ( int code, const QString & source, const QString & desc, const QString & help ) |
||||
{ |
||||
Q_UNUSED(help); |
||||
mHasException = true; |
||||
qCritical() << source << desc << code; |
||||
} |
||||
|
@ -1,50 +0,0 @@ |
||||
/*
|
||||
* UBPowerPointApplication.h |
||||
* |
||||
* Created on: Dec 4, 2008 |
||||
* Author: Luc |
||||
*/ |
||||
|
||||
#ifndef UBPOWERPOINTAPPLICATIONWIN_H_ |
||||
#define UBPOWERPOINTAPPLICATIONWIN_H_ |
||||
|
||||
#include <QtCore> |
||||
#include "UBImportAdaptor.h" |
||||
|
||||
class UBPowerPointApplication : public UBImportAdaptor { |
||||
|
||||
Q_OBJECT; |
||||
|
||||
public: |
||||
UBPowerPointApplication(QObject* parent = 0); |
||||
virtual ~UBPowerPointApplication(); |
||||
|
||||
bool isPowerPointInstalled(); |
||||
|
||||
bool supportPptx(); |
||||
|
||||
virtual QStringList supportedExtentions(); |
||||
virtual QString importFileFilter(); |
||||
virtual UBDocumentProxy* importFile(const QFile& pFile, const QString& pGroup); |
||||
|
||||
virtual bool addFileToDocument(UBDocumentProxy* pDocument, const QFile& pFile); |
||||
|
||||
private: |
||||
|
||||
bool generateImagesFromPptFile(const QString& pptFile, const QString&outputDir, const QString& imageFormat, const QSize& imageSize); |
||||
bool generatePdfFromPptFile(const QString& pptFile, const QString& pOutputFile); |
||||
bool catchAndProcessFile(); |
||||
|
||||
void init(); |
||||
bool mInit; |
||||
bool mHasException; |
||||
bool mSupportPpt; |
||||
bool mSupportPptX; |
||||
|
||||
|
||||
private slots: |
||||
void exception ( int code, const QString & source, const QString & desc, const QString & help); |
||||
|
||||
}; |
||||
|
||||
#endif /* UBPOWERPOINTAPPLICATIONWIN_H_ */ |
@ -1 +0,0 @@ |
||||
opensource@google.com |
@ -1,28 +0,0 @@ |
||||
Copyright (c) 2006, Google Inc. |
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following disclaimer |
||||
in the documentation and/or other materials provided with the |
||||
distribution. |
||||
* Neither the name of Google Inc. nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -1,44 +0,0 @@ |
||||
# Copyright (c) 2010, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
# We only use this file to ease the steps of generating projects after |
||||
# syncing, if we use gclient. All dependencies are svn:externals instead. |
||||
# If you're not using gclient, you need to run the gyp python script to |
||||
# generate the projects. |
||||
# This can be done by the following command (assuming current directory): |
||||
# src\tools\gyp\gyp.bat src\client\windows\breakpad_client.gyp |
||||
hooks = [ |
||||
{ |
||||
# A change to a .gyp, .gypi, or to GYP itself should run the generator. |
||||
"pattern": ".", |
||||
"action": ["python", |
||||
"src/src/tools/gyp/gyp", |
||||
"src/src/client/windows/breakpad_client.gyp"], |
||||
}, |
||||
] |
@ -1,234 +0,0 @@ |
||||
Installation Instructions |
||||
************************* |
||||
|
||||
Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, |
||||
2006 Free Software Foundation, Inc. |
||||
|
||||
This file is free documentation; the Free Software Foundation gives |
||||
unlimited permission to copy, distribute and modify it. |
||||
|
||||
Basic Installation |
||||
================== |
||||
|
||||
Briefly, the shell commands `./configure; make; make install' should |
||||
configure, build, and install this package. The following |
||||
more-detailed instructions are generic; see the `README' file for |
||||
instructions specific to this package. |
||||
|
||||
The `configure' shell script attempts to guess correct values for |
||||
various system-dependent variables used during compilation. It uses |
||||
those values to create a `Makefile' in each directory of the package. |
||||
It may also create one or more `.h' files containing system-dependent |
||||
definitions. Finally, it creates a shell script `config.status' that |
||||
you can run in the future to recreate the current configuration, and a |
||||
file `config.log' containing compiler output (useful mainly for |
||||
debugging `configure'). |
||||
|
||||
It can also use an optional file (typically called `config.cache' |
||||
and enabled with `--cache-file=config.cache' or simply `-C') that saves |
||||
the results of its tests to speed up reconfiguring. Caching is |
||||
disabled by default to prevent problems with accidental use of stale |
||||
cache files. |
||||
|
||||
If you need to do unusual things to compile the package, please try |
||||
to figure out how `configure' could check whether to do them, and mail |
||||
diffs or instructions to the address given in the `README' so they can |
||||
be considered for the next release. If you are using the cache, and at |
||||
some point `config.cache' contains results you don't want to keep, you |
||||
may remove or edit it. |
||||
|
||||
The file `configure.ac' (or `configure.in') is used to create |
||||
`configure' by a program called `autoconf'. You need `configure.ac' if |
||||
you want to change it or regenerate `configure' using a newer version |
||||
of `autoconf'. |
||||
|
||||
The simplest way to compile this package is: |
||||
|
||||
1. `cd' to the directory containing the package's source code and type |
||||
`./configure' to configure the package for your system. |
||||
|
||||
Running `configure' might take a while. While running, it prints |
||||
some messages telling which features it is checking for. |
||||
|
||||
2. Type `make' to compile the package. |
||||
|
||||
3. Optionally, type `make check' to run any self-tests that come with |
||||
the package. |
||||
|
||||
4. Type `make install' to install the programs and any data files and |
||||
documentation. |
||||
|
||||
5. You can remove the program binaries and object files from the |
||||
source code directory by typing `make clean'. To also remove the |
||||
files that `configure' created (so you can compile the package for |
||||
a different kind of computer), type `make distclean'. There is |
||||
also a `make maintainer-clean' target, but that is intended mainly |
||||
for the package's developers. If you use it, you may have to get |
||||
all sorts of other programs in order to regenerate files that came |
||||
with the distribution. |
||||
|
||||
Compilers and Options |
||||
===================== |
||||
|
||||
Some systems require unusual options for compilation or linking that the |
||||
`configure' script does not know about. Run `./configure --help' for |
||||
details on some of the pertinent environment variables. |
||||
|
||||
You can give `configure' initial values for configuration parameters |
||||
by setting variables in the command line or in the environment. Here |
||||
is an example: |
||||
|
||||
./configure CC=c99 CFLAGS=-g LIBS=-lposix |
||||
|
||||
*Note Defining Variables::, for more details. |
||||
|
||||
Compiling For Multiple Architectures |
||||
==================================== |
||||
|
||||
You can compile the package for more than one kind of computer at the |
||||
same time, by placing the object files for each architecture in their |
||||
own directory. To do this, you can use GNU `make'. `cd' to the |
||||
directory where you want the object files and executables to go and run |
||||
the `configure' script. `configure' automatically checks for the |
||||
source code in the directory that `configure' is in and in `..'. |
||||
|
||||
With a non-GNU `make', it is safer to compile the package for one |
||||
architecture at a time in the source code directory. After you have |
||||
installed the package for one architecture, use `make distclean' before |
||||
reconfiguring for another architecture. |
||||
|
||||
Installation Names |
||||
================== |
||||
|
||||
By default, `make install' installs the package's commands under |
||||
`/usr/local/bin', include files under `/usr/local/include', etc. You |
||||
can specify an installation prefix other than `/usr/local' by giving |
||||
`configure' the option `--prefix=PREFIX'. |
||||
|
||||
You can specify separate installation prefixes for |
||||
architecture-specific files and architecture-independent files. If you |
||||
pass the option `--exec-prefix=PREFIX' to `configure', the package uses |
||||
PREFIX as the prefix for installing programs and libraries. |
||||
Documentation and other data files still use the regular prefix. |
||||
|
||||
In addition, if you use an unusual directory layout you can give |
||||
options like `--bindir=DIR' to specify different values for particular |
||||
kinds of files. Run `configure --help' for a list of the directories |
||||
you can set and what kinds of files go in them. |
||||
|
||||
If the package supports it, you can cause programs to be installed |
||||
with an extra prefix or suffix on their names by giving `configure' the |
||||
option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. |
||||
|
||||
Optional Features |
||||
================= |
||||
|
||||
Some packages pay attention to `--enable-FEATURE' options to |
||||
`configure', where FEATURE indicates an optional part of the package. |
||||
They may also pay attention to `--with-PACKAGE' options, where PACKAGE |
||||
is something like `gnu-as' or `x' (for the X Window System). The |
||||
`README' should mention any `--enable-' and `--with-' options that the |
||||
package recognizes. |
||||
|
||||
For packages that use the X Window System, `configure' can usually |
||||
find the X include and library files automatically, but if it doesn't, |
||||
you can use the `configure' options `--x-includes=DIR' and |
||||
`--x-libraries=DIR' to specify their locations. |
||||
|
||||
Specifying the System Type |
||||
========================== |
||||
|
||||
There may be some features `configure' cannot figure out automatically, |
||||
but needs to determine by the type of machine the package will run on. |
||||
Usually, assuming the package is built to be run on the _same_ |
||||
architectures, `configure' can figure that out, but if it prints a |
||||
message saying it cannot guess the machine type, give it the |
||||
`--build=TYPE' option. TYPE can either be a short name for the system |
||||
type, such as `sun4', or a canonical name which has the form: |
||||
|
||||
CPU-COMPANY-SYSTEM |
||||
|
||||
where SYSTEM can have one of these forms: |
||||
|
||||
OS KERNEL-OS |
||||
|
||||
See the file `config.sub' for the possible values of each field. If |
||||
`config.sub' isn't included in this package, then this package doesn't |
||||
need to know the machine type. |
||||
|
||||
If you are _building_ compiler tools for cross-compiling, you should |
||||
use the option `--target=TYPE' to select the type of system they will |
||||
produce code for. |
||||
|
||||
If you want to _use_ a cross compiler, that generates code for a |
||||
platform different from the build platform, you should specify the |
||||
"host" platform (i.e., that on which the generated programs will |
||||
eventually be run) with `--host=TYPE'. |
||||
|
||||
Sharing Defaults |
||||
================ |
||||
|
||||
If you want to set default values for `configure' scripts to share, you |
||||
can create a site shell script called `config.site' that gives default |
||||
values for variables like `CC', `cache_file', and `prefix'. |
||||
`configure' looks for `PREFIX/share/config.site' if it exists, then |
||||
`PREFIX/etc/config.site' if it exists. Or, you can set the |
||||
`CONFIG_SITE' environment variable to the location of the site script. |
||||
A warning: not all `configure' scripts look for a site script. |
||||
|
||||
Defining Variables |
||||
================== |
||||
|
||||
Variables not defined in a site shell script can be set in the |
||||
environment passed to `configure'. However, some packages may run |
||||
configure again during the build, and the customized values of these |
||||
variables may be lost. In order to avoid this problem, you should set |
||||
them in the `configure' command line, using `VAR=value'. For example: |
||||
|
||||
./configure CC=/usr/local2/bin/gcc |
||||
|
||||
causes the specified `gcc' to be used as the C compiler (unless it is |
||||
overridden in the site shell script). |
||||
|
||||
Unfortunately, this technique does not work for `CONFIG_SHELL' due to |
||||
an Autoconf bug. Until the bug is fixed you can use this workaround: |
||||
|
||||
CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash |
||||
|
||||
`configure' Invocation |
||||
====================== |
||||
|
||||
`configure' recognizes the following options to control how it operates. |
||||
|
||||
`--help' |
||||
`-h' |
||||
Print a summary of the options to `configure', and exit. |
||||
|
||||
`--version' |
||||
`-V' |
||||
Print the version of Autoconf used to generate the `configure' |
||||
script, and exit. |
||||
|
||||
`--cache-file=FILE' |
||||
Enable the cache: use and save the results of the tests in FILE, |
||||
traditionally `config.cache'. FILE defaults to `/dev/null' to |
||||
disable caching. |
||||
|
||||
`--config-cache' |
||||
`-C' |
||||
Alias for `--cache-file=config.cache'. |
||||
|
||||
`--quiet' |
||||
`--silent' |
||||
`-q' |
||||
Do not print messages saying which checks are being made. To |
||||
suppress all normal output, redirect it to `/dev/null' (any error |
||||
messages will still be shown). |
||||
|
||||
`--srcdir=DIR' |
||||
Look for the package's source code in directory DIR. Usually |
||||
`configure' can determine that directory automatically. |
||||
|
||||
`configure' also accepts some other, not widely useful, options. Run |
||||
`configure --help' for more details. |
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,43 +0,0 @@ |
||||
Breakpad is a set of client and server components which implement a |
||||
crash-reporting system. |
||||
|
||||
|
||||
----- |
||||
Getting started in 32-bit mode (from trunk) |
||||
Configure: CXXFLAGS=-m32 CFLAGS=-m32 CPPFLAGS=-m32 ./configure |
||||
Build: make |
||||
Test: make check |
||||
Install: make install |
||||
|
||||
If you need to reconfigure your build be sure to run "make distclean" first. |
||||
|
||||
|
||||
----- |
||||
To request change review: |
||||
0. Get access to a read-write copy of source. |
||||
Owners at http://code.google.com/p/google-breakpad/ are able to grant |
||||
this access. |
||||
|
||||
1. Check out a read-write copy of source using instructions at |
||||
http://code.google.com/p/google-breakpad/source/checkout |
||||
|
||||
2. Make changes. Build and test your changes. |
||||
For core code like processor use methods above. |
||||
For linux/mac/windows, there are test targets in each project file. |
||||
|
||||
3. Download http://codereview.appspot.com/static/upload.py |
||||
|
||||
4. Run upload.py from the 'src' directory: |
||||
upload.py --server=breakpad.appspot.com |
||||
|
||||
You will be prompted for credential and a description. |
||||
|
||||
5. At http://breakpad.appspot.com you'll find your issue listed; click on it, |
||||
and select Publish+Mail, and enter in the code reviewer and CC |
||||
google-breakpad-dev@googlegroups.com |
||||
|
||||
6. When applying code review feedback, specify the '-i' option when running |
||||
upload.py again and pass the issue number so it updates the existing issue, |
||||
rather than creating a new one. |
||||
Be sure to rerun upload.py from the same directory as you did for previous |
||||
uploads to allow for proper diff calculations. |
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@ |
||||
/usr/share/automake-1.11/compile |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,584 +0,0 @@ |
||||
#! /bin/sh |
||||
# depcomp - compile a program generating dependencies as side-effects |
||||
|
||||
scriptversion=2006-10-15.18 |
||||
|
||||
# Copyright (C) 1999, 2000, 2003, 2004, 2005, 2006 Free Software |
||||
# Foundation, Inc. |
||||
|
||||
# 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA |
||||
# 02110-1301, USA. |
||||
|
||||
# As a special exception to the GNU General Public License, if you |
||||
# distribute this file as part of a program that contains a |
||||
# configuration script generated by Autoconf, you may include it under |
||||
# the same distribution terms that you use for the rest of that program. |
||||
|
||||
# Originally written by Alexandre Oliva <oliva@dcc.unicamp.br>. |
||||
|
||||
case $1 in |
||||
'') |
||||
echo "$0: No command. Try \`$0 --help' for more information." 1>&2 |
||||
exit 1; |
||||
;; |
||||
-h | --h*) |
||||
cat <<\EOF |
||||
Usage: depcomp [--help] [--version] PROGRAM [ARGS] |
||||
|
||||
Run PROGRAMS ARGS to compile a file, generating dependencies |
||||
as side-effects. |
||||
|
||||
Environment variables: |
||||
depmode Dependency tracking mode. |
||||
source Source file read by `PROGRAMS ARGS'. |
||||
object Object file output by `PROGRAMS ARGS'. |
||||
DEPDIR directory where to store dependencies. |
||||
depfile Dependency file to output. |
||||
tmpdepfile Temporary file to use when outputing dependencies. |
||||
libtool Whether libtool is used (yes/no). |
||||
|
||||
Report bugs to <bug-automake@gnu.org>. |
||||
EOF |
||||
exit $? |
||||
;; |
||||
-v | --v*) |
||||
echo "depcomp $scriptversion" |
||||
exit $? |
||||
;; |
||||
esac |
||||
|
||||
if test -z "$depmode" || test -z "$source" || test -z "$object"; then |
||||
echo "depcomp: Variables source, object and depmode must be set" 1>&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. |
||||
depfile=${depfile-`echo "$object" | |
||||
sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} |
||||
tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} |
||||
|
||||
rm -f "$tmpdepfile" |
||||
|
||||
# Some modes work just like other modes, but use different flags. We |
||||
# parameterize here, but still list the modes in the big case below, |
||||
# to make depend.m4 easier to write. Note that we *cannot* use a case |
||||
# here, because this file can only contain one case statement. |
||||
if test "$depmode" = hp; then |
||||
# HP compiler uses -M and no extra arg. |
||||
gccflag=-M |
||||
depmode=gcc |
||||
fi |
||||
|
||||
if test "$depmode" = dashXmstdout; then |
||||
# This is just like dashmstdout with a different argument. |
||||
dashmflag=-xM |
||||
depmode=dashmstdout |
||||
fi |
||||
|
||||
case "$depmode" in |
||||
gcc3) |
||||
## gcc 3 implements dependency tracking that does exactly what |
||||
## we want. Yay! Note: for some reason libtool 1.4 doesn't like |
||||
## it if -MD -MP comes after the -MF stuff. Hmm. |
||||
## Unfortunately, FreeBSD c89 acceptance of flags depends upon |
||||
## the command line argument order; so add the flags where they |
||||
## appear in depend2.am. Note that the slowdown incurred here |
||||
## affects only configure: in makefiles, %FASTDEP% shortcuts this. |
||||
for arg |
||||
do |
||||
case $arg in |
||||
-c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; |
||||
*) set fnord "$@" "$arg" ;; |
||||
esac |
||||
shift # fnord |
||||
shift # $arg |
||||
done |
||||
"$@" |
||||
stat=$? |
||||
if test $stat -eq 0; then : |
||||
else |
||||
rm -f "$tmpdepfile" |
||||
exit $stat |
||||
fi |
||||
mv "$tmpdepfile" "$depfile" |
||||
;; |
||||
|
||||
gcc) |
||||
## There are various ways to get dependency output from gcc. Here's |
||||
## why we pick this rather obscure method: |
||||
## - Don't want to use -MD because we'd like the dependencies to end |
||||
## up in a subdir. Having to rename by hand is ugly. |
||||
## (We might end up doing this anyway to support other compilers.) |
||||
## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like |
||||
## -MM, not -M (despite what the docs say). |
||||
## - Using -M directly means running the compiler twice (even worse |
||||
## than renaming). |
||||
if test -z "$gccflag"; then |
||||
gccflag=-MD, |
||||
fi |
||||
"$@" -Wp,"$gccflag$tmpdepfile" |
||||
stat=$? |
||||
if test $stat -eq 0; then : |
||||
else |
||||
rm -f "$tmpdepfile" |
||||
exit $stat |
||||
fi |
||||
rm -f "$depfile" |
||||
echo "$object : \\" > "$depfile" |
||||
alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
||||
## The second -e expression handles DOS-style file names with drive letters. |
||||
sed -e 's/^[^:]*: / /' \ |
||||
-e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" |
||||
## This next piece of magic avoids the `deleted header file' problem. |
||||
## The problem is that when a header file which appears in a .P file |
||||
## is deleted, the dependency causes make to die (because there is |
||||
## typically no way to rebuild the header). We avoid this by adding |
||||
## dummy dependencies for each header file. Too bad gcc doesn't do |
||||
## this for us directly. |
||||
tr ' ' ' |
||||
' < "$tmpdepfile" | |
||||
## Some versions of gcc put a space before the `:'. On the theory |
||||
## that the space means something, we add a space to the output as |
||||
## well. |
||||
## Some versions of the HPUX 10.20 sed can't process this invocation |
||||
## correctly. Breaking it into two sed invocations is a workaround. |
||||
sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" |
||||
rm -f "$tmpdepfile" |
||||
;; |
||||
|
||||
hp) |
||||
# This case exists only to let depend.m4 do its work. It works by |
||||
# looking at the text of this script. This case will never be run, |
||||
# since it is checked for above. |
||||
exit 1 |
||||
;; |
||||
|
||||
sgi) |
||||
if test "$libtool" = yes; then |
||||
"$@" "-Wp,-MDupdate,$tmpdepfile" |
||||
else |
||||
"$@" -MDupdate "$tmpdepfile" |
||||
fi |
||||
stat=$? |
||||
if test $stat -eq 0; then : |
||||
else |
||||
rm -f "$tmpdepfile" |
||||
exit $stat |
||||
fi |
||||
rm -f "$depfile" |
||||
|
||||
if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files |
||||
echo "$object : \\" > "$depfile" |
||||
|
||||
# Clip off the initial element (the dependent). Don't try to be |
||||
# clever and replace this with sed code, as IRIX sed won't handle |
||||
# lines with more than a fixed number of characters (4096 in |
||||
# IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; |
||||
# the IRIX cc adds comments like `#:fec' to the end of the |
||||
# dependency line. |
||||
tr ' ' ' |
||||
' < "$tmpdepfile" \ |
||||
| sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \ |
||||
tr ' |
||||
' ' ' >> $depfile |
||||
echo >> $depfile |
||||
|
||||
# The second pass generates a dummy entry for each header file. |
||||
tr ' ' ' |
||||
' < "$tmpdepfile" \ |
||||
| sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ |
||||
>> $depfile |
||||
else |
||||
# The sourcefile does not contain any dependencies, so just |
||||
# store a dummy comment line, to avoid errors with the Makefile |
||||
# "include basename.Plo" scheme. |
||||
echo "#dummy" > "$depfile" |
||||
fi |
||||
rm -f "$tmpdepfile" |
||||
;; |
||||
|
||||
aix) |
||||
# The C for AIX Compiler uses -M and outputs the dependencies |
||||
# in a .u file. In older versions, this file always lives in the |
||||
# current directory. Also, the AIX compiler puts `$object:' at the |
||||
# start of each line; $object doesn't have directory information. |
||||
# Version 6 uses the directory in both cases. |
||||
stripped=`echo "$object" | sed 's/\(.*\)\..*$/\1/'` |
||||
tmpdepfile="$stripped.u" |
||||
if test "$libtool" = yes; then |
||||
"$@" -Wc,-M |
||||
else |
||||
"$@" -M |
||||
fi |
||||
stat=$? |
||||
|
||||
if test -f "$tmpdepfile"; then : |
||||
else |
||||
stripped=`echo "$stripped" | sed 's,^.*/,,'` |
||||
tmpdepfile="$stripped.u" |
||||
fi |
||||
|
||||
if test $stat -eq 0; then : |
||||
else |
||||
rm -f "$tmpdepfile" |
||||
exit $stat |
||||
fi |
||||
|
||||
if test -f "$tmpdepfile"; then |
||||
outname="$stripped.o" |
||||
# Each line is of the form `foo.o: dependent.h'. |
||||
# Do two passes, one to just change these to |
||||
# `$object: dependent.h' and one to simply `dependent.h:'. |
||||
sed -e "s,^$outname:,$object :," < "$tmpdepfile" > "$depfile" |
||||
sed -e "s,^$outname: \(.*\)$,\1:," < "$tmpdepfile" >> "$depfile" |
||||
else |
||||
# The sourcefile does not contain any dependencies, so just |
||||
# store a dummy comment line, to avoid errors with the Makefile |
||||
# "include basename.Plo" scheme. |
||||
echo "#dummy" > "$depfile" |
||||
fi |
||||
rm -f "$tmpdepfile" |
||||
;; |
||||
|
||||
icc) |
||||
# Intel's C compiler understands `-MD -MF file'. However on |
||||
# icc -MD -MF foo.d -c -o sub/foo.o sub/foo.c |
||||
# ICC 7.0 will fill foo.d with something like |
||||
# foo.o: sub/foo.c |
||||
# foo.o: sub/foo.h |
||||
# which is wrong. We want: |
||||
# sub/foo.o: sub/foo.c |
||||
# sub/foo.o: sub/foo.h |
||||
# sub/foo.c: |
||||
# sub/foo.h: |
||||
# ICC 7.1 will output |
||||
# foo.o: sub/foo.c sub/foo.h |
||||
# and will wrap long lines using \ : |
||||
# foo.o: sub/foo.c ... \ |
||||
# sub/foo.h ... \ |
||||
# ... |
||||
|
||||
"$@" -MD -MF "$tmpdepfile" |
||||
stat=$? |
||||
if test $stat -eq 0; then : |
||||
else |
||||
rm -f "$tmpdepfile" |
||||
exit $stat |
||||
fi |
||||
rm -f "$depfile" |
||||
# Each line is of the form `foo.o: dependent.h', |
||||
# or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. |
||||
# Do two passes, one to just change these to |
||||
# `$object: dependent.h' and one to simply `dependent.h:'. |
||||
sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" |
||||
# Some versions of the HPUX 10.20 sed can't process this invocation |
||||
# correctly. Breaking it into two sed invocations is a workaround. |
||||
sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" | |
||||
sed -e 's/$/ :/' >> "$depfile" |
||||
rm -f "$tmpdepfile" |
||||
;; |
||||
|
||||
hp2) |
||||
# The "hp" stanza above does not work with aCC (C++) and HP's ia64 |
||||
# compilers, which have integrated preprocessors. The correct option |
||||
# to use with these is +Maked; it writes dependencies to a file named |
||||
# 'foo.d', which lands next to the object file, wherever that |
||||
# happens to be. |
||||
# Much of this is similar to the tru64 case; see comments there. |
||||
dir=`echo "$object" | sed -e 's|/[^/]*$|/|'` |
||||
test "x$dir" = "x$object" && dir= |
||||
base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'` |
||||
if test "$libtool" = yes; then |
||||
tmpdepfile1=$dir$base.d |
||||
tmpdepfile2=$dir.libs/$base.d |
||||
"$@" -Wc,+Maked |
||||
else |
||||
tmpdepfile1=$dir$base.d |
||||
tmpdepfile2=$dir$base.d |
||||
"$@" +Maked |
||||
fi |
||||
stat=$? |
||||
if test $stat -eq 0; then : |
||||
else |
||||
rm -f "$tmpdepfile1" "$tmpdepfile2" |
||||
exit $stat |
||||
fi |
||||
|
||||
for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" |
||||
do |
||||
test -f "$tmpdepfile" && break |
||||
done |
||||
if test -f "$tmpdepfile"; then |
||||
sed -e "s,^.*\.[a-z]*:,$object:," "$tmpdepfile" > "$depfile" |
||||
# Add `dependent.h:' lines. |
||||
sed -ne '2,${; s/^ *//; s/ \\*$//; s/$/:/; p;}' "$tmpdepfile" >> "$depfile" |
||||
else |
||||
echo "#dummy" > "$depfile" |
||||
fi |
||||
rm -f "$tmpdepfile" "$tmpdepfile2" |
||||
;; |
||||
|
||||
tru64) |
||||
# The Tru64 compiler uses -MD to generate dependencies as a side |
||||
# effect. `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'. |
||||
# At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put |
||||
# dependencies in `foo.d' instead, so we check for that too. |
||||
# Subdirectories are respected. |
||||
dir=`echo "$object" | sed -e 's|/[^/]*$|/|'` |
||||
test "x$dir" = "x$object" && dir= |
||||
base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'` |
||||
|
||||
if test "$libtool" = yes; then |
||||
# With Tru64 cc, shared objects can also be used to make a |
||||
# static library. This mechanism is used in libtool 1.4 series to |
||||
# handle both shared and static libraries in a single compilation. |
||||
# With libtool 1.4, dependencies were output in $dir.libs/$base.lo.d. |
||||
# |
||||
# With libtool 1.5 this exception was removed, and libtool now |
||||
# generates 2 separate objects for the 2 libraries. These two |
||||
# compilations output dependencies in $dir.libs/$base.o.d and |
||||
# in $dir$base.o.d. We have to check for both files, because |
||||
# one of the two compilations can be disabled. We should prefer |
||||
# $dir$base.o.d over $dir.libs/$base.o.d because the latter is |
||||
# automatically cleaned when .libs/ is deleted, while ignoring |
||||
# the former would cause a distcleancheck panic. |
||||
tmpdepfile1=$dir.libs/$base.lo.d # libtool 1.4 |
||||
tmpdepfile2=$dir$base.o.d # libtool 1.5 |
||||
tmpdepfile3=$dir.libs/$base.o.d # libtool 1.5 |
||||
tmpdepfile4=$dir.libs/$base.d # Compaq CCC V6.2-504 |
||||
"$@" -Wc,-MD |
||||
else |
||||
tmpdepfile1=$dir$base.o.d |
||||
tmpdepfile2=$dir$base.d |
||||
tmpdepfile3=$dir$base.d |
||||
tmpdepfile4=$dir$base.d |
||||
"$@" -MD |
||||
fi |
||||
|
||||
stat=$? |
||||
if test $stat -eq 0; then : |
||||
else |
||||
rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4" |
||||
exit $stat |
||||
fi |
||||
|
||||
for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4" |
||||
do |
||||
test -f "$tmpdepfile" && break |
||||
done |
||||
if test -f "$tmpdepfile"; then |
||||
sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile" |
||||
# That's a tab and a space in the []. |
||||
sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile" |
||||
else |
||||
echo "#dummy" > "$depfile" |
||||
fi |
||||
rm -f "$tmpdepfile" |
||||
;; |
||||
|
||||
#nosideeffect) |
||||
# This comment above is used by automake to tell side-effect |
||||
# dependency tracking mechanisms from slower ones. |
||||
|
||||
dashmstdout) |
||||
# Important note: in order to support this mode, a compiler *must* |
||||
# always write the preprocessed file to stdout, regardless of -o. |
||||
"$@" || exit $? |
||||
|
||||
# Remove the call to Libtool. |
||||
if test "$libtool" = yes; then |
||||
while test $1 != '--mode=compile'; do |
||||
shift |
||||
done |
||||
shift |
||||
fi |
||||
|
||||
# Remove `-o $object'. |
||||
IFS=" " |
||||
for arg |
||||
do |
||||
case $arg in |
||||
-o) |
||||
shift |
||||
;; |
||||
$object) |
||||
shift |
||||
;; |
||||
*) |
||||
set fnord "$@" "$arg" |
||||
shift # fnord |
||||
shift # $arg |
||||
;; |
||||
esac |
||||
done |
||||
|
||||
test -z "$dashmflag" && dashmflag=-M |
||||
# Require at least two characters before searching for `:' |
||||
# in the target name. This is to cope with DOS-style filenames: |
||||
# a dependency such as `c:/foo/bar' could be seen as target `c' otherwise. |
||||
"$@" $dashmflag | |
||||
sed 's:^[ ]*[^: ][^:][^:]*\:[ ]*:'"$object"'\: :' > "$tmpdepfile" |
||||
rm -f "$depfile" |
||||
cat < "$tmpdepfile" > "$depfile" |
||||
tr ' ' ' |
||||
' < "$tmpdepfile" | \ |
||||
## Some versions of the HPUX 10.20 sed can't process this invocation |
||||
## correctly. Breaking it into two sed invocations is a workaround. |
||||
sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" |
||||
rm -f "$tmpdepfile" |
||||
;; |
||||
|
||||
dashXmstdout) |
||||
# This case only exists to satisfy depend.m4. It is never actually |
||||
# run, as this mode is specially recognized in the preamble. |
||||
exit 1 |
||||
;; |
||||
|
||||
makedepend) |
||||
"$@" || exit $? |
||||
# Remove any Libtool call |
||||
if test "$libtool" = yes; then |
||||
while test $1 != '--mode=compile'; do |
||||
shift |
||||
done |
||||
shift |
||||
fi |
||||
# X makedepend |
||||
shift |
||||
cleared=no |
||||
for arg in "$@"; do |
||||
case $cleared in |
||||
no) |
||||
set ""; shift |
||||
cleared=yes ;; |
||||
esac |
||||
case "$arg" in |
||||
-D*|-I*) |
||||
set fnord "$@" "$arg"; shift ;; |
||||
# Strip any option that makedepend may not understand. Remove |
||||
# the object too, otherwise makedepend will parse it as a source file. |
||||
-*|$object) |
||||
;; |
||||
*) |
||||
set fnord "$@" "$arg"; shift ;; |
||||
esac |
||||
done |
||||
obj_suffix="`echo $object | sed 's/^.*\././'`" |
||||
touch "$tmpdepfile" |
||||
${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" |
||||
rm -f "$depfile" |
||||
cat < "$tmpdepfile" > "$depfile" |
||||
sed '1,2d' "$tmpdepfile" | tr ' ' ' |
||||
' | \ |
||||
## Some versions of the HPUX 10.20 sed can't process this invocation |
||||
## correctly. Breaking it into two sed invocations is a workaround. |
||||
sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" |
||||
rm -f "$tmpdepfile" "$tmpdepfile".bak |
||||
;; |
||||
|
||||
cpp) |
||||
# Important note: in order to support this mode, a compiler *must* |
||||
# always write the preprocessed file to stdout. |
||||
"$@" || exit $? |
||||
|
||||
# Remove the call to Libtool. |
||||
if test "$libtool" = yes; then |
||||
while test $1 != '--mode=compile'; do |
||||
shift |
||||
done |
||||
shift |
||||
fi |
||||
|
||||
# Remove `-o $object'. |
||||
IFS=" " |
||||
for arg |
||||
do |
||||
case $arg in |
||||
-o) |
||||
shift |
||||
;; |
||||
$object) |
||||
shift |
||||
;; |
||||
*) |
||||
set fnord "$@" "$arg" |
||||
shift # fnord |
||||
shift # $arg |
||||
;; |
||||
esac |
||||
done |
||||
|
||||
"$@" -E | |
||||
sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ |
||||
-e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' | |
||||
sed '$ s: \\$::' > "$tmpdepfile" |
||||
rm -f "$depfile" |
||||
echo "$object : \\" > "$depfile" |
||||
cat < "$tmpdepfile" >> "$depfile" |
||||
sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" |
||||
rm -f "$tmpdepfile" |
||||
;; |
||||
|
||||
msvisualcpp) |
||||
# Important note: in order to support this mode, a compiler *must* |
||||
# always write the preprocessed file to stdout, regardless of -o, |
||||
# because we must use -o when running libtool. |
||||
"$@" || exit $? |
||||
IFS=" " |
||||
for arg |
||||
do |
||||
case "$arg" in |
||||
"-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") |
||||
set fnord "$@" |
||||
shift |
||||
shift |
||||
;; |
||||
*) |
||||
set fnord "$@" "$arg" |
||||
shift |
||||
shift |
||||
;; |
||||
esac |
||||
done |
||||
"$@" -E | |
||||
sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::echo "`cygpath -u \\"\1\\"`":p' | sort | uniq > "$tmpdepfile" |
||||
rm -f "$depfile" |
||||
echo "$object : \\" > "$depfile" |
||||
. "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s:: \1 \\:p' >> "$depfile" |
||||
echo " " >> "$depfile" |
||||
. "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s::\1\::p' >> "$depfile" |
||||
rm -f "$tmpdepfile" |
||||
;; |
||||
|
||||
none) |
||||
exec "$@" |
||||
;; |
||||
|
||||
*) |
||||
echo "Unknown depmode $depmode" 1>&2 |
||||
exit 1 |
||||
;; |
||||
esac |
||||
|
||||
exit 0 |
||||
|
||||
# Local Variables: |
||||
# mode: shell-script |
||||
# sh-indentation: 2 |
||||
# eval: (add-hook 'write-file-hooks 'time-stamp) |
||||
# time-stamp-start: "scriptversion=" |
||||
# time-stamp-format: "%:y-%02m-%02d.%02H" |
||||
# time-stamp-end: "$" |
||||
# End: |
@ -1,507 +0,0 @@ |
||||
#!/bin/sh |
||||
# install - install a program, script, or datafile |
||||
|
||||
scriptversion=2006-10-14.15 |
||||
|
||||
# This originates from X11R5 (mit/util/scripts/install.sh), which was |
||||
# later released in X11R6 (xc/config/util/install.sh) with the |
||||
# following copyright and license. |
||||
# |
||||
# Copyright (C) 1994 X Consortium |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
# of this software and associated documentation files (the "Software"), to |
||||
# deal in the Software without restriction, including without limitation the |
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
||||
# sell copies of the Software, and to permit persons to whom the Software is |
||||
# furnished to do so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in |
||||
# all copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN |
||||
# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- |
||||
# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
# |
||||
# Except as contained in this notice, the name of the X Consortium shall not |
||||
# be used in advertising or otherwise to promote the sale, use or other deal- |
||||
# ings in this Software without prior written authorization from the X Consor- |
||||
# tium. |
||||
# |
||||
# |
||||
# FSF changes to this file are in the public domain. |
||||
# |
||||
# Calling this script install-sh is preferred over install.sh, to prevent |
||||
# `make' implicit rules from creating a file called install from it |
||||
# when there is no Makefile. |
||||
# |
||||
# This script is compatible with the BSD install script, but was written |
||||
# from scratch. |
||||
|
||||
nl=' |
||||
' |
||||
IFS=" "" $nl" |
||||
|
||||
# set DOITPROG to echo to test this script |
||||
|
||||
# Don't use :- since 4.3BSD and earlier shells don't like it. |
||||
doit="${DOITPROG-}" |
||||
if test -z "$doit"; then |
||||
doit_exec=exec |
||||
else |
||||
doit_exec=$doit |
||||
fi |
||||
|
||||
# Put in absolute file names if you don't have them in your path; |
||||
# or use environment vars. |
||||
|
||||
mvprog="${MVPROG-mv}" |
||||
cpprog="${CPPROG-cp}" |
||||
chmodprog="${CHMODPROG-chmod}" |
||||
chownprog="${CHOWNPROG-chown}" |
||||
chgrpprog="${CHGRPPROG-chgrp}" |
||||
stripprog="${STRIPPROG-strip}" |
||||
rmprog="${RMPROG-rm}" |
||||
mkdirprog="${MKDIRPROG-mkdir}" |
||||
|
||||
posix_glob= |
||||
posix_mkdir= |
||||
|
||||
# Desired mode of installed file. |
||||
mode=0755 |
||||
|
||||
chmodcmd=$chmodprog |
||||
chowncmd= |
||||
chgrpcmd= |
||||
stripcmd= |
||||
rmcmd="$rmprog -f" |
||||
mvcmd="$mvprog" |
||||
src= |
||||
dst= |
||||
dir_arg= |
||||
dstarg= |
||||
no_target_directory= |
||||
|
||||
usage="Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE |
||||
or: $0 [OPTION]... SRCFILES... DIRECTORY |
||||
or: $0 [OPTION]... -t DIRECTORY SRCFILES... |
||||
or: $0 [OPTION]... -d DIRECTORIES... |
||||
|
||||
In the 1st form, copy SRCFILE to DSTFILE. |
||||
In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. |
||||
In the 4th, create DIRECTORIES. |
||||
|
||||
Options: |
||||
-c (ignored) |
||||
-d create directories instead of installing files. |
||||
-g GROUP $chgrpprog installed files to GROUP. |
||||
-m MODE $chmodprog installed files to MODE. |
||||
-o USER $chownprog installed files to USER. |
||||
-s $stripprog installed files. |
||||
-t DIRECTORY install into DIRECTORY. |
||||
-T report an error if DSTFILE is a directory. |
||||
--help display this help and exit. |
||||
--version display version info and exit. |
||||
|
||||
Environment variables override the default commands: |
||||
CHGRPPROG CHMODPROG CHOWNPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG |
||||
" |
||||
|
||||
while test $# -ne 0; do |
||||
case $1 in |
||||
-c) shift |
||||
continue;; |
||||
|
||||
-d) dir_arg=true |
||||
shift |
||||
continue;; |
||||
|
||||
-g) chgrpcmd="$chgrpprog $2" |
||||
shift |
||||
shift |
||||
continue;; |
||||
|
||||
--help) echo "$usage"; exit $?;; |
||||
|
||||
-m) mode=$2 |
||||
shift |
||||
shift |
||||
case $mode in |
||||
*' '* | *' '* | *' |
||||
'* | *'*'* | *'?'* | *'['*) |
||||
echo "$0: invalid mode: $mode" >&2 |
||||
exit 1;; |
||||
esac |
||||
continue;; |
||||
|
||||
-o) chowncmd="$chownprog $2" |
||||
shift |
||||
shift |
||||
continue;; |
||||
|
||||
-s) stripcmd=$stripprog |
||||
shift |
||||
continue;; |
||||
|
||||
-t) dstarg=$2 |
||||
shift |
||||
shift |
||||
continue;; |
||||
|
||||
-T) no_target_directory=true |
||||
shift |
||||
continue;; |
||||
|
||||
--version) echo "$0 $scriptversion"; exit $?;; |
||||
|
||||
--) shift |
||||
break;; |
||||
|
||||
-*) echo "$0: invalid option: $1" >&2 |
||||
exit 1;; |
||||
|
||||
*) break;; |
||||
esac |
||||
done |
||||
|
||||
if test $# -ne 0 && test -z "$dir_arg$dstarg"; then |
||||
# When -d is used, all remaining arguments are directories to create. |
||||
# When -t is used, the destination is already specified. |
||||
# Otherwise, the last argument is the destination. Remove it from $@. |
||||
for arg |
||||
do |
||||
if test -n "$dstarg"; then |
||||
# $@ is not empty: it contains at least $arg. |
||||
set fnord "$@" "$dstarg" |
||||
shift # fnord |
||||
fi |
||||
shift # arg |
||||
dstarg=$arg |
||||
done |
||||
fi |
||||
|
||||
if test $# -eq 0; then |
||||
if test -z "$dir_arg"; then |
||||
echo "$0: no input file specified." >&2 |
||||
exit 1 |
||||
fi |
||||
# It's OK to call `install-sh -d' without argument. |
||||
# This can happen when creating conditional directories. |
||||
exit 0 |
||||
fi |
||||
|
||||
if test -z "$dir_arg"; then |
||||
trap '(exit $?); exit' 1 2 13 15 |
||||
|
||||
# Set umask so as not to create temps with too-generous modes. |
||||
# However, 'strip' requires both read and write access to temps. |
||||
case $mode in |
||||
# Optimize common cases. |
||||
*644) cp_umask=133;; |
||||
*755) cp_umask=22;; |
||||
|
||||
*[0-7]) |
||||
if test -z "$stripcmd"; then |
||||
u_plus_rw= |
||||
else |
||||
u_plus_rw='% 200' |
||||
fi |
||||
cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; |
||||
*) |
||||
if test -z "$stripcmd"; then |
||||
u_plus_rw= |
||||
else |
||||
u_plus_rw=,u+rw |
||||
fi |
||||
cp_umask=$mode$u_plus_rw;; |
||||
esac |
||||
fi |
||||
|
||||
for src |
||||
do |
||||
# Protect names starting with `-'. |
||||
case $src in |
||||
-*) src=./$src ;; |
||||
esac |
||||
|
||||
if test -n "$dir_arg"; then |
||||
dst=$src |
||||
dstdir=$dst |
||||
test -d "$dstdir" |
||||
dstdir_status=$? |
||||
else |
||||
|
||||
# Waiting for this to be detected by the "$cpprog $src $dsttmp" command |
||||
# might cause directories to be created, which would be especially bad |
||||
# if $src (and thus $dsttmp) contains '*'. |
||||
if test ! -f "$src" && test ! -d "$src"; then |
||||
echo "$0: $src does not exist." >&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
if test -z "$dstarg"; then |
||||
echo "$0: no destination specified." >&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
dst=$dstarg |
||||
# Protect names starting with `-'. |
||||
case $dst in |
||||
-*) dst=./$dst ;; |
||||
esac |
||||
|
||||
# If destination is a directory, append the input filename; won't work |
||||
# if double slashes aren't ignored. |
||||
if test -d "$dst"; then |
||||
if test -n "$no_target_directory"; then |
||||
echo "$0: $dstarg: Is a directory" >&2 |
||||
exit 1 |
||||
fi |
||||
dstdir=$dst |
||||
dst=$dstdir/`basename "$src"` |
||||
dstdir_status=0 |
||||
else |
||||
# Prefer dirname, but fall back on a substitute if dirname fails. |
||||
dstdir=` |
||||
(dirname "$dst") 2>/dev/null || |
||||
expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ |
||||
X"$dst" : 'X\(//\)[^/]' \| \ |
||||
X"$dst" : 'X\(//\)$' \| \ |
||||
X"$dst" : 'X\(/\)' \| . 2>/dev/null || |
||||
echo X"$dst" | |
||||
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ |
||||
s//\1/ |
||||
q |
||||
} |
||||
/^X\(\/\/\)[^/].*/{ |
||||
s//\1/ |
||||
q |
||||
} |
||||
/^X\(\/\/\)$/{ |
||||
s//\1/ |
||||
q |
||||
} |
||||
/^X\(\/\).*/{ |
||||
s//\1/ |
||||
q |
||||
} |
||||
s/.*/./; q' |
||||
` |
||||
|
||||
test -d "$dstdir" |
||||
dstdir_status=$? |
||||
fi |
||||
fi |
||||
|
||||
obsolete_mkdir_used=false |
||||
|
||||
if test $dstdir_status != 0; then |
||||
case $posix_mkdir in |
||||
'') |
||||
# Create intermediate dirs using mode 755 as modified by the umask. |
||||
# This is like FreeBSD 'install' as of 1997-10-28. |
||||
umask=`umask` |
||||
case $stripcmd.$umask in |
||||
# Optimize common cases. |
||||
*[2367][2367]) mkdir_umask=$umask;; |
||||
.*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; |
||||
|
||||
*[0-7]) |
||||
mkdir_umask=`expr $umask + 22 \ |
||||
- $umask % 100 % 40 + $umask % 20 \ |
||||
- $umask % 10 % 4 + $umask % 2 |
||||
`;; |
||||
*) mkdir_umask=$umask,go-w;; |
||||
esac |
||||
|
||||
# With -d, create the new directory with the user-specified mode. |
||||
# Otherwise, rely on $mkdir_umask. |
||||
if test -n "$dir_arg"; then |
||||
mkdir_mode=-m$mode |
||||
else |
||||
mkdir_mode= |
||||
fi |
||||
|
||||
posix_mkdir=false |
||||
case $umask in |
||||
*[123567][0-7][0-7]) |
||||
# POSIX mkdir -p sets u+wx bits regardless of umask, which |
||||
# is incompatible with FreeBSD 'install' when (umask & 300) != 0. |
||||
;; |
||||
*) |
||||
tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ |
||||
trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 |
||||
|
||||
if (umask $mkdir_umask && |
||||
exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 |
||||
then |
||||
if test -z "$dir_arg" || { |
||||
# Check for POSIX incompatibilities with -m. |
||||
# HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or |
||||
# other-writeable bit of parent directory when it shouldn't. |
||||
# FreeBSD 6.1 mkdir -m -p sets mode of existing directory. |
||||
ls_ld_tmpdir=`ls -ld "$tmpdir"` |
||||
case $ls_ld_tmpdir in |
||||
d????-?r-*) different_mode=700;; |
||||
d????-?--*) different_mode=755;; |
||||
*) false;; |
||||
esac && |
||||
$mkdirprog -m$different_mode -p -- "$tmpdir" && { |
||||
ls_ld_tmpdir_1=`ls -ld "$tmpdir"` |
||||
test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" |
||||
} |
||||
} |
||||
then posix_mkdir=: |
||||
fi |
||||
rmdir "$tmpdir/d" "$tmpdir" |
||||
else |
||||
# Remove any dirs left behind by ancient mkdir implementations. |
||||
rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null |
||||
fi |
||||
trap '' 0;; |
||||
esac;; |
||||
esac |
||||
|
||||
if |
||||
$posix_mkdir && ( |
||||
umask $mkdir_umask && |
||||
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" |
||||
) |
||||
then : |
||||
else |
||||
|
||||
# The umask is ridiculous, or mkdir does not conform to POSIX, |
||||
# or it failed possibly due to a race condition. Create the |
||||
# directory the slow way, step by step, checking for races as we go. |
||||
|
||||
case $dstdir in |
||||
/*) prefix=/ ;; |
||||
-*) prefix=./ ;; |
||||
*) prefix= ;; |
||||
esac |
||||
|
||||
case $posix_glob in |
||||
'') |
||||
if (set -f) 2>/dev/null; then |
||||
posix_glob=true |
||||
else |
||||
posix_glob=false |
||||
fi ;; |
||||
esac |
||||
|
||||
oIFS=$IFS |
||||
IFS=/ |
||||
$posix_glob && set -f |
||||
set fnord $dstdir |
||||
shift |
||||
$posix_glob && set +f |
||||
IFS=$oIFS |
||||
|
||||
prefixes= |
||||
|
||||
for d |
||||
do |
||||
test -z "$d" && continue |
||||
|
||||
prefix=$prefix$d |
||||
if test -d "$prefix"; then |
||||
prefixes= |
||||
else |
||||
if $posix_mkdir; then |
||||
(umask=$mkdir_umask && |
||||
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break |
||||
# Don't fail if two instances are running concurrently. |
||||
test -d "$prefix" || exit 1 |
||||
else |
||||
case $prefix in |
||||
*\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; |
||||
*) qprefix=$prefix;; |
||||
esac |
||||
prefixes="$prefixes '$qprefix'" |
||||
fi |
||||
fi |
||||
prefix=$prefix/ |
||||
done |
||||
|
||||
if test -n "$prefixes"; then |
||||
# Don't fail if two instances are running concurrently. |
||||
(umask $mkdir_umask && |
||||
eval "\$doit_exec \$mkdirprog $prefixes") || |
||||
test -d "$dstdir" || exit 1 |
||||
obsolete_mkdir_used=true |
||||
fi |
||||
fi |
||||
fi |
||||
|
||||
if test -n "$dir_arg"; then |
||||
{ test -z "$chowncmd" || $doit $chowncmd "$dst"; } && |
||||
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && |
||||
{ test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || |
||||
test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 |
||||
else |
||||
|
||||
# Make a couple of temp file names in the proper directory. |
||||
dsttmp=$dstdir/_inst.$$_ |
||||
rmtmp=$dstdir/_rm.$$_ |
||||
|
||||
# Trap to clean up those temp files at exit. |
||||
trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 |
||||
|
||||
# Copy the file name to the temp name. |
||||
(umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && |
||||
|
||||
# and set any options; do chmod last to preserve setuid bits. |
||||
# |
||||
# If any of these fail, we abort the whole thing. If we want to |
||||
# ignore errors from any of these, just make sure not to ignore |
||||
# errors from the above "$doit $cpprog $src $dsttmp" command. |
||||
# |
||||
{ test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } \ |
||||
&& { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } \ |
||||
&& { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } \ |
||||
&& { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && |
||||
|
||||
# Now rename the file to the real destination. |
||||
{ $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null \ |
||||
|| { |
||||
# The rename failed, perhaps because mv can't rename something else |
||||
# to itself, or perhaps because mv is so ancient that it does not |
||||
# support -f. |
||||
|
||||
# Now remove or move aside any old file at destination location. |
||||
# We try this two ways since rm can't unlink itself on some |
||||
# systems and the destination file might be busy for other |
||||
# reasons. In this case, the final cleanup might fail but the new |
||||
# file should still install successfully. |
||||
{ |
||||
if test -f "$dst"; then |
||||
$doit $rmcmd -f "$dst" 2>/dev/null \ |
||||
|| { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null \ |
||||
&& { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }; }\ |
||||
|| { |
||||
echo "$0: cannot unlink or rename $dst" >&2 |
||||
(exit 1); exit 1 |
||||
} |
||||
else |
||||
: |
||||
fi |
||||
} && |
||||
|
||||
# Now rename the file to the real destination. |
||||
$doit $mvcmd "$dsttmp" "$dst" |
||||
} |
||||
} || exit 1 |
||||
|
||||
trap '' 0 |
||||
fi |
||||
done |
||||
|
||||
# Local variables: |
||||
# eval: (add-hook 'write-file-hooks 'time-stamp) |
||||
# time-stamp-start: "scriptversion=" |
||||
# time-stamp-format: "%:y-%02m-%02d.%02H" |
||||
# time-stamp-end: "$" |
||||
# End: |
File diff suppressed because it is too large
Load Diff
@ -1,367 +0,0 @@ |
||||
#! /bin/sh |
||||
# Common stub for a few missing GNU programs while installing. |
||||
|
||||
scriptversion=2006-05-10.23 |
||||
|
||||
# Copyright (C) 1996, 1997, 1999, 2000, 2002, 2003, 2004, 2005, 2006 |
||||
# Free Software Foundation, Inc. |
||||
# Originally by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996. |
||||
|
||||
# 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA |
||||
# 02110-1301, USA. |
||||
|
||||
# As a special exception to the GNU General Public License, if you |
||||
# distribute this file as part of a program that contains a |
||||
# configuration script generated by Autoconf, you may include it under |
||||
# the same distribution terms that you use for the rest of that program. |
||||
|
||||
if test $# -eq 0; then |
||||
echo 1>&2 "Try \`$0 --help' for more information" |
||||
exit 1 |
||||
fi |
||||
|
||||
run=: |
||||
sed_output='s/.* --output[ =]\([^ ]*\).*/\1/p' |
||||
sed_minuso='s/.* -o \([^ ]*\).*/\1/p' |
||||
|
||||
# In the cases where this matters, `missing' is being run in the |
||||
# srcdir already. |
||||
if test -f configure.ac; then |
||||
configure_ac=configure.ac |
||||
else |
||||
configure_ac=configure.in |
||||
fi |
||||
|
||||
msg="missing on your system" |
||||
|
||||
case $1 in |
||||
--run) |
||||
# Try to run requested program, and just exit if it succeeds. |
||||
run= |
||||
shift |
||||
"$@" && exit 0 |
||||
# Exit code 63 means version mismatch. This often happens |
||||
# when the user try to use an ancient version of a tool on |
||||
# a file that requires a minimum version. In this case we |
||||
# we should proceed has if the program had been absent, or |
||||
# if --run hadn't been passed. |
||||
if test $? = 63; then |
||||
run=: |
||||
msg="probably too old" |
||||
fi |
||||
;; |
||||
|
||||
-h|--h|--he|--hel|--help) |
||||
echo "\ |
||||
$0 [OPTION]... PROGRAM [ARGUMENT]... |
||||
|
||||
Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an |
||||
error status if there is no known handling for PROGRAM. |
||||
|
||||
Options: |
||||
-h, --help display this help and exit |
||||
-v, --version output version information and exit |
||||
--run try to run the given command, and emulate it if it fails |
||||
|
||||
Supported PROGRAM values: |
||||
aclocal touch file \`aclocal.m4' |
||||
autoconf touch file \`configure' |
||||
autoheader touch file \`config.h.in' |
||||
autom4te touch the output file, or create a stub one |
||||
automake touch all \`Makefile.in' files |
||||
bison create \`y.tab.[ch]', if possible, from existing .[ch] |
||||
flex create \`lex.yy.c', if possible, from existing .c |
||||
help2man touch the output file |
||||
lex create \`lex.yy.c', if possible, from existing .c |
||||
makeinfo touch the output file |
||||
tar try tar, gnutar, gtar, then tar without non-portable flags |
||||
yacc create \`y.tab.[ch]', if possible, from existing .[ch] |
||||
|
||||
Send bug reports to <bug-automake@gnu.org>." |
||||
exit $? |
||||
;; |
||||
|
||||
-v|--v|--ve|--ver|--vers|--versi|--versio|--version) |
||||
echo "missing $scriptversion (GNU Automake)" |
||||
exit $? |
||||
;; |
||||
|
||||
-*) |
||||
echo 1>&2 "$0: Unknown \`$1' option" |
||||
echo 1>&2 "Try \`$0 --help' for more information" |
||||
exit 1 |
||||
;; |
||||
|
||||
esac |
||||
|
||||
# Now exit if we have it, but it failed. Also exit now if we |
||||
# don't have it and --version was passed (most likely to detect |
||||
# the program). |
||||
case $1 in |
||||
lex|yacc) |
||||
# Not GNU programs, they don't have --version. |
||||
;; |
||||
|
||||
tar) |
||||
if test -n "$run"; then |
||||
echo 1>&2 "ERROR: \`tar' requires --run" |
||||
exit 1 |
||||
elif test "x$2" = "x--version" || test "x$2" = "x--help"; then |
||||
exit 1 |
||||
fi |
||||
;; |
||||
|
||||
*) |
||||
if test -z "$run" && ($1 --version) > /dev/null 2>&1; then |
||||
# We have it, but it failed. |
||||
exit 1 |
||||
elif test "x$2" = "x--version" || test "x$2" = "x--help"; then |
||||
# Could not run --version or --help. This is probably someone |
||||
# running `$TOOL --version' or `$TOOL --help' to check whether |
||||
# $TOOL exists and not knowing $TOOL uses missing. |
||||
exit 1 |
||||
fi |
||||
;; |
||||
esac |
||||
|
||||
# If it does not exist, or fails to run (possibly an outdated version), |
||||
# try to emulate it. |
||||
case $1 in |
||||
aclocal*) |
||||
echo 1>&2 "\ |
||||
WARNING: \`$1' is $msg. You should only need it if |
||||
you modified \`acinclude.m4' or \`${configure_ac}'. You might want |
||||
to install the \`Automake' and \`Perl' packages. Grab them from |
||||
any GNU archive site." |
||||
touch aclocal.m4 |
||||
;; |
||||
|
||||
autoconf) |
||||
echo 1>&2 "\ |
||||
WARNING: \`$1' is $msg. You should only need it if |
||||
you modified \`${configure_ac}'. You might want to install the |
||||
\`Autoconf' and \`GNU m4' packages. Grab them from any GNU |
||||
archive site." |
||||
touch configure |
||||
;; |
||||
|
||||
autoheader) |
||||
echo 1>&2 "\ |
||||
WARNING: \`$1' is $msg. You should only need it if |
||||
you modified \`acconfig.h' or \`${configure_ac}'. You might want |
||||
to install the \`Autoconf' and \`GNU m4' packages. Grab them |
||||
from any GNU archive site." |
||||
files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}` |
||||
test -z "$files" && files="config.h" |
||||
touch_files= |
||||
for f in $files; do |
||||
case $f in |
||||
*:*) touch_files="$touch_files "`echo "$f" | |
||||
sed -e 's/^[^:]*://' -e 's/:.*//'`;; |
||||
*) touch_files="$touch_files $f.in";; |
||||
esac |
||||
done |
||||
touch $touch_files |
||||
;; |
||||
|
||||
automake*) |
||||
echo 1>&2 "\ |
||||
WARNING: \`$1' is $msg. You should only need it if |
||||
you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'. |
||||
You might want to install the \`Automake' and \`Perl' packages. |
||||
Grab them from any GNU archive site." |
||||
find . -type f -name Makefile.am -print | |
||||
sed 's/\.am$/.in/' | |
||||
while read f; do touch "$f"; done |
||||
;; |
||||
|
||||
autom4te) |
||||
echo 1>&2 "\ |
||||
WARNING: \`$1' is needed, but is $msg. |
||||
You might have modified some files without having the |
||||
proper tools for further handling them. |
||||
You can get \`$1' as part of \`Autoconf' from any GNU |
||||
archive site." |
||||
|
||||
file=`echo "$*" | sed -n "$sed_output"` |
||||
test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"` |
||||
if test -f "$file"; then |
||||
touch $file |
||||
else |
||||
test -z "$file" || exec >$file |
||||
echo "#! /bin/sh" |
||||
echo "# Created by GNU Automake missing as a replacement of" |
||||
echo "# $ $@" |
||||
echo "exit 0" |
||||
chmod +x $file |
||||
exit 1 |
||||
fi |
||||
;; |
||||
|
||||
bison|yacc) |
||||
echo 1>&2 "\ |
||||
WARNING: \`$1' $msg. You should only need it if |
||||
you modified a \`.y' file. You may need the \`Bison' package |
||||
in order for those modifications to take effect. You can get |
||||
\`Bison' from any GNU archive site." |
||||
rm -f y.tab.c y.tab.h |
||||
if test $# -ne 1; then |
||||
eval LASTARG="\${$#}" |
||||
case $LASTARG in |
||||
*.y) |
||||
SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'` |
||||
if test -f "$SRCFILE"; then |
||||
cp "$SRCFILE" y.tab.c |
||||
fi |
||||
SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'` |
||||
if test -f "$SRCFILE"; then |
||||
cp "$SRCFILE" y.tab.h |
||||
fi |
||||
;; |
||||
esac |
||||
fi |
||||
if test ! -f y.tab.h; then |
||||
echo >y.tab.h |
||||
fi |
||||
if test ! -f y.tab.c; then |
||||
echo 'main() { return 0; }' >y.tab.c |
||||
fi |
||||
;; |
||||
|
||||
lex|flex) |
||||
echo 1>&2 "\ |
||||
WARNING: \`$1' is $msg. You should only need it if |
||||
you modified a \`.l' file. You may need the \`Flex' package |
||||
in order for those modifications to take effect. You can get |
||||
\`Flex' from any GNU archive site." |
||||
rm -f lex.yy.c |
||||
if test $# -ne 1; then |
||||
eval LASTARG="\${$#}" |
||||
case $LASTARG in |
||||
*.l) |
||||
SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'` |
||||
if test -f "$SRCFILE"; then |
||||
cp "$SRCFILE" lex.yy.c |
||||
fi |
||||
;; |
||||
esac |
||||
fi |
||||
if test ! -f lex.yy.c; then |
||||
echo 'main() { return 0; }' >lex.yy.c |
||||
fi |
||||
;; |
||||
|
||||
help2man) |
||||
echo 1>&2 "\ |
||||
WARNING: \`$1' is $msg. You should only need it if |
||||
you modified a dependency of a manual page. You may need the |
||||
\`Help2man' package in order for those modifications to take |
||||
effect. You can get \`Help2man' from any GNU archive site." |
||||
|
||||
file=`echo "$*" | sed -n "$sed_output"` |
||||
test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"` |
||||
if test -f "$file"; then |
||||
touch $file |
||||
else |
||||
test -z "$file" || exec >$file |
||||
echo ".ab help2man is required to generate this page" |
||||
exit 1 |
||||
fi |
||||
;; |
||||
|
||||
makeinfo) |
||||
echo 1>&2 "\ |
||||
WARNING: \`$1' is $msg. You should only need it if |
||||
you modified a \`.texi' or \`.texinfo' file, or any other file |
||||
indirectly affecting the aspect of the manual. The spurious |
||||
call might also be the consequence of using a buggy \`make' (AIX, |
||||
DU, IRIX). You might want to install the \`Texinfo' package or |
||||
the \`GNU make' package. Grab either from any GNU archive site." |
||||
# The file to touch is that specified with -o ... |
||||
file=`echo "$*" | sed -n "$sed_output"` |
||||
test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"` |
||||
if test -z "$file"; then |
||||
# ... or it is the one specified with @setfilename ... |
||||
infile=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'` |
||||
file=`sed -n ' |
||||
/^@setfilename/{ |
||||
s/.* \([^ ]*\) *$/\1/ |
||||
p |
||||
q |
||||
}' $infile` |
||||
# ... or it is derived from the source name (dir/f.texi becomes f.info) |
||||
test -z "$file" && file=`echo "$infile" | sed 's,.*/,,;s,.[^.]*$,,'`.info |
||||
fi |
||||
# If the file does not exist, the user really needs makeinfo; |
||||
# let's fail without touching anything. |
||||
test -f $file || exit 1 |
||||
touch $file |
||||
;; |
||||
|
||||
tar) |
||||
shift |
||||
|
||||
# We have already tried tar in the generic part. |
||||
# Look for gnutar/gtar before invocation to avoid ugly error |
||||
# messages. |
||||
if (gnutar --version > /dev/null 2>&1); then |
||||
gnutar "$@" && exit 0 |
||||
fi |
||||
if (gtar --version > /dev/null 2>&1); then |
||||
gtar "$@" && exit 0 |
||||
fi |
||||
firstarg="$1" |
||||
if shift; then |
||||
case $firstarg in |
||||
*o*) |
||||
firstarg=`echo "$firstarg" | sed s/o//` |
||||
tar "$firstarg" "$@" && exit 0 |
||||
;; |
||||
esac |
||||
case $firstarg in |
||||
*h*) |
||||
firstarg=`echo "$firstarg" | sed s/h//` |
||||
tar "$firstarg" "$@" && exit 0 |
||||
;; |
||||
esac |
||||
fi |
||||
|
||||
echo 1>&2 "\ |
||||
WARNING: I can't seem to be able to run \`tar' with the given arguments. |
||||
You may want to install GNU tar or Free paxutils, or check the |
||||
command line arguments." |
||||
exit 1 |
||||
;; |
||||
|
||||
*) |
||||
echo 1>&2 "\ |
||||
WARNING: \`$1' is needed, and is $msg. |
||||
You might have modified some files without having the |
||||
proper tools for further handling them. Check the \`README' file, |
||||
it often tells you about the needed prerequisites for installing |
||||
this package. You may also peek at any GNU archive site, in case |
||||
some other package would contain this missing \`$1' program." |
||||
exit 1 |
||||
;; |
||||
esac |
||||
|
||||
exit 0 |
||||
|
||||
# Local variables: |
||||
# eval: (add-hook 'write-file-hooks 'time-stamp) |
||||
# time-stamp-start: "scriptversion=" |
||||
# time-stamp-format: "%:y-%02m-%02d.%02H" |
||||
# time-stamp-end: "$" |
||||
# End: |
@ -1,5 +0,0 @@ |
||||
# This file is used by gcl to get repository specific information. |
||||
CODE_REVIEW_SERVER: breakpad.appspot.com |
||||
CC_LIST: google-breakpad-dev@googlegroups.com |
||||
TRY_ON_UPLOAD: False |
||||
VIEW_VC: http://code.google.com/p/google-breakpad/source/detail?r= |
File diff suppressed because it is too large
Load Diff
@ -1,150 +0,0 @@ |
||||
# Copyright (c) 2006, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
|
||||
AC_PREREQ(2.57) |
||||
|
||||
AC_INIT(breakpad, 0.1, google-breakpad-dev@googlegroups.com) |
||||
dnl Sanity check: the argument is just a file that should exist. |
||||
AC_CONFIG_SRCDIR(README) |
||||
AC_CONFIG_AUX_DIR(autotools) |
||||
AC_CONFIG_MACRO_DIR([m4]) |
||||
AC_CANONICAL_HOST |
||||
|
||||
AM_INIT_AUTOMAKE(subdir-objects tar-ustar 1.11.1) |
||||
AM_CONFIG_HEADER(src/config.h) |
||||
|
||||
AC_PROG_CC |
||||
AM_PROG_CC_C_O |
||||
AC_PROG_CPP |
||||
AC_PROG_CXX |
||||
AC_PROG_RANLIB |
||||
|
||||
AC_HEADER_STDC |
||||
m4_include(m4/ax_pthread.m4) |
||||
AX_PTHREAD |
||||
AC_CHECK_HEADERS([a.out.h]) |
||||
|
||||
# Only build Linux client libs when compiling for Linux |
||||
case $host in |
||||
*-*-linux* | *-android* ) |
||||
LINUX_HOST=true |
||||
;; |
||||
esac |
||||
AM_CONDITIONAL(LINUX_HOST, test x$LINUX_HOST = xtrue) |
||||
|
||||
AC_ARG_ENABLE(m32, |
||||
AS_HELP_STRING([--enable-m32], |
||||
[Compile/build with -m32] |
||||
[(default is no)]), |
||||
[case "${enableval}" in |
||||
yes) |
||||
CFLAGS="${CFLAGS} -m32" |
||||
CXXFLAGS="${CXXFLAGS} -m32" |
||||
usem32=true |
||||
;; |
||||
no) |
||||
usem32=false |
||||
;; |
||||
*) |
||||
AC_MSG_ERROR(bad value ${enableval} for --enable-m32) |
||||
;; |
||||
esac], |
||||
[usem32=false]) |
||||
|
||||
AC_ARG_ENABLE(processor, |
||||
AS_HELP_STRING([--disable-processor], |
||||
[Don't build processor library] |
||||
[(default is no)]), |
||||
[case "${enableval}" in |
||||
yes) |
||||
disable_processor=false |
||||
;; |
||||
no) |
||||
disable_processor=true |
||||
;; |
||||
*) |
||||
AC_MSG_ERROR(bad value ${enableval} for --disable-processor) |
||||
;; |
||||
esac], |
||||
[disable_processor=false]) |
||||
AM_CONDITIONAL(DISABLE_PROCESSOR, test x$disable_processor = xtrue) |
||||
|
||||
AC_ARG_ENABLE(tools, |
||||
AS_HELP_STRING([--disable-tools], |
||||
[Don't build tool binaries] |
||||
[(default is no)]), |
||||
[case "${enableval}" in |
||||
yes) |
||||
disable_tools=false |
||||
;; |
||||
no) |
||||
disable_tools=true |
||||
;; |
||||
*) |
||||
AC_MSG_ERROR(bad value ${enableval} for --disable-tools) |
||||
;; |
||||
esac], |
||||
[disable_tools=false]) |
||||
AM_CONDITIONAL(DISABLE_TOOLS, test x$disable_tools = xtrue) |
||||
|
||||
if test x$LINUX_HOST = xfalse -a x$disable_processor = xtrue -a x$disable_tools = xtrue; then |
||||
AC_MSG_ERROR([--disable-processor and --disable-tools were specified, and not building for Linux. Nothing to build!]) |
||||
fi |
||||
|
||||
AC_CHECK_MEMBER(struct sockaddr.sa_len, |
||||
[AC_DEFINE([GET_SA_LEN(X)],[(((struct sockaddr*)&(X))->sa_len)], |
||||
[actual length of specific struct sockaddr])], |
||||
[AC_DEFINE([GET_SA_LEN(X)], |
||||
[(((struct sockaddr*)&(X))->sa_family == AF_INET ? sizeof(struct sockaddr_in) : \ |
||||
((struct sockaddr*)&(X))->sa_family == AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr))], |
||||
[actual length of specific struct sockaddr])], |
||||
[#include <sys/socket.h>]) |
||||
|
||||
AC_ARG_ENABLE(selftest, |
||||
AS_HELP_STRING([--enable-selftest], |
||||
[Run extra tests with "make check" ] |
||||
[(may conflict with optimizations) ] |
||||
[(default is no)]), |
||||
[case "${enableval}" in |
||||
yes) |
||||
selftest=true |
||||
;; |
||||
no) |
||||
selftest=false |
||||
;; |
||||
*) |
||||
AC_MSG_ERROR(bad value ${enableval} for --enable-selftest) |
||||
;; |
||||
esac], |
||||
[selftest=false]) |
||||
AM_CONDITIONAL(SELFTEST, test x$selftest = xtrue) |
||||
|
||||
AC_CONFIG_FILES([Makefile]) |
||||
AC_OUTPUT |
@ -1,283 +0,0 @@ |
||||
# =========================================================================== |
||||
# http://www.nongnu.org/autoconf-archive/ax_pthread.html |
||||
# =========================================================================== |
||||
# |
||||
# SYNOPSIS |
||||
# |
||||
# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) |
||||
# |
||||
# DESCRIPTION |
||||
# |
||||
# This macro figures out how to build C programs using POSIX threads. It |
||||
# sets the PTHREAD_LIBS output variable to the threads library and linker |
||||
# flags, and the PTHREAD_CFLAGS output variable to any special C compiler |
||||
# flags that are needed. (The user can also force certain compiler |
||||
# flags/libs to be tested by setting these environment variables.) |
||||
# |
||||
# Also sets PTHREAD_CC to any special C compiler that is needed for |
||||
# multi-threaded programs (defaults to the value of CC otherwise). (This |
||||
# is necessary on AIX to use the special cc_r compiler alias.) |
||||
# |
||||
# NOTE: You are assumed to not only compile your program with these flags, |
||||
# but also link it with them as well. e.g. you should link with |
||||
# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS |
||||
# |
||||
# If you are only building threads programs, you may wish to use these |
||||
# variables in your default LIBS, CFLAGS, and CC: |
||||
# |
||||
# LIBS="$PTHREAD_LIBS $LIBS" |
||||
# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" |
||||
# CC="$PTHREAD_CC" |
||||
# |
||||
# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant |
||||
# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name |
||||
# (e.g. PTHREAD_CREATE_UNDETACHED on AIX). |
||||
# |
||||
# ACTION-IF-FOUND is a list of shell commands to run if a threads library |
||||
# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it |
||||
# is not found. If ACTION-IF-FOUND is not specified, the default action |
||||
# will define HAVE_PTHREAD. |
||||
# |
||||
# Please let the authors know if this macro fails on any platform, or if |
||||
# you have any other suggestions or comments. This macro was based on work |
||||
# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help |
||||
# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by |
||||
# Alejandro Forero Cuervo to the autoconf macro repository. We are also |
||||
# grateful for the helpful feedback of numerous users. |
||||
# |
||||
# LICENSE |
||||
# |
||||
# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu> |
||||
# |
||||
# 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 3 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/>. |
||||
# |
||||
# As a special exception, the respective Autoconf Macro's copyright owner |
||||
# gives unlimited permission to copy, distribute and modify the configure |
||||
# scripts that are the output of Autoconf when processing the Macro. You |
||||
# need not follow the terms of the GNU General Public License when using |
||||
# or distributing such scripts, even though portions of the text of the |
||||
# Macro appear in them. The GNU General Public License (GPL) does govern |
||||
# all other use of the material that constitutes the Autoconf Macro. |
||||
# |
||||
# This special exception to the GPL applies to versions of the Autoconf |
||||
# Macro released by the Autoconf Archive. When you make and distribute a |
||||
# modified version of the Autoconf Macro, you may extend this special |
||||
# exception to the GPL to apply to your modified version as well. |
||||
|
||||
#serial 6 |
||||
|
||||
AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) |
||||
AC_DEFUN([AX_PTHREAD], [ |
||||
AC_REQUIRE([AC_CANONICAL_HOST]) |
||||
AC_LANG_SAVE |
||||
AC_LANG_C |
||||
ax_pthread_ok=no |
||||
|
||||
# We used to check for pthread.h first, but this fails if pthread.h |
||||
# requires special compiler flags (e.g. on True64 or Sequent). |
||||
# It gets checked for in the link test anyway. |
||||
|
||||
# First of all, check if the user has set any of the PTHREAD_LIBS, |
||||
# etcetera environment variables, and if threads linking works using |
||||
# them: |
||||
if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then |
||||
save_CFLAGS="$CFLAGS" |
||||
CFLAGS="$CFLAGS $PTHREAD_CFLAGS" |
||||
save_LIBS="$LIBS" |
||||
LIBS="$PTHREAD_LIBS $LIBS" |
||||
AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) |
||||
AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes) |
||||
AC_MSG_RESULT($ax_pthread_ok) |
||||
if test x"$ax_pthread_ok" = xno; then |
||||
PTHREAD_LIBS="" |
||||
PTHREAD_CFLAGS="" |
||||
fi |
||||
LIBS="$save_LIBS" |
||||
CFLAGS="$save_CFLAGS" |
||||
fi |
||||
|
||||
# We must check for the threads library under a number of different |
||||
# names; the ordering is very important because some systems |
||||
# (e.g. DEC) have both -lpthread and -lpthreads, where one of the |
||||
# libraries is broken (non-POSIX). |
||||
|
||||
# Create a list of thread flags to try. Items starting with a "-" are |
||||
# C compiler flags, and other items are library names, except for "none" |
||||
# which indicates that we try without any flags at all, and "pthread-config" |
||||
# which is a program returning the flags for the Pth emulation library. |
||||
|
||||
ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" |
||||
|
||||
# The ordering *is* (sometimes) important. Some notes on the |
||||
# individual items follow: |
||||
|
||||
# pthreads: AIX (must check this before -lpthread) |
||||
# none: in case threads are in libc; should be tried before -Kthread and |
||||
# other compiler flags to prevent continual compiler warnings |
||||
# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) |
||||
# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) |
||||
# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) |
||||
# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) |
||||
# -pthreads: Solaris/gcc |
||||
# -mthreads: Mingw32/gcc, Lynx/gcc |
||||
# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it |
||||
# doesn't hurt to check since this sometimes defines pthreads too; |
||||
# also defines -D_REENTRANT) |
||||
# ... -mt is also the pthreads flag for HP/aCC |
||||
# pthread: Linux, etcetera |
||||
# --thread-safe: KAI C++ |
||||
# pthread-config: use pthread-config program (for GNU Pth library) |
||||
|
||||
case "${host_cpu}-${host_os}" in |
||||
*solaris*) |
||||
|
||||
# On Solaris (at least, for some versions), libc contains stubbed |
||||
# (non-functional) versions of the pthreads routines, so link-based |
||||
# tests will erroneously succeed. (We need to link with -pthreads/-mt/ |
||||
# -lpthread.) (The stubs are missing pthread_cleanup_push, or rather |
||||
# a function called by this macro, so we could check for that, but |
||||
# who knows whether they'll stub that too in a future libc.) So, |
||||
# we'll just look for -pthreads and -lpthread first: |
||||
|
||||
ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" |
||||
;; |
||||
|
||||
*-darwin*) |
||||
acx_pthread_flags="-pthread $acx_pthread_flags" |
||||
;; |
||||
esac |
||||
|
||||
if test x"$ax_pthread_ok" = xno; then |
||||
for flag in $ax_pthread_flags; do |
||||
|
||||
case $flag in |
||||
none) |
||||
AC_MSG_CHECKING([whether pthreads work without any flags]) |
||||
;; |
||||
|
||||
-*) |
||||
AC_MSG_CHECKING([whether pthreads work with $flag]) |
||||
PTHREAD_CFLAGS="$flag" |
||||
;; |
||||
|
||||
pthread-config) |
||||
AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no) |
||||
if test x"$ax_pthread_config" = xno; then continue; fi |
||||
PTHREAD_CFLAGS="`pthread-config --cflags`" |
||||
PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" |
||||
;; |
||||
|
||||
*) |
||||
AC_MSG_CHECKING([for the pthreads library -l$flag]) |
||||
PTHREAD_LIBS="-l$flag" |
||||
;; |
||||
esac |
||||
|
||||
save_LIBS="$LIBS" |
||||
save_CFLAGS="$CFLAGS" |
||||
LIBS="$PTHREAD_LIBS $LIBS" |
||||
CFLAGS="$CFLAGS $PTHREAD_CFLAGS" |
||||
|
||||
# Check for various functions. We must include pthread.h, |
||||
# since some functions may be macros. (On the Sequent, we |
||||
# need a special flag -Kthread to make this header compile.) |
||||
# We check for pthread_join because it is in -lpthread on IRIX |
||||
# while pthread_create is in libc. We check for pthread_attr_init |
||||
# due to DEC craziness with -lpthreads. We check for |
||||
# pthread_cleanup_push because it is one of the few pthread |
||||
# functions on Solaris that doesn't have a non-functional libc stub. |
||||
# We try pthread_create on general principles. |
||||
AC_TRY_LINK([#include <pthread.h> |
||||
static void routine(void* a) {a=0;} |
||||
static void* start_routine(void* a) {return a;}], |
||||
[pthread_t th; pthread_attr_t attr; |
||||
pthread_join(th, 0); |
||||
pthread_attr_init(&attr); |
||||
pthread_cleanup_push(routine, 0); |
||||
pthread_create(&th,0,start_routine,0); |
||||
pthread_cleanup_pop(0); ], |
||||
[ax_pthread_ok=yes]) |
||||
|
||||
LIBS="$save_LIBS" |
||||
CFLAGS="$save_CFLAGS" |
||||
|
||||
AC_MSG_RESULT($ax_pthread_ok) |
||||
if test "x$ax_pthread_ok" = xyes; then |
||||
break; |
||||
fi |
||||
|
||||
PTHREAD_LIBS="" |
||||
PTHREAD_CFLAGS="" |
||||
done |
||||
fi |
||||
|
||||
# Various other checks: |
||||
if test "x$ax_pthread_ok" = xyes; then |
||||
save_LIBS="$LIBS" |
||||
LIBS="$PTHREAD_LIBS $LIBS" |
||||
save_CFLAGS="$CFLAGS" |
||||
CFLAGS="$CFLAGS $PTHREAD_CFLAGS" |
||||
|
||||
# Detect AIX lossage: JOINABLE attribute is called UNDETACHED. |
||||
AC_MSG_CHECKING([for joinable pthread attribute]) |
||||
attr_name=unknown |
||||
for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do |
||||
AC_TRY_LINK([#include <pthread.h>], [int attr=$attr; return attr;], |
||||
[attr_name=$attr; break]) |
||||
done |
||||
AC_MSG_RESULT($attr_name) |
||||
if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then |
||||
AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, |
||||
[Define to necessary symbol if this constant |
||||
uses a non-standard name on your system.]) |
||||
fi |
||||
|
||||
AC_MSG_CHECKING([if more special flags are required for pthreads]) |
||||
flag=no |
||||
case "${host_cpu}-${host_os}" in |
||||
*-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";; |
||||
*solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";; |
||||
esac |
||||
AC_MSG_RESULT(${flag}) |
||||
if test "x$flag" != xno; then |
||||
PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" |
||||
fi |
||||
|
||||
LIBS="$save_LIBS" |
||||
CFLAGS="$save_CFLAGS" |
||||
|
||||
# More AIX lossage: must compile with xlc_r or cc_r |
||||
if test x"$GCC" != xyes; then |
||||
AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC}) |
||||
else |
||||
PTHREAD_CC=$CC |
||||
fi |
||||
else |
||||
PTHREAD_CC="$CC" |
||||
fi |
||||
|
||||
AC_SUBST(PTHREAD_LIBS) |
||||
AC_SUBST(PTHREAD_CFLAGS) |
||||
AC_SUBST(PTHREAD_CC) |
||||
|
||||
# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: |
||||
if test x"$ax_pthread_ok" = xyes; then |
||||
ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) |
||||
: |
||||
else |
||||
ax_pthread_ok=no |
||||
$2 |
||||
fi |
||||
AC_LANG_RESTORE |
||||
])dnl AX_PTHREAD |
File diff suppressed because it is too large
Load Diff
@ -1,368 +0,0 @@ |
||||
# Helper functions for option handling. -*- Autoconf -*- |
||||
# |
||||
# Copyright (C) 2004, 2005, 2007, 2008 Free Software Foundation, Inc. |
||||
# Written by Gary V. Vaughan, 2004 |
||||
# |
||||
# This file is free software; the Free Software Foundation gives |
||||
# unlimited permission to copy and/or distribute it, with or without |
||||
# modifications, as long as this notice is preserved. |
||||
|
||||
# serial 6 ltoptions.m4 |
||||
|
||||
# This is to help aclocal find these macros, as it can't see m4_define. |
||||
AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])]) |
||||
|
||||
|
||||
# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME) |
||||
# ------------------------------------------ |
||||
m4_define([_LT_MANGLE_OPTION], |
||||
[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])]) |
||||
|
||||
|
||||
# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME) |
||||
# --------------------------------------- |
||||
# Set option OPTION-NAME for macro MACRO-NAME, and if there is a |
||||
# matching handler defined, dispatch to it. Other OPTION-NAMEs are |
||||
# saved as a flag. |
||||
m4_define([_LT_SET_OPTION], |
||||
[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl |
||||
m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]), |
||||
_LT_MANGLE_DEFUN([$1], [$2]), |
||||
[m4_warning([Unknown $1 option `$2'])])[]dnl |
||||
]) |
||||
|
||||
|
||||
# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET]) |
||||
# ------------------------------------------------------------ |
||||
# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. |
||||
m4_define([_LT_IF_OPTION], |
||||
[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])]) |
||||
|
||||
|
||||
# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET) |
||||
# ------------------------------------------------------- |
||||
# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME |
||||
# are set. |
||||
m4_define([_LT_UNLESS_OPTIONS], |
||||
[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), |
||||
[m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option), |
||||
[m4_define([$0_found])])])[]dnl |
||||
m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3 |
||||
])[]dnl |
||||
]) |
||||
|
||||
|
||||
# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST) |
||||
# ---------------------------------------- |
||||
# OPTION-LIST is a space-separated list of Libtool options associated |
||||
# with MACRO-NAME. If any OPTION has a matching handler declared with |
||||
# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about |
||||
# the unknown option and exit. |
||||
m4_defun([_LT_SET_OPTIONS], |
||||
[# Set options |
||||
m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), |
||||
[_LT_SET_OPTION([$1], _LT_Option)]) |
||||
|
||||
m4_if([$1],[LT_INIT],[ |
||||
dnl |
||||
dnl Simply set some default values (i.e off) if boolean options were not |
||||
dnl specified: |
||||
_LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no |
||||
]) |
||||
_LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no |
||||
]) |
||||
dnl |
||||
dnl If no reference was made to various pairs of opposing options, then |
||||
dnl we run the default mode handler for the pair. For example, if neither |
||||
dnl `shared' nor `disable-shared' was passed, we enable building of shared |
||||
dnl archives by default: |
||||
_LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED]) |
||||
_LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC]) |
||||
_LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC]) |
||||
_LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install], |
||||
[_LT_ENABLE_FAST_INSTALL]) |
||||
]) |
||||
])# _LT_SET_OPTIONS |
||||
|
||||
|
||||
## --------------------------------- ## |
||||
## Macros to handle LT_INIT options. ## |
||||
## --------------------------------- ## |
||||
|
||||
# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME) |
||||
# ----------------------------------------- |
||||
m4_define([_LT_MANGLE_DEFUN], |
||||
[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])]) |
||||
|
||||
|
||||
# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE) |
||||
# ----------------------------------------------- |
||||
m4_define([LT_OPTION_DEFINE], |
||||
[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl |
||||
])# LT_OPTION_DEFINE |
||||
|
||||
|
||||
# dlopen |
||||
# ------ |
||||
LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes |
||||
]) |
||||
|
||||
AU_DEFUN([AC_LIBTOOL_DLOPEN], |
||||
[_LT_SET_OPTION([LT_INIT], [dlopen]) |
||||
AC_DIAGNOSE([obsolete], |
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you |
||||
put the `dlopen' option into LT_INIT's first parameter.]) |
||||
]) |
||||
|
||||
dnl aclocal-1.4 backwards compatibility: |
||||
dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], []) |
||||
|
||||
|
||||
# win32-dll |
||||
# --------- |
||||
# Declare package support for building win32 dll's. |
||||
LT_OPTION_DEFINE([LT_INIT], [win32-dll], |
||||
[enable_win32_dll=yes |
||||
|
||||
case $host in |
||||
*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-cegcc*) |
||||
AC_CHECK_TOOL(AS, as, false) |
||||
AC_CHECK_TOOL(DLLTOOL, dlltool, false) |
||||
AC_CHECK_TOOL(OBJDUMP, objdump, false) |
||||
;; |
||||
esac |
||||
|
||||
test -z "$AS" && AS=as |
||||
_LT_DECL([], [AS], [0], [Assembler program])dnl |
||||
|
||||
test -z "$DLLTOOL" && DLLTOOL=dlltool |
||||
_LT_DECL([], [DLLTOOL], [0], [DLL creation program])dnl |
||||
|
||||
test -z "$OBJDUMP" && OBJDUMP=objdump |
||||
_LT_DECL([], [OBJDUMP], [0], [Object dumper program])dnl |
||||
])# win32-dll |
||||
|
||||
AU_DEFUN([AC_LIBTOOL_WIN32_DLL], |
||||
[AC_REQUIRE([AC_CANONICAL_HOST])dnl |
||||
_LT_SET_OPTION([LT_INIT], [win32-dll]) |
||||
AC_DIAGNOSE([obsolete], |
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you |
||||
put the `win32-dll' option into LT_INIT's first parameter.]) |
||||
]) |
||||
|
||||
dnl aclocal-1.4 backwards compatibility: |
||||
dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], []) |
||||
|
||||
|
||||
# _LT_ENABLE_SHARED([DEFAULT]) |
||||
# ---------------------------- |
||||
# implement the --enable-shared flag, and supports the `shared' and |
||||
# `disable-shared' LT_INIT options. |
||||
# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. |
||||
m4_define([_LT_ENABLE_SHARED], |
||||
[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl |
||||
AC_ARG_ENABLE([shared], |
||||
[AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@], |
||||
[build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])], |
||||
[p=${PACKAGE-default} |
||||
case $enableval in |
||||
yes) enable_shared=yes ;; |
||||
no) enable_shared=no ;; |
||||
*) |
||||
enable_shared=no |
||||
# Look at the argument we got. We use all the common list separators. |
||||
lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," |
||||
for pkg in $enableval; do |
||||
IFS="$lt_save_ifs" |
||||
if test "X$pkg" = "X$p"; then |
||||
enable_shared=yes |
||||
fi |
||||
done |
||||
IFS="$lt_save_ifs" |
||||
;; |
||||
esac], |
||||
[enable_shared=]_LT_ENABLE_SHARED_DEFAULT) |
||||
|
||||
_LT_DECL([build_libtool_libs], [enable_shared], [0], |
||||
[Whether or not to build shared libraries]) |
||||
])# _LT_ENABLE_SHARED |
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])]) |
||||
LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])]) |
||||
|
||||
# Old names: |
||||
AC_DEFUN([AC_ENABLE_SHARED], |
||||
[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared]) |
||||
]) |
||||
|
||||
AC_DEFUN([AC_DISABLE_SHARED], |
||||
[_LT_SET_OPTION([LT_INIT], [disable-shared]) |
||||
]) |
||||
|
||||
AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) |
||||
AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) |
||||
|
||||
dnl aclocal-1.4 backwards compatibility: |
||||
dnl AC_DEFUN([AM_ENABLE_SHARED], []) |
||||
dnl AC_DEFUN([AM_DISABLE_SHARED], []) |
||||
|
||||
|
||||
|
||||
# _LT_ENABLE_STATIC([DEFAULT]) |
||||
# ---------------------------- |
||||
# implement the --enable-static flag, and support the `static' and |
||||
# `disable-static' LT_INIT options. |
||||
# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. |
||||
m4_define([_LT_ENABLE_STATIC], |
||||
[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl |
||||
AC_ARG_ENABLE([static], |
||||
[AS_HELP_STRING([--enable-static@<:@=PKGS@:>@], |
||||
[build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])], |
||||
[p=${PACKAGE-default} |
||||
case $enableval in |
||||
yes) enable_static=yes ;; |
||||
no) enable_static=no ;; |
||||
*) |
||||
enable_static=no |
||||
# Look at the argument we got. We use all the common list separators. |
||||
lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," |
||||
for pkg in $enableval; do |
||||
IFS="$lt_save_ifs" |
||||
if test "X$pkg" = "X$p"; then |
||||
enable_static=yes |
||||
fi |
||||
done |
||||
IFS="$lt_save_ifs" |
||||
;; |
||||
esac], |
||||
[enable_static=]_LT_ENABLE_STATIC_DEFAULT) |
||||
|
||||
_LT_DECL([build_old_libs], [enable_static], [0], |
||||
[Whether or not to build static libraries]) |
||||
])# _LT_ENABLE_STATIC |
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])]) |
||||
LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])]) |
||||
|
||||
# Old names: |
||||
AC_DEFUN([AC_ENABLE_STATIC], |
||||
[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static]) |
||||
]) |
||||
|
||||
AC_DEFUN([AC_DISABLE_STATIC], |
||||
[_LT_SET_OPTION([LT_INIT], [disable-static]) |
||||
]) |
||||
|
||||
AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) |
||||
AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) |
||||
|
||||
dnl aclocal-1.4 backwards compatibility: |
||||
dnl AC_DEFUN([AM_ENABLE_STATIC], []) |
||||
dnl AC_DEFUN([AM_DISABLE_STATIC], []) |
||||
|
||||
|
||||
|
||||
# _LT_ENABLE_FAST_INSTALL([DEFAULT]) |
||||
# ---------------------------------- |
||||
# implement the --enable-fast-install flag, and support the `fast-install' |
||||
# and `disable-fast-install' LT_INIT options. |
||||
# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. |
||||
m4_define([_LT_ENABLE_FAST_INSTALL], |
||||
[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl |
||||
AC_ARG_ENABLE([fast-install], |
||||
[AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], |
||||
[optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], |
||||
[p=${PACKAGE-default} |
||||
case $enableval in |
||||
yes) enable_fast_install=yes ;; |
||||
no) enable_fast_install=no ;; |
||||
*) |
||||
enable_fast_install=no |
||||
# Look at the argument we got. We use all the common list separators. |
||||
lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," |
||||
for pkg in $enableval; do |
||||
IFS="$lt_save_ifs" |
||||
if test "X$pkg" = "X$p"; then |
||||
enable_fast_install=yes |
||||
fi |
||||
done |
||||
IFS="$lt_save_ifs" |
||||
;; |
||||
esac], |
||||
[enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT) |
||||
|
||||
_LT_DECL([fast_install], [enable_fast_install], [0], |
||||
[Whether or not to optimize for fast installation])dnl |
||||
])# _LT_ENABLE_FAST_INSTALL |
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])]) |
||||
LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])]) |
||||
|
||||
# Old names: |
||||
AU_DEFUN([AC_ENABLE_FAST_INSTALL], |
||||
[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install]) |
||||
AC_DIAGNOSE([obsolete], |
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you put |
||||
the `fast-install' option into LT_INIT's first parameter.]) |
||||
]) |
||||
|
||||
AU_DEFUN([AC_DISABLE_FAST_INSTALL], |
||||
[_LT_SET_OPTION([LT_INIT], [disable-fast-install]) |
||||
AC_DIAGNOSE([obsolete], |
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you put |
||||
the `disable-fast-install' option into LT_INIT's first parameter.]) |
||||
]) |
||||
|
||||
dnl aclocal-1.4 backwards compatibility: |
||||
dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], []) |
||||
dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], []) |
||||
|
||||
|
||||
# _LT_WITH_PIC([MODE]) |
||||
# -------------------- |
||||
# implement the --with-pic flag, and support the `pic-only' and `no-pic' |
||||
# LT_INIT options. |
||||
# MODE is either `yes' or `no'. If omitted, it defaults to `both'. |
||||
m4_define([_LT_WITH_PIC], |
||||
[AC_ARG_WITH([pic], |
||||
[AS_HELP_STRING([--with-pic], |
||||
[try to use only PIC/non-PIC objects @<:@default=use both@:>@])], |
||||
[pic_mode="$withval"], |
||||
[pic_mode=default]) |
||||
|
||||
test -z "$pic_mode" && pic_mode=m4_default([$1], [default]) |
||||
|
||||
_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl |
||||
])# _LT_WITH_PIC |
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])]) |
||||
LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])]) |
||||
|
||||
# Old name: |
||||
AU_DEFUN([AC_LIBTOOL_PICMODE], |
||||
[_LT_SET_OPTION([LT_INIT], [pic-only]) |
||||
AC_DIAGNOSE([obsolete], |
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you |
||||
put the `pic-only' option into LT_INIT's first parameter.]) |
||||
]) |
||||
|
||||
dnl aclocal-1.4 backwards compatibility: |
||||
dnl AC_DEFUN([AC_LIBTOOL_PICMODE], []) |
||||
|
||||
## ----------------- ## |
||||
## LTDL_INIT Options ## |
||||
## ----------------- ## |
||||
|
||||
m4_define([_LTDL_MODE], []) |
||||
LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive], |
||||
[m4_define([_LTDL_MODE], [nonrecursive])]) |
||||
LT_OPTION_DEFINE([LTDL_INIT], [recursive], |
||||
[m4_define([_LTDL_MODE], [recursive])]) |
||||
LT_OPTION_DEFINE([LTDL_INIT], [subproject], |
||||
[m4_define([_LTDL_MODE], [subproject])]) |
||||
|
||||
m4_define([_LTDL_TYPE], []) |
||||
LT_OPTION_DEFINE([LTDL_INIT], [installable], |
||||
[m4_define([_LTDL_TYPE], [installable])]) |
||||
LT_OPTION_DEFINE([LTDL_INIT], [convenience], |
||||
[m4_define([_LTDL_TYPE], [convenience])]) |
@ -1,123 +0,0 @@ |
||||
# ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*- |
||||
# |
||||
# Copyright (C) 2004, 2005, 2007, 2008 Free Software Foundation, Inc. |
||||
# Written by Gary V. Vaughan, 2004 |
||||
# |
||||
# This file is free software; the Free Software Foundation gives |
||||
# unlimited permission to copy and/or distribute it, with or without |
||||
# modifications, as long as this notice is preserved. |
||||
|
||||
# serial 6 ltsugar.m4 |
||||
|
||||
# This is to help aclocal find these macros, as it can't see m4_define. |
||||
AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])]) |
||||
|
||||
|
||||
# lt_join(SEP, ARG1, [ARG2...]) |
||||
# ----------------------------- |
||||
# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their |
||||
# associated separator. |
||||
# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier |
||||
# versions in m4sugar had bugs. |
||||
m4_define([lt_join], |
||||
[m4_if([$#], [1], [], |
||||
[$#], [2], [[$2]], |
||||
[m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])]) |
||||
m4_define([_lt_join], |
||||
[m4_if([$#$2], [2], [], |
||||
[m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])]) |
||||
|
||||
|
||||
# lt_car(LIST) |
||||
# lt_cdr(LIST) |
||||
# ------------ |
||||
# Manipulate m4 lists. |
||||
# These macros are necessary as long as will still need to support |
||||
# Autoconf-2.59 which quotes differently. |
||||
m4_define([lt_car], [[$1]]) |
||||
m4_define([lt_cdr], |
||||
[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])], |
||||
[$#], 1, [], |
||||
[m4_dquote(m4_shift($@))])]) |
||||
m4_define([lt_unquote], $1) |
||||
|
||||
|
||||
# lt_append(MACRO-NAME, STRING, [SEPARATOR]) |
||||
# ------------------------------------------ |
||||
# Redefine MACRO-NAME to hold its former content plus `SEPARATOR'`STRING'. |
||||
# Note that neither SEPARATOR nor STRING are expanded; they are appended |
||||
# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked). |
||||
# No SEPARATOR is output if MACRO-NAME was previously undefined (different |
||||
# than defined and empty). |
||||
# |
||||
# This macro is needed until we can rely on Autoconf 2.62, since earlier |
||||
# versions of m4sugar mistakenly expanded SEPARATOR but not STRING. |
||||
m4_define([lt_append], |
||||
[m4_define([$1], |
||||
m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])]) |
||||
|
||||
|
||||
|
||||
# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...]) |
||||
# ---------------------------------------------------------- |
||||
# Produce a SEP delimited list of all paired combinations of elements of |
||||
# PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list |
||||
# has the form PREFIXmINFIXSUFFIXn. |
||||
# Needed until we can rely on m4_combine added in Autoconf 2.62. |
||||
m4_define([lt_combine], |
||||
[m4_if(m4_eval([$# > 3]), [1], |
||||
[m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl |
||||
[[m4_foreach([_Lt_prefix], [$2], |
||||
[m4_foreach([_Lt_suffix], |
||||
]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[, |
||||
[_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])]) |
||||
|
||||
|
||||
# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ]) |
||||
# ----------------------------------------------------------------------- |
||||
# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited |
||||
# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ. |
||||
m4_define([lt_if_append_uniq], |
||||
[m4_ifdef([$1], |
||||
[m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1], |
||||
[lt_append([$1], [$2], [$3])$4], |
||||
[$5])], |
||||
[lt_append([$1], [$2], [$3])$4])]) |
||||
|
||||
|
||||
# lt_dict_add(DICT, KEY, VALUE) |
||||
# ----------------------------- |
||||
m4_define([lt_dict_add], |
||||
[m4_define([$1($2)], [$3])]) |
||||
|
||||
|
||||
# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE) |
||||
# -------------------------------------------- |
||||
m4_define([lt_dict_add_subkey], |
||||
[m4_define([$1($2:$3)], [$4])]) |
||||
|
||||
|
||||
# lt_dict_fetch(DICT, KEY, [SUBKEY]) |
||||
# ---------------------------------- |
||||
m4_define([lt_dict_fetch], |
||||
[m4_ifval([$3], |
||||
m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]), |
||||
m4_ifdef([$1($2)], [m4_defn([$1($2)])]))]) |
||||
|
||||
|
||||
# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE]) |
||||
# ----------------------------------------------------------------- |
||||
m4_define([lt_if_dict_fetch], |
||||
[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4], |
||||
[$5], |
||||
[$6])]) |
||||
|
||||
|
||||
# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...]) |
||||
# -------------------------------------------------------------- |
||||
m4_define([lt_dict_filter], |
||||
[m4_if([$5], [], [], |
||||
[lt_join(m4_quote(m4_default([$4], [[, ]])), |
||||
lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]), |
||||
[lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl |
||||
]) |
@ -1,23 +0,0 @@ |
||||
# ltversion.m4 -- version numbers -*- Autoconf -*- |
||||
# |
||||
# Copyright (C) 2004 Free Software Foundation, Inc. |
||||
# Written by Scott James Remnant, 2004 |
||||
# |
||||
# This file is free software; the Free Software Foundation gives |
||||
# unlimited permission to copy and/or distribute it, with or without |
||||
# modifications, as long as this notice is preserved. |
||||
|
||||
# Generated from ltversion.in. |
||||
|
||||
# serial 3017 ltversion.m4 |
||||
# This file is part of GNU Libtool |
||||
|
||||
m4_define([LT_PACKAGE_VERSION], [2.2.6b]) |
||||
m4_define([LT_PACKAGE_REVISION], [1.3017]) |
||||
|
||||
AC_DEFUN([LTVERSION_VERSION], |
||||
[macro_version='2.2.6b' |
||||
macro_revision='1.3017' |
||||
_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?]) |
||||
_LT_DECL(, macro_revision, 0) |
||||
]) |
@ -1,92 +0,0 @@ |
||||
# lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*- |
||||
# |
||||
# Copyright (C) 2004, 2005, 2007 Free Software Foundation, Inc. |
||||
# Written by Scott James Remnant, 2004. |
||||
# |
||||
# This file is free software; the Free Software Foundation gives |
||||
# unlimited permission to copy and/or distribute it, with or without |
||||
# modifications, as long as this notice is preserved. |
||||
|
||||
# serial 4 lt~obsolete.m4 |
||||
|
||||
# These exist entirely to fool aclocal when bootstrapping libtool. |
||||
# |
||||
# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN) |
||||
# which have later been changed to m4_define as they aren't part of the |
||||
# exported API, or moved to Autoconf or Automake where they belong. |
||||
# |
||||
# The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN |
||||
# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us |
||||
# using a macro with the same name in our local m4/libtool.m4 it'll |
||||
# pull the old libtool.m4 in (it doesn't see our shiny new m4_define |
||||
# and doesn't know about Autoconf macros at all.) |
||||
# |
||||
# So we provide this file, which has a silly filename so it's always |
||||
# included after everything else. This provides aclocal with the |
||||
# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything |
||||
# because those macros already exist, or will be overwritten later. |
||||
# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. |
||||
# |
||||
# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here. |
||||
# Yes, that means every name once taken will need to remain here until |
||||
# we give up compatibility with versions before 1.7, at which point |
||||
# we need to keep only those names which we still refer to. |
||||
|
||||
# This is to help aclocal find these macros, as it can't see m4_define. |
||||
AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])]) |
||||
|
||||
m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])]) |
||||
m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])]) |
||||
m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])]) |
||||
m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])]) |
||||
m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])]) |
||||
m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])]) |
||||
m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])]) |
||||
m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])]) |
||||
m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])]) |
||||
m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])]) |
||||
m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])]) |
||||
m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])]) |
||||
m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])]) |
||||
m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])]) |
||||
m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])]) |
||||
m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])]) |
||||
m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])]) |
||||
m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])]) |
||||
m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])]) |
||||
m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])]) |
||||
m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])]) |
||||
m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])]) |
||||
m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])]) |
||||
m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])]) |
||||
m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])]) |
||||
m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])]) |
||||
m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])]) |
||||
m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])]) |
||||
m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])]) |
||||
m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])]) |
||||
m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])]) |
||||
m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])]) |
||||
m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])]) |
||||
m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])]) |
||||
m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])]) |
||||
m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])]) |
||||
m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])]) |
||||
m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])]) |
||||
m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])]) |
||||
m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])]) |
||||
m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])]) |
||||
m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])]) |
||||
m4_ifndef([AC_LIBTOOL_RC], [AC_DEFUN([AC_LIBTOOL_RC])]) |
||||
m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])]) |
||||
m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])]) |
||||
m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])]) |
||||
m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])]) |
||||
m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])]) |
||||
m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])]) |
||||
m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])]) |
||||
m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])]) |
||||
m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])]) |
||||
m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])]) |
||||
m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])]) |
||||
m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])]) |
@ -1,36 +0,0 @@ |
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef BREAKPAD_GOOGLETEST_INCLUDES_H__ |
||||
#define BREAKPAD_GOOGLETEST_INCLUDES_H__ |
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h" |
||||
#include "testing/include/gmock/gmock.h" |
||||
|
||||
#endif // BREAKPAD_GOOGLETEST_INCLUDES_H__
|
@ -1,44 +0,0 @@ |
||||
// Copyright (c) 2010, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// The Android NDK doesn't have link.h. Fortunately, the only thing
|
||||
// that Breakpad uses from it is the ElfW macro, so define it here.
|
||||
|
||||
#ifndef GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_LINK_H_ |
||||
#define GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_LINK_H_ |
||||
|
||||
#include <sys/exec_elf.h> |
||||
|
||||
#ifndef ElfW |
||||
#define ElfW(type) _ElfW (Elf, ELFSIZE, type) |
||||
#define _ElfW(e,w,t) _ElfW_1 (e, w, _##t) |
||||
#define _ElfW_1(e,w,t) e##w##t |
||||
#endif |
||||
|
||||
#endif // GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_LINK_H_
|
@ -1,77 +0,0 @@ |
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Android runs a fairly new Linux kernel, so signal info is there,
|
||||
// but the NDK doesn't have the structs defined, so define
|
||||
// them here.
|
||||
// Adapted from platform-linux.cc in V8
|
||||
|
||||
#ifndef GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_UCONTEXT_H_ |
||||
#define GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_UCONTEXT_H_ |
||||
|
||||
#include <signal.h> |
||||
|
||||
#if !defined(__GLIBC__) && (defined(__arm__) || defined(__thumb__)) |
||||
|
||||
struct sigcontext { |
||||
uint32_t trap_no; |
||||
uint32_t error_code; |
||||
uint32_t oldmask; |
||||
uint32_t arm_r0; |
||||
uint32_t arm_r1; |
||||
uint32_t arm_r2; |
||||
uint32_t arm_r3; |
||||
uint32_t arm_r4; |
||||
uint32_t arm_r5; |
||||
uint32_t arm_r6; |
||||
uint32_t arm_r7; |
||||
uint32_t arm_r8; |
||||
uint32_t arm_r9; |
||||
uint32_t arm_r10; |
||||
uint32_t arm_fp; |
||||
uint32_t arm_ip; |
||||
uint32_t arm_sp; |
||||
uint32_t arm_lr; |
||||
uint32_t arm_pc; |
||||
uint32_t arm_cpsr; |
||||
uint32_t fault_address; |
||||
}; |
||||
typedef uint32_t __sigset_t; |
||||
typedef struct sigcontext mcontext_t; |
||||
typedef struct ucontext { |
||||
uint32_t uc_flags; |
||||
struct ucontext* uc_link; |
||||
stack_t uc_stack; |
||||
mcontext_t uc_mcontext; |
||||
__sigset_t uc_sigmask; |
||||
} ucontext_t; |
||||
|
||||
#endif |
||||
|
||||
#endif // GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_UCONTEXT_H_
|
@ -1,44 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_ |
||||
#define CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_ |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
class CrashGenerationServer; |
||||
|
||||
struct ClientInfo { |
||||
CrashGenerationServer* crash_server_; |
||||
pid_t pid_; |
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif // CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_
|
@ -1,89 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <stdio.h> |
||||
#include <sys/socket.h> |
||||
#include <sys/types.h> |
||||
|
||||
#include <algorithm> |
||||
|
||||
#include "client/linux/crash_generation/crash_generation_client.h" |
||||
#include "common/linux/eintr_wrapper.h" |
||||
#include "common/linux/linux_libc_support.h" |
||||
#include "third_party/lss/linux_syscall_support.h" |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
bool |
||||
CrashGenerationClient::RequestDump(const void* blob, size_t blob_size) |
||||
{ |
||||
int fds[2]; |
||||
sys_socketpair(AF_UNIX, SOCK_STREAM, 0, fds); |
||||
static const unsigned kControlMsgSize = CMSG_SPACE(sizeof(int)); |
||||
|
||||
struct kernel_msghdr msg; |
||||
my_memset(&msg, 0, sizeof(struct kernel_msghdr)); |
||||
struct kernel_iovec iov[1]; |
||||
iov[0].iov_base = const_cast<void*>(blob); |
||||
iov[0].iov_len = blob_size; |
||||
|
||||
msg.msg_iov = iov; |
||||
msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]); |
||||
char cmsg[kControlMsgSize]; |
||||
my_memset(cmsg, 0, kControlMsgSize); |
||||
msg.msg_control = cmsg; |
||||
msg.msg_controllen = sizeof(cmsg); |
||||
|
||||
struct cmsghdr* hdr = CMSG_FIRSTHDR(&msg); |
||||
hdr->cmsg_level = SOL_SOCKET; |
||||
hdr->cmsg_type = SCM_RIGHTS; |
||||
hdr->cmsg_len = CMSG_LEN(sizeof(int)); |
||||
int* p = reinterpret_cast<int*>(CMSG_DATA(hdr)); |
||||
*p = fds[1]; |
||||
|
||||
HANDLE_EINTR(sys_sendmsg(server_fd_, &msg, 0)); |
||||
sys_close(fds[1]); |
||||
|
||||
// wait for an ACK from the server
|
||||
char b; |
||||
HANDLE_EINTR(sys_read(fds[0], &b, 1)); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
//static
|
||||
CrashGenerationClient* |
||||
CrashGenerationClient::TryCreate(int server_fd) |
||||
{ |
||||
if (0 > server_fd) |
||||
return NULL; |
||||
return new CrashGenerationClient(server_fd); |
||||
} |
||||
|
||||
} |
@ -1,69 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ |
||||
#define CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ |
||||
|
||||
#include <stddef.h> |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
class CrashGenerationClient { |
||||
public: |
||||
~CrashGenerationClient() |
||||
{ |
||||
} |
||||
|
||||
// Request the crash server to generate a dump. |blob| is a hack,
|
||||
// see exception_handler.h and minidump_writer.h
|
||||
//
|
||||
// Return true if the dump was successful; false otherwise.
|
||||
bool RequestDump(const void* blob, size_t blob_size); |
||||
|
||||
// Return a new CrashGenerationClient if |server_fd| is valid and
|
||||
// connects to a CrashGenerationServer. Otherwise, return NULL.
|
||||
// The returned CrashGenerationClient* is owned by the caller of
|
||||
// this function.
|
||||
static CrashGenerationClient* TryCreate(int server_fd); |
||||
|
||||
private: |
||||
CrashGenerationClient(int server_fd) : server_fd_(server_fd) |
||||
{ |
||||
} |
||||
|
||||
int server_fd_; |
||||
|
||||
// prevent copy construction and assignment
|
||||
CrashGenerationClient(const CrashGenerationClient&); |
||||
CrashGenerationClient& operator=(const CrashGenerationClient&); |
||||
}; |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
|
@ -1,468 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <assert.h> |
||||
#include <dirent.h> |
||||
#include <fcntl.h> |
||||
#include <limits.h> |
||||
#include <poll.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <sys/socket.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/types.h> |
||||
#include <unistd.h> |
||||
|
||||
#include <vector> |
||||
|
||||
#include "client/linux/crash_generation/crash_generation_server.h" |
||||
#include "client/linux/crash_generation/client_info.h" |
||||
#include "client/linux/handler/exception_handler.h" |
||||
#include "client/linux/minidump_writer/minidump_writer.h" |
||||
#include "common/linux/eintr_wrapper.h" |
||||
#include "common/linux/guid_creator.h" |
||||
|
||||
static const char kCommandQuit = 'x'; |
||||
|
||||
static bool |
||||
GetInodeForFileDescriptor(ino_t* inode_out, int fd) |
||||
{ |
||||
assert(inode_out); |
||||
|
||||
struct stat buf; |
||||
if (fstat(fd, &buf) < 0) |
||||
return false; |
||||
|
||||
if (!S_ISSOCK(buf.st_mode)) |
||||
return false; |
||||
|
||||
*inode_out = buf.st_ino; |
||||
return true; |
||||
} |
||||
|
||||
// expected prefix of the target of the /proc/self/fd/%d link for a socket
|
||||
static const char kSocketLinkPrefix[] = "socket:["; |
||||
|
||||
// Parse a symlink in /proc/pid/fd/$x and return the inode number of the
|
||||
// socket.
|
||||
// inode_out: (output) set to the inode number on success
|
||||
// path: e.g. /proc/1234/fd/5 (must be a UNIX domain socket descriptor)
|
||||
static bool |
||||
GetInodeForProcPath(ino_t* inode_out, const char* path) |
||||
{ |
||||
assert(inode_out); |
||||
assert(path); |
||||
|
||||
char buf[256]; |
||||
const ssize_t n = readlink(path, buf, sizeof(buf) - 1); |
||||
if (n == -1) { |
||||
return false; |
||||
} |
||||
buf[n] = 0; |
||||
|
||||
if (0 != memcmp(kSocketLinkPrefix, buf, sizeof(kSocketLinkPrefix) - 1)) { |
||||
return false; |
||||
} |
||||
|
||||
char* endptr; |
||||
const u_int64_t inode_ul = |
||||
strtoull(buf + sizeof(kSocketLinkPrefix) - 1, &endptr, 10); |
||||
if (*endptr != ']') |
||||
return false; |
||||
|
||||
if (inode_ul == ULLONG_MAX) { |
||||
return false; |
||||
} |
||||
|
||||
*inode_out = inode_ul; |
||||
return true; |
||||
} |
||||
|
||||
static bool |
||||
FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode) |
||||
{ |
||||
assert(pid_out); |
||||
bool already_found = false; |
||||
|
||||
DIR* proc = opendir("/proc"); |
||||
if (!proc) { |
||||
return false; |
||||
} |
||||
|
||||
std::vector<pid_t> pids; |
||||
|
||||
struct dirent* dent; |
||||
while ((dent = readdir(proc))) { |
||||
char* endptr; |
||||
const unsigned long int pid_ul = strtoul(dent->d_name, &endptr, 10); |
||||
if (pid_ul == ULONG_MAX || '\0' != *endptr) |
||||
continue; |
||||
pids.push_back(pid_ul); |
||||
} |
||||
closedir(proc); |
||||
|
||||
for (std::vector<pid_t>::const_iterator |
||||
i = pids.begin(); i != pids.end(); ++i) { |
||||
const pid_t current_pid = *i; |
||||
char buf[256]; |
||||
snprintf(buf, sizeof(buf), "/proc/%d/fd", current_pid); |
||||
DIR* fd = opendir(buf); |
||||
if (!fd) |
||||
continue; |
||||
|
||||
while ((dent = readdir(fd))) { |
||||
if (snprintf(buf, sizeof(buf), "/proc/%d/fd/%s", current_pid, |
||||
dent->d_name) >= static_cast<int>(sizeof(buf))) { |
||||
continue; |
||||
} |
||||
|
||||
ino_t fd_inode; |
||||
if (GetInodeForProcPath(&fd_inode, buf) |
||||
&& fd_inode == socket_inode) { |
||||
if (already_found) { |
||||
closedir(fd); |
||||
return false; |
||||
} |
||||
|
||||
already_found = true; |
||||
*pid_out = current_pid; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
closedir(fd); |
||||
} |
||||
|
||||
return already_found; |
||||
} |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
CrashGenerationServer::CrashGenerationServer( |
||||
const int listen_fd, |
||||
OnClientDumpRequestCallback dump_callback, |
||||
void* dump_context, |
||||
OnClientExitingCallback exit_callback, |
||||
void* exit_context, |
||||
bool generate_dumps, |
||||
const std::string* dump_path) : |
||||
server_fd_(listen_fd), |
||||
dump_callback_(dump_callback), |
||||
dump_context_(dump_context), |
||||
exit_callback_(exit_callback), |
||||
exit_context_(exit_context), |
||||
generate_dumps_(generate_dumps), |
||||
started_(false) |
||||
{ |
||||
if (dump_path) |
||||
dump_dir_ = *dump_path; |
||||
else |
||||
dump_dir_ = "/tmp"; |
||||
} |
||||
|
||||
CrashGenerationServer::~CrashGenerationServer() |
||||
{ |
||||
if (started_) |
||||
Stop(); |
||||
} |
||||
|
||||
bool |
||||
CrashGenerationServer::Start() |
||||
{ |
||||
if (started_ || 0 > server_fd_) |
||||
return false; |
||||
|
||||
int control_pipe[2]; |
||||
if (pipe(control_pipe)) |
||||
return false; |
||||
|
||||
if (fcntl(control_pipe[0], F_SETFD, FD_CLOEXEC)) |
||||
return false; |
||||
if (fcntl(control_pipe[1], F_SETFD, FD_CLOEXEC)) |
||||
return false; |
||||
|
||||
if (fcntl(control_pipe[0], F_SETFL, O_NONBLOCK)) |
||||
return false; |
||||
|
||||
control_pipe_in_ = control_pipe[0]; |
||||
control_pipe_out_ = control_pipe[1]; |
||||
|
||||
if (pthread_create(&thread_, NULL, |
||||
ThreadMain, reinterpret_cast<void*>(this))) |
||||
return false; |
||||
|
||||
started_ = true; |
||||
return true; |
||||
} |
||||
|
||||
void |
||||
CrashGenerationServer::Stop() |
||||
{ |
||||
assert(pthread_self() != thread_); |
||||
|
||||
if (!started_) |
||||
return; |
||||
|
||||
HANDLE_EINTR(write(control_pipe_out_, &kCommandQuit, 1)); |
||||
|
||||
void* dummy; |
||||
pthread_join(thread_, &dummy); |
||||
|
||||
started_ = false; |
||||
} |
||||
|
||||
//static
|
||||
bool |
||||
CrashGenerationServer::CreateReportChannel(int* server_fd, int* client_fd) |
||||
{ |
||||
int fds[2]; |
||||
|
||||
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)) |
||||
return false; |
||||
|
||||
static const int on = 1; |
||||
// Enable passcred on the server end of the socket
|
||||
if (setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) |
||||
return false; |
||||
|
||||
if (fcntl(fds[1], F_SETFL, O_NONBLOCK)) |
||||
return false; |
||||
if (fcntl(fds[1], F_SETFD, FD_CLOEXEC)) |
||||
return false; |
||||
|
||||
*client_fd = fds[0]; |
||||
*server_fd = fds[1]; |
||||
return true; |
||||
} |
||||
|
||||
// The following methods/functions execute on the server thread
|
||||
|
||||
void |
||||
CrashGenerationServer::Run() |
||||
{ |
||||
struct pollfd pollfds[2]; |
||||
memset(&pollfds, 0, sizeof(pollfds)); |
||||
|
||||
pollfds[0].fd = server_fd_; |
||||
pollfds[0].events = POLLIN; |
||||
|
||||
pollfds[1].fd = control_pipe_in_; |
||||
pollfds[1].events = POLLIN; |
||||
|
||||
while (true) { |
||||
// infinite timeout
|
||||
int nevents = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), -1); |
||||
if (-1 == nevents) { |
||||
if (EINTR == errno) { |
||||
continue; |
||||
} else { |
||||
return; |
||||
} |
||||
} |
||||
|
||||
if (pollfds[0].revents && !ClientEvent(pollfds[0].revents)) |
||||
return; |
||||
|
||||
if (pollfds[1].revents && !ControlEvent(pollfds[1].revents)) |
||||
return; |
||||
} |
||||
} |
||||
|
||||
bool |
||||
CrashGenerationServer::ClientEvent(short revents) |
||||
{ |
||||
if (POLLHUP & revents) |
||||
return false; |
||||
assert(POLLIN & revents); |
||||
|
||||
// A process has crashed and has signaled us by writing a datagram
|
||||
// to the death signal socket. The datagram contains the crash context needed
|
||||
// for writing the minidump as well as a file descriptor and a credentials
|
||||
// block so that they can't lie about their pid.
|
||||
|
||||
// The length of the control message:
|
||||
static const unsigned kControlMsgSize = |
||||
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); |
||||
// The length of the regular payload:
|
||||
static const unsigned kCrashContextSize = |
||||
sizeof(google_breakpad::ExceptionHandler::CrashContext); |
||||
|
||||
struct msghdr msg = {0}; |
||||
struct iovec iov[1]; |
||||
char crash_context[kCrashContextSize]; |
||||
char control[kControlMsgSize]; |
||||
const ssize_t expected_msg_size = sizeof(crash_context); |
||||
|
||||
iov[0].iov_base = crash_context; |
||||
iov[0].iov_len = sizeof(crash_context); |
||||
msg.msg_iov = iov; |
||||
msg.msg_iovlen = sizeof(iov)/sizeof(iov[0]); |
||||
msg.msg_control = control; |
||||
msg.msg_controllen = kControlMsgSize; |
||||
|
||||
const ssize_t msg_size = HANDLE_EINTR(recvmsg(server_fd_, &msg, 0)); |
||||
if (msg_size != expected_msg_size) |
||||
return true; |
||||
|
||||
if (msg.msg_controllen != kControlMsgSize || |
||||
msg.msg_flags & ~MSG_TRUNC) |
||||
return true; |
||||
|
||||
// Walk the control payload and extract the file descriptor and validated pid.
|
||||
pid_t crashing_pid = -1; |
||||
int signal_fd = -1; |
||||
for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; |
||||
hdr = CMSG_NXTHDR(&msg, hdr)) { |
||||
if (hdr->cmsg_level != SOL_SOCKET) |
||||
continue; |
||||
if (hdr->cmsg_type == SCM_RIGHTS) { |
||||
const unsigned len = hdr->cmsg_len - |
||||
(((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr); |
||||
assert(len % sizeof(int) == 0u); |
||||
const unsigned num_fds = len / sizeof(int); |
||||
if (num_fds > 1 || num_fds == 0) { |
||||
// A nasty process could try and send us too many descriptors and
|
||||
// force a leak.
|
||||
for (unsigned i = 0; i < num_fds; ++i) |
||||
HANDLE_EINTR(close(reinterpret_cast<int*>(CMSG_DATA(hdr))[i])); |
||||
return true; |
||||
} else { |
||||
signal_fd = reinterpret_cast<int*>(CMSG_DATA(hdr))[0]; |
||||
} |
||||
} else if (hdr->cmsg_type == SCM_CREDENTIALS) { |
||||
const struct ucred *cred = |
||||
reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); |
||||
crashing_pid = cred->pid; |
||||
} |
||||
} |
||||
|
||||
if (crashing_pid == -1 || signal_fd == -1) { |
||||
if (signal_fd) |
||||
HANDLE_EINTR(close(signal_fd)); |
||||
return true; |
||||
} |
||||
|
||||
// Kernel bug workaround (broken in 2.6.30 at least):
|
||||
// The kernel doesn't translate PIDs in SCM_CREDENTIALS across PID
|
||||
// namespaces. Thus |crashing_pid| might be garbage from our point of view.
|
||||
// In the future we can remove this workaround, but we have to wait a couple
|
||||
// of years to be sure that it's worked its way out into the world.
|
||||
|
||||
ino_t inode_number; |
||||
if (!GetInodeForFileDescriptor(&inode_number, signal_fd)) { |
||||
HANDLE_EINTR(close(signal_fd)); |
||||
return true; |
||||
} |
||||
|
||||
if (!FindProcessHoldingSocket(&crashing_pid, inode_number - 1)) { |
||||
HANDLE_EINTR(close(signal_fd)); |
||||
return true; |
||||
} |
||||
|
||||
std::string minidump_filename; |
||||
if (!MakeMinidumpFilename(minidump_filename)) |
||||
return true; |
||||
|
||||
if (!google_breakpad::WriteMinidump(minidump_filename.c_str(), |
||||
crashing_pid, crash_context, |
||||
kCrashContextSize)) { |
||||
HANDLE_EINTR(close(signal_fd)); |
||||
return true; |
||||
} |
||||
|
||||
if (dump_callback_) { |
||||
ClientInfo info; |
||||
|
||||
info.crash_server_ = this; |
||||
info.pid_ = crashing_pid; |
||||
|
||||
dump_callback_(dump_context_, &info, &minidump_filename); |
||||
} |
||||
|
||||
// Send the done signal to the process: it can exit now.
|
||||
memset(&msg, 0, sizeof(msg)); |
||||
struct iovec done_iov; |
||||
done_iov.iov_base = const_cast<char*>("\x42"); |
||||
done_iov.iov_len = 1; |
||||
msg.msg_iov = &done_iov; |
||||
msg.msg_iovlen = 1; |
||||
|
||||
HANDLE_EINTR(sendmsg(signal_fd, &msg, MSG_DONTWAIT | MSG_NOSIGNAL)); |
||||
HANDLE_EINTR(close(signal_fd)); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool |
||||
CrashGenerationServer::ControlEvent(short revents) |
||||
{ |
||||
if (POLLHUP & revents) |
||||
return false; |
||||
assert(POLLIN & revents); |
||||
|
||||
char command; |
||||
if (read(control_pipe_in_, &command, 1)) |
||||
return false; |
||||
|
||||
switch (command) { |
||||
case kCommandQuit: |
||||
return false; |
||||
default: |
||||
assert(0); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool |
||||
CrashGenerationServer::MakeMinidumpFilename(std::string& outFilename) |
||||
{ |
||||
GUID guid; |
||||
char guidString[kGUIDStringLength+1]; |
||||
|
||||
if (!(CreateGUID(&guid) |
||||
&& GUIDToString(&guid, guidString, sizeof(guidString)))) |
||||
return false; |
||||
|
||||
char path[PATH_MAX]; |
||||
snprintf(path, sizeof(path), "%s/%s.dmp", dump_dir_.c_str(), guidString); |
||||
|
||||
outFilename = path; |
||||
return true; |
||||
} |
||||
|
||||
// static
|
||||
void* |
||||
CrashGenerationServer::ThreadMain(void *arg) |
||||
{ |
||||
reinterpret_cast<CrashGenerationServer*>(arg)->Run(); |
||||
return NULL; |
||||
} |
||||
|
||||
} // namespace google_breakpad
|
@ -1,133 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_ |
||||
#define CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_ |
||||
|
||||
#include <pthread.h> |
||||
|
||||
#include <string> |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
class ClientInfo; |
||||
|
||||
class CrashGenerationServer { |
||||
public: |
||||
// WARNING: callbacks may be invoked on a different thread
|
||||
// than that which creates the CrashGenerationServer. They must
|
||||
// be thread safe.
|
||||
typedef void (*OnClientDumpRequestCallback)(void* context, |
||||
const ClientInfo* client_info, |
||||
const std::string* file_path); |
||||
|
||||
typedef void (*OnClientExitingCallback)(void* context, |
||||
const ClientInfo* client_info); |
||||
|
||||
// Create an instance with the given parameters.
|
||||
//
|
||||
// Parameter listen_fd: The server fd created by CreateReportChannel().
|
||||
// Parameter dump_callback: Callback for a client crash dump request.
|
||||
// Parameter dump_context: Context for client crash dump request callback.
|
||||
// Parameter exit_callback: Callback for client process exit.
|
||||
// Parameter exit_context: Context for client exit callback.
|
||||
// Parameter generate_dumps: Whether to automatically generate dumps.
|
||||
// Client code of this class might want to generate dumps explicitly
|
||||
// in the crash dump request callback. In that case, false can be
|
||||
// passed for this parameter.
|
||||
// Parameter dump_path: Path for generating dumps; required only if true is
|
||||
// passed for generateDumps parameter; NULL can be passed otherwise.
|
||||
CrashGenerationServer(const int listen_fd, |
||||
OnClientDumpRequestCallback dump_callback, |
||||
void* dump_context, |
||||
OnClientExitingCallback exit_callback, |
||||
void* exit_context, |
||||
bool generate_dumps, |
||||
const std::string* dump_path); |
||||
|
||||
~CrashGenerationServer(); |
||||
|
||||
// Perform initialization steps needed to start listening to clients.
|
||||
//
|
||||
// Return true if initialization is successful; false otherwise.
|
||||
bool Start(); |
||||
|
||||
// Stop the server.
|
||||
void Stop(); |
||||
|
||||
// Create a "channel" that can be used by clients to report crashes
|
||||
// to a CrashGenerationServer. |*server_fd| should be passed to
|
||||
// this class's constructor, and |*client_fd| should be passed to
|
||||
// the ExceptionHandler constructor in the client process.
|
||||
static bool CreateReportChannel(int* server_fd, int* client_fd); |
||||
|
||||
private: |
||||
// Run the server's event loop
|
||||
void Run(); |
||||
|
||||
// Invoked when an child process (client) event occurs
|
||||
// Returning true => "keep running", false => "exit loop"
|
||||
bool ClientEvent(short revents); |
||||
|
||||
// Invoked when the controlling thread (main) event occurs
|
||||
// Returning true => "keep running", false => "exit loop"
|
||||
bool ControlEvent(short revents); |
||||
|
||||
// Return a unique filename at which a minidump can be written
|
||||
bool MakeMinidumpFilename(std::string& outFilename); |
||||
|
||||
// Trampoline to |Run()|
|
||||
static void* ThreadMain(void* arg); |
||||
|
||||
int server_fd_; |
||||
|
||||
OnClientDumpRequestCallback dump_callback_; |
||||
void* dump_context_; |
||||
|
||||
OnClientExitingCallback exit_callback_; |
||||
void* exit_context_; |
||||
|
||||
bool generate_dumps_; |
||||
|
||||
std::string dump_dir_; |
||||
|
||||
bool started_; |
||||
|
||||
pthread_t thread_; |
||||
int control_pipe_in_; |
||||
int control_pipe_out_; |
||||
|
||||
// disable these
|
||||
CrashGenerationServer(const CrashGenerationServer&); |
||||
CrashGenerationServer& operator=(const CrashGenerationServer&); |
||||
}; |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
|
@ -1,3 +0,0 @@ |
||||
MODULE Linux x86 B8CFDE93002D54DA1900A40AA1BD67690 linux-gate.so |
||||
PUBLIC 400 0 __kernel_vsyscall |
||||
STACK WIN 4 400 100 1 1 0 0 0 0 0 1 |
@ -1,3 +0,0 @@ |
||||
MODULE Linux x86 4FBDA58B5A1DF5A379E3CF19A235EA090 linux-gate.so |
||||
PUBLIC 400 0 __kernel_vsyscall |
||||
STACK WIN 4 400 200 3 3 0 0 0 0 0 1 |
@ -1,512 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// The ExceptionHandler object installs signal handlers for a number of
|
||||
// signals. We rely on the signal handler running on the thread which crashed
|
||||
// in order to identify it. This is true of the synchronous signals (SEGV etc),
|
||||
// but not true of ABRT. Thus, if you send ABRT to yourself in a program which
|
||||
// uses ExceptionHandler, you need to use tgkill to direct it to the current
|
||||
// thread.
|
||||
//
|
||||
// The signal flow looks like this:
|
||||
//
|
||||
// SignalHandler (uses a global stack of ExceptionHandler objects to find
|
||||
// | one to handle the signal. If the first rejects it, try
|
||||
// | the second etc...)
|
||||
// V
|
||||
// HandleSignal ----------------------------| (clones a new process which
|
||||
// | | shares an address space with
|
||||
// (wait for cloned | the crashed process. This
|
||||
// process) | allows us to ptrace the crashed
|
||||
// | | process)
|
||||
// V V
|
||||
// (set signal handler to ThreadEntry (static function to bounce
|
||||
// SIG_DFL and rethrow, | back into the object)
|
||||
// killing the crashed |
|
||||
// process) V
|
||||
// DoDump (writes minidump)
|
||||
// |
|
||||
// V
|
||||
// sys_exit
|
||||
//
|
||||
|
||||
// This code is a little fragmented. Different functions of the ExceptionHandler
|
||||
// class run in a number of different contexts. Some of them run in a normal
|
||||
// context and are easy to code, others run in a compromised context and the
|
||||
// restrictions at the top of minidump_writer.cc apply: no libc and use the
|
||||
// alternative malloc. Each function should have comment above it detailing the
|
||||
// context which it runs in.
|
||||
|
||||
#include "client/linux/handler/exception_handler.h" |
||||
|
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <linux/limits.h> |
||||
#include <sched.h> |
||||
#include <signal.h> |
||||
#include <stdio.h> |
||||
#include <sys/mman.h> |
||||
#include <sys/prctl.h> |
||||
#if !defined(__ANDROID__) |
||||
#include <sys/signal.h> |
||||
#endif |
||||
#include <sys/syscall.h> |
||||
#if !defined(__ANDROID__) |
||||
#include <sys/ucontext.h> |
||||
#include <sys/user.h> |
||||
#endif |
||||
#include <sys/wait.h> |
||||
#if !defined(__ANDROID__) |
||||
#include <ucontext.h> |
||||
#endif |
||||
#include <unistd.h> |
||||
|
||||
#include <algorithm> |
||||
#include <utility> |
||||
#include <vector> |
||||
|
||||
#include "common/linux/linux_libc_support.h" |
||||
#include "common/memory.h" |
||||
#include "client/linux/minidump_writer/linux_dumper.h" |
||||
#include "client/linux/minidump_writer/minidump_writer.h" |
||||
#include "common/linux/guid_creator.h" |
||||
#include "common/linux/eintr_wrapper.h" |
||||
#include "third_party/lss/linux_syscall_support.h" |
||||
|
||||
#ifndef PR_SET_PTRACER |
||||
#define PR_SET_PTRACER 0x59616d61 |
||||
#endif |
||||
|
||||
// A wrapper for the tgkill syscall: send a signal to a specific thread.
|
||||
static int tgkill(pid_t tgid, pid_t tid, int sig) { |
||||
return syscall(__NR_tgkill, tgid, tid, sig); |
||||
return 0; |
||||
} |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
// The list of signals which we consider to be crashes. The default action for
|
||||
// all these signals must be Core (see man 7 signal) because we rethrow the
|
||||
// signal after handling it and expect that it'll be fatal.
|
||||
static const int kExceptionSignals[] = { |
||||
SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, -1 |
||||
}; |
||||
|
||||
// We can stack multiple exception handlers. In that case, this is the global
|
||||
// which holds the stack.
|
||||
std::vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL; |
||||
unsigned ExceptionHandler::handler_stack_index_ = 0; |
||||
pthread_mutex_t ExceptionHandler::handler_stack_mutex_ = |
||||
PTHREAD_MUTEX_INITIALIZER; |
||||
|
||||
// Runs before crashing: normal context.
|
||||
ExceptionHandler::ExceptionHandler(const std::string &dump_path, |
||||
FilterCallback filter, |
||||
MinidumpCallback callback, |
||||
void *callback_context, |
||||
bool install_handler) |
||||
: filter_(filter), |
||||
callback_(callback), |
||||
callback_context_(callback_context), |
||||
handler_installed_(install_handler) |
||||
{ |
||||
Init(dump_path, -1); |
||||
} |
||||
|
||||
ExceptionHandler::ExceptionHandler(const std::string &dump_path, |
||||
FilterCallback filter, |
||||
MinidumpCallback callback, |
||||
void* callback_context, |
||||
bool install_handler, |
||||
const int server_fd) |
||||
: filter_(filter), |
||||
callback_(callback), |
||||
callback_context_(callback_context), |
||||
handler_installed_(install_handler) |
||||
{ |
||||
Init(dump_path, server_fd); |
||||
} |
||||
|
||||
// Runs before crashing: normal context.
|
||||
ExceptionHandler::~ExceptionHandler() { |
||||
UninstallHandlers(); |
||||
} |
||||
|
||||
void ExceptionHandler::Init(const std::string &dump_path, |
||||
const int server_fd) |
||||
{ |
||||
crash_handler_ = NULL; |
||||
if (0 <= server_fd) |
||||
crash_generation_client_ |
||||
.reset(CrashGenerationClient::TryCreate(server_fd)); |
||||
|
||||
if (handler_installed_) |
||||
InstallHandlers(); |
||||
|
||||
if (!IsOutOfProcess()) |
||||
set_dump_path(dump_path); |
||||
|
||||
pthread_mutex_lock(&handler_stack_mutex_); |
||||
if (handler_stack_ == NULL) |
||||
handler_stack_ = new std::vector<ExceptionHandler *>; |
||||
handler_stack_->push_back(this); |
||||
pthread_mutex_unlock(&handler_stack_mutex_); |
||||
} |
||||
|
||||
// Runs before crashing: normal context.
|
||||
bool ExceptionHandler::InstallHandlers() { |
||||
// We run the signal handlers on an alternative stack because we might have
|
||||
// crashed because of a stack overflow.
|
||||
|
||||
// We use this value rather than SIGSTKSZ because we would end up overrunning
|
||||
// such a small stack.
|
||||
static const unsigned kSigStackSize = 8192; |
||||
|
||||
signal_stack = malloc(kSigStackSize); |
||||
stack_t stack; |
||||
memset(&stack, 0, sizeof(stack)); |
||||
stack.ss_sp = signal_stack; |
||||
stack.ss_size = kSigStackSize; |
||||
|
||||
if (sys_sigaltstack(&stack, NULL) == -1) |
||||
return false; |
||||
|
||||
struct sigaction sa; |
||||
memset(&sa, 0, sizeof(sa)); |
||||
sigemptyset(&sa.sa_mask); |
||||
|
||||
// mask all exception signals when we're handling one of them.
|
||||
for (unsigned i = 0; kExceptionSignals[i] != -1; ++i) |
||||
sigaddset(&sa.sa_mask, kExceptionSignals[i]); |
||||
|
||||
sa.sa_sigaction = SignalHandler; |
||||
sa.sa_flags = SA_ONSTACK | SA_SIGINFO; |
||||
|
||||
for (unsigned i = 0; kExceptionSignals[i] != -1; ++i) { |
||||
struct sigaction* old = new struct sigaction; |
||||
if (sigaction(kExceptionSignals[i], &sa, old) == -1) |
||||
return false; |
||||
old_handlers_.push_back(std::make_pair(kExceptionSignals[i], old)); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
// Runs before crashing: normal context.
|
||||
void ExceptionHandler::UninstallHandlers() { |
||||
for (unsigned i = 0; i < old_handlers_.size(); ++i) { |
||||
struct sigaction *action = |
||||
reinterpret_cast<struct sigaction*>(old_handlers_[i].second); |
||||
sigaction(old_handlers_[i].first, action, NULL); |
||||
delete action; |
||||
} |
||||
pthread_mutex_lock(&handler_stack_mutex_); |
||||
std::vector<ExceptionHandler*>::iterator handler = |
||||
std::find(handler_stack_->begin(), handler_stack_->end(), this); |
||||
handler_stack_->erase(handler); |
||||
pthread_mutex_unlock(&handler_stack_mutex_); |
||||
old_handlers_.clear(); |
||||
} |
||||
|
||||
// Runs before crashing: normal context.
|
||||
void ExceptionHandler::UpdateNextID() { |
||||
GUID guid; |
||||
char guid_str[kGUIDStringLength + 1]; |
||||
if (CreateGUID(&guid) && GUIDToString(&guid, guid_str, sizeof(guid_str))) { |
||||
next_minidump_id_ = guid_str; |
||||
next_minidump_id_c_ = next_minidump_id_.c_str(); |
||||
|
||||
char minidump_path[PATH_MAX]; |
||||
snprintf(minidump_path, sizeof(minidump_path), "%s/%s.dmp", |
||||
dump_path_c_, |
||||
guid_str); |
||||
|
||||
next_minidump_path_ = minidump_path; |
||||
next_minidump_path_c_ = next_minidump_path_.c_str(); |
||||
} |
||||
} |
||||
|
||||
// void ExceptionHandler::set_crash_handler(HandlerCallback callback) {
|
||||
// crash_handler_ = callback;
|
||||
// }
|
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
// Runs on the crashing thread.
|
||||
// static
|
||||
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { |
||||
// All the exception signals are blocked at this point.
|
||||
pthread_mutex_lock(&handler_stack_mutex_); |
||||
|
||||
if (!handler_stack_->size()) { |
||||
pthread_mutex_unlock(&handler_stack_mutex_); |
||||
return; |
||||
} |
||||
|
||||
for (int i = handler_stack_->size() - 1; i >= 0; --i) { |
||||
if ((*handler_stack_)[i]->HandleSignal(sig, info, uc)) { |
||||
// successfully handled: We are in an invalid state since an exception
|
||||
// signal has been delivered. We don't call the exit handlers because
|
||||
// they could end up corrupting on-disk state.
|
||||
break; |
||||
} |
||||
} |
||||
|
||||
pthread_mutex_unlock(&handler_stack_mutex_); |
||||
|
||||
if (info->si_pid) { |
||||
// This signal was triggered by somebody sending us the signal with kill().
|
||||
// In order to retrigger it, we have to queue a new signal by calling
|
||||
// kill() ourselves.
|
||||
if (tgkill(getpid(), syscall(__NR_gettid), sig) < 0) { |
||||
// If we failed to kill ourselves (e.g. because a sandbox disallows us
|
||||
// to do so), we instead resort to terminating our process. This will
|
||||
// result in an incorrect exit code.
|
||||
_exit(1); |
||||
} |
||||
} else { |
||||
// This was a synchronous signal triggered by a hard fault (e.g. SIGSEGV).
|
||||
// No need to reissue the signal. It will automatically trigger again,
|
||||
// when we return from the signal handler.
|
||||
} |
||||
|
||||
// As soon as we return from the signal handler, our signal will become
|
||||
// unmasked. At that time, we will get terminated with the same signal that
|
||||
// was triggered originally. This allows our parent to know that we crashed.
|
||||
// The default action for all the signals which we catch is Core, so
|
||||
// this is the end of us.
|
||||
signal(sig, SIG_DFL); |
||||
} |
||||
|
||||
struct ThreadArgument { |
||||
pid_t pid; // the crashing process
|
||||
ExceptionHandler* handler; |
||||
const void* context; // a CrashContext structure
|
||||
size_t context_size; |
||||
}; |
||||
|
||||
// This is the entry function for the cloned process. We are in a compromised
|
||||
// context here: see the top of the file.
|
||||
// static
|
||||
int ExceptionHandler::ThreadEntry(void *arg) { |
||||
const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg); |
||||
|
||||
// Block here until the crashing process unblocks us when
|
||||
// we're allowed to use ptrace
|
||||
thread_arg->handler->WaitForContinueSignal(); |
||||
|
||||
return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context, |
||||
thread_arg->context_size) == false; |
||||
} |
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
// Runs on the crashing thread.
|
||||
bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) { |
||||
if (filter_ && !filter_(callback_context_)) |
||||
return false; |
||||
|
||||
// Allow ourselves to be dumped if the signal is trusted.
|
||||
bool signal_trusted = info->si_code > 0; |
||||
bool signal_pid_trusted = info->si_code == SI_USER || |
||||
info->si_code == SI_TKILL; |
||||
if (signal_trusted || (signal_pid_trusted && info->si_pid == getpid())) { |
||||
sys_prctl(PR_SET_DUMPABLE, 1); |
||||
} |
||||
CrashContext context; |
||||
memcpy(&context.siginfo, info, sizeof(siginfo_t)); |
||||
memcpy(&context.context, uc, sizeof(struct ucontext)); |
||||
#if !defined(__ARM_EABI__) |
||||
// FP state is not part of user ABI on ARM Linux.
|
||||
struct ucontext *uc_ptr = (struct ucontext*)uc; |
||||
if (uc_ptr->uc_mcontext.fpregs) { |
||||
memcpy(&context.float_state, |
||||
uc_ptr->uc_mcontext.fpregs, |
||||
sizeof(context.float_state)); |
||||
} |
||||
#endif |
||||
context.tid = syscall(__NR_gettid); |
||||
if (crash_handler_ != NULL) { |
||||
if (crash_handler_(&context, sizeof(context), |
||||
callback_context_)) { |
||||
return true; |
||||
} |
||||
} |
||||
return GenerateDump(&context); |
||||
} |
||||
|
||||
// This function may run in a compromised context: see the top of the file.
|
||||
bool ExceptionHandler::GenerateDump(CrashContext *context) { |
||||
if (IsOutOfProcess()) |
||||
return crash_generation_client_->RequestDump(context, sizeof(*context)); |
||||
|
||||
static const unsigned kChildStackSize = 8000; |
||||
PageAllocator allocator; |
||||
uint8_t* stack = (uint8_t*) allocator.Alloc(kChildStackSize); |
||||
if (!stack) |
||||
return false; |
||||
// clone() needs the top-most address. (scrub just to be safe)
|
||||
stack += kChildStackSize; |
||||
my_memset(stack - 16, 0, 16); |
||||
|
||||
ThreadArgument thread_arg; |
||||
thread_arg.handler = this; |
||||
thread_arg.pid = getpid(); |
||||
thread_arg.context = context; |
||||
thread_arg.context_size = sizeof(*context); |
||||
|
||||
// We need to explicitly enable ptrace of parent processes on some
|
||||
// kernels, but we need to know the PID of the cloned process before we
|
||||
// can do this. Create a pipe here which we can use to block the
|
||||
// cloned process after creating it, until we have explicitly enabled ptrace
|
||||
if(sys_pipe(fdes) == -1) { |
||||
// Creating the pipe failed. We'll log an error but carry on anyway,
|
||||
// as we'll probably still get a useful crash report. All that will happen
|
||||
// is the write() and read() calls will fail with EBADF
|
||||
static const char no_pipe_msg[] = "ExceptionHandler::GenerateDump \
|
||||
sys_pipe failed:"; |
||||
sys_write(2, no_pipe_msg, sizeof(no_pipe_msg) - 1); |
||||
sys_write(2, strerror(errno), strlen(strerror(errno))); |
||||
sys_write(2, "\n", 1); |
||||
} |
||||
|
||||
const pid_t child = sys_clone( |
||||
ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED, |
||||
&thread_arg, NULL, NULL, NULL); |
||||
int r, status; |
||||
// Allow the child to ptrace us
|
||||
prctl(PR_SET_PTRACER, child, 0, 0, 0); |
||||
SendContinueSignalToChild(); |
||||
do { |
||||
r = sys_waitpid(child, &status, __WALL); |
||||
} while (r == -1 && errno == EINTR); |
||||
|
||||
sys_close(fdes[0]); |
||||
sys_close(fdes[1]); |
||||
|
||||
if (r == -1) { |
||||
static const char msg[] = "ExceptionHandler::GenerateDump waitpid failed:"; |
||||
sys_write(2, msg, sizeof(msg) - 1); |
||||
sys_write(2, strerror(errno), strlen(strerror(errno))); |
||||
sys_write(2, "\n", 1); |
||||
} |
||||
|
||||
bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0; |
||||
|
||||
if (callback_) |
||||
success = callback_(dump_path_c_, next_minidump_id_c_, |
||||
callback_context_, success); |
||||
|
||||
return success; |
||||
} |
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
void ExceptionHandler::SendContinueSignalToChild() { |
||||
static const char okToContinueMessage = 'a'; |
||||
int r; |
||||
r = HANDLE_EINTR(sys_write(fdes[1], &okToContinueMessage, sizeof(char))); |
||||
if(r == -1) { |
||||
static const char msg[] = "ExceptionHandler::SendContinueSignalToChild \
|
||||
sys_write failed:"; |
||||
sys_write(2, msg, sizeof(msg) - 1); |
||||
sys_write(2, strerror(errno), strlen(strerror(errno))); |
||||
sys_write(2, "\n", 1); |
||||
} |
||||
} |
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
// Runs on the cloned process.
|
||||
void ExceptionHandler::WaitForContinueSignal() { |
||||
int r; |
||||
char receivedMessage; |
||||
r = HANDLE_EINTR(sys_read(fdes[0], &receivedMessage, sizeof(char))); |
||||
if(r == -1) { |
||||
static const char msg[] = "ExceptionHandler::WaitForContinueSignal \
|
||||
sys_read failed:"; |
||||
sys_write(2, msg, sizeof(msg) - 1); |
||||
sys_write(2, strerror(errno), strlen(strerror(errno))); |
||||
sys_write(2, "\n", 1); |
||||
} |
||||
} |
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
// Runs on the cloned process.
|
||||
bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context, |
||||
size_t context_size) { |
||||
return google_breakpad::WriteMinidump(next_minidump_path_c_, |
||||
crashing_process, |
||||
context, |
||||
context_size, |
||||
mapping_list_); |
||||
} |
||||
|
||||
// static
|
||||
bool ExceptionHandler::WriteMinidump(const std::string &dump_path, |
||||
MinidumpCallback callback, |
||||
void* callback_context) { |
||||
ExceptionHandler eh(dump_path, NULL, callback, callback_context, false); |
||||
return eh.WriteMinidump(); |
||||
} |
||||
|
||||
bool ExceptionHandler::WriteMinidump() { |
||||
#if !defined(__ARM_EABI__) |
||||
// Allow ourselves to be dumped.
|
||||
sys_prctl(PR_SET_DUMPABLE, 1); |
||||
|
||||
CrashContext context; |
||||
int getcontext_result = getcontext(&context.context); |
||||
if (getcontext_result) |
||||
return false; |
||||
memcpy(&context.float_state, context.context.uc_mcontext.fpregs, |
||||
sizeof(context.float_state)); |
||||
context.tid = sys_gettid(); |
||||
|
||||
bool success = GenerateDump(&context); |
||||
UpdateNextID(); |
||||
return success; |
||||
#else |
||||
return false; |
||||
#endif // !defined(__ARM_EABI__)
|
||||
} |
||||
|
||||
void ExceptionHandler::AddMappingInfo(const std::string& name, |
||||
const u_int8_t identifier[sizeof(MDGUID)], |
||||
uintptr_t start_address, |
||||
size_t mapping_size, |
||||
size_t file_offset) { |
||||
MappingInfo info; |
||||
info.start_addr = start_address; |
||||
info.size = mapping_size; |
||||
info.offset = file_offset; |
||||
strncpy(info.name, name.c_str(), std::min(name.size(), sizeof(info))); |
||||
|
||||
MappingEntry mapping; |
||||
mapping.first = info; |
||||
memcpy(mapping.second, identifier, sizeof(MDGUID)); |
||||
mapping_list_.push_back(mapping); |
||||
} |
||||
|
||||
} // namespace google_breakpad
|
@ -1,259 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ |
||||
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include <pthread.h> |
||||
#include <signal.h> |
||||
#include <stdint.h> |
||||
#include <stdio.h> |
||||
|
||||
#if defined(__ANDROID__) |
||||
#include "client/linux/android_ucontext.h" |
||||
#endif |
||||
#include "client/linux/crash_generation/crash_generation_client.h" |
||||
#include "client/linux/minidump_writer/minidump_writer.h" |
||||
#include "google_breakpad/common/minidump_format.h" |
||||
#include "processor/scoped_ptr.h" |
||||
|
||||
struct sigaction; |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
class ExceptionHandler; |
||||
|
||||
// ExceptionHandler
|
||||
//
|
||||
// ExceptionHandler can write a minidump file when an exception occurs,
|
||||
// or when WriteMinidump() is called explicitly by your program.
|
||||
//
|
||||
// To have the exception handler write minidumps when an uncaught exception
|
||||
// (crash) occurs, you should create an instance early in the execution
|
||||
// of your program, and keep it around for the entire time you want to
|
||||
// have crash handling active (typically, until shutdown).
|
||||
// (NOTE): There should be only be one this kind of exception handler
|
||||
// object per process.
|
||||
//
|
||||
// If you want to write minidumps without installing the exception handler,
|
||||
// you can create an ExceptionHandler with install_handler set to false,
|
||||
// then call WriteMinidump. You can also use this technique if you want to
|
||||
// use different minidump callbacks for different call sites.
|
||||
//
|
||||
// In either case, a callback function is called when a minidump is written,
|
||||
// which receives the unqiue id of the minidump. The caller can use this
|
||||
// id to collect and write additional application state, and to launch an
|
||||
// external crash-reporting application.
|
||||
//
|
||||
// Caller should try to make the callbacks as crash-friendly as possible,
|
||||
// it should avoid use heap memory allocation as much as possible.
|
||||
class ExceptionHandler { |
||||
public: |
||||
// A callback function to run before Breakpad performs any substantial
|
||||
// processing of an exception. A FilterCallback is called before writing
|
||||
// a minidump. context is the parameter supplied by the user as
|
||||
// callback_context when the handler was created.
|
||||
//
|
||||
// If a FilterCallback returns true, Breakpad will continue processing,
|
||||
// attempting to write a minidump. If a FilterCallback returns false,
|
||||
// Breakpad will immediately report the exception as unhandled without
|
||||
// writing a minidump, allowing another handler the opportunity to handle it.
|
||||
typedef bool (*FilterCallback)(void *context); |
||||
|
||||
// A callback function to run after the minidump has been written.
|
||||
// minidump_id is a unique id for the dump, so the minidump
|
||||
// file is <dump_path>\<minidump_id>.dmp. context is the parameter supplied
|
||||
// by the user as callback_context when the handler was created. succeeded
|
||||
// indicates whether a minidump file was successfully written.
|
||||
//
|
||||
// If an exception occurred and the callback returns true, Breakpad will
|
||||
// treat the exception as fully-handled, suppressing any other handlers from
|
||||
// being notified of the exception. If the callback returns false, Breakpad
|
||||
// will treat the exception as unhandled, and allow another handler to handle
|
||||
// it. If there are no other handlers, Breakpad will report the exception to
|
||||
// the system as unhandled, allowing a debugger or native crash dialog the
|
||||
// opportunity to handle the exception. Most callback implementations
|
||||
// should normally return the value of |succeeded|, or when they wish to
|
||||
// not report an exception of handled, false. Callbacks will rarely want to
|
||||
// return true directly (unless |succeeded| is true).
|
||||
typedef bool (*MinidumpCallback)(const char *dump_path, |
||||
const char *minidump_id, |
||||
void *context, |
||||
bool succeeded); |
||||
|
||||
// In certain cases, a user may wish to handle the generation of the minidump
|
||||
// themselves. In this case, they can install a handler callback which is
|
||||
// called when a crash has occurred. If this function returns true, no other
|
||||
// processing of occurs and the process will shortly be crashed. If this
|
||||
// returns false, the normal processing continues.
|
||||
typedef bool (*HandlerCallback)(const void* crash_context, |
||||
size_t crash_context_size, |
||||
void* context); |
||||
|
||||
// Creates a new ExceptionHandler instance to handle writing minidumps.
|
||||
// Before writing a minidump, the optional filter callback will be called.
|
||||
// Its return value determines whether or not Breakpad should write a
|
||||
// minidump. Minidump files will be written to dump_path, and the optional
|
||||
// callback is called after writing the dump file, as described above.
|
||||
// If install_handler is true, then a minidump will be written whenever
|
||||
// an unhandled exception occurs. If it is false, minidumps will only
|
||||
// be written when WriteMinidump is called.
|
||||
ExceptionHandler(const std::string &dump_path, |
||||
FilterCallback filter, MinidumpCallback callback, |
||||
void *callback_context, |
||||
bool install_handler); |
||||
|
||||
// Creates a new ExceptionHandler instance that can attempt to
|
||||
// perform out-of-process dump generation if server_fd is valid. If
|
||||
// server_fd is invalid, in-process dump generation will be
|
||||
// used. See the above ctor for a description of the other
|
||||
// parameters.
|
||||
ExceptionHandler(const std::string& dump_path, |
||||
FilterCallback filter, MinidumpCallback callback, |
||||
void* callback_context, |
||||
bool install_handler, |
||||
const int server_fd); |
||||
|
||||
~ExceptionHandler(); |
||||
|
||||
// Get and set the minidump path.
|
||||
std::string dump_path() const { return dump_path_; } |
||||
void set_dump_path(const std::string &dump_path) { |
||||
dump_path_ = dump_path; |
||||
dump_path_c_ = dump_path_.c_str(); |
||||
UpdateNextID(); |
||||
} |
||||
|
||||
void set_crash_handler(HandlerCallback callback) { |
||||
crash_handler_ = callback; |
||||
} |
||||
|
||||
// Writes a minidump immediately. This can be used to capture the
|
||||
// execution state independently of a crash. Returns true on success.
|
||||
bool WriteMinidump(); |
||||
|
||||
// Convenience form of WriteMinidump which does not require an
|
||||
// ExceptionHandler instance.
|
||||
static bool WriteMinidump(const std::string &dump_path, |
||||
MinidumpCallback callback, |
||||
void *callback_context); |
||||
|
||||
// This structure is passed to minidump_writer.h:WriteMinidump via an opaque
|
||||
// blob. It shouldn't be needed in any user code.
|
||||
struct CrashContext { |
||||
siginfo_t siginfo; |
||||
pid_t tid; // the crashing thread.
|
||||
struct ucontext context; |
||||
#if !defined(__ARM_EABI__) |
||||
// #ifdef this out because FP state is not part of user ABI for Linux ARM.
|
||||
struct _libc_fpstate float_state; |
||||
#endif |
||||
}; |
||||
|
||||
// Returns whether out-of-process dump generation is used or not.
|
||||
bool IsOutOfProcess() const { |
||||
return crash_generation_client_.get() != NULL; |
||||
} |
||||
|
||||
// Add information about a memory mapping. This can be used if
|
||||
// a custom library loader is used that maps things in a way
|
||||
// that the linux dumper can't handle by reading the maps file.
|
||||
void AddMappingInfo(const std::string& name, |
||||
const u_int8_t identifier[sizeof(MDGUID)], |
||||
uintptr_t start_address, |
||||
size_t mapping_size, |
||||
size_t file_offset); |
||||
|
||||
private: |
||||
void Init(const std::string &dump_path, |
||||
const int server_fd); |
||||
bool InstallHandlers(); |
||||
void UninstallHandlers(); |
||||
void PreresolveSymbols(); |
||||
bool GenerateDump(CrashContext *context); |
||||
void SendContinueSignalToChild(); |
||||
void WaitForContinueSignal(); |
||||
|
||||
void UpdateNextID(); |
||||
static void SignalHandler(int sig, siginfo_t* info, void* uc); |
||||
bool HandleSignal(int sig, siginfo_t* info, void* uc); |
||||
static int ThreadEntry(void* arg); |
||||
bool DoDump(pid_t crashing_process, const void* context, |
||||
size_t context_size); |
||||
|
||||
const FilterCallback filter_; |
||||
const MinidumpCallback callback_; |
||||
void* const callback_context_; |
||||
|
||||
scoped_ptr<CrashGenerationClient> crash_generation_client_; |
||||
|
||||
std::string dump_path_; |
||||
std::string next_minidump_path_; |
||||
std::string next_minidump_id_; |
||||
|
||||
// Pointers to C-string representations of the above. These are set
|
||||
// when the above are set so we can avoid calling c_str during
|
||||
// an exception.
|
||||
const char* dump_path_c_; |
||||
const char* next_minidump_path_c_; |
||||
const char* next_minidump_id_c_; |
||||
|
||||
const bool handler_installed_; |
||||
void* signal_stack; // the handler stack.
|
||||
HandlerCallback crash_handler_; |
||||
|
||||
// The global exception handler stack. This is need becuase there may exist
|
||||
// multiple ExceptionHandler instances in a process. Each will have itself
|
||||
// registered in this stack.
|
||||
static std::vector<ExceptionHandler*> *handler_stack_; |
||||
// The index of the handler that should handle the next exception.
|
||||
static unsigned handler_stack_index_; |
||||
static pthread_mutex_t handler_stack_mutex_; |
||||
|
||||
// A vector of the old signal handlers.
|
||||
std::vector<std::pair<int, struct sigaction *> > old_handlers_; |
||||
|
||||
// We need to explicitly enable ptrace of parent processes on some
|
||||
// kernels, but we need to know the PID of the cloned process before we
|
||||
// can do this. We create a pipe which we can use to block the
|
||||
// cloned process after creating it, until we have explicitly enabled
|
||||
// ptrace. This is used to store the file descriptors for the pipe
|
||||
int fdes[2]; |
||||
|
||||
// Callers can add extra info about mappings for cases where the
|
||||
// dumper code cannot extract enough information from /proc/<pid>/maps.
|
||||
MappingList mapping_list_; |
||||
}; |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
@ -1,775 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <stdint.h> |
||||
#include <unistd.h> |
||||
#include <signal.h> |
||||
#include <sys/mman.h> |
||||
#include <sys/poll.h> |
||||
#include <sys/socket.h> |
||||
#include <sys/uio.h> |
||||
#include <sys/wait.h> |
||||
|
||||
#include <string> |
||||
|
||||
#include "breakpad_googletest_includes.h" |
||||
#include "client/linux/handler/exception_handler.h" |
||||
#include "client/linux/minidump_writer/minidump_writer.h" |
||||
#include "common/linux/eintr_wrapper.h" |
||||
#include "common/linux/file_id.h" |
||||
#include "common/linux/linux_libc_support.h" |
||||
#include "third_party/lss/linux_syscall_support.h" |
||||
#include "google_breakpad/processor/minidump.h" |
||||
|
||||
using namespace google_breakpad; |
||||
|
||||
#if !defined(__ANDROID__) |
||||
#define TEMPDIR "/tmp" |
||||
#else |
||||
#define TEMPDIR "/data/local/tmp" |
||||
#endif |
||||
|
||||
// Length of a formatted GUID string =
|
||||
// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
|
||||
const int kGUIDStringSize = 37; |
||||
|
||||
static void sigchld_handler(int signo) { } |
||||
|
||||
class ExceptionHandlerTest : public ::testing::Test { |
||||
protected: |
||||
void SetUp() { |
||||
// We need to be able to wait for children, so SIGCHLD cannot be SIG_IGN.
|
||||
struct sigaction sa; |
||||
memset(&sa, 0, sizeof(sa)); |
||||
sa.sa_handler = sigchld_handler; |
||||
ASSERT_NE(sigaction(SIGCHLD, &sa, &old_action), -1); |
||||
} |
||||
|
||||
void TearDown() { |
||||
sigaction(SIGCHLD, &old_action, NULL); |
||||
} |
||||
|
||||
struct sigaction old_action; |
||||
}; |
||||
|
||||
TEST(ExceptionHandlerTest, Simple) { |
||||
ExceptionHandler handler(TEMPDIR, NULL, NULL, NULL, true); |
||||
} |
||||
|
||||
static bool DoneCallback(const char* dump_path, |
||||
const char* minidump_id, |
||||
void* context, |
||||
bool succeeded) { |
||||
if (!succeeded) |
||||
return succeeded; |
||||
|
||||
int fd = (intptr_t) context; |
||||
uint32_t len = my_strlen(minidump_id); |
||||
HANDLE_EINTR(sys_write(fd, &len, sizeof(len))); |
||||
HANDLE_EINTR(sys_write(fd, minidump_id, len)); |
||||
sys_close(fd); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
TEST(ExceptionHandlerTest, ChildCrash) { |
||||
int fds[2]; |
||||
ASSERT_NE(pipe(fds), -1); |
||||
|
||||
const pid_t child = fork(); |
||||
if (child == 0) { |
||||
close(fds[0]); |
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1], |
||||
true); |
||||
*reinterpret_cast<volatile int*>(NULL) = 0; |
||||
} |
||||
close(fds[1]); |
||||
|
||||
int status; |
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); |
||||
ASSERT_TRUE(WIFSIGNALED(status)); |
||||
ASSERT_EQ(WTERMSIG(status), SIGSEGV); |
||||
|
||||
struct pollfd pfd; |
||||
memset(&pfd, 0, sizeof(pfd)); |
||||
pfd.fd = fds[0]; |
||||
pfd.events = POLLIN | POLLERR; |
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); |
||||
ASSERT_EQ(r, 1); |
||||
ASSERT_TRUE(pfd.revents & POLLIN); |
||||
|
||||
uint32_t len; |
||||
ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len)); |
||||
ASSERT_LT(len, (uint32_t)2048); |
||||
char* filename = reinterpret_cast<char*>(malloc(len + 1)); |
||||
ASSERT_EQ(read(fds[0], filename, len), len); |
||||
filename[len] = 0; |
||||
close(fds[0]); |
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename + |
||||
".dmp"; |
||||
|
||||
struct stat st; |
||||
ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0); |
||||
ASSERT_GT(st.st_size, 0u); |
||||
unlink(minidump_filename.c_str()); |
||||
} |
||||
|
||||
// Test that memory around the instruction pointer is written
|
||||
// to the dump as a MinidumpMemoryRegion.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemory) { |
||||
int fds[2]; |
||||
ASSERT_NE(pipe(fds), -1); |
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = 256; // bytes
|
||||
const int kOffset = kMemorySize / 2; |
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; |
||||
|
||||
const pid_t child = fork(); |
||||
if (child == 0) { |
||||
close(fds[0]); |
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1], |
||||
true); |
||||
// Get some executable memory.
|
||||
char* memory = |
||||
reinterpret_cast<char*>(mmap(NULL, |
||||
kMemorySize, |
||||
PROT_READ | PROT_WRITE | PROT_EXEC, |
||||
MAP_PRIVATE | MAP_ANON, |
||||
-1, |
||||
0)); |
||||
if (!memory) |
||||
exit(0); |
||||
|
||||
// Write some instructions that will crash. Put them in the middle
|
||||
// of the block of memory, because the minidump should contain 128
|
||||
// bytes on either side of the instruction pointer.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions)); |
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void); |
||||
void_function memory_function = |
||||
reinterpret_cast<void_function>(memory + kOffset); |
||||
memory_function(); |
||||
} |
||||
close(fds[1]); |
||||
|
||||
int status; |
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); |
||||
ASSERT_TRUE(WIFSIGNALED(status)); |
||||
ASSERT_EQ(WTERMSIG(status), SIGILL); |
||||
|
||||
struct pollfd pfd; |
||||
memset(&pfd, 0, sizeof(pfd)); |
||||
pfd.fd = fds[0]; |
||||
pfd.events = POLLIN | POLLERR; |
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); |
||||
ASSERT_EQ(r, 1); |
||||
ASSERT_TRUE(pfd.revents & POLLIN); |
||||
|
||||
uint32_t len; |
||||
ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len)); |
||||
ASSERT_LT(len, (uint32_t)2048); |
||||
char* filename = reinterpret_cast<char*>(malloc(len + 1)); |
||||
ASSERT_EQ(read(fds[0], filename, len), len); |
||||
filename[len] = 0; |
||||
close(fds[0]); |
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename + |
||||
".dmp"; |
||||
|
||||
struct stat st; |
||||
ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0); |
||||
ASSERT_GT(st.st_size, 0u); |
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
Minidump minidump(minidump_filename); |
||||
ASSERT_TRUE(minidump.Read()); |
||||
|
||||
MinidumpException* exception = minidump.GetException(); |
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList(); |
||||
ASSERT_TRUE(exception); |
||||
ASSERT_TRUE(memory_list); |
||||
ASSERT_LT(0, memory_list->region_count()); |
||||
|
||||
MinidumpContext* context = exception->GetContext(); |
||||
ASSERT_TRUE(context); |
||||
|
||||
u_int64_t instruction_pointer; |
||||
switch (context->GetContextCPU()) { |
||||
case MD_CONTEXT_X86: |
||||
instruction_pointer = context->GetContextX86()->eip; |
||||
break; |
||||
case MD_CONTEXT_AMD64: |
||||
instruction_pointer = context->GetContextAMD64()->rip; |
||||
break; |
||||
case MD_CONTEXT_ARM: |
||||
instruction_pointer = context->GetContextARM()->iregs[15]; |
||||
break; |
||||
default: |
||||
FAIL() << "Unknown context CPU: " << context->GetContextCPU(); |
||||
break; |
||||
} |
||||
|
||||
MinidumpMemoryRegion* region = |
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer); |
||||
ASSERT_TRUE(region); |
||||
|
||||
EXPECT_EQ(kMemorySize, region->GetSize()); |
||||
const u_int8_t* bytes = region->GetMemory(); |
||||
ASSERT_TRUE(bytes); |
||||
|
||||
u_int8_t prefix_bytes[kOffset]; |
||||
u_int8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)]; |
||||
memset(prefix_bytes, 0, sizeof(prefix_bytes)); |
||||
memset(suffix_bytes, 0, sizeof(suffix_bytes)); |
||||
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); |
||||
EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0); |
||||
EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions), |
||||
suffix_bytes, sizeof(suffix_bytes)) == 0); |
||||
|
||||
unlink(minidump_filename.c_str()); |
||||
free(filename); |
||||
} |
||||
|
||||
// Test that the memory region around the instruction pointer is
|
||||
// bounded correctly on the low end.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemoryMinBound) { |
||||
int fds[2]; |
||||
ASSERT_NE(pipe(fds), -1); |
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = 256; // bytes
|
||||
const int kOffset = 0; |
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; |
||||
|
||||
const pid_t child = fork(); |
||||
if (child == 0) { |
||||
close(fds[0]); |
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1], |
||||
true); |
||||
// Get some executable memory.
|
||||
char* memory = |
||||
reinterpret_cast<char*>(mmap(NULL, |
||||
kMemorySize, |
||||
PROT_READ | PROT_WRITE | PROT_EXEC, |
||||
MAP_PRIVATE | MAP_ANON, |
||||
-1, |
||||
0)); |
||||
if (!memory) |
||||
exit(0); |
||||
|
||||
// Write some instructions that will crash. Put them in the middle
|
||||
// of the block of memory, because the minidump should contain 128
|
||||
// bytes on either side of the instruction pointer.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions)); |
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void); |
||||
void_function memory_function = |
||||
reinterpret_cast<void_function>(memory + kOffset); |
||||
memory_function(); |
||||
} |
||||
close(fds[1]); |
||||
|
||||
int status; |
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); |
||||
ASSERT_TRUE(WIFSIGNALED(status)); |
||||
ASSERT_EQ(WTERMSIG(status), SIGILL); |
||||
|
||||
struct pollfd pfd; |
||||
memset(&pfd, 0, sizeof(pfd)); |
||||
pfd.fd = fds[0]; |
||||
pfd.events = POLLIN | POLLERR; |
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); |
||||
ASSERT_EQ(r, 1); |
||||
ASSERT_TRUE(pfd.revents & POLLIN); |
||||
|
||||
uint32_t len; |
||||
ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len)); |
||||
ASSERT_LT(len, (uint32_t)2048); |
||||
char* filename = reinterpret_cast<char*>(malloc(len + 1)); |
||||
ASSERT_EQ(read(fds[0], filename, len), len); |
||||
filename[len] = 0; |
||||
close(fds[0]); |
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename + |
||||
".dmp"; |
||||
|
||||
struct stat st; |
||||
ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0); |
||||
ASSERT_GT(st.st_size, 0u); |
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
Minidump minidump(minidump_filename); |
||||
ASSERT_TRUE(minidump.Read()); |
||||
|
||||
MinidumpException* exception = minidump.GetException(); |
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList(); |
||||
ASSERT_TRUE(exception); |
||||
ASSERT_TRUE(memory_list); |
||||
ASSERT_LT(0, memory_list->region_count()); |
||||
|
||||
MinidumpContext* context = exception->GetContext(); |
||||
ASSERT_TRUE(context); |
||||
|
||||
u_int64_t instruction_pointer; |
||||
switch (context->GetContextCPU()) { |
||||
case MD_CONTEXT_X86: |
||||
instruction_pointer = context->GetContextX86()->eip; |
||||
break; |
||||
case MD_CONTEXT_AMD64: |
||||
instruction_pointer = context->GetContextAMD64()->rip; |
||||
break; |
||||
case MD_CONTEXT_ARM: |
||||
instruction_pointer = context->GetContextARM()->iregs[15]; |
||||
break; |
||||
default: |
||||
FAIL() << "Unknown context CPU: " << context->GetContextCPU(); |
||||
break; |
||||
} |
||||
|
||||
MinidumpMemoryRegion* region = |
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer); |
||||
ASSERT_TRUE(region); |
||||
|
||||
EXPECT_EQ(kMemorySize / 2, region->GetSize()); |
||||
const u_int8_t* bytes = region->GetMemory(); |
||||
ASSERT_TRUE(bytes); |
||||
|
||||
u_int8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)]; |
||||
memset(suffix_bytes, 0, sizeof(suffix_bytes)); |
||||
EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0); |
||||
EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions), |
||||
suffix_bytes, sizeof(suffix_bytes)) == 0); |
||||
|
||||
unlink(minidump_filename.c_str()); |
||||
free(filename); |
||||
} |
||||
|
||||
// Test that the memory region around the instruction pointer is
|
||||
// bounded correctly on the high end.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) { |
||||
int fds[2]; |
||||
ASSERT_NE(pipe(fds), -1); |
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
// Use 4k here because the OS will hand out a single page even
|
||||
// if a smaller size is requested, and this test wants to
|
||||
// test the upper bound of the memory range.
|
||||
const u_int32_t kMemorySize = 4096; // bytes
|
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; |
||||
const int kOffset = kMemorySize - sizeof(instructions); |
||||
|
||||
const pid_t child = fork(); |
||||
if (child == 0) { |
||||
close(fds[0]); |
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1], |
||||
true); |
||||
// Get some executable memory.
|
||||
char* memory = |
||||
reinterpret_cast<char*>(mmap(NULL, |
||||
kMemorySize, |
||||
PROT_READ | PROT_WRITE | PROT_EXEC, |
||||
MAP_PRIVATE | MAP_ANON, |
||||
-1, |
||||
0)); |
||||
if (!memory) |
||||
exit(0); |
||||
|
||||
// Write some instructions that will crash. Put them in the middle
|
||||
// of the block of memory, because the minidump should contain 128
|
||||
// bytes on either side of the instruction pointer.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions)); |
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void); |
||||
void_function memory_function = |
||||
reinterpret_cast<void_function>(memory + kOffset); |
||||
memory_function(); |
||||
} |
||||
close(fds[1]); |
||||
|
||||
int status; |
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); |
||||
ASSERT_TRUE(WIFSIGNALED(status)); |
||||
ASSERT_EQ(WTERMSIG(status), SIGILL); |
||||
|
||||
struct pollfd pfd; |
||||
memset(&pfd, 0, sizeof(pfd)); |
||||
pfd.fd = fds[0]; |
||||
pfd.events = POLLIN | POLLERR; |
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); |
||||
ASSERT_EQ(r, 1); |
||||
ASSERT_TRUE(pfd.revents & POLLIN); |
||||
|
||||
uint32_t len; |
||||
ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len)); |
||||
ASSERT_LT(len, (uint32_t)2048); |
||||
char* filename = reinterpret_cast<char*>(malloc(len + 1)); |
||||
ASSERT_EQ(read(fds[0], filename, len), len); |
||||
filename[len] = 0; |
||||
close(fds[0]); |
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename + |
||||
".dmp"; |
||||
|
||||
struct stat st; |
||||
ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0); |
||||
ASSERT_GT(st.st_size, 0u); |
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
Minidump minidump(minidump_filename); |
||||
ASSERT_TRUE(minidump.Read()); |
||||
|
||||
MinidumpException* exception = minidump.GetException(); |
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList(); |
||||
ASSERT_TRUE(exception); |
||||
ASSERT_TRUE(memory_list); |
||||
ASSERT_LT(0, memory_list->region_count()); |
||||
|
||||
MinidumpContext* context = exception->GetContext(); |
||||
ASSERT_TRUE(context); |
||||
|
||||
u_int64_t instruction_pointer; |
||||
switch (context->GetContextCPU()) { |
||||
case MD_CONTEXT_X86: |
||||
instruction_pointer = context->GetContextX86()->eip; |
||||
break; |
||||
case MD_CONTEXT_AMD64: |
||||
instruction_pointer = context->GetContextAMD64()->rip; |
||||
break; |
||||
case MD_CONTEXT_ARM: |
||||
instruction_pointer = context->GetContextARM()->iregs[15]; |
||||
break; |
||||
default: |
||||
FAIL() << "Unknown context CPU: " << context->GetContextCPU(); |
||||
break; |
||||
} |
||||
|
||||
MinidumpMemoryRegion* region = |
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer); |
||||
ASSERT_TRUE(region); |
||||
|
||||
const size_t kPrefixSize = 128; // bytes
|
||||
EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize()); |
||||
const u_int8_t* bytes = region->GetMemory(); |
||||
ASSERT_TRUE(bytes); |
||||
|
||||
u_int8_t prefix_bytes[kPrefixSize]; |
||||
memset(prefix_bytes, 0, sizeof(prefix_bytes)); |
||||
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); |
||||
EXPECT_TRUE(memcmp(bytes + kPrefixSize, |
||||
instructions, sizeof(instructions)) == 0); |
||||
|
||||
unlink(minidump_filename.c_str()); |
||||
free(filename); |
||||
} |
||||
|
||||
// Ensure that an extra memory block doesn't get added when the
|
||||
// instruction pointer is not in mapped memory.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) { |
||||
int fds[2]; |
||||
ASSERT_NE(pipe(fds), -1); |
||||
|
||||
|
||||
const pid_t child = fork(); |
||||
if (child == 0) { |
||||
close(fds[0]); |
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1], |
||||
true); |
||||
// Try calling a NULL pointer.
|
||||
typedef void (*void_function)(void); |
||||
void_function memory_function = |
||||
reinterpret_cast<void_function>(NULL); |
||||
memory_function(); |
||||
} |
||||
close(fds[1]); |
||||
|
||||
int status; |
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); |
||||
ASSERT_TRUE(WIFSIGNALED(status)); |
||||
ASSERT_EQ(WTERMSIG(status), SIGSEGV); |
||||
|
||||
struct pollfd pfd; |
||||
memset(&pfd, 0, sizeof(pfd)); |
||||
pfd.fd = fds[0]; |
||||
pfd.events = POLLIN | POLLERR; |
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); |
||||
ASSERT_EQ(r, 1); |
||||
ASSERT_TRUE(pfd.revents & POLLIN); |
||||
|
||||
uint32_t len; |
||||
ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len)); |
||||
ASSERT_LT(len, (uint32_t)2048); |
||||
char* filename = reinterpret_cast<char*>(malloc(len + 1)); |
||||
ASSERT_EQ(read(fds[0], filename, len), len); |
||||
filename[len] = 0; |
||||
close(fds[0]); |
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename + |
||||
".dmp"; |
||||
|
||||
struct stat st; |
||||
ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0); |
||||
ASSERT_GT(st.st_size, 0u); |
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
Minidump minidump(minidump_filename); |
||||
ASSERT_TRUE(minidump.Read()); |
||||
|
||||
MinidumpException* exception = minidump.GetException(); |
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList(); |
||||
ASSERT_TRUE(exception); |
||||
ASSERT_TRUE(memory_list); |
||||
ASSERT_EQ((unsigned int)1, memory_list->region_count()); |
||||
|
||||
unlink(minidump_filename.c_str()); |
||||
free(filename); |
||||
} |
||||
|
||||
static bool SimpleCallback(const char* dump_path, |
||||
const char* minidump_id, |
||||
void* context, |
||||
bool succeeded) { |
||||
if (!succeeded) |
||||
return succeeded; |
||||
|
||||
string* minidump_file = reinterpret_cast<string*>(context); |
||||
minidump_file->append(dump_path); |
||||
minidump_file->append("/"); |
||||
minidump_file->append(minidump_id); |
||||
minidump_file->append(".dmp"); |
||||
return true; |
||||
} |
||||
|
||||
// Test that anonymous memory maps can be annotated with names and IDs.
|
||||
TEST(ExceptionHandlerTest, ModuleInfo) { |
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE); |
||||
const char* kMemoryName = "a fake module"; |
||||
const u_int8_t kModuleGUID[sizeof(MDGUID)] = { |
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, |
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF |
||||
}; |
||||
char module_identifier_buffer[kGUIDStringSize]; |
||||
FileID::ConvertIdentifierToString(kModuleGUID, |
||||
module_identifier_buffer, |
||||
sizeof(module_identifier_buffer)); |
||||
string module_identifier(module_identifier_buffer); |
||||
// Strip out dashes
|
||||
size_t pos; |
||||
while ((pos = module_identifier.find('-')) != string::npos) { |
||||
module_identifier.erase(pos, 1); |
||||
} |
||||
// And append a zero, because module IDs include an "age" field
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0"; |
||||
|
||||
// Get some memory.
|
||||
char* memory = |
||||
reinterpret_cast<char*>(mmap(NULL, |
||||
kMemorySize, |
||||
PROT_READ | PROT_WRITE, |
||||
MAP_PRIVATE | MAP_ANON, |
||||
-1, |
||||
0)); |
||||
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory); |
||||
ASSERT_TRUE(memory); |
||||
|
||||
string minidump_filename; |
||||
ExceptionHandler handler(TEMPDIR, NULL, SimpleCallback, |
||||
(void*)&minidump_filename, true); |
||||
// Add info about the anonymous memory mapping.
|
||||
handler.AddMappingInfo(kMemoryName, |
||||
kModuleGUID, |
||||
kMemoryAddress, |
||||
kMemorySize, |
||||
0); |
||||
handler.WriteMinidump(); |
||||
|
||||
// Read the minidump. Load the module list, and ensure that
|
||||
// the mmap'ed |memory| is listed with the given module name
|
||||
// and debug ID.
|
||||
Minidump minidump(minidump_filename); |
||||
ASSERT_TRUE(minidump.Read()); |
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList(); |
||||
ASSERT_TRUE(module_list); |
||||
const MinidumpModule* module = |
||||
module_list->GetModuleForAddress(kMemoryAddress); |
||||
ASSERT_TRUE(module); |
||||
|
||||
EXPECT_EQ(kMemoryAddress, module->base_address()); |
||||
EXPECT_EQ(kMemorySize, module->size()); |
||||
EXPECT_EQ(kMemoryName, module->code_file()); |
||||
EXPECT_EQ(module_identifier, module->debug_identifier()); |
||||
|
||||
unlink(minidump_filename.c_str()); |
||||
} |
||||
|
||||
static const unsigned kControlMsgSize = |
||||
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); |
||||
|
||||
static bool |
||||
CrashHandler(const void* crash_context, size_t crash_context_size, |
||||
void* context) { |
||||
const int fd = (intptr_t) context; |
||||
int fds[2]; |
||||
pipe(fds); |
||||
struct kernel_msghdr msg = {0}; |
||||
struct kernel_iovec iov; |
||||
iov.iov_base = const_cast<void*>(crash_context); |
||||
iov.iov_len = crash_context_size; |
||||
msg.msg_iov = &iov; |
||||
msg.msg_iovlen = 1; |
||||
char cmsg[kControlMsgSize]; |
||||
memset(cmsg, 0, kControlMsgSize); |
||||
msg.msg_control = cmsg; |
||||
msg.msg_controllen = sizeof(cmsg); |
||||
|
||||
struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); |
||||
hdr->cmsg_level = SOL_SOCKET; |
||||
hdr->cmsg_type = SCM_RIGHTS; |
||||
hdr->cmsg_len = CMSG_LEN(sizeof(int)); |
||||
*((int*) CMSG_DATA(hdr)) = fds[1]; |
||||
hdr = CMSG_NXTHDR((struct msghdr*) &msg, hdr); |
||||
hdr->cmsg_level = SOL_SOCKET; |
||||
hdr->cmsg_type = SCM_CREDENTIALS; |
||||
hdr->cmsg_len = CMSG_LEN(sizeof(struct ucred)); |
||||
struct ucred *cred = reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); |
||||
cred->uid = getuid(); |
||||
cred->gid = getgid(); |
||||
cred->pid = getpid(); |
||||
|
||||
HANDLE_EINTR(sys_sendmsg(fd, &msg, 0)); |
||||
sys_close(fds[1]); |
||||
|
||||
char b; |
||||
HANDLE_EINTR(sys_read(fds[0], &b, 1)); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
TEST(ExceptionHandlerTest, ExternalDumper) { |
||||
int fds[2]; |
||||
ASSERT_NE(socketpair(AF_UNIX, SOCK_DGRAM, 0, fds), -1); |
||||
static const int on = 1; |
||||
setsockopt(fds[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); |
||||
setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); |
||||
|
||||
const pid_t child = fork(); |
||||
if (child == 0) { |
||||
close(fds[0]); |
||||
ExceptionHandler handler("/tmp1", NULL, NULL, (void*) fds[1], true); |
||||
handler.set_crash_handler(CrashHandler); |
||||
*reinterpret_cast<volatile int*>(NULL) = 0; |
||||
} |
||||
close(fds[1]); |
||||
struct msghdr msg = {0}; |
||||
struct iovec iov; |
||||
static const unsigned kCrashContextSize = |
||||
sizeof(ExceptionHandler::CrashContext); |
||||
char context[kCrashContextSize]; |
||||
char control[kControlMsgSize]; |
||||
iov.iov_base = context; |
||||
iov.iov_len = kCrashContextSize; |
||||
msg.msg_iov = &iov; |
||||
msg.msg_iovlen = 1; |
||||
msg.msg_control = control; |
||||
msg.msg_controllen = kControlMsgSize; |
||||
|
||||
const ssize_t n = HANDLE_EINTR(recvmsg(fds[0], &msg, 0)); |
||||
ASSERT_EQ(n, kCrashContextSize); |
||||
ASSERT_EQ(msg.msg_controllen, kControlMsgSize); |
||||
ASSERT_EQ(msg.msg_flags, 0); |
||||
|
||||
pid_t crashing_pid = -1; |
||||
int signal_fd = -1; |
||||
for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; |
||||
hdr = CMSG_NXTHDR(&msg, hdr)) { |
||||
if (hdr->cmsg_level != SOL_SOCKET) |
||||
continue; |
||||
if (hdr->cmsg_type == SCM_RIGHTS) { |
||||
const unsigned len = hdr->cmsg_len - |
||||
(((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr); |
||||
ASSERT_EQ(len, sizeof(int)); |
||||
signal_fd = *((int *) CMSG_DATA(hdr)); |
||||
} else if (hdr->cmsg_type == SCM_CREDENTIALS) { |
||||
const struct ucred *cred = |
||||
reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); |
||||
crashing_pid = cred->pid; |
||||
} |
||||
} |
||||
|
||||
ASSERT_NE(crashing_pid, -1); |
||||
ASSERT_NE(signal_fd, -1); |
||||
|
||||
char templ[] = TEMPDIR "/exception-handler-unittest-XXXXXX"; |
||||
mktemp(templ); |
||||
ASSERT_TRUE(WriteMinidump(templ, crashing_pid, context, |
||||
kCrashContextSize)); |
||||
static const char b = 0; |
||||
HANDLE_EINTR(write(signal_fd, &b, 1)); |
||||
|
||||
int status; |
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); |
||||
ASSERT_TRUE(WIFSIGNALED(status)); |
||||
ASSERT_EQ(WTERMSIG(status), SIGSEGV); |
||||
|
||||
struct stat st; |
||||
ASSERT_EQ(stat(templ, &st), 0); |
||||
ASSERT_GT(st.st_size, 0u); |
||||
unlink(templ); |
||||
} |
@ -1,105 +0,0 @@ |
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ |
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ |
||||
|
||||
#include <stdint.h> |
||||
#include <unistd.h> |
||||
#include <limits.h> |
||||
#include <assert.h> |
||||
#include <errno.h> |
||||
#include <string.h> |
||||
|
||||
#include "third_party/lss/linux_syscall_support.h" |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
// A class for enumerating a directory without using diropen/readdir or other
|
||||
// functions which may allocate memory.
|
||||
class DirectoryReader { |
||||
public: |
||||
DirectoryReader(int fd) |
||||
: fd_(fd), |
||||
buf_used_(0) { |
||||
} |
||||
|
||||
// Return the next entry from the directory
|
||||
// name: (output) the NUL terminated entry name
|
||||
//
|
||||
// Returns true iff successful (false on EOF).
|
||||
//
|
||||
// After calling this, one must call |PopEntry| otherwise you'll get the same
|
||||
// entry over and over.
|
||||
bool GetNextEntry(const char** name) { |
||||
struct kernel_dirent* const dent = |
||||
reinterpret_cast<kernel_dirent*>(buf_); |
||||
|
||||
if (buf_used_ == 0) { |
||||
// need to read more entries.
|
||||
const int n = sys_getdents(fd_, dent, sizeof(buf_)); |
||||
if (n < 0) { |
||||
return false; |
||||
} else if (n == 0) { |
||||
hit_eof_ = true; |
||||
} else { |
||||
buf_used_ += n; |
||||
} |
||||
} |
||||
|
||||
if (buf_used_ == 0 && hit_eof_) |
||||
return false; |
||||
|
||||
assert(buf_used_ > 0); |
||||
|
||||
*name = dent->d_name; |
||||
return true; |
||||
} |
||||
|
||||
void PopEntry() { |
||||
if (!buf_used_) |
||||
return; |
||||
|
||||
const struct kernel_dirent* const dent = |
||||
reinterpret_cast<kernel_dirent*>(buf_); |
||||
|
||||
buf_used_ -= dent->d_reclen; |
||||
memmove(buf_, buf_ + dent->d_reclen, buf_used_); |
||||
} |
||||
|
||||
private: |
||||
const int fd_; |
||||
bool hit_eof_; |
||||
unsigned buf_used_; |
||||
uint8_t buf_[sizeof(struct kernel_dirent) + NAME_MAX + 1]; |
||||
}; |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
|
@ -1,77 +0,0 @@ |
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <set> |
||||
#include <string> |
||||
|
||||
#include <dirent.h> |
||||
#include <fcntl.h> |
||||
#include <sys/types.h> |
||||
|
||||
#include "client/linux/minidump_writer/directory_reader.h" |
||||
#include "breakpad_googletest_includes.h" |
||||
|
||||
using namespace google_breakpad; |
||||
|
||||
namespace { |
||||
typedef testing::Test DirectoryReaderTest; |
||||
} |
||||
|
||||
TEST(DirectoryReaderTest, CompareResults) { |
||||
std::set<std::string> dent_set; |
||||
|
||||
DIR *const dir = opendir("/proc/self"); |
||||
ASSERT_TRUE(dir != NULL); |
||||
|
||||
struct dirent* dent; |
||||
while ((dent = readdir(dir))) |
||||
dent_set.insert(dent->d_name); |
||||
|
||||
closedir(dir); |
||||
|
||||
const int fd = open("/proc/self", O_DIRECTORY | O_RDONLY); |
||||
ASSERT_GE(fd, 0); |
||||
|
||||
DirectoryReader dir_reader(fd); |
||||
unsigned seen = 0; |
||||
|
||||
const char* name; |
||||
while (dir_reader.GetNextEntry(&name)) { |
||||
ASSERT_TRUE(dent_set.find(name) != dent_set.end()); |
||||
seen++; |
||||
dir_reader.PopEntry(); |
||||
} |
||||
|
||||
ASSERT_TRUE(dent_set.find("status") != dent_set.end()); |
||||
ASSERT_TRUE(dent_set.find("stat") != dent_set.end()); |
||||
ASSERT_TRUE(dent_set.find("cmdline") != dent_set.end()); |
||||
|
||||
ASSERT_EQ(dent_set.size(), seen); |
||||
close(fd); |
||||
} |
@ -1,130 +0,0 @@ |
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ |
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ |
||||
|
||||
#include <stdint.h> |
||||
#include <assert.h> |
||||
#include <string.h> |
||||
|
||||
#include "third_party/lss/linux_syscall_support.h" |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
// A class for reading a file, line by line, without using fopen/fgets or other
|
||||
// functions which may allocate memory.
|
||||
class LineReader { |
||||
public: |
||||
LineReader(int fd) |
||||
: fd_(fd), |
||||
hit_eof_(false), |
||||
buf_used_(0) { |
||||
} |
||||
|
||||
// The maximum length of a line.
|
||||
static const size_t kMaxLineLen = 512; |
||||
|
||||
// Return the next line from the file.
|
||||
// line: (output) a pointer to the start of the line. The line is NUL
|
||||
// terminated.
|
||||
// len: (output) the length of the line (not inc the NUL byte)
|
||||
//
|
||||
// Returns true iff successful (false on EOF).
|
||||
//
|
||||
// One must call |PopLine| after this function, otherwise you'll continue to
|
||||
// get the same line over and over.
|
||||
bool GetNextLine(const char **line, unsigned *len) { |
||||
for (;;) { |
||||
if (buf_used_ == 0 && hit_eof_) |
||||
return false; |
||||
|
||||
for (unsigned i = 0; i < buf_used_; ++i) { |
||||
if (buf_[i] == '\n' || buf_[i] == 0) { |
||||
buf_[i] = 0; |
||||
*len = i; |
||||
*line = buf_; |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
if (buf_used_ == sizeof(buf_)) { |
||||
// we scanned the whole buffer and didn't find an end-of-line marker.
|
||||
// This line is too long to process.
|
||||
return false; |
||||
} |
||||
|
||||
// We didn't find any end-of-line terminators in the buffer. However, if
|
||||
// this is the last line in the file it might not have one:
|
||||
if (hit_eof_) { |
||||
assert(buf_used_); |
||||
// There's room for the NUL because of the buf_used_ == sizeof(buf_)
|
||||
// check above.
|
||||
buf_[buf_used_] = 0; |
||||
*len = buf_used_; |
||||
buf_used_ += 1; // since we appended the NUL.
|
||||
*line = buf_; |
||||
return true; |
||||
} |
||||
|
||||
// Otherwise, we should pull in more data from the file
|
||||
const ssize_t n = sys_read(fd_, buf_ + buf_used_, |
||||
sizeof(buf_) - buf_used_); |
||||
if (n < 0) { |
||||
return false; |
||||
} else if (n == 0) { |
||||
hit_eof_ = true; |
||||
} else { |
||||
buf_used_ += n; |
||||
} |
||||
|
||||
// At this point, we have either set the hit_eof_ flag, or we have more
|
||||
// data to process...
|
||||
} |
||||
} |
||||
|
||||
void PopLine(unsigned len) { |
||||
// len doesn't include the NUL byte at the end.
|
||||
|
||||
assert(buf_used_ >= len + 1); |
||||
buf_used_ -= len + 1; |
||||
memmove(buf_, buf_ + len + 1, buf_used_); |
||||
} |
||||
|
||||
private: |
||||
const int fd_; |
||||
|
||||
bool hit_eof_; |
||||
unsigned buf_used_; |
||||
char buf_[kMaxLineLen]; |
||||
}; |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
|
@ -1,190 +0,0 @@ |
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <stdlib.h> |
||||
#include <unistd.h> |
||||
#include <sys/types.h> |
||||
|
||||
#include "client/linux/minidump_writer/line_reader.h" |
||||
#include "breakpad_googletest_includes.h" |
||||
|
||||
using namespace google_breakpad; |
||||
|
||||
#if !defined(__ANDROID__) |
||||
#define TEMPDIR "/tmp" |
||||
#else |
||||
#define TEMPDIR "/data/local/tmp" |
||||
#endif |
||||
|
||||
static int TemporaryFile() { |
||||
static const char templ[] = TEMPDIR "/line-reader-unittest-XXXXXX"; |
||||
char templ_copy[sizeof(templ)]; |
||||
memcpy(templ_copy, templ, sizeof(templ)); |
||||
const int fd = mkstemp(templ_copy); |
||||
if (fd >= 0) |
||||
unlink(templ_copy); |
||||
|
||||
return fd; |
||||
} |
||||
|
||||
namespace { |
||||
typedef testing::Test LineReaderTest; |
||||
} |
||||
|
||||
TEST(LineReaderTest, EmptyFile) { |
||||
const int fd = TemporaryFile(); |
||||
LineReader reader(fd); |
||||
|
||||
const char *line; |
||||
unsigned len; |
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len)); |
||||
|
||||
close(fd); |
||||
} |
||||
|
||||
TEST(LineReaderTest, OneLineTerminated) { |
||||
const int fd = TemporaryFile(); |
||||
write(fd, "a\n", 2); |
||||
lseek(fd, 0, SEEK_SET); |
||||
LineReader reader(fd); |
||||
|
||||
const char *line; |
||||
unsigned int len; |
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len)); |
||||
ASSERT_EQ(len, (unsigned int)1); |
||||
ASSERT_EQ(line[0], 'a'); |
||||
ASSERT_EQ(line[1], 0); |
||||
reader.PopLine(len); |
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len)); |
||||
|
||||
close(fd); |
||||
} |
||||
|
||||
TEST(LineReaderTest, OneLine) { |
||||
const int fd = TemporaryFile(); |
||||
write(fd, "a", 1); |
||||
lseek(fd, 0, SEEK_SET); |
||||
LineReader reader(fd); |
||||
|
||||
const char *line; |
||||
unsigned len; |
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len)); |
||||
ASSERT_EQ(len, (unsigned)1); |
||||
ASSERT_EQ(line[0], 'a'); |
||||
ASSERT_EQ(line[1], 0); |
||||
reader.PopLine(len); |
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len)); |
||||
|
||||
close(fd); |
||||
} |
||||
|
||||
TEST(LineReaderTest, TwoLinesTerminated) { |
||||
const int fd = TemporaryFile(); |
||||
write(fd, "a\nb\n", 4); |
||||
lseek(fd, 0, SEEK_SET); |
||||
LineReader reader(fd); |
||||
|
||||
const char *line; |
||||
unsigned len; |
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len)); |
||||
ASSERT_EQ(len, (unsigned)1); |
||||
ASSERT_EQ(line[0], 'a'); |
||||
ASSERT_EQ(line[1], 0); |
||||
reader.PopLine(len); |
||||
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len)); |
||||
ASSERT_EQ(len, (unsigned)1); |
||||
ASSERT_EQ(line[0], 'b'); |
||||
ASSERT_EQ(line[1], 0); |
||||
reader.PopLine(len); |
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len)); |
||||
|
||||
close(fd); |
||||
} |
||||
|
||||
TEST(LineReaderTest, TwoLines) { |
||||
const int fd = TemporaryFile(); |
||||
write(fd, "a\nb", 3); |
||||
lseek(fd, 0, SEEK_SET); |
||||
LineReader reader(fd); |
||||
|
||||
const char *line; |
||||
unsigned len; |
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len)); |
||||
ASSERT_EQ(len, (unsigned)1); |
||||
ASSERT_EQ(line[0], 'a'); |
||||
ASSERT_EQ(line[1], 0); |
||||
reader.PopLine(len); |
||||
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len)); |
||||
ASSERT_EQ(len, (unsigned)1); |
||||
ASSERT_EQ(line[0], 'b'); |
||||
ASSERT_EQ(line[1], 0); |
||||
reader.PopLine(len); |
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len)); |
||||
|
||||
close(fd); |
||||
} |
||||
|
||||
TEST(LineReaderTest, MaxLength) { |
||||
const int fd = TemporaryFile(); |
||||
char l[LineReader::kMaxLineLen - 1]; |
||||
memset(l, 'a', sizeof(l)); |
||||
write(fd, l, sizeof(l)); |
||||
lseek(fd, 0, SEEK_SET); |
||||
LineReader reader(fd); |
||||
|
||||
const char *line; |
||||
unsigned len; |
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len)); |
||||
ASSERT_EQ(len, sizeof(l)); |
||||
ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0); |
||||
ASSERT_EQ(line[len], 0); |
||||
|
||||
close(fd); |
||||
} |
||||
|
||||
TEST(LineReaderTest, TooLong) { |
||||
const int fd = TemporaryFile(); |
||||
char l[LineReader::kMaxLineLen]; |
||||
memset(l, 'a', sizeof(l)); |
||||
write(fd, l, sizeof(l)); |
||||
lseek(fd, 0, SEEK_SET); |
||||
LineReader reader(fd); |
||||
|
||||
const char *line; |
||||
unsigned len; |
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len)); |
||||
|
||||
close(fd); |
||||
} |
@ -1,556 +0,0 @@ |
||||
// Copyright (c) 2010, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// This code deals with the mechanics of getting information about a crashed
|
||||
// process. Since this code may run in a compromised address space, the same
|
||||
// rules apply as detailed at the top of minidump_writer.h: no libc calls and
|
||||
// use the alternative allocator.
|
||||
|
||||
#include "client/linux/minidump_writer/linux_dumper.h" |
||||
|
||||
#include <asm/ptrace.h> |
||||
#include <assert.h> |
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <limits.h> |
||||
#if !defined(__ANDROID__) |
||||
#include <link.h> |
||||
#endif |
||||
#include <stddef.h> |
||||
#include <stdlib.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <sys/ptrace.h> |
||||
#include <sys/wait.h> |
||||
#include <unistd.h> |
||||
|
||||
#include <algorithm> |
||||
|
||||
#include "client/linux/minidump_writer/directory_reader.h" |
||||
#include "client/linux/minidump_writer/line_reader.h" |
||||
#include "common/linux/file_id.h" |
||||
#include "common/linux/linux_libc_support.h" |
||||
#include "third_party/lss/linux_syscall_support.h" |
||||
|
||||
static const char kMappedFileUnsafePrefix[] = "/dev/"; |
||||
static const char kDeletedSuffix[] = " (deleted)"; |
||||
|
||||
// Suspend a thread by attaching to it.
|
||||
static bool SuspendThread(pid_t pid) { |
||||
// This may fail if the thread has just died or debugged.
|
||||
errno = 0; |
||||
if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 && |
||||
errno != 0) { |
||||
return false; |
||||
} |
||||
while (sys_waitpid(pid, NULL, __WALL) < 0) { |
||||
if (errno != EINTR) { |
||||
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); |
||||
return false; |
||||
} |
||||
} |
||||
#if defined(__i386) || defined(__x86_64) |
||||
// On x86, the stack pointer is NULL or -1, when executing trusted code in
|
||||
// the seccomp sandbox. Not only does this cause difficulties down the line
|
||||
// when trying to dump the thread's stack, it also results in the minidumps
|
||||
// containing information about the trusted threads. This information is
|
||||
// generally completely meaningless and just pollutes the minidumps.
|
||||
// We thus test the stack pointer and exclude any threads that are part of
|
||||
// the seccomp sandbox's trusted code.
|
||||
user_regs_struct regs; |
||||
if (sys_ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1 || |
||||
#if defined(__i386) |
||||
!regs.esp |
||||
#elif defined(__x86_64) |
||||
!regs.rsp |
||||
#endif |
||||
) { |
||||
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); |
||||
return false; |
||||
} |
||||
#endif |
||||
return true; |
||||
} |
||||
|
||||
// Resume a thread by detaching from it.
|
||||
static bool ResumeThread(pid_t pid) { |
||||
return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0; |
||||
} |
||||
|
||||
inline static bool IsMappedFileOpenUnsafe( |
||||
const google_breakpad::MappingInfo& mapping) { |
||||
// It is unsafe to attempt to open a mapped file that lives under /dev,
|
||||
// because the semantics of the open may be driver-specific so we'd risk
|
||||
// hanging the crash dumper. And a file in /dev/ almost certainly has no
|
||||
// ELF file identifier anyways.
|
||||
return my_strncmp(mapping.name, |
||||
kMappedFileUnsafePrefix, |
||||
sizeof(kMappedFileUnsafePrefix) - 1) == 0; |
||||
} |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
LinuxDumper::LinuxDumper(int pid) |
||||
: pid_(pid), |
||||
threads_suspended_(false), |
||||
threads_(&allocator_, 8), |
||||
mappings_(&allocator_) { |
||||
} |
||||
|
||||
bool LinuxDumper::Init() { |
||||
return EnumerateThreads(&threads_) && |
||||
EnumerateMappings(&mappings_); |
||||
} |
||||
|
||||
bool LinuxDumper::ThreadsSuspend() { |
||||
if (threads_suspended_) |
||||
return true; |
||||
for (size_t i = 0; i < threads_.size(); ++i) { |
||||
if (!SuspendThread(threads_[i])) { |
||||
// If the thread either disappeared before we could attach to it, or if
|
||||
// it was part of the seccomp sandbox's trusted code, it is OK to
|
||||
// silently drop it from the minidump.
|
||||
memmove(&threads_[i], &threads_[i+1], |
||||
(threads_.size() - i - 1) * sizeof(threads_[i])); |
||||
threads_.resize(threads_.size() - 1); |
||||
--i; |
||||
} |
||||
} |
||||
threads_suspended_ = true; |
||||
return threads_.size() > 0; |
||||
} |
||||
|
||||
bool LinuxDumper::ThreadsResume() { |
||||
if (!threads_suspended_) |
||||
return false; |
||||
bool good = true; |
||||
for (size_t i = 0; i < threads_.size(); ++i) |
||||
good &= ResumeThread(threads_[i]); |
||||
threads_suspended_ = false; |
||||
return good; |
||||
} |
||||
|
||||
void |
||||
LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const { |
||||
assert(path); |
||||
if (!path) { |
||||
return; |
||||
} |
||||
|
||||
path[0] = '\0'; |
||||
|
||||
const unsigned pid_len = my_int_len(pid); |
||||
|
||||
assert(node); |
||||
if (!node) { |
||||
return; |
||||
} |
||||
|
||||
size_t node_len = my_strlen(node); |
||||
assert(node_len < NAME_MAX); |
||||
if (node_len >= NAME_MAX) { |
||||
return; |
||||
} |
||||
|
||||
assert(node_len > 0); |
||||
if (node_len == 0) { |
||||
return; |
||||
} |
||||
|
||||
assert(pid > 0); |
||||
if (pid <= 0) { |
||||
return; |
||||
} |
||||
|
||||
const size_t total_length = 6 + pid_len + 1 + node_len; |
||||
|
||||
assert(total_length < NAME_MAX); |
||||
if (total_length >= NAME_MAX) { |
||||
return; |
||||
} |
||||
|
||||
memcpy(path, "/proc/", 6); |
||||
my_itos(path + 6, pid, pid_len); |
||||
memcpy(path + 6 + pid_len, "/", 1); |
||||
memcpy(path + 6 + pid_len + 1, node, node_len); |
||||
path[total_length] = '\0'; |
||||
} |
||||
|
||||
bool |
||||
LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping, |
||||
bool member, |
||||
unsigned int mapping_id, |
||||
uint8_t identifier[sizeof(MDGUID)]) |
||||
{ |
||||
assert(!member || mapping_id < mappings_.size()); |
||||
my_memset(identifier, 0, sizeof(MDGUID)); |
||||
if (IsMappedFileOpenUnsafe(mapping)) |
||||
return false; |
||||
|
||||
char filename[NAME_MAX]; |
||||
size_t filename_len = my_strlen(mapping.name); |
||||
assert(filename_len < NAME_MAX); |
||||
if (filename_len >= NAME_MAX) |
||||
return false; |
||||
memcpy(filename, mapping.name, filename_len); |
||||
filename[filename_len] = '\0'; |
||||
bool filename_modified = HandleDeletedFileInMapping(filename); |
||||
|
||||
int fd = sys_open(filename, O_RDONLY, 0); |
||||
if (fd < 0) |
||||
return false; |
||||
struct kernel_stat st; |
||||
if (sys_fstat(fd, &st) != 0) { |
||||
sys_close(fd); |
||||
return false; |
||||
} |
||||
#if defined(__x86_64) |
||||
#define sys_mmap2 sys_mmap |
||||
#endif |
||||
void* base = sys_mmap2(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); |
||||
sys_close(fd); |
||||
if (base == MAP_FAILED) |
||||
return false; |
||||
|
||||
bool success = FileID::ElfFileIdentifierFromMappedFile(base, identifier); |
||||
sys_munmap(base, st.st_size); |
||||
if (success && member && filename_modified) { |
||||
mappings_[mapping_id]->name[filename_len - |
||||
sizeof(kDeletedSuffix) + 1] = '\0'; |
||||
} |
||||
|
||||
return success; |
||||
} |
||||
|
||||
void* |
||||
LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const { |
||||
char auxv_path[NAME_MAX]; |
||||
BuildProcPath(auxv_path, pid, "auxv"); |
||||
|
||||
// If BuildProcPath errors out due to invalid input, we'll handle it when
|
||||
// we try to sys_open the file.
|
||||
|
||||
// Find the AT_SYSINFO_EHDR entry for linux-gate.so
|
||||
// See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
|
||||
// information.
|
||||
int fd = sys_open(auxv_path, O_RDONLY, 0); |
||||
if (fd < 0) { |
||||
return NULL; |
||||
} |
||||
|
||||
elf_aux_entry one_aux_entry; |
||||
while (sys_read(fd, |
||||
&one_aux_entry, |
||||
sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) && |
||||
one_aux_entry.a_type != AT_NULL) { |
||||
if (one_aux_entry.a_type == AT_SYSINFO_EHDR) { |
||||
close(fd); |
||||
return reinterpret_cast<void*>(one_aux_entry.a_un.a_val); |
||||
} |
||||
} |
||||
close(fd); |
||||
return NULL; |
||||
} |
||||
|
||||
bool |
||||
LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const { |
||||
char maps_path[NAME_MAX]; |
||||
BuildProcPath(maps_path, pid_, "maps"); |
||||
|
||||
// linux_gate_loc is the beginning of the kernel's mapping of
|
||||
// linux-gate.so in the process. It doesn't actually show up in the
|
||||
// maps list as a filename, so we use the aux vector to find it's
|
||||
// load location and special case it's entry when creating the list
|
||||
// of mappings.
|
||||
const void* linux_gate_loc; |
||||
linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_); |
||||
|
||||
const int fd = sys_open(maps_path, O_RDONLY, 0); |
||||
if (fd < 0) |
||||
return false; |
||||
LineReader* const line_reader = new(allocator_) LineReader(fd); |
||||
|
||||
const char* line; |
||||
unsigned line_len; |
||||
while (line_reader->GetNextLine(&line, &line_len)) { |
||||
uintptr_t start_addr, end_addr, offset; |
||||
|
||||
const char* i1 = my_read_hex_ptr(&start_addr, line); |
||||
if (*i1 == '-') { |
||||
const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1); |
||||
if (*i2 == ' ') { |
||||
const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */); |
||||
if (*i3 == ' ') { |
||||
const char* name = NULL; |
||||
// Only copy name if the name is a valid path name, or if
|
||||
// it's the VDSO image.
|
||||
if (((name = my_strchr(line, '/')) == NULL) && |
||||
linux_gate_loc && |
||||
reinterpret_cast<void*>(start_addr) == linux_gate_loc) { |
||||
name = kLinuxGateLibraryName; |
||||
offset = 0; |
||||
} |
||||
// Merge adjacent mappings with the same name into one module,
|
||||
// assuming they're a single library mapped by the dynamic linker
|
||||
if (name && result->size()) { |
||||
MappingInfo* module = (*result)[result->size() - 1]; |
||||
if ((start_addr == module->start_addr + module->size) && |
||||
(my_strlen(name) == my_strlen(module->name)) && |
||||
(my_strncmp(name, module->name, my_strlen(name)) == 0)) { |
||||
module->size = end_addr - module->start_addr; |
||||
line_reader->PopLine(line_len); |
||||
continue; |
||||
} |
||||
} |
||||
MappingInfo* const module = new(allocator_) MappingInfo; |
||||
memset(module, 0, sizeof(MappingInfo)); |
||||
module->start_addr = start_addr; |
||||
module->size = end_addr - start_addr; |
||||
module->offset = offset; |
||||
if (name != NULL) { |
||||
const unsigned l = my_strlen(name); |
||||
if (l < sizeof(module->name)) |
||||
memcpy(module->name, name, l); |
||||
} |
||||
result->push_back(module); |
||||
} |
||||
} |
||||
} |
||||
line_reader->PopLine(line_len); |
||||
} |
||||
|
||||
sys_close(fd); |
||||
|
||||
return result->size() > 0; |
||||
} |
||||
|
||||
// Parse /proc/$pid/task to list all the threads of the process identified by
|
||||
// pid.
|
||||
bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const { |
||||
char task_path[NAME_MAX]; |
||||
BuildProcPath(task_path, pid_, "task"); |
||||
|
||||
const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0); |
||||
if (fd < 0) |
||||
return false; |
||||
DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd); |
||||
|
||||
// The directory may contain duplicate entries which we filter by assuming
|
||||
// that they are consecutive.
|
||||
int last_tid = -1; |
||||
const char* dent_name; |
||||
while (dir_reader->GetNextEntry(&dent_name)) { |
||||
if (my_strcmp(dent_name, ".") && |
||||
my_strcmp(dent_name, "..")) { |
||||
int tid = 0; |
||||
if (my_strtoui(&tid, dent_name) && |
||||
last_tid != tid) { |
||||
last_tid = tid; |
||||
result->push_back(tid); |
||||
} |
||||
} |
||||
dir_reader->PopEntry(); |
||||
} |
||||
|
||||
sys_close(fd); |
||||
return true; |
||||
} |
||||
|
||||
// Read thread info from /proc/$pid/status.
|
||||
// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable,
|
||||
// these members are set to -1. Returns true iff all three members are
|
||||
// available.
|
||||
bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) { |
||||
assert(info != NULL); |
||||
char status_path[NAME_MAX]; |
||||
BuildProcPath(status_path, tid, "status"); |
||||
|
||||
const int fd = open(status_path, O_RDONLY); |
||||
if (fd < 0) |
||||
return false; |
||||
|
||||
LineReader* const line_reader = new(allocator_) LineReader(fd); |
||||
const char* line; |
||||
unsigned line_len; |
||||
|
||||
info->ppid = info->tgid = -1; |
||||
|
||||
while (line_reader->GetNextLine(&line, &line_len)) { |
||||
if (my_strncmp("Tgid:\t", line, 6) == 0) { |
||||
my_strtoui(&info->tgid, line + 6); |
||||
} else if (my_strncmp("PPid:\t", line, 6) == 0) { |
||||
my_strtoui(&info->ppid, line + 6); |
||||
} |
||||
|
||||
line_reader->PopLine(line_len); |
||||
} |
||||
|
||||
if (info->ppid == -1 || info->tgid == -1) |
||||
return false; |
||||
|
||||
if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1) { |
||||
return false; |
||||
} |
||||
|
||||
#if !defined(__ANDROID__) |
||||
if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) { |
||||
return false; |
||||
} |
||||
#endif |
||||
|
||||
#if defined(__i386) |
||||
if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1) |
||||
return false; |
||||
#endif |
||||
|
||||
#if defined(__i386) || defined(__x86_64) |
||||
for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) { |
||||
if (sys_ptrace( |
||||
PTRACE_PEEKUSER, tid, |
||||
reinterpret_cast<void*> (offsetof(struct user, |
||||
u_debugreg[0]) + i * |
||||
sizeof(debugreg_t)), |
||||
&info->dregs[i]) == -1) { |
||||
return false; |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
const uint8_t* stack_pointer; |
||||
#if defined(__i386) |
||||
memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp)); |
||||
#elif defined(__x86_64) |
||||
memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp)); |
||||
#elif defined(__ARM_EABI__) |
||||
memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp)); |
||||
#else |
||||
#error "This code hasn't been ported to your platform yet." |
||||
#endif |
||||
|
||||
return GetStackInfo(&info->stack, &info->stack_len, |
||||
(uintptr_t) stack_pointer); |
||||
} |
||||
|
||||
// Get information about the stack, given the stack pointer. We don't try to
|
||||
// walk the stack since we might not have all the information needed to do
|
||||
// unwind. So we just grab, up to, 32k of stack.
|
||||
bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len, |
||||
uintptr_t int_stack_pointer) { |
||||
// Move the stack pointer to the bottom of the page that it's in.
|
||||
const uintptr_t page_size = getpagesize(); |
||||
|
||||
uint8_t* const stack_pointer = |
||||
reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1)); |
||||
|
||||
// The number of bytes of stack which we try to capture.
|
||||
static const ptrdiff_t kStackToCapture = 32 * 1024; |
||||
|
||||
const MappingInfo* mapping = FindMapping(stack_pointer); |
||||
if (!mapping) |
||||
return false; |
||||
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr; |
||||
const ptrdiff_t distance_to_end = |
||||
static_cast<ptrdiff_t>(mapping->size) - offset; |
||||
*stack_len = distance_to_end > kStackToCapture ? |
||||
kStackToCapture : distance_to_end; |
||||
*stack = stack_pointer; |
||||
return true; |
||||
} |
||||
|
||||
// static
|
||||
void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src, |
||||
size_t length) { |
||||
unsigned long tmp = 55; |
||||
size_t done = 0; |
||||
static const size_t word_size = sizeof(tmp); |
||||
uint8_t* const local = (uint8_t*) dest; |
||||
uint8_t* const remote = (uint8_t*) src; |
||||
|
||||
while (done < length) { |
||||
const size_t l = length - done > word_size ? word_size : length - done; |
||||
if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) { |
||||
tmp = 0; |
||||
} |
||||
memcpy(local + done, &tmp, l); |
||||
done += l; |
||||
} |
||||
} |
||||
|
||||
// Find the mapping which the given memory address falls in.
|
||||
const MappingInfo* LinuxDumper::FindMapping(const void* address) const { |
||||
const uintptr_t addr = (uintptr_t) address; |
||||
|
||||
for (size_t i = 0; i < mappings_.size(); ++i) { |
||||
const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr); |
||||
if (addr >= start && addr - start < mappings_[i]->size) |
||||
return mappings_[i]; |
||||
} |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
bool LinuxDumper::HandleDeletedFileInMapping(char* path) const { |
||||
static const size_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1; |
||||
|
||||
// Check for ' (deleted)' in |path|.
|
||||
// |path| has to be at least as long as "/x (deleted)".
|
||||
const size_t path_len = my_strlen(path); |
||||
if (path_len < kDeletedSuffixLen + 2) |
||||
return false; |
||||
if (my_strncmp(path + path_len - kDeletedSuffixLen, kDeletedSuffix, |
||||
kDeletedSuffixLen) != 0) { |
||||
return false; |
||||
} |
||||
|
||||
// Check |path| against the /proc/pid/exe 'symlink'.
|
||||
char exe_link[NAME_MAX]; |
||||
char new_path[NAME_MAX]; |
||||
BuildProcPath(exe_link, pid_, "exe"); |
||||
ssize_t new_path_len = sys_readlink(exe_link, new_path, NAME_MAX); |
||||
if (new_path_len <= 0 || new_path_len == NAME_MAX) |
||||
return false; |
||||
new_path[new_path_len] = '\0'; |
||||
if (my_strcmp(path, new_path) != 0) |
||||
return false; |
||||
|
||||
// Check to see if someone actually named their executable 'foo (deleted)'.
|
||||
struct kernel_stat exe_stat; |
||||
struct kernel_stat new_path_stat; |
||||
if (sys_stat(exe_link, &exe_stat) == 0 && |
||||
sys_stat(new_path, &new_path_stat) == 0 && |
||||
exe_stat.st_dev == new_path_stat.st_dev && |
||||
exe_stat.st_ino == new_path_stat.st_ino) { |
||||
return false; |
||||
} |
||||
|
||||
memcpy(path, exe_link, NAME_MAX); |
||||
return true; |
||||
} |
||||
|
||||
} // namespace google_breakpad
|
@ -1,193 +0,0 @@ |
||||
// Copyright (c) 2010, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_ |
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_ |
||||
|
||||
#include <elf.h> |
||||
#include <linux/limits.h> |
||||
#include <stdint.h> |
||||
#include <sys/types.h> |
||||
#if !defined(__ANDROID__) |
||||
#include <sys/user.h> |
||||
#endif |
||||
|
||||
#include "common/memory.h" |
||||
#include "google_breakpad/common/minidump_format.h" |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
#if defined(__i386) || defined(__x86_64) |
||||
typedef typeof(((struct user*) 0)->u_debugreg[0]) debugreg_t; |
||||
#endif |
||||
|
||||
// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
|
||||
#if defined(__i386) || defined(__ARM_EABI__) |
||||
#if !defined(__ANDROID__) |
||||
typedef Elf32_auxv_t elf_aux_entry; |
||||
#else |
||||
// Android is missing this structure definition
|
||||
typedef struct |
||||
{ |
||||
uint32_t a_type; /* Entry type */ |
||||
union |
||||
{ |
||||
uint32_t a_val; /* Integer value */ |
||||
} a_un; |
||||
} elf_aux_entry; |
||||
|
||||
#if !defined(AT_SYSINFO_EHDR) |
||||
#define AT_SYSINFO_EHDR 33 |
||||
#endif |
||||
#endif // __ANDROID__
|
||||
#elif defined(__x86_64__) |
||||
typedef Elf64_auxv_t elf_aux_entry; |
||||
#endif |
||||
// When we find the VDSO mapping in the process's address space, this
|
||||
// is the name we use for it when writing it to the minidump.
|
||||
// This should always be less than NAME_MAX!
|
||||
const char kLinuxGateLibraryName[] = "linux-gate.so"; |
||||
|
||||
// We produce one of these structures for each thread in the crashed process.
|
||||
struct ThreadInfo { |
||||
pid_t tgid; // thread group id
|
||||
pid_t ppid; // parent process
|
||||
|
||||
// Even on platforms where the stack grows down, the following will point to
|
||||
// the smallest address in the stack.
|
||||
const void* stack; // pointer to the stack area
|
||||
size_t stack_len; // length of the stack to copy
|
||||
|
||||
|
||||
#if defined(__i386) || defined(__x86_64) |
||||
user_regs_struct regs; |
||||
user_fpregs_struct fpregs; |
||||
static const unsigned kNumDebugRegisters = 8; |
||||
debugreg_t dregs[8]; |
||||
#if defined(__i386) |
||||
user_fpxregs_struct fpxregs; |
||||
#endif // defined(__i386)
|
||||
|
||||
#elif defined(__ARM_EABI__) |
||||
// Mimicking how strace does this(see syscall.c, search for GETREGS)
|
||||
#if defined(__ANDROID__) |
||||
struct pt_regs regs; |
||||
#else |
||||
struct user_regs regs; |
||||
struct user_fpregs fpregs; |
||||
#endif // __ANDROID__
|
||||
#endif |
||||
}; |
||||
|
||||
// One of these is produced for each mapping in the process (i.e. line in
|
||||
// /proc/$x/maps).
|
||||
struct MappingInfo { |
||||
uintptr_t start_addr; |
||||
size_t size; |
||||
size_t offset; // offset into the backed file.
|
||||
char name[NAME_MAX]; |
||||
}; |
||||
|
||||
class LinuxDumper { |
||||
public: |
||||
explicit LinuxDumper(pid_t pid); |
||||
|
||||
// Parse the data for |threads| and |mappings|.
|
||||
bool Init(); |
||||
|
||||
// Suspend/resume all threads in the given process.
|
||||
bool ThreadsSuspend(); |
||||
bool ThreadsResume(); |
||||
|
||||
// Read information about the given thread. Returns true on success. One must
|
||||
// have called |ThreadsSuspend| first.
|
||||
bool ThreadInfoGet(pid_t tid, ThreadInfo* info); |
||||
|
||||
// These are only valid after a call to |Init|.
|
||||
const wasteful_vector<pid_t> &threads() { return threads_; } |
||||
const wasteful_vector<MappingInfo*> &mappings() { return mappings_; } |
||||
const MappingInfo* FindMapping(const void* address) const; |
||||
|
||||
// Find a block of memory to take as the stack given the top of stack pointer.
|
||||
// stack: (output) the lowest address in the memory area
|
||||
// stack_len: (output) the length of the memory area
|
||||
// stack_top: the current top of the stack
|
||||
bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top); |
||||
|
||||
PageAllocator* allocator() { return &allocator_; } |
||||
|
||||
// memcpy from a remote process.
|
||||
static void CopyFromProcess(void* dest, pid_t child, const void* src, |
||||
size_t length); |
||||
|
||||
// Builds a proc path for a certain pid for a node. path is a
|
||||
// character array that is overwritten, and node is the final node
|
||||
// without any slashes.
|
||||
void BuildProcPath(char* path, pid_t pid, const char* node) const; |
||||
|
||||
// Generate a File ID from the .text section of a mapped entry.
|
||||
// If not a member, mapping_id is ignored.
|
||||
bool ElfFileIdentifierForMapping(const MappingInfo& mapping, |
||||
bool member, |
||||
unsigned int mapping_id, |
||||
uint8_t identifier[sizeof(MDGUID)]); |
||||
|
||||
// Utility method to find the location of where the kernel has
|
||||
// mapped linux-gate.so in memory(shows up in /proc/pid/maps as
|
||||
// [vdso], but we can't guarantee that it's the only virtual dynamic
|
||||
// shared object. Parsing the auxilary vector for AT_SYSINFO_EHDR
|
||||
// is the safest way to go.)
|
||||
void* FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const; |
||||
private: |
||||
bool EnumerateMappings(wasteful_vector<MappingInfo*>* result) const; |
||||
bool EnumerateThreads(wasteful_vector<pid_t>* result) const; |
||||
|
||||
// For the case where a running program has been deleted, it'll show up in
|
||||
// /proc/pid/maps as "/path/to/program (deleted)". If this is the case, then
|
||||
// see if '/path/to/program (deleted)' matches /proc/pid/exe and return
|
||||
// /proc/pid/exe in |path| so ELF identifier generation works correctly. This
|
||||
// also checks to see if '/path/to/program (deleted)' exists, so it does not
|
||||
// get fooled by a poorly named binary.
|
||||
// For programs that don't end with ' (deleted)', this is a no-op.
|
||||
// This assumes |path| is a buffer with length NAME_MAX.
|
||||
// Returns true if |path| is modified.
|
||||
bool HandleDeletedFileInMapping(char* path) const; |
||||
|
||||
const pid_t pid_; |
||||
|
||||
mutable PageAllocator allocator_; |
||||
|
||||
bool threads_suspended_; |
||||
wasteful_vector<pid_t> threads_; // the ids of all the threads
|
||||
wasteful_vector<MappingInfo*> mappings_; // info from /proc/<pid>/maps
|
||||
}; |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_
|
@ -1,354 +0,0 @@ |
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <string> |
||||
|
||||
#include <fcntl.h> |
||||
#include <limits.h> |
||||
#include <unistd.h> |
||||
#include <signal.h> |
||||
#include <stdint.h> |
||||
#include <sys/mman.h> |
||||
#include <sys/poll.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/types.h> |
||||
|
||||
#include "breakpad_googletest_includes.h" |
||||
#include "client/linux/minidump_writer/linux_dumper.h" |
||||
#include "common/linux/eintr_wrapper.h" |
||||
#include "common/linux/file_id.h" |
||||
#include "common/memory.h" |
||||
|
||||
using std::string; |
||||
using namespace google_breakpad; |
||||
|
||||
namespace { |
||||
typedef testing::Test LinuxDumperTest; |
||||
|
||||
string GetHelperBinary() { |
||||
// Locate helper binary next to the current binary.
|
||||
char self_path[PATH_MAX]; |
||||
if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) { |
||||
return ""; |
||||
} |
||||
string helper_path(self_path); |
||||
size_t pos = helper_path.rfind('/'); |
||||
if (pos == string::npos) { |
||||
return ""; |
||||
} |
||||
helper_path.erase(pos + 1); |
||||
helper_path += "linux_dumper_unittest_helper"; |
||||
|
||||
return helper_path; |
||||
} |
||||
|
||||
} |
||||
|
||||
TEST(LinuxDumperTest, Setup) { |
||||
LinuxDumper dumper(getpid()); |
||||
} |
||||
|
||||
TEST(LinuxDumperTest, FindMappings) { |
||||
LinuxDumper dumper(getpid()); |
||||
ASSERT_TRUE(dumper.Init()); |
||||
|
||||
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid))); |
||||
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf))); |
||||
ASSERT_FALSE(dumper.FindMapping(NULL)); |
||||
} |
||||
|
||||
TEST(LinuxDumperTest, ThreadList) { |
||||
LinuxDumper dumper(getpid()); |
||||
ASSERT_TRUE(dumper.Init()); |
||||
|
||||
ASSERT_GE(dumper.threads().size(), (size_t)1); |
||||
bool found = false; |
||||
for (size_t i = 0; i < dumper.threads().size(); ++i) { |
||||
if (dumper.threads()[i] == getpid()) { |
||||
found = true; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Helper stack class to close a file descriptor and unmap
|
||||
// a mmap'ed mapping.
|
||||
class StackHelper { |
||||
public: |
||||
StackHelper(int fd, char* mapping, size_t size) |
||||
: fd_(fd), mapping_(mapping), size_(size) {} |
||||
~StackHelper() { |
||||
munmap(mapping_, size_); |
||||
close(fd_); |
||||
} |
||||
|
||||
private: |
||||
int fd_; |
||||
char* mapping_; |
||||
size_t size_; |
||||
}; |
||||
|
||||
TEST(LinuxDumperTest, MergedMappings) { |
||||
string helper_path(GetHelperBinary()); |
||||
if (helper_path.empty()) { |
||||
FAIL() << "Couldn't find helper binary"; |
||||
exit(1); |
||||
} |
||||
|
||||
// mmap two segments out of the helper binary, one
|
||||
// enclosed in the other, but with different protections.
|
||||
const size_t kPageSize = sysconf(_SC_PAGESIZE); |
||||
const size_t kMappingSize = 3 * kPageSize; |
||||
int fd = open(helper_path.c_str(), O_RDONLY); |
||||
ASSERT_NE(-1, fd); |
||||
char* mapping = |
||||
reinterpret_cast<char*>(mmap(NULL, |
||||
kMappingSize, |
||||
PROT_READ, |
||||
MAP_SHARED, |
||||
fd, |
||||
0)); |
||||
ASSERT_TRUE(mapping); |
||||
|
||||
const u_int64_t kMappingAddress = reinterpret_cast<u_int64_t>(mapping); |
||||
|
||||
// Ensure that things get cleaned up.
|
||||
StackHelper helper(fd, mapping, kMappingSize); |
||||
|
||||
// Carve a page out of the first mapping with different permissions.
|
||||
char* inside_mapping = reinterpret_cast<char*>(mmap(mapping + 2 *kPageSize, |
||||
kPageSize, |
||||
PROT_NONE, |
||||
MAP_SHARED | MAP_FIXED, |
||||
fd, |
||||
// Map a different offset just to
|
||||
// better test real-world conditions.
|
||||
kPageSize)); |
||||
ASSERT_TRUE(inside_mapping); |
||||
|
||||
// Now check that LinuxDumper interpreted the mappings properly.
|
||||
LinuxDumper dumper(getpid()); |
||||
ASSERT_TRUE(dumper.Init()); |
||||
int mapping_count = 0; |
||||
for (unsigned i = 0; i < dumper.mappings().size(); ++i) { |
||||
const MappingInfo& mapping = *dumper.mappings()[i]; |
||||
if (strcmp(mapping.name, helper_path.c_str()) == 0) { |
||||
// This mapping should encompass the entire original mapped
|
||||
// range.
|
||||
EXPECT_EQ(kMappingAddress, mapping.start_addr); |
||||
EXPECT_EQ(kMappingSize, mapping.size); |
||||
EXPECT_EQ(0, mapping.offset); |
||||
mapping_count++; |
||||
} |
||||
} |
||||
EXPECT_EQ(1, mapping_count); |
||||
} |
||||
|
||||
TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) { |
||||
static const int kNumberOfThreadsInHelperProgram = 5; |
||||
char kNumberOfThreadsArgument[2]; |
||||
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram); |
||||
|
||||
int fds[2]; |
||||
ASSERT_NE(-1, pipe(fds)); |
||||
|
||||
pid_t child_pid = fork(); |
||||
if (child_pid == 0) { |
||||
// In child process.
|
||||
close(fds[0]); |
||||
|
||||
string helper_path(GetHelperBinary()); |
||||
if (helper_path.empty()) { |
||||
FAIL() << "Couldn't find helper binary"; |
||||
exit(1); |
||||
} |
||||
|
||||
// Pass the pipe fd and the number of threads as arguments.
|
||||
char pipe_fd_string[8]; |
||||
sprintf(pipe_fd_string, "%d", fds[1]); |
||||
execl(helper_path.c_str(), |
||||
"linux_dumper_unittest_helper", |
||||
pipe_fd_string, |
||||
kNumberOfThreadsArgument, |
||||
NULL); |
||||
// Kill if we get here.
|
||||
printf("Errno from exec: %d", errno); |
||||
FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno); |
||||
exit(0); |
||||
} |
||||
close(fds[1]); |
||||
// Wait for the child process to signal that it's ready.
|
||||
struct pollfd pfd; |
||||
memset(&pfd, 0, sizeof(pfd)); |
||||
pfd.fd = fds[0]; |
||||
pfd.events = POLLIN | POLLERR; |
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); |
||||
ASSERT_EQ(1, r); |
||||
ASSERT_TRUE(pfd.revents & POLLIN); |
||||
uint8_t junk; |
||||
read(fds[0], &junk, sizeof(junk)); |
||||
close(fds[0]); |
||||
|
||||
// Child is ready now.
|
||||
LinuxDumper dumper(child_pid); |
||||
ASSERT_TRUE(dumper.Init()); |
||||
EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size()); |
||||
EXPECT_TRUE(dumper.ThreadsSuspend()); |
||||
|
||||
ThreadInfo one_thread; |
||||
for(size_t i = 0; i < dumper.threads().size(); ++i) { |
||||
EXPECT_TRUE(dumper.ThreadInfoGet(dumper.threads()[i], &one_thread)); |
||||
// In the helper program, we stored a pointer to the thread id in a
|
||||
// specific register. Check that we can recover its value.
|
||||
#if defined(__ARM_EABI__) |
||||
pid_t *process_tid_location = (pid_t *)(one_thread.regs.uregs[3]); |
||||
#elif defined(__i386) |
||||
pid_t *process_tid_location = (pid_t *)(one_thread.regs.ecx); |
||||
#elif defined(__x86_64) |
||||
pid_t *process_tid_location = (pid_t *)(one_thread.regs.rcx); |
||||
#else |
||||
#error This test has not been ported to this platform. |
||||
#endif |
||||
pid_t one_thread_id; |
||||
dumper.CopyFromProcess(&one_thread_id, |
||||
dumper.threads()[i], |
||||
process_tid_location, |
||||
4); |
||||
EXPECT_EQ(dumper.threads()[i], one_thread_id); |
||||
} |
||||
kill(child_pid, SIGKILL); |
||||
} |
||||
|
||||
TEST(LinuxDumperTest, BuildProcPath) { |
||||
const pid_t pid = getpid(); |
||||
LinuxDumper dumper(pid); |
||||
|
||||
char maps_path[256] = "dummymappath"; |
||||
char maps_path_expected[256]; |
||||
snprintf(maps_path_expected, sizeof(maps_path_expected), |
||||
"/proc/%d/maps", pid); |
||||
dumper.BuildProcPath(maps_path, pid, "maps"); |
||||
ASSERT_STREQ(maps_path, maps_path_expected); |
||||
|
||||
// In release mode, we expect BuildProcPath to handle the invalid
|
||||
// parameters correctly and fill map_path with an empty
|
||||
// NULL-terminated string.
|
||||
#ifdef NDEBUG |
||||
snprintf(maps_path, sizeof(maps_path), "dummymappath"); |
||||
dumper.BuildProcPath(maps_path, 0, "maps"); |
||||
EXPECT_STREQ(maps_path, ""); |
||||
|
||||
snprintf(maps_path, sizeof(maps_path), "dummymappath"); |
||||
dumper.BuildProcPath(maps_path, getpid(), ""); |
||||
EXPECT_STREQ(maps_path, ""); |
||||
|
||||
snprintf(maps_path, sizeof(maps_path), "dummymappath"); |
||||
dumper.BuildProcPath(maps_path, getpid(), NULL); |
||||
EXPECT_STREQ(maps_path, ""); |
||||
#endif |
||||
} |
||||
|
||||
#if !defined(__ARM_EABI__) |
||||
TEST(LinuxDumperTest, MappingsIncludeLinuxGate) { |
||||
LinuxDumper dumper(getpid()); |
||||
ASSERT_TRUE(dumper.Init()); |
||||
|
||||
void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid()); |
||||
ASSERT_TRUE(linux_gate_loc); |
||||
bool found_linux_gate = false; |
||||
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings(); |
||||
const MappingInfo* mapping; |
||||
for (unsigned i = 0; i < mappings.size(); ++i) { |
||||
mapping = mappings[i]; |
||||
if (!strcmp(mapping->name, kLinuxGateLibraryName)) { |
||||
found_linux_gate = true; |
||||
break; |
||||
} |
||||
} |
||||
EXPECT_TRUE(found_linux_gate); |
||||
EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr)); |
||||
EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG)); |
||||
} |
||||
#endif |
||||
|
||||
TEST(LinuxDumperTest, FileIDsMatch) { |
||||
// Calculate the File ID of our binary using both
|
||||
// FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
|
||||
// and ensure that we get the same result from both.
|
||||
char exe_name[PATH_MAX]; |
||||
ssize_t len = readlink("/proc/self/exe", exe_name, PATH_MAX - 1); |
||||
ASSERT_NE(len, -1); |
||||
exe_name[len] = '\0'; |
||||
|
||||
int fds[2]; |
||||
ASSERT_NE(-1, pipe(fds)); |
||||
|
||||
// fork a child so we can ptrace it
|
||||
const pid_t child = fork(); |
||||
if (child == 0) { |
||||
close(fds[1]); |
||||
// now wait forever for the parent
|
||||
char b; |
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b))); |
||||
close(fds[0]); |
||||
syscall(__NR_exit); |
||||
} |
||||
close(fds[0]); |
||||
|
||||
LinuxDumper dumper(child); |
||||
ASSERT_TRUE(dumper.Init()); |
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings(); |
||||
bool found_exe = false; |
||||
unsigned i; |
||||
for (i = 0; i < mappings.size(); ++i) { |
||||
const MappingInfo* mapping = mappings[i]; |
||||
if (!strcmp(mapping->name, exe_name)) { |
||||
found_exe = true; |
||||
break; |
||||
} |
||||
} |
||||
ASSERT_TRUE(found_exe); |
||||
|
||||
uint8_t identifier1[sizeof(MDGUID)]; |
||||
uint8_t identifier2[sizeof(MDGUID)]; |
||||
EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i, |
||||
identifier1)); |
||||
FileID fileid(exe_name); |
||||
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2)); |
||||
char identifier_string1[37]; |
||||
char identifier_string2[37]; |
||||
FileID::ConvertIdentifierToString(identifier1, identifier_string1, |
||||
37); |
||||
FileID::ConvertIdentifierToString(identifier2, identifier_string2, |
||||
37); |
||||
EXPECT_STREQ(identifier_string1, identifier_string2); |
||||
close(fds[1]); |
||||
} |
@ -1,85 +0,0 @@ |
||||
// Copyright (c) 2010, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Helper program for the linux_dumper class, which creates a bunch of
|
||||
// threads. The first word of each thread's stack is set to the thread
|
||||
// id.
|
||||
|
||||
#include <pthread.h> |
||||
#include <stdint.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <sys/syscall.h> |
||||
#include <unistd.h> |
||||
|
||||
#include "third_party/lss/linux_syscall_support.h" |
||||
|
||||
#if defined(__ARM_EABI__) |
||||
#define TID_PTR_REGISTER "r3" |
||||
#elif defined(__i386) |
||||
#define TID_PTR_REGISTER "ecx" |
||||
#elif defined(__x86_64) |
||||
#define TID_PTR_REGISTER "rcx" |
||||
#else |
||||
#error This test has not been ported to this platform. |
||||
#endif |
||||
|
||||
void *thread_function(void *data) { |
||||
volatile pid_t thread_id = syscall(__NR_gettid); |
||||
register volatile pid_t *thread_id_ptr asm(TID_PTR_REGISTER) = &thread_id; |
||||
while (true) |
||||
asm volatile ("" : : "r" (thread_id_ptr)); |
||||
return NULL; |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) { |
||||
if (argc < 2) { |
||||
fprintf(stderr, |
||||
"usage: linux_dumper_unittest_helper <pipe fd> <# of threads\n"); |
||||
return 1; |
||||
} |
||||
int pipefd = atoi(argv[1]); |
||||
int num_threads = atoi(argv[2]); |
||||
if (num_threads < 1) { |
||||
fprintf(stderr, "ERROR: number of threads is 0"); |
||||
return 1; |
||||
} |
||||
pthread_t threads[num_threads]; |
||||
pthread_attr_t thread_attributes; |
||||
pthread_attr_init(&thread_attributes); |
||||
pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_DETACHED); |
||||
for (int i = 1; i < num_threads; i++) { |
||||
pthread_create(&threads[i], &thread_attributes, &thread_function, NULL); |
||||
} |
||||
// Signal parent that this process has started all threads.
|
||||
uint8_t byte = 1; |
||||
write(pipefd, &byte, sizeof(byte)); |
||||
thread_function(NULL); |
||||
return 0; |
||||
} |
@ -1,75 +0,0 @@ |
||||
/* Copyright (c) 2010, Google Inc.
|
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
||||
|
||||
/* minidump_extension_linux.h: A definition of exception codes for
|
||||
* Linux |
||||
* |
||||
* (This is C99 source, please don't corrupt it with C++.) |
||||
* |
||||
* Author: Adam Langley |
||||
* Split into its own file: Markus Gutschke */ |
||||
|
||||
|
||||
#ifndef SRC_CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_EXTENSION_LINUX_H_ |
||||
#define SRC_CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_EXTENSION_LINUX_H_ |
||||
|
||||
#include <stddef.h> |
||||
|
||||
#include "google_breakpad/common/breakpad_types.h" |
||||
#include "google_breakpad/common/minidump_format.h" |
||||
|
||||
// These are additional minidump stream values which are specific to the linux
|
||||
// breakpad implementation.
|
||||
enum { |
||||
MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */ |
||||
MD_LINUX_PROC_STATUS = 0x47670004, /* /proc/$x/status */ |
||||
MD_LINUX_LSB_RELEASE = 0x47670005, /* /etc/lsb-release */ |
||||
MD_LINUX_CMD_LINE = 0x47670006, /* /proc/$x/cmdline */ |
||||
MD_LINUX_ENVIRON = 0x47670007, /* /proc/$x/environ */ |
||||
MD_LINUX_AUXV = 0x47670008, /* /proc/$x/auxv */ |
||||
MD_LINUX_MAPS = 0x47670009, /* /proc/$x/maps */ |
||||
MD_LINUX_DSO_DEBUG = 0x4767000A /* DSO data */ |
||||
}; |
||||
|
||||
typedef struct { |
||||
void* addr; |
||||
MDRVA name; |
||||
void* ld; |
||||
} MDRawLinkMap; |
||||
|
||||
typedef struct { |
||||
u_int32_t version; |
||||
MDRVA map; |
||||
u_int32_t dso_count; |
||||
void* brk; |
||||
void* ldbase; |
||||
void* dynamic; |
||||
} MDRawDebug; |
||||
|
||||
#endif // SRC_CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_EXTENSION_LINUX_H_
|
File diff suppressed because it is too large
Load Diff
@ -1,67 +0,0 @@ |
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ |
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ |
||||
|
||||
#include <stdint.h> |
||||
#include <unistd.h> |
||||
|
||||
#include <list> |
||||
#include <utility> |
||||
|
||||
#include "google_breakpad/common/minidump_format.h" |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
// A list of <MappingInfo, GUID>
|
||||
typedef std::pair<struct MappingInfo, u_int8_t[sizeof(MDGUID)]> MappingEntry; |
||||
typedef std::list<MappingEntry> MappingList; |
||||
|
||||
// Write a minidump to the filesystem. This function does not malloc nor use
|
||||
// libc functions which may. Thus, it can be used in contexts where the state
|
||||
// of the heap may be corrupt.
|
||||
// filename: the filename to write to. This is opened O_EXCL and fails if
|
||||
// open fails.
|
||||
// crashing_process: the pid of the crashing process. This must be trusted.
|
||||
// blob: a blob of data from the crashing process. See exception_handler.h
|
||||
// blob_size: the length of |blob|, in bytes
|
||||
//
|
||||
// Returns true iff successful.
|
||||
bool WriteMinidump(const char* filename, pid_t crashing_process, |
||||
const void* blob, size_t blob_size); |
||||
|
||||
// This overload also allows passing a list of known mappings.
|
||||
bool WriteMinidump(const char* filename, pid_t crashing_process, |
||||
const void* blob, size_t blob_size, |
||||
const MappingList& mappings); |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
@ -1,396 +0,0 @@ |
||||
// Copyright (c) 2011 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <fcntl.h> |
||||
#include <sys/poll.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/syscall.h> |
||||
#include <sys/types.h> |
||||
#include <unistd.h> |
||||
|
||||
#include <string> |
||||
|
||||
#include "breakpad_googletest_includes.h" |
||||
#include "client/linux/handler/exception_handler.h" |
||||
#include "client/linux/minidump_writer/linux_dumper.h" |
||||
#include "client/linux/minidump_writer/minidump_writer.h" |
||||
#include "common/linux/eintr_wrapper.h" |
||||
#include "common/linux/file_id.h" |
||||
#include "google_breakpad/processor/minidump.h" |
||||
|
||||
using namespace google_breakpad; |
||||
|
||||
#if !defined(__ANDROID__) |
||||
#define TEMPDIR "/tmp" |
||||
#else |
||||
#define TEMPDIR "/data/local/tmp" |
||||
#endif |
||||
|
||||
// Length of a formatted GUID string =
|
||||
// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
|
||||
const int kGUIDStringSize = 37; |
||||
|
||||
namespace { |
||||
typedef testing::Test MinidumpWriterTest; |
||||
} |
||||
|
||||
TEST(MinidumpWriterTest, Setup) { |
||||
int fds[2]; |
||||
ASSERT_NE(-1, pipe(fds)); |
||||
|
||||
const pid_t child = fork(); |
||||
if (child == 0) { |
||||
close(fds[1]); |
||||
char b; |
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b))); |
||||
close(fds[0]); |
||||
syscall(__NR_exit); |
||||
} |
||||
close(fds[0]); |
||||
|
||||
ExceptionHandler::CrashContext context; |
||||
memset(&context, 0, sizeof(context)); |
||||
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX"; |
||||
mktemp(templ); |
||||
// Set a non-zero tid to avoid tripping asserts.
|
||||
context.tid = 1; |
||||
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context))); |
||||
struct stat st; |
||||
ASSERT_EQ(stat(templ, &st), 0); |
||||
ASSERT_GT(st.st_size, 0u); |
||||
unlink(templ); |
||||
|
||||
close(fds[1]); |
||||
} |
||||
|
||||
// Test that mapping info can be specified when writing a minidump,
|
||||
// and that it ends up in the module list of the minidump.
|
||||
TEST(MinidumpWriterTest, MappingInfo) { |
||||
int fds[2]; |
||||
ASSERT_NE(-1, pipe(fds)); |
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE); |
||||
const char* kMemoryName = "a fake module"; |
||||
const u_int8_t kModuleGUID[sizeof(MDGUID)] = { |
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, |
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF |
||||
}; |
||||
char module_identifier_buffer[kGUIDStringSize]; |
||||
FileID::ConvertIdentifierToString(kModuleGUID, |
||||
module_identifier_buffer, |
||||
sizeof(module_identifier_buffer)); |
||||
string module_identifier(module_identifier_buffer); |
||||
// Strip out dashes
|
||||
size_t pos; |
||||
while ((pos = module_identifier.find('-')) != string::npos) { |
||||
module_identifier.erase(pos, 1); |
||||
} |
||||
// And append a zero, because module IDs include an "age" field
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0"; |
||||
|
||||
// Get some memory.
|
||||
char* memory = |
||||
reinterpret_cast<char*>(mmap(NULL, |
||||
kMemorySize, |
||||
PROT_READ | PROT_WRITE, |
||||
MAP_PRIVATE | MAP_ANON, |
||||
-1, |
||||
0)); |
||||
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory); |
||||
ASSERT_TRUE(memory); |
||||
|
||||
const pid_t child = fork(); |
||||
if (child == 0) { |
||||
close(fds[1]); |
||||
char b; |
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b))); |
||||
close(fds[0]); |
||||
syscall(__NR_exit); |
||||
} |
||||
close(fds[0]); |
||||
|
||||
ExceptionHandler::CrashContext context; |
||||
memset(&context, 0, sizeof(context)); |
||||
context.tid = 1; |
||||
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX"; |
||||
mktemp(templ); |
||||
|
||||
// Add information about the mapped memory.
|
||||
MappingInfo info; |
||||
info.start_addr = kMemoryAddress; |
||||
info.size = kMemorySize; |
||||
info.offset = 0; |
||||
strcpy(info.name, kMemoryName); |
||||
|
||||
MappingList mappings; |
||||
MappingEntry mapping; |
||||
mapping.first = info; |
||||
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); |
||||
mappings.push_back(mapping); |
||||
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context), mappings)); |
||||
|
||||
// Read the minidump. Load the module list, and ensure that
|
||||
// the mmap'ed |memory| is listed with the given module name
|
||||
// and debug ID.
|
||||
Minidump minidump(templ); |
||||
ASSERT_TRUE(minidump.Read()); |
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList(); |
||||
ASSERT_TRUE(module_list); |
||||
const MinidumpModule* module = |
||||
module_list->GetModuleForAddress(kMemoryAddress); |
||||
ASSERT_TRUE(module); |
||||
|
||||
EXPECT_EQ(kMemoryAddress, module->base_address()); |
||||
EXPECT_EQ(kMemorySize, module->size()); |
||||
EXPECT_EQ(kMemoryName, module->code_file()); |
||||
EXPECT_EQ(module_identifier, module->debug_identifier()); |
||||
|
||||
unlink(templ); |
||||
close(fds[1]); |
||||
} |
||||
|
||||
// Test that mapping info can be specified, and that it overrides
|
||||
// existing mappings that are wholly contained within the specified
|
||||
// range.
|
||||
TEST(MinidumpWriterTest, MappingInfoContained) { |
||||
int fds[2]; |
||||
ASSERT_NE(-1, pipe(fds)); |
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE); |
||||
const char* kMemoryName = "a fake module"; |
||||
const u_int8_t kModuleGUID[sizeof(MDGUID)] = { |
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, |
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF |
||||
}; |
||||
char module_identifier_buffer[kGUIDStringSize]; |
||||
FileID::ConvertIdentifierToString(kModuleGUID, |
||||
module_identifier_buffer, |
||||
sizeof(module_identifier_buffer)); |
||||
string module_identifier(module_identifier_buffer); |
||||
// Strip out dashes
|
||||
size_t pos; |
||||
while ((pos = module_identifier.find('-')) != string::npos) { |
||||
module_identifier.erase(pos, 1); |
||||
} |
||||
// And append a zero, because module IDs include an "age" field
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0"; |
||||
|
||||
// mmap a file
|
||||
char tempfile[] = TEMPDIR "/minidump-writer-unittest-temp-XXXXXX"; |
||||
mktemp(tempfile); |
||||
int fd = open(tempfile, O_RDWR | O_CREAT, 0); |
||||
ASSERT_NE(-1, fd); |
||||
unlink(tempfile); |
||||
// fill with zeros
|
||||
char buffer[kMemorySize]; |
||||
memset(buffer, 0, kMemorySize); |
||||
ASSERT_EQ(kMemorySize, write(fd, buffer, kMemorySize)); |
||||
lseek(fd, 0, SEEK_SET); |
||||
|
||||
char* memory = |
||||
reinterpret_cast<char*>(mmap(NULL, |
||||
kMemorySize, |
||||
PROT_READ | PROT_WRITE, |
||||
MAP_PRIVATE, |
||||
fd, |
||||
0)); |
||||
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory); |
||||
ASSERT_TRUE(memory); |
||||
close(fd); |
||||
|
||||
const pid_t child = fork(); |
||||
if (child == 0) { |
||||
close(fds[1]); |
||||
char b; |
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b))); |
||||
close(fds[0]); |
||||
syscall(__NR_exit); |
||||
} |
||||
close(fds[0]); |
||||
|
||||
ExceptionHandler::CrashContext context; |
||||
memset(&context, 0, sizeof(context)); |
||||
context.tid = 1; |
||||
|
||||
char dumpfile[] = TEMPDIR "/minidump-writer-unittest-XXXXXX"; |
||||
mktemp(dumpfile); |
||||
|
||||
// Add information about the mapped memory. Report it as being larger than
|
||||
// it actually is.
|
||||
MappingInfo info; |
||||
info.start_addr = kMemoryAddress - kMemorySize; |
||||
info.size = kMemorySize * 3; |
||||
info.offset = 0; |
||||
strcpy(info.name, kMemoryName); |
||||
|
||||
MappingList mappings; |
||||
MappingEntry mapping; |
||||
mapping.first = info; |
||||
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); |
||||
mappings.push_back(mapping); |
||||
ASSERT_TRUE( |
||||
WriteMinidump(dumpfile, child, &context, sizeof(context), mappings)); |
||||
|
||||
// Read the minidump. Load the module list, and ensure that
|
||||
// the mmap'ed |memory| is listed with the given module name
|
||||
// and debug ID.
|
||||
Minidump minidump(dumpfile); |
||||
ASSERT_TRUE(minidump.Read()); |
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList(); |
||||
ASSERT_TRUE(module_list); |
||||
const MinidumpModule* module = |
||||
module_list->GetModuleForAddress(kMemoryAddress); |
||||
ASSERT_TRUE(module); |
||||
|
||||
EXPECT_EQ(info.start_addr, module->base_address()); |
||||
EXPECT_EQ(info.size, module->size()); |
||||
EXPECT_EQ(kMemoryName, module->code_file()); |
||||
EXPECT_EQ(module_identifier, module->debug_identifier()); |
||||
|
||||
unlink(dumpfile); |
||||
close(fds[1]); |
||||
} |
||||
|
||||
TEST(MinidumpWriterTest, DeletedBinary) { |
||||
static const int kNumberOfThreadsInHelperProgram = 1; |
||||
char kNumberOfThreadsArgument[2]; |
||||
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram); |
||||
|
||||
// Locate helper binary next to the current binary.
|
||||
char self_path[PATH_MAX]; |
||||
if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) { |
||||
FAIL() << "readlink failed: " << strerror(errno); |
||||
exit(1); |
||||
} |
||||
string helper_path(self_path); |
||||
size_t pos = helper_path.rfind('/'); |
||||
if (pos == string::npos) { |
||||
FAIL() << "no trailing slash in path: " << helper_path; |
||||
exit(1); |
||||
} |
||||
helper_path.erase(pos + 1); |
||||
helper_path += "linux_dumper_unittest_helper"; |
||||
|
||||
// Copy binary to a temp file.
|
||||
char binpath[] = TEMPDIR "/linux-dumper-unittest-helper-XXXXXX"; |
||||
mktemp(binpath); |
||||
char cmdline[2 * PATH_MAX]; |
||||
sprintf(cmdline, "/bin/cp \"%s\" \"%s\"", helper_path.c_str(), binpath); |
||||
ASSERT_EQ(0, system(cmdline)); |
||||
ASSERT_EQ(0, chmod(binpath, 0755)); |
||||
|
||||
int fds[2]; |
||||
ASSERT_NE(-1, pipe(fds)); |
||||
|
||||
pid_t child_pid = fork(); |
||||
if (child_pid == 0) { |
||||
// In child process.
|
||||
close(fds[0]); |
||||
|
||||
// Pass the pipe fd and the number of threads as arguments.
|
||||
char pipe_fd_string[8]; |
||||
sprintf(pipe_fd_string, "%d", fds[1]); |
||||
execl(binpath, |
||||
binpath, |
||||
pipe_fd_string, |
||||
kNumberOfThreadsArgument, |
||||
NULL); |
||||
} |
||||
close(fds[1]); |
||||
// Wait for the child process to signal that it's ready.
|
||||
struct pollfd pfd; |
||||
memset(&pfd, 0, sizeof(pfd)); |
||||
pfd.fd = fds[0]; |
||||
pfd.events = POLLIN | POLLERR; |
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); |
||||
ASSERT_EQ(1, r); |
||||
ASSERT_TRUE(pfd.revents & POLLIN); |
||||
uint8_t junk; |
||||
read(fds[0], &junk, sizeof(junk)); |
||||
close(fds[0]); |
||||
|
||||
// Child is ready now.
|
||||
// Unlink the test binary.
|
||||
unlink(binpath); |
||||
|
||||
ExceptionHandler::CrashContext context; |
||||
memset(&context, 0, sizeof(context)); |
||||
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX"; |
||||
mktemp(templ); |
||||
// Set a non-zero tid to avoid tripping asserts.
|
||||
context.tid = 1; |
||||
ASSERT_TRUE(WriteMinidump(templ, child_pid, &context, sizeof(context))); |
||||
kill(child_pid, SIGKILL); |
||||
|
||||
struct stat st; |
||||
ASSERT_EQ(stat(templ, &st), 0); |
||||
ASSERT_GT(st.st_size, 0u); |
||||
|
||||
|
||||
|
||||
Minidump minidump(templ); |
||||
ASSERT_TRUE(minidump.Read()); |
||||
|
||||
// Check that the main module filename is correct.
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList(); |
||||
ASSERT_TRUE(module_list); |
||||
const MinidumpModule* module = module_list->GetMainModule(); |
||||
EXPECT_STREQ(binpath, module->code_file().c_str()); |
||||
// Check that the file ID is correct.
|
||||
FileID fileid(helper_path.c_str()); |
||||
uint8_t identifier[sizeof(MDGUID)]; |
||||
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier)); |
||||
char identifier_string[kGUIDStringSize]; |
||||
FileID::ConvertIdentifierToString(identifier, |
||||
identifier_string, |
||||
kGUIDStringSize); |
||||
string module_identifier(identifier_string); |
||||
// Strip out dashes
|
||||
while ((pos = module_identifier.find('-')) != string::npos) { |
||||
module_identifier.erase(pos, 1); |
||||
} |
||||
// And append a zero, because module IDs include an "age" field
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0"; |
||||
EXPECT_EQ(module_identifier, module->debug_identifier()); |
||||
|
||||
unlink(templ); |
||||
} |
@ -1,104 +0,0 @@ |
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "common/linux/google_crashdump_uploader.h" |
||||
#include "third_party/linux/include/gflags/gflags.h" |
||||
#include <string> |
||||
#include <iostream> |
||||
|
||||
using std::string; |
||||
|
||||
DEFINE_string(crash_server, "http://clients2.google.com/cr", |
||||
"The crash server to upload minidumps to."); |
||||
DEFINE_string(product_name, "", |
||||
"The product name that the minidump corresponds to."); |
||||
DEFINE_string(product_version, "", |
||||
"The version of the product that produced the minidump."); |
||||
DEFINE_string(client_id, "", |
||||
"The client GUID"); |
||||
DEFINE_string(minidump_path, "", |
||||
"The path of the minidump file."); |
||||
DEFINE_string(ptime, "", |
||||
"The process uptime in milliseconds."); |
||||
DEFINE_string(ctime, "", |
||||
"The cumulative process uptime in milliseconds."); |
||||
DEFINE_string(email, "", |
||||
"The user's email address."); |
||||
DEFINE_string(comments, "", |
||||
"Extra user comments"); |
||||
DEFINE_string(proxy_host, "", |
||||
"Proxy host"); |
||||
DEFINE_string(proxy_userpasswd, "", |
||||
"Proxy username/password in user:pass format."); |
||||
|
||||
|
||||
bool CheckForRequiredFlagsOrDie() { |
||||
std::string error_text = ""; |
||||
if (FLAGS_product_name.empty()) { |
||||
error_text.append("\nProduct name must be specified."); |
||||
} |
||||
|
||||
if (FLAGS_product_version.empty()) { |
||||
error_text.append("\nProduct version must be specified."); |
||||
} |
||||
|
||||
if (FLAGS_client_id.empty()) { |
||||
error_text.append("\nClient ID must be specified."); |
||||
} |
||||
|
||||
if (FLAGS_minidump_path.empty()) { |
||||
error_text.append("\nMinidump pathname must be specified."); |
||||
} |
||||
|
||||
if (!error_text.empty()) { |
||||
std::cout << error_text; |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) { |
||||
google::InitGoogleLogging(argv[0]); |
||||
google::ParseCommandLineFlags(&argc, &argv, true); |
||||
if (!CheckForRequiredFlagsOrDie()) { |
||||
return 1; |
||||
} |
||||
google_breakpad::GoogleCrashdumpUploader g(FLAGS_product_name, |
||||
FLAGS_product_version, |
||||
FLAGS_client_id, |
||||
FLAGS_ptime, |
||||
FLAGS_ctime, |
||||
FLAGS_email, |
||||
FLAGS_comments, |
||||
FLAGS_minidump_path, |
||||
FLAGS_crash_server, |
||||
FLAGS_proxy_host, |
||||
FLAGS_proxy_userpasswd); |
||||
g.Upload(); |
||||
} |
@ -1,316 +0,0 @@ |
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Framework to provide a simple C API to crash reporting for
|
||||
// applications. By default, if any machine-level exception (e.g.,
|
||||
// EXC_BAD_ACCESS) occurs, it will be handled by the BreakpadRef
|
||||
// object as follows:
|
||||
//
|
||||
// 1. Create a minidump file (see Breakpad for details)
|
||||
// 2. Prompt the user (using CFUserNotification)
|
||||
// 3. Invoke a command line reporting tool to send the minidump to a
|
||||
// server
|
||||
//
|
||||
// By specifying parameters to the BreakpadCreate function, you can
|
||||
// modify the default behavior to suit your needs and wants and
|
||||
// desires.
|
||||
|
||||
typedef void *BreakpadRef; |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#include <CoreFoundation/CoreFoundation.h> |
||||
#include <Foundation/Foundation.h> |
||||
|
||||
// Keys for configuration file
|
||||
#define kReporterMinidumpDirectoryKey "MinidumpDir" |
||||
#define kReporterMinidumpIDKey "MinidumpID" |
||||
|
||||
// Filename for recording uploaded IDs
|
||||
#define kReporterLogFilename "uploads.log" |
||||
|
||||
// The default subdirectory of the Library to put crash dumps in
|
||||
// The subdirectory is
|
||||
// ~/Library/<kDefaultLibrarySubdirectory>/<GoogleBreakpadProduct>
|
||||
#define kDefaultLibrarySubdirectory "Breakpad" |
||||
|
||||
// Specify some special keys to be used in the configuration file that is
|
||||
// generated by Breakpad and consumed by the crash_sender.
|
||||
#define BREAKPAD_PRODUCT "BreakpadProduct" |
||||
#define BREAKPAD_PRODUCT_DISPLAY "BreakpadProductDisplay" |
||||
#define BREAKPAD_VERSION "BreakpadVersion" |
||||
#define BREAKPAD_VENDOR "BreakpadVendor" |
||||
#define BREAKPAD_URL "BreakpadURL" |
||||
#define BREAKPAD_REPORT_INTERVAL "BreakpadReportInterval" |
||||
#define BREAKPAD_SKIP_CONFIRM "BreakpadSkipConfirm" |
||||
#define BREAKPAD_CONFIRM_TIMEOUT "BreakpadConfirmTimeout" |
||||
#define BREAKPAD_SEND_AND_EXIT "BreakpadSendAndExit" |
||||
#define BREAKPAD_DUMP_DIRECTORY "BreakpadMinidumpLocation" |
||||
#define BREAKPAD_INSPECTOR_LOCATION "BreakpadInspectorLocation" |
||||
#define BREAKPAD_REPORTER_EXE_LOCATION \ |
||||
"BreakpadReporterExeLocation" |
||||
#define BREAKPAD_LOGFILES "BreakpadLogFiles" |
||||
#define BREAKPAD_LOGFILE_UPLOAD_SIZE "BreakpadLogFileTailSize" |
||||
#define BREAKPAD_REQUEST_COMMENTS "BreakpadRequestComments" |
||||
#define BREAKPAD_COMMENTS "BreakpadComments" |
||||
#define BREAKPAD_REQUEST_EMAIL "BreakpadRequestEmail" |
||||
#define BREAKPAD_EMAIL "BreakpadEmail" |
||||
#define BREAKPAD_SERVER_TYPE "BreakpadServerType" |
||||
#define BREAKPAD_SERVER_PARAMETER_DICT "BreakpadServerParameters" |
||||
|
||||
// The keys below are NOT user supplied, and are used internally.
|
||||
#define BREAKPAD_PROCESS_START_TIME "BreakpadProcStartTime" |
||||
#define BREAKPAD_PROCESS_UP_TIME "BreakpadProcessUpTime" |
||||
#define BREAKPAD_PROCESS_CRASH_TIME "BreakpadProcessCrashTime" |
||||
#define BREAKPAD_LOGFILE_KEY_PREFIX "BreakpadAppLogFile" |
||||
#define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_" |
||||
#define BREAKPAD_ON_DEMAND "BreakpadOnDemand" |
||||
|
||||
// Optional user-defined function to dec to decide if we should handle
|
||||
// this crash or forward it along.
|
||||
// Return true if you want Breakpad to handle it.
|
||||
// Return false if you want Breakpad to skip it
|
||||
// The exception handler always returns false, as if SEND_AND_EXIT were false
|
||||
// (which means the next exception handler will take the exception)
|
||||
typedef bool (*BreakpadFilterCallback)(int exception_type, |
||||
int exception_code, |
||||
mach_port_t crashing_thread, |
||||
void *context); |
||||
|
||||
// Create a new BreakpadRef object and install it as an exception
|
||||
// handler. The |parameters| will typically be the contents of your
|
||||
// bundle's Info.plist.
|
||||
//
|
||||
// You can also specify these additional keys for customizable behavior:
|
||||
// Key: Value:
|
||||
// BREAKPAD_PRODUCT Product name (e.g., "MyAwesomeProduct")
|
||||
// This one is used as the key to identify
|
||||
// the product when uploading. Falls back to
|
||||
// CFBundleName if not specified.
|
||||
// REQUIRED
|
||||
//
|
||||
// BREAKPAD_PRODUCT_DISPLAY This is the display name, e.g. a pretty
|
||||
// name for the product when the crash_sender
|
||||
// pops up UI for the user. Falls back first to
|
||||
// CFBundleDisplayName and then to
|
||||
// BREAKPAD_PRODUCT if not specified.
|
||||
//
|
||||
// BREAKPAD_VERSION Product version (e.g., 1.2.3), used
|
||||
// as metadata for crash report. Falls back to
|
||||
// CFBundleVersion if not specified.
|
||||
// REQUIRED
|
||||
//
|
||||
// BREAKPAD_VENDOR Vendor name, used in UI (e.g. "A report has
|
||||
// been created that you can send to <vendor>")
|
||||
//
|
||||
// BREAKPAD_URL URL destination for reporting
|
||||
// REQUIRED
|
||||
//
|
||||
// BREAKPAD_REPORT_INTERVAL # of seconds between sending
|
||||
// reports. If an additional report is
|
||||
// generated within this time, it will
|
||||
// be ignored. Default: 3600sec.
|
||||
// Specify 0 to send all reports.
|
||||
//
|
||||
// BREAKPAD_SKIP_CONFIRM If true, the reporter will send the report
|
||||
// without any user intervention.
|
||||
// Defaults to NO
|
||||
//
|
||||
// BREAKPAD_CONFIRM_TIMEOUT Number of seconds before the upload
|
||||
// confirmation dialog will be automatically
|
||||
// dismissed (cancelling the upload).
|
||||
// Default: 300 seconds (min of 60).
|
||||
// Specify 0 to prevent timeout.
|
||||
//
|
||||
// BREAKPAD_SEND_AND_EXIT If true, the handler will exit after sending.
|
||||
// This will prevent any other handler (e.g.,
|
||||
// CrashReporter) from getting the crash.
|
||||
// Defaults TO YES
|
||||
//
|
||||
// BREAKPAD_DUMP_DIRECTORY The directory to store crash-dumps
|
||||
// in. By default, we use
|
||||
// ~/Library/Breakpad/<BREAKPAD_PRODUCT>
|
||||
// The path you specify here is tilde-expanded.
|
||||
//
|
||||
// BREAKPAD_INSPECTOR_LOCATION The full path to the Inspector executable.
|
||||
// Defaults to <Framework resources>/Inspector
|
||||
//
|
||||
// BREAKPAD_REPORTER_EXE_LOCATION The full path to the Reporter/sender
|
||||
// executable.
|
||||
// Default:
|
||||
// <Framework Resources>/crash_report_sender.app
|
||||
//
|
||||
// BREAKPAD_LOGFILES Indicates an array of log file paths that
|
||||
// should be uploaded at crash time.
|
||||
//
|
||||
// BREAKPAD_REQUEST_COMMENTS If true, the message dialog will have a
|
||||
// text box for the user to enter comments.
|
||||
// Default: NO
|
||||
//
|
||||
// BREAKPAD_REQUEST_EMAIL If true and BREAKPAD_REQUEST_COMMENTS is also
|
||||
// true, the message dialog will have a text
|
||||
// box for the user to enter their email address.
|
||||
// Default: NO
|
||||
//
|
||||
// BREAKPAD_SERVER_TYPE A parameter that tells Breakpad how to
|
||||
// rewrite the upload parameters for a specific
|
||||
// server type. The currently valid values are
|
||||
// 'socorro' or 'google'. If you want to add
|
||||
// other types, see the function in
|
||||
// crash_report_sender.m that maps parameters to
|
||||
// URL parameters. Defaults to 'google'.
|
||||
//
|
||||
// BREAKPAD_SERVER_PARAMETER_DICT A plist dictionary of static
|
||||
// parameters that are uploaded to the
|
||||
// server. The parameters are sent as
|
||||
// is to the crash server. Their
|
||||
// content isn't added to the minidump
|
||||
// but pass as URL parameters when
|
||||
// uploading theminidump to the crash
|
||||
// server.
|
||||
//=============================================================================
|
||||
// The BREAKPAD_PRODUCT, BREAKPAD_VERSION and BREAKPAD_URL are
|
||||
// required to have non-NULL values. By default, the BREAKPAD_PRODUCT
|
||||
// will be the CFBundleName and the BREAKPAD_VERSION will be the
|
||||
// CFBundleVersion when these keys are present in the bundle's
|
||||
// Info.plist, which is usually passed in to BreakpadCreate() as an
|
||||
// NSDictionary (you could also pass in another dictionary that had
|
||||
// the same keys configured). If the BREAKPAD_PRODUCT or
|
||||
// BREAKPAD_VERSION are ultimately undefined, BreakpadCreate() will
|
||||
// fail. You have been warned.
|
||||
//
|
||||
// If you are running in a debugger, Breakpad will not install, unless the
|
||||
// BREAKPAD_IGNORE_DEBUGGER envionment variable is set and/or non-zero.
|
||||
//
|
||||
// The BREAKPAD_SKIP_CONFIRM and BREAKPAD_SEND_AND_EXIT default
|
||||
// values are NO and YES. However, they can be controlled by setting their
|
||||
// values in a user or global plist.
|
||||
//
|
||||
// It's easiest to use Breakpad via the Framework, but if you're compiling the
|
||||
// code in directly, BREAKPAD_INSPECTOR_LOCATION and
|
||||
// BREAKPAD_REPORTER_EXE_LOCATION allow you to specify custom paths
|
||||
// to the helper executables.
|
||||
//
|
||||
//=============================================================================
|
||||
// The following are NOT user-supplied but are documented here for
|
||||
// completeness. They are calculated by Breakpad during initialization &
|
||||
// crash-dump generation, or entered in by the user.
|
||||
//
|
||||
// BREAKPAD_PROCESS_START_TIME The time the process started.
|
||||
//
|
||||
// BREAKPAD_PROCESS_CRASH_TIME The time the process crashed.
|
||||
//
|
||||
// BREAKPAD_PROCESS_UP_TIME The total time the process has been
|
||||
// running. This parameter is not set
|
||||
// until the crash-dump-generation phase.
|
||||
//
|
||||
// BREAKPAD_LOGFILE_KEY_PREFIX Used to find out which parameters in the
|
||||
// parameter dictionary correspond to log
|
||||
// file paths.
|
||||
//
|
||||
// BREAKPAD_SERVER_PARAMETER_PREFIX This prefix is used by Breakpad
|
||||
// internally, because Breakpad uses
|
||||
// the same dictionary internally to
|
||||
// track both its internal
|
||||
// configuration parameters and
|
||||
// parameters meant to be uploaded
|
||||
// to the server. This string is
|
||||
// used internally by Breakpad to
|
||||
// prefix user-supplied parameter
|
||||
// names so those can be sent to the
|
||||
// server without leaking Breakpad's
|
||||
// internal values.
|
||||
//
|
||||
// BREAKPAD_ON_DEMAND Used internally to indicate to the
|
||||
// Reporter that we're sending on-demand,
|
||||
// not as result of a crash.
|
||||
//
|
||||
// BREAKPAD_COMMENTS The text the user provided as comments.
|
||||
// Only used in crash_report_sender.
|
||||
|
||||
// Returns a new BreakpadRef object on success, NULL otherwise.
|
||||
BreakpadRef BreakpadCreate(NSDictionary *parameters); |
||||
|
||||
// Uninstall and release the data associated with |ref|.
|
||||
void BreakpadRelease(BreakpadRef ref); |
||||
|
||||
// Clients may set an optional callback which gets called when a crash
|
||||
// occurs. The callback function should return |true| if we should
|
||||
// handle the crash, generate a crash report, etc. or |false| if we
|
||||
// should ignore it and forward the crash (normally to CrashReporter).
|
||||
// Context is a pointer to arbitrary data to make the callback with.
|
||||
void BreakpadSetFilterCallback(BreakpadRef ref, |
||||
BreakpadFilterCallback callback, |
||||
void *context); |
||||
|
||||
// User defined key and value string storage. Generally this is used
|
||||
// to configure Breakpad's internal operation, such as whether the
|
||||
// crash_sender should prompt the user, or the filesystem location for
|
||||
// the minidump file. See Breakpad.h for some parameters that can be
|
||||
// set. Anything longer than 255 bytes will be truncated. Note that
|
||||
// the string is converted to UTF8 before truncation, so any multibyte
|
||||
// character that straddles the 255(256 - 1 for terminator) byte limit
|
||||
// will be mangled.
|
||||
//
|
||||
// A maximum number of 64 key/value pairs are supported. An assert()
|
||||
// will fire if more than this number are set. Unfortunately, right
|
||||
// now, the same dictionary is used for both Breakpad's parameters AND
|
||||
// the Upload parameters.
|
||||
//
|
||||
// TODO (nealsid): Investigate how necessary this is if we don't
|
||||
// automatically upload parameters to the server anymore.
|
||||
// TODO (nealsid): separate server parameter dictionary from the
|
||||
// dictionary used to configure Breakpad, and document limits for each
|
||||
// independently.
|
||||
void BreakpadSetKeyValue(BreakpadRef ref, NSString *key, NSString *value); |
||||
NSString *BreakpadKeyValue(BreakpadRef ref, NSString *key); |
||||
void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key); |
||||
|
||||
// You can use this method to specify parameters that will be uploaded
|
||||
// to the crash server. They will be automatically encoded as
|
||||
// necessary. Note that as mentioned above there are limits on both
|
||||
// the number of keys and their length.
|
||||
void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key, |
||||
NSString *value); |
||||
|
||||
// This method will remove a previously-added parameter from the
|
||||
// upload parameter set.
|
||||
void BreakpadRemoveUploadParameter(BreakpadRef ref, NSString *key); |
||||
|
||||
// Add a log file for Breakpad to read and send upon crash dump
|
||||
void BreakpadAddLogFile(BreakpadRef ref, NSString *logPathname); |
||||
|
||||
// Generate a minidump and send
|
||||
void BreakpadGenerateAndSendReport(BreakpadRef ref); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
@ -1,996 +0,0 @@ |
||||
// Copyright (c) 2006, Google Inc. |
||||
// All rights reserved. |
||||
// |
||||
// Redistribution and use in source and binary forms, with or without |
||||
// modification, are permitted provided that the following conditions are |
||||
// met: |
||||
// |
||||
// * Redistributions of source code must retain the above copyright |
||||
// notice, this list of conditions and the following disclaimer. |
||||
// * Redistributions in binary form must reproduce the above |
||||
// copyright notice, this list of conditions and the following disclaimer |
||||
// in the documentation and/or other materials provided with the |
||||
// distribution. |
||||
// * Neither the name of Google Inc. nor the names of its |
||||
// contributors may be used to endorse or promote products derived from |
||||
// this software without specific prior written permission. |
||||
// |
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
// |
||||
|
||||
#define VERBOSE 0 |
||||
|
||||
#if VERBOSE |
||||
static bool gDebugLog = true; |
||||
#else |
||||
static bool gDebugLog = false; |
||||
#endif |
||||
|
||||
#define DEBUGLOG if (gDebugLog) fprintf |
||||
#define IGNORE_DEBUGGER "BREAKPAD_IGNORE_DEBUGGER" |
||||
|
||||
#import "common/mac/MachIPC.h" |
||||
#import "common/mac/SimpleStringDictionary.h" |
||||
|
||||
#import "client/mac/crash_generation/Inspector.h" |
||||
#import "client/mac/handler/exception_handler.h" |
||||
#import "client/mac/Framework/Breakpad.h" |
||||
#import "client/mac/Framework/OnDemandServer.h" |
||||
#import "client/mac/handler/protected_memory_allocator.h" |
||||
|
||||
#import <sys/stat.h> |
||||
#import <sys/sysctl.h> |
||||
|
||||
#import <Foundation/Foundation.h> |
||||
|
||||
|
||||
using google_breakpad::KeyValueEntry; |
||||
using google_breakpad::MachPortSender; |
||||
using google_breakpad::MachReceiveMessage; |
||||
using google_breakpad::MachSendMessage; |
||||
using google_breakpad::ReceivePort; |
||||
using google_breakpad::SimpleStringDictionary; |
||||
using google_breakpad::SimpleStringDictionaryIterator; |
||||
|
||||
//============================================================================= |
||||
// We want any memory allocations which are used by breakpad during the |
||||
// exception handling process (after a crash has happened) to be read-only |
||||
// to prevent them from being smashed before a crash occurs. Unfortunately |
||||
// we cannot protect against smashes to our exception handling thread's |
||||
// stack. |
||||
// |
||||
// NOTE: Any memory allocations which are not used during the exception |
||||
// handling process may be allocated in the normal ways. |
||||
// |
||||
// The ProtectedMemoryAllocator class provides an Allocate() method which |
||||
// we'll using in conjunction with placement operator new() to control |
||||
// allocation of C++ objects. Note that we don't use operator delete() |
||||
// but instead call the objects destructor directly: object->~ClassName(); |
||||
// |
||||
ProtectedMemoryAllocator *gMasterAllocator = NULL; |
||||
ProtectedMemoryAllocator *gKeyValueAllocator = NULL; |
||||
ProtectedMemoryAllocator *gBreakpadAllocator = NULL; |
||||
|
||||
// Mutex for thread-safe access to the key/value dictionary used by breakpad. |
||||
// It's a global instead of an instance variable of Breakpad |
||||
// since it can't live in a protected memory area. |
||||
pthread_mutex_t gDictionaryMutex; |
||||
|
||||
//============================================================================= |
||||
// Stack-based object for thread-safe access to a memory-protected region. |
||||
// It's assumed that normally the memory block (allocated by the allocator) |
||||
// is protected (read-only). Creating a stack-based instance of |
||||
// ProtectedMemoryLocker will unprotect this block after taking the lock. |
||||
// Its destructor will first re-protect the memory then release the lock. |
||||
class ProtectedMemoryLocker { |
||||
public: |
||||
// allocator may be NULL, in which case no Protect() or Unprotect() calls |
||||
// will be made, but a lock will still be taken |
||||
ProtectedMemoryLocker(pthread_mutex_t *mutex, |
||||
ProtectedMemoryAllocator *allocator) |
||||
: mutex_(mutex), allocator_(allocator) { |
||||
// Lock the mutex |
||||
assert(pthread_mutex_lock(mutex_) == 0); |
||||
|
||||
// Unprotect the memory |
||||
if (allocator_ ) { |
||||
allocator_->Unprotect(); |
||||
} |
||||
} |
||||
|
||||
~ProtectedMemoryLocker() { |
||||
// First protect the memory |
||||
if (allocator_) { |
||||
allocator_->Protect(); |
||||
} |
||||
|
||||
// Then unlock the mutex |
||||
assert(pthread_mutex_unlock(mutex_) == 0); |
||||
}; |
||||
|
||||
private: |
||||
// Keep anybody from ever creating one of these things not on the stack. |
||||
ProtectedMemoryLocker() { } |
||||
ProtectedMemoryLocker(const ProtectedMemoryLocker&); |
||||
ProtectedMemoryLocker & operator=(ProtectedMemoryLocker&); |
||||
|
||||
pthread_mutex_t *mutex_; |
||||
ProtectedMemoryAllocator *allocator_; |
||||
}; |
||||
|
||||
//============================================================================= |
||||
class Breakpad { |
||||
public: |
||||
// factory method |
||||
static Breakpad *Create(NSDictionary *parameters) { |
||||
// Allocate from our special allocation pool |
||||
Breakpad *breakpad = |
||||
new (gBreakpadAllocator->Allocate(sizeof(Breakpad))) |
||||
Breakpad(); |
||||
|
||||
if (!breakpad) |
||||
return NULL; |
||||
|
||||
if (!breakpad->Initialize(parameters)) { |
||||
// Don't use operator delete() here since we allocated from special pool |
||||
breakpad->~Breakpad(); |
||||
return NULL; |
||||
} |
||||
|
||||
return breakpad; |
||||
} |
||||
|
||||
~Breakpad(); |
||||
|
||||
void SetKeyValue(NSString *key, NSString *value); |
||||
NSString *KeyValue(NSString *key); |
||||
void RemoveKeyValue(NSString *key); |
||||
|
||||
void GenerateAndSendReport(); |
||||
|
||||
void SetFilterCallback(BreakpadFilterCallback callback, void *context) { |
||||
filter_callback_ = callback; |
||||
filter_callback_context_ = context; |
||||
} |
||||
|
||||
private: |
||||
Breakpad() |
||||
: handler_(NULL), |
||||
config_params_(NULL), |
||||
send_and_exit_(true), |
||||
filter_callback_(NULL), |
||||
filter_callback_context_(NULL) { |
||||
inspector_path_[0] = 0; |
||||
} |
||||
|
||||
bool Initialize(NSDictionary *parameters); |
||||
|
||||
bool ExtractParameters(NSDictionary *parameters); |
||||
|
||||
// Dispatches to HandleException() |
||||
static bool ExceptionHandlerDirectCallback(void *context, |
||||
int exception_type, |
||||
int exception_code, |
||||
int exception_subcode, |
||||
mach_port_t crashing_thread); |
||||
|
||||
bool HandleException(int exception_type, |
||||
int exception_code, |
||||
int exception_subcode, |
||||
mach_port_t crashing_thread); |
||||
|
||||
// Since ExceptionHandler (w/o namespace) is defined as typedef in OSX's |
||||
// MachineExceptions.h, we have to explicitly name the handler. |
||||
google_breakpad::ExceptionHandler *handler_; // The actual handler (STRONG) |
||||
|
||||
char inspector_path_[PATH_MAX]; // Path to inspector tool |
||||
|
||||
SimpleStringDictionary *config_params_; // Create parameters (STRONG) |
||||
|
||||
OnDemandServer inspector_; |
||||
|
||||
bool send_and_exit_; // Exit after sending, if true |
||||
|
||||
BreakpadFilterCallback filter_callback_; |
||||
void *filter_callback_context_; |
||||
}; |
||||
|
||||
#pragma mark - |
||||
#pragma mark Helper functions |
||||
|
||||
//============================================================================= |
||||
// Helper functions |
||||
|
||||
//============================================================================= |
||||
static BOOL IsDebuggerActive() { |
||||
BOOL result = NO; |
||||
NSUserDefaults *stdDefaults = [NSUserDefaults standardUserDefaults]; |
||||
|
||||
// We check both defaults and the environment variable here |
||||
|
||||
BOOL ignoreDebugger = [stdDefaults boolForKey:@IGNORE_DEBUGGER]; |
||||
|
||||
if (!ignoreDebugger) { |
||||
char *ignoreDebuggerStr = getenv(IGNORE_DEBUGGER); |
||||
ignoreDebugger = (ignoreDebuggerStr ? strtol(ignoreDebuggerStr, NULL, 10) : 0) != 0; |
||||
} |
||||
|
||||
if (!ignoreDebugger) { |
||||
pid_t pid = getpid(); |
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; |
||||
int mibSize = sizeof(mib) / sizeof(int); |
||||
size_t actualSize; |
||||
|
||||
if (sysctl(mib, mibSize, NULL, &actualSize, NULL, 0) == 0) { |
||||
struct kinfo_proc *info = (struct kinfo_proc *)malloc(actualSize); |
||||
|
||||
if (info) { |
||||
// This comes from looking at the Darwin xnu Kernel |
||||
if (sysctl(mib, mibSize, info, &actualSize, NULL, 0) == 0) |
||||
result = (info->kp_proc.p_flag & P_TRACED) ? YES : NO; |
||||
|
||||
free(info); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
//============================================================================= |
||||
bool Breakpad::ExceptionHandlerDirectCallback(void *context, |
||||
int exception_type, |
||||
int exception_code, |
||||
int exception_subcode, |
||||
mach_port_t crashing_thread) { |
||||
Breakpad *breakpad = (Breakpad *)context; |
||||
|
||||
// If our context is damaged or something, just return false to indicate that |
||||
// the handler should continue without us. |
||||
if (!breakpad) |
||||
return false; |
||||
|
||||
return breakpad->HandleException( exception_type, |
||||
exception_code, |
||||
exception_subcode, |
||||
crashing_thread); |
||||
} |
||||
|
||||
//============================================================================= |
||||
#pragma mark - |
||||
|
||||
#include <dlfcn.h> |
||||
|
||||
//============================================================================= |
||||
// Returns the pathname to the Resources directory for this version of |
||||
// Breakpad which we are now running. |
||||
// |
||||
// Don't make the function static, since _dyld_lookup_and_bind_fully needs a |
||||
// simple non-static C name |
||||
// |
||||
extern "C" { |
||||
NSString * GetResourcePath(); |
||||
NSString * GetResourcePath() { |
||||
NSString *resourcePath = nil; |
||||
|
||||
// If there are multiple breakpads installed then calling bundleWithIdentifier |
||||
// will not work properly, so only use that as a backup plan. |
||||
// We want to find the bundle containing the code where this function lives |
||||
// and work from there |
||||
// |
||||
|
||||
// Get the pathname to the code which contains this function |
||||
Dl_info info; |
||||
if (dladdr((const void*)GetResourcePath, &info) != 0) { |
||||
NSFileManager *filemgr = [NSFileManager defaultManager]; |
||||
NSString *filePath = |
||||
[filemgr stringWithFileSystemRepresentation:info.dli_fname |
||||
length:strlen(info.dli_fname)]; |
||||
NSString *bundlePath = [filePath stringByDeletingLastPathComponent]; |
||||
// The "Resources" directory should be in the same directory as the |
||||
// executable code, since that's how the Breakpad framework is built. |
||||
resourcePath = [bundlePath stringByAppendingPathComponent:@"Resources/"]; |
||||
} else { |
||||
DEBUGLOG(stderr, "Could not find GetResourcePath\n"); |
||||
// fallback plan |
||||
NSBundle *bundle = |
||||
[NSBundle bundleWithIdentifier:@"com.Google.BreakpadFramework"]; |
||||
resourcePath = [bundle resourcePath]; |
||||
} |
||||
|
||||
return resourcePath; |
||||
} |
||||
} // extern "C" |
||||
|
||||
//============================================================================= |
||||
bool Breakpad::Initialize(NSDictionary *parameters) { |
||||
// Initialize |
||||
config_params_ = NULL; |
||||
handler_ = NULL; |
||||
|
||||
// Check for debugger |
||||
if (IsDebuggerActive()) { |
||||
DEBUGLOG(stderr, "Debugger is active: Not installing handler\n"); |
||||
return true; |
||||
} |
||||
|
||||
// Gather any user specified parameters |
||||
if (!ExtractParameters(parameters)) { |
||||
return false; |
||||
} |
||||
|
||||
// Get path to Inspector executable. |
||||
NSString *inspectorPathString = KeyValue(@BREAKPAD_INSPECTOR_LOCATION); |
||||
|
||||
// Standardize path (resolve symlinkes, etc.) and escape spaces |
||||
inspectorPathString = [inspectorPathString stringByStandardizingPath]; |
||||
inspectorPathString = [[inspectorPathString componentsSeparatedByString:@" "] |
||||
componentsJoinedByString:@"\\ "]; |
||||
|
||||
// Create an on-demand server object representing the Inspector. |
||||
// In case of a crash, we simply need to call the LaunchOnDemand() |
||||
// method on it, then send a mach message to its service port. |
||||
// It will then launch and perform a process inspection of our crashed state. |
||||
// See the HandleException() method for the details. |
||||
#define RECEIVE_PORT_NAME "com.Breakpad.Inspector" |
||||
|
||||
name_t portName; |
||||
snprintf(portName, sizeof(name_t), "%s%d", RECEIVE_PORT_NAME, getpid()); |
||||
|
||||
// Save the location of the Inspector |
||||
strlcpy(inspector_path_, [inspectorPathString fileSystemRepresentation], |
||||
sizeof(inspector_path_)); |
||||
|
||||
// Append a single command-line argument to the Inspector path |
||||
// representing the bootstrap name of the launch-on-demand receive port. |
||||
// When the Inspector is launched, it can use this to lookup the port |
||||
// by calling bootstrap_check_in(). |
||||
strlcat(inspector_path_, " ", sizeof(inspector_path_)); |
||||
strlcat(inspector_path_, portName, sizeof(inspector_path_)); |
||||
|
||||
kern_return_t kr = inspector_.Initialize(inspector_path_, |
||||
portName, |
||||
true); // shutdown on exit |
||||
|
||||
if (kr != KERN_SUCCESS) { |
||||
return false; |
||||
} |
||||
|
||||
// Create the handler (allocating it in our special protected pool) |
||||
handler_ = |
||||
new (gBreakpadAllocator->Allocate( |
||||
sizeof(google_breakpad::ExceptionHandler))) |
||||
google_breakpad::ExceptionHandler( |
||||
Breakpad::ExceptionHandlerDirectCallback, this, true); |
||||
return true; |
||||
} |
||||
|
||||
//============================================================================= |
||||
Breakpad::~Breakpad() { |
||||
// Note that we don't use operator delete() on these pointers, |
||||
// since they were allocated by ProtectedMemoryAllocator objects. |
||||
// |
||||
if (config_params_) { |
||||
config_params_->~SimpleStringDictionary(); |
||||
} |
||||
|
||||
if (handler_) |
||||
handler_->~ExceptionHandler(); |
||||
} |
||||
|
||||
//============================================================================= |
||||
bool Breakpad::ExtractParameters(NSDictionary *parameters) { |
||||
NSUserDefaults *stdDefaults = [NSUserDefaults standardUserDefaults]; |
||||
NSString *skipConfirm = [stdDefaults stringForKey:@BREAKPAD_SKIP_CONFIRM]; |
||||
NSString *sendAndExit = [stdDefaults stringForKey:@BREAKPAD_SEND_AND_EXIT]; |
||||
|
||||
NSString *serverType = [parameters objectForKey:@BREAKPAD_SERVER_TYPE]; |
||||
NSString *display = [parameters objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; |
||||
NSString *product = [parameters objectForKey:@BREAKPAD_PRODUCT]; |
||||
NSString *version = [parameters objectForKey:@BREAKPAD_VERSION]; |
||||
NSString *urlStr = [parameters objectForKey:@BREAKPAD_URL]; |
||||
NSString *interval = [parameters objectForKey:@BREAKPAD_REPORT_INTERVAL]; |
||||
NSString *inspectorPathString = |
||||
[parameters objectForKey:@BREAKPAD_INSPECTOR_LOCATION]; |
||||
NSString *reporterPathString = |
||||
[parameters objectForKey:@BREAKPAD_REPORTER_EXE_LOCATION]; |
||||
NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT]; |
||||
NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES]; |
||||
NSString *logFileTailSize = |
||||
[parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE]; |
||||
NSString *requestUserText = |
||||
[parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS]; |
||||
NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL]; |
||||
NSString *vendor = |
||||
[parameters objectForKey:@BREAKPAD_VENDOR]; |
||||
NSString *dumpSubdirectory = |
||||
[parameters objectForKey:@BREAKPAD_DUMP_DIRECTORY]; |
||||
|
||||
NSDictionary *serverParameters = |
||||
[parameters objectForKey:@BREAKPAD_SERVER_PARAMETER_DICT]; |
||||
|
||||
// These may have been set above as user prefs, which take priority. |
||||
if (!skipConfirm) { |
||||
skipConfirm = [parameters objectForKey:@BREAKPAD_SKIP_CONFIRM]; |
||||
} |
||||
if (!sendAndExit) { |
||||
sendAndExit = [parameters objectForKey:@BREAKPAD_SEND_AND_EXIT]; |
||||
} |
||||
|
||||
if (!product) |
||||
product = [parameters objectForKey:@"CFBundleName"]; |
||||
|
||||
if (!display) { |
||||
display = [parameters objectForKey:@"CFBundleDisplayName"]; |
||||
if (!display) { |
||||
display = product; |
||||
} |
||||
} |
||||
|
||||
if (!version) |
||||
version = [parameters objectForKey:@"CFBundleVersion"]; |
||||
|
||||
if (!interval) |
||||
interval = @"3600"; |
||||
|
||||
if (!timeout) |
||||
timeout = @"300"; |
||||
|
||||
if (!logFileTailSize) |
||||
logFileTailSize = @"200000"; |
||||
|
||||
if (!vendor) { |
||||
vendor = @"Vendor not specified"; |
||||
} |
||||
|
||||
// Normalize the values. |
||||
if (skipConfirm) { |
||||
skipConfirm = [skipConfirm uppercaseString]; |
||||
|
||||
if ([skipConfirm isEqualToString:@"YES"] || |
||||
[skipConfirm isEqualToString:@"TRUE"] || |
||||
[skipConfirm isEqualToString:@"1"]) |
||||
skipConfirm = @"YES"; |
||||
else |
||||
skipConfirm = @"NO"; |
||||
} else { |
||||
skipConfirm = @"NO"; |
||||
} |
||||
|
||||
send_and_exit_ = true; |
||||
if (sendAndExit) { |
||||
sendAndExit = [sendAndExit uppercaseString]; |
||||
|
||||
if ([sendAndExit isEqualToString:@"NO"] || |
||||
[sendAndExit isEqualToString:@"FALSE"] || |
||||
[sendAndExit isEqualToString:@"0"]) |
||||
send_and_exit_ = false; |
||||
} |
||||
|
||||
if (requestUserText) { |
||||
requestUserText = [requestUserText uppercaseString]; |
||||
|
||||
if ([requestUserText isEqualToString:@"YES"] || |
||||
[requestUserText isEqualToString:@"TRUE"] || |
||||
[requestUserText isEqualToString:@"1"]) |
||||
requestUserText = @"YES"; |
||||
else |
||||
requestUserText = @"NO"; |
||||
} else { |
||||
requestUserText = @"NO"; |
||||
} |
||||
|
||||
// Find the helper applications if not specified in user config. |
||||
NSString *resourcePath = nil; |
||||
if (!inspectorPathString || !reporterPathString) { |
||||
resourcePath = GetResourcePath(); |
||||
if (!resourcePath) { |
||||
DEBUGLOG(stderr, "Could not get resource path\n"); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// Find Inspector. |
||||
if (!inspectorPathString) { |
||||
inspectorPathString = |
||||
[resourcePath stringByAppendingPathComponent:@"Inspector"]; |
||||
} |
||||
|
||||
// Verify that there is an Inspector tool. |
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) { |
||||
DEBUGLOG(stderr, "Cannot find Inspector tool\n"); |
||||
return false; |
||||
} |
||||
|
||||
// Find Reporter. |
||||
if (!reporterPathString) { |
||||
reporterPathString = |
||||
[resourcePath |
||||
stringByAppendingPathComponent:@"crash_report_sender.app"]; |
||||
reporterPathString = |
||||
[[NSBundle bundleWithPath:reporterPathString] executablePath]; |
||||
} |
||||
|
||||
// Verify that there is a Reporter application. |
||||
if (![[NSFileManager defaultManager] |
||||
fileExistsAtPath:reporterPathString]) { |
||||
DEBUGLOG(stderr, "Cannot find Reporter tool\n"); |
||||
return false; |
||||
} |
||||
|
||||
if (!dumpSubdirectory) { |
||||
dumpSubdirectory = @""; |
||||
} |
||||
|
||||
// The product, version, and URL are required values. |
||||
if (![product length]) { |
||||
DEBUGLOG(stderr, "Missing required product key.\n"); |
||||
return false; |
||||
} |
||||
|
||||
if (![version length]) { |
||||
DEBUGLOG(stderr, "Missing required version key.\n"); |
||||
return false; |
||||
} |
||||
|
||||
if (![urlStr length]) { |
||||
DEBUGLOG(stderr, "Missing required URL key.\n"); |
||||
return false; |
||||
} |
||||
|
||||
config_params_ = |
||||
new (gKeyValueAllocator->Allocate(sizeof(SimpleStringDictionary)) ) |
||||
SimpleStringDictionary(); |
||||
|
||||
SimpleStringDictionary &dictionary = *config_params_; |
||||
|
||||
dictionary.SetKeyValue(BREAKPAD_SERVER_TYPE, [serverType UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_PRODUCT_DISPLAY, [display UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_PRODUCT, [product UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_VERSION, [version UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_URL, [urlStr UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_REPORT_INTERVAL, [interval UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_SKIP_CONFIRM, [skipConfirm UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_CONFIRM_TIMEOUT, [timeout UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_INSPECTOR_LOCATION, |
||||
[inspectorPathString fileSystemRepresentation]); |
||||
dictionary.SetKeyValue(BREAKPAD_REPORTER_EXE_LOCATION, |
||||
[reporterPathString fileSystemRepresentation]); |
||||
dictionary.SetKeyValue(BREAKPAD_LOGFILE_UPLOAD_SIZE, |
||||
[logFileTailSize UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_REQUEST_COMMENTS, |
||||
[requestUserText UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_REQUEST_EMAIL, [requestEmail UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_VENDOR, [vendor UTF8String]); |
||||
dictionary.SetKeyValue(BREAKPAD_DUMP_DIRECTORY, |
||||
[dumpSubdirectory UTF8String]); |
||||
|
||||
struct timeval tv; |
||||
gettimeofday(&tv, NULL); |
||||
char timeStartedString[32]; |
||||
sprintf(timeStartedString, "%zd", tv.tv_sec); |
||||
dictionary.SetKeyValue(BREAKPAD_PROCESS_START_TIME, |
||||
timeStartedString); |
||||
|
||||
if (logFilePaths) { |
||||
char logFileKey[255]; |
||||
for(unsigned int i = 0; i < [logFilePaths count]; i++) { |
||||
sprintf(logFileKey,"%s%d", BREAKPAD_LOGFILE_KEY_PREFIX, i); |
||||
dictionary.SetKeyValue(logFileKey, |
||||
[[logFilePaths objectAtIndex:i] |
||||
fileSystemRepresentation]); |
||||
} |
||||
} |
||||
|
||||
if (serverParameters) { |
||||
// For each key-value pair, call BreakpadAddUploadParameter() |
||||
NSEnumerator *keyEnumerator = [serverParameters keyEnumerator]; |
||||
NSString *aParameter; |
||||
while ((aParameter = [keyEnumerator nextObject])) { |
||||
BreakpadAddUploadParameter(this, aParameter, |
||||
[serverParameters objectForKey:aParameter]); |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
//============================================================================= |
||||
void Breakpad::SetKeyValue(NSString *key, NSString *value) { |
||||
// We allow nil values. This is the same as removing the keyvalue. |
||||
if (!config_params_ || !key) |
||||
return; |
||||
|
||||
config_params_->SetKeyValue([key UTF8String], [value UTF8String]); |
||||
} |
||||
|
||||
//============================================================================= |
||||
NSString *Breakpad::KeyValue(NSString *key) { |
||||
if (!config_params_ || !key) |
||||
return nil; |
||||
|
||||
const char *value = config_params_->GetValueForKey([key UTF8String]); |
||||
return value ? [NSString stringWithUTF8String:value] : nil; |
||||
} |
||||
|
||||
//============================================================================= |
||||
void Breakpad::RemoveKeyValue(NSString *key) { |
||||
if (!config_params_ || !key) return; |
||||
|
||||
config_params_->RemoveKey([key UTF8String]); |
||||
} |
||||
|
||||
//============================================================================= |
||||
void Breakpad::GenerateAndSendReport() { |
||||
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "YES"); |
||||
HandleException(0, 0, 0, mach_thread_self()); |
||||
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "NO"); |
||||
} |
||||
|
||||
//============================================================================= |
||||
bool Breakpad::HandleException(int exception_type, |
||||
int exception_code, |
||||
int exception_subcode, |
||||
mach_port_t crashing_thread) { |
||||
DEBUGLOG(stderr, "Breakpad: an exception occurred\n"); |
||||
|
||||
if (filter_callback_) { |
||||
bool should_handle = filter_callback_(exception_type, |
||||
exception_code, |
||||
crashing_thread, |
||||
filter_callback_context_); |
||||
if (!should_handle) return false; |
||||
} |
||||
|
||||
// We need to reset the memory protections to be read/write, |
||||
// since LaunchOnDemand() requires changing state. |
||||
gBreakpadAllocator->Unprotect(); |
||||
// Configure the server to launch when we message the service port. |
||||
// The reason we do this here, rather than at startup, is that we |
||||
// can leak a bootstrap service entry if this method is called and |
||||
// there never ends up being a crash. |
||||
inspector_.LaunchOnDemand(); |
||||
gBreakpadAllocator->Protect(); |
||||
|
||||
// The Inspector should send a message to this port to verify it |
||||
// received our information and has finished the inspection. |
||||
ReceivePort acknowledge_port; |
||||
|
||||
// Send initial information to the Inspector. |
||||
MachSendMessage message(kMsgType_InspectorInitialInfo); |
||||
message.AddDescriptor(mach_task_self()); // our task |
||||
message.AddDescriptor(crashing_thread); // crashing thread |
||||
message.AddDescriptor(mach_thread_self()); // exception-handling thread |
||||
message.AddDescriptor(acknowledge_port.GetPort());// message receive port |
||||
|
||||
InspectorInfo info; |
||||
info.exception_type = exception_type; |
||||
info.exception_code = exception_code; |
||||
info.exception_subcode = exception_subcode; |
||||
info.parameter_count = config_params_->GetCount(); |
||||
message.SetData(&info, sizeof(info)); |
||||
|
||||
MachPortSender sender(inspector_.GetServicePort()); |
||||
|
||||
kern_return_t result = sender.SendMessage(message, 2000); |
||||
|
||||
if (result == KERN_SUCCESS) { |
||||
// Now, send a series of key-value pairs to the Inspector. |
||||
const KeyValueEntry *entry = NULL; |
||||
SimpleStringDictionaryIterator iter(*config_params_); |
||||
|
||||
while ( (entry = iter.Next()) ) { |
||||
KeyValueMessageData keyvalue_data(*entry); |
||||
|
||||
MachSendMessage keyvalue_message(kMsgType_InspectorKeyValuePair); |
||||
keyvalue_message.SetData(&keyvalue_data, sizeof(keyvalue_data)); |
||||
|
||||
result = sender.SendMessage(keyvalue_message, 2000); |
||||
|
||||
if (result != KERN_SUCCESS) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (result == KERN_SUCCESS) { |
||||
// Wait for acknowledgement that the inspection has finished. |
||||
MachReceiveMessage acknowledge_messsage; |
||||
result = acknowledge_port.WaitForMessage(&acknowledge_messsage, 5000); |
||||
} |
||||
} |
||||
|
||||
#if VERBOSE |
||||
PRINT_MACH_RESULT(result, "Breakpad: SendMessage "); |
||||
printf("Breakpad: Inspector service port = %#x\n", |
||||
inspector_.GetServicePort()); |
||||
#endif |
||||
|
||||
// If we don't want any forwarding, return true here to indicate that we've |
||||
// processed things as much as we want. |
||||
if (send_and_exit_) return true; |
||||
|
||||
return false; |
||||
} |
||||
|
||||
//============================================================================= |
||||
//============================================================================= |
||||
|
||||
#pragma mark - |
||||
#pragma mark Public API |
||||
|
||||
//============================================================================= |
||||
BreakpadRef BreakpadCreate(NSDictionary *parameters) { |
||||
try { |
||||
// This is confusing. Our two main allocators for breakpad memory are: |
||||
// - gKeyValueAllocator for the key/value memory |
||||
// - gBreakpadAllocator for the Breakpad, ExceptionHandler, and other |
||||
// breakpad allocations which are accessed at exception handling time. |
||||
// |
||||
// But in order to avoid these two allocators themselves from being smashed, |
||||
// we'll protect them as well by allocating them with gMasterAllocator. |
||||
// |
||||
// gMasterAllocator itself will NOT be protected, but this doesn't matter, |
||||
// since once it does its allocations and locks the memory, smashes to itself |
||||
// don't affect anything we care about. |
||||
gMasterAllocator = |
||||
new ProtectedMemoryAllocator(sizeof(ProtectedMemoryAllocator) * 2); |
||||
|
||||
gKeyValueAllocator = |
||||
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator))) |
||||
ProtectedMemoryAllocator(sizeof(SimpleStringDictionary)); |
||||
|
||||
// Create a mutex for use in accessing the SimpleStringDictionary |
||||
int mutexResult = pthread_mutex_init(&gDictionaryMutex, NULL); |
||||
if (mutexResult == 0) { |
||||
|
||||
// With the current compiler, gBreakpadAllocator is allocating 1444 bytes. |
||||
// Let's round up to the nearest page size. |
||||
// |
||||
int breakpad_pool_size = 4096; |
||||
|
||||
/* |
||||
sizeof(Breakpad) |
||||
+ sizeof(google_breakpad::ExceptionHandler) |
||||
+ sizeof( STUFF ALLOCATED INSIDE ExceptionHandler ) |
||||
*/ |
||||
|
||||
gBreakpadAllocator = |
||||
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator))) |
||||
ProtectedMemoryAllocator(breakpad_pool_size); |
||||
|
||||
// Stack-based autorelease pool for Breakpad::Create() obj-c code. |
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
||||
Breakpad *breakpad = Breakpad::Create(parameters); |
||||
|
||||
if (breakpad) { |
||||
// Make read-only to protect against memory smashers |
||||
gMasterAllocator->Protect(); |
||||
gKeyValueAllocator->Protect(); |
||||
gBreakpadAllocator->Protect(); |
||||
// Can uncomment this line to figure out how much space was actually |
||||
// allocated using this allocator |
||||
// printf("gBreakpadAllocator allocated size = %d\n", |
||||
// gBreakpadAllocator->GetAllocatedSize() ); |
||||
[pool release]; |
||||
return (BreakpadRef)breakpad; |
||||
} |
||||
|
||||
[pool release]; |
||||
} |
||||
} catch(...) { // don't let exceptions leave this C API |
||||
fprintf(stderr, "BreakpadCreate() : error\n"); |
||||
} |
||||
|
||||
if (gKeyValueAllocator) { |
||||
gKeyValueAllocator->~ProtectedMemoryAllocator(); |
||||
gKeyValueAllocator = NULL; |
||||
} |
||||
|
||||
if (gBreakpadAllocator) { |
||||
gBreakpadAllocator->~ProtectedMemoryAllocator(); |
||||
gBreakpadAllocator = NULL; |
||||
} |
||||
|
||||
delete gMasterAllocator; |
||||
gMasterAllocator = NULL; |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
//============================================================================= |
||||
void BreakpadRelease(BreakpadRef ref) { |
||||
try { |
||||
Breakpad *breakpad = (Breakpad *)ref; |
||||
|
||||
if (gMasterAllocator) { |
||||
gMasterAllocator->Unprotect(); |
||||
gKeyValueAllocator->Unprotect(); |
||||
gBreakpadAllocator->Unprotect(); |
||||
|
||||
breakpad->~Breakpad(); |
||||
|
||||
// Unfortunately, it's not possible to deallocate this stuff |
||||
// because the exception handling thread is still finishing up |
||||
// asynchronously at this point... OK, it could be done with |
||||
// locks, etc. But since BreakpadRelease() should usually only |
||||
// be called right before the process exits, it's not worth |
||||
// deallocating this stuff. |
||||
#if 0 |
||||
gKeyValueAllocator->~ProtectedMemoryAllocator(); |
||||
gBreakpadAllocator->~ProtectedMemoryAllocator(); |
||||
delete gMasterAllocator; |
||||
|
||||
gMasterAllocator = NULL; |
||||
gKeyValueAllocator = NULL; |
||||
gBreakpadAllocator = NULL; |
||||
#endif |
||||
|
||||
pthread_mutex_destroy(&gDictionaryMutex); |
||||
} |
||||
} catch(...) { // don't let exceptions leave this C API |
||||
fprintf(stderr, "BreakpadRelease() : error\n"); |
||||
} |
||||
} |
||||
|
||||
//============================================================================= |
||||
void BreakpadSetKeyValue(BreakpadRef ref, NSString *key, NSString *value) { |
||||
try { |
||||
// Not called at exception time |
||||
Breakpad *breakpad = (Breakpad *)ref; |
||||
|
||||
if (breakpad && key && gKeyValueAllocator) { |
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); |
||||
|
||||
breakpad->SetKeyValue(key, value); |
||||
} |
||||
} catch(...) { // don't let exceptions leave this C API |
||||
fprintf(stderr, "BreakpadSetKeyValue() : error\n"); |
||||
} |
||||
} |
||||
|
||||
void BreakpadAddUploadParameter(BreakpadRef ref, |
||||
NSString *key, |
||||
NSString *value) { |
||||
// The only difference, internally, between an upload parameter and |
||||
// a key value one that is set with BreakpadSetKeyValue is that we |
||||
// prepend the keyname with a special prefix. This informs the |
||||
// crash sender that the parameter should be sent along with the |
||||
// POST of the crash dump upload. |
||||
try { |
||||
Breakpad *breakpad = (Breakpad *)ref; |
||||
|
||||
if (breakpad && key && gKeyValueAllocator) { |
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); |
||||
|
||||
NSString *prefixedKey = [@BREAKPAD_SERVER_PARAMETER_PREFIX |
||||
stringByAppendingString:key]; |
||||
breakpad->SetKeyValue(prefixedKey, value); |
||||
} |
||||
} catch(...) { // don't let exceptions leave this C API |
||||
fprintf(stderr, "BreakpadSetKeyValue() : error\n"); |
||||
} |
||||
} |
||||
|
||||
void BreakpadRemoveUploadParameter(BreakpadRef ref, |
||||
NSString *key) { |
||||
try { |
||||
// Not called at exception time |
||||
Breakpad *breakpad = (Breakpad *)ref; |
||||
|
||||
if (breakpad && key && gKeyValueAllocator) { |
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); |
||||
|
||||
NSString *prefixedKey = [NSString stringWithFormat:@"%@%@", |
||||
@BREAKPAD_SERVER_PARAMETER_PREFIX, key]; |
||||
breakpad->RemoveKeyValue(prefixedKey); |
||||
} |
||||
} catch(...) { // don't let exceptions leave this C API |
||||
fprintf(stderr, "BreakpadRemoveKeyValue() : error\n"); |
||||
} |
||||
} |
||||
//============================================================================= |
||||
NSString *BreakpadKeyValue(BreakpadRef ref, NSString *key) { |
||||
NSString *value = nil; |
||||
|
||||
try { |
||||
// Not called at exception time |
||||
Breakpad *breakpad = (Breakpad *)ref; |
||||
|
||||
if (!breakpad || !key || !gKeyValueAllocator) |
||||
return nil; |
||||
|
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); |
||||
|
||||
value = breakpad->KeyValue(key); |
||||
} catch(...) { // don't let exceptions leave this C API |
||||
fprintf(stderr, "BreakpadKeyValue() : error\n"); |
||||
} |
||||
|
||||
return value; |
||||
} |
||||
|
||||
//============================================================================= |
||||
void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key) { |
||||
try { |
||||
// Not called at exception time |
||||
Breakpad *breakpad = (Breakpad *)ref; |
||||
|
||||
if (breakpad && key && gKeyValueAllocator) { |
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); |
||||
|
||||
breakpad->RemoveKeyValue(key); |
||||
} |
||||
} catch(...) { // don't let exceptions leave this C API |
||||
fprintf(stderr, "BreakpadRemoveKeyValue() : error\n"); |
||||
} |
||||
} |
||||
|
||||
//============================================================================= |
||||
void BreakpadGenerateAndSendReport(BreakpadRef ref) { |
||||
try { |
||||
Breakpad *breakpad = (Breakpad *)ref; |
||||
|
||||
if (breakpad && gKeyValueAllocator) { |
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); |
||||
|
||||
gBreakpadAllocator->Unprotect(); |
||||
breakpad->GenerateAndSendReport(); |
||||
gBreakpadAllocator->Protect(); |
||||
} |
||||
} catch(...) { // don't let exceptions leave this C API |
||||
fprintf(stderr, "BreakpadGenerateAndSendReport() : error\n"); |
||||
} |
||||
} |
||||
|
||||
//============================================================================= |
||||
void BreakpadSetFilterCallback(BreakpadRef ref, |
||||
BreakpadFilterCallback callback, |
||||
void *context) { |
||||
|
||||
try { |
||||
Breakpad *breakpad = (Breakpad *)ref; |
||||
|
||||
if (breakpad && gBreakpadAllocator) { |
||||
// share the dictionary mutex here (we really don't need a mutex) |
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gBreakpadAllocator); |
||||
|
||||
breakpad->SetFilterCallback(callback, context); |
||||
} |
||||
} catch(...) { // don't let exceptions leave this C API |
||||
fprintf(stderr, "BreakpadSetFilterCallback() : error\n"); |
||||
} |
||||
} |
||||
|
||||
//============================================================================ |
||||
void BreakpadAddLogFile(BreakpadRef ref, NSString *logPathname) { |
||||
int logFileCounter = 0; |
||||
|
||||
NSString *logFileKey = [NSString stringWithFormat:@"%@%d", |
||||
@BREAKPAD_LOGFILE_KEY_PREFIX, |
||||
logFileCounter]; |
||||
|
||||
NSString *existingLogFilename = nil; |
||||
existingLogFilename = BreakpadKeyValue(ref, logFileKey); |
||||
// Find the first log file key that we can use by testing for existence |
||||
while (existingLogFilename) { |
||||
if ([existingLogFilename isEqualToString:logPathname]) { |
||||
return; |
||||
} |
||||
logFileCounter++; |
||||
logFileKey = [NSString stringWithFormat:@"%@%d", |
||||
@BREAKPAD_LOGFILE_KEY_PREFIX, |
||||
logFileCounter]; |
||||
existingLogFilename = BreakpadKeyValue(ref, logFileKey); |
||||
} |
||||
|
||||
BreakpadSetKeyValue(ref, logFileKey, logPathname); |
||||
} |
@ -1,8 +0,0 @@ |
||||
// |
||||
// Prefix header for all source files of the 'Breakpad' target in the |
||||
// 'Breakpad' project. |
||||
// |
||||
|
||||
#ifdef __OBJC__ |
||||
#import <Cocoa/Cocoa.h> |
||||
#endif |
@ -1,26 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>CFBundleDevelopmentRegion</key> |
||||
<string>English</string> |
||||
<key>CFBundleExecutable</key> |
||||
<string>${EXECUTABLE_NAME}</string> |
||||
<key>CFBundleName</key> |
||||
<string>${PRODUCT_NAME}</string> |
||||
<key>CFBundleIconFile</key> |
||||
<string></string> |
||||
<key>CFBundleIdentifier</key> |
||||
<string>com.googlecode.google-breakpad</string> |
||||
<key>CFBundleInfoDictionaryVersion</key> |
||||
<string>6.0</string> |
||||
<key>CFBundlePackageType</key> |
||||
<string>FMWK</string> |
||||
<key>CFBundleSignature</key> |
||||
<string>????</string> |
||||
<key>CFBundleVersion</key> |
||||
<string>1.0</string> |
||||
<key>NSPrincipalClass</key> |
||||
<string></string> |
||||
</dict> |
||||
</plist> |
@ -1,146 +0,0 @@ |
||||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <iostream> |
||||
#import <mach/mach.h> |
||||
#import <servers/bootstrap.h> |
||||
#import <stdio.h> |
||||
#import <stdlib.h> |
||||
#import <sys/stat.h> |
||||
#import <unistd.h> |
||||
|
||||
//==============================================================================
|
||||
// class OnDemandServer :
|
||||
// A basic on-demand server launcher supporting a single named service port
|
||||
//
|
||||
// Example Usage :
|
||||
//
|
||||
// kern_return_t result;
|
||||
// OnDemandServer *server = OnDemandServer::Create("/tmp/myserver",
|
||||
// "com.MyCompany.MyServiceName",
|
||||
// true,
|
||||
// &result);
|
||||
//
|
||||
// if (server) {
|
||||
// server->LaunchOnDemand();
|
||||
// mach_port_t service_port = GetServicePort();
|
||||
//
|
||||
// // Send a mach message to service_port and "myserver" will be launched
|
||||
// }
|
||||
//
|
||||
//
|
||||
// ---- Now in the server code ----
|
||||
//
|
||||
// // "myserver" should get the service port and read the message which
|
||||
// // launched it:
|
||||
// mach_port_t service_rcv_port_;
|
||||
// kern_return_t kr = bootstrap_check_in(bootstrap_port,
|
||||
// "com.MyCompany.MyServiceName",
|
||||
// &service_rcv_port_);
|
||||
// // mach_msg() read service_rcv_port_ ....
|
||||
//
|
||||
// ....
|
||||
//
|
||||
// // Later "myserver" may want to unregister the service if it doesn't
|
||||
// // want its bootstrap service to stick around after it exits.
|
||||
//
|
||||
// // DO NOT use mach_port_deallocate() here -- it will fail and the
|
||||
// // following bootstrap_register() will also fail leaving our service
|
||||
// // name hanging around forever (until reboot)
|
||||
// kern_return_t kr = mach_port_destroy(mach_task_self(), service_rcv_port_);
|
||||
//
|
||||
// kr = bootstrap_register(bootstrap_port,
|
||||
// "com.MyCompany.MyServiceName",
|
||||
// MACH_PORT_NULL);
|
||||
|
||||
class OnDemandServer { |
||||
public: |
||||
// must call Initialize() to be useful
|
||||
OnDemandServer() |
||||
: server_port_(MACH_PORT_NULL), |
||||
service_port_(MACH_PORT_NULL), |
||||
unregister_on_cleanup_(true) { |
||||
} |
||||
|
||||
// Creates the bootstrap server and service
|
||||
kern_return_t Initialize(const char *server_command, |
||||
const char *service_name, |
||||
bool unregister_on_cleanup); |
||||
|
||||
// Returns an OnDemandServer object if successful, or NULL if there's
|
||||
// an error. The error result will be returned in out_result.
|
||||
//
|
||||
// server_command : the full path name including optional command-line
|
||||
// arguments to the executable representing the server
|
||||
//
|
||||
// service_name : represents service name
|
||||
// something like "com.company.ServiceName"
|
||||
//
|
||||
// unregister_on_cleanup : if true, unregisters the service name
|
||||
// when the OnDemandServer is deleted -- unregistering will
|
||||
// ONLY be possible if LaunchOnDemand() has NOT been called.
|
||||
// If false, then the service will continue to be registered
|
||||
// even after the current process quits.
|
||||
//
|
||||
// out_result : if non-NULL, returns the result
|
||||
// this value will be KERN_SUCCESS if Create() returns non-NULL
|
||||
//
|
||||
static OnDemandServer *Create(const char *server_command, |
||||
const char *service_name, |
||||
bool unregister_on_cleanup, |
||||
kern_return_t *out_result); |
||||
|
||||
// Cleans up and if LaunchOnDemand() has not yet been called then
|
||||
// the bootstrap service will be unregistered.
|
||||
~OnDemandServer(); |
||||
|
||||
// This must be called if we intend to commit to launching the server
|
||||
// by sending a mach message to our service port. Do not call it otherwise
|
||||
// or it will be difficult (impossible?) to unregister the service name.
|
||||
void LaunchOnDemand(); |
||||
|
||||
// This is the port we need to send a mach message to after calling
|
||||
// LaunchOnDemand(). Sending a message causing an immediate launch
|
||||
// of the server
|
||||
mach_port_t GetServicePort() { return service_port_; }; |
||||
|
||||
private: |
||||
// Disallow copy constructor
|
||||
OnDemandServer(const OnDemandServer&); |
||||
|
||||
// Cleans up and if LaunchOnDemand() has not yet been called then
|
||||
// the bootstrap service will be unregistered.
|
||||
void Unregister(); |
||||
|
||||
name_t service_name_; |
||||
|
||||
mach_port_t server_port_; |
||||
mach_port_t service_port_; |
||||
bool unregister_on_cleanup_; |
||||
}; |
@ -1,145 +0,0 @@ |
||||
// Copyright (c) 2007, Google Inc. |
||||
// All rights reserved. |
||||
// |
||||
// Redistribution and use in source and binary forms, with or without |
||||
// modification, are permitted provided that the following conditions are |
||||
// met: |
||||
// |
||||
// * Redistributions of source code must retain the above copyright |
||||
// notice, this list of conditions and the following disclaimer. |
||||
// * Redistributions in binary form must reproduce the above |
||||
// copyright notice, this list of conditions and the following disclaimer |
||||
// in the documentation and/or other materials provided with the |
||||
// distribution. |
||||
// * Neither the name of Google Inc. nor the names of its |
||||
// contributors may be used to endorse or promote products derived from |
||||
// this software without specific prior written permission. |
||||
// |
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
#import "OnDemandServer.h" |
||||
|
||||
#if DEBUG |
||||
#define PRINT_MACH_RESULT(result_, message_) \ |
||||
printf(message_"%s (%d)\n", mach_error_string(result_), result_ ); |
||||
#else |
||||
#define PRINT_MACH_RESULT(result_, message_) |
||||
#endif |
||||
|
||||
//============================================================================== |
||||
OnDemandServer *OnDemandServer::Create(const char *server_command, |
||||
const char *service_name, |
||||
bool unregister_on_cleanup, |
||||
kern_return_t *out_result) { |
||||
OnDemandServer *server = new OnDemandServer(); |
||||
|
||||
if (!server) return NULL; |
||||
|
||||
kern_return_t result = server->Initialize(server_command, |
||||
service_name, |
||||
unregister_on_cleanup); |
||||
|
||||
if (out_result) { |
||||
*out_result = result; |
||||
} |
||||
|
||||
if (result == KERN_SUCCESS) { |
||||
return server; |
||||
} |
||||
|
||||
delete server; |
||||
return NULL; |
||||
}; |
||||
|
||||
//============================================================================== |
||||
kern_return_t OnDemandServer::Initialize(const char *server_command, |
||||
const char *service_name, |
||||
bool unregister_on_cleanup) { |
||||
unregister_on_cleanup_ = unregister_on_cleanup; |
||||
|
||||
kern_return_t kr = |
||||
bootstrap_create_server(bootstrap_port, |
||||
const_cast<char*>(server_command), |
||||
geteuid(), // server uid |
||||
true, |
||||
&server_port_); |
||||
|
||||
if (kr != KERN_SUCCESS) { |
||||
PRINT_MACH_RESULT(kr, "bootstrap_create_server() : "); |
||||
return kr; |
||||
} |
||||
|
||||
strlcpy(service_name_, service_name, sizeof(service_name_)); |
||||
|
||||
// Create a service called service_name, and return send rights to |
||||
// that port in service_port_. |
||||
kr = bootstrap_create_service(server_port_, |
||||
const_cast<char*>(service_name), |
||||
&service_port_); |
||||
|
||||
if (kr != KERN_SUCCESS) { |
||||
PRINT_MACH_RESULT(kr, "bootstrap_create_service() : "); |
||||
|
||||
// perhaps the service has already been created - try to look it up |
||||
kr = bootstrap_look_up(bootstrap_port, (char*)service_name, &service_port_); |
||||
|
||||
if (kr != KERN_SUCCESS) { |
||||
PRINT_MACH_RESULT(kr, "bootstrap_look_up() : "); |
||||
Unregister(); // clean up server port |
||||
return kr; |
||||
} |
||||
} |
||||
|
||||
return KERN_SUCCESS; |
||||
} |
||||
|
||||
//============================================================================== |
||||
OnDemandServer::~OnDemandServer() { |
||||
if (unregister_on_cleanup_) { |
||||
Unregister(); |
||||
} |
||||
} |
||||
|
||||
//============================================================================== |
||||
void OnDemandServer::LaunchOnDemand() { |
||||
// We need to do this, since the launched server is another process |
||||
// and holding on to this port delays launching until the current process |
||||
// exits! |
||||
mach_port_deallocate(mach_task_self(), server_port_); |
||||
server_port_ = MACH_PORT_DEAD; |
||||
|
||||
// Now, the service is still registered and all we need to do is send |
||||
// a mach message to the service port in order to launch the server. |
||||
} |
||||
|
||||
//============================================================================== |
||||
void OnDemandServer::Unregister() { |
||||
if (service_port_ != MACH_PORT_NULL) { |
||||
mach_port_deallocate(mach_task_self(), service_port_); |
||||
service_port_ = MACH_PORT_NULL; |
||||
} |
||||
|
||||
if (server_port_ != MACH_PORT_NULL) { |
||||
// unregister the service |
||||
kern_return_t kr = bootstrap_register(server_port_, |
||||
service_name_, |
||||
MACH_PORT_NULL); |
||||
|
||||
if (kr != KERN_SUCCESS) { |
||||
PRINT_MACH_RESULT(kr, "Breakpad UNREGISTER : bootstrap_register() : "); |
||||
} |
||||
|
||||
mach_port_deallocate(mach_task_self(), server_port_); |
||||
server_port_ = MACH_PORT_NULL; |
||||
} |
||||
} |
@ -1,20 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>CFBundleDevelopmentRegion</key> |
||||
<string>English</string> |
||||
<key>CFBundleExecutable</key> |
||||
<string>${EXECUTABLE_NAME}</string> |
||||
<key>CFBundleIdentifier</key> |
||||
<string>com.yourcompany.${PRODUCT_NAME:identifier}</string> |
||||
<key>CFBundleInfoDictionaryVersion</key> |
||||
<string>6.0</string> |
||||
<key>CFBundlePackageType</key> |
||||
<string>BNDL</string> |
||||
<key>CFBundleSignature</key> |
||||
<string>????</string> |
||||
<key>CFBundleVersion</key> |
||||
<string>1.0</string> |
||||
</dict> |
||||
</plist> |
@ -1,193 +0,0 @@ |
||||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Interface file between the Breakpad.framework and
|
||||
// the Inspector process.
|
||||
|
||||
#import "common/mac/SimpleStringDictionary.h" |
||||
|
||||
#import <Foundation/Foundation.h> |
||||
#import "client/mac/handler/minidump_generator.h" |
||||
|
||||
#define VERBOSE 0 |
||||
|
||||
extern bool gDebugLog; |
||||
|
||||
#define DEBUGLOG if (gDebugLog) fprintf |
||||
|
||||
// Types of mach messsages (message IDs)
|
||||
enum { |
||||
kMsgType_InspectorInitialInfo = 0, // data is InspectorInfo
|
||||
kMsgType_InspectorKeyValuePair = 1, // data is KeyValueMessageData
|
||||
kMsgType_InspectorAcknowledgement = 2 // no data sent
|
||||
}; |
||||
|
||||
// Initial information sent from the crashed process by
|
||||
// Breakpad.framework to the Inspector process
|
||||
// The mach message with this struct as data will also include
|
||||
// several descriptors for sending mach port rights to the crashed
|
||||
// task, etc.
|
||||
struct InspectorInfo { |
||||
int exception_type; |
||||
int exception_code; |
||||
int exception_subcode; |
||||
unsigned int parameter_count; // key-value pairs
|
||||
}; |
||||
|
||||
// Key/value message data to be sent to the Inspector
|
||||
struct KeyValueMessageData { |
||||
public: |
||||
KeyValueMessageData() {} |
||||
KeyValueMessageData(const google_breakpad::KeyValueEntry &inEntry) { |
||||
strlcpy(key, inEntry.GetKey(), sizeof(key) ); |
||||
strlcpy(value, inEntry.GetValue(), sizeof(value) ); |
||||
} |
||||
|
||||
char key[google_breakpad::KeyValueEntry::MAX_STRING_STORAGE_SIZE]; |
||||
char value[google_breakpad::KeyValueEntry::MAX_STRING_STORAGE_SIZE]; |
||||
}; |
||||
|
||||
using std::string; |
||||
using google_breakpad::MinidumpGenerator; |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
static BOOL EnsureDirectoryPathExists(NSString *dirPath); |
||||
|
||||
//=============================================================================
|
||||
class ConfigFile { |
||||
public: |
||||
ConfigFile() { |
||||
config_file_ = -1; |
||||
config_file_path_[0] = 0; |
||||
has_created_file_ = false; |
||||
}; |
||||
|
||||
~ConfigFile() { |
||||
}; |
||||
|
||||
void WriteFile(const SimpleStringDictionary *configurationParameters, |
||||
const char *dump_dir, |
||||
const char *minidump_id); |
||||
|
||||
const char *GetFilePath() { return config_file_path_; } |
||||
|
||||
void Unlink() { |
||||
if (config_file_ != -1) |
||||
unlink(config_file_path_); |
||||
|
||||
config_file_ = -1; |
||||
} |
||||
|
||||
private: |
||||
BOOL WriteData(const void *data, size_t length); |
||||
|
||||
BOOL AppendConfigData(const char *key, |
||||
const void *data, |
||||
size_t length); |
||||
|
||||
BOOL AppendConfigString(const char *key, |
||||
const char *value); |
||||
|
||||
int config_file_; // descriptor for config file
|
||||
char config_file_path_[PATH_MAX]; // Path to configuration file
|
||||
bool has_created_file_; |
||||
}; |
||||
|
||||
//=============================================================================
|
||||
class MinidumpLocation { |
||||
public: |
||||
MinidumpLocation(const NSString *minidumpDir) { |
||||
// Ensure that the path exists. Fallback to /tmp if unable to locate path.
|
||||
assert(minidumpDir); |
||||
if (!EnsureDirectoryPathExists(minidumpDir)) { |
||||
DEBUGLOG(stderr, "Unable to create: %s\n", [minidumpDir UTF8String]); |
||||
minidumpDir = @"/tmp"; |
||||
} |
||||
|
||||
strlcpy(minidump_dir_path_, [minidumpDir fileSystemRepresentation], |
||||
sizeof(minidump_dir_path_)); |
||||
|
||||
// now generate a unique ID
|
||||
string dump_path(minidump_dir_path_); |
||||
string next_minidump_id; |
||||
|
||||
string next_minidump_path_ = |
||||
(MinidumpGenerator::UniqueNameInDirectory(dump_path, &next_minidump_id)); |
||||
|
||||
strlcpy(minidump_id_, next_minidump_id.c_str(), sizeof(minidump_id_)); |
||||
}; |
||||
|
||||
const char *GetPath() { return minidump_dir_path_; } |
||||
const char *GetID() { return minidump_id_; } |
||||
|
||||
private: |
||||
char minidump_dir_path_[PATH_MAX]; // Path to minidump directory
|
||||
char minidump_id_[128]; |
||||
}; |
||||
|
||||
//=============================================================================
|
||||
class Inspector { |
||||
public: |
||||
Inspector() {}; |
||||
|
||||
// given a bootstrap service name, receives mach messages
|
||||
// from a crashed process, then inspects it, creates a minidump file
|
||||
// and asks the user if he wants to upload it to a server.
|
||||
void Inspect(const char *receive_port_name); |
||||
|
||||
private: |
||||
kern_return_t ServiceCheckIn(const char *receive_port_name); |
||||
kern_return_t ServiceCheckOut(const char *receive_port_name); |
||||
|
||||
kern_return_t ReadMessages(); |
||||
|
||||
bool InspectTask(); |
||||
kern_return_t SendAcknowledgement(); |
||||
void LaunchReporter(const char *inConfigFilePath); |
||||
|
||||
void SetCrashTimeParameters(); |
||||
|
||||
mach_port_t service_rcv_port_; |
||||
|
||||
int exception_type_; |
||||
int exception_code_; |
||||
int exception_subcode_; |
||||
mach_port_t remote_task_; |
||||
mach_port_t crashing_thread_; |
||||
mach_port_t handler_thread_; |
||||
mach_port_t ack_port_; |
||||
|
||||
SimpleStringDictionary config_params_; |
||||
|
||||
ConfigFile config_file_; |
||||
}; |
||||
|
||||
|
||||
} // namespace google_breakpad
|
@ -1,555 +0,0 @@ |
||||
// Copyright (c) 2007, Google Inc. |
||||
// All rights reserved. |
||||
// |
||||
// Redistribution and use in source and binary forms, with or without |
||||
// modification, are permitted provided that the following conditions are |
||||
// met: |
||||
// |
||||
// * Redistributions of source code must retain the above copyright |
||||
// notice, this list of conditions and the following disclaimer. |
||||
// * Redistributions in binary form must reproduce the above |
||||
// copyright notice, this list of conditions and the following disclaimer |
||||
// in the documentation and/or other materials provided with the |
||||
// distribution. |
||||
// * Neither the name of Google Inc. nor the names of its |
||||
// contributors may be used to endorse or promote products derived from |
||||
// this software without specific prior written permission. |
||||
// |
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
// |
||||
// Utility that can inspect another process and write a crash dump |
||||
|
||||
#include <cstdio> |
||||
#include <iostream> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <string> |
||||
#include <sys/time.h> |
||||
|
||||
#import "client/mac/crash_generation/Inspector.h" |
||||
|
||||
#import "client/mac/Framework/Breakpad.h" |
||||
#import "client/mac/handler/minidump_generator.h" |
||||
|
||||
#import "common/mac/SimpleStringDictionary.h" |
||||
#import "common/mac/MachIPC.h" |
||||
|
||||
#import "GTMDefines.h" |
||||
|
||||
#import <Foundation/Foundation.h> |
||||
|
||||
#if VERBOSE |
||||
bool gDebugLog = true; |
||||
#else |
||||
bool gDebugLog = false; |
||||
#endif |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
//============================================================================= |
||||
static BOOL EnsureDirectoryPathExists(NSString *dirPath) { |
||||
NSFileManager *mgr = [NSFileManager defaultManager]; |
||||
|
||||
// If we got a relative path, prepend the current directory |
||||
if (![dirPath isAbsolutePath]) |
||||
dirPath = [[mgr currentDirectoryPath] stringByAppendingPathComponent:dirPath]; |
||||
|
||||
NSString *path = dirPath; |
||||
|
||||
// Ensure that no file exists within the path which would block creation |
||||
while (1) { |
||||
BOOL isDir; |
||||
if ([mgr fileExistsAtPath:path isDirectory:&isDir]) { |
||||
if (isDir) |
||||
break; |
||||
|
||||
return NO; |
||||
} |
||||
|
||||
path = [path stringByDeletingLastPathComponent]; |
||||
} |
||||
|
||||
// Path now contains the first valid directory (or is empty) |
||||
if (![path length]) |
||||
return NO; |
||||
|
||||
NSString *common = |
||||
[dirPath commonPrefixWithString:path options:NSLiteralSearch]; |
||||
|
||||
// If everything is good |
||||
if ([common isEqualToString:dirPath]) |
||||
return YES; |
||||
|
||||
// Break up the difference into components |
||||
NSString *diff = [dirPath substringFromIndex:[common length] + 1]; |
||||
NSArray *components = [diff pathComponents]; |
||||
NSUInteger count = [components count]; |
||||
|
||||
// Rebuild the path one component at a time |
||||
NSDictionary *attrs = |
||||
[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:0750] |
||||
forKey:NSFilePosixPermissions]; |
||||
path = common; |
||||
for (NSUInteger i = 0; i < count; ++i) { |
||||
path = [path stringByAppendingPathComponent:[components objectAtIndex:i]]; |
||||
|
||||
if (![mgr createDirectoryAtPath:path attributes:attrs]) |
||||
return NO; |
||||
} |
||||
|
||||
return YES; |
||||
} |
||||
|
||||
//============================================================================= |
||||
BOOL ConfigFile::WriteData(const void *data, size_t length) { |
||||
size_t result = write(config_file_, data, length); |
||||
|
||||
return result == length; |
||||
} |
||||
|
||||
//============================================================================= |
||||
BOOL ConfigFile::AppendConfigData(const char *key, |
||||
const void *data, size_t length) { |
||||
assert(config_file_ != -1); |
||||
|
||||
if (!key) { |
||||
DEBUGLOG(stderr, "Breakpad: Missing Key\n"); |
||||
return NO; |
||||
} |
||||
|
||||
if (!data) { |
||||
DEBUGLOG(stderr, "Breakpad: Missing data for key: %s\n", key ? key : |
||||
"<Unknown Key>"); |
||||
return NO; |
||||
} |
||||
|
||||
// Write the key, \n, length of data (ascii integer), \n, data |
||||
char buffer[16]; |
||||
char nl = '\n'; |
||||
BOOL result = WriteData(key, strlen(key)); |
||||
|
||||
snprintf(buffer, sizeof(buffer) - 1, "\n%lu\n", length); |
||||
result &= WriteData(buffer, strlen(buffer)); |
||||
result &= WriteData(data, length); |
||||
result &= WriteData(&nl, 1); |
||||
return result; |
||||
} |
||||
|
||||
//============================================================================= |
||||
BOOL ConfigFile::AppendConfigString(const char *key, |
||||
const char *value) { |
||||
return AppendConfigData(key, value, strlen(value)); |
||||
} |
||||
|
||||
//============================================================================= |
||||
void ConfigFile::WriteFile(const SimpleStringDictionary *configurationParameters, |
||||
const char *dump_dir, |
||||
const char *minidump_id) { |
||||
|
||||
assert(config_file_ == -1); |
||||
|
||||
// Open and write out configuration file preamble |
||||
strlcpy(config_file_path_, "/tmp/Config-XXXXXX", |
||||
sizeof(config_file_path_)); |
||||
config_file_ = mkstemp(config_file_path_); |
||||
|
||||
if (config_file_ == -1) { |
||||
DEBUGLOG(stderr, |
||||
"mkstemp(config_file_path_) == -1 (%s)\n", |
||||
strerror(errno)); |
||||
return; |
||||
} |
||||
else { |
||||
DEBUGLOG(stderr, "Writing config file to (%s)\n", config_file_path_); |
||||
} |
||||
|
||||
has_created_file_ = true; |
||||
|
||||
// Add the minidump dir |
||||
AppendConfigString(kReporterMinidumpDirectoryKey, dump_dir); |
||||
AppendConfigString(kReporterMinidumpIDKey, minidump_id); |
||||
|
||||
// Write out the configuration parameters |
||||
BOOL result = YES; |
||||
const SimpleStringDictionary &dictionary = *configurationParameters; |
||||
|
||||
const KeyValueEntry *entry = NULL; |
||||
SimpleStringDictionaryIterator iter(dictionary); |
||||
|
||||
while ((entry = iter.Next())) { |
||||
DEBUGLOG(stderr, |
||||
"config: (%s) -> (%s)\n", |
||||
entry->GetKey(), |
||||
entry->GetValue()); |
||||
result = AppendConfigString(entry->GetKey(), entry->GetValue()); |
||||
|
||||
if (!result) |
||||
break; |
||||
} |
||||
|
||||
close(config_file_); |
||||
config_file_ = -1; |
||||
} |
||||
|
||||
//============================================================================= |
||||
void Inspector::Inspect(const char *receive_port_name) { |
||||
kern_return_t result = ServiceCheckIn(receive_port_name); |
||||
|
||||
if (result == KERN_SUCCESS) { |
||||
result = ReadMessages(); |
||||
|
||||
if (result == KERN_SUCCESS) { |
||||
// Inspect the task and write a minidump file. |
||||
bool wrote_minidump = InspectTask(); |
||||
|
||||
// Send acknowledgement to the crashed process that the inspection |
||||
// has finished. It will then be able to cleanly exit. |
||||
// The return value is ignored because failure isn't fatal. If the process |
||||
// didn't get the message there's nothing we can do, and we still want to |
||||
// send the report. |
||||
SendAcknowledgement(); |
||||
|
||||
if (wrote_minidump) { |
||||
// Ask the user if he wants to upload the crash report to a server, |
||||
// and do so if he agrees. |
||||
LaunchReporter(config_file_.GetFilePath()); |
||||
} else { |
||||
fprintf(stderr, "Inspection of crashed process failed\n"); |
||||
} |
||||
|
||||
// Now that we're done reading messages, cleanup the service, but only |
||||
// if there was an actual exception |
||||
// Otherwise, it means the dump was generated on demand and the process |
||||
// lives on, and we might be needed again in the future. |
||||
if (exception_code_) { |
||||
ServiceCheckOut(receive_port_name); |
||||
} |
||||
} else { |
||||
PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
//============================================================================= |
||||
kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) { |
||||
// We need to get the mach port representing this service, so we can |
||||
// get information from the crashed process. |
||||
kern_return_t kr = bootstrap_check_in(bootstrap_port, |
||||
(char*)receive_port_name, |
||||
&service_rcv_port_); |
||||
|
||||
if (kr != KERN_SUCCESS) { |
||||
#if VERBOSE |
||||
PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()"); |
||||
#endif |
||||
} |
||||
|
||||
return kr; |
||||
} |
||||
|
||||
//============================================================================= |
||||
kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) { |
||||
// We're done receiving mach messages from the crashed process, |
||||
// so clean up a bit. |
||||
kern_return_t kr; |
||||
|
||||
// DO NOT use mach_port_deallocate() here -- it will fail and the |
||||
// following bootstrap_register() will also fail leaving our service |
||||
// name hanging around forever (until reboot) |
||||
kr = mach_port_destroy(mach_task_self(), service_rcv_port_); |
||||
|
||||
if (kr != KERN_SUCCESS) { |
||||
PRINT_MACH_RESULT(kr, |
||||
"Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()"); |
||||
return kr; |
||||
} |
||||
|
||||
// Unregister the service associated with the receive port. |
||||
kr = bootstrap_register(bootstrap_port, |
||||
(char*)receive_port_name, |
||||
MACH_PORT_NULL); |
||||
|
||||
if (kr != KERN_SUCCESS) { |
||||
PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()"); |
||||
} |
||||
|
||||
return kr; |
||||
} |
||||
|
||||
//============================================================================= |
||||
kern_return_t Inspector::ReadMessages() { |
||||
// Wait for an initial message from the crashed process containing basic |
||||
// information about the crash. |
||||
ReceivePort receive_port(service_rcv_port_); |
||||
|
||||
MachReceiveMessage message; |
||||
kern_return_t result = receive_port.WaitForMessage(&message, 1000); |
||||
|
||||
if (result == KERN_SUCCESS) { |
||||
InspectorInfo &info = (InspectorInfo &)*message.GetData(); |
||||
exception_type_ = info.exception_type; |
||||
exception_code_ = info.exception_code; |
||||
exception_subcode_ = info.exception_subcode; |
||||
|
||||
#if VERBOSE |
||||
printf("message ID = %d\n", message.GetMessageID()); |
||||
#endif |
||||
|
||||
remote_task_ = message.GetTranslatedPort(0); |
||||
crashing_thread_ = message.GetTranslatedPort(1); |
||||
handler_thread_ = message.GetTranslatedPort(2); |
||||
ack_port_ = message.GetTranslatedPort(3); |
||||
|
||||
#if VERBOSE |
||||
printf("exception_type = %d\n", exception_type_); |
||||
printf("exception_code = %d\n", exception_code_); |
||||
printf("exception_subcode = %d\n", exception_subcode_); |
||||
printf("remote_task = %d\n", remote_task_); |
||||
printf("crashing_thread = %d\n", crashing_thread_); |
||||
printf("handler_thread = %d\n", handler_thread_); |
||||
printf("ack_port_ = %d\n", ack_port_); |
||||
printf("parameter count = %d\n", info.parameter_count); |
||||
#endif |
||||
|
||||
// In certain situations where multiple crash requests come |
||||
// through quickly, we can end up with the mach IPC messages not |
||||
// coming through correctly. Since we don't know what parameters |
||||
// we've missed, we can't do much besides abort the crash dump |
||||
// situation in this case. |
||||
unsigned int parameters_read = 0; |
||||
// The initial message contains the number of key value pairs that |
||||
// we are expected to read. |
||||
// Read each key/value pair, one mach message per key/value pair. |
||||
for (unsigned int i = 0; i < info.parameter_count; ++i) { |
||||
MachReceiveMessage parameter_message; |
||||
result = receive_port.WaitForMessage(¶meter_message, 1000); |
||||
|
||||
if(result == KERN_SUCCESS) { |
||||
KeyValueMessageData &key_value_data = |
||||
(KeyValueMessageData&)*parameter_message.GetData(); |
||||
// If we get a blank key, make sure we don't increment the |
||||
// parameter count; in some cases (notably on-demand generation |
||||
// many times in a short period of time) caused the Mach IPC |
||||
// messages to not come through correctly. |
||||
if (strlen(key_value_data.key) == 0) { |
||||
continue; |
||||
} |
||||
parameters_read++; |
||||
|
||||
config_params_.SetKeyValue(key_value_data.key, key_value_data.value); |
||||
} else { |
||||
PRINT_MACH_RESULT(result, "Inspector: key/value message"); |
||||
break; |
||||
} |
||||
} |
||||
if (parameters_read != info.parameter_count) { |
||||
DEBUGLOG(stderr, "Only read %d parameters instead of %d, aborting crash " |
||||
"dump generation.", parameters_read, info.parameter_count); |
||||
return KERN_FAILURE; |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
//============================================================================= |
||||
// Sets keys in the parameters dictionary that are specific to process uptime. |
||||
// The two we set are process up time, and process crash time. |
||||
void Inspector::SetCrashTimeParameters() { |
||||
// Set process uptime parameter |
||||
struct timeval tv; |
||||
gettimeofday(&tv, NULL); |
||||
|
||||
char processUptimeString[32], processCrashtimeString[32]; |
||||
const char *processStartTimeString = |
||||
config_params_.GetValueForKey(BREAKPAD_PROCESS_START_TIME); |
||||
|
||||
// Set up time if we've received the start time. |
||||
if (processStartTimeString) { |
||||
time_t processStartTime = strtol(processStartTimeString, NULL, 10); |
||||
time_t processUptime = tv.tv_sec - processStartTime; |
||||
sprintf(processUptimeString, "%zd", processUptime); |
||||
config_params_.SetKeyValue(BREAKPAD_PROCESS_UP_TIME, processUptimeString); |
||||
} |
||||
|
||||
sprintf(processCrashtimeString, "%zd", tv.tv_sec); |
||||
config_params_.SetKeyValue(BREAKPAD_PROCESS_CRASH_TIME, |
||||
processCrashtimeString); |
||||
} |
||||
|
||||
bool Inspector::InspectTask() { |
||||
// keep the task quiet while we're looking at it |
||||
task_suspend(remote_task_); |
||||
DEBUGLOG(stderr, "Suspended Remote task\n"); |
||||
|
||||
NSString *minidumpDir; |
||||
|
||||
const char *minidumpDirectory = |
||||
config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY); |
||||
|
||||
SetCrashTimeParameters(); |
||||
// If the client app has not specified a minidump directory, |
||||
// use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name> |
||||
if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) { |
||||
NSArray *libraryDirectories = |
||||
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, |
||||
NSUserDomainMask, |
||||
YES); |
||||
|
||||
NSString *applicationSupportDirectory = |
||||
[libraryDirectories objectAtIndex:0]; |
||||
NSString *library_subdirectory = [NSString |
||||
stringWithUTF8String:kDefaultLibrarySubdirectory]; |
||||
NSString *breakpad_product = [NSString |
||||
stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)]; |
||||
|
||||
NSArray *path_components = [NSArray |
||||
arrayWithObjects:applicationSupportDirectory, |
||||
library_subdirectory, |
||||
breakpad_product, |
||||
nil]; |
||||
|
||||
minidumpDir = [NSString pathWithComponents:path_components]; |
||||
} else { |
||||
minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory] |
||||
stringByExpandingTildeInPath]; |
||||
} |
||||
DEBUGLOG(stderr, |
||||
"Writing minidump to directory (%s)\n", |
||||
[minidumpDir UTF8String]); |
||||
|
||||
MinidumpLocation minidumpLocation(minidumpDir); |
||||
|
||||
// Obscure bug alert: |
||||
// Don't use [NSString stringWithFormat] to build up the path here since it |
||||
// assumes system encoding and in RTL locales will prepend an LTR override |
||||
// character for paths beginning with '/' which fileSystemRepresentation does |
||||
// not remove. Filed as rdar://6889706 . |
||||
NSString *path_ns = [NSString |
||||
stringWithUTF8String:minidumpLocation.GetPath()]; |
||||
NSString *pathid_ns = [NSString |
||||
stringWithUTF8String:minidumpLocation.GetID()]; |
||||
NSString *minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns]; |
||||
minidumpPath = [minidumpPath |
||||
stringByAppendingPathExtension:@"dmp"]; |
||||
|
||||
DEBUGLOG(stderr, |
||||
"minidump path (%s)\n", |
||||
[minidumpPath UTF8String]); |
||||
|
||||
|
||||
config_file_.WriteFile( &config_params_, |
||||
minidumpLocation.GetPath(), |
||||
minidumpLocation.GetID()); |
||||
|
||||
|
||||
MinidumpGenerator generator(remote_task_, handler_thread_); |
||||
|
||||
if (exception_type_ && exception_code_) { |
||||
generator.SetExceptionInformation(exception_type_, |
||||
exception_code_, |
||||
exception_subcode_, |
||||
crashing_thread_); |
||||
} |
||||
|
||||
|
||||
bool result = generator.Write([minidumpPath fileSystemRepresentation]); |
||||
|
||||
if (result) { |
||||
DEBUGLOG(stderr, "Wrote minidump - OK\n"); |
||||
} else { |
||||
DEBUGLOG(stderr, "Error writing minidump - errno=%s\n", strerror(errno)); |
||||
} |
||||
|
||||
// let the task continue |
||||
task_resume(remote_task_); |
||||
DEBUGLOG(stderr, "Resumed remote task\n"); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
//============================================================================= |
||||
// The crashed task needs to be told that the inspection has finished. |
||||
// It will wait on a mach port (with timeout) until we send acknowledgement. |
||||
kern_return_t Inspector::SendAcknowledgement() { |
||||
if (ack_port_ != MACH_PORT_DEAD) { |
||||
MachPortSender sender(ack_port_); |
||||
MachSendMessage ack_message(kMsgType_InspectorAcknowledgement); |
||||
|
||||
DEBUGLOG(stderr, "Inspector: trying to send acknowledgement to port %d\n", |
||||
ack_port_); |
||||
|
||||
kern_return_t result = sender.SendMessage(ack_message, 2000); |
||||
|
||||
#if VERBOSE |
||||
PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement"); |
||||
#endif |
||||
|
||||
return result; |
||||
} |
||||
|
||||
DEBUGLOG(stderr, "Inspector: port translation failure!\n"); |
||||
return KERN_INVALID_NAME; |
||||
} |
||||
|
||||
//============================================================================= |
||||
void Inspector::LaunchReporter(const char *inConfigFilePath) { |
||||
// Extract the path to the reporter executable. |
||||
const char *reporterExecutablePath = |
||||
config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION); |
||||
DEBUGLOG(stderr, "reporter path = %s\n", reporterExecutablePath); |
||||
|
||||
// Setup and launch the crash dump sender. |
||||
const char *argv[3]; |
||||
argv[0] = reporterExecutablePath; |
||||
argv[1] = inConfigFilePath; |
||||
argv[2] = NULL; |
||||
|
||||
// Launch the reporter |
||||
pid_t pid = fork(); |
||||
|
||||
// If we're in the child, load in our new executable and run. |
||||
// The parent will not wait for the child to complete. |
||||
if (pid == 0) { |
||||
execv(argv[0], (char * const *)argv); |
||||
config_file_.Unlink(); // launch failed - get rid of config file |
||||
DEBUGLOG(stderr, "Inspector: unable to launch reporter app\n"); |
||||
_exit(1); |
||||
} |
||||
|
||||
// Wait until the Reporter child process exits. |
||||
// |
||||
|
||||
// We'll use a timeout of one minute. |
||||
int timeoutCount = 60; // 60 seconds |
||||
|
||||
while (timeoutCount-- > 0) { |
||||
int status; |
||||
pid_t result = waitpid(pid, &status, WNOHANG); |
||||
|
||||
if (result == 0) { |
||||
// The child has not yet finished. |
||||
sleep(1); |
||||
} else if (result == -1) { |
||||
DEBUGLOG(stderr, "Inspector: waitpid error (%d) waiting for reporter app\n", |
||||
errno); |
||||
break; |
||||
} else { |
||||
// child has finished |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
} // namespace google_breakpad |
||||
|
@ -1,65 +0,0 @@ |
||||
// Copyright (c) 2007, Google Inc. |
||||
// All rights reserved. |
||||
// |
||||
// Redistribution and use in source and binary forms, with or without |
||||
// modification, are permitted provided that the following conditions are |
||||
// met: |
||||
// |
||||
// * Redistributions of source code must retain the above copyright |
||||
// notice, this list of conditions and the following disclaimer. |
||||
// * Redistributions in binary form must reproduce the above |
||||
// copyright notice, this list of conditions and the following disclaimer |
||||
// in the documentation and/or other materials provided with the |
||||
// distribution. |
||||
// * Neither the name of Google Inc. nor the names of its |
||||
// contributors may be used to endorse or promote products derived from |
||||
// this software without specific prior written permission. |
||||
// |
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
// |
||||
// Main driver for Inspector |
||||
|
||||
#import "client/mac/crash_generation/Inspector.h" |
||||
#import <Cocoa/Cocoa.h> |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
//============================================================================= |
||||
extern "C" { |
||||
|
||||
int main(int argc, char *const argv[]) { |
||||
#if DEBUG |
||||
// Since we're launched on-demand, this is necessary to see debugging |
||||
// output in the console window. |
||||
freopen("/dev/console", "w", stdout); |
||||
freopen("/dev/console", "w", stderr); |
||||
#endif |
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
||||
|
||||
if (argc != 2) { |
||||
exit(0); |
||||
} |
||||
// Our first command-line argument contains the name of the service |
||||
// that we're providing. |
||||
google_breakpad::Inspector inspector; |
||||
inspector.Inspect(argv[1]); |
||||
|
||||
[pool release]; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
} // extern "C" |
||||
|
||||
} // namespace google_breakpad |
@ -1,47 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_MAC_CRASH_GENERATION_CLIENT_INFO_H_ |
||||
#define CLIENT_MAC_CRASH_GENERATION_CLIENT_INFO_H_ |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
class ClientInfo { |
||||
public: |
||||
explicit ClientInfo(pid_t pid) : pid_(pid) {} |
||||
|
||||
pid_t pid() const { return pid_; } |
||||
|
||||
private: |
||||
pid_t pid_; |
||||
}; |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_MAC_CRASH_GENERATION_CLIENT_INFO_H_
|
@ -1,72 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "client/mac/crash_generation/crash_generation_client.h" |
||||
|
||||
#include "client/mac/crash_generation/crash_generation_server.h" |
||||
#include "common/mac/MachIPC.h" |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
bool CrashGenerationClient::RequestDumpForException( |
||||
int exception_type, |
||||
int exception_code, |
||||
int exception_subcode, |
||||
mach_port_t crashing_thread) { |
||||
// The server will send a message to this port indicating that it
|
||||
// has finished its work.
|
||||
ReceivePort acknowledge_port; |
||||
|
||||
MachSendMessage message(kDumpRequestMessage); |
||||
message.AddDescriptor(mach_task_self()); // this task
|
||||
message.AddDescriptor(crashing_thread); // crashing thread
|
||||
message.AddDescriptor(mach_thread_self()); // handler thread
|
||||
message.AddDescriptor(acknowledge_port.GetPort()); // message receive port
|
||||
|
||||
ExceptionInfo info; |
||||
info.exception_type = exception_type; |
||||
info.exception_code = exception_code; |
||||
info.exception_subcode = exception_subcode; |
||||
message.SetData(&info, sizeof(info)); |
||||
|
||||
const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000; |
||||
kern_return_t result = sender_.SendMessage(message, kSendTimeoutMs); |
||||
if (result != KERN_SUCCESS) |
||||
return false; |
||||
|
||||
// Give the server slightly longer to reply since it has to
|
||||
// inspect this task and write the minidump.
|
||||
const mach_msg_timeout_t kReceiveTimeoutMs = 5 * 1000; |
||||
MachReceiveMessage acknowledge_message; |
||||
result = acknowledge_port.WaitForMessage(&acknowledge_message, |
||||
kReceiveTimeoutMs); |
||||
return result == KERN_SUCCESS; |
||||
} |
||||
|
||||
} // namespace google_breakpad
|
@ -1,65 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ |
||||
#define GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ |
||||
|
||||
#include "common/mac/MachIPC.h" |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
class CrashGenerationClient { |
||||
public: |
||||
explicit CrashGenerationClient(const char* mach_port_name) |
||||
: sender_(mach_port_name) { |
||||
} |
||||
|
||||
// Request the crash server to generate a dump.
|
||||
//
|
||||
// Return true if the dump was successful; false otherwise.
|
||||
bool RequestDumpForException(int exception_type, |
||||
int exception_code, |
||||
int exception_subcode, |
||||
mach_port_t crashing_thread); |
||||
|
||||
bool RequestDump() { |
||||
return RequestDumpForException(0, 0, 0, MACH_PORT_NULL); |
||||
} |
||||
|
||||
private: |
||||
MachPortSender sender_; |
||||
|
||||
// Prevent copy construction and assignment.
|
||||
CrashGenerationClient(const CrashGenerationClient&); |
||||
CrashGenerationClient& operator=(const CrashGenerationClient&); |
||||
}; |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
|
@ -1,160 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "client/mac/crash_generation/crash_generation_server.h" |
||||
|
||||
#include "client/mac/crash_generation/client_info.h" |
||||
#include "client/mac/handler/minidump_generator.h" |
||||
#include "common/mac/scoped_task_suspend-inl.h" |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
CrashGenerationServer::CrashGenerationServer( |
||||
const char *mach_port_name, |
||||
OnClientDumpRequestCallback dump_callback, |
||||
void *dump_context, |
||||
OnClientExitingCallback exit_callback, |
||||
void *exit_context, |
||||
bool generate_dumps, |
||||
const std::string &dump_path) |
||||
: dump_callback_(dump_callback), |
||||
dump_context_(dump_context), |
||||
exit_callback_(exit_callback), |
||||
exit_context_(exit_context), |
||||
generate_dumps_(generate_dumps), |
||||
dump_dir_(dump_path.empty() ? "/tmp" : dump_path), |
||||
started_(false), |
||||
receive_port_(mach_port_name), |
||||
mach_port_name_(mach_port_name) { |
||||
} |
||||
|
||||
CrashGenerationServer::~CrashGenerationServer() { |
||||
if (started_) |
||||
Stop(); |
||||
} |
||||
|
||||
bool CrashGenerationServer::Start() { |
||||
int thread_create_result = pthread_create(&server_thread_, NULL, |
||||
&WaitForMessages, this); |
||||
started_ = thread_create_result == 0; |
||||
return started_; |
||||
} |
||||
|
||||
bool CrashGenerationServer::Stop() { |
||||
if (!started_) |
||||
return false; |
||||
|
||||
// Send a quit message to the background thread, and then join it.
|
||||
MachPortSender sender(mach_port_name_.c_str()); |
||||
MachSendMessage quit_message(kQuitMessage); |
||||
const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000; |
||||
kern_return_t result = sender.SendMessage(quit_message, kSendTimeoutMs); |
||||
if (result == KERN_SUCCESS) { |
||||
int thread_join_result = pthread_join(server_thread_, NULL); |
||||
started_ = thread_join_result != 0; |
||||
} |
||||
|
||||
return !started_; |
||||
} |
||||
|
||||
// static
|
||||
void *CrashGenerationServer::WaitForMessages(void *server) { |
||||
CrashGenerationServer *self = |
||||
reinterpret_cast<CrashGenerationServer*>(server); |
||||
while (self->WaitForOneMessage()) {} |
||||
return NULL; |
||||
} |
||||
|
||||
bool CrashGenerationServer::WaitForOneMessage() { |
||||
MachReceiveMessage message; |
||||
kern_return_t result = receive_port_.WaitForMessage(&message, |
||||
MACH_MSG_TIMEOUT_NONE); |
||||
if (result == KERN_SUCCESS) { |
||||
switch (message.GetMessageID()) { |
||||
case kDumpRequestMessage: { |
||||
ExceptionInfo &info = (ExceptionInfo &)*message.GetData(); |
||||
|
||||
mach_port_t remote_task = message.GetTranslatedPort(0); |
||||
mach_port_t crashing_thread = message.GetTranslatedPort(1); |
||||
mach_port_t handler_thread = message.GetTranslatedPort(2); |
||||
mach_port_t ack_port = message.GetTranslatedPort(3); |
||||
pid_t remote_pid = -1; |
||||
pid_for_task(remote_task, &remote_pid); |
||||
ClientInfo client(remote_pid); |
||||
|
||||
bool result; |
||||
std::string dump_path; |
||||
if (generate_dumps_) { |
||||
ScopedTaskSuspend suspend(remote_task); |
||||
|
||||
MinidumpGenerator generator(remote_task, handler_thread); |
||||
dump_path = generator.UniqueNameInDirectory(dump_dir_, NULL); |
||||
|
||||
if (info.exception_type && info.exception_code) { |
||||
generator.SetExceptionInformation(info.exception_type, |
||||
info.exception_code, |
||||
info.exception_subcode, |
||||
crashing_thread); |
||||
} |
||||
result = generator.Write(dump_path.c_str()); |
||||
} else { |
||||
result = true; |
||||
} |
||||
|
||||
if (result && dump_callback_) { |
||||
dump_callback_(dump_context_, client, dump_path); |
||||
} |
||||
|
||||
// TODO(ted): support a way for the client to send additional data,
|
||||
// perhaps with a callback so users of the server can read the data
|
||||
// themselves?
|
||||
|
||||
if (ack_port != MACH_PORT_DEAD && ack_port != MACH_PORT_NULL) { |
||||
MachPortSender sender(ack_port); |
||||
MachSendMessage ack_message(kAcknowledgementMessage); |
||||
const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000; |
||||
|
||||
sender.SendMessage(ack_message, kSendTimeoutMs); |
||||
} |
||||
|
||||
if (exit_callback_) { |
||||
exit_callback_(exit_context_, client); |
||||
} |
||||
break; |
||||
} |
||||
case kQuitMessage: |
||||
return false; |
||||
} |
||||
} else { // result != KERN_SUCCESS
|
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
} // namespace google_breakpad
|
@ -1,141 +0,0 @@ |
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_ |
||||
#define GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_ |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#include <string> |
||||
|
||||
#include "common/mac/MachIPC.h" |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
class ClientInfo; |
||||
|
||||
// Messages the server can read via its mach port
|
||||
enum { |
||||
kDumpRequestMessage = 1, |
||||
kAcknowledgementMessage = 2, |
||||
kQuitMessage = 3 |
||||
}; |
||||
|
||||
// Exception details sent by the client when requesting a dump.
|
||||
struct ExceptionInfo { |
||||
int32_t exception_type; |
||||
int32_t exception_code; |
||||
int32_t exception_subcode; |
||||
}; |
||||
|
||||
class CrashGenerationServer { |
||||
public: |
||||
// WARNING: callbacks may be invoked on a different thread
|
||||
// than that which creates the CrashGenerationServer. They must
|
||||
// be thread safe.
|
||||
typedef void (*OnClientDumpRequestCallback)(void *context, |
||||
const ClientInfo &client_info, |
||||
const std::string &file_path); |
||||
|
||||
typedef void (*OnClientExitingCallback)(void *context, |
||||
const ClientInfo &client_info); |
||||
|
||||
// Create an instance with the given parameters.
|
||||
//
|
||||
// mach_port_name: Named server port to listen on.
|
||||
// dump_callback: Callback for a client crash dump request.
|
||||
// dump_context: Context for client crash dump request callback.
|
||||
// exit_callback: Callback for client process exit.
|
||||
// exit_context: Context for client exit callback.
|
||||
// generate_dumps: Whether to automatically generate dumps.
|
||||
// Client code of this class might want to generate dumps explicitly
|
||||
// in the crash dump request callback. In that case, false can be
|
||||
// passed for this parameter.
|
||||
// dump_path: Path for generating dumps; required only if true is
|
||||
// passed for generateDumps parameter; NULL can be passed otherwise.
|
||||
CrashGenerationServer(const char *mach_port_name, |
||||
OnClientDumpRequestCallback dump_callback, |
||||
void *dump_context, |
||||
OnClientExitingCallback exit_callback, |
||||
void *exit_context, |
||||
bool generate_dumps, |
||||
const std::string &dump_path); |
||||
|
||||
~CrashGenerationServer(); |
||||
|
||||
// Perform initialization steps needed to start listening to clients.
|
||||
//
|
||||
// Return true if initialization is successful; false otherwise.
|
||||
bool Start(); |
||||
|
||||
// Stop the server.
|
||||
bool Stop(); |
||||
|
||||
private: |
||||
// Return a unique filename at which a minidump can be written.
|
||||
bool MakeMinidumpFilename(std::string &outFilename); |
||||
|
||||
// Loop reading client messages and responding to them until
|
||||
// a quit message is received.
|
||||
static void *WaitForMessages(void *server); |
||||
|
||||
// Wait for a single client message and respond to it. Returns false
|
||||
// if a quit message was received or if an error occurred.
|
||||
bool WaitForOneMessage(); |
||||
|
||||
OnClientDumpRequestCallback dump_callback_; |
||||
void *dump_context_; |
||||
|
||||
OnClientExitingCallback exit_callback_; |
||||
void *exit_context_; |
||||
|
||||
bool generate_dumps_; |
||||
|
||||
std::string dump_dir_; |
||||
|
||||
bool started_; |
||||
|
||||
// The mach port that receives requests to dump from child processes.
|
||||
ReceivePort receive_port_; |
||||
|
||||
// The name of the mach port. Stored so the Stop method can message
|
||||
// the background thread to shut it down.
|
||||
std::string mach_port_name_; |
||||
|
||||
// The thread that waits on the receive port.
|
||||
pthread_t server_thread_; |
||||
|
||||
// Disable copy constructor and operator=.
|
||||
CrashGenerationServer(const CrashGenerationServer&); |
||||
CrashGenerationServer& operator=(const CrashGenerationServer&); |
||||
}; |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,258 +0,0 @@ |
||||
#ifndef _exc_user_ |
||||
#define _exc_user_ |
||||
|
||||
/* Module exc */ |
||||
|
||||
#include <string.h> |
||||
#include <mach/ndr.h> |
||||
#include <mach/boolean.h> |
||||
#include <mach/kern_return.h> |
||||
#include <mach/notify.h> |
||||
#include <mach/mach_types.h> |
||||
#include <mach/message.h> |
||||
#include <mach/mig_errors.h> |
||||
#include <mach/port.h> |
||||
|
||||
#ifdef AUTOTEST |
||||
#ifndef FUNCTION_PTR_T |
||||
#define FUNCTION_PTR_T |
||||
typedef void (*function_ptr_t)(mach_port_t, char *, mach_msg_type_number_t); |
||||
typedef struct { |
||||
char *name; |
||||
function_ptr_t function; |
||||
} function_table_entry; |
||||
typedef function_table_entry *function_table_t; |
||||
#endif /* FUNCTION_PTR_T */ |
||||
#endif /* AUTOTEST */ |
||||
|
||||
#ifndef exc_MSG_COUNT |
||||
#define exc_MSG_COUNT 3 |
||||
#endif /* exc_MSG_COUNT */ |
||||
|
||||
#include <mach/std_types.h> |
||||
#include <mach/mig.h> |
||||
#include <mach/mig.h> |
||||
#include <mach/mach_types.h> |
||||
|
||||
#ifdef __BeforeMigUserHeader |
||||
__BeforeMigUserHeader |
||||
#endif /* __BeforeMigUserHeader */ |
||||
|
||||
#include <sys/cdefs.h> |
||||
__BEGIN_DECLS |
||||
|
||||
|
||||
/* Routine exception_raise */ |
||||
#ifdef mig_external |
||||
mig_external |
||||
#else |
||||
extern |
||||
#endif /* mig_external */ |
||||
kern_return_t exception_raise |
||||
( |
||||
mach_port_t exception_port, |
||||
mach_port_t thread, |
||||
mach_port_t task, |
||||
exception_type_t exception, |
||||
exception_data_t code, |
||||
mach_msg_type_number_t codeCnt |
||||
); |
||||
|
||||
/* Routine exception_raise_state */ |
||||
#ifdef mig_external |
||||
mig_external |
||||
#else |
||||
extern |
||||
#endif /* mig_external */ |
||||
kern_return_t exception_raise_state |
||||
( |
||||
mach_port_t exception_port, |
||||
exception_type_t exception, |
||||
const exception_data_t code, |
||||
mach_msg_type_number_t codeCnt, |
||||
int *flavor, |
||||
const thread_state_t old_state, |
||||
mach_msg_type_number_t old_stateCnt, |
||||
thread_state_t new_state, |
||||
mach_msg_type_number_t *new_stateCnt |
||||
); |
||||
|
||||
/* Routine exception_raise_state_identity */ |
||||
#ifdef mig_external |
||||
mig_external |
||||
#else |
||||
extern |
||||
#endif /* mig_external */ |
||||
kern_return_t exception_raise_state_identity |
||||
( |
||||
mach_port_t exception_port, |
||||
mach_port_t thread, |
||||
mach_port_t task, |
||||
exception_type_t exception, |
||||
exception_data_t code, |
||||
mach_msg_type_number_t codeCnt, |
||||
int *flavor, |
||||
thread_state_t old_state, |
||||
mach_msg_type_number_t old_stateCnt, |
||||
thread_state_t new_state, |
||||
mach_msg_type_number_t *new_stateCnt |
||||
); |
||||
|
||||
__END_DECLS |
||||
|
||||
/********************** Caution **************************/ |
||||
/* The following data types should be used to calculate */ |
||||
/* maximum message sizes only. The actual message may be */ |
||||
/* smaller, and the position of the arguments within the */ |
||||
/* message layout may vary from what is presented here. */ |
||||
/* For example, if any of the arguments are variable- */ |
||||
/* sized, and less than the maximum is sent, the data */ |
||||
/* will be packed tight in the actual message to reduce */ |
||||
/* the presence of holes. */ |
||||
/********************** Caution **************************/ |
||||
|
||||
/* typedefs for all requests */ |
||||
|
||||
#ifndef __Request__exc_subsystem__defined |
||||
#define __Request__exc_subsystem__defined |
||||
|
||||
#ifdef __MigPackStructs |
||||
#pragma pack(4) |
||||
#endif |
||||
typedef struct { |
||||
mach_msg_header_t Head; |
||||
/* start of the kernel processed data */ |
||||
mach_msg_body_t msgh_body; |
||||
mach_msg_port_descriptor_t thread; |
||||
mach_msg_port_descriptor_t task; |
||||
/* end of the kernel processed data */ |
||||
NDR_record_t NDR; |
||||
exception_type_t exception; |
||||
mach_msg_type_number_t codeCnt; |
||||
integer_t code[2]; |
||||
} __Request__exception_raise_t; |
||||
#ifdef __MigPackStructs |
||||
#pragma pack() |
||||
#endif |
||||
|
||||
#ifdef __MigPackStructs |
||||
#pragma pack(4) |
||||
#endif |
||||
typedef struct { |
||||
mach_msg_header_t Head; |
||||
NDR_record_t NDR; |
||||
exception_type_t exception; |
||||
mach_msg_type_number_t codeCnt; |
||||
integer_t code[2]; |
||||
int flavor; |
||||
mach_msg_type_number_t old_stateCnt; |
||||
natural_t old_state[144]; |
||||
} __Request__exception_raise_state_t; |
||||
#ifdef __MigPackStructs |
||||
#pragma pack() |
||||
#endif |
||||
|
||||
#ifdef __MigPackStructs |
||||
#pragma pack(4) |
||||
#endif |
||||
typedef struct { |
||||
mach_msg_header_t Head; |
||||
/* start of the kernel processed data */ |
||||
mach_msg_body_t msgh_body; |
||||
mach_msg_port_descriptor_t thread; |
||||
mach_msg_port_descriptor_t task; |
||||
/* end of the kernel processed data */ |
||||
NDR_record_t NDR; |
||||
exception_type_t exception; |
||||
mach_msg_type_number_t codeCnt; |
||||
integer_t code[2]; |
||||
int flavor; |
||||
mach_msg_type_number_t old_stateCnt; |
||||
natural_t old_state[144]; |
||||
} __Request__exception_raise_state_identity_t; |
||||
#ifdef __MigPackStructs |
||||
#pragma pack() |
||||
#endif |
||||
#endif /* !__Request__exc_subsystem__defined */ |
||||
|
||||
/* union of all requests */ |
||||
|
||||
#ifndef __RequestUnion__exc_subsystem__defined |
||||
#define __RequestUnion__exc_subsystem__defined |
||||
union __RequestUnion__exc_subsystem { |
||||
__Request__exception_raise_t Request_exception_raise; |
||||
__Request__exception_raise_state_t Request_exception_raise_state; |
||||
__Request__exception_raise_state_identity_t Request_exception_raise_state_identity; |
||||
}; |
||||
#endif /* !__RequestUnion__exc_subsystem__defined */ |
||||
/* typedefs for all replies */ |
||||
|
||||
#ifndef __Reply__exc_subsystem__defined |
||||
#define __Reply__exc_subsystem__defined |
||||
|
||||
#ifdef __MigPackStructs |
||||
#pragma pack(4) |
||||
#endif |
||||
typedef struct { |
||||
mach_msg_header_t Head; |
||||
NDR_record_t NDR; |
||||
kern_return_t RetCode; |
||||
} __Reply__exception_raise_t; |
||||
#ifdef __MigPackStructs |
||||
#pragma pack() |
||||
#endif |
||||
|
||||
#ifdef __MigPackStructs |
||||
#pragma pack(4) |
||||
#endif |
||||
typedef struct { |
||||
mach_msg_header_t Head; |
||||
NDR_record_t NDR; |
||||
kern_return_t RetCode; |
||||
int flavor; |
||||
mach_msg_type_number_t new_stateCnt; |
||||
natural_t new_state[144]; |
||||
} __Reply__exception_raise_state_t; |
||||
#ifdef __MigPackStructs |
||||
#pragma pack() |
||||
#endif |
||||
|
||||
#ifdef __MigPackStructs |
||||
#pragma pack(4) |
||||
#endif |
||||
typedef struct { |
||||
mach_msg_header_t Head; |
||||
NDR_record_t NDR; |
||||
kern_return_t RetCode; |
||||
int flavor; |
||||
mach_msg_type_number_t new_stateCnt; |
||||
natural_t new_state[144]; |
||||
} __Reply__exception_raise_state_identity_t; |
||||
#ifdef __MigPackStructs |
||||
#pragma pack() |
||||
#endif |
||||
#endif /* !__Reply__exc_subsystem__defined */ |
||||
|
||||
/* union of all replies */ |
||||
|
||||
#ifndef __ReplyUnion__exc_subsystem__defined |
||||
#define __ReplyUnion__exc_subsystem__defined |
||||
union __ReplyUnion__exc_subsystem { |
||||
__Reply__exception_raise_t Reply_exception_raise; |
||||
__Reply__exception_raise_state_t Reply_exception_raise_state; |
||||
__Reply__exception_raise_state_identity_t Reply_exception_raise_state_identity; |
||||
}; |
||||
#endif /* !__RequestUnion__exc_subsystem__defined */ |
||||
|
||||
#ifndef subsystem_to_name_map_exc |
||||
#define subsystem_to_name_map_exc \ |
||||
{ "exception_raise", 2401 },\
|
||||
{ "exception_raise_state", 2402 },\
|
||||
{ "exception_raise_state_identity", 2403 } |
||||
#endif |
||||
|
||||
#ifdef __AfterMigUserHeader |
||||
__AfterMigUserHeader |
||||
#endif /* __AfterMigUserHeader */ |
||||
|
||||
#endif /* _exc_user_ */ |
@ -1,412 +0,0 @@ |
||||
/*
|
||||
* Copyright (c) 1999 Apple Computer, Inc. All rights reserved. |
||||
* |
||||
* @APPLE_LICENSE_HEADER_START@ |
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code |
||||
* as defined in and that are subject to the Apple Public Source License |
||||
* Version 2.0 (the 'License'). You may not use this file except in |
||||
* compliance with the License. Please obtain a copy of the License at |
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file. |
||||
*
|
||||
* The Original Code and all software distributed under the License are |
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER |
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, |
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
||||
* Please see the License for the specific language governing rights and |
||||
* limitations under the License. |
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@ |
||||
*/ |
||||
/*
|
||||
* Copyright (c) 1989, 1993 |
||||
* The Regents of the University of California. All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions |
||||
* are met: |
||||
* 1. Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* 2. Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* 3. All advertising materials mentioning features or use of this software |
||||
* must display the following acknowledgement: |
||||
* This product includes software developed by the University of |
||||
* California, Berkeley and its contributors. |
||||
* 4. Neither the name of the University nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
||||
* SUCH DAMAGE. |
||||
*/ |
||||
|
||||
|
||||
/*
|
||||
* This file was copied from libc/gen/nlist.c from Darwin's source code
|
||||
* The version of nlist used as a base is from 10.5.2, libc-498
|
||||
* http://www.opensource.apple.com/darwinsource/10.5.2/Libc-498/gen/nlist.c
|
||||
*
|
||||
* The full tarball is at:
|
||||
* http://www.opensource.apple.com/darwinsource/tarballs/apsl/Libc-498.tar.gz
|
||||
*
|
||||
* I've modified it to be compatible with 64-bit images. |
||||
*/ |
||||
|
||||
#include "breakpad_nlist_64.h" |
||||
|
||||
#include <fcntl.h> |
||||
#include <mach-o/nlist.h> |
||||
#include <mach-o/loader.h> |
||||
#include <mach-o/fat.h> |
||||
#include <mach/mach.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <sys/types.h> |
||||
#include <sys/uio.h> |
||||
#include <TargetConditionals.h> |
||||
#include <unistd.h> |
||||
|
||||
/* Stuff lifted from <a.out.h> and <sys/exec.h> since they are gone */ |
||||
/*
|
||||
* Header prepended to each a.out file. |
||||
*/ |
||||
struct exec { |
||||
unsigned short a_machtype; /* machine type */ |
||||
unsigned short a_magic; /* magic number */ |
||||
unsigned long a_text; /* size of text segment */ |
||||
unsigned long a_data; /* size of initialized data */ |
||||
unsigned long a_bss; /* size of uninitialized data */ |
||||
unsigned long a_syms; /* size of symbol table */ |
||||
unsigned long a_entry; /* entry point */ |
||||
unsigned long a_trsize; /* size of text relocation */ |
||||
unsigned long a_drsize; /* size of data relocation */ |
||||
}; |
||||
|
||||
#define OMAGIC 0407 /* old impure format */ |
||||
#define NMAGIC 0410 /* read-only text */ |
||||
#define ZMAGIC 0413 /* demand load format */ |
||||
|
||||
#define N_BADMAG(x) \ |
||||
(((x).a_magic)!=OMAGIC && ((x).a_magic)!=NMAGIC && ((x).a_magic)!=ZMAGIC) |
||||
#define N_TXTOFF(x) \ |
||||
((x).a_magic==ZMAGIC ? 0 : sizeof (struct exec)) |
||||
#define N_SYMOFF(x) \ |
||||
(N_TXTOFF(x) + (x).a_text+(x).a_data + (x).a_trsize+(x).a_drsize) |
||||
|
||||
// Traits structs for specializing function templates to handle
|
||||
// 32-bit/64-bit Mach-O files.
|
||||
template<typename T> |
||||
struct MachBits {}; |
||||
|
||||
typedef struct nlist nlist32; |
||||
typedef struct nlist_64 nlist64; |
||||
|
||||
template<> |
||||
struct MachBits<nlist32> { |
||||
typedef mach_header mach_header_type; |
||||
typedef uint32_t word_type; |
||||
static const uint32_t magic = MH_MAGIC; |
||||
}; |
||||
|
||||
template<> |
||||
struct MachBits<nlist64> { |
||||
typedef mach_header_64 mach_header_type; |
||||
typedef uint64_t word_type; |
||||
static const uint32_t magic = MH_MAGIC_64; |
||||
}; |
||||
|
||||
template<typename nlist_type> |
||||
int |
||||
__breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames, |
||||
cpu_type_t cpu_type); |
||||
|
||||
/*
|
||||
* nlist - retreive attributes from name list (string table version) |
||||
*/ |
||||
|
||||
template <typename nlist_type> |
||||
int breakpad_nlist_common(const char *name, |
||||
nlist_type *list, |
||||
const char **symbolNames, |
||||
cpu_type_t cpu_type) { |
||||
int fd = open(name, O_RDONLY, 0); |
||||
if (fd < 0) |
||||
return -1; |
||||
int n = __breakpad_fdnlist(fd, list, symbolNames, cpu_type); |
||||
close(fd); |
||||
return n; |
||||
} |
||||
|
||||
int breakpad_nlist(const char *name, |
||||
struct nlist *list, |
||||
const char **symbolNames, |
||||
cpu_type_t cpu_type) { |
||||
return breakpad_nlist_common(name, list, symbolNames, cpu_type); |
||||
} |
||||
|
||||
int breakpad_nlist(const char *name, |
||||
struct nlist_64 *list, |
||||
const char **symbolNames, |
||||
cpu_type_t cpu_type) { |
||||
return breakpad_nlist_common(name, list, symbolNames, cpu_type); |
||||
} |
||||
|
||||
/* Note: __fdnlist() is called from kvm_nlist in libkvm's kvm.c */ |
||||
|
||||
template<typename nlist_type> |
||||
int __breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames, |
||||
cpu_type_t cpu_type) { |
||||
typedef typename MachBits<nlist_type>::mach_header_type mach_header_type; |
||||
typedef typename MachBits<nlist_type>::word_type word_type; |
||||
|
||||
const uint32_t magic = MachBits<nlist_type>::magic; |
||||
|
||||
int maxlen = 500; |
||||
int nreq = 0; |
||||
for (nlist_type* q = list; |
||||
symbolNames[q-list] && symbolNames[q-list][0]; |
||||
q++, nreq++) { |
||||
|
||||
q->n_type = 0; |
||||
q->n_value = 0; |
||||
q->n_desc = 0; |
||||
q->n_sect = 0; |
||||
q->n_un.n_strx = 0; |
||||
} |
||||
|
||||
struct exec buf; |
||||
if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf) || |
||||
(N_BADMAG(buf) && *((long *)&buf) != magic && |
||||
NXSwapBigLongToHost(*((long *)&buf)) != FAT_MAGIC) && |
||||
/* The following is the big-endian ppc64 check */ |
||||
(*((long*)&buf)) != FAT_MAGIC) { |
||||
return -1; |
||||
} |
||||
|
||||
/* Deal with fat file if necessary */ |
||||
unsigned arch_offset = 0; |
||||
if (NXSwapBigLongToHost(*((long *)&buf)) == FAT_MAGIC || |
||||
/* The following is the big-endian ppc64 check */ |
||||
*((unsigned int *)&buf) == FAT_MAGIC) { |
||||
/* Get host info */ |
||||
host_t host = mach_host_self(); |
||||
unsigned i = HOST_BASIC_INFO_COUNT; |
||||
struct host_basic_info hbi; |
||||
kern_return_t kr; |
||||
if ((kr = host_info(host, HOST_BASIC_INFO, |
||||
(host_info_t)(&hbi), &i)) != KERN_SUCCESS) { |
||||
return -1; |
||||
} |
||||
mach_port_deallocate(mach_task_self(), host); |
||||
|
||||
/* Read in the fat header */ |
||||
struct fat_header fh; |
||||
if (lseek(fd, 0, SEEK_SET) == -1) { |
||||
return -1; |
||||
} |
||||
if (read(fd, (char *)&fh, sizeof(fh)) != sizeof(fh)) { |
||||
return -1; |
||||
} |
||||
|
||||
/* Convert fat_narchs to host byte order */ |
||||
fh.nfat_arch = NXSwapBigIntToHost(fh.nfat_arch); |
||||
|
||||
/* Read in the fat archs */ |
||||
struct fat_arch *fat_archs = |
||||
(struct fat_arch *)malloc(fh.nfat_arch * sizeof(struct fat_arch)); |
||||
if (fat_archs == NULL) { |
||||
return -1; |
||||
} |
||||
if (read(fd, (char *)fat_archs, |
||||
sizeof(struct fat_arch) * fh.nfat_arch) != |
||||
(ssize_t)sizeof(struct fat_arch) * fh.nfat_arch) { |
||||
free(fat_archs); |
||||
return -1; |
||||
} |
||||
|
||||
/*
|
||||
* Convert archs to host byte ordering (a constraint of |
||||
* cpusubtype_getbestarch() |
||||
*/ |
||||
for (unsigned i = 0; i < fh.nfat_arch; i++) { |
||||
fat_archs[i].cputype = |
||||
NXSwapBigIntToHost(fat_archs[i].cputype); |
||||
fat_archs[i].cpusubtype = |
||||
NXSwapBigIntToHost(fat_archs[i].cpusubtype); |
||||
fat_archs[i].offset = |
||||
NXSwapBigIntToHost(fat_archs[i].offset); |
||||
fat_archs[i].size = |
||||
NXSwapBigIntToHost(fat_archs[i].size); |
||||
fat_archs[i].align = |
||||
NXSwapBigIntToHost(fat_archs[i].align); |
||||
} |
||||
|
||||
struct fat_arch *fap = NULL; |
||||
for (unsigned i = 0; i < fh.nfat_arch; i++) { |
||||
if (fat_archs[i].cputype == cpu_type) { |
||||
fap = &fat_archs[i]; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (!fap) { |
||||
free(fat_archs); |
||||
return -1; |
||||
} |
||||
arch_offset = fap->offset; |
||||
free(fat_archs); |
||||
|
||||
/* Read in the beginning of the architecture-specific file */ |
||||
if (lseek(fd, arch_offset, SEEK_SET) == -1) { |
||||
return -1; |
||||
} |
||||
if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf)) { |
||||
return -1; |
||||
} |
||||
} |
||||
|
||||
off_t sa; /* symbol address */ |
||||
off_t ss; /* start of strings */ |
||||
register register_t n; |
||||
if (*((unsigned int *)&buf) == magic) { |
||||
if (lseek(fd, arch_offset, SEEK_SET) == -1) { |
||||
return -1; |
||||
} |
||||
mach_header_type mh; |
||||
if (read(fd, (char *)&mh, sizeof(mh)) != sizeof(mh)) { |
||||
return -1; |
||||
} |
||||
|
||||
struct load_command *load_commands = |
||||
(struct load_command *)malloc(mh.sizeofcmds); |
||||
if (load_commands == NULL) { |
||||
return -1; |
||||
} |
||||
if (read(fd, (char *)load_commands, mh.sizeofcmds) != |
||||
mh.sizeofcmds) { |
||||
free(load_commands); |
||||
return -1; |
||||
} |
||||
struct symtab_command *stp = NULL; |
||||
struct load_command *lcp = load_commands; |
||||
// iterate through all load commands, looking for
|
||||
// LC_SYMTAB load command
|
||||
for (long i = 0; i < mh.ncmds; i++) { |
||||
if (lcp->cmdsize % sizeof(word_type) != 0 || |
||||
lcp->cmdsize <= 0 || |
||||
(char *)lcp + lcp->cmdsize > |
||||
(char *)load_commands + mh.sizeofcmds) { |
||||
free(load_commands); |
||||
return -1; |
||||
} |
||||
if (lcp->cmd == LC_SYMTAB) { |
||||
if (lcp->cmdsize != |
||||
sizeof(struct symtab_command)) { |
||||
free(load_commands); |
||||
return -1; |
||||
} |
||||
stp = (struct symtab_command *)lcp; |
||||
break; |
||||
} |
||||
lcp = (struct load_command *) |
||||
((char *)lcp + lcp->cmdsize); |
||||
} |
||||
if (stp == NULL) { |
||||
free(load_commands); |
||||
return -1; |
||||
} |
||||
// sa points to the beginning of the symbol table
|
||||
sa = stp->symoff + arch_offset; |
||||
// ss points to the beginning of the string table
|
||||
ss = stp->stroff + arch_offset; |
||||
// n is the number of bytes in the symbol table
|
||||
// each symbol table entry is an nlist structure
|
||||
n = stp->nsyms * sizeof(nlist_type); |
||||
free(load_commands); |
||||
} else { |
||||
sa = N_SYMOFF(buf) + arch_offset; |
||||
ss = sa + buf.a_syms + arch_offset; |
||||
n = buf.a_syms; |
||||
} |
||||
|
||||
if (lseek(fd, sa, SEEK_SET) == -1) { |
||||
return -1; |
||||
} |
||||
|
||||
// the algorithm here is to read the nlist entries in m-sized
|
||||
// chunks into q. q is then iterated over. for each entry in q,
|
||||
// use the string table index(q->n_un.n_strx) to read the symbol
|
||||
// name, then scan the nlist entries passed in by the user(via p),
|
||||
// and look for a match
|
||||
while (n) { |
||||
nlist_type space[BUFSIZ/sizeof (nlist_type)]; |
||||
register register_t m = sizeof (space); |
||||
|
||||
if (n < m) |
||||
m = n; |
||||
if (read(fd, (char *)space, m) != m) |
||||
break; |
||||
n -= m; |
||||
long savpos = lseek(fd, 0, SEEK_CUR); |
||||
if (savpos == -1) { |
||||
return -1; |
||||
} |
||||
for (nlist_type* q = space; (m -= sizeof(nlist_type)) >= 0; q++) { |
||||
char nambuf[BUFSIZ]; |
||||
|
||||
if (q->n_un.n_strx == 0 || q->n_type & N_STAB) |
||||
continue; |
||||
|
||||
// seek to the location in the binary where the symbol
|
||||
// name is stored & read it into memory
|
||||
if (lseek(fd, ss+q->n_un.n_strx, SEEK_SET) == -1) { |
||||
return -1; |
||||
} |
||||
if (read(fd, nambuf, maxlen+1) == -1) { |
||||
return -1; |
||||
} |
||||
const char *s2 = nambuf; |
||||
for (nlist_type *p = list;
|
||||
symbolNames[p-list] && symbolNames[p-list][0]; |
||||
p++) { |
||||
// get the symbol name the user has passed in that
|
||||
// corresponds to the nlist entry that we're looking at
|
||||
const char *s1 = symbolNames[p - list]; |
||||
while (*s1) { |
||||
if (*s1++ != *s2++) |
||||
goto cont; |
||||
} |
||||
if (*s2) |
||||
goto cont; |
||||
|
||||
p->n_value = q->n_value; |
||||
p->n_type = q->n_type; |
||||
p->n_desc = q->n_desc; |
||||
p->n_sect = q->n_sect; |
||||
p->n_un.n_strx = q->n_un.n_strx; |
||||
if (--nreq == 0) |
||||
return nreq; |
||||
|
||||
break; |
||||
cont: ; |
||||
} |
||||
} |
||||
if (lseek(fd, savpos, SEEK_SET) == -1) { |
||||
return -1; |
||||
} |
||||
} |
||||
return nreq; |
||||
} |
@ -1,47 +0,0 @@ |
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// breakpad_nlist.h
|
||||
//
|
||||
// This file is meant to provide a header for clients of the modified
|
||||
// nlist function implemented to work on 64-bit.
|
||||
|
||||
#ifndef CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__ |
||||
|
||||
#include <mach/machine.h> |
||||
|
||||
int breakpad_nlist(const char *name, |
||||
struct nlist *list, |
||||
const char **symbolNames, |
||||
cpu_type_t cpu_type); |
||||
int breakpad_nlist(const char *name, |
||||
struct nlist_64 *list, |
||||
const char **symbolNames, |
||||
cpu_type_t cpu_type); |
||||
|
||||
#endif /* CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__ */ |
@ -1,567 +0,0 @@ |
||||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "client/mac/handler/dynamic_images.h" |
||||
|
||||
extern "C" { // needed to compile on Leopard
|
||||
#include <mach-o/nlist.h> |
||||
#include <stdlib.h> |
||||
#include <stdio.h> |
||||
} |
||||
|
||||
#include "breakpad_nlist_64.h" |
||||
#include <AvailabilityMacros.h> |
||||
#include <assert.h> |
||||
#include <CoreServices/CoreServices.h> |
||||
#include <dlfcn.h> |
||||
#include <mach/mach_vm.h> |
||||
#include <mach/task_info.h> |
||||
#include <sys/sysctl.h> |
||||
|
||||
#include <algorithm> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_6 |
||||
#define MAC_OS_X_VERSION_10_6 1060 |
||||
#endif |
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 |
||||
|
||||
// Fallback declarations for TASK_DYLD_INFO and friends, introduced in
|
||||
// <mach/task_info.h> in the Mac OS X 10.6 SDK.
|
||||
#define TASK_DYLD_INFO 17 |
||||
struct task_dyld_info { |
||||
mach_vm_address_t all_image_info_addr; |
||||
mach_vm_size_t all_image_info_size; |
||||
}; |
||||
typedef struct task_dyld_info task_dyld_info_data_t; |
||||
typedef struct task_dyld_info *task_dyld_info_t; |
||||
#define TASK_DYLD_INFO_COUNT (sizeof(task_dyld_info_data_t) / sizeof(natural_t)) |
||||
|
||||
#endif |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
using std::string; |
||||
using std::vector; |
||||
|
||||
//==============================================================================
|
||||
// Returns the size of the memory region containing |address| and the
|
||||
// number of bytes from |address| to the end of the region.
|
||||
// We potentially, will extend the size of the original
|
||||
// region by the size of the following region if it's contiguous with the
|
||||
// first in order to handle cases when we're reading strings and they
|
||||
// straddle two vm regions.
|
||||
//
|
||||
static mach_vm_size_t GetMemoryRegionSize(task_port_t target_task, |
||||
const uint64_t address, |
||||
mach_vm_size_t *size_to_end) { |
||||
mach_vm_address_t region_base = (mach_vm_address_t)address; |
||||
mach_vm_size_t region_size; |
||||
natural_t nesting_level = 0; |
||||
vm_region_submap_info_64 submap_info; |
||||
mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64; |
||||
|
||||
// Get information about the vm region containing |address|
|
||||
vm_region_recurse_info_t region_info; |
||||
region_info = reinterpret_cast<vm_region_recurse_info_t>(&submap_info); |
||||
|
||||
kern_return_t result = |
||||
mach_vm_region_recurse(target_task, |
||||
®ion_base, |
||||
®ion_size, |
||||
&nesting_level, |
||||
region_info, |
||||
&info_count); |
||||
|
||||
if (result == KERN_SUCCESS) { |
||||
// Get distance from |address| to the end of this region
|
||||
*size_to_end = region_base + region_size -(mach_vm_address_t)address; |
||||
|
||||
// If we want to handle strings as long as 4096 characters we may need
|
||||
// to check if there's a vm region immediately following the first one.
|
||||
// If so, we need to extend |*size_to_end| to go all the way to the end
|
||||
// of the second region.
|
||||
if (*size_to_end < 4096) { |
||||
// Second region starts where the first one ends
|
||||
mach_vm_address_t region_base2 = |
||||
(mach_vm_address_t)(region_base + region_size); |
||||
mach_vm_size_t region_size2; |
||||
|
||||
// Get information about the following vm region
|
||||
result = |
||||
mach_vm_region_recurse(target_task, |
||||
®ion_base2, |
||||
®ion_size2, |
||||
&nesting_level, |
||||
region_info, |
||||
&info_count); |
||||
|
||||
// Extend region_size to go all the way to the end of the 2nd region
|
||||
if (result == KERN_SUCCESS |
||||
&& region_base2 == region_base + region_size) { |
||||
region_size += region_size2; |
||||
} |
||||
} |
||||
|
||||
*size_to_end = region_base + region_size -(mach_vm_address_t)address; |
||||
} else { |
||||
region_size = 0; |
||||
*size_to_end = 0; |
||||
} |
||||
|
||||
return region_size; |
||||
} |
||||
|
||||
#define kMaxStringLength 8192 |
||||
//==============================================================================
|
||||
// Reads a NULL-terminated string from another task.
|
||||
//
|
||||
// Warning! This will not read any strings longer than kMaxStringLength-1
|
||||
//
|
||||
static string ReadTaskString(task_port_t target_task, |
||||
const uint64_t address) { |
||||
// The problem is we don't know how much to read until we know how long
|
||||
// the string is. And we don't know how long the string is, until we've read
|
||||
// the memory! So, we'll try to read kMaxStringLength bytes
|
||||
// (or as many bytes as we can until we reach the end of the vm region).
|
||||
mach_vm_size_t size_to_end; |
||||
GetMemoryRegionSize(target_task, address, &size_to_end); |
||||
|
||||
if (size_to_end > 0) { |
||||
mach_vm_size_t size_to_read = |
||||
size_to_end > kMaxStringLength ? kMaxStringLength : size_to_end; |
||||
|
||||
vector<uint8_t> bytes; |
||||
if (ReadTaskMemory(target_task, address, (size_t)size_to_read, bytes) != |
||||
KERN_SUCCESS) |
||||
return string(); |
||||
|
||||
return string(reinterpret_cast<const char*>(&bytes[0])); |
||||
} |
||||
|
||||
return string(); |
||||
} |
||||
|
||||
//==============================================================================
|
||||
// Reads an address range from another task. The bytes read will be returned
|
||||
// in bytes, which will be resized as necessary.
|
||||
kern_return_t ReadTaskMemory(task_port_t target_task, |
||||
const uint64_t address, |
||||
size_t length, |
||||
vector<uint8_t> &bytes) { |
||||
int systemPageSize = getpagesize(); |
||||
|
||||
// use the negative of the page size for the mask to find the page address
|
||||
mach_vm_address_t page_address = address & (-systemPageSize); |
||||
|
||||
mach_vm_address_t last_page_address = |
||||
(address + length + (systemPageSize - 1)) & (-systemPageSize); |
||||
|
||||
mach_vm_size_t page_size = last_page_address - page_address; |
||||
uint8_t* local_start; |
||||
uint32_t local_length; |
||||
|
||||
kern_return_t r = mach_vm_read(target_task, |
||||
page_address, |
||||
page_size, |
||||
reinterpret_cast<vm_offset_t*>(&local_start), |
||||
&local_length); |
||||
|
||||
if (r != KERN_SUCCESS) |
||||
return r; |
||||
|
||||
bytes.resize(length); |
||||
memcpy(&bytes[0], |
||||
&local_start[(mach_vm_address_t)address - page_address], |
||||
length); |
||||
mach_vm_deallocate(mach_task_self(), (uintptr_t)local_start, local_length); |
||||
return KERN_SUCCESS; |
||||
} |
||||
|
||||
#pragma mark - |
||||
|
||||
//==============================================================================
|
||||
// Traits structs for specializing function templates to handle
|
||||
// 32-bit/64-bit Mach-O files.
|
||||
struct MachO32 { |
||||
typedef mach_header mach_header_type; |
||||
typedef segment_command mach_segment_command_type; |
||||
typedef dyld_image_info32 dyld_image_info; |
||||
typedef dyld_all_image_infos32 dyld_all_image_infos; |
||||
typedef struct nlist nlist_type; |
||||
static const uint32_t magic = MH_MAGIC; |
||||
static const uint32_t segment_load_command = LC_SEGMENT; |
||||
}; |
||||
|
||||
struct MachO64 { |
||||
typedef mach_header_64 mach_header_type; |
||||
typedef segment_command_64 mach_segment_command_type; |
||||
typedef dyld_image_info64 dyld_image_info; |
||||
typedef dyld_all_image_infos64 dyld_all_image_infos; |
||||
typedef struct nlist_64 nlist_type; |
||||
static const uint32_t magic = MH_MAGIC_64; |
||||
static const uint32_t segment_load_command = LC_SEGMENT_64; |
||||
}; |
||||
|
||||
template<typename MachBits> |
||||
bool FindTextSection(DynamicImage& image) { |
||||
typedef typename MachBits::mach_header_type mach_header_type; |
||||
typedef typename MachBits::mach_segment_command_type |
||||
mach_segment_command_type; |
||||
|
||||
const mach_header_type* header = |
||||
reinterpret_cast<const mach_header_type*>(&image.header_[0]); |
||||
|
||||
if(header->magic != MachBits::magic) { |
||||
return false; |
||||
} |
||||
|
||||
const struct load_command *cmd = |
||||
reinterpret_cast<const struct load_command *>(header + 1); |
||||
|
||||
bool found_text_section = false; |
||||
bool found_dylib_id_command = false; |
||||
for (unsigned int i = 0; cmd && (i < header->ncmds); ++i) { |
||||
if (!found_text_section) { |
||||
if (cmd->cmd == MachBits::segment_load_command) { |
||||
const mach_segment_command_type *seg = |
||||
reinterpret_cast<const mach_segment_command_type *>(cmd); |
||||
|
||||
if (!strcmp(seg->segname, "__TEXT")) { |
||||
image.vmaddr_ = seg->vmaddr; |
||||
image.vmsize_ = seg->vmsize; |
||||
image.slide_ = 0; |
||||
|
||||
if (seg->fileoff == 0 && seg->filesize != 0) { |
||||
image.slide_ = |
||||
(uintptr_t)image.GetLoadAddress() - (uintptr_t)seg->vmaddr; |
||||
} |
||||
found_text_section = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (!found_dylib_id_command) { |
||||
if (cmd->cmd == LC_ID_DYLIB) { |
||||
const struct dylib_command *dc = |
||||
reinterpret_cast<const struct dylib_command *>(cmd); |
||||
|
||||
image.version_ = dc->dylib.current_version; |
||||
found_dylib_id_command = true; |
||||
} |
||||
} |
||||
|
||||
if (found_dylib_id_command && found_text_section) { |
||||
return true; |
||||
} |
||||
|
||||
cmd = reinterpret_cast<const struct load_command *> |
||||
(reinterpret_cast<const char *>(cmd) + cmd->cmdsize); |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
//==============================================================================
|
||||
// Initializes vmaddr_, vmsize_, and slide_
|
||||
void DynamicImage::CalculateMemoryAndVersionInfo() { |
||||
// unless we can process the header, ensure that calls to
|
||||
// IsValid() will return false
|
||||
vmaddr_ = 0; |
||||
vmsize_ = 0; |
||||
slide_ = 0; |
||||
version_ = 0; |
||||
|
||||
// The function template above does all the real work.
|
||||
if (Is64Bit()) |
||||
FindTextSection<MachO64>(*this); |
||||
else |
||||
FindTextSection<MachO32>(*this); |
||||
} |
||||
|
||||
//==============================================================================
|
||||
// The helper function template abstracts the 32/64-bit differences.
|
||||
template<typename MachBits> |
||||
uint32_t GetFileTypeFromHeader(DynamicImage& image) { |
||||
typedef typename MachBits::mach_header_type mach_header_type; |
||||
|
||||
const mach_header_type* header = |
||||
reinterpret_cast<const mach_header_type*>(&image.header_[0]); |
||||
return header->filetype; |
||||
} |
||||
|
||||
uint32_t DynamicImage::GetFileType() { |
||||
if (Is64Bit()) |
||||
return GetFileTypeFromHeader<MachO64>(*this); |
||||
|
||||
return GetFileTypeFromHeader<MachO32>(*this); |
||||
} |
||||
|
||||
#pragma mark - |
||||
|
||||
//==============================================================================
|
||||
// Loads information about dynamically loaded code in the given task.
|
||||
DynamicImages::DynamicImages(mach_port_t task) |
||||
: task_(task), |
||||
cpu_type_(DetermineTaskCPUType(task)), |
||||
image_list_() { |
||||
ReadImageInfoForTask(); |
||||
} |
||||
|
||||
template<typename MachBits> |
||||
static uint64_t LookupSymbol(const char* symbol_name, |
||||
const char* filename, |
||||
cpu_type_t cpu_type) { |
||||
typedef typename MachBits::nlist_type nlist_type; |
||||
|
||||
nlist_type symbol_info[8] = {}; |
||||
const char *symbolNames[2] = { symbol_name, "\0" }; |
||||
nlist_type &list = symbol_info[0]; |
||||
int invalidEntriesCount = breakpad_nlist(filename, |
||||
&list, |
||||
symbolNames, |
||||
cpu_type); |
||||
|
||||
if(invalidEntriesCount != 0) { |
||||
return 0; |
||||
} |
||||
|
||||
assert(list.n_value); |
||||
return list.n_value; |
||||
} |
||||
|
||||
static SInt32 GetOSVersionInternal() { |
||||
SInt32 os_version = 0; |
||||
Gestalt(gestaltSystemVersion, &os_version); |
||||
return os_version; |
||||
} |
||||
|
||||
static SInt32 GetOSVersion() { |
||||
static SInt32 os_version = GetOSVersionInternal(); |
||||
return os_version; |
||||
} |
||||
|
||||
static bool IsSnowLeopardOrLater() { |
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 |
||||
return true; |
||||
#else |
||||
return GetOSVersion() >= 0x1060; |
||||
#endif |
||||
} |
||||
|
||||
uint64_t DynamicImages::GetDyldAllImageInfosPointer() { |
||||
if (IsSnowLeopardOrLater()) { |
||||
task_dyld_info_data_t task_dyld_info; |
||||
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; |
||||
if (task_info(task_, TASK_DYLD_INFO, (task_info_t)&task_dyld_info, |
||||
&count) != KERN_SUCCESS) { |
||||
return NULL; |
||||
} |
||||
|
||||
return (uint64_t)task_dyld_info.all_image_info_addr; |
||||
} else { |
||||
const char *imageSymbolName = "_dyld_all_image_infos"; |
||||
const char *dyldPath = "/usr/lib/dyld"; |
||||
|
||||
if (Is64Bit()) |
||||
return LookupSymbol<MachO64>(imageSymbolName, dyldPath, cpu_type_); |
||||
return LookupSymbol<MachO32>(imageSymbolName, dyldPath, cpu_type_); |
||||
} |
||||
} |
||||
|
||||
//==============================================================================
|
||||
// This code was written using dyld_debug.c (from Darwin) as a guide.
|
||||
|
||||
template<typename MachBits> |
||||
void ReadImageInfo(DynamicImages& images, |
||||
uint64_t image_list_address) { |
||||
typedef typename MachBits::dyld_image_info dyld_image_info; |
||||
typedef typename MachBits::dyld_all_image_infos dyld_all_image_infos; |
||||
typedef typename MachBits::mach_header_type mach_header_type; |
||||
|
||||
// Read the structure inside of dyld that contains information about
|
||||
// loaded images. We're reading from the desired task's address space.
|
||||
|
||||
// Here we make the assumption that dyld loaded at the same address in
|
||||
// the crashed process vs. this one. This is an assumption made in
|
||||
// "dyld_debug.c" and is said to be nearly always valid.
|
||||
vector<uint8_t> dyld_all_info_bytes; |
||||
if (ReadTaskMemory(images.task_, |
||||
image_list_address, |
||||
sizeof(dyld_all_image_infos), |
||||
dyld_all_info_bytes) != KERN_SUCCESS) |
||||
return; |
||||
|
||||
dyld_all_image_infos *dyldInfo = |
||||
reinterpret_cast<dyld_all_image_infos*>(&dyld_all_info_bytes[0]); |
||||
|
||||
// number of loaded images
|
||||
int count = dyldInfo->infoArrayCount; |
||||
|
||||
// Read an array of dyld_image_info structures each containing
|
||||
// information about a loaded image.
|
||||
vector<uint8_t> dyld_info_array_bytes; |
||||
if (ReadTaskMemory(images.task_, |
||||
dyldInfo->infoArray, |
||||
count * sizeof(dyld_image_info), |
||||
dyld_info_array_bytes) != KERN_SUCCESS) |
||||
return; |
||||
|
||||
dyld_image_info *infoArray = |
||||
reinterpret_cast<dyld_image_info*>(&dyld_info_array_bytes[0]); |
||||
images.image_list_.reserve(count); |
||||
|
||||
for (int i = 0; i < count; ++i) { |
||||
dyld_image_info &info = infoArray[i]; |
||||
|
||||
// First read just the mach_header from the image in the task.
|
||||
vector<uint8_t> mach_header_bytes; |
||||
if (ReadTaskMemory(images.task_, |
||||
info.load_address_, |
||||
sizeof(mach_header_type), |
||||
mach_header_bytes) != KERN_SUCCESS) |
||||
continue; // bail on this dynamic image
|
||||
|
||||
mach_header_type *header = |
||||
reinterpret_cast<mach_header_type*>(&mach_header_bytes[0]); |
||||
|
||||
// Now determine the total amount necessary to read the header
|
||||
// plus all of the load commands.
|
||||
size_t header_size = |
||||
sizeof(mach_header_type) + header->sizeofcmds; |
||||
|
||||
if (ReadTaskMemory(images.task_, |
||||
info.load_address_, |
||||
header_size, |
||||
mach_header_bytes) != KERN_SUCCESS) |
||||
continue; |
||||
|
||||
header = reinterpret_cast<mach_header_type*>(&mach_header_bytes[0]); |
||||
|
||||
// Read the file name from the task's memory space.
|
||||
string file_path; |
||||
if (info.file_path_) { |
||||
// Although we're reading kMaxStringLength bytes, it's copied in the
|
||||
// the DynamicImage constructor below with the correct string length,
|
||||
// so it's not really wasting memory.
|
||||
file_path = ReadTaskString(images.task_, info.file_path_); |
||||
} |
||||
|
||||
// Create an object representing this image and add it to our list.
|
||||
DynamicImage *new_image; |
||||
new_image = new DynamicImage(&mach_header_bytes[0], |
||||
header_size, |
||||
info.load_address_, |
||||
file_path, |
||||
info.file_mod_date_, |
||||
images.task_, |
||||
images.cpu_type_); |
||||
|
||||
if (new_image->IsValid()) { |
||||
images.image_list_.push_back(DynamicImageRef(new_image)); |
||||
} else { |
||||
delete new_image; |
||||
} |
||||
} |
||||
|
||||
// sorts based on loading address
|
||||
sort(images.image_list_.begin(), images.image_list_.end()); |
||||
// remove duplicates - this happens in certain strange cases
|
||||
// You can see it in DashboardClient when Google Gadgets plugin
|
||||
// is installed. Apple's crash reporter log and gdb "info shared"
|
||||
// both show the same library multiple times at the same address
|
||||
|
||||
vector<DynamicImageRef>::iterator it = unique(images.image_list_.begin(), |
||||
images.image_list_.end()); |
||||
images.image_list_.erase(it, images.image_list_.end()); |
||||
} |
||||
|
||||
void DynamicImages::ReadImageInfoForTask() { |
||||
uint64_t imageList = GetDyldAllImageInfosPointer(); |
||||
|
||||
if (imageList) { |
||||
if (Is64Bit()) |
||||
ReadImageInfo<MachO64>(*this, imageList); |
||||
else |
||||
ReadImageInfo<MachO32>(*this, imageList); |
||||
} |
||||
} |
||||
|
||||
//==============================================================================
|
||||
DynamicImage *DynamicImages::GetExecutableImage() { |
||||
int executable_index = GetExecutableImageIndex(); |
||||
|
||||
if (executable_index >= 0) { |
||||
return GetImage(executable_index); |
||||
} |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
//==============================================================================
|
||||
// returns -1 if failure to find executable
|
||||
int DynamicImages::GetExecutableImageIndex() { |
||||
int image_count = GetImageCount(); |
||||
|
||||
for (int i = 0; i < image_count; ++i) { |
||||
DynamicImage *image = GetImage(i); |
||||
if (image->GetFileType() == MH_EXECUTE) { |
||||
return i; |
||||
} |
||||
} |
||||
|
||||
return -1; |
||||
} |
||||
|
||||
//==============================================================================
|
||||
// static
|
||||
cpu_type_t DynamicImages::DetermineTaskCPUType(task_t task) { |
||||
if (task == mach_task_self()) |
||||
return GetNativeCPUType(); |
||||
|
||||
int mib[CTL_MAXNAME]; |
||||
size_t mibLen = CTL_MAXNAME; |
||||
int err = sysctlnametomib("sysctl.proc_cputype", mib, &mibLen); |
||||
if (err == 0) { |
||||
assert(mibLen < CTL_MAXNAME); |
||||
pid_for_task(task, &mib[mibLen]); |
||||
mibLen += 1; |
||||
|
||||
cpu_type_t cpu_type; |
||||
size_t cpuTypeSize = sizeof(cpu_type); |
||||
sysctl(mib, mibLen, &cpu_type, &cpuTypeSize, 0, 0); |
||||
return cpu_type; |
||||
} |
||||
|
||||
return GetNativeCPUType(); |
||||
} |
||||
|
||||
} // namespace google_breakpad
|
@ -1,313 +0,0 @@ |
||||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// dynamic_images.h
|
||||
//
|
||||
// Implements most of the function of the dyld API, but allowing an
|
||||
// arbitrary task to be introspected, unlike the dyld API which
|
||||
// only allows operation on the current task. The current implementation
|
||||
// is limited to use by 32-bit tasks.
|
||||
|
||||
#ifndef CLIENT_MAC_HANDLER_DYNAMIC_IMAGES_H__ |
||||
#define CLIENT_MAC_HANDLER_DYNAMIC_IMAGES_H__ |
||||
|
||||
#include <mach/mach.h> |
||||
#include <mach-o/dyld.h> |
||||
#include <mach-o/loader.h> |
||||
#include <sys/types.h> |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
using std::string; |
||||
using std::vector; |
||||
|
||||
//==============================================================================
|
||||
// The memory layout of this struct matches the dyld_image_info struct
|
||||
// defined in "dyld_gdb.h" in the darwin source.
|
||||
typedef struct dyld_image_info32 { |
||||
uint32_t load_address_; // struct mach_header*
|
||||
uint32_t file_path_; // char*
|
||||
uint32_t file_mod_date_; |
||||
} dyld_image_info32; |
||||
|
||||
typedef struct dyld_image_info64 { |
||||
uint64_t load_address_; // struct mach_header*
|
||||
uint64_t file_path_; // char*
|
||||
uint64_t file_mod_date_; |
||||
} dyld_image_info64; |
||||
|
||||
//==============================================================================
|
||||
// This is as defined in "dyld_gdb.h" in the darwin source.
|
||||
// _dyld_all_image_infos (in dyld) is a structure of this type
|
||||
// which will be used to determine which dynamic code has been loaded.
|
||||
typedef struct dyld_all_image_infos32 { |
||||
uint32_t version; // == 1 in Mac OS X 10.4
|
||||
uint32_t infoArrayCount; |
||||
uint32_t infoArray; // const struct dyld_image_info*
|
||||
uint32_t notification; |
||||
bool processDetachedFromSharedRegion; |
||||
} dyld_all_image_infos32; |
||||
|
||||
typedef struct dyld_all_image_infos64 { |
||||
uint32_t version; // == 1 in Mac OS X 10.4
|
||||
uint32_t infoArrayCount; |
||||
uint64_t infoArray; // const struct dyld_image_info*
|
||||
uint64_t notification; |
||||
bool processDetachedFromSharedRegion; |
||||
} dyld_all_image_infos64; |
||||
|
||||
// some typedefs to isolate 64/32 bit differences
|
||||
#ifdef __LP64__ |
||||
typedef mach_header_64 breakpad_mach_header; |
||||
typedef segment_command_64 breakpad_mach_segment_command; |
||||
#else |
||||
typedef mach_header breakpad_mach_header; |
||||
typedef segment_command breakpad_mach_segment_command; |
||||
#endif |
||||
|
||||
// Helper functions to deal with 32-bit/64-bit Mach-O differences.
|
||||
class DynamicImage; |
||||
template<typename MachBits> |
||||
bool FindTextSection(DynamicImage& image); |
||||
|
||||
template<typename MachBits> |
||||
uint32_t GetFileTypeFromHeader(DynamicImage& image); |
||||
|
||||
//==============================================================================
|
||||
// Represents a single dynamically loaded mach-o image
|
||||
class DynamicImage { |
||||
public: |
||||
DynamicImage(uint8_t *header, // data is copied
|
||||
size_t header_size, // includes load commands
|
||||
uint64_t load_address, |
||||
string file_path, |
||||
uintptr_t image_mod_date, |
||||
mach_port_t task, |
||||
cpu_type_t cpu_type) |
||||
: header_(header, header + header_size), |
||||
header_size_(header_size), |
||||
load_address_(load_address), |
||||
vmaddr_(0), |
||||
vmsize_(0), |
||||
slide_(0), |
||||
version_(0), |
||||
file_path_(file_path), |
||||
file_mod_date_(image_mod_date), |
||||
task_(task), |
||||
cpu_type_(cpu_type) { |
||||
CalculateMemoryAndVersionInfo(); |
||||
} |
||||
|
||||
// Size of mach_header plus load commands
|
||||
size_t GetHeaderSize() const {return header_.size();} |
||||
|
||||
// Full path to mach-o binary
|
||||
string GetFilePath() {return file_path_;} |
||||
|
||||
uint64_t GetModDate() const {return file_mod_date_;} |
||||
|
||||
// Actual address where the image was loaded
|
||||
uint64_t GetLoadAddress() const {return load_address_;} |
||||
|
||||
// Address where the image should be loaded
|
||||
mach_vm_address_t GetVMAddr() const {return vmaddr_;} |
||||
|
||||
// Difference between GetLoadAddress() and GetVMAddr()
|
||||
ptrdiff_t GetVMAddrSlide() const {return slide_;} |
||||
|
||||
// Size of the image
|
||||
mach_vm_size_t GetVMSize() const {return vmsize_;} |
||||
|
||||
// Task owning this loaded image
|
||||
mach_port_t GetTask() {return task_;} |
||||
|
||||
// CPU type of the task
|
||||
cpu_type_t GetCPUType() {return cpu_type_;} |
||||
|
||||
// filetype from the Mach-O header.
|
||||
uint32_t GetFileType(); |
||||
|
||||
// Return true if the task is a 64-bit architecture.
|
||||
bool Is64Bit() { return (GetCPUType() & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; } |
||||
|
||||
uint32_t GetVersion() {return version_;} |
||||
// For sorting
|
||||
bool operator<(const DynamicImage &inInfo) { |
||||
return GetLoadAddress() < inInfo.GetLoadAddress(); |
||||
} |
||||
|
||||
// Sanity checking
|
||||
bool IsValid() {return GetVMSize() != 0;} |
||||
|
||||
private: |
||||
DynamicImage(const DynamicImage &); |
||||
DynamicImage &operator=(const DynamicImage &); |
||||
|
||||
friend class DynamicImages; |
||||
template<typename MachBits> |
||||
friend bool FindTextSection(DynamicImage& image); |
||||
template<typename MachBits> |
||||
friend uint32_t GetFileTypeFromHeader(DynamicImage& image); |
||||
|
||||
// Initializes vmaddr_, vmsize_, and slide_
|
||||
void CalculateMemoryAndVersionInfo(); |
||||
|
||||
const vector<uint8_t> header_; // our local copy of the header
|
||||
size_t header_size_; // mach_header plus load commands
|
||||
uint64_t load_address_; // base address image is mapped into
|
||||
mach_vm_address_t vmaddr_; |
||||
mach_vm_size_t vmsize_; |
||||
ptrdiff_t slide_; |
||||
uint32_t version_; // Dylib version
|
||||
string file_path_; // path dyld used to load the image
|
||||
uintptr_t file_mod_date_; // time_t of image file
|
||||
|
||||
mach_port_t task_; |
||||
cpu_type_t cpu_type_; // CPU type of task_
|
||||
}; |
||||
|
||||
//==============================================================================
|
||||
// DynamicImageRef is just a simple wrapper for a pointer to
|
||||
// DynamicImage. The reason we use it instead of a simple typedef is so
|
||||
// that we can use stl::sort() on a vector of DynamicImageRefs
|
||||
// and simple class pointers can't implement operator<().
|
||||
//
|
||||
class DynamicImageRef { |
||||
public: |
||||
explicit DynamicImageRef(DynamicImage *inP) : p(inP) {} |
||||
// The copy constructor is required by STL
|
||||
DynamicImageRef(const DynamicImageRef &inRef) : p(inRef.p) {} |
||||
|
||||
bool operator<(const DynamicImageRef &inRef) const { |
||||
return (*const_cast<DynamicImageRef*>(this)->p) |
||||
< (*const_cast<DynamicImageRef&>(inRef).p); |
||||
} |
||||
|
||||
bool operator==(const DynamicImageRef &inInfo) const { |
||||
return (*const_cast<DynamicImageRef*>(this)->p).GetLoadAddress() == |
||||
(*const_cast<DynamicImageRef&>(inInfo)).GetLoadAddress(); |
||||
} |
||||
|
||||
// Be just like DynamicImage*
|
||||
DynamicImage *operator->() {return p;} |
||||
operator DynamicImage*() {return p;} |
||||
|
||||
private: |
||||
DynamicImage *p; |
||||
}; |
||||
|
||||
// Helper function to deal with 32-bit/64-bit Mach-O differences.
|
||||
class DynamicImages; |
||||
template<typename MachBits> |
||||
void ReadImageInfo(DynamicImages& images, uint64_t image_list_address); |
||||
|
||||
//==============================================================================
|
||||
// An object of type DynamicImages may be created to allow introspection of
|
||||
// an arbitrary task's dynamically loaded mach-o binaries. This makes the
|
||||
// assumption that the current task has send rights to the target task.
|
||||
class DynamicImages { |
||||
public: |
||||
explicit DynamicImages(mach_port_t task); |
||||
|
||||
~DynamicImages() { |
||||
for (int i = 0; i < GetImageCount(); ++i) { |
||||
delete image_list_[i]; |
||||
} |
||||
} |
||||
|
||||
// Returns the number of dynamically loaded mach-o images.
|
||||
int GetImageCount() const {return static_cast<int>(image_list_.size());} |
||||
|
||||
// Returns an individual image.
|
||||
DynamicImage *GetImage(int i) { |
||||
if (i < (int)image_list_.size()) { |
||||
return image_list_[i]; |
||||
} |
||||
return NULL; |
||||
} |
||||
|
||||
// Returns the image corresponding to the main executable.
|
||||
DynamicImage *GetExecutableImage(); |
||||
int GetExecutableImageIndex(); |
||||
|
||||
// Returns the task which we're looking at.
|
||||
mach_port_t GetTask() const {return task_;} |
||||
|
||||
// CPU type of the task
|
||||
cpu_type_t GetCPUType() {return cpu_type_;} |
||||
|
||||
// Return true if the task is a 64-bit architecture.
|
||||
bool Is64Bit() { return (GetCPUType() & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; } |
||||
|
||||
// Determine the CPU type of the task being dumped.
|
||||
static cpu_type_t DetermineTaskCPUType(task_t task); |
||||
|
||||
// Get the native CPU type of this task.
|
||||
static cpu_type_t GetNativeCPUType() { |
||||
#if defined(__i386__) |
||||
return CPU_TYPE_I386; |
||||
#elif defined(__x86_64__) |
||||
return CPU_TYPE_X86_64; |
||||
#elif defined(__ppc__) |
||||
return CPU_TYPE_POWERPC; |
||||
#elif defined(__ppc64__) |
||||
return CPU_TYPE_POWERPC64; |
||||
#else |
||||
#error "GetNativeCPUType not implemented for this architecture" |
||||
#endif |
||||
} |
||||
|
||||
private: |
||||
template<typename MachBits> |
||||
friend void ReadImageInfo(DynamicImages& images, uint64_t image_list_address); |
||||
|
||||
bool IsOurTask() {return task_ == mach_task_self();} |
||||
|
||||
// Initialization
|
||||
void ReadImageInfoForTask(); |
||||
uint64_t GetDyldAllImageInfosPointer(); |
||||
|
||||
mach_port_t task_; |
||||
cpu_type_t cpu_type_; // CPU type of task_
|
||||
vector<DynamicImageRef> image_list_; |
||||
}; |
||||
|
||||
// Fill bytes with the contents of memory at a particular
|
||||
// location in another task.
|
||||
kern_return_t ReadTaskMemory(task_port_t target_task, |
||||
const uint64_t address, |
||||
size_t length, |
||||
vector<uint8_t> &bytes); |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_MAC_HANDLER_DYNAMIC_IMAGES_H__
|
@ -1,813 +0,0 @@ |
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <map> |
||||
#include <pthread.h> |
||||
|
||||
#include "client/mac/handler/exception_handler.h" |
||||
#include "client/mac/handler/minidump_generator.h" |
||||
#include "common/mac/macho_utilities.h" |
||||
#include "common/mac/scoped_task_suspend-inl.h" |
||||
|
||||
#ifndef USE_PROTECTED_ALLOCATIONS |
||||
#define USE_PROTECTED_ALLOCATIONS 0 |
||||
#endif |
||||
|
||||
// If USE_PROTECTED_ALLOCATIONS is activated then the
|
||||
// gBreakpadAllocator needs to be setup in other code
|
||||
// ahead of time. Please see ProtectedMemoryAllocator.h
|
||||
// for more details.
|
||||
#if USE_PROTECTED_ALLOCATIONS |
||||
#include "protected_memory_allocator.h" |
||||
extern ProtectedMemoryAllocator *gBreakpadAllocator; |
||||
#endif |
||||
|
||||
|
||||
namespace google_breakpad { |
||||
|
||||
using std::map; |
||||
|
||||
// These structures and techniques are illustrated in
|
||||
// Mac OS X Internals, Amit Singh, ch 9.7
|
||||
struct ExceptionMessage { |
||||
mach_msg_header_t header; |
||||
mach_msg_body_t body; |
||||
mach_msg_port_descriptor_t thread; |
||||
mach_msg_port_descriptor_t task; |
||||
NDR_record_t ndr; |
||||
exception_type_t exception; |
||||
mach_msg_type_number_t code_count; |
||||
integer_t code[EXCEPTION_CODE_MAX]; |
||||
char padding[512]; |
||||
}; |
||||
|
||||
struct ExceptionParameters { |
||||
ExceptionParameters() : count(0) {} |
||||
mach_msg_type_number_t count; |
||||
exception_mask_t masks[EXC_TYPES_COUNT]; |
||||
mach_port_t ports[EXC_TYPES_COUNT]; |
||||
exception_behavior_t behaviors[EXC_TYPES_COUNT]; |
||||
thread_state_flavor_t flavors[EXC_TYPES_COUNT]; |
||||
}; |
||||
|
||||
struct ExceptionReplyMessage { |
||||
mach_msg_header_t header; |
||||
NDR_record_t ndr; |
||||
kern_return_t return_code; |
||||
}; |
||||
|
||||
// Only catch these three exceptions. The other ones are nebulously defined
|
||||
// and may result in treating a non-fatal exception as fatal.
|
||||
exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS | |
||||
EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT; |
||||
|
||||
extern "C" |
||||
{ |
||||
// Forward declarations for functions that need "C" style compilation
|
||||
boolean_t exc_server(mach_msg_header_t *request, |
||||
mach_msg_header_t *reply); |
||||
|
||||
// This symbol must be visible to dlsym() - see
|
||||
// http://code.google.com/p/google-breakpad/issues/detail?id=345 for details.
|
||||
kern_return_t catch_exception_raise(mach_port_t target_port, |
||||
mach_port_t failed_thread, |
||||
mach_port_t task, |
||||
exception_type_t exception, |
||||
exception_data_t code, |
||||
mach_msg_type_number_t code_count) |
||||
__attribute__((visibility("default"))); |
||||
|
||||
kern_return_t ForwardException(mach_port_t task, |
||||
mach_port_t failed_thread, |
||||
exception_type_t exception, |
||||
exception_data_t code, |
||||
mach_msg_type_number_t code_count); |
||||
|
||||
kern_return_t exception_raise(mach_port_t target_port, |
||||
mach_port_t failed_thread, |
||||
mach_port_t task, |
||||
exception_type_t exception, |
||||
exception_data_t exception_code, |
||||
mach_msg_type_number_t exception_code_count); |
||||
|
||||
kern_return_t |
||||
exception_raise_state(mach_port_t target_port, |
||||
mach_port_t failed_thread, |
||||
mach_port_t task, |
||||
exception_type_t exception, |
||||
exception_data_t exception_code, |
||||
mach_msg_type_number_t code_count, |
||||
thread_state_flavor_t *target_flavor, |
||||
thread_state_t in_thread_state, |
||||
mach_msg_type_number_t in_thread_state_count, |
||||
thread_state_t out_thread_state, |
||||
mach_msg_type_number_t *out_thread_state_count); |
||||
|
||||
kern_return_t |
||||
exception_raise_state_identity(mach_port_t target_port, |
||||
mach_port_t failed_thread, |
||||
mach_port_t task, |
||||
exception_type_t exception, |
||||
exception_data_t exception_code, |
||||
mach_msg_type_number_t exception_code_count, |
||||
thread_state_flavor_t *target_flavor, |
||||
thread_state_t in_thread_state, |
||||
mach_msg_type_number_t in_thread_state_count, |
||||
thread_state_t out_thread_state, |
||||
mach_msg_type_number_t *out_thread_state_count); |
||||
|
||||
kern_return_t breakpad_exception_raise_state(mach_port_t exception_port, |
||||
exception_type_t exception, |
||||
const exception_data_t code, |
||||
mach_msg_type_number_t codeCnt, |
||||
int *flavor, |
||||
const thread_state_t old_state, |
||||
mach_msg_type_number_t old_stateCnt, |
||||
thread_state_t new_state, |
||||
mach_msg_type_number_t *new_stateCnt |
||||
); |
||||
|
||||
kern_return_t breakpad_exception_raise_state_identity(mach_port_t exception_port, |
||||
mach_port_t thread, |
||||
mach_port_t task, |
||||
exception_type_t exception, |
||||
exception_data_t code, |
||||
mach_msg_type_number_t codeCnt, |
||||
int *flavor, |
||||
thread_state_t old_state, |
||||
mach_msg_type_number_t old_stateCnt, |
||||
thread_state_t new_state, |
||||
mach_msg_type_number_t *new_stateCnt |
||||
); |
||||
|
||||
kern_return_t breakpad_exception_raise(mach_port_t port, mach_port_t failed_thread, |
||||
mach_port_t task, |
||||
exception_type_t exception, |
||||
exception_data_t code, |
||||
mach_msg_type_number_t code_count); |
||||
} |
||||
|
||||
|
||||
|
||||
kern_return_t breakpad_exception_raise_state(mach_port_t exception_port, |
||||
exception_type_t exception, |
||||
const exception_data_t code, |
||||
mach_msg_type_number_t codeCnt, |
||||
int *flavor, |
||||
const thread_state_t old_state, |
||||
mach_msg_type_number_t old_stateCnt, |
||||
thread_state_t new_state, |
||||
mach_msg_type_number_t *new_stateCnt |
||||
) |
||||
{ |
||||
return KERN_SUCCESS; |
||||
} |
||||
|
||||
kern_return_t breakpad_exception_raise_state_identity(mach_port_t exception_port, |
||||
mach_port_t thread, |
||||
mach_port_t task, |
||||
exception_type_t exception, |
||||
exception_data_t code, |
||||
mach_msg_type_number_t codeCnt, |
||||
int *flavor, |
||||
thread_state_t old_state, |
||||
mach_msg_type_number_t old_stateCnt, |
||||
thread_state_t new_state, |
||||
mach_msg_type_number_t *new_stateCnt |
||||
) |
||||
{ |
||||
return KERN_SUCCESS; |
||||
} |
||||
|
||||
kern_return_t breakpad_exception_raise(mach_port_t port, mach_port_t failed_thread, |
||||
mach_port_t task, |
||||
exception_type_t exception, |
||||
exception_data_t code, |
||||
mach_msg_type_number_t code_count) { |
||||
|
||||
if (task != mach_task_self()) { |
||||
return KERN_FAILURE; |
||||
} |
||||
return ForwardException(task, failed_thread, exception, code, code_count); |
||||
} |
||||
|
||||
|
||||
ExceptionHandler::ExceptionHandler(const string &dump_path, |
||||
FilterCallback filter, |
||||
MinidumpCallback callback, |
||||
void *callback_context, |
||||
bool install_handler, |
||||
const char *port_name) |
||||
: dump_path_(), |
||||
filter_(filter), |
||||
callback_(callback), |
||||
callback_context_(callback_context), |
||||
directCallback_(NULL), |
||||
handler_thread_(NULL), |
||||
handler_port_(MACH_PORT_NULL), |
||||
previous_(NULL), |
||||
installed_exception_handler_(false), |
||||
is_in_teardown_(false), |
||||
last_minidump_write_result_(false), |
||||
use_minidump_write_mutex_(false) { |
||||
// This will update to the ID and C-string pointers
|
||||
set_dump_path(dump_path); |
||||
MinidumpGenerator::GatherSystemInformation(); |
||||
if (port_name) |
||||
crash_generation_client_.reset(new CrashGenerationClient(port_name)); |
||||
Setup(install_handler); |
||||
} |
||||
|
||||
// special constructor if we want to bypass minidump writing and
|
||||
// simply get a callback with the exception information
|
||||
ExceptionHandler::ExceptionHandler(DirectCallback callback, |
||||
void *callback_context, |
||||
bool install_handler) |
||||
: dump_path_(), |
||||
filter_(NULL), |
||||
callback_(NULL), |
||||
callback_context_(callback_context), |
||||
directCallback_(callback), |
||||
handler_thread_(NULL), |
||||
handler_port_(MACH_PORT_NULL), |
||||
previous_(NULL), |
||||
installed_exception_handler_(false), |
||||
is_in_teardown_(false), |
||||
last_minidump_write_result_(false), |
||||
use_minidump_write_mutex_(false) { |
||||
MinidumpGenerator::GatherSystemInformation(); |
||||
Setup(install_handler); |
||||
} |
||||
|
||||
ExceptionHandler::~ExceptionHandler() { |
||||
Teardown(); |
||||
} |
||||
|
||||
bool ExceptionHandler::WriteMinidump(bool write_exception_stream) { |
||||
// If we're currently writing, just return
|
||||
if (use_minidump_write_mutex_) |
||||
return false; |
||||
|
||||
use_minidump_write_mutex_ = true; |
||||
last_minidump_write_result_ = false; |
||||
|
||||
// Lock the mutex. Since we just created it, this will return immediately.
|
||||
if (pthread_mutex_lock(&minidump_write_mutex_) == 0) { |
||||
// Send an empty message to the handle port so that a minidump will
|
||||
// be written
|
||||
SendMessageToHandlerThread(write_exception_stream ? |
||||
kWriteDumpWithExceptionMessage : |
||||
kWriteDumpMessage); |
||||
|
||||
// Wait for the minidump writer to complete its writing. It will unlock
|
||||
// the mutex when completed
|
||||
pthread_mutex_lock(&minidump_write_mutex_); |
||||
} |
||||
|
||||
use_minidump_write_mutex_ = false; |
||||
UpdateNextID(); |
||||
return last_minidump_write_result_; |
||||
} |
||||
|
||||
// static
|
||||
bool ExceptionHandler::WriteMinidump(const string &dump_path, |
||||
bool write_exception_stream, |
||||
MinidumpCallback callback, |
||||
void *callback_context) { |
||||
ExceptionHandler handler(dump_path, NULL, callback, callback_context, false, |
||||
NULL); |
||||
return handler.WriteMinidump(write_exception_stream); |
||||
} |
||||
|
||||
// static
|
||||
bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child, |
||||
mach_port_t child_blamed_thread, |
||||
const string &dump_path, |
||||
MinidumpCallback callback, |
||||
void *callback_context) { |
||||
ScopedTaskSuspend suspend(child); |
||||
|
||||
MinidumpGenerator generator(child, MACH_PORT_NULL); |
||||
string dump_id; |
||||
string dump_filename = generator.UniqueNameInDirectory(dump_path, &dump_id); |
||||
|
||||
generator.SetExceptionInformation(EXC_BREAKPOINT, |
||||
#if defined (__i386__) || defined(__x86_64__) |
||||
EXC_I386_BPT, |
||||
#elif defined (__ppc__) || defined (__ppc64__) |
||||
EXC_PPC_BREAKPOINT, |
||||
#else |
||||
#error architecture not supported |
||||
#endif |
||||
0, |
||||
child_blamed_thread); |
||||
bool result = generator.Write(dump_filename.c_str()); |
||||
|
||||
if (callback) { |
||||
return callback(dump_path.c_str(), dump_id.c_str(), |
||||
callback_context, result); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
bool ExceptionHandler::WriteMinidumpWithException(int exception_type, |
||||
int exception_code, |
||||
int exception_subcode, |
||||
mach_port_t thread_name, |
||||
bool exit_after_write) { |
||||
bool result = false; |
||||
|
||||
if (directCallback_) { |
||||
if (directCallback_(callback_context_, |
||||
exception_type, |
||||
exception_code, |
||||
exception_subcode, |
||||
thread_name) ) { |
||||
if (exit_after_write) |
||||
_exit(exception_type); |
||||
} |
||||
} else if (IsOutOfProcess()) { |
||||
if (exception_type && exception_code) { |
||||
// If this is a real exception, give the filter (if any) a chance to
|
||||
// decide if this should be sent.
|
||||
if (filter_ && !filter_(callback_context_)) |
||||
return false; |
||||
return crash_generation_client_->RequestDumpForException( |
||||
exception_type, |
||||
exception_code, |
||||
exception_subcode, |
||||
thread_name); |
||||
} |
||||
} else { |
||||
string minidump_id; |
||||
|
||||
// Putting the MinidumpGenerator in its own context will ensure that the
|
||||
// destructor is executed, closing the newly created minidump file.
|
||||
if (!dump_path_.empty()) { |
||||
MinidumpGenerator md; |
||||
if (exception_type && exception_code) { |
||||
// If this is a real exception, give the filter (if any) a chance to
|
||||
// decide if this should be sent.
|
||||
if (filter_ && !filter_(callback_context_)) |
||||
return false; |
||||
|
||||
md.SetExceptionInformation(exception_type, exception_code, |
||||
exception_subcode, thread_name); |
||||
} |
||||
|
||||
result = md.Write(next_minidump_path_c_); |
||||
} |
||||
|
||||
// Call user specified callback (if any)
|
||||
if (callback_) { |
||||
// If the user callback returned true and we're handling an exception
|
||||
// (rather than just writing out the file), then we should exit without
|
||||
// forwarding the exception to the next handler.
|
||||
if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_, |
||||
result)) { |
||||
if (exit_after_write) |
||||
_exit(exception_type); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread, |
||||
exception_type_t exception, |
||||
exception_data_t code, |
||||
mach_msg_type_number_t code_count) { |
||||
// At this time, we should have called Uninstall() on the exception handler
|
||||
// so that the current exception ports are the ones that we should be
|
||||
// forwarding to.
|
||||
ExceptionParameters current; |
||||
|
||||
current.count = EXC_TYPES_COUNT; |
||||
mach_port_t current_task = mach_task_self(); |
||||
kern_return_t result = task_get_exception_ports(current_task, |
||||
s_exception_mask, |
||||
current.masks, |
||||
¤t.count, |
||||
current.ports, |
||||
current.behaviors, |
||||
current.flavors); |
||||
|
||||
// Find the first exception handler that matches the exception
|
||||
unsigned int found; |
||||
for (found = 0; found < current.count; ++found) { |
||||
if (current.masks[found] & (1 << exception)) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// Nothing to forward
|
||||
if (found == current.count) { |
||||
fprintf(stderr, "** No previous ports for forwarding!! \n"); |
||||
exit(KERN_FAILURE); |
||||
} |
||||
|
||||
mach_port_t target_port = current.ports[found]; |
||||
exception_behavior_t target_behavior = current.behaviors[found]; |
||||
thread_state_flavor_t target_flavor = current.flavors[found]; |
||||
|
||||
mach_msg_type_number_t thread_state_count = THREAD_STATE_MAX; |
||||
breakpad_thread_state_data_t thread_state; |
||||
switch (target_behavior) { |
||||
case EXCEPTION_DEFAULT: |
||||
result = exception_raise(target_port, failed_thread, task, exception, |
||||
code, code_count); |
||||
break; |
||||
|
||||
case EXCEPTION_STATE: |
||||
result = thread_get_state(failed_thread, target_flavor, thread_state, |
||||
&thread_state_count); |
||||
if (result == KERN_SUCCESS) |
||||
result = exception_raise_state(target_port, failed_thread, task, |
||||
exception, code, |
||||
code_count, &target_flavor, |
||||
thread_state, thread_state_count, |
||||
thread_state, &thread_state_count); |
||||
if (result == KERN_SUCCESS) |
||||
result = thread_set_state(failed_thread, target_flavor, thread_state, |
||||
thread_state_count); |
||||
break; |
||||
|
||||
case EXCEPTION_STATE_IDENTITY: |
||||
result = thread_get_state(failed_thread, target_flavor, thread_state, |
||||
&thread_state_count); |
||||
if (result == KERN_SUCCESS) |
||||
result = exception_raise_state_identity(target_port, failed_thread, |
||||
task, exception, code, |
||||
code_count, &target_flavor, |
||||
thread_state, |
||||
thread_state_count, |
||||
thread_state, |
||||
&thread_state_count); |
||||
if (result == KERN_SUCCESS) |
||||
result = thread_set_state(failed_thread, target_flavor, thread_state, |
||||
thread_state_count); |
||||
break; |
||||
|
||||
default: |
||||
fprintf(stderr, "** Unknown exception behavior\n"); |
||||
result = KERN_FAILURE; |
||||
break; |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
// Callback from exc_server()
|
||||
kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread, |
||||
mach_port_t task, |
||||
exception_type_t exception, |
||||
exception_data_t code, |
||||
mach_msg_type_number_t code_count) { |
||||
if (task != mach_task_self()) { |
||||
return KERN_FAILURE; |
||||
} |
||||
return ForwardException(task, failed_thread, exception, code, code_count); |
||||
} |
||||
|
||||
// static
|
||||
void *ExceptionHandler::WaitForMessage(void *exception_handler_class) { |
||||
ExceptionHandler *self = |
||||
reinterpret_cast<ExceptionHandler *>(exception_handler_class); |
||||
ExceptionMessage receive; |
||||
|
||||
// Wait for the exception info
|
||||
while (1) { |
||||
receive.header.msgh_local_port = self->handler_port_; |
||||
receive.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(receive)); |
||||
kern_return_t result = mach_msg(&(receive.header), |
||||
MACH_RCV_MSG | MACH_RCV_LARGE, 0, |
||||
receive.header.msgh_size, |
||||
self->handler_port_, |
||||
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
||||
|
||||
|
||||
if (result == KERN_SUCCESS) { |
||||
// Uninstall our handler so that we don't get in a loop if the process of
|
||||
// writing out a minidump causes an exception. However, if the exception
|
||||
// was caused by a fork'd process, don't uninstall things
|
||||
|
||||
// If the actual exception code is zero, then we're calling this handler
|
||||
// in a way that indicates that we want to either exit this thread or
|
||||
// generate a minidump
|
||||
//
|
||||
// While reporting, all threads (except this one) must be suspended
|
||||
// to avoid misleading stacks. If appropriate they will be resumed
|
||||
// afterwards.
|
||||
if (!receive.exception) { |
||||
// Don't touch self, since this message could have been sent
|
||||
// from its destructor.
|
||||
if (receive.header.msgh_id == kShutdownMessage) |
||||
return NULL; |
||||
|
||||
self->SuspendThreads(); |
||||
|
||||
#if USE_PROTECTED_ALLOCATIONS |
||||
if(gBreakpadAllocator) |
||||
gBreakpadAllocator->Unprotect(); |
||||
#endif |
||||
|
||||
mach_port_t thread = MACH_PORT_NULL; |
||||
int exception_type = 0; |
||||
int exception_code = 0; |
||||
if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) { |
||||
thread = receive.thread.name; |
||||
exception_type = EXC_BREAKPOINT; |
||||
#if defined (__i386__) || defined(__x86_64__) |
||||
exception_code = EXC_I386_BPT; |
||||
#elif defined (__ppc__) || defined (__ppc64__) |
||||
exception_code = EXC_PPC_BREAKPOINT; |
||||
#else |
||||
#error architecture not supported |
||||
#endif |
||||
} |
||||
|
||||
// Write out the dump and save the result for later retrieval
|
||||
self->last_minidump_write_result_ = |
||||
self->WriteMinidumpWithException(exception_type, exception_code, |
||||
0, thread, |
||||
false); |
||||
|
||||
#if USE_PROTECTED_ALLOCATIONS |
||||
if(gBreakpadAllocator) |
||||
gBreakpadAllocator->Protect(); |
||||
#endif |
||||
|
||||
self->ResumeThreads(); |
||||
|
||||
if (self->use_minidump_write_mutex_) |
||||
pthread_mutex_unlock(&self->minidump_write_mutex_); |
||||
} else { |
||||
// When forking a child process with the exception handler installed,
|
||||
// if the child crashes, it will send the exception back to the parent
|
||||
// process. The check for task == self_task() ensures that only
|
||||
// exceptions that occur in the parent process are caught and
|
||||
// processed. If the exception was not caused by this task, we
|
||||
// still need to call into the exception server and have it return
|
||||
// KERN_FAILURE (see breakpad_exception_raise) in order for the kernel
|
||||
// to move onto the host exception handler for the child task
|
||||
if (receive.task.name == mach_task_self()) { |
||||
self->SuspendThreads(); |
||||
|
||||
#if USE_PROTECTED_ALLOCATIONS |
||||
if(gBreakpadAllocator) |
||||
gBreakpadAllocator->Unprotect(); |
||||
#endif |
||||
|
||||
int subcode = 0; |
||||
if (receive.exception == EXC_BAD_ACCESS && receive.code_count > 1) |
||||
subcode = receive.code[1]; |
||||
|
||||
// Generate the minidump with the exception data.
|
||||
self->WriteMinidumpWithException(receive.exception, receive.code[0], |
||||
subcode, receive.thread.name, true); |
||||
|
||||
self->UninstallHandler(true); |
||||
|
||||
#if USE_PROTECTED_ALLOCATIONS |
||||
if(gBreakpadAllocator) |
||||
gBreakpadAllocator->Protect(); |
||||
#endif |
||||
} |
||||
// Pass along the exception to the server, which will setup the
|
||||
// message and call breakpad_exception_raise() and put the return
|
||||
// code into the reply.
|
||||
ExceptionReplyMessage reply; |
||||
if (!exc_server(&receive.header, &reply.header)) |
||||
exit(1); |
||||
|
||||
// Send a reply and exit
|
||||
result = mach_msg(&(reply.header), MACH_SEND_MSG, |
||||
reply.header.msgh_size, 0, MACH_PORT_NULL, |
||||
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
bool ExceptionHandler::InstallHandler() { |
||||
try { |
||||
#if USE_PROTECTED_ALLOCATIONS |
||||
previous_ = new (gBreakpadAllocator->Allocate(sizeof(ExceptionParameters)) ) |
||||
ExceptionParameters(); |
||||
#else |
||||
previous_ = new ExceptionParameters(); |
||||
#endif |
||||
|
||||
} |
||||
catch (std::bad_alloc) { |
||||
return false; |
||||
} |
||||
|
||||
// Save the current exception ports so that we can forward to them
|
||||
previous_->count = EXC_TYPES_COUNT; |
||||
mach_port_t current_task = mach_task_self(); |
||||
kern_return_t result = task_get_exception_ports(current_task, |
||||
s_exception_mask, |
||||
previous_->masks, |
||||
&previous_->count, |
||||
previous_->ports, |
||||
previous_->behaviors, |
||||
previous_->flavors); |
||||
|
||||
// Setup the exception ports on this task
|
||||
if (result == KERN_SUCCESS) |
||||
result = task_set_exception_ports(current_task, s_exception_mask, |
||||
handler_port_, EXCEPTION_DEFAULT, |
||||
THREAD_STATE_NONE); |
||||
|
||||
installed_exception_handler_ = (result == KERN_SUCCESS); |
||||
|
||||
return installed_exception_handler_; |
||||
} |
||||
|
||||
bool ExceptionHandler::UninstallHandler(bool in_exception) { |
||||
kern_return_t result = KERN_SUCCESS; |
||||
|
||||
if (installed_exception_handler_) { |
||||
mach_port_t current_task = mach_task_self(); |
||||
|
||||
// Restore the previous ports
|
||||
for (unsigned int i = 0; i < previous_->count; ++i) { |
||||
result = task_set_exception_ports(current_task, previous_->masks[i], |
||||
previous_->ports[i], |
||||
previous_->behaviors[i], |
||||
previous_->flavors[i]); |
||||
if (result != KERN_SUCCESS) |
||||
return false; |
||||
} |
||||
|
||||
// this delete should NOT happen if an exception just occurred!
|
||||
if (!in_exception) { |
||||
#if USE_PROTECTED_ALLOCATIONS |
||||
previous_->~ExceptionParameters(); |
||||
#else |
||||
delete previous_; |
||||
#endif |
||||
} |
||||
|
||||
previous_ = NULL; |
||||
installed_exception_handler_ = false; |
||||
} |
||||
|
||||
return result == KERN_SUCCESS; |
||||
} |
||||
|
||||
bool ExceptionHandler::Setup(bool install_handler) { |
||||
if (pthread_mutex_init(&minidump_write_mutex_, NULL)) |
||||
return false; |
||||
|
||||
// Create a receive right
|
||||
mach_port_t current_task = mach_task_self(); |
||||
kern_return_t result = mach_port_allocate(current_task, |
||||
MACH_PORT_RIGHT_RECEIVE, |
||||
&handler_port_); |
||||
// Add send right
|
||||
if (result == KERN_SUCCESS) |
||||
result = mach_port_insert_right(current_task, handler_port_, handler_port_, |
||||
MACH_MSG_TYPE_MAKE_SEND); |
||||
|
||||
if (install_handler && result == KERN_SUCCESS) |
||||
if (!InstallHandler()) |
||||
return false; |
||||
|
||||
if (result == KERN_SUCCESS) { |
||||
// Install the handler in its own thread, detached as we won't be joining.
|
||||
pthread_attr_t attr; |
||||
pthread_attr_init(&attr); |
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
||||
int thread_create_result = pthread_create(&handler_thread_, &attr, |
||||
&WaitForMessage, this); |
||||
pthread_attr_destroy(&attr); |
||||
result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS; |
||||
} |
||||
|
||||
return result == KERN_SUCCESS ? true : false; |
||||
} |
||||
|
||||
bool ExceptionHandler::Teardown() { |
||||
kern_return_t result = KERN_SUCCESS; |
||||
is_in_teardown_ = true; |
||||
|
||||
if (!UninstallHandler(false)) |
||||
return false; |
||||
|
||||
// Send an empty message so that the handler_thread exits
|
||||
if (SendMessageToHandlerThread(kShutdownMessage)) { |
||||
mach_port_t current_task = mach_task_self(); |
||||
result = mach_port_deallocate(current_task, handler_port_); |
||||
if (result != KERN_SUCCESS) |
||||
return false; |
||||
} else { |
||||
return false; |
||||
} |
||||
|
||||
handler_thread_ = NULL; |
||||
handler_port_ = NULL; |
||||
pthread_mutex_destroy(&minidump_write_mutex_); |
||||
|
||||
return result == KERN_SUCCESS; |
||||
} |
||||
|
||||
bool ExceptionHandler::SendMessageToHandlerThread( |
||||
HandlerThreadMessage message_id) { |
||||
ExceptionMessage msg; |
||||
memset(&msg, 0, sizeof(msg)); |
||||
msg.header.msgh_id = message_id; |
||||
if (message_id == kWriteDumpMessage || |
||||
message_id == kWriteDumpWithExceptionMessage) { |
||||
// Include this thread's port.
|
||||
msg.thread.name = mach_thread_self(); |
||||
msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND; |
||||
msg.thread.type = MACH_MSG_PORT_DESCRIPTOR; |
||||
} |
||||
msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding); |
||||
msg.header.msgh_remote_port = handler_port_; |
||||
msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, |
||||
MACH_MSG_TYPE_MAKE_SEND_ONCE); |
||||
kern_return_t result = mach_msg(&(msg.header), |
||||
MACH_SEND_MSG | MACH_SEND_TIMEOUT, |
||||
msg.header.msgh_size, 0, 0, |
||||
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
||||
|
||||
return result == KERN_SUCCESS; |
||||
} |
||||
|
||||
void ExceptionHandler::UpdateNextID() { |
||||
next_minidump_path_ = |
||||
(MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_)); |
||||
|
||||
next_minidump_path_c_ = next_minidump_path_.c_str(); |
||||
next_minidump_id_c_ = next_minidump_id_.c_str(); |
||||
} |
||||
|
||||
bool ExceptionHandler::SuspendThreads() { |
||||
thread_act_port_array_t threads_for_task; |
||||
mach_msg_type_number_t thread_count; |
||||
|
||||
if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) |
||||
return false; |
||||
|
||||
// suspend all of the threads except for this one
|
||||
for (unsigned int i = 0; i < thread_count; ++i) { |
||||
if (threads_for_task[i] != mach_thread_self()) { |
||||
if (thread_suspend(threads_for_task[i])) |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool ExceptionHandler::ResumeThreads() { |
||||
thread_act_port_array_t threads_for_task; |
||||
mach_msg_type_number_t thread_count; |
||||
|
||||
if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) |
||||
return false; |
||||
|
||||
// resume all of the threads except for this one
|
||||
for (unsigned int i = 0; i < thread_count; ++i) { |
||||
if (threads_for_task[i] != mach_thread_self()) { |
||||
if (thread_resume(threads_for_task[i])) |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
} // namespace google_breakpad
|
@ -1,259 +0,0 @@ |
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// exception_handler.h: MacOS exception handler
|
||||
// This class can install a Mach exception port handler to trap most common
|
||||
// programming errors. If an exception occurs, a minidump file will be
|
||||
// generated which contains detailed information about the process and the
|
||||
// exception.
|
||||
|
||||
#ifndef CLIENT_MAC_HANDLER_EXCEPTION_HANDLER_H__ |
||||
#define CLIENT_MAC_HANDLER_EXCEPTION_HANDLER_H__ |
||||
|
||||
#include <mach/mach.h> |
||||
|
||||
#include <string> |
||||
|
||||
#include "client/mac/crash_generation/crash_generation_client.h" |
||||
#include "processor/scoped_ptr.h" |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
using std::string; |
||||
|
||||
struct ExceptionParameters; |
||||
|
||||
enum HandlerThreadMessage { |
||||
// Message ID telling the handler thread to write a dump.
|
||||
kWriteDumpMessage = 0, |
||||
// Message ID telling the handler thread to write a dump and include
|
||||
// an exception stream.
|
||||
kWriteDumpWithExceptionMessage = 1, |
||||
// Message ID telling the handler thread to quit.
|
||||
kShutdownMessage = 2 |
||||
}; |
||||
|
||||
class ExceptionHandler { |
||||
public: |
||||
// A callback function to run before Breakpad performs any substantial
|
||||
// processing of an exception. A FilterCallback is called before writing
|
||||
// a minidump. context is the parameter supplied by the user as
|
||||
// callback_context when the handler was created.
|
||||
//
|
||||
// If a FilterCallback returns true, Breakpad will continue processing,
|
||||
// attempting to write a minidump. If a FilterCallback returns false, Breakpad
|
||||
// will immediately report the exception as unhandled without writing a
|
||||
// minidump, allowing another handler the opportunity to handle it.
|
||||
typedef bool (*FilterCallback)(void *context); |
||||
|
||||
// A callback function to run after the minidump has been written.
|
||||
// |minidump_id| is a unique id for the dump, so the minidump
|
||||
// file is <dump_dir>/<minidump_id>.dmp.
|
||||
// |context| is the value passed into the constructor.
|
||||
// |succeeded| indicates whether a minidump file was successfully written.
|
||||
// Return true if the exception was fully handled and breakpad should exit.
|
||||
// Return false to allow any other exception handlers to process the
|
||||
// exception.
|
||||
typedef bool (*MinidumpCallback)(const char *dump_dir, |
||||
const char *minidump_id, |
||||
void *context, bool succeeded); |
||||
|
||||
// A callback function which will be called directly if an exception occurs.
|
||||
// This bypasses the minidump file writing and simply gives the client
|
||||
// the exception information.
|
||||
typedef bool (*DirectCallback)( void *context, |
||||
int exception_type, |
||||
int exception_code, |
||||
int exception_subcode, |
||||
mach_port_t thread_name); |
||||
|
||||
// Creates a new ExceptionHandler instance to handle writing minidumps.
|
||||
// Minidump files will be written to dump_path, and the optional callback
|
||||
// is called after writing the dump file, as described above.
|
||||
// If install_handler is true, then a minidump will be written whenever
|
||||
// an unhandled exception occurs. If it is false, minidumps will only
|
||||
// be written when WriteMinidump is called.
|
||||
// If port_name is non-NULL, attempt to perform out-of-process dump generation
|
||||
// If port_name is NULL, in-process dump generation will be used.
|
||||
ExceptionHandler(const string &dump_path, |
||||
FilterCallback filter, MinidumpCallback callback, |
||||
void *callback_context, bool install_handler, |
||||
const char *port_name); |
||||
|
||||
// A special constructor if we want to bypass minidump writing and
|
||||
// simply get a callback with the exception information.
|
||||
ExceptionHandler(DirectCallback callback, |
||||
void *callback_context, |
||||
bool install_handler); |
||||
|
||||
~ExceptionHandler(); |
||||
|
||||
// Get and set the minidump path.
|
||||
string dump_path() const { return dump_path_; } |
||||
void set_dump_path(const string &dump_path) { |
||||
dump_path_ = dump_path; |
||||
dump_path_c_ = dump_path_.c_str(); |
||||
UpdateNextID(); // Necessary to put dump_path_ in next_minidump_path_.
|
||||
} |
||||
|
||||
// Writes a minidump immediately. This can be used to capture the
|
||||
// execution state independently of a crash. Returns true on success.
|
||||
bool WriteMinidump() { |
||||
return WriteMinidump(false); |
||||
} |
||||
|
||||
bool WriteMinidump(bool write_exception_stream); |
||||
|
||||
// Convenience form of WriteMinidump which does not require an
|
||||
// ExceptionHandler instance.
|
||||
static bool WriteMinidump(const string &dump_path, MinidumpCallback callback, |
||||
void *callback_context) { |
||||
return WriteMinidump(dump_path, false, callback, callback_context); |
||||
} |
||||
|
||||
static bool WriteMinidump(const string &dump_path, |
||||
bool write_exception_stream, |
||||
MinidumpCallback callback, |
||||
void *callback_context); |
||||
|
||||
// Write a minidump of child immediately. This can be used to capture
|
||||
// the execution state of a child process independently of a crash.
|
||||
static bool WriteMinidumpForChild(mach_port_t child, |
||||
mach_port_t child_blamed_thread, |
||||
const std::string &dump_path, |
||||
MinidumpCallback callback, |
||||
void *callback_context); |
||||
|
||||
// Returns whether out-of-process dump generation is used or not.
|
||||
bool IsOutOfProcess() const { |
||||
return crash_generation_client_.get() != NULL; |
||||
} |
||||
|
||||
private: |
||||
// Install the mach exception handler
|
||||
bool InstallHandler(); |
||||
|
||||
// Uninstall the mach exception handler (if any)
|
||||
bool UninstallHandler(bool in_exception); |
||||
|
||||
// Setup the handler thread, and if |install_handler| is true, install the
|
||||
// mach exception port handler
|
||||
bool Setup(bool install_handler); |
||||
|
||||
// Uninstall the mach exception handler (if any) and terminate the helper
|
||||
// thread
|
||||
bool Teardown(); |
||||
|
||||
// Send a mach message to the exception handler. Return true on
|
||||
// success, false otherwise.
|
||||
bool SendMessageToHandlerThread(HandlerThreadMessage message_id); |
||||
|
||||
// All minidump writing goes through this one routine
|
||||
bool WriteMinidumpWithException(int exception_type, |
||||
int exception_code, |
||||
int exception_subcode, |
||||
mach_port_t thread_name, |
||||
bool exit_after_write); |
||||
|
||||
// When installed, this static function will be call from a newly created
|
||||
// pthread with |this| as the argument
|
||||
static void *WaitForMessage(void *exception_handler_class); |
||||
|
||||
// disallow copy ctor and operator=
|
||||
explicit ExceptionHandler(const ExceptionHandler &); |
||||
void operator=(const ExceptionHandler &); |
||||
|
||||
// Generates a new ID and stores it in next_minidump_id_, and stores the
|
||||
// path of the next minidump to be written in next_minidump_path_.
|
||||
void UpdateNextID(); |
||||
|
||||
// These functions will suspend/resume all threads except for the
|
||||
// reporting thread
|
||||
bool SuspendThreads(); |
||||
bool ResumeThreads(); |
||||
|
||||
// The destination directory for the minidump
|
||||
string dump_path_; |
||||
|
||||
// The basename of the next minidump w/o extension
|
||||
string next_minidump_id_; |
||||
|
||||
// The full path to the next minidump to be written, including extension
|
||||
string next_minidump_path_; |
||||
|
||||
// Pointers to the UTF-8 versions of above
|
||||
const char *dump_path_c_; |
||||
const char *next_minidump_id_c_; |
||||
const char *next_minidump_path_c_; |
||||
|
||||
// The callback function and pointer to be passed back after the minidump
|
||||
// has been written
|
||||
FilterCallback filter_; |
||||
MinidumpCallback callback_; |
||||
void *callback_context_; |
||||
|
||||
// The callback function to be passed back when we don't want a minidump
|
||||
// file to be written
|
||||
DirectCallback directCallback_; |
||||
|
||||
// The thread that is created for the handler
|
||||
pthread_t handler_thread_; |
||||
|
||||
// The port that is waiting on an exception message to be sent, if the
|
||||
// handler is installed
|
||||
mach_port_t handler_port_; |
||||
|
||||
// These variables save the previous exception handler's data so that it
|
||||
// can be re-installed when this handler is uninstalled
|
||||
ExceptionParameters *previous_; |
||||
|
||||
// True, if we've installed the exception handler
|
||||
bool installed_exception_handler_; |
||||
|
||||
// True, if we're in the process of uninstalling the exception handler and
|
||||
// the thread.
|
||||
bool is_in_teardown_; |
||||
|
||||
// Save the last result of the last minidump
|
||||
bool last_minidump_write_result_; |
||||
|
||||
// A mutex for use when writing out a minidump that was requested on a
|
||||
// thread other than the exception handler.
|
||||
pthread_mutex_t minidump_write_mutex_; |
||||
|
||||
// True, if we're using the mutext to indicate when mindump writing occurs
|
||||
bool use_minidump_write_mutex_; |
||||
|
||||
// Client for out-of-process dump generation.
|
||||
scoped_ptr<CrashGenerationClient> crash_generation_client_; |
||||
}; |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_MAC_HANDLER_EXCEPTION_HANDLER_H__
|
File diff suppressed because it is too large
Load Diff
@ -1,191 +0,0 @@ |
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// minidump_generator.h: Create a minidump of the current MacOS process.
|
||||
|
||||
#ifndef CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__ |
||||
#define CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__ |
||||
|
||||
#include <mach/mach.h> |
||||
|
||||
#include <string> |
||||
|
||||
#include "client/minidump_file_writer.h" |
||||
#include "common/memory.h" |
||||
#include "common/mac/macho_utilities.h" |
||||
#include "google_breakpad/common/minidump_format.h" |
||||
|
||||
#include "dynamic_images.h" |
||||
|
||||
namespace google_breakpad { |
||||
|
||||
using std::string; |
||||
|
||||
const u_int64_t TOP_OF_THREAD0_STACK_64BIT = 0x00007fff5fbff000LL; |
||||
const u_int32_t TOP_OF_THREAD0_STACK_32BIT = 0xbffff000; |
||||
|
||||
// Use the REGISTER_FROM_THREADSTATE to access a register name from the
|
||||
// breakpad_thread_state_t structure.
|
||||
#if __DARWIN_UNIX03 || TARGET_CPU_X86_64 || TARGET_CPU_PPC64 |
||||
// In The 10.5 SDK Headers Apple prepended __ to the variable names in the
|
||||
// i386_thread_state_t structure. There's no good way to tell what version of
|
||||
// the SDK we're compiling against so we just toggle on the same preprocessor
|
||||
// symbol Apple's headers use.
|
||||
#define REGISTER_FROM_THREADSTATE(a, b) ((a)->__ ## b) |
||||
#else |
||||
#define REGISTER_FROM_THREADSTATE(a, b) (a->b) |
||||
#endif |
||||
|
||||
// Creates a minidump file of the current process. If there is exception data,
|
||||
// use SetExceptionInformation() to add this to the minidump. The minidump
|
||||
// file is generated by the Write() function.
|
||||
// Usage:
|
||||
// MinidumpGenerator minidump();
|
||||
// minidump.Write("/tmp/minidump");
|
||||
//
|
||||
class MinidumpGenerator { |
||||
public: |
||||
MinidumpGenerator(); |
||||
MinidumpGenerator(mach_port_t crashing_task, mach_port_t handler_thread); |
||||
|
||||
~MinidumpGenerator(); |
||||
|
||||
// Return <dir>/<unique_name>.dmp
|
||||
// Sets |unique_name| (if requested) to the unique name for the minidump
|
||||
static string UniqueNameInDirectory(const string &dir, string *unique_name); |
||||
|
||||
// Write out the minidump into |path|
|
||||
// All of the components of |path| must exist and be writable
|
||||
// Return true if successful, false otherwise
|
||||
bool Write(const char *path); |
||||
|
||||
// Specify some exception information, if applicable
|
||||
void SetExceptionInformation(int type, int code, int subcode, |
||||
mach_port_t thread_name) { |
||||
exception_type_ = type; |
||||
exception_code_ = code; |
||||
exception_subcode_ = subcode; |
||||
exception_thread_ = thread_name; |
||||
} |
||||
|
||||
// Gather system information. This should be call at least once before using
|
||||
// the MinidumpGenerator class.
|
||||
static void GatherSystemInformation(); |
||||
|
||||
private: |
||||
typedef bool (MinidumpGenerator::*WriteStreamFN)(MDRawDirectory *); |
||||
|
||||
// Stream writers
|
||||
bool WriteThreadListStream(MDRawDirectory *thread_list_stream); |
||||
bool WriteMemoryListStream(MDRawDirectory *memory_list_stream); |
||||
bool WriteExceptionStream(MDRawDirectory *exception_stream); |
||||
bool WriteSystemInfoStream(MDRawDirectory *system_info_stream); |
||||
bool WriteModuleListStream(MDRawDirectory *module_list_stream); |
||||
bool WriteMiscInfoStream(MDRawDirectory *misc_info_stream); |
||||
bool WriteBreakpadInfoStream(MDRawDirectory *breakpad_info_stream); |
||||
|
||||
// Helpers
|
||||
u_int64_t CurrentPCForStack(breakpad_thread_state_data_t state); |
||||
bool GetThreadState(thread_act_t target_thread, thread_state_t state, |
||||
mach_msg_type_number_t *count); |
||||
bool WriteStackFromStartAddress(mach_vm_address_t start_addr, |
||||
MDMemoryDescriptor *stack_location); |
||||
bool WriteStack(breakpad_thread_state_data_t state, |
||||
MDMemoryDescriptor *stack_location); |
||||
bool WriteContext(breakpad_thread_state_data_t state, |
||||
MDLocationDescriptor *register_location); |
||||
bool WriteThreadStream(mach_port_t thread_id, MDRawThread *thread); |
||||
bool WriteCVRecord(MDRawModule *module, int cpu_type,
|
||||
const char *module_path); |
||||
bool WriteModuleStream(unsigned int index, MDRawModule *module); |
||||
size_t CalculateStackSize(mach_vm_address_t start_addr); |
||||
int FindExecutableModule(); |
||||
|
||||
// Per-CPU implementations of these methods
|
||||
bool WriteStackPPC(breakpad_thread_state_data_t state, |
||||
MDMemoryDescriptor *stack_location); |
||||
bool WriteContextPPC(breakpad_thread_state_data_t state, |
||||
MDLocationDescriptor *register_location); |
||||
u_int64_t CurrentPCForStackPPC(breakpad_thread_state_data_t state); |
||||
bool WriteStackPPC64(breakpad_thread_state_data_t state, |
||||
MDMemoryDescriptor *stack_location); |
||||
bool WriteContextPPC64(breakpad_thread_state_data_t state, |
||||
MDLocationDescriptor *register_location); |
||||
u_int64_t CurrentPCForStackPPC64(breakpad_thread_state_data_t state); |
||||
bool WriteStackX86(breakpad_thread_state_data_t state, |
||||
MDMemoryDescriptor *stack_location); |
||||
bool WriteContextX86(breakpad_thread_state_data_t state, |
||||
MDLocationDescriptor *register_location); |
||||
u_int64_t CurrentPCForStackX86(breakpad_thread_state_data_t state); |
||||
bool WriteStackX86_64(breakpad_thread_state_data_t state, |
||||
MDMemoryDescriptor *stack_location); |
||||
bool WriteContextX86_64(breakpad_thread_state_data_t state, |
||||
MDLocationDescriptor *register_location); |
||||
u_int64_t CurrentPCForStackX86_64(breakpad_thread_state_data_t state); |
||||
|
||||
// disallow copy ctor and operator=
|
||||
explicit MinidumpGenerator(const MinidumpGenerator &); |
||||
void operator=(const MinidumpGenerator &); |
||||
|
||||
// Use this writer to put the data to disk
|
||||
MinidumpFileWriter writer_; |
||||
|
||||
// Exception information
|
||||
int exception_type_; |
||||
int exception_code_; |
||||
int exception_subcode_; |
||||
mach_port_t exception_thread_; |
||||
mach_port_t crashing_task_; |
||||
mach_port_t handler_thread_; |
||||
|
||||
// CPU type of the task being dumped.
|
||||
cpu_type_t cpu_type_; |
||||
|
||||
// System information
|
||||
static char build_string_[16]; |
||||
static int os_major_version_; |
||||
static int os_minor_version_; |
||||
static int os_build_number_; |
||||
|
||||
// Information about dynamically loaded code
|
||||
DynamicImages *dynamic_images_; |
||||
|
||||
// PageAllocator makes it possible to allocate memory
|
||||
// directly from the system, even while handling an exception.
|
||||
mutable PageAllocator allocator_; |
||||
|
||||
// Blocks of memory written to the dump. These are all currently
|
||||
// written while writing the thread list stream, but saved here
|
||||
// so a memory list stream can be written afterwards.
|
||||
wasteful_vector<MDMemoryDescriptor> memory_blocks_; |
||||
}; |
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__
|
@ -1,20 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>CFBundleDevelopmentRegion</key> |
||||
<string>English</string> |
||||
<key>CFBundleExecutable</key> |
||||
<string>${EXECUTABLE_NAME}</string> |
||||
<key>CFBundleIdentifier</key> |
||||
<string>com.google.breakpad.minidump_tests32</string> |
||||
<key>CFBundleInfoDictionaryVersion</key> |
||||
<string>6.0</string> |
||||
<key>CFBundlePackageType</key> |
||||
<string>BNDL</string> |
||||
<key>CFBundleSignature</key> |
||||
<string>????</string> |
||||
<key>CFBundleVersion</key> |
||||
<string>1.0</string> |
||||
</dict> |
||||
</plist> |
@ -1,22 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>CFBundleDevelopmentRegion</key> |
||||
<string>English</string> |
||||
<key>CFBundleExecutable</key> |
||||
<string>${EXECUTABLE_NAME}</string> |
||||
<key>CFBundleIdentifier</key> |
||||
<string>com.google.breakpad.minidump_tests64</string> |
||||
<key>CFBundleInfoDictionaryVersion</key> |
||||
<string>6.0</string> |
||||
<key>CFBundlePackageType</key> |
||||
<string>BNDL</string> |
||||
<key>CFBundleSignature</key> |
||||
<string>????</string> |
||||
<key>CFBundleVersion</key> |
||||
<string>1.0</string> |
||||
<key>CSResourcesFileMapped</key> |
||||
<string>yes</string> |
||||
</dict> |
||||
</plist> |
@ -1,20 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>CFBundleDevelopmentRegion</key> |
||||
<string>English</string> |
||||
<key>CFBundleExecutable</key> |
||||
<string>${EXECUTABLE_NAME}</string> |
||||
<key>CFBundleIdentifier</key> |
||||
<string>com.yourcompany.${PRODUCT_NAME:identifier}</string> |
||||
<key>CFBundleInfoDictionaryVersion</key> |
||||
<string>6.0</string> |
||||
<key>CFBundlePackageType</key> |
||||
<string>BNDL</string> |
||||
<key>CFBundleSignature</key> |
||||
<string>????</string> |
||||
<key>CFBundleVersion</key> |
||||
<string>1.0</string> |
||||
</dict> |
||||
</plist> |
@ -1,92 +0,0 @@ |
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// ProtectedMemoryAllocator
|
||||
//
|
||||
// See the header file for documentation
|
||||
|
||||
#include "protected_memory_allocator.h" |
||||
#include <assert.h> |
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
ProtectedMemoryAllocator::ProtectedMemoryAllocator(vm_size_t pool_size)
|
||||
: pool_size_(pool_size), |
||||
next_alloc_offset_(0), |
||||
valid_(false) { |
||||
|
||||
kern_return_t result = vm_allocate(mach_task_self(), |
||||
&base_address_, |
||||
pool_size, |
||||
TRUE |
||||
); |
||||
|
||||
valid_ = (result == KERN_SUCCESS); |
||||
assert(valid_); |
||||
} |
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
ProtectedMemoryAllocator::~ProtectedMemoryAllocator() { |
||||
vm_deallocate(mach_task_self(), |
||||
base_address_, |
||||
pool_size_ |
||||
); |
||||
} |
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
char *ProtectedMemoryAllocator::Allocate(vm_size_t bytes) { |
||||
if (valid_ && next_alloc_offset_ + bytes <= pool_size_) { |
||||
char *p = (char*)base_address_ + next_alloc_offset_; |
||||
next_alloc_offset_ += bytes; |
||||
return p; |
||||
} |
||||
|
||||
return NULL; // ran out of memory in our allocation block
|
||||
} |
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
kern_return_t ProtectedMemoryAllocator::Protect() { |
||||
kern_return_t result = vm_protect(mach_task_self(), |
||||
base_address_, |
||||
pool_size_, |
||||
FALSE, |
||||
VM_PROT_READ); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
kern_return_t ProtectedMemoryAllocator::Unprotect() { |
||||
kern_return_t result = vm_protect(mach_task_self(), |
||||
base_address_, |
||||
pool_size_, |
||||
FALSE, |
||||
VM_PROT_READ | VM_PROT_WRITE); |
||||
|
||||
return result; |
||||
} |
@ -1,85 +0,0 @@ |
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// ProtectedMemoryAllocator
|
||||
//
|
||||
// A very simple allocator class which allows allocation, but not deallocation.
|
||||
// The allocations can be made read-only with the Protect() method.
|
||||
// This class is NOT useful as a general-purpose memory allocation system,
|
||||
// since it does not allow deallocation. It is useful to use for a group
|
||||
// of allocations which are created in the same time-frame and destroyed
|
||||
// in the same time-frame. It is useful for making allocations of memory
|
||||
// which will not need to change often once initialized. This memory can then
|
||||
// be protected from memory smashers by calling the Protect() method.
|
||||
|
||||
#ifndef PROTECTED_MEMORY_ALLOCATOR_H__ |
||||
#define PROTECTED_MEMORY_ALLOCATOR_H__ |
||||
|
||||
#include <mach/mach.h> |
||||
|
||||
//
|
||||
class ProtectedMemoryAllocator { |
||||
public: |
||||
ProtectedMemoryAllocator(vm_size_t pool_size);
|
||||
~ProtectedMemoryAllocator(); |
||||
|
||||
// Returns a pointer to an allocation of size n within the pool.
|
||||
// Fails by returning NULL is no more space is available.
|
||||
// Please note that the pointers returned from this method should not
|
||||
// be freed in any way (for example by calling free() on them ).
|
||||
char * Allocate(vm_size_t n); |
||||
|
||||
// Returns the base address of the allocation pool.
|
||||
char * GetBaseAddress() { return (char*)base_address_; } |
||||
|
||||
// Returns the size of the allocation pool, including allocated
|
||||
// plus free space.
|
||||
vm_size_t GetTotalSize() { return pool_size_; } |
||||
|
||||
// Returns the number of bytes already allocated in the pool.
|
||||
vm_size_t GetAllocatedSize() { return next_alloc_offset_; } |
||||
|
||||
// Returns the number of bytes available for allocation.
|
||||
vm_size_t GetFreeSize() { return pool_size_ - next_alloc_offset_; } |
||||
|
||||
// Makes the entire allocation pool read-only including, of course,
|
||||
// all allocations made from the pool.
|
||||
kern_return_t Protect();
|
||||
|
||||
// Makes the entire allocation pool read/write.
|
||||
kern_return_t Unprotect();
|
||||
|
||||
private: |
||||
vm_size_t pool_size_; |
||||
vm_address_t base_address_; |
||||
vm_size_t next_alloc_offset_; |
||||
bool valid_; |
||||
}; |
||||
|
||||
#endif // PROTECTED_MEMORY_ALLOCATOR_H__
|
@ -1,85 +0,0 @@ |
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// DynamicImagesTests.cpp
|
||||
// minidump_test
|
||||
//
|
||||
// Created by Neal Sidhwaney on 4/17/08.
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include "client/mac/handler/testcases/DynamicImagesTests.h" |
||||
#include "client/mac/handler/dynamic_images.h" |
||||
|
||||
DynamicImagesTests test2(TEST_INVOCATION(DynamicImagesTests, |
||||
ReadTaskMemoryTest)); |
||||
DynamicImagesTests test3(TEST_INVOCATION(DynamicImagesTests, |
||||
ReadLibrariesFromLocalTaskTest)); |
||||
|
||||
DynamicImagesTests::DynamicImagesTests(TestInvocation *invocation) |
||||
: TestCase(invocation) { |
||||
} |
||||
|
||||
DynamicImagesTests::~DynamicImagesTests() { |
||||
} |
||||
|
||||
void DynamicImagesTests::ReadTaskMemoryTest() { |
||||
kern_return_t kr; |
||||
|
||||
// pick test2 as a symbol we know to be valid to read
|
||||
// anything will work, really
|
||||
void *addr = reinterpret_cast<void*>(&test2); |
||||
void *buf; |
||||
|
||||
fprintf(stderr, "reading 0x%p\n", addr); |
||||
buf = google_breakpad::ReadTaskMemory(mach_task_self(), |
||||
addr, |
||||
getpagesize(), |
||||
&kr); |
||||
|
||||
CPTAssert(kr == KERN_SUCCESS); |
||||
|
||||
CPTAssert(buf != NULL); |
||||
|
||||
CPTAssert(0 == memcmp(buf, (const void*)addr, getpagesize())); |
||||
|
||||
free(buf); |
||||
} |
||||
|
||||
void DynamicImagesTests::ReadLibrariesFromLocalTaskTest() { |
||||
|
||||
mach_port_t me = mach_task_self(); |
||||
google_breakpad::DynamicImages *d = new google_breakpad::DynamicImages(me); |
||||
|
||||
fprintf(stderr,"Local task image count: %d\n", d->GetImageCount()); |
||||
|
||||
d->TestPrint(); |
||||
|
||||
CPTAssert(d->GetImageCount() > 0); |
||||
} |
@ -1,52 +0,0 @@ |
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// DynamicImagesTests.h
|
||||
// minidump_test
|
||||
//
|
||||
// Created by Neal Sidhwaney on 4/17/08.
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef _CLIENT_MAC_HANDLER_TESTCASES_DYNAMICIMAGESTESTS_H__ |
||||
#define _CLIENT_MAC_HANDLER_TESTCASES_DYNAMICIMAGESTESTS_H__ |
||||
|
||||
#include <CPlusTest/CPlusTest.h> |
||||
|
||||
class DynamicImagesTests : public TestCase { |
||||
public: |
||||
explicit DynamicImagesTests(TestInvocation* invocation); |
||||
virtual ~DynamicImagesTests(); |
||||
|
||||
void ReadTaskMemoryTest(); |
||||
void ReadLibrariesFromLocalTaskTest(); |
||||
}; |
||||
|
||||
#endif /* _CLIENT_MAC_HANDLER_TESTCASES_DYNAMICIMAGESTESTS_H__ */ |
@ -1,106 +0,0 @@ |
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// breakpad_nlist_test.cc
|
||||
// minidump_test
|
||||
//
|
||||
// Created by Neal Sidhwaney on 4/13/08.
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include "client/mac/handler/testcases/breakpad_nlist_test.h" |
||||
#include <mach-o/nlist.h> |
||||
#include "client/mac/handler/breakpad_nlist_64.h" |
||||
|
||||
BreakpadNlistTest test1(TEST_INVOCATION(BreakpadNlistTest, CompareToNM)); |
||||
|
||||
BreakpadNlistTest::BreakpadNlistTest(TestInvocation *invocation) |
||||
: TestCase(invocation) { |
||||
} |
||||
|
||||
|
||||
BreakpadNlistTest::~BreakpadNlistTest() { |
||||
} |
||||
|
||||
void BreakpadNlistTest::CompareToNM() { |
||||
#if TARGET_CPU_X86_64 |
||||
system("/usr/bin/nm -arch x86_64 /usr/lib/dyld > /tmp/dyld-namelist.txt"); |
||||
#elif TARGET_CPU_PPC64 |
||||
system("/usr/bin/nm -arch ppc64 /usr/lib/dyld > /tmp/dyld-namelist.txt"); |
||||
#endif |
||||
|
||||
FILE *fd = fopen("/tmp/dyld-namelist.txt", "rt"); |
||||
|
||||
char oneNMAddr[30]; |
||||
char symbolType; |
||||
char symbolName[500]; |
||||
while (!feof(fd)) { |
||||
fscanf(fd, "%s %c %s", oneNMAddr, &symbolType, symbolName); |
||||
breakpad_nlist symbolList[2]; |
||||
breakpad_nlist &list = symbolList[0]; |
||||
|
||||
memset(symbolList, 0, sizeof(breakpad_nlist)*2); |
||||
const char *symbolNames[2]; |
||||
symbolNames[0] = (const char*)symbolName; |
||||
symbolNames[1] = "\0"; |
||||
breakpad_nlist_64("/usr/lib/dyld", &list, symbolNames); |
||||
uint64_t nmAddr = strtol(oneNMAddr, NULL, 16); |
||||
if (!IsSymbolMoreThanOnceInDyld(symbolName)) { |
||||
CPTAssert(nmAddr == symbolList[0].n_value); |
||||
} |
||||
} |
||||
|
||||
fclose(fd); |
||||
} |
||||
|
||||
bool BreakpadNlistTest::IsSymbolMoreThanOnceInDyld(const char *symbolName) { |
||||
// These are the symbols that occur more than once when nm dumps
|
||||
// the symbol table of /usr/lib/dyld. Our nlist program returns
|
||||
// the first address because it's doing a search so we need to exclude
|
||||
// these from causing the test to fail
|
||||
const char *multipleSymbols[] = { |
||||
"__Z41__static_initialization_and_destruction_0ii", |
||||
"___tcf_0", |
||||
"___tcf_1", |
||||
"_read_encoded_value_with_base", |
||||
"_read_sleb128", |
||||
"_read_uleb128", |
||||
"\0"}; |
||||
|
||||
bool found = false; |
||||
|
||||
for (int i = 0; multipleSymbols[i][0]; i++) { |
||||
if (!strcmp(multipleSymbols[i], symbolName)) { |
||||
found = true; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return found; |
||||
} |
@ -1,62 +0,0 @@ |
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// breakpad_nlist_test.h
|
||||
// minidump_test
|
||||
//
|
||||
// Created by Neal Sidhwaney on 4/13/08.
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef CLIENT_MAC_HANDLER_TESTCASES_BREAKPAD_NLIST_TEST_H__ |
||||
#define CLIENT_MAC_HANDLER_TESTCASES_BREAKPAD_NLIST_TEST_H__ |
||||
|
||||
#include <CPlusTest/CPlusTest.h> |
||||
|
||||
class BreakpadNlistTest : public TestCase { |
||||
private: |
||||
|
||||
// nm dumps multiple addresses for the same symbol in
|
||||
// /usr/lib/dyld. So we track those so we don't report failures
|
||||
// in mismatches between what our nlist returns and what nm has
|
||||
// for the duplicate symbols.
|
||||
bool IsSymbolMoreThanOnceInDyld(const char *symbolName); |
||||
|
||||
public: |
||||
explicit BreakpadNlistTest(TestInvocation* invocation); |
||||
virtual ~BreakpadNlistTest(); |
||||
|
||||
|
||||
/* This test case runs nm on /usr/lib/dyld and then compares the
|
||||
output of every symbol to what our nlist implementation returns */ |
||||
void CompareToNM(); |
||||
}; |
||||
|
||||
#endif /* CLIENT_MAC_HANDLER_TESTCASES_BREAKPAD_NLIST_TEST_H__*/ |
@ -1,46 +0,0 @@ |
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// dwarftests.h
|
||||
// minidump_test
|
||||
//
|
||||
// Created by Neal Sidhwaney on 9/24/08.
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <SenTestingKit/SenTestingKit.h> |
||||
|
||||
|
||||
@interface dwarftests : SenTestCase { |
||||
|
||||
} |
||||
|
||||
- (void) testDWARFSymbolFileGeneration; |
||||
|
||||
@end |
@ -1,60 +0,0 @@ |
||||
// Copyright (c) 2008, Google Inc. |
||||
// All rights reserved |
||||
// Redistribution and use in source and binary forms, with or without |
||||
// modification, are permitted provided that the following conditions are |
||||
// met: |
||||
// |
||||
// * Redistributions of source code must retain the above copyright |
||||
// notice, this list of conditions and the following disclaimer. |
||||
// * Redistributions in binary form must reproduce the above |
||||
// copyright notice, this list of conditions and the following disclaimer |
||||
// in the documentation and/or other materials provided with the |
||||
// distribution. |
||||
// * Neither the name of Google Inc. nor the names of its |
||||
// contributors may be used to endorse or promote products derived from |
||||
// this software without specific prior written permission. |
||||
// |
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
// |
||||
// dwarftests.m |
||||
// minidump_test |
||||
// |
||||
// Created by Neal Sidhwaney on 9/24/08. |
||||
// Copyright 2008 Google Inc. All rights reserved. |
||||
// |
||||
|
||||
#import "dwarftests.h" |
||||
#import "dump_syms.h" |
||||
|
||||
@implementation dwarftests |
||||
- (void) testDWARFSymbolFileGeneration { |
||||
NSString *inputBreakpadSymbolFile = @"testcases/testdata/dump_syms_i386_breakpad.sym"; |
||||
NSString *outputBreakpadSymbolFile = @"/tmp/dump_syms_i386.breakpad"; |
||||
|
||||
DumpSymbols *dump = [[DumpSymbols alloc] initWithContentsOfFile:@"testcases/testdata/dump_syms_dwarf_data"]; |
||||
|
||||
STAssertNotNil(dump, @"DumpSymbols is nil"); |
||||
[dump setArchitecture:@"i386"]; |
||||
[dump writeSymbolFile:outputBreakpadSymbolFile]; |
||||
|
||||
NSData *d = [[NSData alloc] initWithContentsOfFile:inputBreakpadSymbolFile]; |
||||
STAssertNotNil(d, @"Input breakpad symbol file not found"); |
||||
|
||||
NSData *d1 = [[NSData alloc] initWithContentsOfFile:outputBreakpadSymbolFile]; |
||||
STAssertNotNil(d1, @"Output breakpad symbol file not found"); |
||||
|
||||
STAssertTrue([d isEqualToData:d1], |
||||
@"Symbol files were not equal!",nil); |
||||
} |
||||
@end |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue