简体   繁体   English

在 Windows XP 上初始化 plog::RollingFileAppender 触发访问冲突(空指针)

[英]Initializing plog::RollingFileAppender on Windows XP Triggers Access Violation (Null Pointer)

When using [plog][1] on Windows XP.在 Windows XP 上使用 [plog][1] 时。 In this case, the code is:在这种情况下,代码是:

void LogInit(void)
{   
    static plog::RollingFileAppender<plog::TxtFormatter> fileAppender("log.log");

Using Visual Studio 2019 but the project uses the platform toolset Visual Studio 2017 - Windows XP (v141_XP)使用 Visual Studio 2019 但项目使用平台工具集 Visual Studio 2017 - Windows XP (v141_XP)

The output assembly is: output 组件为:

;   COMDAT _LogInit
_TEXT   SEGMENT
_status$1$ = -516                   ; size = 4
_appender$66 = -516                 ; size = 4
$T65 = -512                     ; size = 256
$T64 = -512                     ; size = 256
$T62 = -512                     ; size = 256
$T60 = -512                     ; size = 256
$T58 = -256                     ; size = 256
$T57 = -256                     ; size = 256
$T41 = -256                     ; size = 256
_LogInit PROC                       ; COMDAT

; 108  : {  

  00000 55       push    ebp
  00001 8b ec        mov     ebp, esp
  00003 83 e4 f8     and     esp, -8            ; fffffff8H

; 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]

The null pointer is because EAX (__tls_array) and ECX (__tls_index) area both null. null 指针是因为 EAX (__tls_array) 和 ECX (__tls_index) 区域都是 null。 Output from WinDbg:来自 WinDbg 的 Output:

TGLOBALFLAG:  70

APPLICATION_VERIFIER_FLAGS:  0

CONTEXT:  (.ecxr)
eax=00000000 ebx=00000000 ecx=00000000 edx=7c90e4f4 esi=0012f624 edi=00000000
eip=1000366a esp=001afda4 ebp=001affb4 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010216
LogTest!LogInit+0x1a:
1000366a 8b3488          mov     esi,dword ptr [eax+ecx*4] ds:0023:00000000=????????
Resetting default scope

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

PROCESS_NAME:  notepad.exe

READ_ADDRESS:  00000000 

ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.

EXCEPTION_CODE_STR:  c0000005

EXCEPTION_PARAMETER1:  00000000

EXCEPTION_PARAMETER2:  00000000

FAULTING_LOCAL_VARIABLE_NAME:  fileAppender

STACK_TEXT:  
001affb4 7c80b713     00000000 00000000 0012f624 LogTest!LogInit+0x1a
001affec 00000000     10003650 00000000 00000000 kernel32!BaseThreadStart+0x37


STACK_COMMAND:  ~1s; .ecxr ; kb

FAULTING_SOURCE_LINE:  d:\test\logtest.cpp

FAULTING_SOURCE_FILE:  d:\test\logtest.cpp

FAULTING_SOURCE_LINE_NUMBER:  109

FAULTING_SOURCE_CODE:  
   105: 
   106: // This is an example of an exported function.
   107: LogInit_API void LogInit(void)
   108: {   
>  109:     static plog::RollingFileAppender<plog::TxtFormatter> fileAppender(";pg.log");
   110:     plog::init(plog::info, &fileAppender);
   111:     
   112: 
   113:     
   114: 


SYMBOL_NAME:  LogTest!LogInit+1a

MODULE_NAME: LogTest

IMAGE_NAME:  LogTest.dll

FAILURE_BUCKET_ID:  NULL_POINTER_READ_c0000005_LogTest.dll!LogInit

OS_VERSION:  5.1.2600.5512

BUILDLAB_STR:  xpsp

OSPLATFORM_TYPE:  x86

OSNAME:  Windows XP

FAILURE_ID_HASH:  {0218fa42-bce4-328f-5683-a7e3657927fc}

Followup:     MachineOwner
---------

Code for affected class is:受影响的 class 的代码是:

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

            if (m_firstWrite)
            {
                openLogFile();
                m_firstWrite = false;
            }
            else if (m_maxFiles > 0 && m_fileSize > m_maxFileSize && static_cast<size_t>(-1) != m_fileSize)
            {
                rollLogFiles();
            }

            size_t bytesWritten = m_file.write(Converter::convert(Formatter::format(record)));

            if (static_cast<size_t>(-1) != bytesWritten)
            {
                m_fileSize += bytesWritten;
            }
        }

        void setFileName(const util::nchar* fileName)
        {
            util::MutexLock lock(m_mutex);

            util::splitFileName(fileName, m_fileNameNoExt, m_fileExt);

            m_file.close();
            m_firstWrite = true;
        }

#ifdef _WIN32
        void setFileName(const char* fileName)
        {
            setFileName(util::toWide(fileName).c_str());
        }
#endif

        void setMaxFiles(int maxFiles)
        {
            m_maxFiles = maxFiles;
        }

        void setMaxFileSize(size_t maxFileSize)
        {
            m_maxFileSize = (std::max)(maxFileSize, static_cast<size_t>(1000)); // set a lower limit for the maxFileSize
        }

        void rollLogFiles()
        {
            m_file.close();

            util::nstring lastFileName = buildFileName(m_maxFiles - 1);
            util::File::unlink(lastFileName.c_str());

            for (int fileNumber = m_maxFiles - 2; fileNumber >= 0; --fileNumber)
            {
                util::nstring currentFileName = buildFileName(fileNumber);
                util::nstring nextFileName = buildFileName(fileNumber + 1);

                util::File::rename(currentFileName.c_str(), nextFileName.c_str());
            }

            openLogFile();
            m_firstWrite = false;
        }

    private:
        void openLogFile()
        {
            util::nstring fileName = buildFileName();
            m_fileSize = m_file.open(fileName.c_str());

            if (0 == m_fileSize)
            {
                size_t bytesWritten = m_file.write(Converter::header(Formatter::header()));

                if (static_cast<size_t>(-1) != bytesWritten)
                {
                    m_fileSize += bytesWritten;
                }
            }
        }

        util::nstring buildFileName(int fileNumber = 0)
        {
            util::nostringstream ss;
            ss << m_fileNameNoExt;

            if (fileNumber > 0)
            {
                ss << '.' << fileNumber;
            }

            if (!m_fileExt.empty())
            {
                ss << '.' << m_fileExt;
            }

            return ss.str();
        }

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

Is there code or compiler settings that can be modified to fix/remove the references to __tls_array / __tls_index.是否有可以修改的代码或编译器设置来修复/删除对 __tls_array / __tls_index 的引用。 This occurs in both debug & release builds.这发生在调试和发布版本中。 [1]: https://github.com/SergiusTheBest/plog [1]: https://github.com/SergiusTheBest/plog

Setting compiler option /Zc:threadSafeInit- removes the references to __tls_array and __tls_index and stops the access violation crash.设置编译器选项 /Zc:threadSafeInit- 删除对 __tls_array 和 __tls_index 的引用并停止访问冲突崩溃。

Microsoft documentation here mentions: 此处的 Microsoft 文档提到:

In the C++11 standard, block scope variables with static or thread storage duration must be zero-initialized before any other initialization takes place.在 C++11 标准中,具有 static 的块 scope 变量或线程存储持续时间必须在任何其他初始化发生之前进行零初始化。 Initialization occurs when control first passes through the declaration of the variable.初始化发生在控制第一次通过变量的声明时。 If an exception is thrown during initialization, the variable is considered uninitialized, and initialization is re-attempted the next time control passes through the declaration.如果在初始化期间抛出异常,则认为该变量未初始化,并且在下一次控制通过声明时重新尝试初始化。 If control enters the declaration concurrently with initialization, the concurrent execution blocks while initialization is completed.如果控制与初始化同时进入声明,则在初始化完成时并发执行阻塞。 The behavior is undefined if control re-enters the declaration recursively during initialization.如果控件在初始化期间递归地重新进入声明,则行为未定义。 By default, Visual Studio starting in Visual Studio 2015 implements this standard behavior.默认情况下,从 Visual Studio 2015 开始的 Visual Studio 实现此标准行为。 This behavior may be explicitly specified by setting the /Zc:threadSafeInit compiler option.可以通过设置 /Zc:threadSafeInit 编译器选项显式指定此行为。

The /Zc:threadSafeInit compiler option is on by default. /Zc:threadSafeInit 编译器选项默认打开。 The /permissive- option does not affect /Zc:threadSafeInit. /permissive- 选项不影响 /Zc:threadSafeInit。

Thread-safe initialization of static local variables relies on code implemented in the Universal C run-time library (UCRT). static 局部变量的线程安全初始化依赖于通用 C 运行时库 (UCRT) 中实现的代码。 To avoid taking a dependency on the UCRT, or to preserve the non-thread-safe initialization behavior of versions of Visual Studio prior to Visual Studio 2015, use the /Zc:threadSafeInit- option.若要避免依赖 UCRT,或保留 Visual Studio 2015 之前版本的 Visual Studio 的非线程安全初始化行为,请使用 /Zc:threadSafeInit- 选项。 If you know that thread-safety is not required, use this option to generate slightly smaller, faster code around static local declarations.如果您知道不需要线程安全,请使用此选项围绕 static 局部声明生成更小、更快的代码。

Thread-safe static local variables use thread-local storage (TLS) internally to provide efficient execution when the static has already been initialized.线程安全的 static 局部变量在内部使用线程局部存储 (TLS) 以在 static 已经初始化时提供高效执行。 The implementation of this feature relies on Windows operating system support functions in Windows Vista and later operating systems.此功能的实现依赖于 Windows Vista 及更高版本操作系统中的 Windows 操作系统支持功能。 Windows XP, Windows Server 2003, and older operating systems do not have this support, so they do not get the efficiency advantage. Windows XP、Windows Server 2003,以及更老的操作系统没有这个支持,所以他们没有获得效率优势。 These operating systems also have a lower limit on the number of TLS sections that can be loaded.这些操作系统对可以加载的 TLS 部分的数量也有下限。 Exceeding the TLS section limit can cause a crash.超过 TLS 部分限制可能会导致崩溃。 If this is a problem in your code, especially in code that must run on older operating systems, use /Zc:threadSafeInit- to disable the thread-safe initialization code.如果这是您的代码中的问题,尤其是必须在旧操作系统上运行的代码中,请使用 /Zc:threadSafeInit- 禁用线程安全初始化代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM