참조:
http://kcats.tistory.com/145
http://vosej_v.blog.me/50169180000
http://mlpworld.tistory.com/entry/%ED%9E%99-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A0%A8-API
Memory
max
0
프로그램을 실행하게되면, OS는 프로그램이 실행에 필요로 하는 자원(memory)을 제공한다.
제공된 Memory에는 프로그램의 코드와 함께 프로그램을 실행하면서 생긴 데이터들이 저장되게된다.
이때의 메모리구조는 위의 그림처럼 크게 4가지영역으로 나눌수 있다.
Text section
프로그램의 코드를 포함한다.
Data section
프로그램이 시작할때 생성되고, 종료시 소멸되는 메모리 영역으로 전역 변수(global variables)와 static 변수를 포함
한다. Data section의 경우 초기화된 데이터영역과 초기화 되지않은 데이터영역으로 다시 나눌수 있다.
** 초기화된 데이터영역과 Text section은 프로그램 파일내에 존재한다.
Heap section
실행시간동안 동적으로 할당받는 데이터 변수들을 포함하는 영역으로 new(C++), malloc(C)과 같은 메모리할당 명령
어를 사용하여 데이터를 할당받는다. stack과 data section의 변수들은 종료시 메모리가 자동으로 소멸되나 Heap memory는 프로그래머가 직접 메모리 해제를 해주어야 한다.
Stack section
함수의 매개변수, 반환되는 주소와 지역변수(local variables)등의 임시적인 데이터를 갖는 공간으로 프로그램에서 함
수가 실행되었을때, 변수들이 스택에 쌓였다가 함수의 종료시 메모리할당이 자동으로 해제된다.
** Stack memory의 경우, 함수종료시 소멸되기 때문에 보통 메모리의 마지막번지에 지정된다
<힙>
운영체제는 프로세스를 만들 때 1바이트 크기의 디폴트 힙을 같이 생성한다.
힙은 어디까지나 예약된 메모리일 뿐이므로 힙 자체가 물리적인 메모리를 축내지는 않는다.
즉 최초 힙이 만들어질 때는 1M 바이트 크기만큼 예약만 되어 있을 뿐 확정되어 있지는 않다.
가상 메모리를 직접 할당하는 것에 비해 힙을 사용하는 방법은
작은 메모리 블록을 할당하는데 훨씬 더 유리하다.
가상메모리는 페이지 단위이기 때문이다.(4K 단위로 할당하기 떄문에 메모리 낭비 심하다)
그래서 VirtualAlloc 를 사용하기 보단 malloc, new,사용하거나
디폴트 힙으로부터 메모리를 할당 받을 때는 다음 세 함수를 사용하는데
HANDLE GetProcessHeap(VOID);
LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, DWORD dwBytes);
BOOL HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem);
Win32 의 모든 힙 함수는 대상이 되는 힙의 핸들을 첫 번째 인수로 지정한다.
디폴트 힙을 사용할 때는 GetProcessHeap 함수로 디폴트 힙의 핸들을 먼저 얻을 후 이 핸들을 넘겨 HeapAlloc 으로 할당한다.
HeapAlloc의 세 번째 인수 dwBytes 할당하고자 하는 메모리 양을 지정하여
두 번쨰 dwFlags 는 힙을 할당하는 방법을 지정하는 플래그이다.
다음 세 개의 플래그가 있다.
이 플래기들 중 두 번쨰 HEAP_NO_SERIALIZE 플래그는 다소 설명이 필요하다.
힙에 대한 액세스는 운영체제에 의해서 기본적으로 동기화 되며
두 개 이상의 스레드가 동시에 힙에서 메모리를 할당하더라도 우연히 같은 번지를 할당하지 않도록 되어 있다.
시스템이 이런 처리를 하지 않으면 두 스레드가 같은 번지를 동시에 할당해서 서로 방해할 수 있으므로 이 처리는 꼭 필요하다.
물론 확률 상으로 동시에 같은 번지를 할당하는 일은 거의 없겠지만, 멀티 스레딩 환경에서는 스레드가 동시에 실행되며 스위칭 시점을 예측할 수 없으므로 이런 가능성을 전혀 배제할 수 없다.
그래서 힙에 대한 모든 처리( 할당, 해제 , 조사) 는 시스템에 의해 동기화 된다.
단, 이런 동기화는 한 스레드가 힙을 쓰는 동안 나머지 스레드가 힙을 쓰지 못하도록 대기시켜야 하므로 여분의 코드가 더 필요하며 따라서 동기화 하지 않을 때 보다 당연히 속도가 느리다.
물론 동기화를 하지 않을 때도 다음 두 함수로 힙을 잠시 잠글 수는 있다.
BOOL HeapLock(HANDLE hHeap);
BOOL HeapUnlock(HANDLE hHeap);
스레드가 힙에 대해 락을 걸면 이 힙을 잠시 독점적으로 소유하며 다른 스레드가 건드리지 못하게 한다 . HEAP_NOSERIALIZE 플래그는 할당할 뿐만 아니라 추가 힙을 생성할 때, 해제할 때, 힙을 조사할 때 , 재할당할 때 각각 사용되는데 가급적이면 이 플래그는 쓰지 않는 편이 좋다.
<힙의 관리 함수들>
LPVOID HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, DWORD dwBytes);
:C 런타임 라이브러리의 realloc 과 동일하며
DWORD HeapSize(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem);
: _msize 함수와 동일하다.
SIZE_T HeapCompact(HANDLE hHeap, DWORD dwFlags);
: 힙의 빈공간 병합하여 좀 더 큰 여유 공간을 만들고 과다하게 확정된 영역은 확정 해제하여 시스템에 반납한다.
BOOL HeapValidate(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem);
:lpMem이 지정하는 블록이 유효한 블록인지를 검사한다. 제대로 할당한 블록이라면 물론 유효하겠지만 버그에 의해 이 영역이 잘못 덮어졋다면 그렇지 못할 수도 있는데 ,이 함수로 블록의 안전성 여부를 점검할 수 있다. lpMem이 NULL이면 힙 전체의 블록을 점검해 보고 이상이 있는지를 조사한다. 만약 이 함수가 FALSE를 리턴한다면 프로그램의 논리에 뭔가 이상이 있다고 볼 수 있다.
BOOL HeapWalk(HANDLE hHeap, LPPROCESS_HEAP_ENTRY lpEntry);
: HeapWalk 함수는 힙의 모든 블록을 열거한다. 이 함수를 반복적으로 호출하면 힙의 첫 블록부터 마지막 블록가지 순회하면서 다음 구조체에 블록에 대한 정보를 채운다.
typedefstruct _PROCESS_HEAP_ENTRY {
PVOID lpData;
DWORD cbData;
BYTE cbOverhead;
BYTE iRegionIndex;
WORD wFlags;
union {
struct {
HANDLE hMem;
DWORD dwReserved[3];
} Block;
struct {
DWORD dwCommittedSize;
DWORD dwUnCommittedSize;
LPVOID lpFirstBlock;
LPVOID lpLastBlock;
} Region;
};
} PROCESS_HEAP_ENTRY, *LPPROCESS_HEAP_ENTRY;
블록을 순회하면서 각 블록의 위치와 크기들을 점검해보면 어디서 이상이 발생헀는지 알 수 있다.
<새로운 힙의 생성>
디폴트 힙의 용량이 부족해도 자동으로 늘리므로 디폴트 힙만 사용해도 웬만한 메모리 요구는 다 충족 할 수 있다.
허나 스레드당 힙이라던지, 특정 데이터를 목적으로 하는 힙이라는던지 등등하여 새로운 힙이 필요할 수도 있다.
힙을 더 만드는 것은 논리적으로 메모리의 구획을 나눔으로써 안전성과 편의성을 높이는 훌륭한 기능이다.
다음 함수를 이용한다.
HANDLE HeapCreate(DWORD flOptions, DWORD dwInitialSize, DWORD dwMaximumSize);
첫 번째 인수 flOptions는 새로 생성되는 속성을 설정하는 플래기를 지정하는데,
보통 0으로 지정한다.(예외 발생 , 동기화 금지 등등 함수 있다).
두 번째 인수는 초기에 확정될 힙의 크기를 지정하며
세 번째 인수로 힙의 최대 크기를 지정한다.
한마디로 두 번쨰 인수로 확정하고 세 번쨰 인수로 예약하는 것이다.
메모리 할당 방법 비교
다음은 다양한 메모리 할당 방법의 간단한 비교이다 :
하지만 되는 GlobalAlloc , 되는 LocalAlloc 과 를 HeapAlloc의 기능이 궁극적으로 동일한 힙에서 메모리를 할당, 각 기능의 약간 다른 세트를 제공합니다. 예를 들어, 를 HeapAlloc은 메모리가 할당 될 수없는 경우, 사용할 수없는 기능 예외를 발생하도록 지시 할 수 되는 LocalAlloc를 . 되는 LocalAlloc는 핸들 값을 변경하지 않고 재 할당함으로써 이동되도록 기본 메모리 허용 핸들의 할당을 지원하는 기능을하지 사용할 수 를 HeapAlloc .
32 비트 Windows와 함께 시작 되는 GlobalAlloc 및 되는 LocalAlloc은 전화 래퍼 함수로 구현됩니다 를 HeapAlloc을 프로세스의 기본 힙에 대한 핸들을 사용하여. 따라서 되는 GlobalAlloc 및 되는 LocalAlloc은 보다 큰 오버 헤드가 를 HeapAlloc을 .
다른 힙 할당 자 다른 메커니즘을 사용하여 독특한 기능을 제공하기 때문에, 올바른 기능과 메모리를 해제해야합니다. 예를 들어, 할당 메모리 를 HeapAlloc은 함께 해제해야 HeapFree 하지 LocalFree 사용 또는 하여 GlobalFree . 할당 메모리되는 GlobalAlloc 이나 되는 LocalAlloc은 , 쿼리 검증, 및 해당 전역 또는 로컬 기능을 해제해야합니다.
VirtualAlloc을의 기능을 사용하면 메모리 할당에 대한 추가 옵션을 지정할 수 있습니다. 그러나, 그것의 할당은 그렇게 사용, 페이지 단위를 사용 은 VirtualAlloc을 더 높은 메모리 사용이 발생할 수 있습니다.
의 malloc 함수는 런타임에 종속되는 단점이있다. 새로운 연산자는 컴파일러에 의존하고 언어 의존의 단점이있다.
CoTaskMemAlloc의 함수는 C, C ++, 비주얼 베이직이나 하나에서 잘 작동의 장점을 갖는다. MIDL을 사용하기 때문에 그것은 또한 COM 기반 응용 프로그램의 메모리를 공유 할 수있는 유일한 방법입니다 CoTaskMemAlloc 및CoTaskMemFree를 메모리를 마샬링.
HeapAlloc vs. LocalAlloc
LocalAlloc 을 쉽게 말해서 HeapAlloc의 wrapper 함수라고 생각하면 된다. HeapAlloc 의 경우 HeapCreate 를 통하지 않고 현재의 process 의 handle 을 얻어서 그 값을 가지고 memory 할당을 받을 수 있다. 즉, process의 global heap 영역에서 할당 받는 방식이 있다.
LocalAlloc 을 호출하게 되면 궁극적으로 HeapAlloc 을 호출하게 되는데, 이 때에 private heap 에서 할당받는 것이 아니라 global heap 에서 할당받게 된다. 그리고 LocalAlloc 의 경우 할당 받은 메모리를 handle 로 관리하게 되므로 (HeapAlloc 은 list 로...) 이에 대한 handle 값을 return 하게 된다.
즉, 간단하게 말해서 LocalAlloc 은 HeapAlloc 의 wrapper function 인데, process 영역의 global heap 에서 할당을 받게 되고, 할당 받은 영역을 handle 로서 관리를 해줘야 한다. 즉, 직접적으로 HeapAlloc 을 쓰는 것에 비해 overhead 가 있다.
빈번히 사용하는 동적 메모리 할당을 이용하고, 이에 대한 성능 개선이 필요하다면 HeapAlloc 을 이용하는 것이 훨씬 도움이 된다는 것을 알 수 있다. malloc 도 결국 HeapAlloc을 호출한다... HeapAlloc 을 잘 쓰면 많음 도움이 된다!
Heap Spray 공격을 공부하면서 VMMAP을 이용하여 Heap 영역에 NOP sled와 Shellcode가 들어가는 것을
확인할 수 있었다. 하지만 Heap Spray로 증가된 영역은 Private 영역이었고 따로 Heap 영역도 존재하여서,
왜 Private영역에 내가 넣은 코드가 들어가고 heap 영역에는 들어가지 않는지 궁금하였다.
▲Heap Spray 공격 이후 증가된 Private 영역
먼저 어떻게 된 것인지 구글신에게 물어보니 함수를 기준으로 설명해 놓은 블로그들을 많이 보았다.
가장 큰 기준은 HeapAlloc VS etc 라는 것이다. etc에는 C에서 사용되는 malloc과 C++에서 사용되는 new를
포함하여 LocalAlloc과 HeapAlloc도 포함된다.
이에 etc의 중 하나인 LocallAlloc를 기준으로 HeapAlloc과 비교하면서 VMMAP에 나오는 heap부분과
Private data 부분에 대해 정리하였다.
힙 할당 함수
LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, DWORD dwBytes);
HLOCAL LocalAlloc(UINT uFlags, UINT uBytes);
HGLOBAL GlobalAlloc(UINT uFlags, DWORD dwBytes);
LocalAlloc과 GlobalAlloc을 같은 특성을 지녀 묶고, HeapAlloc과 구분지어 설명하도록 하겠다.
먼저 LocalAlloc부분은 항상 디폴트 힙에서 할당된다. 디폴트 힙이라 하면 하나의 어플리케이션이
하나씩 가지는 힙이다. 기본적으로 1MB로 할당되며 /Heap 옵션을 주어서 크기를 조절할 수 있다.
그렇다고 해서 1MB로 디폴트 힙을 할당해놓으면 1MB이상의 메모리 주소를 할당하지 못하는건 아니다.
윈도우가 알아서 필요하다면 늘려준다고 한다. 먼저 디폴트 힙 영역을 RESERVE 해놓은 이유는
미리 처리하여 시간을 줄이기 위해서라고 한다. 그 다음 HeapAlloc의 특성은 아래에 몰아서 설명하겠다.
리턴값을 기준으로 살펴보면 LocalAlloc이 핸들을 리턴하고 HeapAlloc이 포인터를 리턴한다.
그래서 LocalAlloc으로 할당받은 메모리 블록을 실제로 사용할 때는 LocalLock함수로 포인터를 얻어와야한다.
불편하지만 Lock이라는 단어에서 알 수 있듯이 동기화 기능을 제공한다. 하나의 프로세스 안에 있는
멀티 스레드들이 같은 영역의 메모리를 할당하거나 해제하려 할 때 예외발생을 방지하려는 것이다.
아래 그림에서 보듯이 LocalAlloc을 이용하여 디폴트 힙 영역을 할당할 때 단편화가 발생할 수 있는데,
LocalAlloc의 첫번째 인자로 GMEM_MOVEABLE이라는 특성을 지정하여서
힙관리자가 메모리 블록을 이동 시킬 수 있다.
이때 이동하여 생긴 잘못된 주소 참조를 할 수도 있으므로 핸들로 관리하는 것이다.
디폴트 힙 영역을 사용하면 무작위 메모리 할당 및 그에 따른 힙 크기의 증가 때문에
단편화(fragmentation)이 발생할 확률이 높다. 단편화가 많이 일어나면 로컬리티특성이 낮아서
성능이 떨어진다. 그래서 동적 힙영역을 사용하여 사용할 부분을 RESERVE하기 때문에 메모리 단편화가
발생하지 않는다. 아래그림을 참고 하라.
▲동적 힙 영역(왼), 디폴트 힙 영역(오른)
로컬리티
템퍼럴 로컬리티
프로그램 실행 시 한번 접근이 이뤄진 주소의 메모리 영역은 자주 접근하게 된다는 특성
스페셜 로컬리티
프로그램 실행 시 접근하는 메모리 영역은 이미 접근이 이루어진 영역의 근처일 확률이 높다는 특성
HeapAlloc은 디폴트 힙 영역이 아니라 임의의 그러니까 동적 힙영역에 생성된다. 디폴트 힙 이외에 Windows
시스템에서 제공하는 함수호출을 통해 프로세스에 추가로 생성하는 힙을 private heap이라고 한다.
여기에 내가 원하던 답이 있었다. 결국에는 처음에 컴파일 시에 링킹 옵션을 통해 정하면서 생성되는 부분을
디폴트 힙이며 그 부분이 VMMAP에 나오는 heap이고, Windows 시스템 함수에 의해 추가로 생성되는 힙을
Private Heap이라고 하는 것이다!!!
Process Defualt heap 이외에 Windows 시스템에서 제공하는 함수 호출(HeapCreate)을 통해서
프로세스에 추가로 생성하는 힙을 Private Heap이라고 한단다.
이를 통해 위에서 말했는 것을 포함해 몇가지의 장점이 있다.
1.단편화 최소를 통한 성능향상
2.동기화 문제에서 자유로움
이러한 힙을 (생성과 소멸), (할당과 해제)에 각각의 함수들이 쓰인다.
(HeapCreate HeapDestroy), (HeapAlloc HeapFree)
여기서 인자들이 많이 들어가는데 힙의 기본 할당 사이즈를 맞춰주는 부분이나 동기화부분 같은 옵션들이
들어간다.
이렇게 VMMAP에서의 Heap 부분과 Private 부분을 통해서 Heap에 대해 알아봤다.
정리하자면
LocallAlloc, GlobalAlloc, malloc, new 등의 디폴트 힙에 생기며 단편화가 발생하고
HeapAlloc은 동적 힙영역에 생기며 단편화가 생기지 않지만 디폴트 힙처럼 미리 RESERVE를 해두는 것이
아니므로 처리시간이 좀 걸린다고 생각하면 될 것 같다.
'프로그래밍 > 기타정보' 카테고리의 다른 글
프로그램 기본기 정리 (1) | 2015.01.26 |
---|---|
게임 엔진 프레임워크 (0) | 2015.01.17 |
Windows 메모리/리소스 누수 디버깅 기법들 (0) | 2015.01.06 |
데드락, Live락 대드락의 설명 (0) | 2014.08.08 |
java 와 c/c++의 차이 (0) | 2014.07.21 |