Home | Info | Research | Blog | Repos | Messages | Contact Me

 


안녕하세요. 이재홍입니다.

윈도우 프로젝트 필수 유틸리티 Subversion, Trac, CruiseControl.NET에는 420페이지 "날짜를 버전으로 사용하기" 부분이 있습니다.

이제 2010년이 되었습니다. 하지만 책에 나와있는 PowerShell 스크립트 rcversion-date.ps1, tracversion-date.ps1은 2009년까지만 정상 동작하도록 되어 있습니다.

자세한 수정 방법은 윈도우 프로젝트 필수 유틸리티 추가팁 2010년 이후 PowerShell 스크립트 설정을 참고하세요.



VS2005의 버그 때문에 이 VS2005로 컴파일한 exe 파일만 실행해도 재부팅이 발생할 수 있습니다.

그 조합은 Windows XP SP2, IE6(기타 보안패치를 설치하지 않은 상태)에 Visual Studio 2005 상세 버전 8.0.50727.42로 빌드한 exe 파일을 실행할때 입니다.

문제는 exe 파일을 한번만 실행하면 괜찮은데, 2회에서 3회 반복 실행하면 재부팅이 발생합니다. (이것도 PC마다 다름)

*** An Access Violation occurred in C:\WINDOWS\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,3072,512 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ProfileControl=Off MaxRequestThreads=16:

The instruction at 7C9401B3 tried to write to an invalid address, 75DE193E

 *** enter .exr 018FECE8 for the exception record
 ***  enter .cxr 018FED04 for the context
 *** then kb to get the faulting stack

Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
001b:7c93120e cc              int     3
1: kd> !analyze -v
FAULTING_IP:
ntdll!RtlAllocateHeap+1da
001b:7c9401b3 884706          mov     byte ptr [edi+6],al

EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 7c93120e (ntdll!DbgBreakPoint)
   ExceptionCode: 80000003 (Break instruction exception)
  ExceptionFlags: 00000000
NumberParameters: 3
   Parameter[0]: 00000000
   Parameter[1]: 7c997810
   Parameter[2]: 00000028

ERROR_CODE: (NTSTATUS) 0x80000003 - {

EXCEPTION_CODE: (HRESULT) 0x80000003 (2147483651) -

EXCEPTION_PARAMETER1:  00000000

EXCEPTION_PARAMETER2:  7c997810

EXCEPTION_PARAMETER3:  00000028

NTGLOBALFLAG:  0

FAULTING_THREAD:  ffffffff

ADDITIONAL_DEBUG_TEXT:  Enable Pageheap/AutoVerifer

DEFAULT_BUCKET_ID:  HEAP_CORRUPTION

PRIMARY_PROBLEM_CLASS:  HEAP_CORRUPTION

BUGCHECK_STR:  APPLICATION_FAULT_HEAP_CORRUPTION_STATUS_BREAKPOINT

LAST_CONTROL_TRANSFER:  from 75df2137 to 7c9401b3

STACK_TEXT: 
7c9401b3 ntdll!RtlAllocateHeap+0x1da
75df2137 sxs!CSxsPointerBase<CXMLNamespaceManager::CNamespacePrefix,CSxsPointer<CXMLNamespaceManager::CNamespacePrefix,CXMLNamespaceManager::CNamespacePrefix::ms_szTypeName> >::HrAllocateBase+0x59
75de2f21 sxs!CXMLNamespaceManager::OnCreateNode+0x12e
75de38d2 sxs!CNodeFactory::CreateNode+0xa3
75de435f sxs!XMLParser::Run+0x2fc
75de8baa sxs!SxspIncorporateAssembly+0x8b8
75de9a0f sxs!SxspCloseManifestGraph+0x98
75de98cd sxs!SxsGenerateActivationContext+0x54c
75a9a5ed basesrv!BaseSrvSxsCreateActivationContextFromStruct+0x194
75a9a760 basesrv!BaseSrvSxsCreateActivationContextFromMessage+0x79
75a84a47 CSRSRV!CsrApiRequestThread+0x431


STACK_COMMAND:  .cxr 018FED04 ; kb ; dds 18fefd0 ; kb

SYMBOL_NAME:  heap_corruption!heap_corruption

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: heap_corruption

IMAGE_NAME:  heap_corruption

DEBUG_FLR_IMAGE_TIMESTAMP:  0

FAILURE_BUCKET_ID:  HEAP_CORRUPTION_80000003_heap_corruption!heap_corruption

BUCKET_ID:  APPLICATION_FAULT_HEAP_CORRUPTION_STATUS_BREAKPOINT_heap_corruption!heap_corruption

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


이 상태에서 F5(Go)를 하면 다시 아래와 같은 BugCheck이 발생하면서 완전히 재부팅됩니다.

1: kd> g

*** Fatal System Error: 0xc000021a
                       (0xE10C62F8,0xC0000005,0x7C9401B3,0x018FED04)

STOP: c000021a Unknown Hard Error
Unknown Hard ErrorBreak instruction exception - code 80000003 (first chance)

A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

Use !analyze -v to get detailed debugging information.

BugCheck C000021A, {e10c62f8, c0000005, 7c9401b3, 18fed04}

unable to get nt!KiCurrentEtwBufferOffset
unable to get nt!KiCurrentEtwBufferBase

*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

WINLOGON_FATAL_ERROR (c000021a)
The Winlogon process terminated unexpectedly.
Arguments:
Arg1: e10c62f8, String that identifies the problem.
Arg2: c0000005, Error Code.
Arg3: 7c9401b3
Arg4: 018fed04

Debugging Details:
------------------

unable to get nt!KiCurrentEtwBufferOffset
unable to get nt!KiCurrentEtwBufferBase

... 생략 ...

ERROR_CODE: (NTSTATUS) 0xc000021a - {

EXCEPTION_CODE: (NTSTATUS) 0xc000021a - {

EXCEPTION_PARAMETER1:  e10c62f8

EXCEPTION_PARAMETER2:  c0000005

EXCEPTION_PARAMETER3:  7c9401b3

EXCEPTION_PARAMETER4: 18fed04

ADDITIONAL_DEBUG_TEXT:  Windows SubSystem

BUGCHECK_STR:  0xc000021a_csrss.exe_c0000005

DEFAULT_BUCKET_ID:  DRIVER_FAULT

PROCESS_NAME:  System

LAST_CONTROL_TRANSFER:  from 8085f2bf to 8080ba52

SYMBOL_ON_RAW_STACK:  1

STACK_ADDR_RAW_STACK_SYMBOL: fffffffff78d2b8c

STACK_COMMAND:  dds F78D2B8C-0x20 ; kb

STACK_TEXT: 
f78d2b6c  8a2e8240
f78d2b70  00000000
f78d2b74  e165e4f8
f78d2b78  e15ae350
f78d2b7c  e165e488
f78d2b80  00000000
f78d2b84  00000000
f78d2b88  b78268a4 mrxdav!_NULL_IMPORT_DESCRIPTOR+0x3c
f78d2b8c  b77fe000 mrxdav!MrxDAVEfsControlCompletion <PERF> (mrxdav+0x0)
f78d2b90  808cf973 nt!MiSnapThunk+0x6f
f78d2b94  8a32c008
f78d2b98  b782735e mrxdav!_NULL_IMPORT_DESCRIPTOR+0xaf6
f78d2b9c  000000ff
f78d2ba0  b78268a4 mrxdav!_NULL_IMPORT_DESCRIPTOR+0x3c
f78d2ba4  b7804aa8 mrxdav!_imp__GetSecurityUserInfo
f78d2ba8  808cfae6 nt!MiSnapThunk+0x382
f78d2bac  00000001
f78d2bb0  00000000
f78d2bb4  b77fe000 mrxdav!MrxDAVEfsControlCompletion <PERF> (mrxdav+0x0)
f78d2bb8  f78ee924
f78d2bbc  b7804aa8 mrxdav!_imp__GetSecurityUserInfo
f78d2bc0  b78274ba mrxdav!_NULL_IMPORT_DESCRIPTOR+0xc52
f78d2bc4  00000000
f78d2bc8  e165e301
f78d2bcc  f7477958 KSecDD!DESParityTable <PERF> (KSecDD+0x14958)
f78d2bd0  00000000
f78d2bd4  0000000e
f78d2bd8  b7827372 mrxdav!_NULL_IMPORT_DESCRIPTOR+0xb0a
f78d2bdc  8a2e8240
f78d2be0  f78ee9b4
f78d2be4  f7b774ba Ntfs!NtfsFsdClose+0x3b0
f78d2be8  8a0c1978


FOLLOWUP_IP:
mrxdav!_NULL_IMPORT_DESCRIPTOR+3c
b78268a4 5c              pop     esp

SYMBOL_NAME:  mrxdav!_NULL_IMPORT_DESCRIPTOR+3c

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: mrxdav

IMAGE_NAME:  mrxdav.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  41107b91

FAILURE_BUCKET_ID:  0xc000021a_csrss.exe_c0000005_mrxdav!_NULL_IMPORT_DESCRIPTOR+3c

BUCKET_ID:  0xc000021a_csrss.exe_c0000005_mrxdav!_NULL_IMPORT_DESCRIPTOR+3c

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

자세한 원인 분석은 생략하겠습니다. 귀찮기도 하고 분석을 해봐도 뭐가 잘못된건지도 잘 모르겠고...

해결책은 이렇습니다.

VS2005 서비스팩1(KB926605)과 패치(KB932237)를 설치한다.

결론은 서비스팩1과 패치가 설치되지 않은 VS2005는 잘못된 exe를 생성해낸다는 것입니다.

지금까지 설명한 것과 비슷한 문제가 발생한다면, Visual Studio 2005 서비스팩과 패치를 설치해보는 것을 권장합니다.

- 원인을 발견하기까지
빌드 서버에서 빌드한 exe를 실행하면 계속 재부팅 현상이 발생하는데, 개발자 PC에서 빌드한 바이너리는 정상적으로 동작하는 것이었습니다. 약 이틀동안의 삽질을 하면서 소스코드의 별별 장소를 다 바꿔보고, 컴파일 옵션도 바꿔봤지만 소용이 없었습니다.

더 웃긴것은 문제가 발생하는 사람도 있고 그렇지 않은 사람도 있었던 것입니다.

그때 문득 팀원 중 한명이... 개발자 PC와 빌드 서버의 환경이 다른것이 아닐까? 하는 의견과 함께 VS2005의 버전을 확인하기 시작했습니다.

문제가 생기지 않는 개발자의 VS2005 버전은 8.0.50727.867(서비스팩1과 패치 설치)이었고 문제가 생기는 개발자와 빌드 서버의 VS2005 버전은 8.0.50727.42 였던 것입니다.

- 끝으로
빌드 서버를 이용한 개발환경에서 개발 도구(VS2005)의 버전 때문에 예상치 못한 문제가 발생할 수 있다는 점을 경험하게 되었습니다.

개발자 PC의 개발 도구(VS2005)와 빌드 서버의 개발 도구(VS2005)의 버전은 항상 일치시키켜야 합니다. 그렇지 않으면 버전 차이로 인해 원인 추적이 까다로운 문제가 발생할 수 있습니다.





윈도우 프로젝트 필수 유틸리티 - Subversion, Trac, CruiseControl.NET이 출간되었습니다.

요즘 제가 블로그에 글 쓰는 것이 뜸했습니다. 다 이 책 때문이었습니다. 원래는 좀더 일찍 나올 수 있었는데, 표지 결정이 늦어지면서 5월 30일에 출간하게 되었습니다. (이때를 틈타 막판에 원고를 수정할 수 있었습니다. 편집자들이 너무 고생을해서 저를 잡아먹으려고 작전을 세우고 있다는 소문이...)

윈도우 기반 환경에서 Subversion, Trac, CruiseControl.NET을 활용하는 방법에 관한 책입니다.

윈도우용 Subversion, TortoiseSVN의 기본적인 사용 방법과 Apache와 연동하기, Trac 설치 및 사용 방법, CruiseControl.NET 빌드 스크립트 문법과 사용 방법을 설명합니다.

그리고 PDB 파일에 Subversion 저장소 정보를 기록하고 심볼 서버 형태로 저장하는 방법도 포함되어 있습니다. 이 과정을 CruiseControl.NET과 연동하는 방법도 설명합니다.

배포 자동화를 위한 릴리스(Release) 서버 구축 방법, 그리고 문서화 도구인 Doxygen 사용 방법과 CruiseControl.NET을 연동하기, Subversion 저장소와 Trac 데이터베이스를 자동으로 백업하기, 윈도우 드라이버 자동 빌드 하기 등의 내용으로 구성되어 있습니다.

내용 설명이 길었습니다. 결론은 "개발자가 일하기 편한 환경 만들기"입니다. 개발 이외의 관리에 소비되는 시간을 절약하여 좀더 개발에 집중할 수 있게 하는 것이 목적입니다.

많은 개발자들이 삽질에서 벗어나 좀더 편한 환경에서 일할 수 있었으면 합니다.

자주 하는 질문
추가 팁
독자 후기
오탈자




윈도우에서 Subversion 커밋 메일을 보내는 방법을 문서로 작성하였습니다.

이번 문서는 커밋 메일 설정에 어려움을 겪고 계신 분들을 위해 작성하였습니다. 커밋 메일에 관련된 질문은 이 글의 댓글로 달아주시면 됩니다.

http://www.pyrasis.com/main/SubversionCommitMailForWindows




C언어를 사용하다 보면 예외처리 부분이 상당히 미흡하다는 것을 알 수 있습니다. C++은 언어 차원에서 try, catch라는 예외처리 문법을 제공해 주고 있습니다.

C언어는 C 표준 라이브러리에 있는 setjmp(), longjmp() 함수를 사용하여 예외처리를 할 수 있지만 상당히 구식이라 직관적이지도 못하고 사용하기도 불편합니다.

그래서 C언어에서는 Structured Exception Handler(SEH)라는 예외처리 방식을 사용할 수 있습니다. SEH는 Microsoft Visual C++ 컴파일러 차원에서 제공하는 예외처리 문법입니다. 물론 윈도우상에서만 사용할 수 있습니다.

void tryexcept()
{
    __try
    {
        int *world = NULL;

        *world = 8// 널 포인터에 값 대입

        printf("hello try %d\n", *world);
    } 
    __except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?
        EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
    {
        printf("EXCEPTION_ACCESS_VIOLATION\n");
    }
}


__try 부분에 예외를 검사할 코드를 넣습니다. 예외가 발생하면 __except 부분에서 지정한 코드가 실행되게 됩니다. 예외도 종류가 엄청 많은데, __except의 괄호 안에 예외 검사식을 넣어줘야 합니다.

위 예제의 경우 널 포인터에 값을 대입하고 있습니다. 이 코드를 실행하면 메모리 접근 위반 예외가 발생하고 __except 부분의 코드가 실행됩니다. 괄호안의 부분이 메모리 접근 위반 예외인지 판단하는 부분인데, GetExceptionCode() 함수로 현재 발생한 예외 코드를 구해옵니다. 그리고 그 예외가 EXCEPTION_ACCESS_VIOLATION (메모리 접근 위반 예외)인지 검사하고 맞으면 __except 블록 안의 코드를 실행하고 아니면, 상위 예외 처리 핸들러에게 넘깁니다.

  • EXCEPTION_EXECUTE_HANDLER (1)는 __except 블록 안의 코드를 실행합니다.
  • EXCEPTION_CONTINUE_SEARCH (0)는 __except 블록 안의 코드를 실행하지 않고 상위 예외 처리 핸들러에게 넘깁니다.
  • EXCEPTION_CONTINUE_EXECUTION (-1)은 예외를 무시하고 예외가 발생한 부분 부터 코드를 다시 실행합니다. 하지만 예외가 발생한 원인을 해결해 주지 못하면 무한루프에 빠집니다.

이 매크로들은 excpt.h에 정의되어 있습니다.

__except의 괄호 안에 모든 예외의 종류를 다 적어줄 순 없는 노릇입니다. 그래서 예외 코드를 판별하여 각각 처리해주는 함수를 만들어 사용할 수 있습니다. 여기서 예외의 원인을 수정하여 EXCEPTION_CONTINUE_EXECUTION를 리턴하면 코드를 계속 실행 할 수 있습니다.

int Value = 0;

DWORD ExceptionFilter(DWORD ExceptionCode)
{
    switch (ExceptionCode)
    {
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
        {
            // 예외가 발생한 원인을 수정
            Value = 1;

            return EXCEPTION_CONTINUE_EXECUTION;
        }
    case EXCEPTION_ACCESS_VIOLATION:
        {
            // 예외가 발생한 원인을 수정

            return EXCEPTION_CONTINUE_EXECUTION;
        }
    // 다른 여러가지 예외 코드를 처리
    //case EXCEPTION_XXX
    default:
        return EXCEPTION_EXECUTE_HANDLER;
    }
}

void tryexceptfilter()
{
    __try
    {
        int world = 10;

        world = world / Value;
    } 
    __except (ExceptionFilter(GetExceptionCode()))
    {
        printf("EXCEPTION !\n");
    }
}


위 예제는 GetExceptionCode() 함수를 이용하여 예제코드를 ExceptionFilter() 함수에 넘기고 각 코드에 맞는 예외의 원인을 수정하도록 되어 있습니다.

world를 Value로 나누었는데 Value는 0입니다. 그래서 EXCEPTION_INT_DIVIDE_BY_ZERO예외가 발생하였고, ExceptionFilter() 함수에서는 이 예외 코드에 맞게 Value에 1을 대입하여 주었습니다. 그리고 EXCEPTION_CONTINUE_EXECUTION를 리턴하여 예외가 발생한 부분에서 코드를 계속 실행하게 됩니다.

이 ExceptionFilter()에 여러가지 다른 예외 코드를 추가해서 처리할 수 있습니다. 물론 여기에 추가되지 않은 예외가 발생하면 EXCEPTION_EXECUTE_HANDLER가 리턴되고 __except의 부분이 실행됩니다.

이번에는 __finally의 사용입니다. __finally 부분은 __try 부분이 실행에 성공하던 예외가 발생하던 무조건 실행됩니다.

void tryfinally()
{
    __try
    {
        printf("hello try\n");
    } 
    __finally
    {
        printf("hello finally\n");
    }
}


주로 사용되는 패턴은 __try 부분에서 동적 메모리 등을 할당했을때 __finally 부분에서 메모리가 할당 되었으면 해제하는 식입니다. __finally는 무조건 실행되기 때문에 메모리를 할당 했더라도 어떤 예외가 발생하여 프로그램이 중단되면 __finally 부분에서 할당한 메모리를 해제 할 수 있습니다.

비슷한 패턴으로는 크리티컬 섹션이나, 뮤텍스등 동기화 객체를 사용할 때, 객체를 획득한 상태에서 예외가 발생했더라도 __finally 부분에서 획득한 동기화 객체를 해제 할 수도 있습니다.

__try 안에서 return을 하더라도 __finally 부분이 실행되고 값을 리턴 합니다.

이 __finally는 __except와는 동시에 사용할 수 없습니다. 무조건 __try, __finally 혹은 __try, __except로만 사용할 수 있습니다.



CPU는 mov eax, 0x100 같은 명령을 그대로 알아듣지 못합니다. 그래서 CPU가 알 수 있는 2진수 형태의 기계어로 변환해서 실행을 하게 됩니다.

오늘은 그 CPU가 실행하는 기계어를 사람이 알아 볼 수 있는 어셈블리 형태로 해석하는 방법을 알아보도록 하겠습니다.

먼저 인텔 CPU 매뉴얼이 필요합니다.(이건 인텔 웹사이트에서 받으세요, 이번 글에서는 Volume 2A: Instruction Set Reference, A-M"를 보면 됩니다.) 각 기계어가 의미하는 뜻을 알아보려면 매뉴얼을 봐야 합니다. 이 글에서는 mov 명령을 예로 들면서 설명하도록 하겠습니다.

B8 00 01 00 00   mov         eax, 100h

mov 명령을 인텔 매뉴얼에서 찾아보면 다음과 같이 B8입니다.


mov 명령도 B8만 있는 것이 아니고 뒤에 오는 오퍼랜드의 종류에 따라서 여러가지가 있습니다. 이것은 인텔 매뉴얼 Instruction Set Reference의 mov 부분에 목록이 나와 있습니다. 그런데 mov 도표에 보면 B8+라고 되어 있는데 이+는 목적지 오퍼랜드의 종류에 따라 바뀐다는 것을 뜻합니다. 이 순서는 eax, ecx, edx, ebx, esp, ebp, esi, edi 순으로 지정되어 있는데, 따라서 mov eax는 B8, ecx는 B9, edx는 BA, ... edi는 BF가 됩니다.

그런데 mov 명령 중에서도 뒤에 오퍼랜드가 eax, ecx등 여러가지로 바뀌는데도 기계어는 똑같은 경우가 있습니다. 이것은 레지스터에 주소의 값을 대입하는 경우인데, ModR/M 바이트를 보고 판별합니다.

ModR/M 바이트는 3부분으로 구성되어 있는데, Mod 2비트, Reg/Opcode 3비트, R/M 3비트로 구성되어 있습니다. 이것은 인텔 매뉴얼 Instruction Set Reference의 앞부분에 보면 "32Bit Addressing Forms with the ModR/M Byte"라고 표 형태로 되어 있습니다.

8B 07            mov         eax, [edi]



위의 예에서 mov는 8B이고 eax, [edi] 부분은 07입니다. 이것은 ModR/M 표에서 찾으면 되는데, 첫번째 오퍼랜드는 세로줄이며, 두번째 오퍼랜드는 가로줄입니다. eax 줄에서 [edi]를 찾으면 07이 나옵니다. (이 경우에는 목적지가 레지스터기 때문에 mov가 8B입니다. 목적지가 메모리주소가 될 경우 mov는 89로 바뀌고, 표에서 찾을 때에도 첫번째 오퍼랜드는 가로줄이 되고 두번째 오퍼랜드는 세로줄이 됩니다. 그렇기 때문에 오퍼랜드의 종류와 방향에 따라 명령어의 기계어가 바뀌게 됩니다.)

8B 59 04         mov         ebx, [ecx+4]



이것 역시 mov는 8B이고 ebx, [ecx+] 부분은 59입니다. 4는 그 바로 뒤에 나오고 있습니다. 이걸 ModR/M 표에서 찾으면 ebx 줄에서 [ecx]+disp8에 있습니다. +disp8은 +4와 같은 오프셋을 뜻합니다. 8은 8비트 숫자를 나타냅니다. +disp32는 32비트 오프셋이겠죠.

8B 0C 50         mov         ecx, [eax+edx*2]



이번에 것을 찾아보면 ecx 줄에 [eax+edx*2]라는 것이 없을 겁니다. 이런 경우는 [--][--]를 찾으면 됩니다. 이건 0C인데 eax+ebx*2는 어디에 있느냐? 50이 바로 eax+ebx*2를 뜻하고 있습니다. 이것은 ModR/M 표에서는 안나오고 ModR/M 바로 뒤에 보면 "32Bit Addressing Forms with the SIB Byte" 표에 나와 있습니다. eax 줄에서 edx*2를 찾으면 50이 나옵니다.



 이 표를 찾는 것만 알고 있으면 어떤 기계어가 나와도 인텔 매뉴얼에서 다 찾을 수 있을 것입니다.



WebSVN 2.0rc4 한글 패치를 공개합니다.

만든지는 좀 오래 되었는데 패치를 원하시는 분들이 계셔서 늦게나마 공개합니다. 패치로 수정된 것들은 다음과 같습니다.
  • 한글을 정상적으로 표시. (로그 보기, 파일 내용 보기, 비교, 수정한 사람 보기)
  • enscript를 사용한 상태에서도 한글을 정상적으로 표시
  • 시간과 날짜를 정상적으로 표시
  • 한글 번역 메시지 추가.

http://www.pyrasis.com/main/WebSVNPatch

본 사이트 소스코드 저장소에서도 패치 내용을 조회 할 수 있습니다.




CDE는 Collaborative Development Environment의 약자인데 말그대로 협업 개발 환경입니다. 이 협업 개발 환경의 중요성은 이제 손아프니 더 이상 강조하지 않기로 하고, 본론으로 들어가서 현재 나와있는 CDE 시스템들을 리뷰해 보도록 하겠습니다.

리뷰한 시스템들 중에서 상용 시스템들도 있으며 무료로 사용할 수 있는 것들도 있습니다.

CollabNet Enterprise Edition
http://www.collab.net/products/enterprise_edition
Subversion을 만든 CollabNet의 CDE 시스템입니다. 15명 까지 무료로 사용할 수 있습니다.

일단 설치가 아주 짜증납니다. 스크립트를 받아와서 설치를 해야 하는데 Red Hat Enterprise Linux ES release 3 (Taroon Update 6)에서 설치해야 합니다. 다른 리눅스 배포판에서 설치할 경우 상당한 삽질을 각오해야합니다. 첫 진입 장벽이 이렇게 높으니 15명 무료라고 해도 아무도 안쓴다는 결론이 나옵니다.

데모 사이트도 사용법이 불분명하고 도무지 제품을 팔 생각이 있는지 의심이 됩니다. 삼성전자에 팔았다고 광고를 하고 있긴 한데, 제 예상으로는 설치된 서버를 통째로 팔고 거기 설치, 유지 보수, 관리 인력도 함께 팔았을(?) 것 같은 생각이 듭니다.

데모 사이트 보다는 http://tigris.org를 보는 것이 속편하며 이것이 CollabNet Enterprise Edition을 사용한 사이트라고 봐도 됩니다. 버그 트래커, 소스코드 저장소 뷰어, 메일링 리스트, 파일릴리즈 등 기본적인 기능을 갖추었으나 모두 상당히 미흡(허접)합니다.

SourceForge Enterprise Edition
http://sourceforge.net/powerbar/sfee
오픈소스 프로젝트 호스팅 사이트로 유명한 소스포지의 엔터프라이즈 버전입니다. 이것도 15명까지 무료로 사용할 수 있습니다.

설치는 Collab 제품과는 달리 아주 간편합니다. 따로 설치할 필요는 없고 VMware 이미지(VMware Virtual Appliance)로 배포를 하고 있어서, 이것만 받아서 VMware에서 실행하면 서버가 바로 동작됩니다.

서버를 부팅하고 root 로그인을 하면 설정하는 것이 나오는데 서버 IP 설정과 SMTP 서버 설정만 해주면 바로 작동이 됩니다.

sourceforge.net 처럼 사용자를 등록하고 그 사용자가 프로젝트를 제출하고 프로젝트 공간을 호스팅 받아 운영을 하는 방식입니다. 프로젝트와 사용자가 많아도 일관성 있게 관리할 수 있도록 되어 있습니다.

트래커, 문서화, 태스크, 게시판, 소스코드 저장소 뷰어, 리포트, 파일릴리즈, 위키등의 기능을 가지고 있으며, sourceforge.net과는 전혀 다른 모습을 보여줍니다. 모든 트래커, 태스크, 문서, 커밋등은 고유의 ID를 가지고 있어서 관리 및 추적이 편리합니다. 위키에서도 이 고유 ID를 적어주면 바로 링크가 됩니다. 하지만 위키는 대충 모양만 내서 그런지 일반 위키에 비해 기능이 미흡합니다.

각 트래커나 태스크, 문서화, 게시판, 위키등을 모니터링 하여 메일로 받아 볼 수 있지만 소스코드 커밋은 모니터링이 되지 않는 단점이 있습니다.

저장소는 CVS와 Subversion을 사용할 수 있고 하나의 프로젝트에 여러개의 저장소를 만들 수 있습니다. 저장소 뷰어는 ViewVC를 사용하는데 소스코드 컬러링이 되지 않습니다.

저장소에 커밋을 하고 트래커나 태스크의 고유 ID를 적어주면 해당 트래커, 태스크에 커밋로그가 붙습니다. 커밋을 할 때 꼭 트래커나 태스크 ID를 적어줘야 커밋이 되도록 설정할 수도 있습니다.

디자인이 매우 딱딱하며 버튼들은 유닉스의 모티프류 어플리케이션을 보는 것 같은 느낌이 듭니다.

데모 사이트에서 대강 모양을 확인할 수 있습니다. 하지만 모양만 확인이 가능하고 전체 기능을 사용하려면 VMware 이미지를 받아서 해야 합니다.

GForge Advanced Server
http://gforgegroup.com/es
소스포지를 포크하여 개발한 시스템인데 완전 새로 개발하여 전혀 다른 시스템이 되었습니다. GForge라는 오픈소스 프로젝트를 기반으로 만든 것이지만 오픈 소스 버전과는 질적으로 수준이 다른 시스템입니다. GForge 프로젝트가 새로운 회사를 차려서 판매를 하고 있고, 15명 까지 무료로 사용할 수 있습니다.

소스포지 엔터프라이즈와 같이 VMware 이미지를 받아서 네트워크 설정만 하면 간편하게 사용할 수 있습니다.

트래커, 게시판, 리포트, 문서화, 뉴스, 파일릴리즈, 메일링 리스트, 위키, 소스코드 저장소 뷰어를 갖추고 있으며, 트래커에서는 게시물의 세부적인 속성을 하나 하나 지정할 수 있고 위키에서 트래커를 바로 링크할 수 있습니다. 하지만 저장소의 리비전은 링크가 되지 않습니다. 이 위키도 모양만 내서 그런지 일반 위키에 비해 기능이 매우 미흡합니다.

트래커, 게시판, 문서화, 파일릴리즈등 각각의 게시물을 하나 하나 모니터링 할 수 있습니다. 하지만 위키 변경사항은 모니터링 되지 않습니다.

개인 페이지도 소스포지 엔터프라이즈에 비해 기능이 좋으며 개인 위키, 개인 파일 저장공간, 개인 일정(gantt chart)을 제공합니다.

저장소도 CVS, Subversion 뿐만 아니라 ClearCase, Visual Source Safe도 사용이 가능했고 커밋로그를 메일링 리스트로 보내도록 설정하면 자동으로 커밋로그 메일링 리스트가 생성되어 편리합니다. 하지만 소스코드 컬러링은 되지 않고, 프로젝트 하나에 저장소 하나만 표시할 수 있습니다.

소스포지와 마찬가지로 저장소에 커밋을 하고 트래커의 고유 ID를 적어주면 해당 트래커에 커밋로그가 붙습니다. 커밋을 할 때 꼭 트래커 ID를 적어줘야 커밋이 되도록 설정할 수도 있습니다.

디자인은 그라데이션을 사용하여 소스포지 보다는 괜찮지만 칙칙한 감이 있으며 한글로 설정해서 사용할 경우 폰트 크기가 코딱지 만하게 나와서 보기도 불편하고 클릭하기도 힘듭니다.(클릭을 하려면 조준(?)을 해서 해야합니다, 이건 아시아권 시장을 포기한거나 마찬가지겠죠)

데모 사이트에서 사용자를 등록하고 테스트 프로젝트를 만들어서 각 기능을 사용해 볼 수 있습니다.

Trac
http://trac.edgewall.org
가장 최근에 나온 시스템이며 여러 오픈소스 프로젝트 및 기업에서 사용하고 있습니다. 오픈소스 프로젝트로 제한 없이 사용할 수 있습니다. 하지만 얼마전에 회사가 망해서 커뮤니티 형태로 계속 개발이 되고 있긴 하지만 버전 1.0도 나오지 않았는데 이렇게 되서 안타까운감이 있습니다.

VMware 이미지 같은 것은 제공해 주지 않지만 덩치가 크지 않아 설치가 크게 어렵지 않은 편입니다.

전체 시스템이 위키 기반으로 되어 있고 위키의 기능도 일반 위키 만큼 상당히 좋습니다. 위키에서 각 티켓, 저장소 리비전 등을 손쉽게 링크할 수 있습니다.
 
로드맵(마일스톤)을 이용해서 프로젝트의 개발 진행상태를 파악할 수 있고, 타임라인을 통해서 프로젝트의 전체적인 변경사항들을 한눈에 볼 수 있습니다. 로드맵은 iCalendar 형식으로 출력할 수 있고 타임라인은 RSS 형태로 받아 볼 수 있어서 아주 편리합니다.

저장소 브라우저도 소스코드 컬러링이 아주 잘되며(이것을 중점적으로 개발한 것 같은 느낌이 듭니다. 백엔드로 Silver City와 Enscript를 사용합니다.), 디자인도 괜찮습니다. 리비전간 Diff도 깔끔하게 출력되고 한글 폰트 크기도 적당하게 나옵니다.

저장소에 커밋을 하면 커밋 로그를 메일로 보낼 수 있고 해당 티켓에 커밋 로그를 붙일 수 도 있습니다. 저장소 타입도 Subversion 뿐만 아니라 CVS, Darcs, Bazaar, Mercurial, Git, Perforce, SVK, Arch, monotone, ClearCase등 많은 버전 컨트롤 시스템을 지원합니다.

각종 플러그인이 많이 나와 있지만 파이썬 프로그래머가 많지 않아서 PHP 기반 프로젝트들에 비해 개발 속도도 느리고 결과물의 양이 많지 않은 것 같습니다.

단점으로는 정식버전이 아직 나오지 않았다는 것과 위의 CDE 시스템들에 비해 여러 프로젝트를 관리하는 기능이 없어 불편합니다. 그리고 사용자 관리와 개인 페이지기능도 매우 부실합니다.

프로젝트 생성과 관리에 있어서도 다른 시스템들은 웹상에서 모든 것을 처리할 수 있지만 트랙은 커맨트라인 도구(trac-admin)을 이용해야 하는 불편함이 있습니다.

기타 시스템들
GForge - 오픈소스라서 제한 없이 사용할 수 있습니다. 국내에서는 kldp.net에서 사용하고 있지만 설치 및 관리를 하려면 상당한 삽질을 해야하고, 기능도 그다지 뛰어나지 않습니다.
ClearCase - 기능이 막강하다고 하는데, 가격이 매우(!) 비싸고 사용법이 복잡해서 쓸일은 없을 것 같습니다.

맺음
지금까지 여러가지 CDE 시스템들을 알아보았는데 완벽한 시스템은 없었습니다. 그리고 사용계층이 대중적이지 않은데다 이런 시스템을 사용하는 소프트웨어 개발업체가 적어, 제품이 많이 팔리지도 않고, 가격도 비싸고, 개발도 활발하지 못한 악순환이 계속 되고 있습니다. 이 협업 시스템이 널리 퍼져 제품도 많이 발전했으면 합니다.



"CVS에서 Subversion으로 바꾸기, 좋을까(http://cwryu.tistory.com/17)"라는 글이 많은 사람들에게 인용되고 있고 마치 전문가의 글인것 처럼 전달되면서 편협된 정보를 전달할 우려가 있어, 제가 이에 대한 반박을 하고자 합니다.

그놈 CVS가 subversion으로 전환한 이후 얼마가 지났으나 사실 큰 장점을 못 느끼는 게 사실이다.  애초에 subversion은 cvs와 같은 모델의 vc를 만들면서 cvs의 단점을 보완하는 게 목적이었고, 사용법부터 시작해서 크게 다르지 않다.  흔히 cvs와 비교해 장점이라고 하는 것들을 살펴보면:
1. renaming, file property 따위의 versioning

안정된 프로젝트일 수록 디렉토리 이름을 바꾸는 일은 그리 많지 않다.  (과거에 회사에서 오타가 섞인 이름마저 끝내 바꾸지 못했던 기억이 있다.)

바꾸어 말하면 안정된 프로젝트까지 가기에는 여러가지 바뀔게 많다는 소리인데, 이런 상황에서는 어쩌란 말인지? 거기다 오타가 섞인 이름을 끝내 바꾸지 못했다는 것을 당연하다는 듯이 이야기 하는 것도 정말 어처구니가 없습니다.

버전 관리 시스템을 사용한다는 것은 잘못된 것은 수정하고 그 기록을 남기기 위해서 사용하는 것입니다. CVS에서 이름 바꾸기가 잘 되지 않는다고 잘못된 것을 방치하는 것은 주객이 전도된 상황이 아닌가요?

이 주장은 단점도 아닌데(아주 당연한 기능인데) 단점을 만들기 위해 근거를 끼워 맞춘것에 불과합니다.

2. atomic commit/tag

CVS의 commit/tag가 atomic이 아니라고 해서 문제가 될 상황도 별로 많지 않다.  오히려 (사람이 직접 신경 쓰지 않으면 해결할 방법이 없는) 논리적인 충돌이 더 많이 발생한다.

문제가 될 상황이 별로 많지 않다는 것은 한번이라도 문제가 생길 수 있다는 소리이며, 커밋과 태깅을 하다가 중간에 짤리면 그것을 수정하기 위해 엄청난 시간과 노력이 소비된다는 것을 알아야 합니다.

Subversion은 커밋과 태깅이 atomic 해서 이런 문제가 전혀 발생하지 않으므로 여기에 소비되는 시간과 노력을 절약할 수 있습니다.

논리적 충돌은 곧 버그를 의미하며 이것을 안고치면 계속 버그로 남아있을 텐데 당연히 신경을 쓸 수 밖에 없습니다. 그리고 이런 이유로 atomic 커밋이 별 장점이 되지 않는다고 주장하는 것은 문제의 핵심을 벗어난 것입니다.

atomic 커밋, 태깅의 핵심은 이것을 지원하지 않음으로 인해서 생기는 문제가 어떤 것이냐는 것입니다.

3. lightweight branching

이 부분은 서버의 로드와 관련된 것이므로, 알기 어렵다.  분명히 장점이다.

알기 어려우면 왜 적었는지 모르겠습니다. 제가 다시 설명하자면, Subversion은 태깅 및 브랜칭을 할 때 내부적으로 링크만 생성하는 것이기 때문에 서버측의 로드는 거의 제로에 까깝습니다.

4. diff/revert에 네트워크 연결이 필요없다

이것만은 확실히 좋다는 걸 느낀다.  cvs는 diff를 할 때마다 해당 파일의 전체 내용을 전송받는데, 유럽에 있는 gnome cvs 서버에서 이 내용을 받는 건 만만치 않은 일이다.  심심풀이 해커도 그리 느끼는데 업으로 삼는 사람들은 더더욱 좋다고 생각할 듯.

서버가 멀리있는 사람 뿐만 아니라 내부 네트워크에서도 이런 차이가 쾌적한 개발 환경을 제공해 준다는것을 간과하고 있습니다.

subversion은 cvs와 비교해 분명한 장점이 있으나 멀쩡히 안정적으로 잘 돌아가는 닫힌 프로젝트의 vcs를 cvs에서 svn으로 바꾸는 일은 별로 추천하지 못하겠다.  옮겨가는 데 별로 무리도 없지만 얻는 장점도 별로 없기 때문이다.  (gnome처럼 vcs 이용자가 워낙 많고 세계 여기저기에 퍼져 있는 경우라면 위의 3/4 항목으로 큰 이득을 볼 수도 있겠지만)

쓰는 사람 입장에서 CVS가 불편하다면 닫힌 프로젝트라도 바꾸는 것이 당연하고, 얻는 장점도 별로 없는 것이 아니라, 인용글에서 제시한 1, 2, 3, 4번의 장점을  장점을 모두 얻을 수 있는데, 뭔 장점이 별로 없다는 것인지 모르겠습니다.

저는 CVS에서 Subversion 전환을 강요할 목적은 전혀 없습니다. 하지만 잘못된 정보가 전달되는 것을 막기 위해 이렇게 글을 썻음을 다시 한번 알려드립니다.



윈도우와 유닉스는 시간단위가 달라서 서로 호환이 안됩니다.

윈도우 시간은 64비트 정수에 1601년 1월 1일 부터 100나노 초 단위로 1씩 증가하고, 유닉스 시간은 32비트 정수에 1970년 1월 1일부터 1초 단위로 1씩 증가합니다.

특히나 윈도우에서 사용되는 C 표준함수(C Run-Time Library)들은 유닉스와 같이 1970년 1월 1일을 기준으로 날짜를 처리합니다.

그래서 윈도우 API와 C 표준함수를 섞어서 프로그램을 만들었을 경우 시간 단위가 맞지 않게 됩니다. 윈도우 API만 사용하거나 C 표준함수만 사용하여 프로그램을 만드는 것이 좋겠지만 섞어서 쓰게되는 경우가 있을 수 있습니다. 예를 들면 유닉스(리눅스)의 프로그램을 윈도우에 포팅한다거나 그 반대의 경우입니다. 이럴 때 두 시간 단위를 어떻게 변환하는지 설명하겠습니다.

윈도우 시간에서 유닉스 시간으로 변환
윈도우 시간은 1601년 부터 시작했고 유닉스 시간은 1970년 부터 시작했기 때문에 먼저 시작한 윈도우 시간이 훨씬 숫자가 큽니다. 윈도우 시간 단위(100나노 초)를 유닉스 시간 단위(1초)로 변환 한 뒤, 1601에서 1970년의 차이인 369년을 빼주면 됩니다.

유닉스 시간에서 윈도우 시간으로 변환
위와 반대로 유닉스 시간이 나중에 시작 했기 때문에 윈도우 시간보다 숫자가 작습니다. 유닉스 시간 단위(1초)에서 윈도우 시간 단위(100나노 초)로 변환한 뒤 1601년과 1970년의 차이인 369년을 더해주면 됩니다.

그럼 이제 실제로 계산을 해보겠습니다.

윈도우 -> 유닉스
윈도우 시간 / 10000000을 하면 유닉스 시간 단위인 1초 단위가 됩니다. 1초는 1000000000나노 초이기 때문에 윈도우에서는 1초가 10000000입니다. (100나노초가 최소 단위이므로)

여기에 369년을 뺍니다. 369년 x 365일 = 134685일이고 여기에 89일을 뺀 134774일을 빼야 합니다. 왜 89일이냐하면 1601년과 1970년 사이에 윤년이 계산상으로는 92개지만, 한 세기가 시작하는 해는 윤년이 아닙니다. 즉 1700, 1800, 1900년은 윤년이 아니므로 이 3개를 제외하면 실제 윤년은 89개가 됩니다. (윤년은 하루가 더 길기 때문에 때문에 89일이 됩니다.)

(참고로 한 세기가 시작하는 해라고 하더라도 400으로 나누어 떨어지는 해는 윤년이 아닙니다. 즉 1600년, 2000년은 윤년이 아닙니다.)

134774일을 초로 바꾸면 134774일 x 24시간 x 60분 x 60초 = 11644473600초가 되고 유닉스 시간단위로 변환한 값에 11644473600초를 빼면 됩니다.

유닉스 -> 윈도우
유닉스 시간에 11644473600초를 먼저 더한 값을 윈도우 시간 단위로 변환합니다. 유닉스 시간 * 10000000을 하면 100나노 초 단위인 윈도우 시간이 됩니다. (윈도우 시간으로 먼저 변환한 뒤 100나노초 단위인 116444736000000000을 더해줘도 됩니다.) 물론 변수는 64비트 정수형을 사용해야 하겠죠.

간단하게 하면 정리를 하면
윈도우 -> 유닉스 : 윈도우 / 10000000 - 11644473600
유닉스 -> 윈도우 : (유닉스 + 11644473600) * 10000000 혹은 유닉스 * 10000000 + 116444736000000000