사용자 도구

사이트 도구


kb:visualcpptips


Visual C++ Tips

VisualCpp 관련 팁들

Visual Studio x64 툴체인 사용하기

Visual Studio는 기본적으로 32비트 툴체인(cl.exe, lib.exe, link.exe)을 사용하는데, 프로젝트 규모가 커지면 여러가지 문제가 발생한다.

제일 난감한게 이거…

32비트라서 나오는 현상이니 64비트 버전의 툴셋을 사용해주면 된다. 커맨드 라인에서는 이런 느낌이다.

> C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\vsvars32.bat amd64
> C:\Program Files (x86)\MSBuild\12.0\Bin\amd64\msbuild.exe" XXX.sln /t:rebuild /p:Configuration=Release /p:Platform="x64" /m

VisualStudio IDE 자체에서 사용하는 툴셋을 변경하는 방법이 어디 있을 것 같아 찾아보니 스택오버플로에 있다.

긴 글 요약하자면, 환경 변수에 추가하거나…

set PreferredToolArchitecture=x64

아니면 프로젝트 파일(.vcxproj)에다 추가하면 된다는 이야기다.

 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <PropertyGroup>
    <PreferredToolArchitecture>x64</PreferredToolArchitecture>
  </PropertyGroup>

다만 이 내용들은 VS2013으로 VS2013 프로젝트를 빌드할 때 적용 가능한 이야기들이며, VS2013으로 VS2010 프로젝트를 빌드하거나 할 때에는 잘 안 되는 모양이다.

스택 오버플로우 발생시 덤프 기록하기

스택 오버플로우 발생시의 덤프 기록은 약간 다르게 처리를 해줘야한다. 어려운 건 아니고, 그냥 다른 스레드를 하나 생성해서 그쪽으로 스레드 핸들과 예외 포인터를 넘겨서 덤프를 기록하면 된다. 현재 스레드에서는 덤프 함수를 호출할 스택마저 모자랄 수 있기 때문이다. 하지만 다른 스레드를 하나 따로 생성하면 새로 생성한 스레드에서는 스택 공간이 충분하기 때문에 덤프를 무사히 기록할 수 있는 것이다. (스택은 스레드별로 유지되니까…)

typedef struct _DUMP_PARAMETER
{
    HANDLE              Thread;
    PEXCEPTION_POINTERS ExPtrs;
} DUMP_PARAMETER, *PDUMP_PARAMETER;
 
DWORD WINAPI WriteDump(LPVOID param)
{
    PDUMP_PARAMETER dumpParam = reinterpret_cast<PDUMP_PARAMETER>(param);
    ...여기서 실제 덤프를 수행...
}
 
LONG WINAPI HandleException(PEXCEPTION_POINTERS exPtrs)
{
    if (exPtrs)
    {
        PDUMP_PARAMETER dumpParam = (PDUMP_PARAMETER)malloc(sizeof(DUMP_PARAMETER));
        dumpParam->Thread = GetCurrentThread();
        dumpParam->ExPtrs = exPtrs;
        if (exPtrs->ExceptionRecord->ExceptionCode != EXCEPTION_STACK_OVERFLOW)
        {
            WriteDump(dumpParam);
        }
        else // 스택 오버플로 발생 시에는 별도의 스레드를 생성해서 덤프를 기록한다.
        {
            HANDLE hThread = CreateThread(NULL, 102400, WriteDump, dumpParam, 0, NULL);
            WaitForSingleObject(hThread, INFINITE);
            CloseHandle(hThread);
        }
    }
    return EXCEPTION_EXECUTE_HANDLER;
}
 
void main()
{
    ...
    // 예외 처리 핸들러를 설정한다.
    SetUnhandledExceptionFilter(HandleException);
    ...
}

디버깅을 위한 자잘한 함수들

CRT 시리즈는 제외. 음… 적어놓고 보니 꽤 많은데… -_-

함수 설명
_set_new_handler 동적 메모리 할당이 실패했을 때 호출되는 콜백 함수를 설정한다.
_set_new_mode malloc 함수가 실패했을 때 _set_new_handler에서 설정한 함수를 호출할 것인가의 여부를 반환한다.
set_terminate 처리되지 않은 예외로 인해 프로그램이 종료될 때 호출할 함수를 설정한다.
set_unexpected 처리되지 않은 예외가 발생했을 때 호출할 함수를 설정한다.
uncaught_exception ???
_set_error_mode 에러 발생시 메시지 출력되는 곳(콘솔화면 또는 메시지박스)을 설정한다.
_set_purecall_handler 순수 가상 함수가 호출되었을 때 호출할 콜백 함수를 설정한다.
_set_sbh_threshold Small Block Heap에 할당되는 메모리의 최대양을 설정한다.
_set_se_translator Win32 Structured Exception을 C++ Exception으로 처리 가능하게 한다. 자세한 것은 StructuredExceptionAsCppException 페이지를 참고.
_set_security_error_handler BufferOverrun이 발생했을 때 호출할 콜백 함수를 설정한다.
_setmaxstdio 동시에 열 수 있는 최대 파일 갯수를 설정한다. ulimit와 비슷하다.
_matherr math.h 함수 실행 에러 발생시 호출되는 함수(?)
_fpieee_flt IEEE 부동 소수점 연산 예외 발생시 호출되는 함수를 설정한다.
atexit 메인 함수가 반환된 후 호출되는 함수를 설정한다

스레드에 이름 붙이기

from Setting a Thread Name (Unmanaged)

//
// Usage: SetThreadName (-1, "MainThread");
//
typedef struct tagTHREADNAME_INFO
{
    DWORD dwType; // must be 0x1000
    LPCSTR szName; // pointer to name (in user addr space)
    DWORD dwThreadID; // thread ID (-1=caller thread)
    DWORD dwFlags; // reserved for future use, must be zero
} THREADNAME_INFO;
 
void SetThreadName( DWORD dwThreadID, LPCSTR szThreadName)
{
    THREADNAME_INFO info;
    info.dwType = 0x1000;
    info.szName = szThreadName;
    info.dwThreadID = dwThreadID;
    info.dwFlags = 0;
 
    __try
    {
        RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info );
    }
    __except(EXCEPTION_CONTINUE_EXECUTION)
    {
    }
}

SetThreadName 함수를 스레드 메인 루프 제일 위쪽(즉 스레드 ID를 할당 받고난 후)에서 호출해 주면, 스레드창에 스레드 이름들이 출력되는 것을 볼 수 있다. 스레드 이름 없어도 스레드 함수로 대부분 어느 스레드인지를 알 수 있기는 하지만… 참고로 디버거가 붙어있는 상태에서만 동작한다. IsDebuggerPresent 함수를 이용해서 디버거가 붙어있는 경우에만 이름을 설정하자.

Critical Section에 락을 건 스레드 알아내기

순서

  1. 심볼 설치는 필수. DrWatson 페이지를 참고.
  2. 아무 곳이나 브레이크 포인트를 설정해서 실행을 일시 중지시킨다.
  3. 도구 모음 → 디버그 위치 → 스레드 메뉴를 이용해 락을 걸려 하고 있는 스레드를 찾아낸다. (EnterCriticalSection 함수에 멈춰있는 스레드가 있을 것이다.)
  4. 락을 걸려 하는 스레드를 찾았다면 콜스택을 표시한다. 대강 아래와 같은 모양의 콜스택이 출력될 것이다.
    NTDLL.DLL!_NtWaitForSingleObject@12()  + 0xb    
    NTDLL.DLL!_RtlpWaitForCriticalSection@4()  + 0x74   
    NTDLL.DLL!_RtlEnterCriticalSection@4()  + 0x91f5    
    GameServerD.exe!BasicCriticalSection::enter()  줄 41 + 0xf  C++
    GameServerD.exe!CriticalSection::lock(...) const 줄 94  C++
    GameServerD.exe!DeadlockTestThread::run()  줄 135 + 0x27    C++
    GameServerD.exe!thread_func_proxy(void * pContext=0x0012feb4)  줄 41 + 0xd  C++
    GameServerD.exe!_threadstartex(void * ptd=0x00e45300)  줄 241 + 0xd C
    KERNEL32.DLL!77e5b382()     
    NTDLL.DLL!_RtlAllocateHeapSlowly@12()  + 0x14acd    
  5. 콜스택에서 NTDLL.DLL!_RtlEnterCriticalSection@4() 프레임으로 간다.
  6. 디버그 → 창 → 메모리 메뉴를 이용해 메모리 윈도우를 띄운다.
  7. 메모리 윈도우 컨텍스트 메뉴를 이용해서 출력을 4바이트 정수, 표시를 16진수로 한다.
  8. 메모리 윈도우의 주소창에서 '@vframe'이라고 입력한다.
  9. 제일 첫번째 4바이트 정수 값이 함수의 리턴 위치다. 두번째 4바이트 정수값이 크리티컬 섹션의 위치다. 이 값을 카피해서 메모리 윈도우의 주소창에다 입력한다.
  10. 앞에서 네번째의 4바이트 정수가 크리티컬 섹션에 락을 건 스레드의 ID다. 조사식 윈도우에서 값을 표시해 보면, 스레드 목록에 일치하는 스레드가 있을 것이다.

알아둘 점

  • @vframe이 제대로 표시되지 않는 경우가 있는데, 이는 콜스택 자체가 잘못 출력되고 있다는 말이다.
  • 콜스택이 제대로 나오는 상태라면, 메모리 윈도우를 띄울 필요 없이 조사식에서 다음과 같이 입력하면 스레드 ID가 출력된다. \\
    *((unsigned int*)(*(unsigned int*)(@vframe+4))+3)

콜스택이 나오지 않거나 이상한 경우

디버그 심볼이 없는 경우

더 이상 말할 가치가 있는가?

잘못된 OS 심볼

VC6과 VC7은 심볼 파일의 형식이 약간 다른데, 기본적으로 마이크로소프트에서 배포하는 심볼은 VC6 형식이다. OS와 밀접하게 관련되어 있는 프로그램일 경우 문제가 된다.

어셈블리어로 만들어진 함수

어셈블리어로 프로그램을 제작하는 경우, 디버거가 상당히 혼란을 겪게 된다. 기본 C 런타임이 대표적인 예이다.

스택 자체가 오염된 경우

스택 오버플로우 등의 이유로 인해 콜스택 데이터 자체가 날아가버린 경우다. 이 경우 메모리 쪽으로 버그를 찾을 것이 아니라, C 런타임 함수를 사용하거나, 컴파일할 때 '/gs' 플래그를 주는 것이 좋다.

잘못된 ESP/EIP 레지스터 값

이는 코드가 쓰레기값을 실행하기 시작할 때 발생한다. 삭제된 객체의 vtable을 참조하거나, 잘못된 함수 포인터를 사용하는 경우가 대표적이라고 할 수 있다. 잘못된 타입 캐스트도 원인이 될 수 있다.

Debugging Tools for Windows에 딸려오는 gflags.exe를 이용해 Page Heap 옵션을 켠 다음, WinDbg를 이용해 디버그하기 바란다. (VisualCpp 디버거로는 안 잡힌다.)

Page Heap 옵션에 관해서는 PageHeap 문서를 참고하면 된다. pageheap.exe 어쩌구 저쩌구 하는 건 무시하고, 그냥 gflags.exe를 이용하기만 하면 된다.

함수 합치기 (Linker COMDAT folding)

두 함수가 완전히 같은 머신 코드를 생성하는 경우, 컴파일러가 이를 하나로 합치는 경우가 있다. 함수 이름이 틀리니, 디버거가 혼란을 일으킬 수 밖에 없다. 최적화를 끄고 테스트해보기 바란다.

예외를 만들지 않고, 그냥 사라지는 프로세스

잘못된 예외 처리

SEH 부분을 보면 알겠지만, 예외 처리를 잘못하는 경우, OS가 JIT 디버거를 띄울 기회를 얻지 못할 수도 있다.

try-except 구문에서 예외 필터 부분 안에서 예외를 던지는 경우

다음과 같은 코드를 컴파일해보시라.

DWORD check()
{
    throw 333;
    return EXCEPTION_EXECUTE_HANDLER;
}
 
void main()
{
    __try
    {
        int* p = NULL;
        *p = 444;
    }
    __except(check()) // 필터 안에서 예외가 발생한다.
    {
        throw 111;
    }
}

디버거를 붙인 상태로 실행하면 예외 처리가 가능하겠지만, 그냥 실행하면 아무 말도 하지 않고 사라지는 것을 볼 수 있을 것이다.

잘못된 예외 처리 핸들러를 설정한 경우

SetUnhandledExceptionFilter, AddVectoredExceptionHandler, _set_se_translator 함수를 이용해서 예외 처리 핸들러를 설정할 수 있다. 핸들러가 잘못 짰다면 OS에게 기회가 오지 않을 수 있다. DLL이 로드되면서 자동으로 예외 처리 핸들러를 설정하는 경우, 이런 문제가 자주 발생한다. Windows Server 2003의 경우, SafeSEH 리스트에 없는 핸들러를 수동으로 설정하려는 경우, 발생한다.

스택 오버플로우를 잘못 처리하는 경우

스택 오버플로우가 발생하는 경우, 스택의 끝에 있는 가드 페이지(guard page)가 없어진다. 예외 필터에서 스택 오버플로우 예외를 잡았다면, 가드 페이지도 재설정해줘야 한다. 가드 페이지를 재설정해 주지 않으면, 다음 스택 오버 플로우 시에 프로세스가 그냥 사라지게 된다.

무한 루프

익셉션 핸들러 자체를 가비지로 덮어쓴 경우

정상적인 프로세스 종료

TerminateProcess, ExitProcess, TerminateThread, ExitThread 등의 함수를 사용해서 프로그램을 종료한 경우, OS는 이를 정상적인 동작으로 취급한다.

에러코드 문자열로 변환하기

별 건 아니다만…

/// \brief 에러 코드를 문자열로 변환해서 리턴한다.
std::string Error2String(unsigned int errorCode)
{
    LPVOID msgBuf = NULL;
    std::string message = "Error2String() call failed!";
 
    if (::FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, 
        errorCode, 
        //MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN),
        MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
        (LPTSTR)&msgBuf,
        0,
        NULL))
    {
        message = reinterpret_cast<LPCTSTR>(msgBuf);
        ::LocalFree(msgBuf);
    }
 
    return message;
}
 
/// \brief GetLastError() + Error2String()
std::string GetLastErrorString()
{
    return Error2String(::GetLastError());
}

C++ 예외 처리 방식

실컷 정리하고 보니 옛날에 정리했었네;;; StructuredExceptionVsCppException 페이지 참고.

프로젝트 속성 → C/C++ → 코드 생성 → C++ 예외 처리 가능 옵션에 관한 정리.

  • /EHs
    • 동기적 예외 처리
    • try/catch 구문에서 SEH 예외를 잡지 않는다. 간단히 말해 catch (…) 구문이 C++ 예외만 잡아낸다.
    • SEH 예외가 발생한 경우, 스코프 안에서 생성된 객체들이 소멸되지 않는다. 생성자/소멸자를 이용해 락을 걸고 푸는 코드 같은 경우 문제가 될 수 있다.
  • /EHa
    • 비동기적 예외 처리
    • try/catch 구문에서 SEH 예외도 잡는다.
    • catch 블록에 대한 최적화가 잘 이루어지지 않아, 성능이 떨어질 수 있다.
  • /EHsc
    • 'C 함수들이 예외를 던지지 않는 걸로 간주한다'인데 'EHs + 좀 더 최적화' 정도가 되겠다.

비동기적 예외 처리 모드(/EHa) 에서는 _set_se_translator() 함수를 통해 SEH 예외를 C++ 예외로 변환해서 코드를 좀 더 아름답게(-_-) 만들 수도 있다. MSDN 발췌

#include <stdio.h>
#include <windows.h>
#include <eh.h>
void SEFunc();
void trans_func( unsigned int, EXCEPTION_POINTERS* );
 
class SE_Exception
{
private:
    unsigned int nSE;
public:
    SE_Exception() {}
    SE_Exception( unsigned int n ) : nSE( n ) {}
    ~SE_Exception() {}
    unsigned int getSeNumber() { return nSE; }
};
 
int main( void )
{
    try
    {
        _set_se_translator( trans_func );
        SEFunc();
    }
    catch( SE_Exception e )
    {
        printf( "Caught a __try exception with SE_Exception.\n" );
    }
}
 
void SEFunc()
{
    __try
    {
        int x, y=0;
        x = 5 / y;
    }
    __finally
    {
        printf( "In finally\n" );
    }
}
 
void trans_func( unsigned int u, EXCEPTION_POINTERS* pExp )
{
    printf( "In trans_func.\n" );
    throw SE_Exception();
}    

Effective Exception Handling in Visual C++ 페이지에 예외 처리 관련한 다른 내용들도 잘 정리되어 있다.

메모리 디버그 코드

VisualCpp 메모리 코드

의미
0xCD, 0xCDCDCDCD 최초로 할당된 메모리
0xFD, 0xFDFDFDFD 유저가 할당한 메모리 뒤에 붙는 바운드 체크용 메모리
0xDD, 0xDDDDDDDD 삭제된 메모리. _CRTDBG_DELAY_FREE_MEM_DF 플래그 값이다.
0xCC, 0xCCCCCCCC 초기화되지 않은 지역 변수. /GX 옵션을 켜야 한다.
0xABABABAB LocalAlloc 함수로 할당한 메모리 뒤에 붙는 바운드 체크용 메모리
0xBAADF00D LocalAlloc 함수로 할당한 메모리
0xFEEEFEEE HeapAlloc 함수를 위해 확보는 해놨으나, 아직 실제로 할당되지는 않은 메모리. 또는 HeapFree 함수를 통해 해제된 메모리

디버거에서 변수값 여러 형식으로 보기

from http://blogs.msdn.com/vcblog/archive/2006/08/04/689026.aspx

int i = 0x12345678;

이런 값이 있을 때 디버거 창에서 해당 변수 이름 앞에다 by, wo, dw을 붙이면 변수의 값을 BYTE, WORD, DWORD 형식으로 볼 수 있다. 레지스터값도 마찬가지.

i    | 0x12345678 | int
by i | 0x78 'x'   | unsigned char
wo i | 0x5678     | unsigned short
dw i | 0x12345678 | unsigned long

변수를 8진수, 10진수, 16진수로 보기 위해서는 포맷 지정자를 이용한다.

i   | 42         | int
i,o | 052        | int
i,d | 42         | int
i,x | 0x0000002a | int

포인터 변수를 문자열 형태로 보려면 “s”, “s8”, “su”를 이용한다. 각각 그냥 문자열, UTF-8 문자열, 유니코드 문자열을 의미한다.

char str[] = "hello";
wchar_t str2[] = L"world";

str            | 0x0012ff00 "hello" | char [6]
str,s          | "hello"            | char [6]
str2           | 0x0012fee8 "world" | wchar_t [6]
(void*)str2,su | "world"            | void *

“m” 글자를 붙이면 해당 변수의 주소로부터 64 바이트까지를 쭉 볼 수 있다.

str,m   | 0x0012ff00  68 65 6c 6c 6f 00 cc cc cc cc cc cc cc cc cc cc  hello. | char [6]
str,mb  | 0x0012ff00  68 65 6c 6c 6f 00 cc cc cc cc cc cc cc cc cc cc  hello. | char [6]
str,mw  | 0x0012ff00  6568 6c6c 006f cccc cccc cccc cccc cccc                 | char [6]
str,md  | 0x0012ff00  6c6c6568 cccc006f cccccccc cccccccc                     | char [6]
str2,mu | 0x0012feec  0077 006f 0072 006c 0064 0000 cccc cccc  world.??       | wchar_t [6]
str,mq  | 0x0012ff00  cccc006f6c6c6568 cccccccccccccccc                       | char [6]
str,ma  | 0x0012ff00  hello.(..(......T..                                     | char [6]

“wc”, “wm”, “hr”은 각각 윈도우 클래스, 윈도우 메시지, HRESULT를 의미한다.

0x00400000,wc | WS_OVERLAPPEDWINDOW                         | int
0x10,wm       | WM_CLOSE                                    | int
0x10,hr       | 0x00000010 The directory cannot be removed. | int

”!” 연산자를 이용하면 STL Visualization을 끌 수 있다.

str   | "hello world"                                    | std::basic_string< ... >
str,! | {_Bx={...} _Mysize=0x0000000b _Myres=0x0000000f} | std::basic_string<...>

Custom Visualizer

실행 파일 크기 줄이기

간단한 콘솔 프로그램을 위한 Makefile

간단한 테스트 프로그램 작성하는데, 일일이 솔루션 만드는 것도 지긋지긋해서 한번 만들어봤다. 프로젝트 템플릿 만드는 것도 하나의 방법이겠지만…

VCDIR = C:\Program Files\Microsoft Visual Studio 8\VC

#------------------------------------------------------------
# Compiler Settings
#------------------------------------------------------------
CL       = "$(VCDIR)\bin\cl.exe"
DEFS     = /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" 
CLFLAGS  = /Od /EHsc /MTd /W4 /c /nologo /TP /Wp64
INCLUDES = /I "$(VCDIR)\include" /I "$(VCDIR)\PlatformSDK\Include"

#------------------------------------------------------------
# Linker Settings
#------------------------------------------------------------
LINK      = "$(VCDIR)\bin\link.exe"
LINKFLAGS = /SUBSYSTEM:console 
LIBPATH   = /LIBPATH:"$(VCDIR)\lib" /LIBPATH:"$(VCDIR)\PlatformSDK\Lib" 
LIBS      = libcmtd.lib 

#------------------------------------------------------------
# Targets
#------------------------------------------------------------
OBJS = main.obj
TARGET = main.exe

main.exe: $(OBJS)
    $(LINK) $(LIBPATH) $(LINKFLAGS) $(LIBS) $(OBJS) /out:$(TARGET)

clean:
    -@erase *.obj
    -@erase $(TARGET)

.cpp.obj:
    $(CL) $(DEFS) $(CLFLAGS) $(INCLUDES) $<

커맨드 라인에서 빌드를 하기 위해서는 몇가지 환경 변수를 설정해줘야한다. 이를 위한 스크립트를 만들어봤다.

vsvars.vbs

Const HKLM = &H80000002
Const strKeyPath = "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
Const strVsInstallDir = "C:\Program Files\Microsoft Visual Studio 8"
 
Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
    "." & "\root\default:StdRegProv")
Set objValues = CreateObject("Scripting.Dictionary") 
Set objTargetValues = CreateObject("Scripting.Dictionary") 
 
objTargetValues.Add "Path", _
    strVsInstallDir & "\Common7\IDE;" & _
    strVsInstallDir & "\Common7\Tools;" & _
    strVsInstallDir & "\Common7\Tools\bin;" & _
    strVsInstallDir & "\SDK\v2.0\bin;" & _
    strVsInstallDir & "\VC\BIN;" & _
    strVsInstallDir & "\VC\PlatformSDK\bin;" & _
    strVsInstallDir & "\VC\VCPackages;" & _
    "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727;" 
objTargetValues.Add "INCLUDE", _
    strVsInstallDir & "\VC\ATLMFC\INCLUDE;" & _ 
    strVsInstallDir & "\VC\INCLUDE;" & _
    strVsInstallDir & "\VC\PlatformSDK\include;" & _
    strVsInstallDir & "\SDK\v2.0\include;"
objTargetValues.Add "LIB", _
    strVsInstallDir & "\VC\ATLMFC\LIB;" & _
    strVsInstallDir & "\VC\LIB;" & _
    strVsInstallDir & "\VC\PlatformSDK\lib;" & _
    strVsInstallDir & "\SDK\v2.0\lib;"
objTargetValues.Add "LIBPATH", _
    strVsInstallDir & "\VC\ATLMFC\LIB;" & _
    "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727;"
 
objTargetValues.Add "VSINSTALLDIR", strVsInstallDir
objTargetValues.Add "VCINSTALLDIR", strVsInstallDir & "\VC"
objTargetValues.Add "FrameworkDir", "C:\WINDOWS\Microsoft.NET\Framework"
objTargetValues.Add "FrameworkVersion", "v2.0.50727"
objTargetValues.Add "FrameworkSDKDir", strVsInstallDir & "\SDK\v2.0"
objTargetValues.Add "DevEnvDir", strVsInstallDir & "\Common7\IDE"
 
objReg.EnumValues HKLM, strKeyPath, arrNames
For Each name In arrNames
    If name = "Path" or name = "INCLUDE" or name = "LIB" or name = "LIBPATH" Then
        objReg.GetStringValue HKLM, strKeyPath, name, strValue
        If Right(strValue, 1) <> ";" Then
            strValue = strValue & ";"
        End If
        objValues.Add name, strValue & objTargetValues.Item(name)
    End If
Next
 
Keys = objTargetValues.Keys
For Each name in Keys
    If Not objValues.Exists(name) Then
        objValues.Add name, objTargetValues.Item(name)
    End If 
Next
 
Keys = objValues.Keys
For Each name in Keys
    If name = "Path" or name = "INCLUDE" or name = "LIB" or name = "LIBPATH" Then
        value = RemoveDuplicate(objValues.Item(name), ";")
        objValues.Item(name) = value
    End If
Next
 
Keys = objValues.Keys
For Each name in Keys
    value = objValues.Item(name)
    output = value
    If name = "Path" or name = "INCLUDE" or name = "LIB" or name = "LIBPATH" Then
        value = RemoveDuplicate(objValues.Item(name), ";")
        output = Replace(value, ";", ";" & vbLf)
    End If
    'WScript.Echo name & vbLf & vbLf & output
    objReg.SetStringValue HKLM, strKeyPath, name, objValues.Item(name)
Next
 
WScript.Echo "Finished"
 
Function RemoveDuplicate(text, seperator)
    Set objPathDic = CreateObject("Scripting.Dictionary") 
 
    arrPath = Split(text, seperator)
    For i = LBound(arrPath) to UBound(arrPath)
        key = Trim(LCase(arrPath(i)))
        value = Trim(arrPath(i))
        If not objPathDic.Exists(key) Then
            objPathDic.Add key, value
        End If
    Next
 
    result = ""
    For Each key in objPathDic.Keys
        result = result & objPathDic.Item(key) & seperator
    Next
 
    If result <> "" Then
        RemoveDuplicate = Left(result, Len(result)-1)
    Else
        RemoveDuplicate = result
    End If
End Function

프로세스에 디버거 붙이기

  • Visual Studio 2003 (VisualCpp 7.1) 기준
  • 디버그 → 프로세스 → 프로세스 선택 → 연결.
  • 프로세스 선택한 다음에 연결 버튼을 누르기 전에 CTRL 키를 누르고 있으면, “디버깅할 프로그램 종류 선택” 다이얼로그가 뜨지 않는다.

커맨드 라인에서 프로세스 이름을 이용하여 디버거 붙이기

attach.bat

rem from http://blogs.msdn.com/greggm/archive/2004/04/22/118475.aspx

@echo off
if "%1"=="-?" goto help
if "%1"=="/?" goto help
if "%1"=="" echo The syntax of the command is incorrect.& exit /b -1
if NOT "%2"=="" echo The syntax of the command is incorrect.& exit /b -1

call :find_file tlist.exe
if NOT %ERRORLEVEL%==0 exit /b %ERRORLEVEL%

if not exist "%CommonProgramFiles%\Microsoft Shared\VS7Debug\vs7jit.exe" echo Could not find vs7jit.exe & exit /b 1

setlocal
set __found=0
for /f "tokens=1,2" %%d in ('tlist.exe') do call :parse_process %1 %%e %%d
if %__found%==0 echo Process '%1' is not running
endlocal
exit /b 0

:parse_process
if /i "%1"=="%2" goto run_command
if /i "%1.exe"=="%2" goto run_command
exit /b 0

:run_command
set __found=1
call "%CommonProgramFiles%\Microsoft Shared\VS7Debug\vs7jit.exe" -p %3
exit /b 0

:find_file
if "%~$PATH:1"=="" if "%~z1" == "" echo Error! Could not find %1. & exit /b -1
exit /b 0

:help
echo attach.bat ^<process_name^>
echo.
echo Debug a process with VS7/VS7.1.
echo example: attach.cmd notepad.exe
echo.

:!: Native Debugging 에서만 동작한다.

dumpbin 사용법

dumpbin은 비주얼 스튜디오랑 같이 딸려오는 실행 파일 분석용 유틸리티다. Dependency Walker 있으면 별로 쓸 일 없다만… 2003 버전의 경우 …vc7/bin 디렉토리에 있는데 경로가 잡혀있지 않으므로 잡아줘야한다.

사용법

dumpbin /dependents 실행파일

파일을 실행하기 위해서 필요한 DLL의 목록을 알 수 있다.

dumpbin /imports 실행파일

파일을 실행하기 위해서 필요한 DLL의 목록과 각각의 DLL에서 임포트해오는 함수의 목록을 알 수 있다.

링크

kb/visualcpptips.txt · 마지막으로 수정됨: 2016/03/24 11:08 저자 excel96