feat:增加crash;单独的路径工具

This commit is contained in:
YangSn 2026-10-08 14:56:58 +08:00
parent 76b63103af
commit 6626ee33e5
11 changed files with 1443 additions and 241 deletions

View File

@ -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

View File

@ -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()) {

View File

@ -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);
}

View File

@ -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

View 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

View File

@ -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
View File

@ -0,0 +1,11 @@
#ifndef VR_PATH_UTILS_H
#define VR_PATH_UTILS_H
#include <string>
namespace VrPathUtils
{
std::string GetLogDirectory();
}
#endif

View 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, &current) == 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;
}

View File

@ -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)

View 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
}
}

View File

@ -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 \