윈도우 디버깅 툴 몇가지

아래 툴을 이용하면 직접적인 디버깅은 아니지만 프로그램이 문제를 일으킬 경우 정보를 수집하는데 도움을 준다.

ProcessMonitor
윈도우 이벤트를 수집할 수 있다. 예전에 RegMon과 FileMon툴이 하나로 합쳐진 것.

ProcessExplore
작업 관리자 보다 많은 정보를 보여준다

TcpMonitor or cports
윈도우에 열려있는 여러 socket의 상태 관련 정보들을 보여준다.

DebugView
Debug Console로 출력을 한 메세지들을 보여준다.

Handle
각 프로세스가 잡고 있는 핸들값와 그 정보를 출력한다.

windbg
가장 유명한 윈도우 커널 디버거

위에 소개한 대부분 아래 주소에서 받을 수 있다
http://technet.microsoft.com/en-US/sysinternals

Linking 101

아래 글은 외국 블로그의 글을 무단(!)으로 번역하여 포스팅한것 입니다.

주소 : http://vladimir_prus.blogspot.com/2009/06/linking-101.html

최근에, 나는 점점 더 많은 사람들이 링킹 과정에서 문제를 겪는 것을 보게된다.

마치 가장 재수없는 경우의 에러이거나 보통 사람은 고칠수 없는 것처럼 여긴다.

그럴만한데는 여러가지 이유가 있는데,

대학교에서 자바를 기본 언어로 사용하고 있다는 것과 and alarming spread of header-only-philia. (?)

이제 나는 링크시 발생하는 에러를 진단할 수 있는 간단한 검사방법을 알려주겠다.

우선 몇가지 기본지식을 살펴보자.

만약 당신의 일이 C++로 프로그래밍 하는 것이라면, -l 옵션과 -L 옵션이 무엇인지, 어떻게 다른것인지 알 필요가 있다.

또한 full path가 주어진 라이브러리 파일( .a 또는 .so 또는 .lib)은, 2가지 방식으로 링크를 할 수 있다.

만약 당신이 위와 같은 내용에 대해서 모른다면, 희망이 없다. 그냥 다른 일자리를 알아보는게 좋은것같다.

위 내용에 대해 알고 있다면, 가장 흔한 에러인 ‘undefined symbol’ 에서를 분석하는 것을 살펴보자.

첫번째로, 못찾은 심볼이 어디에 정의되어 있는지 살펴본다.

경험에서 우러나온 추측이 대체로 도움이 된다.

예를들어, boost::system::foobar라는 심볼 이름은 대부분 Boost.System 라이브러리에 담겨져 있다.

( 많은 사람들이 그런 추측을 하지 못한다는것이 놀랍다 )

그리고나서, 컴포넌트의 문서를 살펴보거나, 리눅스 패키지를 살펴봄으로써 logical 컴포넌트들을 링크시키게 할 수 있을지 찾아보자.

예를 들어, -lboost_filesystem 같은 링커 커멘드를 추가해야 한다는 것을 결정해야 한다.

두번째로, 사용된 physical 라이브러리 파일이 맞는것인지 확실하게 확인한다.

그리고 링커가 당신이 바라지 않은 다른 버전의 라이브러리 파일을 디렉토리에서 참조하고 있는건 아닌지도 확인해보자.

만약 당신이 프로그램을 링킹하는 동안에 에러가 발생하면다면 GNU 링커에 -t 옵션을 줄 수 있다.

(또는 -Wl,-t 를 gcc command link에서 사용할 수 있다)

해당 옵션은 -lfoo 명령과 같이 사용자가 명시한 라이브러리를 포함한 링킹에 사용되는 모든 라이브러리 파일의 전체 경로를 출력해 준다.

정적 링킹을 할때에는, 정적 라이브러리 안에 어떤 오브젝트 파일들이 사용되었는지 알려준다.

만약 당신이 프로그램을 수행중에 에러를 만나게 된다면, 당신은 LD_DEBUG 환경 변수를 이용할 수 있다.

만약 당신이 프로그램을 돌리기 전에 이 변수를 설정해 놓았다면, 당신은 유용한 값의 목록을 얻을 수 있을 것이다.

우리의 경우 가장 중요한 변수를 파일들이다.

세번째로, 만약 당신이 제대로된 라이브러리를 링크한건처럼 보인다면, 3가지 가능성이 있다.

첫번째,, 아마도 라이브러리가 우리가 원하는 심볼을 가지고 있지 않다.

이것은 당신의 컴파일 하는 동안 잘못된 해더를 사용하면 발생할 수 있다.

그리고 이것은 gcc에게 -save-temps라는 옵션을 줌으로써 디버깅할 수 있다. 이 옵션은 .ii 파일을 생성하고 그것을 확인하자.

두번째,, 심볼은 확실히 라이브러리안에 있는 거처럼 보이지만, 다른 calling convention(윈도우에서)를 사용하거나,

wchat_t 모드이거나(또한 윈도우에서) 또는 다른 종류의 파라미터를 사용하거나, 또는 다른 namespace를 사용하는 경우이다.

이 경우, 당신은 라이브러리의 요구사항에 맞도록 어플리케이션의 컴파일 옵션을 바꾸어 줌으로서 해결 할 수 있다.

마지막으로, 라이브러리에 버그가 있어서 심볼이 잘못된 경우이다. 이경우 해당 개발자에게 연락을 하자.

위의 3가지 경우들을 구분짓기 위해서는, 당신은 라이브러리의 심볼 목록을 일일이 검사해야 할 필요가 있다.

gcc를 쓴다면 ‘nm’커멘드가 정적 라이브러리를 살펴보는데 도움이 되고, 공유 라이브러리를 분석하는데는 ‘readelf’가 도움이 된다. (Unix 전용)

아직 윈도우에서는 멋진 방법을 찾지 못했다. 제안은 언제나 환영

이것들은 일반적인 경우이다. 아래에는 특정 경우에 일반적인 문제점들을 나열했다.

이 리스트는 완벽하지 않다. 따라서 당신이 다른 종료의 사례를 알고 있으면 알려줘라.

Static linking.

정정 링크의 경우에는,커멘드 라인의 라이브러리 순서가 문제를 발생한다.

따라서 만약 당신 오브젝트 파일의 심볼을 링킹하려고 하지 않는다면,

당신은 라이브러리의 순서를 재정의하거나 —start-group 옵션을 사용해야 할지도 모른다.

ld documentation을 보면 더 자세한 내용이 나와있다. 최근에는 —start-group 옵션의 성능 비용은 문제가 되지 않는다.

References to vtable.

GNU C++ 컴파일러는 때때로 unresolved reference to ‘vtable for SomeClass’ 문제를 출력한다.

이것은 SomeClass의 메소드가 정의되어 있지 않다는 이야기이다. GCC FAQ를 살펴봐라.

Windows DLLs.

윈도우즈에서, 만약 어플리케이션이 DLL안에 있는 함수를 사용하기를 원한다면,

DLL과 application둘다 declspec(dllexport) 와 declspec(dllimport)를 사용해야 한다.

만약 하지 않는다면, 링커를 에러는 발생한다.

mingw 에서, 일반적인 에러는 undefined reference to `imp__WHATEVER’ 이다.

이것은 라이브러리는 정적인데, 어플리케이션이 원하는 것은 공유 라이브러리 하는 것이다.

Windows import libraries.

윈도우에서 DLL파일을 직접적으로 link하는 것은 불가능하다.

대신에, 중요한 라이브러리는 일반적으로 링커에게 /IMPLIB 옵션을 전달해 줌으로써 만들고 사용할 수 있다.

만약 링커가 아무런 에러를 발생하지 않았음에도, 라이브러리를 참조하지 않고 있다면,

그것은 DLL파일 안에 exported된 어떠한 함수도 없다는 확실한 신호이다.

코드를 다시 한번 확인해보고 __declspec(dllexport)를 추가해 준다.

64-bit compilation.

64비트 어플리케이션을 빌드 할 때, “relocation RX8664_32″에 대해 말하고 있는 어떤 에러를 만날수 있다.

이건 -fPIC 옵션에 대한것임을 암시한다.

64비트 어플레이케션은 오직 -fPIC 옵션으로 컴파일된 코드만을 참조할 수 있는 이슈가 있다.

만약 어떤 정적 라이브러리를 링크하는 것은 대비하여(?–__?), 이들 라이브러리들은 -fPIC 옵션으로 컴파일 되어야 한다.

Cache 와 Self Modifying Code Problem

원본 문서 : http://blogs.arm.com/software-enablement/141-caches-and-self-modifying-code/

이상적으로, 캐시는 프로세서와 메모리 사이에 끼여서 속도를 더 빠르게 만들어주는 마법을 부리는 로직이다.

성능에 민감한 코드를 작성할 때에는 캐시의 특성과 기능을 고려한다면 좀 더 좋다.

일반적으로 그렇게 마음속에 일반적인 캐시의 행동을 고려하면서 코드를 작정하는 것은 바람직하다.

하지만 여기에 당신이 원하는 결과를 얻기 위해서 캐시의 행동을 반드시 고려해야만 하는 몇가지 케이스를 보여주겠다.

자체 수정 코드(self modifying code)는 가장 좋은 예제 이다.

ARM 아키텍쳐는 데이터와 인스트럭션이 분리되서 액세스하는 캐시를 가지고 있다.

그것을 각각 D-Cache, I-Cache 라고 부른다. 이러한 이유로 수정된 하버드 아키텍처라고 불린다.

이런 설계에는 몇가지 장점이 있다.

CPU가 두 개의 인터페이스를 가짐으로 써 동시에 명령어와 데이터를 로드할 수 있다고 예전에 이야기 한적이 있다

이런 하버드 스타일의 아키텍처는 메모리 인터페이스을 사용하는 성능은 유용하지만,

하버드 아키텍쳐이기 때문에 발생하는 문제점이 있다.

순수한 하버드 아키텍쳐의 일반적인 결점은 명령어 메모리와 동일한 주소에 있는 데이터 메모리로 직접 접근할 수 가 없다는 점이다.

(역주) 순수한 하버드 아키텍처는 Intruction Memory 와 Data Memory가 완벽하게 분리되어야 한다.

따라서 Intruntion 주소와 Data 주소가 따로 존재하며, 서로 접근이 불가능하다.

이런 제약 조건은 ARM아키텍처에는 해당하지 않는다.

ARM에서, 당신은 명령어를 메모리에 새롭게 작성할 수 있다.

하지만 그렇게 되면 D-Cache와 I-Cache는 일관성이 없어진다.

새롭게 작성된 명령어는 I-Cache에 존재하는 기존 코드에게 가려져있게 된다.

이것은 프로세서가 예전 명령어를 수행하게 만들것이다.

문제점

가상으로 자체 수정 코드에 대해 살펴보자.

몇몇 JIT 컴파일된 코드는 런타임시에 함수 주소를 레지스터로 로드한다음 그곳으로 점프 한다.

JIT 컴파일러는 특정 함수를 새로운 주소로 점프하게 하려고 하고, 원래 점프하려고 했던 포인터 주소값을 수정하게 된다.

이렇게 런타임시에 목적 함수가 다른 번지를 호출하게 하는 작업은 JIT 컴파일러에겐 일반적인 작업이다.

왜냐하면 컴파일시에 목적지 번지를 알 수 없는 경우가 있기 때문이다.

상당히 단순화 시킨 그림으로 프로세서가 새로운 코드를 작성하기 전에 상태를 보자면 아래와 같다.

myWPEdit Image

이 경우에 I-Cache는 기존 코드를 이미 로드 했다.

만약 코드가 아직 실행되지 않았다면, 해당 코드가 I-Cache에 없을것 같지만, 아직 실행되지 않은 코드들도 있을 수 있다.

우리는 I-Cache가 기존 코드를 담고 있다고 가정한다.

프로세서는 오직 I-Cache에 들어있는 코드만을 실행시킬수 있으며, D-Cache에 있는 데이터만을 읽을 수 있다.

일반적으로 Cache를 뛰어 넘어서 메모리에 직접적으로 접근하는 것을 불가능하다.

D-cache에 있는 명령어를 바로 실행할 수 없고, I-Cache에 있는 데이터를 수정가능하게 읽거나 쓸 수 없다는 것은 중요한 의미가 있다.

이 말은 우리가 작성한 새로운 코드를 I-Cache(또는 주 메모리)에 직접적으로 써 넣을 수 없다는 말이다.

myWPEdit Image

만약 당신이 작성한 코드를 그냥 실행하려고 시도한다면, 프로세서는 단순하게 과거 코드를 수행하려고 할 것이다.

왜냐하면 I-Cache 안에는 여전히 과거 코드가 들어있고, 그게 새로운 코드와 차이가 있다는 것을 모르기 때문이다.

JIT 컴파일러 같은 자체 수정 코드를 활용하는 응용 프로그램들에게는 꽤나 귀찮은 일이다.

해결책

우리는 D-cache의 새로운 코드를 I-Cache에 넣어줄 필요가 있다.

데이터는 반드시 주 메모리로 옮겨진 다음 다시 I-Cache로 읽어들여야만 한다.

미래에 어느 시점에, 프로세서는 D-cache에 있는 새로운 코드를 메모리로 옮기게 될 것이다.

그리고 그 코드는 주 메모리에서 I-Cache로 다시 읽어들일 필요가 있다.

하지만 보통의 경우 이런 작업들은 일반적인 작업이 아니므로, 우리는 강제적으로 수행하게 만들어야 한다.

프로세서 아키텍처마다 캐시는 다를 수 있다. 따라서 여기서는 중요한 사항만 다루도록 하겠다.

현재 우리는, D-Cache에 새로운 코드를 넣었고, 이것은 주 메모리의 내용과 동일하지 않다.

이것을 Dirty data라고 한다. 이것을 주 메모리로 내보내기 위해서 Cache Clean을 해준다.

그리고 작업이 완료되기를 기다린다.

작업이 끝나면 아래와 같은 상태가 된다.

myWPEdit Image

이것을 하기 위해서 우리는 I-Cache를 invalidate 시켜주어야 한다. 그렇게 되면 아래와 같이 된다.

myWPEdit Image

만약 주 메모리에 있는 내용의 코드를 수행하려고 한다면, intruntion fetch는 I-Cache miss가 발생할 것이고,

프로세서는 주 메모리에서 해당 내용을 얻으려고 할것이다.

결과적으로 새로 수정한 코드가 우리의 의도대로 수행되게 된다.

하지만 이게 이야기의 전부는 아니다.

이것 말고도 고려해야할 몇가지 사항이 더 존재한다.

만약 프로세서에 분기 예측기(branch prediction)가 존재한다면, branch target buffer를 clear 해줄 필요가 있다.

일반적으로, 프로세서는 write buffer안에다 주 메모리로 쓰려고 하는 정보를 큐잉하고 있는다.

따라서 우리는 D-Cache를 clean할때 write buffer를 drain 해주어야 한다.

물론, 실제론 이러한 작업들은 당신이 사용하는 프로세서에 매우 specific한 것을 이기 때문에,

당신은 라이브러리 함수들을 이용함으로서 이런 작업을 할 수 있다.

당신이 그저 자제 수정 코드를 작성하고 싶은거라면, 각각의 특정한 프로세서들의 가타 부타한 세세한 내용까지 이해할 필요는 없다.

하지만 라이브러리의 함수들이 어떻게 동작하고 왜 그것이 필요한지 알고 있는 것은 중요하다.

끝으로, 우리는 pli 명령어를 이용해서 프로세서에게 새로운 코드를 I-Cache로 preload할 필요가 있다고 힌트를 주는것을 고려해 볼수 있다.

이 인스트럭션은 실제로 우리가 그 코드를 수행하려고 할때, 메모리로부터 읽어오기위해 stall되지 않게 해주므로,

괜찬은 성능 증가를 가져다 준다.

물론, 힌트를 준다고 해도 전혀 효과가 없을 수도 있다. 하지만 몇몇 구현에서는 이득을 줄 수 있다.

코드

위 내용을 이해했다고 해도, 실제 코드에서는 어떻게 관련이 있는걸까?

늘 그렇듯이 CP15와 관련된 명령어들은 non-privileged 모드에서는 수행이 불가능 하다 (대부분의 응용프로그램이 도는 모드)

운영체제들이 당신을 대신해서 해당 오퍼레이션을 수행해 준다는 의미이다.

다행스럽게도, 대부분의 시스템에서 non-previleged모드에서 캐시를 clean 또는 invalidate 할 수 있는 메커니즘을 제공하고 있다.

Linux (GCC)

In GCC on Linux, you should use the _clearcache function:

CODE

void _clearcache(char beg, char end);

Of course, there is little documentation for this important function, and you have to root around a fair bit to find out what it actually does. Essentially, _clearcache does the following (using a system call):

Clean the specified data cache range. Invalidate the specified instruction cache range. 

The start address (char beg) is inclusive, whilst the end address (char end) is exclusive.

The function will also flush the write buffer and perform any other necessary processor-specific fiddling about that you, as the caller, do not want to worry about. If you really want to know exactly what it does, you will need to look in the Linux kernel, in arch/arm/mm/cache-v7.S (or the equivalent file for whichever architecture you are using).

For an example that you can play with, here is one I made earlier.

Others

Operating System — Relevant Library Function

Linux (GCC) — _clearcache

Google Android — cacheflush

Windows CE — FlushInstructionCache