feat:增加crash;单独的路径工具
This commit is contained in:
parent
76b63103af
commit
6626ee33e5
@ -1,24 +1,24 @@
|
||||
#ifndef LASER_DATA_LOADER_H
|
||||
#define LASER_DATA_LOADER_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#ifndef LASER_DATA_LOADER_H
|
||||
#define LASER_DATA_LOADER_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "VZNL_Types.h"
|
||||
#include "VrError.h"
|
||||
#include "PointCloudXYZRange.h"
|
||||
|
||||
// 保存进度回调接口
|
||||
class ISaveProgressCallback
|
||||
{
|
||||
public:
|
||||
virtual ~ISaveProgressCallback() = default;
|
||||
// 进度回调,progress 范围 0.0~1.0
|
||||
virtual void onSaveProgress(float progress) = 0;
|
||||
// 返回 true 取消保存
|
||||
virtual bool isSaveCancelled() const { return false; }
|
||||
};
|
||||
|
||||
|
||||
// 保存进度回调接口
|
||||
class ISaveProgressCallback
|
||||
{
|
||||
public:
|
||||
virtual ~ISaveProgressCallback() = default;
|
||||
// 进度回调,progress 范围 0.0~1.0
|
||||
virtual void onSaveProgress(float progress) = 0;
|
||||
// 返回 true 取消保存
|
||||
virtual bool isSaveCancelled() const { return false; }
|
||||
};
|
||||
|
||||
// 范围过滤后的源扫描线布局,仅在对应回调期间使用。
|
||||
struct LaserLineLoadInfo
|
||||
{
|
||||
@ -43,10 +43,10 @@ public:
|
||||
using LaserLoadFinishedCallback =
|
||||
std::function<void(int, int, float, int, int)>;
|
||||
|
||||
LaserDataLoader();
|
||||
~LaserDataLoader();
|
||||
|
||||
// 从文件加载激光扫描数据 - 统一接口,根据文件格式自动判断数据类型
|
||||
LaserDataLoader();
|
||||
~LaserDataLoader();
|
||||
|
||||
// 从文件加载激光扫描数据 - 统一接口,根据文件格式自动判断数据类型
|
||||
int LoadLaserScanData(const std::string& fileName,
|
||||
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
|
||||
int& lineNum,
|
||||
@ -82,38 +82,49 @@ public:
|
||||
const LaserLineLoadIndexedCallback& lineCallback,
|
||||
const LaserLoadFinishedCallback& finishedCallback,
|
||||
const PointCloudXYZRange& range);
|
||||
|
||||
// 保存激光扫描数据到文件 - 统一接口,支持两种类型的数据
|
||||
int SaveLaserScanData(const std::string& fileName,
|
||||
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
|
||||
int lineNum,
|
||||
float scanSpeed,
|
||||
int maxTimeStamp,
|
||||
int clockPerSecond);
|
||||
|
||||
int DebugSaveLaser(std::string fileName, std::vector<std::vector<SVzNL3DPosition>> xyzData);
|
||||
int DebugSaveLaser(std::string fileName, std::vector<std::vector<SVzNL3DPosition>> xyzData,
|
||||
ISaveProgressCallback* callback);
|
||||
|
||||
// 释放统一格式数据内存
|
||||
void FreeLaserScanData(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines);
|
||||
|
||||
// 释放转换后的SVzNL3DLaserLine数据内存
|
||||
void FreeConvertedData(std::vector<SVzNL3DLaserLine>& xyzData);
|
||||
|
||||
// 释放转换后的SVzNLXYZRGBDLaserLine数据内存
|
||||
void FreeConvertedData(std::vector<SVzNLXYZRGBDLaserLine>& rgbdData);
|
||||
|
||||
// 转换统一格式数据为SVzNL3DLaserLine格式
|
||||
int ConvertToSVzNL3DLaserLine(const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& unifiedData,
|
||||
std::vector<SVzNL3DLaserLine>& xyzData);
|
||||
|
||||
// 转换统一格式数据为SVzNLXYZRGBDLaserLine格式
|
||||
int ConvertToSVzNLXYZRGBDLaserLine(const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& unifiedData,
|
||||
std::vector<SVzNLXYZRGBDLaserLine>& rgbdData);
|
||||
|
||||
// 转换统一格式数据为std::vector<std::vector<SVzNL3DPosition>>格式
|
||||
// validPointCount: 可选输出参数,返回有效点个数(xyz全非0的点)
|
||||
|
||||
// 保存激光扫描数据到文件 - 统一接口,支持两种类型的数据
|
||||
// lineNum 参数保留以兼容调用方,文件行数始终使用 laserLines.size()。
|
||||
int SaveLaserScanData(const std::string& fileName,
|
||||
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
|
||||
int lineNum,
|
||||
float scanSpeed,
|
||||
int maxTimeStamp,
|
||||
int clockPerSecond);
|
||||
|
||||
// 保存兼容旧加载器的 .dat:每行最多4096点,仅保留XYZ和双目坐标,不保存颜色/强度。
|
||||
// lineNum 参数保留以兼容调用方,文件行数始终使用 laserLines.size()。
|
||||
// 旧格式帧号为有符号32位、时间戳为无符号32位;加载后统一为Position,二维坐标取整。
|
||||
int SaveLaserScanDataBinary(const std::string& fileName,
|
||||
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
|
||||
int lineNum,
|
||||
float scanSpeed,
|
||||
int maxTimeStamp,
|
||||
int clockPerSecond);
|
||||
|
||||
int DebugSaveLaser(std::string fileName, std::vector<std::vector<SVzNL3DPosition>> xyzData);
|
||||
int DebugSaveLaser(std::string fileName, std::vector<std::vector<SVzNL3DPosition>> xyzData,
|
||||
ISaveProgressCallback* callback);
|
||||
|
||||
// 释放统一格式数据内存
|
||||
void FreeLaserScanData(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines);
|
||||
|
||||
// 释放转换后的SVzNL3DLaserLine数据内存
|
||||
void FreeConvertedData(std::vector<SVzNL3DLaserLine>& xyzData);
|
||||
|
||||
// 释放转换后的SVzNLXYZRGBDLaserLine数据内存
|
||||
void FreeConvertedData(std::vector<SVzNLXYZRGBDLaserLine>& rgbdData);
|
||||
|
||||
// 转换统一格式数据为SVzNL3DLaserLine格式
|
||||
int ConvertToSVzNL3DLaserLine(const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& unifiedData,
|
||||
std::vector<SVzNL3DLaserLine>& xyzData);
|
||||
|
||||
// 转换统一格式数据为SVzNLXYZRGBDLaserLine格式
|
||||
int ConvertToSVzNLXYZRGBDLaserLine(const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& unifiedData,
|
||||
std::vector<SVzNLXYZRGBDLaserLine>& rgbdData);
|
||||
|
||||
// 转换统一格式数据为std::vector<std::vector<SVzNL3DPosition>>格式
|
||||
// validPointCount: 可选输出参数,返回有效点个数(xyz全非0的点)
|
||||
int ConvertToSVzNL3DPosition(const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& unifiedData,
|
||||
std::vector<std::vector<SVzNL3DPosition>>& scanLines,
|
||||
size_t* validPointCount = nullptr);
|
||||
@ -131,19 +142,19 @@ public:
|
||||
// 保留旧名称,兼容已有调用方。
|
||||
int LoadPolySegments(const std::string& fileName,
|
||||
std::vector<std::vector<SVzNLPointXYZRGBA>>& polyLines);
|
||||
|
||||
// 获取最后的错误信息
|
||||
std::string GetLastError() const { return m_lastError; }
|
||||
|
||||
private:
|
||||
|
||||
// 获取最后的错误信息
|
||||
std::string GetLastError() const { return m_lastError; }
|
||||
|
||||
private:
|
||||
// 读取XYZ格式的激光数据
|
||||
int _ParseLaserScanPoint(const std::string& data, SVzNL3DPosition& sData, SVzNL2DPosition& s2DData);
|
||||
int _ParseLaserScanPoint(const std::string& data, SVzNL3DPosition& sData, SVzNL2DPositionF& s2DData);
|
||||
|
||||
// 读取RGBD格式的激光数据 - 新增RGBD支持
|
||||
int _ParseLaserScanPoint(const std::string& data, SVzNLPointXYZRGBA& sData, SVzNL2DLRPoint& s2DData);
|
||||
|
||||
// 获取激光数据类型
|
||||
int _ParseLaserScanPoint(const std::string& data, SVzNLPointXYZRGBA& sData, SVzNL2DLRPoint& s2DData);
|
||||
|
||||
// 获取激光数据类型
|
||||
int _GetLaserType(const std::string& fileName, EVzResultDataType& eDataType);
|
||||
|
||||
// Read binary .dat laser scan data.
|
||||
@ -162,7 +173,7 @@ private:
|
||||
const PointCloudXYZRange& range);
|
||||
|
||||
std::string m_lastError;
|
||||
static const int VZ_LASER_LINE_PT_MAX_NUM = 4096;
|
||||
};
|
||||
|
||||
#endif // LASER_DATA_LOADER_H
|
||||
static const int VZ_LASER_LINE_PT_MAX_NUM = 4096;
|
||||
};
|
||||
|
||||
#endif // LASER_DATA_LOADER_H
|
||||
|
||||
@ -10,9 +10,11 @@
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <regex>
|
||||
|
||||
#include "VrLog.h"
|
||||
@ -97,6 +99,7 @@ struct DatLineScan3DData
|
||||
};
|
||||
|
||||
static_assert(sizeof(DatLaserScan3DLinesHeader) == 40, "Unexpected dat header layout");
|
||||
static_assert(sizeof(DatLineScan3DData) == 16, "Unexpected dat line layout");
|
||||
static_assert(sizeof(Dat3DFeaturePoint) == 112, "Unexpected dat feature point layout");
|
||||
|
||||
template <typename T>
|
||||
@ -989,11 +992,15 @@ int LaserDataLoader::SaveLaserScanData(const std::string& fileName,
|
||||
{
|
||||
LOG_INFO("Saving to file: %s\n", fileName.c_str());
|
||||
|
||||
if (laserLines.empty() || lineNum <= 0) {
|
||||
if (laserLines.empty() ||
|
||||
laserLines.size() > static_cast<size_t>((std::numeric_limits<int>::max)()))
|
||||
{
|
||||
m_lastError = "Invalid input parameters for saving unified data";
|
||||
LOG_ERROR("Invalid parameters for saving unified laser data\n");
|
||||
return ERR_CODE(DEV_ARG_INVAILD);
|
||||
}
|
||||
// 以实际写入的扫描线数为准,避免过滤或首尾裁剪后沿用旧行数。
|
||||
lineNum = static_cast<int>(laserLines.size());
|
||||
|
||||
try {
|
||||
std::ofstream sw(fileName);
|
||||
@ -1115,6 +1122,12 @@ int LaserDataLoader::SaveLaserScanData(const std::string& fileName,
|
||||
return ERR_CODE(FILE_ERR_WRITE);
|
||||
}
|
||||
sw.close();
|
||||
// 关闭文件时也可能提交写入失败,不能让调用方误以为现场已保存完整。
|
||||
if (!sw.good())
|
||||
{
|
||||
m_lastError = "Failed while closing file: " + fileName;
|
||||
return ERR_CODE(FILE_ERR_WRITE);
|
||||
}
|
||||
return SUCCESS;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
@ -1124,6 +1137,177 @@ int LaserDataLoader::SaveLaserScanData(const std::string& fileName,
|
||||
}
|
||||
}
|
||||
|
||||
int LaserDataLoader::SaveLaserScanDataBinary(
|
||||
const std::string& fileName,
|
||||
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
|
||||
int lineNum,
|
||||
float scanSpeed,
|
||||
int maxTimeStamp,
|
||||
int clockPerSecond)
|
||||
{
|
||||
LOG_INFO("Saving binary laser scan data to file: %s\n", fileName.c_str());
|
||||
if (laserLines.empty() ||
|
||||
laserLines.size() > static_cast<size_t>((std::numeric_limits<int>::max)()))
|
||||
{
|
||||
m_lastError = "Invalid input parameters for binary laser data";
|
||||
return ERR_CODE(DEV_ARG_INVAILD);
|
||||
}
|
||||
// 与文本格式一致,中间全零线仍占一行,不按有效点或原始帧号计数。
|
||||
lineNum = static_cast<int>(laserLines.size());
|
||||
|
||||
// 打开目标文件前校验全部扫描线,避免无效输入覆盖已有文件。
|
||||
size_t maxPointCount = 0;
|
||||
for (const auto& linePair : laserLines)
|
||||
{
|
||||
const SVzLaserLineData& line = linePair.second;
|
||||
if (line.nPointCount < 0 || line.nPointCount > VZ_LASER_LINE_PT_MAX_NUM)
|
||||
{
|
||||
m_lastError = "Point count outside binary laser line limit";
|
||||
return ERR_CODE(DATA_ERR_LEN);
|
||||
}
|
||||
if (linePair.first != keResultDataType_Position &&
|
||||
linePair.first != keResultDataType_PositionF &&
|
||||
linePair.first != keResultDataType_PointXYZRGBA &&
|
||||
linePair.first != keResultDataType_PointXYZI)
|
||||
{
|
||||
m_lastError = "Unsupported laser data type for binary storage";
|
||||
return ERR_CODE(DEV_ARG_INVAILD);
|
||||
}
|
||||
if (line.nPointCount > 0 && !line.p3DPoint)
|
||||
{
|
||||
m_lastError = "Missing 3D data for binary laser line";
|
||||
return ERR_CODE(DEV_ARG_INVAILD);
|
||||
}
|
||||
if (line.llFrameIdx > static_cast<unsigned long long>((std::numeric_limits<int>::max)()) ||
|
||||
line.llTimeStamp > (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
m_lastError = "Frame index or timestamp exceeds legacy binary format range";
|
||||
return ERR_CODE(DEV_ARG_INVAILD);
|
||||
}
|
||||
maxPointCount = std::max(maxPointCount, static_cast<size_t>(line.nPointCount));
|
||||
}
|
||||
|
||||
// 复用单行缓冲区,减少逐点写入调用,内存用量不随扫描行数增长。
|
||||
std::vector<Dat3DFeaturePoint> features(maxPointCount);
|
||||
std::ofstream outputFile(fileName, std::ios::binary | std::ios::trunc);
|
||||
if (!outputFile.is_open())
|
||||
{
|
||||
m_lastError = "Cannot open file for binary writing: " + fileName;
|
||||
return ERR_CODE(FILE_ERR_WRITE);
|
||||
}
|
||||
|
||||
DatLaserScan3DLinesHeader header = {};
|
||||
header.adjusted = 1;
|
||||
header.scanLineNum = lineNum;
|
||||
header.lineV = static_cast<double>(scanSpeed);
|
||||
header.clock = clockPerSecond;
|
||||
header.maxTimeStamp = static_cast<std::uint32_t>(std::max(0, maxTimeStamp));
|
||||
outputFile.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
if (!outputFile.good())
|
||||
{
|
||||
m_lastError = "Failed while writing binary header: " + fileName;
|
||||
return ERR_CODE(FILE_ERR_WRITE);
|
||||
}
|
||||
|
||||
for (const auto& linePair : laserLines)
|
||||
{
|
||||
const SVzLaserLineData& line = linePair.second;
|
||||
const int pointCount = line.nPointCount;
|
||||
DatLineScan3DData lineHeader = {};
|
||||
lineHeader.lineIndex = static_cast<int>(line.llFrameIdx);
|
||||
lineHeader.timeStamp = static_cast<std::uint32_t>(line.llTimeStamp);
|
||||
lineHeader.lineStepping = line.dStep;
|
||||
char lineRecord[40] = {};
|
||||
std::memcpy(lineRecord, &lineHeader, sizeof(lineHeader));
|
||||
outputFile.write(lineRecord, sizeof(lineRecord));
|
||||
|
||||
outputFile.write(reinterpret_cast<const char*>(&pointCount), sizeof(pointCount));
|
||||
const SVzNL3DPosition* positionPoints = nullptr;
|
||||
const SVzNLPointXYZRGBA* rgbaPoints = nullptr;
|
||||
const SVzNLPointXYZI* intensityPoints = nullptr;
|
||||
if (linePair.first == keResultDataType_Position ||
|
||||
linePair.first == keResultDataType_PositionF)
|
||||
{
|
||||
positionPoints = static_cast<const SVzNL3DPosition*>(line.p3DPoint);
|
||||
}
|
||||
else if (linePair.first == keResultDataType_PointXYZRGBA)
|
||||
{
|
||||
rgbaPoints = static_cast<const SVzNLPointXYZRGBA*>(line.p3DPoint);
|
||||
}
|
||||
else if (linePair.first == keResultDataType_PointXYZI)
|
||||
{
|
||||
intensityPoints = static_cast<const SVzNLPointXYZI*>(line.p3DPoint);
|
||||
}
|
||||
for (int index = 0; index < pointCount; ++index)
|
||||
{
|
||||
Dat3DFeaturePoint& feature = features[index];
|
||||
feature = Dat3DFeaturePoint();
|
||||
feature.index = index;
|
||||
feature.flag = 1;
|
||||
if (positionPoints)
|
||||
{
|
||||
feature.eye.X = positionPoints[index].pt3D.x;
|
||||
feature.eye.Y = positionPoints[index].pt3D.y;
|
||||
feature.eye.Z = positionPoints[index].pt3D.z;
|
||||
}
|
||||
else if (rgbaPoints)
|
||||
{
|
||||
feature.eye.X = rgbaPoints[index].x;
|
||||
feature.eye.Y = rgbaPoints[index].y;
|
||||
feature.eye.Z = rgbaPoints[index].z;
|
||||
}
|
||||
else if (intensityPoints)
|
||||
{
|
||||
feature.eye.X = intensityPoints[index].x;
|
||||
feature.eye.Y = intensityPoints[index].y;
|
||||
feature.eye.Z = intensityPoints[index].z;
|
||||
}
|
||||
if (line.p2DPoint && linePair.first == keResultDataType_Position)
|
||||
{
|
||||
const SVzNL2DPosition* points2D = static_cast<const SVzNL2DPosition*>(line.p2DPoint);
|
||||
feature.stereo.x = points2D[index].ptLeft2D.x;
|
||||
feature.stereo.y = points2D[index].ptLeft2D.y;
|
||||
feature.stereoRight.x = points2D[index].ptRight2D.x;
|
||||
feature.stereoRight.y = points2D[index].ptRight2D.y;
|
||||
}
|
||||
else if (line.p2DPoint && linePair.first == keResultDataType_PositionF)
|
||||
{
|
||||
const SVzNL2DPositionF* points2D = static_cast<const SVzNL2DPositionF*>(line.p2DPoint);
|
||||
feature.stereo.x = points2D[index].ptLeft2D.x;
|
||||
feature.stereo.y = points2D[index].ptLeft2D.y;
|
||||
feature.stereoRight.x = points2D[index].ptRight2D.x;
|
||||
feature.stereoRight.y = points2D[index].ptRight2D.y;
|
||||
}
|
||||
else if (line.p2DPoint)
|
||||
{
|
||||
const SVzNL2DLRPoint* points2D = static_cast<const SVzNL2DLRPoint*>(line.p2DPoint);
|
||||
feature.stereo.x = points2D[index].sLeft.x;
|
||||
feature.stereo.y = points2D[index].sLeft.y;
|
||||
feature.stereoRight.x = points2D[index].sRight.x;
|
||||
feature.stereoRight.y = points2D[index].sRight.y;
|
||||
}
|
||||
}
|
||||
if (pointCount > 0)
|
||||
{
|
||||
outputFile.write(reinterpret_cast<const char*>(features.data()),
|
||||
static_cast<std::streamsize>(pointCount * sizeof(Dat3DFeaturePoint)));
|
||||
}
|
||||
if (!outputFile.good())
|
||||
{
|
||||
m_lastError = "Failed while writing binary laser line: " + fileName;
|
||||
return ERR_CODE(FILE_ERR_WRITE);
|
||||
}
|
||||
}
|
||||
|
||||
outputFile.close();
|
||||
if (!outputFile.good())
|
||||
{
|
||||
m_lastError = "Failed while writing binary file: " + fileName;
|
||||
return ERR_CODE(FILE_ERR_WRITE);
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
int LaserDataLoader::DebugSaveLaser(std::string fileName, std::vector<std::vector<SVzNL3DPosition> > xyzData)
|
||||
{
|
||||
return DebugSaveLaser(std::move(fileName), std::move(xyzData), nullptr);
|
||||
@ -1566,9 +1750,8 @@ int LaserDataLoader::ConvertToSVzNLPositionD(
|
||||
}
|
||||
}
|
||||
|
||||
if (!scanLine.empty()) {
|
||||
scanLines.push_back(scanLine);
|
||||
}
|
||||
// 中间零点数扫描线也必须占位,转换后的行序与过滤及保存结果一致。
|
||||
scanLines.push_back(std::move(scanLine));
|
||||
}
|
||||
|
||||
if (scanLines.empty()) {
|
||||
|
||||
@ -404,8 +404,9 @@ void PointCloudGLWidget::repositionOverlayControls()
|
||||
const int y = qMax(0, (widgetHeight - buttonHeight * 2 - gap) / 2);
|
||||
m_zoomInButton->move(x, y);
|
||||
m_zoomOutButton->move(x, y + buttonHeight + gap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void PointCloudGLWidget::updateViewAngleDisplay()
|
||||
@ -3005,14 +3006,7 @@ void PointCloudGLWidget::drawPosePoints()
|
||||
}
|
||||
|
||||
// 从视图矩阵取相机右/上方向,把轴线拉成面向屏幕的窄带,避免 GLES2 线宽被钳成 1 像素。
|
||||
const QMatrix4x4 invView = m_view.inverted();
|
||||
const QVector3D camRight = invView.column(0).toVector3D().normalized();
|
||||
const QVector3D camUp = invView.column(1).toVector3D().normalized();
|
||||
const float pixelWorld = m_distance *
|
||||
std::tan(qDegreesToRadians(22.5f)) /
|
||||
static_cast<float>(qMax(1, height() / 2));
|
||||
|
||||
auto applyEulerRotation = [this](QMatrix4x4& poseLocal, const PosePoint& pose)
|
||||
auto applyEulerRotation = [this](QMatrix4x4& poseLocal, const PosePoint& pose)
|
||||
{
|
||||
switch (m_eulerRotationOrder)
|
||||
{
|
||||
@ -3049,45 +3043,32 @@ void PointCloudGLWidget::drawPosePoints()
|
||||
}
|
||||
};
|
||||
|
||||
auto appendWorld = [](QVector<float>& dst, const QVector3D& p)
|
||||
{
|
||||
dst.append(p.x());
|
||||
dst.append(p.y());
|
||||
dst.append(p.z());
|
||||
};
|
||||
|
||||
auto appendAxisBand = [&](QVector<float>& positions,
|
||||
QVector<float>& colors,
|
||||
const QVector3D& start,
|
||||
const QVector3D& end,
|
||||
const QVector3D& color,
|
||||
float halfWidth)
|
||||
{
|
||||
QVector3D side = QVector3D::crossProduct(end - start, camUp);
|
||||
if (side.lengthSquared() < 1.0e-12f)
|
||||
{
|
||||
side = QVector3D::crossProduct(end - start, camRight);
|
||||
}
|
||||
if (!side.isNull())
|
||||
{
|
||||
side.normalize();
|
||||
side *= halfWidth;
|
||||
}
|
||||
const QVector3D s0 = start - side;
|
||||
const QVector3D s1 = start + side;
|
||||
const QVector3D e0 = end - side;
|
||||
const QVector3D e1 = end + side;
|
||||
const QVector3D quad[6] = {s0, e0, e1, s0, e1, s1};
|
||||
for (const QVector3D& p : quad)
|
||||
{
|
||||
appendWorld(positions, p);
|
||||
appendWorld(colors, color);
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& pose : m_posePoints)
|
||||
{
|
||||
QMatrix4x4 poseLocal;
|
||||
QVector<float> positions;
|
||||
QVector<float> colors;
|
||||
positions.reserve(m_posePoints.size() * 18);
|
||||
colors.reserve(m_posePoints.size() * 18);
|
||||
|
||||
const auto appendAxis = [&](const QVector3D& start,
|
||||
const QVector3D& end,
|
||||
const QVector3D& color)
|
||||
{
|
||||
positions.append(start.x());
|
||||
positions.append(start.y());
|
||||
positions.append(start.z());
|
||||
positions.append(end.x());
|
||||
positions.append(end.y());
|
||||
positions.append(end.z());
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
colors.append(color.x());
|
||||
colors.append(color.y());
|
||||
colors.append(color.z());
|
||||
}
|
||||
};
|
||||
|
||||
for (const PosePoint& pose : m_posePoints)
|
||||
{
|
||||
QMatrix4x4 poseLocal;
|
||||
poseLocal.setToIdentity();
|
||||
poseLocal.translate(pose.x, pose.y, pose.z);
|
||||
applyEulerRotation(poseLocal, pose);
|
||||
@ -3097,40 +3078,18 @@ void PointCloudGLWidget::drawPosePoints()
|
||||
const QVector3D xEnd = poseLocal.map(QVector3D(axisLength, 0.0f, 0.0f));
|
||||
const QVector3D yEnd = poseLocal.map(QVector3D(0.0f, axisLength, 0.0f));
|
||||
const QVector3D zEnd = poseLocal.map(QVector3D(0.0f, 0.0f, axisLength));
|
||||
const float halfWidth = qMax(axisLength * 0.02f, pixelWorld * 2.0f);
|
||||
|
||||
QVector<float> bandPositions;
|
||||
QVector<float> bandColors;
|
||||
bandPositions.reserve(54);
|
||||
bandColors.reserve(54);
|
||||
appendAxisBand(bandPositions, bandColors, origin, xEnd, QVector3D(1.0f, 0.0f, 0.0f), halfWidth);
|
||||
appendAxisBand(bandPositions, bandColors, origin, yEnd, QVector3D(0.0f, 1.0f, 0.0f), halfWidth);
|
||||
appendAxisBand(bandPositions, bandColors, origin, zEnd, QVector3D(0.0f, 0.0f, 1.0f), halfWidth);
|
||||
|
||||
// 关闭深度测试,避免姿态轴被周围点云挡住。
|
||||
drawPrimitives(m_mvp,
|
||||
bandPositions.constData(),
|
||||
bandPositions.size() / 3,
|
||||
GL_TRIANGLES,
|
||||
bandColors.constData(),
|
||||
QVector3D(1.0f, 1.0f, 1.0f),
|
||||
1.0f,
|
||||
1.0f,
|
||||
false);
|
||||
|
||||
const float tipPositions[] = {
|
||||
origin.x(), origin.y(), origin.z(),
|
||||
xEnd.x(), xEnd.y(), xEnd.z(),
|
||||
yEnd.x(), yEnd.y(), yEnd.z(),
|
||||
zEnd.x(), zEnd.y(), zEnd.z()
|
||||
};
|
||||
const float tipColors[] = {
|
||||
1.0f, 1.0f, 1.0f,
|
||||
1.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f
|
||||
};
|
||||
drawPrimitives(m_mvp, tipPositions, 4, GL_POINTS, tipColors,
|
||||
QVector3D(1.0f, 1.0f, 1.0f), 10.0f, 1.0f, false);
|
||||
}
|
||||
}
|
||||
appendAxis(origin, xEnd, QVector3D(1.0f, 0.0f, 0.0f));
|
||||
appendAxis(origin, yEnd, QVector3D(0.0f, 1.0f, 0.0f));
|
||||
appendAxis(origin, zEnd, QVector3D(0.0f, 0.0f, 1.0f));
|
||||
|
||||
}
|
||||
drawPrimitives(m_mvp,
|
||||
positions.constData(),
|
||||
positions.size() / 3,
|
||||
GL_LINES,
|
||||
colors.constData(),
|
||||
QVector3D(1.0f, 1.0f, 1.0f),
|
||||
1.0f,
|
||||
1.0f,
|
||||
false);
|
||||
}
|
||||
|
||||
@ -7,14 +7,22 @@
|
||||
#include "VrMD5Utils.h"
|
||||
#include "VrStringUtils.h"
|
||||
#include "VrNumUtils.h"
|
||||
#include "VrLog.h"
|
||||
#include "VrLog.h"
|
||||
#include "VrPathUtils.h"
|
||||
#include "VrCrashHandler.h"
|
||||
#include "VrShellUtils.h"
|
||||
#include "VrNetUtils.h"
|
||||
#include "VrDebugTime.h"
|
||||
#include "VrNTPUtils.h"
|
||||
#include "../crc/checksum.h"
|
||||
#include "../ini/SimpleIni.h"
|
||||
|
||||
#include "../ini/SimpleIni.h"
|
||||
|
||||
namespace VrUtilsDetail
|
||||
{
|
||||
// 静态库调用方保留处理器对象文件,安装过程不经过日志初始化。
|
||||
static VrCrashHandler::AutoInitializer crashHandlerAutoInitializer;
|
||||
}
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
28
VrUtils/Inc/VrCrashHandler.h
Normal file
28
VrUtils/Inc/VrCrashHandler.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef VR_CRASH_HANDLER_H
|
||||
#define VR_CRASH_HANDLER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace VrCrashHandler
|
||||
{
|
||||
// 启动阶段独立初始化,多个入口只执行一次。
|
||||
void Initialize();
|
||||
// 正常上下文调用,创建目录并安装进程级处理器。路径为 UTF-8。
|
||||
// 首次空路径使用 VrPathUtils 的日志目录/crash;重复空路径调用保留当前目录。
|
||||
bool Enable(const std::string& outputDirectory = std::string());
|
||||
// 在启动/配置阶段调用;恢复安装前的处理器,不删除已保存文件。
|
||||
void Disable();
|
||||
bool IsEnabled();
|
||||
std::string GetOutputDirectory();
|
||||
|
||||
class AutoInitializer
|
||||
{
|
||||
public:
|
||||
AutoInitializer()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -2,11 +2,8 @@
|
||||
#define __VR_LOG__H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include <chrono>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma comment(lib, "Shell32.lib") // VrLog 需要 SHGetFolderPathA
|
||||
#endif // _WIN32
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define VR_UTILS_API
|
||||
@ -61,8 +58,9 @@ enum VrLogType
|
||||
KELOGTYPE_All = 0xff,
|
||||
};
|
||||
|
||||
namespace VrLogUtils
|
||||
{
|
||||
namespace VrLogUtils
|
||||
{
|
||||
/// 返回与日志文件相同的目录规则,供崩溃文件等诊断数据复用。
|
||||
/// 初始化log
|
||||
VR_UTILS_API void InitLog();
|
||||
|
||||
|
||||
11
VrUtils/Inc/VrPathUtils.h
Normal file
11
VrUtils/Inc/VrPathUtils.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef VR_PATH_UTILS_H
|
||||
#define VR_PATH_UTILS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace VrPathUtils
|
||||
{
|
||||
std::string GetLogDirectory();
|
||||
}
|
||||
|
||||
#endif
|
||||
997
VrUtils/Src/VrCrashHandler.cpp
Normal file
997
VrUtils/Src/VrCrashHandler.cpp
Normal file
@ -0,0 +1,997 @@
|
||||
#include "VrCrashHandler.h"
|
||||
#include "VrPathUtils.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <csignal>
|
||||
#elif defined(__linux__)
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <time.h>
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
#if defined(__GLIBC__)
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
#if defined(_WIN32) || defined(__linux__)
|
||||
const int maxFrames = 64;
|
||||
|
||||
struct OutputTarget
|
||||
{
|
||||
std::string directory;
|
||||
#ifdef _WIN32
|
||||
std::wstring directoryPath;
|
||||
#endif
|
||||
};
|
||||
|
||||
// 回调只读不可变快照,不获取配置锁。旧快照保留到进程结束,避免切换目录时悬空。
|
||||
std::atomic<OutputTarget*> outputTarget{nullptr};
|
||||
std::atomic_flag handlingCrash = ATOMIC_FLAG_INIT;
|
||||
|
||||
#ifdef _WIN32
|
||||
using NativeFile = HANDLE;
|
||||
const NativeFile invalidFile = INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
using NativeFile = int;
|
||||
const NativeFile invalidFile = -1;
|
||||
#endif
|
||||
|
||||
void writeBytes(NativeFile file, const char* data, size_t size)
|
||||
{
|
||||
while (size > 0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD written = 0;
|
||||
if (!WriteFile(file, data, static_cast<DWORD>(size), &written, nullptr) || written == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#else
|
||||
const ssize_t written = write(file, data, size);
|
||||
if (written < 0 && errno == EINTR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (written <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
data += written;
|
||||
size -= static_cast<size_t>(written);
|
||||
}
|
||||
}
|
||||
|
||||
void writeText(NativeFile file, const char* text)
|
||||
{
|
||||
size_t size = 0;
|
||||
while (text[size] != '\0')
|
||||
{
|
||||
++size;
|
||||
}
|
||||
writeBytes(file, text, size);
|
||||
}
|
||||
|
||||
// 信号路径不用 printf/iostream 或日志库,避免堆分配和 stdio/日志锁重入。
|
||||
void writeNumber(NativeFile file, const char* label, uint64_t value, unsigned int base = 10)
|
||||
{
|
||||
char buffer[32];
|
||||
char* end = buffer + sizeof(buffer);
|
||||
char* begin = end;
|
||||
do
|
||||
{
|
||||
*--begin = "0123456789abcdef"[value % base];
|
||||
value /= base;
|
||||
} while (value != 0);
|
||||
writeText(file, label);
|
||||
if (base == 16)
|
||||
{
|
||||
writeText(file, "0x");
|
||||
}
|
||||
writeBytes(file, begin, static_cast<size_t>(end - begin));
|
||||
writeText(file, "\n");
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
struct DebugFunctions
|
||||
{
|
||||
HMODULE library = nullptr;
|
||||
decltype(&SymInitialize) initialize = nullptr;
|
||||
decltype(&SymSetOptions) setOptions = nullptr;
|
||||
decltype(&SymFromAddr) fromAddress = nullptr;
|
||||
decltype(&SymGetLineFromAddr64) getLine = nullptr;
|
||||
decltype(&SymFunctionTableAccess64) functionTable = nullptr;
|
||||
decltype(&SymGetModuleBase64) moduleBase = nullptr;
|
||||
decltype(&StackWalk64) walk = nullptr;
|
||||
decltype(&MiniDumpWriteDump) dump = nullptr;
|
||||
bool symbolsReady = false;
|
||||
};
|
||||
|
||||
// 正常初始化阶段加载 DbgHelp,静态库的调用方无需额外链接 dbghelp.lib。
|
||||
DebugFunctions debugFunctions;
|
||||
|
||||
void prepareUnwinder()
|
||||
{
|
||||
if (debugFunctions.library)
|
||||
{
|
||||
return;
|
||||
}
|
||||
wchar_t systemPath[MAX_PATH] = {};
|
||||
const UINT length = GetSystemDirectoryW(systemPath, MAX_PATH);
|
||||
if (length == 0 || length >= MAX_PATH - 13)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const std::wstring libraryPath = std::wstring(systemPath) + L"\\dbghelp.dll";
|
||||
debugFunctions.library = LoadLibraryW(libraryPath.c_str());
|
||||
if (!debugFunctions.library)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#define VR_LOAD_DEBUG_FUNCTION(member, name) \
|
||||
debugFunctions.member = reinterpret_cast<decltype(debugFunctions.member)>(GetProcAddress(debugFunctions.library, name))
|
||||
VR_LOAD_DEBUG_FUNCTION(initialize, "SymInitialize");
|
||||
VR_LOAD_DEBUG_FUNCTION(setOptions, "SymSetOptions");
|
||||
VR_LOAD_DEBUG_FUNCTION(fromAddress, "SymFromAddr");
|
||||
VR_LOAD_DEBUG_FUNCTION(getLine, "SymGetLineFromAddr64");
|
||||
VR_LOAD_DEBUG_FUNCTION(functionTable, "SymFunctionTableAccess64");
|
||||
VR_LOAD_DEBUG_FUNCTION(moduleBase, "SymGetModuleBase64");
|
||||
VR_LOAD_DEBUG_FUNCTION(walk, "StackWalk64");
|
||||
VR_LOAD_DEBUG_FUNCTION(dump, "MiniDumpWriteDump");
|
||||
#undef VR_LOAD_DEBUG_FUNCTION
|
||||
if (debugFunctions.setOptions)
|
||||
{
|
||||
debugFunctions.setOptions(SYMOPT_UNDNAME | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS | SYMOPT_FAIL_CRITICAL_ERRORS);
|
||||
}
|
||||
if (debugFunctions.initialize)
|
||||
{
|
||||
debugFunctions.symbolsReady = debugFunctions.initialize(GetCurrentProcess(), nullptr, TRUE) != FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
void writeWindowsFrame(NativeFile file, DWORD64 address)
|
||||
{
|
||||
writeNumber(file, " pc=", address, 16);
|
||||
MEMORY_BASIC_INFORMATION memory = {};
|
||||
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(address)), &memory, sizeof(memory)))
|
||||
{
|
||||
writeNumber(file, " module_offset=", address - reinterpret_cast<uintptr_t>(memory.AllocationBase), 16);
|
||||
wchar_t modulePath[MAX_PATH] = {};
|
||||
if (GetModuleFileNameW(static_cast<HMODULE>(memory.AllocationBase), modulePath, MAX_PATH))
|
||||
{
|
||||
char utf8Path[MAX_PATH * 3] = {};
|
||||
if (WideCharToMultiByte(CP_UTF8, 0, modulePath, -1, utf8Path, sizeof(utf8Path), nullptr, nullptr))
|
||||
{
|
||||
writeText(file, " module=");
|
||||
writeText(file, utf8Path);
|
||||
writeText(file, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!debugFunctions.symbolsReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
alignas(SYMBOL_INFO) char symbolBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME] = {};
|
||||
SYMBOL_INFO* symbol = reinterpret_cast<SYMBOL_INFO*>(symbolBuffer);
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
symbol->MaxNameLen = MAX_SYM_NAME;
|
||||
DWORD64 displacement = 0;
|
||||
if (debugFunctions.fromAddress && debugFunctions.fromAddress(GetCurrentProcess(), address, &displacement, symbol))
|
||||
{
|
||||
writeText(file, " symbol=");
|
||||
writeText(file, symbol->Name);
|
||||
writeNumber(file, "+", displacement, 16);
|
||||
}
|
||||
IMAGEHLP_LINE64 line = {};
|
||||
line.SizeOfStruct = sizeof(line);
|
||||
DWORD lineDisplacement = 0;
|
||||
if (debugFunctions.getLine && debugFunctions.getLine(GetCurrentProcess(), address, &lineDisplacement, &line))
|
||||
{
|
||||
writeText(file, " source=");
|
||||
writeText(file, line.FileName);
|
||||
writeNumber(file, ":", line.LineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
void writeWindowsExceptionName(NativeFile file, DWORD code)
|
||||
{
|
||||
const char* name = "unknown";
|
||||
switch (code)
|
||||
{
|
||||
case EXCEPTION_ACCESS_VIOLATION: name = "EXCEPTION_ACCESS_VIOLATION"; break;
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: name = "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; break;
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT: name = "EXCEPTION_DATATYPE_MISALIGNMENT"; break;
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO: name = "EXCEPTION_FLT_DIVIDE_BY_ZERO"; break;
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION: name = "EXCEPTION_ILLEGAL_INSTRUCTION"; break;
|
||||
case EXCEPTION_IN_PAGE_ERROR: name = "EXCEPTION_IN_PAGE_ERROR"; break;
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO: name = "EXCEPTION_INT_DIVIDE_BY_ZERO"; break;
|
||||
case EXCEPTION_STACK_OVERFLOW: name = "EXCEPTION_STACK_OVERFLOW"; break;
|
||||
case 0xC0000374U: name = "STATUS_HEAP_CORRUPTION"; break;
|
||||
case 0xC0000409U: name = "STATUS_STACK_BUFFER_OVERRUN"; break;
|
||||
default: break;
|
||||
}
|
||||
writeText(file, "exception_name=");
|
||||
writeText(file, name);
|
||||
writeText(file, "\n");
|
||||
}
|
||||
|
||||
void writeWindowsRegisters(NativeFile file, const CONTEXT& context)
|
||||
{
|
||||
writeText(file, "Registers:\n");
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
writeNumber(file, " rip=", context.Rip, 16);
|
||||
writeNumber(file, " rsp=", context.Rsp, 16);
|
||||
writeNumber(file, " rbp=", context.Rbp, 16);
|
||||
writeNumber(file, " rflags=", context.EFlags, 16);
|
||||
writeNumber(file, " rax=", context.Rax, 16);
|
||||
writeNumber(file, " rbx=", context.Rbx, 16);
|
||||
writeNumber(file, " rcx=", context.Rcx, 16);
|
||||
writeNumber(file, " rdx=", context.Rdx, 16);
|
||||
writeNumber(file, " rsi=", context.Rsi, 16);
|
||||
writeNumber(file, " rdi=", context.Rdi, 16);
|
||||
writeNumber(file, " r8=", context.R8, 16);
|
||||
writeNumber(file, " r9=", context.R9, 16);
|
||||
#elif defined(_M_IX86) || defined(__i386__)
|
||||
writeNumber(file, " eip=", context.Eip, 16);
|
||||
writeNumber(file, " esp=", context.Esp, 16);
|
||||
writeNumber(file, " ebp=", context.Ebp, 16);
|
||||
writeNumber(file, " eflags=", context.EFlags, 16);
|
||||
writeNumber(file, " eax=", context.Eax, 16);
|
||||
writeNumber(file, " ebx=", context.Ebx, 16);
|
||||
writeNumber(file, " ecx=", context.Ecx, 16);
|
||||
writeNumber(file, " edx=", context.Edx, 16);
|
||||
writeNumber(file, " esi=", context.Esi, 16);
|
||||
writeNumber(file, " edi=", context.Edi, 16);
|
||||
#elif defined(_M_ARM64) || defined(__aarch64__)
|
||||
writeNumber(file, " pc=", context.Pc, 16);
|
||||
writeNumber(file, " sp=", context.Sp, 16);
|
||||
writeNumber(file, " fp=", context.Fp, 16);
|
||||
writeNumber(file, " lr=", context.Lr, 16);
|
||||
for (int index = 0; index < 31; ++index)
|
||||
{
|
||||
char label[8] = {};
|
||||
label[0] = ' '; label[1] = 'x';
|
||||
if (index >= 10)
|
||||
{
|
||||
label[2] = static_cast<char>('0' + index / 10);
|
||||
label[3] = static_cast<char>('0' + index % 10);
|
||||
label[4] = '=';
|
||||
}
|
||||
else
|
||||
{
|
||||
label[2] = static_cast<char>('0' + index);
|
||||
label[3] = '=';
|
||||
}
|
||||
writeNumber(file, label, context.X[index], 16);
|
||||
}
|
||||
writeNumber(file, " cpsr=", context.Cpsr, 16);
|
||||
#elif defined(_M_ARM) || defined(__arm__)
|
||||
writeNumber(file, " pc=", context.Pc, 16);
|
||||
writeNumber(file, " sp=", context.Sp, 16);
|
||||
writeNumber(file, " fp=", context.R11, 16);
|
||||
writeNumber(file, " lr=", context.Lr, 16);
|
||||
writeNumber(file, " r0=", context.R0, 16);
|
||||
writeNumber(file, " r1=", context.R1, 16);
|
||||
writeNumber(file, " r2=", context.R2, 16);
|
||||
writeNumber(file, " r3=", context.R3, 16);
|
||||
writeNumber(file, " r12=", context.R12, 16);
|
||||
writeNumber(file, " cpsr=", context.Cpsr, 16);
|
||||
#endif
|
||||
}
|
||||
|
||||
void writeWindowsStack(NativeFile file, CONTEXT context)
|
||||
{
|
||||
STACKFRAME64 frame = {};
|
||||
DWORD machine = 0;
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
machine = IMAGE_FILE_MACHINE_AMD64;
|
||||
frame.AddrPC.Offset = context.Rip;
|
||||
frame.AddrFrame.Offset = context.Rbp;
|
||||
frame.AddrStack.Offset = context.Rsp;
|
||||
#elif defined(_M_IX86) || defined(__i386__)
|
||||
machine = IMAGE_FILE_MACHINE_I386;
|
||||
frame.AddrPC.Offset = context.Eip;
|
||||
frame.AddrFrame.Offset = context.Ebp;
|
||||
frame.AddrStack.Offset = context.Esp;
|
||||
#elif defined(_M_ARM64) || defined(__aarch64__)
|
||||
machine = IMAGE_FILE_MACHINE_ARM64;
|
||||
frame.AddrPC.Offset = context.Pc;
|
||||
frame.AddrFrame.Offset = context.Fp;
|
||||
frame.AddrStack.Offset = context.Sp;
|
||||
#endif
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
writeText(file, "Faulting thread stack (frame 0 is the captured PC):\n");
|
||||
for (int index = 0; index < maxFrames && frame.AddrPC.Offset != 0; ++index)
|
||||
{
|
||||
writeNumber(file, "frame=", index);
|
||||
writeWindowsFrame(file, frame.AddrPC.Offset);
|
||||
if (!debugFunctions.symbolsReady || !debugFunctions.walk || !debugFunctions.functionTable || !debugFunctions.moduleBase)
|
||||
{
|
||||
break;
|
||||
}
|
||||
const DWORD64 previousPc = frame.AddrPC.Offset;
|
||||
const DWORD64 previousSp = frame.AddrStack.Offset;
|
||||
if (!debugFunctions.walk(machine, GetCurrentProcess(), GetCurrentThread(), &frame, &context,
|
||||
nullptr, debugFunctions.functionTable, debugFunctions.moduleBase, nullptr) ||
|
||||
(frame.AddrPC.Offset == previousPc && frame.AddrStack.Offset == previousSp))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeWindowsArchitecture(NativeFile file)
|
||||
{
|
||||
#if defined(_M_ARM64) || defined(__aarch64__)
|
||||
writeText(file, "architecture=arm64\n");
|
||||
#elif defined(_M_ARM) || defined(__arm__)
|
||||
writeText(file, "architecture=arm32\n");
|
||||
#elif defined(_M_X64) || defined(__x86_64__)
|
||||
writeText(file, "architecture=x64\n");
|
||||
#elif defined(_M_IX86) || defined(__i386__)
|
||||
writeText(file, "architecture=x86\n");
|
||||
#else
|
||||
writeText(file, "architecture=unknown\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void writeWindowsReport(EXCEPTION_POINTERS* exceptionInfo, int signalNumber)
|
||||
{
|
||||
if (handlingCrash.test_and_set())
|
||||
{
|
||||
TerminateProcess(GetCurrentProcess(), 1);
|
||||
return;
|
||||
}
|
||||
OutputTarget* target = outputTarget.load(std::memory_order_acquire);
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SYSTEMTIME localTime = {};
|
||||
GetLocalTime(&localTime);
|
||||
wchar_t stackPath[32768] = {};
|
||||
wchar_t dumpPath[32768] = {};
|
||||
const int stackPathLength = _snwprintf_s(stackPath, _countof(stackPath), _TRUNCATE,
|
||||
L"%s\\crash_stack_%04u%02u%02u_%02u%02u%02u_%03u.txt",
|
||||
target->directoryPath.c_str(), localTime.wYear, localTime.wMonth, localTime.wDay,
|
||||
localTime.wHour, localTime.wMinute, localTime.wSecond, localTime.wMilliseconds);
|
||||
const int dumpPathLength = _snwprintf_s(dumpPath, _countof(dumpPath), _TRUNCATE,
|
||||
L"%s\\crash_%04u%02u%02u_%02u%02u%02u_%03u.dmp",
|
||||
target->directoryPath.c_str(), localTime.wYear, localTime.wMonth, localTime.wDay,
|
||||
localTime.wHour, localTime.wMinute, localTime.wSecond, localTime.wMilliseconds);
|
||||
if (stackPathLength <= 0 || dumpPathLength <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const NativeFile file = CreateFileW(stackPath, GENERIC_WRITE, FILE_SHARE_READ,
|
||||
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (file == invalidFile)
|
||||
{
|
||||
return;
|
||||
}
|
||||
writeText(file, "VrUtils crash report\n");
|
||||
writeWindowsArchitecture(file);
|
||||
writeNumber(file, "pid=", GetCurrentProcessId());
|
||||
writeNumber(file, "tid=", GetCurrentThreadId());
|
||||
FILETIME time = {};
|
||||
GetSystemTimeAsFileTime(&time);
|
||||
const uint64_t ticks = (static_cast<uint64_t>(time.dwHighDateTime) << 32) | time.dwLowDateTime;
|
||||
writeNumber(file, "unix_time=", (ticks - 116444736000000000ULL) / 10000000ULL);
|
||||
writeNumber(file, "signal=", signalNumber);
|
||||
const EXCEPTION_RECORD* record = exceptionInfo ? exceptionInfo->ExceptionRecord : nullptr;
|
||||
const CONTEXT* capturedContext = exceptionInfo ? exceptionInfo->ContextRecord : nullptr;
|
||||
if (record)
|
||||
{
|
||||
writeNumber(file, "exception_code=", record->ExceptionCode, 16);
|
||||
writeNumber(file, "exception_address=", reinterpret_cast<uintptr_t>(record->ExceptionAddress), 16);
|
||||
writeWindowsExceptionName(file, record->ExceptionCode);
|
||||
if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && record->NumberParameters >= 2)
|
||||
{
|
||||
writeText(file, "access_type=");
|
||||
writeText(file, record->ExceptionInformation[0] == 0 ? "read" :
|
||||
(record->ExceptionInformation[0] == 1 ? "write" : "execute"));
|
||||
writeText(file, "\n");
|
||||
writeNumber(file, "access_address=", record->ExceptionInformation[1], 16);
|
||||
}
|
||||
if (record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR && record->NumberParameters >= 3)
|
||||
{
|
||||
writeNumber(file, "in_page_status=", record->ExceptionInformation[0], 16);
|
||||
writeNumber(file, "in_page_address=", record->ExceptionInformation[1], 16);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writeText(file, "exception_code=unavailable\n");
|
||||
writeText(file, "exception_address=unavailable\n");
|
||||
}
|
||||
// 先提交异常头,再尽力生成 dump 和展开栈;展开器自身异常时仍有最低限度现场。
|
||||
FlushFileBuffers(file);
|
||||
if (debugFunctions.dump && exceptionInfo)
|
||||
{
|
||||
const HANDLE dumpFile = CreateFileW(dumpPath, GENERIC_WRITE, FILE_SHARE_READ,
|
||||
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (dumpFile != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
MINIDUMP_EXCEPTION_INFORMATION information = {};
|
||||
information.ThreadId = GetCurrentThreadId();
|
||||
information.ExceptionPointers = exceptionInfo;
|
||||
information.ClientPointers = FALSE;
|
||||
const BOOL success = debugFunctions.dump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile,
|
||||
static_cast<MINIDUMP_TYPE>(MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules), &information, nullptr, nullptr);
|
||||
writeNumber(file, "minidump_written=", success ? 1 : 0);
|
||||
FlushFileBuffers(dumpFile);
|
||||
CloseHandle(dumpFile);
|
||||
}
|
||||
}
|
||||
if (capturedContext)
|
||||
{
|
||||
writeWindowsRegisters(file, *capturedContext);
|
||||
writeText(file, "Faulting instruction:\n");
|
||||
const DWORD64 faultAddress = record && record->ExceptionAddress
|
||||
? reinterpret_cast<DWORD64>(record->ExceptionAddress)
|
||||
: 0;
|
||||
if (faultAddress != 0)
|
||||
{
|
||||
writeWindowsFrame(file, faultAddress);
|
||||
}
|
||||
writeWindowsStack(file, *capturedContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
writeText(file, "Faulting thread stack unavailable\n");
|
||||
}
|
||||
FlushFileBuffers(file);
|
||||
CloseHandle(file);
|
||||
}
|
||||
|
||||
bool isFatalWindowsException(DWORD code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
case EXCEPTION_IN_PAGE_ERROR:
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||
case EXCEPTION_INT_OVERFLOW:
|
||||
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||||
case EXCEPTION_STACK_OVERFLOW:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
LONG CALLBACK windowsVectoredExceptionHandler(EXCEPTION_POINTERS* exceptionInfo)
|
||||
{
|
||||
if (exceptionInfo && exceptionInfo->ExceptionRecord &&
|
||||
isFatalWindowsException(exceptionInfo->ExceptionRecord->ExceptionCode))
|
||||
{
|
||||
writeWindowsReport(exceptionInfo, 0);
|
||||
}
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
LONG WINAPI windowsExceptionHandler(EXCEPTION_POINTERS* exceptionInfo)
|
||||
{
|
||||
writeWindowsReport(exceptionInfo, 0);
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
void windowsSignalHandler(int signalNumber)
|
||||
{
|
||||
CONTEXT context = {};
|
||||
RtlCaptureContext(&context);
|
||||
EXCEPTION_RECORD record = {};
|
||||
record.ExceptionCode = 0xe0000000U | static_cast<DWORD>(signalNumber);
|
||||
EXCEPTION_POINTERS information = {&record, &context};
|
||||
writeWindowsReport(&information, signalNumber);
|
||||
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(128 + signalNumber));
|
||||
}
|
||||
|
||||
const int fatalSignals[] = {SIGABRT, SIGSEGV, SIGILL, SIGFPE};
|
||||
using SignalHandler = void (*)(int);
|
||||
#else
|
||||
void prepareUnwinder()
|
||||
{
|
||||
#if defined(__GLIBC__)
|
||||
// 提前加载 libgcc 展开器,减少首次崩溃时的动态加载/内存分配。
|
||||
void* frames[1];
|
||||
backtrace(frames, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void writeLinuxContext(NativeFile file, void* rawContext)
|
||||
{
|
||||
if (!rawContext)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const ucontext_t* context = static_cast<const ucontext_t*>(rawContext);
|
||||
#if defined(__x86_64__)
|
||||
writeNumber(file, "fault_pc=", context->uc_mcontext.gregs[REG_RIP], 16);
|
||||
writeNumber(file, "fault_sp=", context->uc_mcontext.gregs[REG_RSP], 16);
|
||||
#elif defined(__i386__)
|
||||
writeNumber(file, "fault_pc=", context->uc_mcontext.gregs[REG_EIP], 16);
|
||||
writeNumber(file, "fault_sp=", context->uc_mcontext.gregs[REG_ESP], 16);
|
||||
#elif defined(__aarch64__)
|
||||
writeNumber(file, "fault_pc=", context->uc_mcontext.pc, 16);
|
||||
writeNumber(file, "fault_sp=", context->uc_mcontext.sp, 16);
|
||||
writeNumber(file, "fault_lr=", context->uc_mcontext.regs[30], 16);
|
||||
writeNumber(file, "fault_fp=", context->uc_mcontext.regs[29], 16);
|
||||
writeNumber(file, "fault_x0=", context->uc_mcontext.regs[0], 16);
|
||||
writeNumber(file, "fault_x1=", context->uc_mcontext.regs[1], 16);
|
||||
writeNumber(file, "fault_x2=", context->uc_mcontext.regs[2], 16);
|
||||
writeNumber(file, "fault_x3=", context->uc_mcontext.regs[3], 16);
|
||||
writeNumber(file, "fault_x4=", context->uc_mcontext.regs[4], 16);
|
||||
writeNumber(file, "fault_x5=", context->uc_mcontext.regs[5], 16);
|
||||
writeNumber(file, "fault_x6=", context->uc_mcontext.regs[6], 16);
|
||||
writeNumber(file, "fault_x7=", context->uc_mcontext.regs[7], 16);
|
||||
writeNumber(file, "fault_pstate=", context->uc_mcontext.pstate, 16);
|
||||
#elif defined(__arm__)
|
||||
writeNumber(file, "fault_pc=", context->uc_mcontext.arm_pc, 16);
|
||||
writeNumber(file, "fault_sp=", context->uc_mcontext.arm_sp, 16);
|
||||
writeNumber(file, "fault_lr=", context->uc_mcontext.arm_lr, 16);
|
||||
writeNumber(file, "fault_fp=", context->uc_mcontext.arm_fp, 16);
|
||||
writeNumber(file, "fault_r0=", context->uc_mcontext.arm_r0, 16);
|
||||
writeNumber(file, "fault_r1=", context->uc_mcontext.arm_r1, 16);
|
||||
writeNumber(file, "fault_r2=", context->uc_mcontext.arm_r2, 16);
|
||||
writeNumber(file, "fault_r3=", context->uc_mcontext.arm_r3, 16);
|
||||
writeNumber(file, "fault_r12=", context->uc_mcontext.arm_ip, 16);
|
||||
writeNumber(file, "fault_cpsr=", context->uc_mcontext.arm_cpsr, 16);
|
||||
#endif
|
||||
}
|
||||
|
||||
void writeLinuxArchitecture(NativeFile file)
|
||||
{
|
||||
#if defined(__aarch64__)
|
||||
writeText(file, "architecture=arm64\n");
|
||||
#elif defined(__arm__)
|
||||
writeText(file, "architecture=arm32\n");
|
||||
#elif defined(__x86_64__)
|
||||
writeText(file, "architecture=x64\n");
|
||||
#elif defined(__i386__)
|
||||
writeText(file, "architecture=x86\n");
|
||||
#else
|
||||
writeText(file, "architecture=unknown\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void writeLinuxFaultFrames(NativeFile file, void* rawContext)
|
||||
{
|
||||
if (!rawContext)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const ucontext_t* context = static_cast<const ucontext_t*>(rawContext);
|
||||
writeText(file, "Fault registers used for symbol lookup:\n");
|
||||
#if defined(__aarch64__)
|
||||
writeNumber(file, " fault_frame_pc=", context->uc_mcontext.pc, 16);
|
||||
writeNumber(file, " fault_frame_lr=", context->uc_mcontext.regs[30], 16);
|
||||
writeNumber(file, " fault_frame_fp=", context->uc_mcontext.regs[29], 16);
|
||||
#elif defined(__arm__)
|
||||
writeNumber(file, " fault_frame_pc=", context->uc_mcontext.arm_pc, 16);
|
||||
writeNumber(file, " fault_frame_lr=", context->uc_mcontext.arm_lr, 16);
|
||||
writeNumber(file, " fault_frame_fp=", context->uc_mcontext.arm_fp, 16);
|
||||
#elif defined(__x86_64__)
|
||||
writeNumber(file, " fault_frame_pc=", context->uc_mcontext.gregs[REG_RIP], 16);
|
||||
#elif defined(__i386__)
|
||||
writeNumber(file, " fault_frame_pc=", context->uc_mcontext.gregs[REG_EIP], 16);
|
||||
#endif
|
||||
}
|
||||
|
||||
void linuxSignalHandler(int signalNumber, siginfo_t* information, void* context)
|
||||
{
|
||||
if (handlingCrash.test_and_set())
|
||||
{
|
||||
_exit(128 + signalNumber);
|
||||
}
|
||||
OutputTarget* target = outputTarget.load(std::memory_order_acquire);
|
||||
char stackPath[PATH_MAX] = {};
|
||||
struct timespec time = {};
|
||||
clock_gettime(CLOCK_REALTIME, &time);
|
||||
struct tm localTime = {};
|
||||
localtime_r(&time.tv_sec, &localTime);
|
||||
if (target)
|
||||
{
|
||||
std::snprintf(stackPath, sizeof(stackPath),
|
||||
"%s/crash_stack_%04d%02d%02d_%02d%02d%02d_%03ld.txt",
|
||||
target->directory.c_str(), localTime.tm_year + 1900, localTime.tm_mon + 1,
|
||||
localTime.tm_mday, localTime.tm_hour, localTime.tm_min, localTime.tm_sec,
|
||||
time.tv_nsec / 1000000L);
|
||||
}
|
||||
NativeFile file = target ? open(stackPath, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600) : invalidFile;
|
||||
const bool ownsFile = file != invalidFile;
|
||||
if (!ownsFile)
|
||||
{
|
||||
file = STDERR_FILENO;
|
||||
}
|
||||
writeText(file, "VrUtils crash report\n");
|
||||
writeLinuxArchitecture(file);
|
||||
writeNumber(file, "pid=", getpid());
|
||||
writeNumber(file, "tid=", static_cast<uint64_t>(syscall(SYS_gettid)));
|
||||
writeNumber(file, "unix_time=", static_cast<uint64_t>(time.tv_sec));
|
||||
writeNumber(file, "signal=", signalNumber);
|
||||
if (information)
|
||||
{
|
||||
writeNumber(file, "si_code_unsigned=", static_cast<unsigned int>(information->si_code));
|
||||
if (information->si_code > 0 && signalNumber != SIGABRT)
|
||||
{
|
||||
writeNumber(file, "fault_address=", reinterpret_cast<uintptr_t>(information->si_addr), 16);
|
||||
}
|
||||
}
|
||||
writeLinuxContext(file, context);
|
||||
writeLinuxFaultFrames(file, context);
|
||||
writeText(file, "Faulting instruction address is fault_pc above; resolve it against the maps below.\n");
|
||||
|
||||
// 路径、加载地址和文件偏移用于离线解析 PIE/共享库中的地址。
|
||||
writeText(file, "Memory maps (/proc/self/maps):\n");
|
||||
const int maps = open("/proc/self/maps", O_RDONLY | O_CLOEXEC);
|
||||
if (maps >= 0)
|
||||
{
|
||||
char buffer[4096];
|
||||
ssize_t size = 0;
|
||||
while ((size = read(maps, buffer, sizeof(buffer))) > 0)
|
||||
{
|
||||
writeBytes(file, buffer, static_cast<size_t>(size));
|
||||
}
|
||||
close(maps);
|
||||
}
|
||||
if (ownsFile)
|
||||
{
|
||||
fsync(file);
|
||||
}
|
||||
writeText(file, "Faulting thread backtrace (includes signal trampoline/handler):\n");
|
||||
#if defined(__GLIBC__)
|
||||
// backtrace 并非 POSIX 异步信号安全接口,只尽力展开;原始 PC 和映射已先写入。
|
||||
void* frames[maxFrames];
|
||||
const int count = backtrace(frames, maxFrames);
|
||||
for (int index = 0; index < count; ++index)
|
||||
{
|
||||
writeNumber(file, "frame=", index);
|
||||
writeNumber(file, " pc=", reinterpret_cast<uintptr_t>(frames[index]), 16);
|
||||
}
|
||||
backtrace_symbols_fd(frames, count, file);
|
||||
#else
|
||||
writeText(file, "Unwinder unavailable on this libc; use fault_pc and memory maps.\n");
|
||||
#endif
|
||||
if (ownsFile)
|
||||
{
|
||||
fsync(file);
|
||||
close(file);
|
||||
}
|
||||
// 恢复默认动作并给当前故障线程重发信号,保留 core dump 和信号退出状态。
|
||||
struct sigaction action = {};
|
||||
action.sa_handler = SIG_DFL;
|
||||
sigemptyset(&action.sa_mask);
|
||||
sigaction(signalNumber, &action, nullptr);
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, signalNumber);
|
||||
sigprocmask(SIG_UNBLOCK, &mask, nullptr);
|
||||
raise(signalNumber);
|
||||
_exit(128 + signalNumber);
|
||||
}
|
||||
|
||||
const int fatalSignals[] = {SIGSEGV, SIGABRT, SIGILL, SIGFPE, SIGBUS};
|
||||
#endif
|
||||
|
||||
void terminateHandler()
|
||||
{
|
||||
// 未捕获 C++ 异常最终进入 SIGABRT,保留 terminate 调用链。
|
||||
std::abort();
|
||||
}
|
||||
|
||||
struct HandlerState
|
||||
{
|
||||
std::mutex mutex;
|
||||
bool enabled = false;
|
||||
std::terminate_handler previousTerminate = nullptr;
|
||||
std::vector<std::unique_ptr<OutputTarget>> targets;
|
||||
#ifdef _WIN32
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER previousException = nullptr;
|
||||
PVOID vectoredExceptionHandler = nullptr;
|
||||
SignalHandler previousSignals[sizeof(fatalSignals) / sizeof(fatalSignals[0])] = {};
|
||||
#else
|
||||
struct sigaction previousSignals[sizeof(fatalSignals) / sizeof(fatalSignals[0])] = {};
|
||||
#endif
|
||||
};
|
||||
|
||||
HandlerState& handlerState()
|
||||
{
|
||||
// 不参与静态析构,程序退出阶段崩溃时路径和处理器仍有效。
|
||||
static HandlerState* state = new HandlerState;
|
||||
return *state;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
std::wstring fromUtf8(const std::string& text)
|
||||
{
|
||||
const int count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.c_str(), -1, nullptr, 0);
|
||||
if (count <= 0)
|
||||
{
|
||||
return std::wstring();
|
||||
}
|
||||
std::wstring result(static_cast<size_t>(count), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.c_str(), -1, &result[0], count);
|
||||
result.resize(static_cast<size_t>(count - 1));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool createDirectories(const std::wstring& path)
|
||||
{
|
||||
const DWORD attributes = GetFileAttributesW(path.c_str());
|
||||
if (attributes != INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
}
|
||||
const size_t slash = path.find_last_of(L"\\/");
|
||||
if (slash != std::wstring::npos && slash > 0 && !createDirectories(path.substr(0, slash)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return CreateDirectoryW(path.c_str(), nullptr) != FALSE || GetLastError() == ERROR_ALREADY_EXISTS;
|
||||
}
|
||||
#else
|
||||
bool createDirectories(const std::string& path)
|
||||
{
|
||||
struct stat information = {};
|
||||
if (stat(path.c_str(), &information) == 0)
|
||||
{
|
||||
return S_ISDIR(information.st_mode);
|
||||
}
|
||||
const size_t slash = path.find_last_of('/');
|
||||
if (slash != std::string::npos && slash > 0 && !createDirectories(path.substr(0, slash)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return mkdir(path.c_str(), 0700) == 0 || errno == EEXIST;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<OutputTarget> prepareTarget(const std::string& directory)
|
||||
{
|
||||
if (directory.find('\0') != std::string::npos)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr<OutputTarget> target(new OutputTarget);
|
||||
#ifdef _WIN32
|
||||
const std::wstring requested = fromUtf8(directory);
|
||||
wchar_t absolutePath[32768] = {};
|
||||
const DWORD length = GetFullPathNameW(requested.c_str(), 32768, absolutePath, nullptr);
|
||||
if (requested.empty() || length == 0 || length >= 32768 || !createDirectories(absolutePath))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
target->directoryPath = absolutePath;
|
||||
const int count = WideCharToMultiByte(CP_UTF8, 0, absolutePath, -1, nullptr, 0, nullptr, nullptr);
|
||||
if (count <= 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
target->directory.resize(static_cast<size_t>(count));
|
||||
WideCharToMultiByte(CP_UTF8, 0, absolutePath, -1, &target->directory[0], count, nullptr, nullptr);
|
||||
target->directory.resize(static_cast<size_t>(count - 1));
|
||||
#else
|
||||
if (!createDirectories(directory))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
char* absolutePath = realpath(directory.c_str(), nullptr);
|
||||
if (!absolutePath)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
target->directory = absolutePath;
|
||||
free(absolutePath);
|
||||
#endif
|
||||
return target;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void VrCrashHandler::Initialize()
|
||||
{
|
||||
// 多个入口只安装一次,后续主动关闭不会被重复初始化重新开启。
|
||||
static const bool initialized = []()
|
||||
{
|
||||
// Lambda has no implicit this pointer; qualify the public function explicitly.
|
||||
const bool enabled = VrCrashHandler::Enable();
|
||||
if (!enabled)
|
||||
{
|
||||
std::fputs("[VrUtils] failed to enable crash reporting\n", stderr);
|
||||
}
|
||||
return enabled;
|
||||
}();
|
||||
static_cast<void>(initialized);
|
||||
}
|
||||
|
||||
bool VrCrashHandler::Enable(const std::string& outputDirectory)
|
||||
{
|
||||
#if defined(_WIN32) || defined(__linux__)
|
||||
// 回调中的原子访问必须无锁,不能在信号打断时进入库内部互斥量。
|
||||
if (!outputTarget.is_lock_free())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
HandlerState& state = handlerState();
|
||||
std::lock_guard<std::mutex> lock(state.mutex);
|
||||
OutputTarget* target = outputTarget.load();
|
||||
const std::string directory = outputDirectory.empty()
|
||||
? (target ? target->directory : VrPathUtils::GetLogDirectory() + "/crash") : outputDirectory;
|
||||
if (!target || directory != target->directory)
|
||||
{
|
||||
std::unique_ptr<OutputTarget> prepared = prepareTarget(directory);
|
||||
if (!prepared)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
target = prepared.get();
|
||||
state.targets.push_back(std::move(prepared));
|
||||
}
|
||||
if (state.enabled)
|
||||
{
|
||||
outputTarget.store(target, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
prepareUnwinder();
|
||||
outputTarget.store(target, std::memory_order_release);
|
||||
handlingCrash.clear();
|
||||
size_t installed = 0;
|
||||
for (; installed < sizeof(fatalSignals) / sizeof(fatalSignals[0]); ++installed)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
state.previousSignals[installed] = std::signal(fatalSignals[installed], windowsSignalHandler);
|
||||
if (state.previousSignals[installed] == SIG_ERR)
|
||||
{
|
||||
break;
|
||||
}
|
||||
#else
|
||||
struct sigaction action = {};
|
||||
action.sa_sigaction = linuxSignalHandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
if (sigaction(fatalSignals[installed], &action, &state.previousSignals[installed]) != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (installed != sizeof(fatalSignals) / sizeof(fatalSignals[0]))
|
||||
{
|
||||
while (installed > 0)
|
||||
{
|
||||
--installed;
|
||||
#ifdef _WIN32
|
||||
std::signal(fatalSignals[installed], state.previousSignals[installed]);
|
||||
#else
|
||||
sigaction(fatalSignals[installed], &state.previousSignals[installed], nullptr);
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
state.vectoredExceptionHandler = AddVectoredExceptionHandler(1, windowsVectoredExceptionHandler);
|
||||
if (!state.vectoredExceptionHandler)
|
||||
{
|
||||
while (installed > 0)
|
||||
{
|
||||
--installed;
|
||||
std::signal(fatalSignals[installed], state.previousSignals[installed]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
state.previousException = SetUnhandledExceptionFilter(windowsExceptionHandler);
|
||||
#endif
|
||||
state.previousTerminate = std::set_terminate(terminateHandler);
|
||||
state.enabled = true;
|
||||
return true;
|
||||
#else
|
||||
(void)outputDirectory;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VrCrashHandler::Disable()
|
||||
{
|
||||
#if defined(_WIN32) || defined(__linux__)
|
||||
HandlerState& state = handlerState();
|
||||
std::lock_guard<std::mutex> lock(state.mutex);
|
||||
if (!state.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (std::get_terminate() == terminateHandler)
|
||||
{
|
||||
std::set_terminate(state.previousTerminate);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (state.vectoredExceptionHandler)
|
||||
{
|
||||
RemoveVectoredExceptionHandler(state.vectoredExceptionHandler);
|
||||
state.vectoredExceptionHandler = nullptr;
|
||||
}
|
||||
const auto currentException = SetUnhandledExceptionFilter(state.previousException);
|
||||
if (currentException != windowsExceptionHandler)
|
||||
{
|
||||
SetUnhandledExceptionFilter(currentException);
|
||||
}
|
||||
#endif
|
||||
for (size_t index = 0; index < sizeof(fatalSignals) / sizeof(fatalSignals[0]); ++index)
|
||||
{
|
||||
// 若第三方后来接管了处理器,不覆盖它的配置。
|
||||
#ifdef _WIN32
|
||||
const auto current = std::signal(fatalSignals[index], state.previousSignals[index]);
|
||||
if (current != windowsSignalHandler && current != SIG_ERR)
|
||||
{
|
||||
std::signal(fatalSignals[index], current);
|
||||
}
|
||||
#else
|
||||
struct sigaction current = {};
|
||||
if (sigaction(fatalSignals[index], nullptr, ¤t) == 0 &&
|
||||
(current.sa_flags & SA_SIGINFO) && current.sa_sigaction == linuxSignalHandler)
|
||||
{
|
||||
sigaction(fatalSignals[index], &state.previousSignals[index], nullptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
state.enabled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool VrCrashHandler::IsEnabled()
|
||||
{
|
||||
#if defined(_WIN32) || defined(__linux__)
|
||||
HandlerState& state = handlerState();
|
||||
std::lock_guard<std::mutex> lock(state.mutex);
|
||||
return state.enabled;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string VrCrashHandler::GetOutputDirectory()
|
||||
{
|
||||
#if defined(_WIN32) || defined(__linux__)
|
||||
OutputTarget* target = outputTarget.load(std::memory_order_acquire);
|
||||
return target ? target->directory : std::string();
|
||||
#else
|
||||
return std::string();
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// 对象文件被链接后独立自动启用,不依赖 VrLog 的初始化顺序。
|
||||
VrCrashHandler::AutoInitializer crashHandlerAutoInitializer;
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
#include "VrLog.h"
|
||||
#include "VrLog.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <stdarg.h>
|
||||
#include <clocale>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <stdarg.h>
|
||||
#include <clocale>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
#include <log4cpp/Category.hh>
|
||||
#include <log4cpp/Appender.hh>
|
||||
@ -16,12 +16,12 @@
|
||||
#include <log4cpp/RollingFileAppender.hh>
|
||||
#include <log4cpp/PropertyConfigurator.hh>
|
||||
|
||||
#include "VrFileUtils.h"
|
||||
#include "VrFileUtils.h"
|
||||
#include "VrPathUtils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <direct.h>
|
||||
#include <shlobj.h>
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include <WinSock2.h>
|
||||
#pragma comment(lib,"Ws2_32.lib")
|
||||
@ -120,58 +120,6 @@ static inline const char* TruncateFilePath(const char* filePath) {
|
||||
#endif // __ANROID__
|
||||
///******************************************************************************************************************/
|
||||
|
||||
// 动态获取日志路径(包含应用名称)
|
||||
static std::string GetLogPath()
|
||||
{
|
||||
std::string appName = "YJApp"; // 默认应用名称
|
||||
|
||||
// 尝试从进程路径获取应用名称
|
||||
#ifdef _WIN32
|
||||
// 获取用户文档目录
|
||||
|
||||
// 如果无法获取用户目录,则使用原来的方式
|
||||
char exePath[MAX_PATH];
|
||||
if (GetModuleFileNameA(NULL, exePath, MAX_PATH) != 0) {
|
||||
std::string fullPath(exePath);
|
||||
size_t pos = fullPath.find_last_of("\\");
|
||||
if (pos != std::string::npos) {
|
||||
std::string fileName = fullPath.substr(pos + 1);
|
||||
size_t dotPos = fileName.find_last_of(".");
|
||||
if (dotPos != std::string::npos) {
|
||||
appName = fileName.substr(0, dotPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char userPath[MAX_PATH];
|
||||
if (SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, userPath) == S_OK) {
|
||||
std::string userDir(userPath);
|
||||
return userDir + "\\" + appName + "\\Log";
|
||||
}
|
||||
|
||||
return ".\\" + appName + "\\Log";
|
||||
#else
|
||||
// Linux/Android平台
|
||||
char exePath[1024];
|
||||
ssize_t len = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1);
|
||||
if (len != -1) {
|
||||
exePath[len] = '\0';
|
||||
std::string fullPath(exePath);
|
||||
size_t pos = fullPath.find_last_of("/");
|
||||
if (pos != std::string::npos) {
|
||||
appName = fullPath.substr(pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
return "/sdcard/" + appName + "/Log";
|
||||
#else
|
||||
return appName + "/Log";
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// 自动初始化和清理类,确保程序启动时初始化日志,退出时清理日志资源
|
||||
class VrLogAutoCleaner {
|
||||
public:
|
||||
@ -188,8 +136,8 @@ public:
|
||||
static VrLogAutoCleaner g_logAutoCleaner;
|
||||
|
||||
// 实际执行初始化的内部函数
|
||||
static void DoInitLog()
|
||||
{
|
||||
static void DoInitLog()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// 设置控制台代码页为 UTF-8,解决中文乱码
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
@ -199,7 +147,7 @@ static void DoInitLog()
|
||||
#endif
|
||||
|
||||
// if file exist then load file config
|
||||
std::string logPath = GetLogPath();
|
||||
std::string logPath = VrPathUtils::GetLogDirectory();
|
||||
std::string logConfig = logPath + PATH_SEP + LOG_CONFIG_FILE;
|
||||
if (CVrFileUtils::IsFileExist(logConfig.c_str()))
|
||||
{
|
||||
@ -308,15 +256,15 @@ void VrLogUtils::EchoLog(VrLogLevel eLogLevel, const char* sFilePath, const int
|
||||
}
|
||||
|
||||
// load log info
|
||||
va_list args;
|
||||
va_start(args, sFormat);
|
||||
char szLogInfo[1024] = { 0 };
|
||||
#ifdef _WIN32
|
||||
vsnprintf_s(szLogInfo, sizeof(szLogInfo), _TRUNCATE, sFormat, args);
|
||||
#else
|
||||
vsnprintf(szLogInfo, sizeof(szLogInfo), sFormat, args);
|
||||
#endif
|
||||
va_end(args);
|
||||
va_list args;
|
||||
va_start(args, sFormat);
|
||||
char szLogInfo[1024] = { 0 };
|
||||
#ifdef _WIN32
|
||||
vsnprintf_s(szLogInfo, sizeof(szLogInfo), _TRUNCATE, sFormat, args);
|
||||
#else
|
||||
vsnprintf(szLogInfo, sizeof(szLogInfo), sFormat, args);
|
||||
#endif
|
||||
va_end(args);
|
||||
log4cpp::Category& root = log4cpp::Category::getRoot();
|
||||
|
||||
switch (eLogLevel)
|
||||
|
||||
55
VrUtils/Src/VrPathUtils.cpp
Normal file
55
VrUtils/Src/VrPathUtils.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include "VrPathUtils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#pragma comment(lib, "Shell32.lib")
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace VrPathUtils
|
||||
{
|
||||
std::string GetLogDirectory()
|
||||
{
|
||||
std::string appName = "YJApp";
|
||||
#ifdef _WIN32
|
||||
char exePath[MAX_PATH] = {};
|
||||
if (GetModuleFileNameA(nullptr, exePath, MAX_PATH) != 0)
|
||||
{
|
||||
const std::string fullPath(exePath);
|
||||
const size_t separator = fullPath.find_last_of("\\");
|
||||
if (separator != std::string::npos)
|
||||
{
|
||||
const std::string fileName = fullPath.substr(separator + 1);
|
||||
const size_t dot = fileName.find_last_of('.');
|
||||
appName = dot == std::string::npos ? fileName : fileName.substr(0, dot);
|
||||
}
|
||||
}
|
||||
char userPath[MAX_PATH] = {};
|
||||
if (SHGetFolderPathA(nullptr, CSIDL_PERSONAL, nullptr, SHGFP_TYPE_CURRENT, userPath) == S_OK)
|
||||
{
|
||||
return std::string(userPath) + "\\" + appName + "\\Log";
|
||||
}
|
||||
return ".\\" + appName + "\\Log";
|
||||
#else
|
||||
char exePath[1024] = {};
|
||||
const ssize_t length = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1);
|
||||
if (length > 0)
|
||||
{
|
||||
exePath[length] = '\0';
|
||||
const std::string fullPath(exePath);
|
||||
const size_t separator = fullPath.find_last_of('/');
|
||||
if (separator != std::string::npos && separator + 1 < fullPath.size())
|
||||
{
|
||||
appName = fullPath.substr(separator + 1);
|
||||
}
|
||||
}
|
||||
#ifdef __ANDROID__
|
||||
return "/sdcard/" + appName + "/Log";
|
||||
#else
|
||||
return appName + "/Log";
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -22,7 +22,9 @@ unix:!macx {
|
||||
win32-msvc*:QMAKE_CXXFLAGS += /D_HAS_AUTO_PTR_ETC=1
|
||||
|
||||
HEADERS += \
|
||||
Inc/IVrUtils.h \
|
||||
Inc/IVrUtils.h \
|
||||
Inc/VrCrashHandler.h \
|
||||
Inc/VrPathUtils.h \
|
||||
Inc/VrDateUtils.h \
|
||||
Inc/VrFileUtils.h \
|
||||
Inc/VrLog.h \
|
||||
@ -97,7 +99,9 @@ HEADERS += \
|
||||
jsoncpp/json/value.h \
|
||||
jsoncpp/json/writer.h
|
||||
|
||||
SOURCES += \
|
||||
SOURCES += \
|
||||
Src/VrCrashHandler.cpp \
|
||||
Src/VrPathUtils.cpp \
|
||||
Src/VrDateUtils.cpp \
|
||||
Src/VrFileUtils.cpp \
|
||||
Src/VrLog.cpp \
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user