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

Viewed 17

When using [plog][1] on Windows XP. 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)

The output assembly is:

;   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. Output from WinDbg:

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:

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. This occurs in both debug & release builds. [1]: https://github.com/SergiusTheBest/plog

1 Answers

Setting compiler option /Zc:threadSafeInit- removes the references to __tls_array and __tls_index and stops the access violation crash.

Microsoft documentation here mentions:

In the C++11 standard, block scope variables with static or thread storage duration must be zero-initialized before any other initialization takes place. 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. This behavior may be explicitly specified by setting the /Zc:threadSafeInit compiler option.

The /Zc:threadSafeInit compiler option is on by default. The /permissive- option does not affect /Zc:threadSafeInit.

Thread-safe initialization of static local variables relies on code implemented in the Universal C run-time library (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. If you know that thread-safety is not required, use this option to generate slightly smaller, faster code around static local declarations.

Thread-safe static local variables use thread-local storage (TLS) internally to provide efficient execution when the static has already been initialized. The implementation of this feature relies on Windows operating system support functions in Windows Vista and later operating systems. Windows XP, Windows Server 2003, and older operating systems do not have this support, so they do not get the efficiency advantage. These operating systems also have a lower limit on the number of TLS sections that can be loaded. Exceeding the TLS section limit can cause a crash. 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.

Related