안녕하세요. 까막입니다.
지난 포스팅에 이어 MSVS 2013 -> MSVS 2019 로 개발 환경을 옮기면서 발생한 2번째 문제입니다.

해당 환경에서 MSVS 2019 로 옮기면서 컴파일러 버전 업 및 옵티마이저 이득을 약간 얻고자 + 
MSVS 2013 기술지원이 종료되었는지 업데이트도 않되고 해서 변경을 진행했습니다.

현상
전체 빌드 프로젝트가 좀 크고 여러개여서 인지는 모르겠지만 전체 빌드 혹은 덩치 큰 플젝 빌드할 경우
윈도우가 파란 화면을 뛰우는 경우가 자주 발생하더군요.

관련 글들
https://developercommunity.visualstudio.com/t/multiple-times-a-day-a-blue-screen-when-working-in/274372

 

multiple times a day a blue screen when working in VS ...

We have 6 identical HP Z440 workstations with a Intel XEON E5-2630 V3 and 64 GB memory in our company for the developers we use all visual studio 201...

developercommunity.visualstudio.com

https://docs.microsoft.com/en-us/answers/questions/342963/visual-studio-2017-crashes-after-every-run-into-a.html

 

Visual Studio 2017 crashes after every run into a blue screen of death - Microsoft Q&A

 

docs.microsoft.com

결론
위와 관련된 자료들을 취합하여 여러가지 방법을 수행하여 보았으나 
최종적으로 윈도우의 페이징 파일을 자동 관리에서, 요청 크기 2배 이상으로 늘린 후 더 이상 현상이 발생하지 않네요.

결론적인 내용이지만 툴이 빌드할 때 페이징 파일을 사용하는지 생각보다 크게 늘어 있었구요
자동 확장되면서 무슨 문제가 생기는게 아닌가 라는 그런 의심만 남은 상태입니다. 

저같은 경우는 자동 상태에서 약 11GB 쓰고 있었는데, 40 GB로 확 늘려 버린 이후로는 약 1주일째 현상이 없네요.

 

PS. 조금 오래된 글들에 첨부된 이미지와 파일들이 모두 깨져 있네요.
       참고 자료 형태로 글을 남기는 곳인데, 저렇게 다 깨져 있어서 ㅡㅜ 
       시간이 될 때 마다, 조금씩 복구 해 보도록 하겠습니다. 

 

안녕하세요. 까막입니다.
오랜만에 글을 남기네요.

C++ 에서 싱글톤 패턴을 적용하여 인스턴스를 생성할 때, 인스턴스가 리턴되지 않고
무한 대기(내부적으로는 아래 설명에 따라 무한 루프)에 빠지는 문제에 대한 내용입니다.
이 문제는 Visual Studio 2015 (c++ 11 포함 이상)부터 상위 버전에서 발생할 수 있는 문제라고 되어 있네요.

 

싱글톤에 대한 간략한 설명 
https://boycoding.tistory.com/109

 

C++ 디자인 패턴 05. 싱글턴 패턴, Singleton Pattern

싱글턴 패턴, Singleton Pattern 오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공합니다. (GoF의 디자인 패턴 181쪽) GoF의 싱글턴 패턴은 의도와는 달리 득보다는

boycoding.tistory.com

 

MSDN
https://docs.microsoft.com/en-us/cpp/build/reference/zc-threadsafeinit-thread-safe-local-static-initialization?view=msvc-170 

 

/Zc:threadSafeInit (Thread-safe Local Static Initialization)

Learn more about: /Zc:threadSafeInit (Thread-safe Local Static Initialization)

docs.microsoft.com

 

비슷한 현상
https://forum.juce.com/t/calling-my-singleton-causes-eternal-loop-in-init-thread-header/37340

 

Calling my singleton causes eternal loop in _Init_thread_header

Hi there, I’m experiencing issues with one of my singletons, which I assume is related to threads somehow (pardon my modest knowledge of C++ and multithreading). This is what my function looks like: SettingsManager& SettingsManager::getInstance() { stati

forum.juce.com

 

겪은 현상 설명
최근 Visual Studio 2013 버전에서 Visual Studio 2019로 마이그레이션을 진행하면서 겪은 내용을 남겨 놓고자 글을 씁니다.
싱글톤을 리턴할 때 MSDN에서는 스레드 세이프하도록 가드를 넣었다고 설명하고 있습니다.
(아래 그림이 첫번째 인스턴스를 생성할 때, 가드를 치는 c runtime 내부 소스입니다.)

/Zc:threadSafeInit(스레드로부터 안전한 로컬 정적 초기화) - MSDN 설명중 일부 발췌
/ Zc:threadSafeInit 컴파일러 옵션은 컴파일러에게 스레드로부터 안전한 방식으로 정적 로컬(함수 범위) 변수를 초기화하도록 지시하므로 수동 동기화가 필요하지 않습니다. 초기화만 스레드로부터 안전합니다. 다중 스레드에 의한 정적 지역 변수의 사용 및 수정은 여전히 수동으로 동기화되어야 합니다. 이 옵션은 Visual Studio 2015부터 사용할 수 있습니다. 기본적으로 Visual Studio는 이 옵션을 활성화합니다.

즉, 위의 내용은 싱글톤을 패턴을 포함한 정적 로컬 변수의 초기화시 안정성을 보장하는 옵션에 대한 설명이며
Visual Studio 2015 부터는 기본 옵션으로 포함되어 있습니다.

멀티 스레드 환경에서도 정적 변수 초기화시 별다른 lock 없이 사용할 수 있도록 보장하는 내용이지요.
그게 아니라면 별도로 lock 을 걸어서 안정성을 보장해 주어야 합니다.

최근 테스트된 환경에서 고속으로 돌아가는 개별 스레드에서 싱글톤 객체를 생성할 때, 초기화 코드를 호출하고
해당 코드가 리턴되기 전에 다른 스레드에서 또 객체 생성을 호출하면 위 그림의 while 루프에 빠져 무한 루프를
도는 현상이 확인 되었습니다.

통상적으로 코드 자체에는 문제 없고, 로직이나 이런것에 전혀 문제가 없는 상황에서 발생하는데요
이번에 확인된 부분은 콘솔 터미널 에서는 문제없이 돌아가는 실행파일이, 리모트 터미널 환경에서 hang 걸리는 
극악한 환경에서 발생해서 원인을 찾는데 상당히 애를 먹었네요.

아마도 리모트 환경에서는 콘솔에 비하여 바이너리 로딩이나 초기화 등에서 조금더 지연이 발생 하나 봅니다.

해법은 컴파일러 옵션에서 /Zc:threadSafeInit- 옵션을 넣어서 간단하게 해결할 수 있습니다.
즉, 기본옵션인 /Zc:threadSafeInit 를 강제로 해제하는 내용입니다.

이런 경우 해당 객체의 스레드 안정성은 직접 보장해 주시거나, 기존 동작에 문제가 없었다면 옵션만 넣고 빌드하시면 됩니다.

해당 자료를 검색하고, 해결하는 과정에서 작업이 끝나고 보니 아래 자료에 도움될만한 내용이 있었네요.
https://www.slideshare.net/utilforever/c-korea-3rd-seminar-c-visual-studio

 

[C++ Korea 3rd Seminar] 새 C++은 새 Visual Studio에, 좌충우돌 마이그레이션 이야기

C++11을 시작으로 모던 C++이 도입된 지도 어느새 6년이라는 시간이 흘렀습니다. 올해는 C++17 표준이 도입될 예정입니다. 그만큼 많이 개선되고 새로운 기능들이 많이 도입되었기에 실무에서 사용

www.slideshare.net

 

오랜만에 MMF (Memory Mapped File)를 살펴볼 일이 있어 예전 자료를 찾아다 쓰면서.. 

이것 저것 실험하는 중 불편한 코드 정리 및 대충 알던거 살펴보기.. 잘모르던거 제대로 알기.. 

이런 저런 관점에서 정리해보는 글입니다.


MMF가 무엇인지 어떤 장단점을 가지고 있는지와 관련된 내용이나 참조 사항은 다음 링크..

위키 : http://ko.wikipedia.org/wiki/메모리_맵_파일

이전자료 : http://crowback.tistory.com/198

MSDN : http://msdn.microsoft.com/en-us/library/ms810613.aspx


우선 정리된 클래스 골격



class CMMFObject

{
#define BIGINT LARGE_INTEGER

public:
    CMMFObject()
    {
        m_FileSize.QuadPart = 0;
        m_FileHandle        = INVALID_HANDLE_VALUE;
        m_hMemoryMap        = NULL;
        m_pMemoryMap        = NULL;
        m_bAudoDelete       = false;
        m_FilePath[0]       = 0;
    }

    virtual ~CMMFObject() { Close(); }


    /* 만들어져 있는 MMF를 열 때 사용하는 열기 함수 */
    DWORD Open(const TCHAR* pszMapName, DWORD dwDesiredAccess = FILE_MAP_READ);


    /* 파일 핸들이 있거나 시스템 페이지를 이용하여 MMF를 생성하는 함수 */
    DWORD Create(HANDLE hFile, const TCHAR* pszMapName, LONGLONG iFilesize, LPSECURITY_ATTRIBUTES lpAttributes = NULL);


    /* 파일을 직접 지정하여 MMF를 생성하는 함수 */
    DWORD Create(const TCHAR* pszMapFilePath, const TCHAR* pszMapName, LONGLONG iFilesize, bool bAudoDelete = true, LPSECURITY_ATTRIBUTES lpAttributes = NULL);


    /* 열려 있는 모든 내부정보를 닫고, 초기화 */

    DWORD Close(DWORD rc = ERROR_SUCCESS);


    /* 생성된 MMF의 접근 가능한 주소 */

    LPBYTE GetPtr() { return m_pMemoryMap; }

protected:

    DWORD __MapViewOfFile(DWORD dwDesiredAccess )
    {
        m_pMemoryMap = (BYTE*)::MapViewOfFile(m_hMemoryMap, dwDesiredAccess, 0, 0, 0);
        if(!m_pMemoryMap)
            return Close(GetLastError());

        return ERROR_SUCCESS;
    }

    CMMFObject(const CMMFObject& ) { /* barrier */ }
    CMMFObject& operator = (const CMMFObject &) { return *this; /* barrier */ }

private:
    BIGINT m_FileSize;
    HANDLE m_FileHandle;
    HANDLE m_hMemoryMap;
    LPBYTE m_pMemoryMap;
    TCHAR  m_FilePath[MAX_PATH];
    bool   m_bAudoDelete;
};




간단한 사용법


CMMFObject a;
CMMFObject b;

/* a 객체는 C:\\a.map 파일을 열거나 생성하여 1024 크기로 만들고, 이름을 LOCAL_FILE 으로 지정한다.

    b 객체는 LOCAL_FILE 이름을 가진 MMF를 읽고/쓰기 모드로 연다. */
if(a.Create(_T("C:\\a.map"), _T("LOCAL_FILE"), 1024) == ERROR_SUCCESS)
    _tcscpy((LPTSTR)a.GetPtr(), _T("까마귀날자 배떨어진다."));


if(b.Open(_T("LOCAL_FILE"), FILE_MAP_READ | FILE_MAP_WRITE) == ERROR_SUCCESS)
    _tcscpy((LPTSTR)b.GetPtr(), _T("날아도 배 안떨어져요."));


/* a 객체는 시스템 페이지를 이용하여 1024 크기로 만들고, 이름을 SYSTEM_PAGE 으로 지정한다.

    b 객체는SYSTEM_PAGE 이름을 가진 MMF를 읽기 모드로 연다. */
if(a.Create(INVALID_HANDLE_VALUE, _T("SYSTEM_PAGE"), 1024) == ERROR_SUCCESS)
    _tcscpy((LPTSTR)a.GetPtr(), _T("까마귀날자 배떨어진다."));


if(b.Open(_T("SYSTEM_PAGE"), FILE_MAP_READ) == ERROR_SUCCESS)
    _tcscpy((LPTSTR)b.GetPtr(), _T("날아도 배 안떨어져요.")); <- 에러: 읽기 모드로만 열어서 쓰기 금지


CMMFObject c(a);   <- 에러: 복사 생성에 의한 다중 참조 금지

CMMFObject d = b;  <- 에러: 대입 연산에 의한 다중 참조 금지



코드를 다시 정리하면서...


1. MMF는 생성 시점에 바로 메모리에 올라가지 않는다.

     실제 생성한 MMF를 액세스 하는 시점에 해당 사용한 영역 만큼만 메모리에 올라간다.

     즉, 1 GB 짜리 MMF를 생성해도 시스템 메모리 사용량은 변함이 없고, 거기에 읽고/쓰는 영역이 생기면 그 때 변화한다.


2. 32 Bit OS에서 이론적으로 4GB까지 MMF를 생성할 수 있다고 하는데... 

     일반적으로 32 bit Windows 에서는 1.5~1.8 GB 까지 생성가능하다. 

     쫌더 쓰려면?  /3GB boot parameter 를 이용하여 2.5~2.7 GB 까지 가능하기는 하지만.. 서버용으로는 쓰기 곤란하다.

     즉, 32 Bit OS 에서는 많이 써봐야 1.8 GB 정도로 보면 된다.


3. MMF를 생성하면 메모리가 증가 하겠지만, 오픈 할 때도 증가???

     MMF를 생성하면 당연히 할당한 크기만큼 메모리가 증가한다. 

     그런데 MMF를 열면,  원래 MMF가 지정한 크기만큼 메모리가 다시 증가한다. 또 열면 또 그만큼 증가한다.


     아래의 그림을 참고하자. a는 MMF를 생성, b는 MMF를 읽기모드로 오픈한 객체 정보의 비교이다. 

     데이터에 모두 b 라는 문자열을 가졌지만.. 해당 메모리의 주소가 서로 다르다.

     MMF의 오픈이라는 의미는 생성해둔 MMF 주소를 가져다 빌려쓰는게 아닌.. 같은 크기만큼의 메모리를 만들어서

     미러형식으로 데이터의 동기화를 이루어 공유하는 것처럼 보이는 방식이다.


4. 파일을 직적 연결하여 MMF를 생성했을 때, 메모리 방식과 직접 파일 액세스 방식은 혼용하면 않된다.

     내가 직접 만든 파일로 MMF를 생성할 경우, 2가지 방식으로 액세스 할 수 있는 상태가 된다.

     하나는 직접 파일을  read/write 하는 방식과 다른 하나는 메모리를 매핑된 MMF의 주소를 이용함 메모리 방식이다.

     각각 혹은 2가지 방식을 동시에 사용할 수 있지만.. 매핑된 메모리에 쓴 내용이 바로 디스크에 반영되지 않기 때문에

     2가지 방식으로 데이터를 액세스 하면 같은 위치 영역의 디스크 데이터와  메모리 데이터가 틀어질 수 있다.


5. 그럼 파일이 빠르냐?  MMF가 빠르냐?

     MMF가 디스크에 스왑되어 있을 경우는 메모리에 올라갈 동안 지연이 일부 생기지만.. 일단 메모리에 올라간 이후라면 

     MMF가 빠르다.. 랜덤 액세스일 경우는 성능 차이가 많이 벌어진다. 

     하지만 MMF는 파일 크기를 변경할 수 없기 때문에 쓰기 작업을 이용하여 늘려가면서 쓰는것이 불가능하다.

     또한, 작업중 디스크에 스왑되기 전에 전원이 꺼진다던가 하면 작업 내용이 사라진다..  





윈도우의 터미널 서버를 이용하는 경우, 해당 세션별 정보나 프로세스 정보
그러한 목록을 가지고 먼가 작업을 해야 하는경우.. 사용할 수 있는 간단한 래핑클래스 입니다.

class CWTSSession

클래스의 경우는 해당 세션  ID를 알고 있을 경우 세션의 디테일한 정보를 읽어 오는 클래스이죠.
클래스의 내부 멤버를 보시면 어떤 정보를 열거하는지 바로 확인이 가능합니다.

class CWTSBase
위 클래스는 어떤 세션이 존재하는지, 어떤 서버가 존재하는지, 프로세스 목록은 어떤게 있는지
등등을 열거해주는 기능을 제공해줍니다.

[-] Collapse

#include <WtsApi32.h>

class CWTSSession
{
public:
    CWTSSession();
    CWTSSession(HANDLE hWTS, DWORD SessionID);
    ~CWTSSession();

    CHAR    IPAddress[16];
    USHORT  HorizontalResolution;
    USHORT  VerticalResolution;
    USHORT  ColorDepth;
    USHORT  ProtocolType;
    LPTSTR  ClientName;
    LPTSTR  DomainName;
    LPTSTR  UserName;
    LPTSTR  WinStation;
protected:
    void Query(HANDLE hWTS, DWORD SessionID);
    void Init();
    void Free();
};

class CWTSBase
{
public:
    CWTSBase();
    virtual ~CWTSBase();

    BOOL Open(LPTSTR servername);
    void Close();
    void Free(void* data);
    HANDLE GetHandle();

    BOOL EnumSessions(PWTS_SESSION_INFO& info, LPDWORD count);
    BOOL EnumServers(LPTSTR domainname, PWTS_SERVER_INFO& info, LPDWORD count);
    BOOL EnumProcesses(PWTS_PROCESS_INFO& info, LPDWORD count);

protected:
    HANDLE m_hWTS;
}; 

세션 아이디를 알고 있을 경우, 해당 세션의 세부정보를 읽어오는 샘플
CWTSSession ws(WTS_CURRENT_SERVER_HANDLE, 세션아이디);

모든 세션에 걸친 프로세스 목록을 읽어오는 샘플
CWTSBase wts;
DWORD count;
PWTS_PROCESS_INFO info;
wts.EnumProcesses(info, &count);

어떤 세션이 존재하는지, 세션 목록 정보를 읽어오는 샘플
CWTSBase wts;
DWORD count;
PWTS_SESSION_INFO info;
wts.EnumProcesses(info, &count);

현재 나의 프로그램이 어떤 세션에 속해있는지를 판단하는 샘플
DWORD PID = GetCurrentProcessId();
DWORD SID = 9999999;
if(ProcessIdToSessionId(PID, &SID))
{
    CWTSSession ws(WTS_CURRENT_SERVER_HANDLE, SID);
    printf("%s\n", ws.IPAddress);
    printf("%s\n", ws.ClientName);
    printf("%s\n", ws.DomainName);
    printf("%s\n", ws.UserName);
    printf("%s\n", ws.WinStation);
}

지정된 세션에 동일한 프로세스가 있을 경우, 해당 세션의 특정 프로세스에 메시지 보내기
오직 하나의 프로그램이 동작하도록 할경우를 제외한 하나의 세션당 하나의 프로그래만
구동되도록 작성해야 할경우.. 사용한 샘플입니다.
BOOL CALLBACK CROWBACK_WNDENUMPROC(HWND hWnd, LPARAM lp)
{
    DWORD target = lp;
    DWORD pid = 9999999;
    GetWindowThreadProcessId(hWnd, &pid);
    if(pid == target)
    {
        char caption[512] = {0};
        ::GetWindowText(hWnd, caption, 512);
        if(strstr(caption, "Alert Management"))
        {
            ::PostMessage(hWnd, 0x9999, 0, 0);
            return FALSE;
        }
    }

    return TRUE;
}

DWORD PID = GetCurrentProcessId();
DWORD SID = 9999999;

if(ProcessIdToSessionId(PID, &SID))
{
    AllowSetForegroundWindow(PID);

    char Module[512] = {0};
    ::GetModuleFileName(NULL, Module, 512);
    CString name = Module;
    name = name.Right(name.GetLength()-name.ReverseFind('\\')-1);
    CWTSBase wts;

    DWORD count;
    PWTS_PROCESS_INFO info2;
    wts.EnumProcesses(info2, &count);
    for(UINT i=0; i<count; i++)
    {
        if(info2[i].SessionId == SID && info2[i].ProcessId != PID)
        {
            if(name.CompareNoCase(info2[i].pProcessName) == 0)
            {
                ::EnumWindows(CROWBACK_WNDENUMPROC, info2[i].ProcessId);
                return TRUE;
            }
        }
    }
}

평소 별로 관심을 가져보지 못하던 영역의 자료가 올라와서 한번 살펴보았습니다.
웹쪽과는 인연이 없다보니 잘 살펴보지 않는 영역의 자룐데, 잘 정리된걸 보니 이뿌네요 ㅜㅜ;
취미로 해보기엔 좀 벅차고, 일로 떨어지면 헤딩이라도 해볼텐데.. ^^;;;

 익스플로러 보안설정 변경  | ActiveX/COM 2008-12-22 오후 5:21:55
최재권 (houseman)  최재권님께 메시지 보내기최재권님을 내 주소록에 추가합니다.최재권님의 개인게시판 가기  / 등록자 IP: xxx.xxx.244.109 번호: 8208   / 평점:  (9.0)  / 읽음:187

인터넷 익스플로러의 보안 설정 변경 관련.

 

1. 신뢰할수 있는 사이트 추가.

2. 구역별 보안등급 변경.

3. 임의의 선택한 보안 수준 변경.

 

 

1. 신뢰할수 있는 사이트 추가. &  2. 구역별 보안등급 변경.

 

 HRESULT hr;
 IInternetSecurityManager * pSecurityMgr;
 IInternetZoneManager * pZoneMgr;
 DWORD dwEnum, dwZoneCount;
 DWORD dwZone;
 ZONEATTRIBUTES zoneAttr;
 int nLevel = 2;
 
 CoInitialize(NULL);


 // 먼저 IinternetSecurityManager 인터페이스를 초기화한다.
 hr = CoCreateInstance(CLSID_InternetSecurityManager, NULL, CLSCTX_ALL, IID_IInternetSecurityManager, (void**)&pSecurityMgr);
 if (hr != S_OK)
 {
       CoUninitialize();
       return;
 }


 // 다음으로 IinternetZoneManager 인터페이스를 초기화한다.
 hr = CoCreateInstance(CLSID_InternetZoneManager, NULL, CLSCTX_ALL, IID_IInternetZoneManager, (void**)&pZoneMgr);
 if (hr != S_OK)
 {
       CoUninitialize();
       return;
 }
 dwEnum = 0;

 

 // Zone Enumerator를 초기화한다.
 pZoneMgr->CreateZoneEnumerator(&dwEnum, &dwZoneCount, 0);

 

 // 2번 존에 대한 정보를 얻는다. 2번 존이 신뢰할 수 있는 영역 존이다.
 pZoneMgr->GetZoneAt( dwEnum, nLevel, &dwZone);
 pZoneMgr->GetZoneAttributes(dwZone, &zoneAttr);

 

 // 지금 예제는 보통의 HTTP 사이트를 등록하는 것이기 때문에 HTTPS만을 등록해야 하는 제약 조건을 없앤다.
 if (zoneAttr.dwFlags &ZAFLAGS_REQUIRE_VERIFICATION)
 {
       // 서버 확인 부분을 뺀다.
       zoneAttr.dwFlags = (zoneAttr.dwFlags & ~(ZAFLAGS_REQUIRE_VERIFICATION));
 }

 

 // 현재 보안 설정이 낮음이 아니면 낮음으로 설정한다.
 if (zoneAttr.dwTemplateCurrentLevel != 0x10000)
 {
       zoneAttr.dwTemplateCurrentLevel = 0x10000;
       zoneAttr.dwTemplateMinLevel = 0x10000;
 }
 pZoneMgr->SetZoneAttributes(dwZone, &zoneAttr);
 


 // 등록 사이트
 CString strDomain = "http://www.devpia.com";

 

 // 이 값을 유니코드로 변경한다.
 BSTR bDomain = strDomain.AllocSysString();
 


 // IInternetSecurity 인터페이스의 SetZoneMapping 함수를 이용해 등록한다.
 hr = pSecurityMgr->SetZoneMapping(nLevel, bDomain, SZM_CREATE);


 if (hr == E_ACCESSDENIED)  // 존 설정이 서버 확인을 필요로 하는 곳이면
 {
       ::MessageBox(NULL, "등록하려는 영역이 서버 확인이 필요한 것으로 설정되어 있습니다.", "영역 추가 에러", MB_OK);
 }
 else if (hr == ERROR_FILE_EXISTS) // 이미 다른 영역으로 등록된 것이면
 {
       ::MessageBox(NULL, "등록하려는 주소가 이미 다른 영역으로 설정되어 있습니다.", "영역 추가 에러", MB_OK);
 }
 


 ::SysFreeString(bDomain);
 


 if (dwEnum != 0)
       pZoneMgr->DestroyZoneEnumerator(dwEnum);
 


 pSecurityMgr->Release();
 pZoneMgr->Release();
 CoUninitialize();

 

--------------------------------------------------------------------------------

위의 코드는 한기용(Keeyong@wisenut.com)님의 코드를 그대로 인용 하였음.

 

 

 

1,2 의 내용만으로 거의 모든 문제가 해결되리라 생각된다.

그러나 몇몇 보안 관련 부분은 보안 등급을 최소로 해도 문제가 해결되지 않는 경우가 있다.

이 경우엔 직접 원하는 보안관련 설정을 원하는 수준으로 변경 해야만 한다.

대표적인 예가 "안전하지 않은 것으로 표시된 ActiveX 컨트롤 초기화 및 스크립트"이다.

때때로 사소한 ActiveX들이 필요한 경우가 있는데 이런것들을 매번 서명을 받기도 번거로운 일이다.

이러한 문제를 해결하기위해서 보안수준을 변경할 필요가 있다.

 

이 항목을 직접 변경하는 코드를 살펴보자.

 

 

 

3. 임의의 선택한 보안 수준 변경.

CRegKey reg;     // #include <Atlbase.h>

if(reg.Open(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Zones\\2") == ERROR_SUCCESS)
{
     reg.SetValue((DWORD)0, "1201");  // 0:허용  1:확인  3:사용안함
     reg.Close();
}

if(reg.Open(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Zones\\2") == ERROR_SUCCESS)
{
     reg.SetValue((DWORD)0, "1201");  
     reg.Close();
}

 

----------------------------------------------------------------------

 

위의 예제는 [신뢰할수 있는 사이트>안전하지 않은 것으로 표시된 ActiveX 컨트롤 초기화 및 스크립트 => 사용] 으로 레지스트리를 직접 변경하는 코드이다.

 

그러나, 한기용(Keeyong@wisenut.com)님의 글에서는 레지스트리를 직접 편집하는것은 좋지 않은 방법이라고 설명하고 있다. 그 이유를 다음과 같이 설명하고 있다.

---------------------------------------------------------------

그 이유는 다음 버전의 IE에서도 레지스트리의 이 항목들을 URL 보안 관련

정보를 저장하는데 사용한다는 보장이 없기 때문이다. 따라서 URL 보안 영역

관련 인터페이스를 사용하는 것이 좋다.

---------------------------------------------------------------

 

따라서, 방법의 선택은 독자의 몫이다.

 

 

※ Zones > 2 는 신뢰할수 있는 사이트를 나타낸다.

  • Zone 0 (My computer 영역을 의미)
  • Zone 1 (Local intranet 영역을 의미)
  • Zone 2 (trusted sites 영역을 의미)
  • Zone 3 (Internet 영역을 의미)
  • Zone 4 (Restricted sites 영역을 의미)

     

    ※ 똑같은 내용이 hkey_local_machine과 hkey_current_user 두곳에 존재하는데 필자는 그 차이를 모르겠다. 따라서 두곳 모두 바꾸어 준다. 그러나 테스트 결과 hkey_current_user만 수정해도 무방하였다.

     

    ※ SetValue의 첫번째 파라미터는 값을 나타낸다.

         0:허용  1:확인  3:사용안함

     

    ※ SetValue의 두번째 파라미터는 값이름을 나타낸다. (1201 : 안전하지 않은 것으로 표시된 ActiveX 컨트롤 초기화 및 스크립트)

     

    참조

    엔트리 정의 기본값 권장값
    1001 서명된 ActiveX 컨트롤 다운로드 0 3
    1004 서명 안 된 ActiveX 컨트롤 다운로드 0 3
    1200 ActiveX 컨트롤과 플러그인 실행 0 1
    1201 안전하지 않은 것으로 표시된 ActiveX 컨트롤 초기화 및 스크립트 1 3
    1400 액티브 스크립팅 허용 0 1
    1402 자바 애플릿 스크립트 0 1
    1406 도메인 간의 데이터 소스 액세스 0 3
    1407 스크립트를 통한 붙여넣기 작업 허용 0 3
    1601 암호화되지 않은 폼 데이터 제출 0 1
    1604 폰트 다운로드 0 3
    1606 사용자 데이터 유지 0 3
    1607 서로 다른 도메인 간의 하위프레임 이동 0 3
    1802 파일의 드래그 앤 드롭이나 복사 및 붙여넣기 0 1
    1803 파일 다운로드 0 3
    1804 IFRAME에 프로그램과 파일 시작 0 3
    1E05 소프트웨어 채널 사용권한 30000 20000

     

    MSDN참고: http://technet.microsoft.com/ko-kr/library/cc700750.aspx

     

    ※ 보안관련 수정 내용은 새로 시작되는 익스플로러부터 적용된다.

    ----------------------------------------------------------------------------------------------

    최재권 jk9053@nate.com


  • 지금껏 이벤트로그에 쓰는건 해봤어도, 이벤트 로그를 읽을일이 없어 그냥 무심히 지나쳐왔었는데요..
    일이란걸 하다보니, 이벤트 로그를 읽어들이면 간단하게 끝날 수 있는데,
    직접 코딩하려니 무자게 빡센 업무가 되는것들도 있더라구요.. 흠...

    지금 하려는 작업은 시스템 이벤트중 보안(Security) 이벤트에 먼가가 써지면 그 이벤트를 바로 받아서
    어떤게 써졌는지 읽어오는 작업이 되겠습니다.

    // 이벤트 로그의 핸들과, 그것의 이벤트를 처리할 EVENT 핸들
    HANDLE m_hEventLog;
    HANDLE m_hEvent;

    // 이벤트 로그를 오픈하고, 새로운 이벤트를 하나 만든다.
    m_hEventLog = OpenEventLog(NULL, "Security);
    m_hEvent = CreateEvent(NULL, FALSE, FALSE, "crowback_m_hEvent");

    // 열린 이벤트로그에, 새로 만든 EVENT를 등록하여
    // 이벤트 로그의 이벤트를 받아보자.
    if(NotifyChangeEventLog(m_hEventLog, m_hEvent) == FALSE)
    {
       if(m_hEvent)
            CloseHandle(m_hEvent);
       CloseEventLog(m_hEventLog);
    }

    비동기로 계속 이벤트를 모니터링 해야하므로 별도의 스레드가 하나 있어야겠죠?
    프로그램 자체의 메인 루프에다가 처리해도 상관없을꺼구요..

    // 스레드나 메인루프에서 이벤트를 대기하다가, 시그널이 날라오면
    // 해당 이벤트 로그를 열어서 필요한 정보를 추출한다.
    while(종료조건)
    {
        if(WaitForSingleObject(pThis->EventLogGetEventHandle(), 100) == WAIT_OBJECT_0)
            EventLogCallback();
    }

    // 이벤트 로그를 열어서 최근 데이터를 읽어들인다.
    void EventLogCallback()
    {
        HANDLE evt = OpenEventLog(NULL, "Security");
        if (evt == NULL)
            return;

        BYTE bBuffer[4096] = {0};
        DWORD dwRead, dwNeeded;
        EVENTLOGRECORD *pevlr = (EVENTLOGRECORD *) &bBuffer;
        LPCTSTR lpSourceName;
        
        if(ReadEventLog(evt,   // Event log handle
            EVENTLOG_BACKWARDS_READ | // 최근 것을 읽어들인다.
            EVENTLOG_SEQUENTIAL_READ, // Sequential read
            0,       // Ignored for sequential read
            pevlr,      // Pointer to buffer
            4096,      // Size of buffer
            &dwRead,     // Number of bytes read
            &dwNeeded) == FALSE)   
        {
            CloseEventLog(evt);
            return;
        }
        CloseEventLog(evt);

        lpSourceName = (LPCTSTR) ((LPBYTE) pevlr + sizeof(EVENTLOGRECORD));

        // EVENTLOGRECORD 구조체에 필요한 정보가 모두 들어있다.
    }

    C나 C++에서 수식을 연산하다 잘못된 메모리를 읽거나, 저장된 파일을 읽어들이거나
    0으로 나누거나 등등의 이유로 실수값에 -1.#INF0 과 같은 값이 지정된 것을 본적이
    있을것이다. 혹은 -1.#IND0 과 같은 것도 있다.

    .#IND Not a Number <= 숫자가 아닌값(실수체계에서 나올 수 없는 값과 같은 경우)
    .#INF Infinite Number <= 0으로 나누는 등으로 인하여 무한히 큰값

    숫자가 아닌값을 검출할 때는 CRT의 _isnan()
    무한대의 숫자를 검출할 때는 CRT의 _finite()
    주어진 숫자가 의심스러울 때 혹은 상태를 검출할 때는 _fpclass()
    모두 float.h 에 선언되어 있습니다.

    제목에서 처럼 0으로 나누어졌을 때는 숫자가 무한히 커져 .#INF00 의 무한값을 나타내고
    다른 예로 double a = sqrt(-1) (데브피아 Neo.L 님께서 제시해주신 예시) 과 같은 경우는
    실수체계에서 표현할 수 없는 숫자라는 의미로 .#IND00 과 같이 표기됩니다.

    이 부분은  IEEE754,854 문서에 표기된 표준 표기에 의한것 (데브피아 stiletto 님께서 표기해줌)
    으로 모두 정상적인 표기 방법입니다.

    다행한것이 설계를 그렇게 한것인지, 저런 값이 나온다고 하여 연산하다가 죽는 경우는 없다는
    점이다. 단, 필요한 요소에서 꼭 확인하여 엉뚱한 결과로 인한 피해를 줄이자.

    사용자 삽입 이미지

















    잘 모르는 내용을 올리다가 망신살도 좀 뻗치고.. 아무튼 이런 기회에 새로운걸 공부해 보네요.
    http://www.kriss.re.kr/ 한국 표준과학 연구원

    SNTP (Simple Network Time Protocol)를 이용하며, 한국표준시 Time Server에 자동으로 접속하여 시간을 맞출 수 있다. (타임서버의 도메인: time.kriss.re.kr)


    참고 소스는 http://www.codeproject.com/KB/IP/csntp.aspx
    CSNTPClient - An SNTP Implementation

    참고 자료는 http://blog.naver.com/cyonsang?Redirect=Log&logNo=140015175394
    SNTP(단일 네트워크 기간 프로토콜) 시간 서버 목록


    위와 같은 자료가 필요한 이유는, 배포된 프로그램에 시간을 이용해서 확인하는 작업등이
    존재할경우 CMOS나, OS시간을 변경시켜버리면 확인하기 힘든 부분이 존재하기 때문이다.
    여러가지 방식으로 막을 수는 있겠지만, 인터넷에 연결되어져야 하는 컴퓨터라면 혹은
    인터넷이 되어야만 사용할 수 있는 프로그램이라면 위 방법으로 쉽게 해결할 수 있다.
    프로그램을 작성하다 보니.. 파일의 시간을 변경할 일이 발생했다...
    Win32 API에서 여러가지 함수를 제공하기는 하는데.. time_t를 바로 지원하지는 않는다. -_-;;;

    다행히도 MSDN에서 이를 지원하는 KB가 있어 옮겨 보았다.

    How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME

    Article ID : 167296
    Last Review : November 21, 2006
    Revision : 3.3
    This article was previously published under Q167296

    SUMMARY

    Under UNIX platforms, file times are maintained in the form of a ANSI C runtime arithmetic type named 'time_t', which represents seconds since midnight January 1, 1970 UTC (coordinated universal time).

    Under Win32 platforms, file times are maintained primarily in the form of a 64-bit FILETIME structure, which represents the number of 100-nanosecond intervals since January 1, 1601 UTC (coordinate universal time).

    This article shows how to convert UNIX time to other Win32 time formats.

    MORE INFORMATION

    The following function converts a filetime in the UNIX time_t format to a Win32 FILETIME format. Note that time_t is a 32-bit value and FILETIME is a 64-bit structure, so the Win32 function, Int32x32To64() is used in the following function:
       #include <winbase.h>
       #include <winnt.h>
       #include <time.h>
    
       void UnixTimeToFileTime(time_t t, LPFILETIME pft)
       {
         // Note that LONGLONG is a 64-bit value
         LONGLONG ll;
    
         ll = Int32x32To64(t, 10000000) + 116444736000000000;
         pft->dwLowDateTime = (DWORD)ll;
         pft->dwHighDateTime = ll >> 32;
       }
    				
    Once the UNIX time is converted to a FILETIME structure, other Win32 time formats can be easily obtained by using Win32 functions such as FileTimeToSystemTime() and FileTimeToDosDateTime().
       void UnixTimeToSystemTime(time_t t, LPSYSTEMTIME pst)
       {
         FILETIME ft;
    
         UnixTimeToFileTime(t, &ft);
         FileTimeToSystemTime(&ft, pst);
       }
    				

    APPLIES TO
    Microsoft Win32 Application Programming Interface, when used with:
        Microsoft Windows 95
        Microsoft Windows 98 Standard Edition
        Microsoft Windows Millennium Edition
        Microsoft Windows NT 3.51 Service Pack 5
        Microsoft Windows NT 4.0
        Microsoft Windows 2000 Standard Edition
        Microsoft Windows XP Professional

    Back to the top

    Keywords:
    kbdatetime kbhowto kbkernbase kbprogramming KB167296
     

    권한에 관한 부분은 상당히 방대한 양이라.. 그냥 필요해서 쓰는것만 추려봤습니다.
    MSDN에 잘 나와았지만 대부분 WCHAR 타입의 인자를 넘겨야해서 그냥 껍데기를
    씌운 형태입니다. ^^;

    1. 유저 영역
       -> 유저 생성, 유저 삭제, 유저 목록, 암호 변경, 유저가 속한 그룹 목록, 유저 정보등..

    2. 그룹 영역
      -> 그룹 생성, 그룹 삭제, 그룹 목록, 그룹에 유저 추가, 그룹에서 유저 삭제등..

    3. 권한 영역
      -> 유저의 SID 얻어오기, SID를 문자열로 만들기, 현재 유저의 권한 얻어오기
          권한의 활성화/비활성화, 프로세스에 속한 권한 목록 얻어오기, 권한 상승
          프로세스 소유자 얻어오기 등등...


    // Policy.h: interface for the CPolicy class.
    //
    //////////////////////////////////////////////////////////////////////
    
    #if !defined(AFX_POLICY_H__8DE86AF1_C525_4546_A661_0FC4D12902EF__INCLUDED_)
    #define AFX_POLICY_H__8DE86AF1_C525_4546_A661_0FC4D12902EF__INCLUDED_
    
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    
    #define RTN_OK 0
    #define RTN_USAGE 1
    #define RTN_ERROR 13
    
    #include <ntsecapi.h>
    #include <vector>
    #include <comutil.h>
    #include <lm.h>
    #include <sddl.h>
    #include <Ntsecapi.h>
    
    
    namespace CLM // Control Local Machine
    {
        LPCTSTR GetLogonUser();
        LPCTSTR GetHostName();
        LPCTSTR GetDomainName();
    
        LPWCH  MBtoWC(LPCTSTR src, PWCHAR dst, int dstlen);
        LPTSTR WCtoMB(LPCWCH src, LPTSTR dst, int dstlen);
    
        void InitLsaString(PLSA_UNICODE_STRING LsaString, LPWSTR String);
    
        NTSTATUS OpenPolicy(
            LPCTSTR ServerName,                // machine to open policy
            DWORD DesiredAccess,            // desired access to policy
            PLSA_HANDLE PolicyHandle);        // resultant policy handle, must be LsaClose(PolicyHandle)
    
        NTSTATUS OpenPolicy(
            PLSA_HANDLE PolicyHandle);        // resultant policy handle, must be LsaClose(PolicyHandle)
    
        BOOL GetUserSid(
            LPCTSTR servername,                // where to lookup account
            LPCTSTR username,                // account of interest
            PSID *Sid);                        // resultant buffer containing SID, must be HeapFree(GetProcessHeap(), 0, pSid);
    
        BOOL GetCurrentUserSid(
            PSID *pSid);                    // resultant buffer containing SID, must be HeapFree(GetProcessHeap(), 0, pSid);
    
        BOOL GetSIDToString(PSID sid, LPTSTR& strstring);
    
        namespace USER
        {
            /*  Declared in Lmaccess.h; include Lm.h. Use Netapi32.lib.
    
                NetUserAdd                Adds a user account and assigns a password and privilege level. 
                NetUserChangePassword    Changes a user's password for a specified network server or domain. 
                NetUserDel                Deletes a user account from the server. 
                NetUserEnum                Lists all user accounts on a server. 
                NetUserGetGroups        Returns a list of global group names to which a user belongs. 
                NetUserGetInfo            Returns information about a particular user account on a server. 
                NetUserGetLocalGroups    Returns a list of local group names to which a user belongs. 
                NetUserSetGroups        Sets global group memberships for a specified user account. 
                NetUserSetInfo            Sets the password and other elements of a user account. 
            */
    
            NET_API_STATUS NetUserAdd(
                LPCTSTR servername,                // NULL is localcomputer
                LPCTSTR username,                // Not NULL
                LPCTSTR password,                // allow NULL
                LPCTSTR comment,                // allow NULL
                DWORD privilege,                // USER_PRIV_GUEST, USER_PRIV_USER, USER_PRIV_ADMIN
                DWORD flags);                    // 권한에 대한 제한. UF_SCRIPT | UF_DONT_EXPIRE_PASSWD 기타 등등..
    
            NET_API_STATUS NetUserChangePassword(
                LPCTSTR domainname,                // NULL is localcomputer
                LPCTSTR username,                // NULL is current logon user
                LPCTSTR oldpassword,            // current password, Not NULL
                LPCTSTR newpassword);            // new password, Not NULL
        
            NET_API_STATUS NetUserDel(
                LPCTSTR domainname,                // NULL is localcomputer
                LPCTSTR username);                // Not NULL
        
            NET_API_STATUS NetUserEnum(
                LPCTSTR domainname,                // NULL is localcomputer
                LPDWORD dwCount,                // user count
                LPUSER_INFO_20& userinfos);        // user info array, must be free -> NetApiBufferFree(userinfos);
        
            NET_API_STATUS NetUserGetGroups(
                LPCTSTR domainname,                // NULL is localcomputer
                LPCTSTR username,                // NULL is current user
                std::vector<_bstr_t>& parray);    // global group name array
    
            NET_API_STATUS NetUserGetInfo(
                LPCTSTR domainname,                // NULL is localcomputer
                LPCTSTR username,                // NULL is current user
                LPUSER_INFO_20& userinfos);        // user info array, must be free -> NetApiBufferFree(userinfos);
        
            NET_API_STATUS NetUserGetLocalGroups(
                LPCTSTR domainname,                // NULL is localcomputer
                LPCTSTR username,                // NULL is current user
                std::vector<_bstr_t>& parray);    // local group name array
        
            NET_API_STATUS NetUserSetGroups(
                LPCTSTR domainname,                // NULL is localcomputer
                LPCTSTR username,                // NULL is current user
                LPCTSTR groupname);                // Not NULL, global groupname
        }
    
    
        namespace GROUP
        {
            /*  Declared in Lmaccess.h; include Lm.h. Use Netapi32.lib.
    
                NetLocalGroupAdd        Creates a local group. 
                NetLocalGroupAddMembers Adds one or more users or global groups to an existing local group. 
                NetLocalGroupDel        Deletes a local group, removing all existing members from the group. 
                NetLocalGroupDelMembers Removes one or more members from an existing local group. 
                NetLocalGroupEnum        Returns information about each local group account on a server. 
                NetLocalGroupGetInfo    Returns information about a particular local group account on a server. 
                NetLocalGroupGetMembers Lists all members of a specified local group. 
                NetLocalGroupSetInfo    Sets general information about a local group. 
                NetLocalGroupSetMembers Assigns members to a local group. 
            */
        
            NET_API_STATUS NetLocalGroupAdd(
                LPCTSTR domainname,                // NULL is localcomputer
                LPCTSTR groupname,                // Not NULL, groupname
                LPCTSTR comment);                // Allow NULL
    
            NET_API_STATUS NetLocalGroupAddMembers(
                LPCTSTR domainname,                // NULL is localcomputer
                LPCTSTR groupname,                // Not NULL, groupname
                LPCTSTR username);                // Not NULL, username
    
            NET_API_STATUS NetLocalGroupDel(
                LPCTSTR domainname,                // NULL is localcomputer
                LPCTSTR groupname);                // Not NULL, groupname
        
            NET_API_STATUS NetLocalGroupDelMembers(
                LPCTSTR domainname,                // NULL is localcomputer
                LPCTSTR groupname,                // Not NULL, groupname
                LPCTSTR username);                // Not NULL, username
    
            NET_API_STATUS NetLocalGroupEnum(
                LPCTSTR domainname,                // NULL is localcomputer
                LPDWORD dwCount,                // group count
                PLOCALGROUP_INFO_1& groupinfo);    // group info array, must be free -> NetApiBufferFree(groupinfo);
    
            NET_API_STATUS NetLocalGroupGetInfo(
                LPCTSTR domainname,                // NULL is localcomputer
                LPCTSTR groupname,                // Not NULL, groupname
                PLOCALGROUP_INFO_1& groupinfo);    // group info, must be free -> NetApiBufferFree(groupinfo);
    
        };
    
        
        
        namespace PRIVILEGE
        {
            // 현재 프로세스에 주어진 권한을 인에이블/디스에이블 시킨다.
            BOOL SetProcessPrivilege(
                LPCTSTR lpszPrivilege,            // privilege to grant
                BOOL bEnablePrivilege);            // add or remove
    
            // 사용자 계정에 특별한 권한을 추가시킨다.
            NTSTATUS SetPrivilegeOnAccount(
                LSA_HANDLE PolicyHandle,        // open policy handle
                PSID AccountSid,                // SID to grant privilege to
                LPWSTR PrivilegeName,            // privilege to grant (Unicode)
                BOOL bEnable);                    // enable or disable
    
            // 해당 유저에게 할당된 부가적인 프리빌리지를 얻어온다.
            // 일반적인 프리빌리지를 얻어올 경우는, 해당 유저가 띄운 프로세스를 이용하여
            // 아래의 GetPrivilegeFromProcessHandle 함수를 이용하여 가져온다.
            NTSTATUS EnumPrivilegeOnAccount(
                LSA_HANDLE PolicyHandle,        // open policy handle
                PSID AccountSid,                // SID to grant privilege to
                std::vector<_bstr_t>& parray);    // privileges array
    
            // 프로세스 핸들을 이용하여 해당 유저 이름을 얻어온다.
            BOOL GetUserFromProcessHandle(
                LPTSTR AccountName,                // account of interest
                DWORD* cbName,                    // string buffer length
                HANDLE hProcess = NULL);        // process id, NULL is owner process
    
            // 프로세스 아이디를 이용하여 해당 유저 이름을 얻어온다.
            BOOL GetUserFromProcessID(
                LPTSTR AccountName,                // account of interest
                DWORD* cbName,                    // string buffer length
                DWORD nProcessID = 0xFFFFFFFF);    // process id, 0xFFFFFFFF is owner process
    
            // 프로세스 핸들을 이용하여 해당 프로세스에 설정된 프리빌리지 목록을 얻어온다.
            BOOL GetPrivilegeFromProcessHandle(
                std::vector<_bstr_t>& parray,    // privileges array
                HANDLE hProcess = NULL);        // process id, NULL is owner process
    
            // 프로세스 아이디을 이용하여 해당 프로세스에 설정된 프리빌리지 목록을 얻어온다.
            BOOL GetPrivilegeFromProcessID(
                std::vector<_bstr_t>& parray,    // privileges array
                DWORD nProcessID = 0xFFFFFFFF);    // process id, 0xFFFFFFFF is owner process
        };
    };

    #endif
    // !defined(AFX_POLICY_H__8DE86AF1_C525_4546_A661_0FC4D12902EF__INCLUDED_)







    + Recent posts