Windows XP下初始化plog::RollingFileAppender触发空指针访问违规
在Windows XP系统中,使用Visual Studio 2019开发,项目采用**Visual Studio 2017 - Windows XP (v141_XP)**平台工具集,初始化plog日志库的静态RollingFileAppender对象时触发访问违规(空指针异常),异常代码为c0000005。
触发代码
void LogInit(void) { static plog::RollingFileAppender<plog::TxtFormatter> fileAppender("log.log"); plog::init(plog::info, &fileAppender); }
异常细节
生成的汇编代码显示,程序尝试访问__tls_array和__tls_index:
; 109 : static plog::RollingFileAppender<plog::TxtFormatter> fileAppender("log.log"); 00006 64 a1 00 00 00 00 mov eax, DWORD PTR fs:__tls_array 0000c 81 ec 04 02 00 00 sub esp, 516 ; 00000204H 00012 8b 0d 00 00 00 00 mov ecx, DWORD PTR __tls_index 00018 53 push ebx 00019 56 push esi 0001a 8b 34 88 mov esi, DWORD PTR [eax+ecx*4]
WinDbg调试输出显示__tls_array和__tls_index均为空指针,导致访问0x00000000地址触发异常:
EXCEPTION_RECORD: (.exr -1) ExceptionAddress: 1000366a (LogTest!LogInit+0x0000001a) ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000000 Parameter[1]: 00000000 Attempt to read from address 00000000
涉及的RollingFileAppender类核心代码
namespace plog { template<class Formatter, class Converter = NativeEOLConverter<UTF8Converter> > class PLOG_LINKAGE_HIDDEN RollingFileAppender : public IAppender { public: RollingFileAppender(const util::nchar* fileName, size_t maxFileSize = 0, int maxFiles = 0) : m_fileSize(), m_maxFileSize(), m_maxFiles(maxFiles), m_firstWrite(true) { setFileName(fileName); setMaxFileSize(maxFileSize); } #ifdef _WIN32 RollingFileAppender(const char* fileName, size_t maxFileSize = 0, int maxFiles = 0) : m_fileSize(), m_maxFileSize(), m_maxFiles(maxFiles), m_firstWrite(true) { setFileName(fileName); setMaxFileSize(maxFileSize); } #endif virtual void write(const Record& record) { util::MutexLock lock(m_mutex); // ...日志写入逻辑 } private: util::Mutex m_mutex; util::File m_file; size_t m_fileSize; size_t m_maxFileSize; int m_maxFiles; util::nstring m_fileExt; util::nstring m_fileNameNoExt; bool m_firstWrite; // ...其他私有方法 }; }
提问:是否可以通过修改代码或编译器设置消除对__tls_array/__tls_index的引用,解决该空指针访问违规问题?该问题在Debug和Release构建中均会出现。
问题根源是Visual Studio的线程安全静态初始化特性依赖TLS(线程本地存储),而Windows XP的DLL加载机制中,TLS可能在静态对象初始化阶段未完成初始化,导致空指针访问。可以通过以下两种方式解决:
一、调整编译器设置
禁用线程安全静态初始化
- 打开项目属性,导航到
C/C++ -> 代码生成 - 将
启用线程安全的静态初始化设置为否(/Zc:threadSafeInit-) - 该设置会关闭VS自动生成的TLS依赖代码,改用传统的静态初始化方式,避免访问未初始化的TLS变量。
- 打开项目属性,导航到
检查并禁用不必要的TLS相关选项
- 确认项目未启用
/GT(支持纤维安全的线程本地存储)选项,该选项会强制生成TLS相关代码,XP环境下可关闭。 - 确保运行库选择与v141_XP兼容的版本(如
多线程(/MT)或多线程DLL(/MD)),避免因运行库不兼容导致的TLS问题。
- 确认项目未启用
二、修改代码规避TLS依赖
1. 替换静态局部对象为动态分配
将静态局部对象改为全局动态分配对象,手动控制初始化时机,避免编译器生成的TLS依赖代码:
// 全局变量,存储日志appender指针 static plog::RollingFileAppender<plog::TxtFormatter>* g_logAppender = nullptr; void LogInit(void) { if (!g_logAppender) { // 手动初始化,避免静态初始化的TLS依赖 g_logAppender = new plog::RollingFileAppender<plog::TxtFormatter>("log.log"); plog::init(plog::info, g_logAppender); } }
注意:如果是DLL项目,需确保在DLL卸载时释放该对象,避免内存泄漏。
2. 替换依赖TLS的互斥实现
RollingFileAppender中的util::Mutex可能依赖TLS实现,可替换为Windows XP原生的互斥量API:
// 修改RollingFileAppender类的私有成员 namespace plog { template<class Formatter, class Converter = NativeEOLConverter<UTF8Converter> > class PLOG_LINKAGE_HIDDEN RollingFileAppender : public IAppender { // ...其他成员 private: HANDLE m_mutex; // 替换原util::Mutex // ...其他私有成员 // 修改构造函数 RollingFileAppender(const util::nchar* fileName, size_t maxFileSize = 0, int maxFiles = 0) : m_fileSize(), m_maxFileSize(), m_maxFiles(maxFiles), m_firstWrite(true) , m_mutex(CreateMutex(NULL, FALSE, NULL)) // 创建原生互斥量 { setFileName(fileName); setMaxFileSize(maxFileSize); } // 添加析构函数释放互斥量 ~RollingFileAppender() { if (m_mutex) { CloseHandle(m_mutex); } } // 修改write方法的互斥逻辑 virtual void write(const Record& record) { WaitForSingleObject(m_mutex, INFINITE); // 等待互斥量 try { // ...原日志写入逻辑 } catch(...) { ReleaseMutex(m_mutex); // 异常时释放互斥量 throw; } ReleaseMutex(m_mutex); // 正常释放互斥量 } // ...其他方法 }; }
该修改彻底移除了对plog内置Mutex的依赖,避免其可能引入的TLS调用。
内容的提问来源于stack exchange,提问作者Malcolm McCaffery

