며칠 전에 zelon님과 함께 코드리뷰를 하다가 vector의 capacity에 대한 이야기가 나왔는데, 재미있는 일이 있었다. 지난 수년간 나는 vector의 capacity를 줄이기 위해서 clear() 함수 대신 resize(0)를 사용하고 있었는데, 내가 잘 못 알고 있었던 것.

나는 clear()는 데이터만 삭제할 뿐 내부 버퍼로 사용하는 메모리는 유지되는 반면 resize(0)는 버퍼까지 모두 정리하는 것이라고 말했는데, zelon님은 내가 반대로 알고 있다고 말했다. 그래서 누가 맞는지를 검색해보자고 찾아봤더니… 둘 다 틀렸음 ㅋㅋㅋ 결론적으로는 두 함수 모두 버퍼를 줄이지 않는다.

ESTL의 챕터 17에서 이에 대한 설명을 찾아볼 수 있다. 그리고 capacity를 사이즈에 맞게 줄일 수 있는 트릭을 제시하고 있는데,

vector<Contestant>(contestants).swap(contestants);

그리 쉽게 눈에 읽히지는 않는다.

헌데, C++11에서 capacity를 줄일 수 있는 새로운 인터페이스 shrink_to_fit() 이 추가됐다. vs2010에서는 ESTL과 동일하게 임시객체를 써서 구현되었다고 하는데, vs2013은 구현도 약간 다르다.

void shrink_to_fit()
{	// reduce capacity
	if (_Has_unused_capacity())
	{	// worth shrinking, do it
		if (empty())
			_Tidy();
		else
			_Reallocate(size());
	}
}

늘어난 버퍼를 줄이고 싶다면 resize()로 먼저 데이터 크기를 줄인 후, (혹은 clear()로 데이터를 비운 후) shrink_to_fit()을 호출해준다.

int _tmain(int argc, _TCHAR* argv[])
{
	std::vector<int> vecData;

	const int max_size = 10000;
	vecData.reserve(10000);

	for (int i = 0; i < max_size; i++)
	{
		vecData.push_back(i);
	}

	vecData.resize(200);
	vecData.shrink_to_fit();

	return 0;
}

'프로그래밍 팁 > C++' 카테고리의 다른 글

vector::shrink_to_fit  (0) 2013.12.24
Reconstructing parameters from x64 crash dumps  (0) 2013.07.17
x64 Stack Frame layout  (0) 2013.07.04
x64 Calling Convention  (0) 2013.07.04
Faux variadics  (0) 2013.06.21
type traits  (0) 2012.02.14
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

Timing Wheel

2013. 12. 18. 17:25 from 프로그래밍 팁/기타

a4438aed0a7eea5a7840df89fe4cfdb6.png

Timing Wheel은 타이머를 구현할 때 유용하게 활용할 수 있는 자료구조다. 블로그에 한 번 정리해 둬야지 하고 미루기만 하다 이제야 끄적임. 정리라고 해봐야, 나중에 다시 검색할 때 키워드를 까먹지 않도록 자료구조 이름이라도 남겨두자는 것 뿐이다. NHN(이제는 Naver가 되어버린)의 hello world 블로그에 한글로 잘 정리된 포스팅이 있다. Timeing Wheel Data Structure를 구글링해도 자료는 많이 있음.

예전에 내가 직접 만들었던 서버 엔진에는 구조가 심플했고 독립적으로 정리된 타이머 모듈은 없었다. 이후에 간단한 타이머를 직접 만들어본 적은 있으나 그것도 boost::timer를 이용해서 대충 뚝딱 만들어본 단순한 구조였는데, 대량의 작업을 처리하기 위한 효율적인 타이머를 구현하려면 타이밍 휠을 이용해 만드는 것이 좋겠다.

Hello World 블로그의 글에 잘 설명되어 있지만 간략히 재정리 하자면, 배열을 이용해 일반적으로 만들면 O(n) 으로 처리해야 할 일들을 타이밍 휠을 써서 O(1)로 처리할 수 있게 된다. 타이머로 처리해야 할 task가 많다면 보다 효율적인 처리가 가능하다. 효율을 얻는 대신 감수해야 하는 단점도 있다. 메모리 사용량의 증가라든지, 고정된 단위시간 이하의 정밀한 시간 컨트롤이 어렵다든지, 미리 잡아놓은 circular-array 크기 * 단위시간 이상의 긴 시간에는 타이머를 걸 수 없다든지 하는 것들.

이 중 긴 시간을 쓸 수 없는 문제는 여분의 대기버퍼를 하나 더 마련하여 해결할 수 있다. 타이밍 휠에 바로 들어갈 수 없는 긴 시간의 task는 대기 버퍼에서 잠시 대기시키다 집어넣으면 됨. 나머지 단점 들이야 필요한 케이스에 맞춰 적절히 조절해주면 큰 문제랄 것도 없을 것 같다. 적어도 내가 겪어본 게임 서버 개발 시에는.

Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

발번역 입니다. 원본 : http://analyze-v.com/?p=482

Note : 만약 크래시 발생 시점의 풀 메모리 덤프를 가지고 있고, 스택 프레임 등 디버깅 데이터를 적당히 유지하도록 빌드한 상태였다면 WinDbg의 dv 명령을 먼저 확인할 것. 덤프에 정보가 풍성하게 유지된 경우는 WinDbg가 웬만하면 알아서 지역변수들을 모두 분석해 보기 쉽게 출력해준다. 아래는 덤프분석 연습 삼아 번역해둔 것.

영문은 의역이 많고, 퇴고시 원본 대조 없이 내가 이해하기 쉬운 말투로 재조정 했으니 말 그대로 발번역.

 

(번역 시작)

우리는 x64 환경의 크래시와 관련된 이야기들을 이어나가고 있고, 실제 시스템상에서 발생한 크래시 덤프를 어느 정도 분석할 수 있는 수준에 도달했다. 이번 글에서는 x64 크래시에서 함수에 전달된 실제 인자값을 찾아내는 데모를 설명해 보고자 한다. 앞에서 언급했던 바와 같이, x64 컴파일러가 인자를 전달할 때 값이 유지되지 않는(volatile) 레지스터들을 사용하기 때문에 조금 까다로운 부분이 있다. 그래서 다른 레지스터들에 저장된 값이나 스택에 적힌 값들을 참고해야만 한다.

실제로 문제가 발생한 서브루틴으로 전달된 인자값을 읽어낼 덤프를 분석해 보도록 하자. 아래에는 크래시가 발생한 모듈에서 문제가 되는 명령어를 보여주고 있다.

faultinginst

문제가 발생한 명령어는 두 번째 화살표가 가리키는 위치이고, 첫 번째 화살표는 RDX 레지스터에서 RBX레지스터로 잘못된 포인터 값을 로딩하는 부분을 가리키고 있다. 내용을 분석해보면 RBX 레지스터에 있는 잘못된 포인터 값을 읽다가(dereference) 문제가 발생했음을 알 수 있다. 우리가 x64 호출규약에 대해서 정리했던 것처럼, RDX는 함수로 전달되는 두 번째 인자값이니까 아마 이 함수는 두 번째 인자값으로 잘못된 포인터의 주소를 전달받았을 것이라고 짐작할 수 있다.

구글에서 함수 이름을 검색해보면 RtlDeleteNoSplay 함수의 원형을 확인할 수 있다.

VOID RtlDeleteNoSplay(
     
IN PRTL_SPLAY_LINKS Links,
      IN OUT PRTL_SPLAY_LINKS *Root
);

이제 보다 많은 정보들을 분석하기 위해 일단 이 함수로 전달된 두 개의 인자값들을 읽어내 보자. 이 작업은 아주 간단히 끝날 수도 있고 무척이나 짜증스러운 과정이 될 수도 있는데, 이 함수의 첫 번째 인자값을 알아내는 과정을 세 가지 다른 난이도의 방법으로 설명하려고 한다. 이 방법들은 모두 특정 상황에만 해당되는 극히 한정적인 해법일 수 있긴 하지만, 여기에서 설명하는 기법들이 실제로 쓸모가 있기를 기대해 본다.

 

 

EASY – 쉬운 방법

이 경우에는 아주 운 좋게도 첫 번째 인자를 쉽게 확인할 방법이 있다. 크래시가 발생하기 전에 RCX의 값이 R11레지스터에도 로딩 되었기 때문이다.

rcxr11

R11 레지스터는 값이 보존된 레지스터?이기 때문에 크래시 시점의 프레임에서 R11레지스터의 값이 유지되고 있다고 말할 수 있고, R11의 값을 확인함으로써 첫 번째 인자값을 알아낼 수 있다.
(역주: 원본 글에서는 R11을 non-volatile이라고 적었는데, 원본글 아래에 이에 관련된 댓글이 달려있다. R11은 volatile 레지스터이므로, 무작정 R11의 값을 신임할 수는 없다. 하지만 위의 경우 R11값에 대한 추가적인 조작이 없으므로, 이 경우에 한해서는 값이 유지되는 (non-volatile)레지스터라고 여겨도 무방할 뿐이다.)

r11param

하지만, 크래시가 발생하기 전에 RCX의 값을 R11에 저장하지 않았다면 어떻게 할 것인가? 그런 경우엔 이전의 프레임으로 좀 더 깊이 파고들어가 분석을 계속 해야 한다. 나는 가능하다면 다음에 설명할 방법을 종종 연습 삼아 해보는데, 인자값을 이미 파악 했다 하더라도 좀 더 난이도 있는 다음의 방법으로 알아낸 값과 비교해보면 값을 제대로 된 값을 읽었는지 확인해 볼 수 있기 때문이다.

 

MEDIUM – 조금 더 어렵게.

그럼 R11레지스터를 사용하지 않고 어떻게 첫 번째 인자값을 알아낼 수 있을까? 콜스택에서 바로 이전에 실행된 함수(즉 caller;호출자)는, 바로 다음 프레임에서 사용할 수 있도록 RCX레지스터에 값을 올려 두어야만 한다. 이점을 이용해서, 스택 상에서 앞 단계에 위치한 함수부터 분석을 시작해보자. 지금 예로 든 덤프의 경우에는 TreeUnlinkNoBalance 함수가 될 것이다.

treeunlink1

RtlDeleteNoSplay 함수를 호출하기 전에 RCX의 값이 RBX에 저장된다는 사실을 확인할 수 있다. 그럼 이제 RtlDeleteNoSplay 함수 시작점에서 RCX의 값과 RBX의 값이 같다는 사실을 알 았고, RtlDeleteNoSplay 함수 안쪽에서 인자값 확인에 참고할 수 있는 레지스터가 한 가지 더 늘어난 셈이다. 그럼 RtlDeleteNoSplay 함수를 다시 한 번 살펴보자.

rtldeleteprolog

RtlDeleteNoSplay 의 첫 번째 명령어를 주목해보자 – RBX를 무려 스택에 집어넣고 있다! 이 루틴으로 전달된 첫 번째 인자는 실행 스택에 보존되어 있다. 크래시가 발생한 프레임(trap frame)에서 RSP값이 곧 크래시가 발생한 시점의 스택 포인터 값이다. 스택에 저장된 값을 다시 읽어내려면 값을 스택에 저장한 시점 이후 처리된 스택의 조작을 역으로 다시 풀어줄(unwinding) 필요가 있다. 이 예제의 경우에라면 스택 포인터에 0x20만 더해주면 된다. push 연산 수행 후 RSP에서 0x20을 빼 주었던 명령만을 되돌리면 되기 때문이다.

r11match

 

HARD – 어려운 방법

그런데 콜스택이 인자값을 알고 싶어하는 함수를 지나 위쪽으로 더 진행되어 버렸다면 어떻게 해야 할까? 함수가 호출된 시점의 인자를 알아내는 것이 가능할까? 이런 경우를 한 번 알아보자.

이번 데모에서는 이전과 다른 함수의 다른 인자값을 알아내 보자. 콜스택이 추가 진행한 상황에 대한 좀 더 난이도 있는 분석방법을 이야기 해야 하기 때문이다.

아래의 스샷은 PurgeStreamNameCache 함수의 어셈블을 보여준다. 이 함수는 진행 중 DeleteNameCacheNodes를 호출한다.

deletenamecachecall

이 함수는 RSI의 값을 RCX에 로딩시켜서 함수 호출을 준비하고 있음을 주목하라. 이 동작으로 인해서 DeleteNameCacheNodes함수의 내부에서 첫 번째 인자값을 구할 때 RCX뿐만 아니라 RSI의 값도 함께 조사할 수 있게 됐다. 이 두 레지스터 중 아무 값이라도 다른 레지스터에 넣는다든지, 스택에 저장한다든지 하는 명령이 있는지 알아보면 될 것이다.

rsihomed

DeleteNameCacheNodes 함수가 바로 시작하자마자 RSI의 값을 R9의 홈 공간에 해당하는 스택 위치에 저장하는 것을 볼 수 있다. 이 명령어는 엄청난 희소식이다. 이제 알고 싶어하는 인자값은 스택에 저장된 올바른 위치를 찾아 읽어내기만 하면 구할 수 있다. 하지만, 이 때 사용해야 할 스택 포인터의 값을 어떻게 알아낼 수 있을까?

이 부분은 x64에서 인자를 재구성하는데 아주 중요한 트릭이다. 사실 아주 간단하다. 우리가 조사하고 있는 프레임의 Child-SP값을 확인하기 위해 k 명령어를 실행해 주기만 하면 된다. (역주: WinDbg의 k 명령은 콜스택을 덤프해 보여준다. 명령어 설명은 이 곳의 15. Call stack 부분 참조.)

childsp

이 값은 서브루틴이 만들어지는 시점에, 서브루틴의 호출이 끝나고 되돌아올 위치값을 저장한 스택 포인터이다. (역주: 이 부분을 이해하는 것이 중요하다. 원문은 That is the value the stack pointer will be upon return from the current call that subroutine is making.) 이 부분이 많이 헷갈릴 텐데, 이 예제의 경우 DeleteNameCacheNodesTreeUnlinkMulti 함수를 호출하는 시점을 주목해야 한다. 이 때의 Child-SP는 함수 호출이 끝나고 되돌아올 곳인 RSP를 가리킨다. 우리는 이 값을 이용해 DeleteNameCacheNodes 함수의 실행시점 안에서 스택에 담겨 있는 다른 정보들을 쉽게 찾아낼 수 있다. TreeUnlinkMulti 진입점의 prolog에서 계산한 RSP값에서, 우리가 알아내고자 하는 값에 도달할 때까지 스택의 변경사항을 역으로 풀어내기만 하면(unwinding)된다.

offsets

(Note: 위의 이미지는 1/15에 익명의 유저가 올린 댓글에 맞추어 수정되었다. 본문 아래의 댓글 란을 참고할 것)

이제 값이 저장된 위치를 알았으니, dq 명령어를 이용해 해당 메모리 위치의 값을 조회해 볼 수 있다.

paramback

 

마지막 팁으로, kv 명령어가 스택 상의 네 개의 홈 공간을 출력으로 보여준다는 사실을 이용하면 이 값을 더 빨리 알아낼 수도 있다. RSI의 값은 R9 홈 공간에 저장되었으므로 (RSP+0x20), kv 명령어를 실행하고 여기에 출력된 DeleteNameCacheNodes 항목의 네 번째 값을 확인하면 된다.

kveasy

(번역 끝)

 

이로서 http://analyze-v.com/에 있는 글 번역의 시리즈는 일단락 되었다. 원문이 영어인지라, 지금 읽었던 글을 정리해두지 않으면 금방 까먹을 것 같아서.. 미래의 날 위해 날림이지만 번역해 올려둠.

위의 글들도 좋지만 사실 x64 명령어 call의 동작 방식을 이해하는 것이 콜스택을 분석하는데 큰 도움이 되는데, 이 부분은 따로 다시 한 번 더 정리해야겠다.

'프로그래밍 팁 > C++' 카테고리의 다른 글

vector::shrink_to_fit  (0) 2013.12.24
Reconstructing parameters from x64 crash dumps  (0) 2013.07.17
x64 Stack Frame layout  (0) 2013.07.04
x64 Calling Convention  (0) 2013.07.04
Faux variadics  (0) 2013.06.21
type traits  (0) 2012.02.14
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

발번역 합니다. 원본 : http://analyze-v.com/?p=468

이전 글(의 발번역)을 보려면 http://devnote.tistory.com/249

 

이 글을 적고 있는 시점은 뉴잉글랜드 패트리어츠 경기의 하프타임이다. x64 컴파일러가 스택을 다루는 방법에 대해 이야기하기 딱 좋은 시간이다.

윈도우즈의 x64 컴파일러는 함수의 prolog 영역을 처리하는 동안 스택을 조작하는 모든 동작을 수행한다. (그렇지는 않다는 댓글도 달려 있음. 아래쪽 Update 단에 적혀있다.) 이 과정에는 값을 유지해야할(non-volatile) 레지스터들의 값을 스택에 저장하고, 지역 변수들을 위한 공간을 할당하고, 호출한 함수쪽으로 전달해야 할 인자들을 위한 공간도 할당하게 된다. (처음 네 개의 인자들은 레지스터를 통해 전달되고, 나머지 인자들이 스택을 통해 전달된다는 사실을 기억할 것.)

이러한 동작 중에는 x64 컴파일러가 스택을 사용하는, 또하나의 흥미로운 특성을 포함한다. 모든 서브루틴들은 호출자쪽이 서브루틴을 위한 용도의 ‘홈’ 공간을 마련해주어 이를 사용할 수 있다는 점이다. 이 공간은 레지스터들 (RCX, RDX, R8, R9)를 통해서 전달되는 처음 네 개의 인자들을 저장하기에도 충분한 공간이다. 그리고 이 공간은 다섯 번째 인자가 저장되는 공간의 앞부분에 마련된다.

아래의 그림을 보면 보다 분명하게 이해될 것이다. 서브루틴으로 진입할 때, RSP는 루틴이 되돌아가야 할 번지를 담고 있는 곳의 위치를 가리킨다. RSP+8h는 RCX 홈 공간을, RSP+10h는 RDX홈 공간을 가리킨다.

x64-frame1

 

하지만 여기서 또다시 중요한 점은, 스택에 마련되는 이 공간은 단지 RCX, RDX, R8, R9 레지스터 값을 유지하기 위한 용도로만 사용되는 것은 아니라는 점이다. 어떤 경우는 함수의 prolog 코드가 이 공간에 레지스터행 인자들을 저장하기도 하지만, 또 다른 경우에는 여기에 subset을 저장하기도 한다. 또 어떨 때는 전혀 상관없는 다른 레지스터 값들을 이곳에 저장할 수도 있고, 이 공간을 무시한 채 아무 것도 저장하지 않을 수도 있다. 사실상 이 공간은 서브루틴의 입장에서 적합한 용도로 사용할 수 있는 여분의 공간(scratch space)이라고 보아도 무방하다.

이제 우리는 임의의 x64 크래시를 분석할 때 이 여분의 공간을 좀 더 광범위하게 활용할 수 있는 방법에 대해 이야기 해보려 한다. 이 공간은 함수로 전달된 인자들과 데이터 유지용(non-volatile) 레지스터들의 값이 덤프로 붙잡은 현재 프레임에서는 보이지 않을 때, 이를 알아내기 위한 최고의 장소로 쓰일 수 있다. 다음 포스팅에선 이 공간을 이용해 함수로 전달된 파라미터 값을 손쉽게 재구성하는 방법에 대해 이야기 해 보자.

(막 경기 후반전이 시작되는 시점에 타이핑을 끝냈다!)

업데이트

익명의 댓글에서 흥미로운 사실이 달렸다.

중요하게 짚고 넘어가야 할 부분이 있습니다. 스택의 조작은 prolog 코드가 수행되는 동안에 이루어지는 것은 맞지만, RSP 레지스터는 함수의 본체가 실행되는 동안에 변경되기도 합니다. 예를 들면, 함수 _alloca()를 썼다든가 할 때에요. (누구 다른 케이스를 알고 계신 분?)

이러한 동작은 커널모드의 프로그래밍 동작에서는 흔치 않은 일이라, 위 글에 답글을 적지는 못했다. 이런 케이스에 대해 알고 있는 사람은 우리에게도 알려주었으면 한다.

(번역 끝)

본문은 여기까지고, 달린 댓글 중에 참고할 만한 게 하나 더 있다면

FYI, the stack grows towards low addresses. So, the picture is kinda upside down if you`re trying to see a ‘stack’

참고로 덧붙이자면 스택은 주소 번지가 낮은 쪽으로 자라납니다. 그러니 위 이미지에서 스택을 나타내려는 것이라면 거꾸로 표현되어야겠지요.

I tend to like to show these diagrams with the highest address on the top instead of the bottom. As you mentioned, the stacks grown down so starting at the top and working down makes sense to me. I can see the argument for going the other way as well and if you do a Google image search there’s a pretty decent mix of both.

(글쓴이의 답변글) 나는 의도적으로 이 다이어그램에서 높은 주소번지를 아래쪽이 아닌 위쪽으로 보이도록 하였습니다. 님께서 언급하신 것 같이, 스택은 아래쪽으로 자라나기 때문에 위쪽에서 시작해서 아래쪽으로 쌓여가도록 표현하는 것이 제가 생각하기에도 상식적입니다. 하지만 스택 방향을 꼭 그렇게 맞춰 표현하지 않으면 어떻느냐는 의견도 많이 있고요, 구글에서 이미지 검색을 해보면 두 가지 방식의 표현 모두가 적절히 사용되고 있는 걸 확인할 수 있습니다.

(답변 번역도 끝)

 

Note : x64에서 스택은 거꾸로 자란다(down-growing). 나같은 초보들은 이미지만 보고 넘어가다가는 오해할 수도 있겠군. 첨부된 이미지를 보면, low / high 주소공간 위치를 거꾸로 그린 것은 둘 째치고, 스택이 분명 거꾸로 자란다고 되어있으면서, 설명하는 공간들은 RSP를 기준으로 높은 주소번지로 자라 나가는데, 이것은 서브루틴이 사용할 스택의 공간 방향이 아니다. 위의 그림 기준에서라면 함수의 로컬변수 등은 아래쪽에 넣게 된다.

image

실제로 테스트 코드를 만들어 확인해보면 바로 알 수 있는데, 위에 빨간 동그라미를 보면 첫 번째 인자로 받은 int 변수 b의 값을 [rsp+8]에 넣고 있다. 하지만 아래 push rdi 명령을 보면 rsp보다 낮은 주소번지로 값이 들어간다.

일단 첨언은 여기까지만. 한동안은 x64 asm 글을 계속 올릴 듯 한데 좀 더 자세히 정리할 기회가 있겠지.

'프로그래밍 팁 > C++' 카테고리의 다른 글

vector::shrink_to_fit  (0) 2013.12.24
Reconstructing parameters from x64 crash dumps  (0) 2013.07.17
x64 Stack Frame layout  (0) 2013.07.04
x64 Calling Convention  (0) 2013.07.04
Faux variadics  (0) 2013.06.21
type traits  (0) 2012.02.14
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

발번역 합니다. 원본은 : http://analyze-v.com/?p=458

x64 덤프를 다루기 위해서는 호출규약(Calling Convention)에 대해 이해할 필요가 있다. 이를 알고 있어야 특정 함수로 전달하는 인자(parameter)들을 확인한다던지 하는 일들이 가능하다.

가장 기본적인 룰은 함수로 전달하는 처음 네 개의 인자들은 레지스터를 이용하며, 나머지 추가 인자들은 스택을 통해 전달된다는 것이다. 보통 사용되는 레지스터들은 아래와 같다.

  • Parameter 1 – RCX
  • Parameter 2 – RDX
  • Parameter 3 – R8
  • Parameter 4 – R9

(주의: 실수형 인자를 사용할 때 같은 특정 케이스에서는 별도의 규칙이 적용된다. 이에 대한 전체적인 세부설명은 이 곳에서 확인할 수 있다.)

예를 들어, 만약 어떤 루틴이 시작점에서 RDX 레지스터에 접근한다면, 이 루틴은 두 번째 인자에 접근하고 있음을 확인할 수 있다. 또한 이를 통해 유추할 수 있는 것은, 루틴을 호출하는 곳에서는 이보다 더 앞선 프레임(frame)에서,  RDX 레지스터에 올바른 두 번째 인자 값을 채워 넣었어야 한다는 점이다. (역자 주 – 함수가 시작하자 마자 인자값에 접근을 하니까, 함수가 시작되기 전에 이미 레지스터에 값이 쓰여져 있어야 하고, 이는 호출자(caller)의 역할이라는 뜻)

rdx

이러한 호출 규약에는 한 가지 중요한 이슈가 있다. 컴파일러가 함수의 처음 네 개의 인자들을 휘발적으로(volatile) 처리한다는 점인데, 다시 말하자면 서브루틴 호출이 진행되는 동안 이 값들이 유지되지 않는다는 말이다. 서브루틴이 처리되는 과정에서 컴파일러는 이 네 개의 레지스터에 아무 연관 없는 다른 값들을 덮어써버릴 수 있다. 이러한 동작 방식은 x64에서 인자들의 값을 다시 복원하기 어렵게 만들어 버린다.

다음의 포스팅에서는 x64 컴파일러가 스택을 활용하는 방법에 대해 이야기할 것이다. 인자로 전달된 값들을 알고 싶을 때, 스택을 이용해서 원하는 정보를 얻을 수 있는 자세한 방법에 대해 논의해 보자.

'프로그래밍 팁 > C++' 카테고리의 다른 글

Reconstructing parameters from x64 crash dumps  (0) 2013.07.17
x64 Stack Frame layout  (0) 2013.07.04
x64 Calling Convention  (0) 2013.07.04
Faux variadics  (0) 2013.06.21
type traits  (0) 2012.02.14
Direct2D 레퍼런스 모음  (0) 2011.06.03
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

Faux variadics

2013. 6. 21. 15:18 from 프로그래밍 팁/C++


원본 출처 : http://msdn.microsoft.com/en-us/library/vstudio/hh567368.aspx

아래는 발번역.

vs2012의 C++은 가변 인자 템플릿을 흉내내는 새로운 구문을 가진다. vs2008 SP1과 vs2010에서는, 매크로 정의를 바꿔가며 하위 헤더파일을 반복적으로 인클루드하는 방식으로 인자 개수가 다른 오버로드를 찍어내는 식이었다. 예를 들어, <memory> 헤더는 하위 헤더인 <xxshared>를 반복적으로 인클루드하며 make_shared<T>(args, args, args)를 찍어냈다. vs2012에서는 이런 하위 헤더파일이 사라졌다. 이제는 가변 인자 템플릿이 매크로를 이용해 정의되고, 주 매크로(master macro)를 이용해 확장된다. 이러한 내부 구현의 변화는 다음과 같은 변화를 만든다.

  • 코드의 유지/보수성이 좋아지고, 사용하기 쉬워졌다. (하위 헤더 파일을 인클루드 하는 것은 제법 가볍지 않은 작업이다) 또한 코드의 가독성도 높아졌다.
  • 디버거에서 코드 안을 확인하기가 더 어려워졌다 – 미안하게 생각한다! (헐…)
  • std::pair의 생성자인 pair(piecewise_construct_t, tuple<Args1…>, tuple<Args2…>) 에 흥미로운 현상이 있는데, 이 생성자는 N2개의 오버로드를 가진다. (10개의 인자를 가지는 tuple를 지원할 경우 총 121개의 오버로드를 가지는데, 인자를 받지 않는 생성자를 포함해 계산하기 때문이다.) 11 * 11 = 121.

다량의 pair-tuple 객체 오버로드 생성과, 위치 변경 오버로드(emplacement overload)까지 더해져서 컴파일 시점에 많은 양의 메모리를 필요로 하게 되었다. 그래서 우리는 인자 개수 제한을 줄였다. vs 2008 SP1과 vs 2010에서는 10개 까지의 인자 개수가 지원되었다 (가변 템플릿 인자가 0개에서 10개까지 제공된다는 의미). 하지만 vs2012에서는 디폴트로 인자 개수 제한을 5개로 낮추었다. 이렇게 해서 컴파일러의 메모리 사용량을 vs2010때와 유사하게 조정해 두었다.

만약 더 많은 수의 인자를 사용해야 한다면 (6개 타입을 갖는 tuple이라든지) 이를 위한 탈출구도 마련되어 있다. 프로젝트 전역으로 _VARIADIC_MAX 매크로를 5~10 사이의 값으로 정의하면 된다. 이렇게 하면 더 많은 메모리를 필요하게 되기 때문에, 컴파일러 옵션에 /Zm을 사용해 pre-compiled header를 위한 메모리 공간을 더 확보해야 할 수도 있다.

(번역 끝.)

 

지금 우리 프로젝트가 _VARIADIC_MAX = 10을 사용하고 있는 상황. 이 때문에 pch 사이즈가 엄청 커져서 컴파일에 file I/O도 엄청 늘었고, 빌드 시간도 많이 느려졌다. 지금도 빌드 걸어놓고 기다리다가 심심해서 발번역이나 한 번 해 봄.

야 이 M$ 잡것들아 가뜩이나 욕먹는 default STL에 이상한 짓좀 하지 말어 ㅜㅠ… 그리고 이런 요상한 것 할 시간 있으면 차라리 variadic template 문법적으로 제대로 추가하는 작업이나 서둘러 해라 ㅜㅠ…

'프로그래밍 팁 > C++' 카테고리의 다른 글

x64 Stack Frame layout  (0) 2013.07.04
x64 Calling Convention  (0) 2013.07.04
Faux variadics  (0) 2013.06.21
type traits  (0) 2012.02.14
Direct2D 레퍼런스 모음  (0) 2011.06.03
[vs2005] __assume 키워드  (0) 2011.05.25
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

LuaSocket

in-house 툴에서 주어진 데이터를 확인하고 결과 내용을 메일로 전송할 일이 생겼는데, 간단한 툴이기 때문에 그냥 100% 루아로만 작성해보고 싶었다. 그래서 lua 자체로 메일 보내는 기능을 찾아봤는데, LuaSocket을 이용하면 쉽게 보낼 수 있더라. 별도로 다운받아서 빌드해 사용할 수도 있지만 Lua For Windows를 설치하면 자동으로 같이 설치된다.

사용법은 정말 별것이 없다. SMTP 소개 페이지에 있는 샘플 코드 복사해다가 실행시키면 그냥 실행됨 –_-)b 다만 메일에 한글을 쓰려고 하니 제대로 동작하지 않는다. – 그것이 이 글을 적는 이유이기도 하다 – utf-8 인코딩으로 텍스트를 보내고 싶다면 아래 코드를 참고하면 된다.

local smtp = require("socket.smtp")
local ltn12 = require("ltn12")
local mime = require("mime")

local function headerencode(src)
    return "=?UTF-8?B?" ..
        ltn12.source.chain(
            ltn12.source.string(src),
            ltn12.filter.chain(
                mime.encode("base64"),
                mime.wrap()
            )
        )() ..
        "?="
end

local mesgt = {
    headers = {
        From = headerencode('あわわわ') .. " ",
        To = headerencode('あわわわ') .. " ",
        Subject = headerencode("日本語の件名"),
        ['Content-Type'] = 'text/plain; charset="UTF-8";',
        ['Content-Transfer-Encoding'] = 'base64',
    },
    body = ltn12.source.chain(
        ltn12.source.string("日本語の本文も送信できます."),
        ltn12.filter.chain(
            mime.encode("base64"),
            mime.wrap()
        )
    )
}

assert(smtp.send{
    from = "",
    rcpt = "",
    source = smtp.message(mesgt),
    server = 'localhost'
})

코드는 여기에서 가져와서 indentation만 수정했음.

lua 자체에서 메일 보내는게 어려우면 python이나 powershell로 단계적으로 넘어가려고 했는데, 이 정도면 충분히 만족 :)

Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

참고 자료들 링크만 일단 정리.

지금 당장은 블로그를 갈아탈 생각은 없지만, 만약 나중에 티스토리를 떠나서 다른 곳으로 이사해야 한다면 1순위로 GitHub를 고려해 볼 듯. 데이터를 로컬에 늘 백업해 둘 수 있다는 것과 오프라인에서 글 쓰기 좋다는 점, 디자인 커스터마이징이 쉽다(?)는 점 등이 매력있게 느껴진다.

일단은 내 Pages 에는 개인 프로필 페이지를 간단하게 만들어 올려두었다. 요즘 jQuery와 Bootstrap을 만지는 게 재미있어서, 이것 저것 만들어보고 있다. 그 중에 정리되는 대로 Pages에 올려볼까 생각 중.

image

Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

twitter bootstrap

2013. 3. 24. 21:01 from 프로그래밍 팁/www

image

이런 물건이 있다는 건 전부터 알고는 있었지만 이번에 써먹어 볼까 싶은 데가 있어서 첫 사용해봄. twitter에서 오픈소스로 공개해둔 웹 스타일링 프레임웍(?) 정도 되는데, 적용하기도 쉽고 디자인도 아주 깔끔하다.

연습 삼아 예전에 만들어 두었던 우리 애기 태어난 날짜 수 계산해주는 페이지에 적용.

image

처음엔 요 모양이었지만 (링크)

image

bootstrap 연결하고 조금 건드려보니 많이 깔끔해짐. (링크)

개인적인 장난감 프로젝트나 팀에서 가볍게 사용할 인하우스 툴의 디자인은 bootstrap 정도면 정말 안성맞춤이다. 디자인 때문에 시간을 더 쓸 필요도 없고, 아무 디자인도 안된 구린 웹페이지 때문에 불면증이 찾아올 이유도 없다.

Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

같은 삽질을 두 번 다시 반복하지 않기 위한 셋팅 로그.

기본적인 설정 방법은 공식 매뉴얼에 잘 나와있다. --install, --remove 등의 서비스 설치 삭제 관련 플래그를 사용하면 된다.

헌데 나는 오늘 설치해보니 아래 오류가 계속 나면서 시간을 꽤나 잡아먹었는데, 서비스로 install을 끝내고 시작하려고 하면

C:\>net start MongoDB
시스템 오류 2이(가) 생겼습니다.
지정된 파일을 찾을 수 없습니다.
C:\>

이게 계속 뜬다. 그래서 이 오류 메세지로 구글링을 좀 해보면 MongoDB 이슈트래커에 관련된 항목이 나오는데, 이 문제는 2.1.0 버전에 해결되고 close된 이슈. 오늘 내가 사용한 버전은 2.2.3 버전이니 해당사항이 없는 이야기다. 그래서 서비스 설정된 레지스트리를 직접 찾아가보기로 하고 RegEdit를 열어 아래 주소로 이동.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\MongoDB

아… mongod.exe의 경로에 한글이 들어가니까 깨지는구나. 서비스로 뭔가 등록해보는게 오랜만이라 깜박했다. (ImagePath 항목에서 실행파일의 full path를 확인할 수 있다.)


여기부터는 실제 셋팅 경로와 config 파일들.

D:\에 DB 폴더를 생성하고 아래와 같이 구성한다.

image

bin 폴더에는 mongodb.org에서 다운받은 최신 버전의 윈도우용 바이너리들을 넣고, mongodb.cfg는 아래와 같이 구성.

   1: dbpath = D:\DB\Data\
   2: logpath = D:\DB\Log\Logs.log
   3: logappend = true
   4: rest = true
   5: directoryperdb = true
   6:  

path 환경변수에 D:\DB\bin 폴더를 넣어준다. 인터프리터 방식 클라이언트 mongo.exe를 바로 사용할 수 있어 편하다.

  • 서버 구동 테스트 :
    D:\DB\bin>mongod --config D:\DB\mongodb.cfg
  • 서비스모드로 mongod 설치:
    D:\DB\bin>mongod --config D:\DB\mongodb.cfg --install
   1:  
   2: D:\DB\bin>mongod --config D:\DB\mongodb.cfg --install
   3: all output going to: D:\DB\Log\Logs.log
   4:  
   5: D:\DB\bin>

로그를 열었을 때 별다른 에러 메세지가 없다면 net start MongoDB로 서비스를 시작한다.

서비스 구동상태를 확인하려면 sc qeury MongoDB

   1:  
   2: D:\DB\bin>sc query MongoDB
   3:  
   4: SERVICE_NAME: MongoDB
   5:         종류               : 10  WIN32_OWN_PROCESS
   6:         상태               : 4  RUNNING
   7:                                 (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)
   8:         WIN32_EXIT_CODE    : 0  (0x0)
   9:         SERVICE_EXIT_CODE  : 0  (0x0)
  10:         검사점             : 0x0
  11:         WAIT_HINT          : 0x0
  12:  
  13: D:\DB\bin>

이제 데이터를 막 집어넣으면 됨 :)

  • cfg에서 bind_ip = 127.0.0.1항목을 사용하면 로컬에서만 접속하도록 띄울 수 있다.
  • 서버 구동 후 http://localhost:28017 을 브라우저로 열면 관리 정보를 확인할 수 있다.
Posted by leafbird 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply Favicon of https://devnote.tistory.com BlogIcon leafbird 2013.04.23 21:00 신고

    오늘 맥북에서도 homebrew로 mongodb를 설치.
    아직 맥이 익숙치 않아 초기 설정한 내용만 간단 정리 :
    1. 기본적으로 설정 파일은 /usr/local/etc/mongod.conf에 위치. 추가하고 싶은 옵션은 이곳에 넣는다.
    2. brew에서 빌드 및 설치가 끝나고 나오는 안내 문구를 따라 데몬으로 설정.
    ==> Caveats
    To have launchd start mongodb at login:
    ln -sfv /usr/local/opt/mongodb/*.plist ~/Library/LaunchAgents
    Then to load mongodb now:
    launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist
    Or, if you don't want/need launchctl, you can just run:
    mongod