이번에는 Stomp Allocator를 통해 기존 new와 delete에 생길 수 있던 문제점을 해결해보겠습니다.
전에 있던 글 중에서 new, delete가 작동하는 원리에 대해 보시면 더 이해가 잘될 것입니다.
간단히 설명하자면 new로 할당을 하고 delete로 해제를 해주었는데도 접근이 가끔 가능했던 문제점이 있었습니다. 실수로 이렇게 접근하게 된다면 문제가 되겠죠.
그래서 전에 만들었던 xxnew, xxdelete 함수에 Stomp Allocator로 할당을 해주어 고치려고 합니다.
먼저 VirtualAlloc, VirtualFree를 이용해 운영체제에게 직접 할당과 해제를 하도록 합시다.
void* Alloc(int32 size) {
int64 pageCount = (size + 4096 - 1) / 4096;
void* addr = VirtualAlloc(NULL, pageCount * 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
return addr;
}
void Release(void* ptr) {
VirtualFree(ptr, 0, MEM_RELEASE);
}
int main() {
Knight* knight = xxnew<Knight>();
knight->_hp = 100;
xxdelete<Knight>(knight);
knight->_hp = 200;
}
이렇게 바꾸면 혹여나 실수로 knight에 접근해도 크래쉬가 나게 됩니다. 아까와는 달리 운영체제에게 바로 해제를 요구했기 때문입니다. 다만 좀 아쉬운 점이 추가로 있습니다.
아쉬운 점은 VirtualAlloc은 최소 4096 비트를 할당하기에 작은 영역의 할당을 하더라도 더 넓은 영역을 할당해야한다는 단점이 있습니다.
그러다보니 생기는 문제점인데 할당된 영역 그 이상의 공간을 건드려도 VirtualAlloc으로 쓰기, 읽기 권한이 있는 공간이라 건드려도 크래쉬가 발생하지 않습니다.
class Player {
public:
Player() {
}
public:
int32 _id = 0;
};
class Knight : public Player {
public:
Knight() {
cout << "Knight()" << endl;
}
Knight(int32 hp) : _hp(hp) {
cout << "Knight(hp)" << endl;
}
~Knight() {
cout << "~Knight()" << endl;
}
public:
int32 _hp;
};
int main() {
Knight* knight = (Knight*)xxnew<Player>();
knight->_hp = 100;
xxdelete<Knight>(knight);
}
이렇게 더 적은 용량의 Player로 선언하고 이를 상속받아 더 큰 용량인 Knight로 TypeCasting을 해도 크래쉬가 발생하지 않습니다.
이를 해결하기 위해 마지막 영역에서부터 할당을 하면 될 것입니다. 왜냐하면 오버플로가 일어난다면 그 공간은 VirtualAlloc으로 할당된 곳 외의 공간이기 때문입니다.
void* Alloc(int32 size) {
int64 pageCount = (size + 4096 - 1) / 4096;
int64 offSet = pageCount * 4096 - size;
void* addr = VirtualAlloc(NULL, pageCount * 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
return static_cast<void*>(static_cast<int8*>(addr) + offSet);
}
void Release(void* ptr) {
int64 addr = reinterpret_cast<int64>(ptr);
int64 pageCount = addr / 4096;
int64 originAddr = addr - (addr % 4096) - (pageCount * 4096);
VirtualFree(reinterpret_cast<void*>(originAddr), 0, MEM_RELEASE);
}
이렇게 한다면 오버플로우가 일어나지 않을 것입니다.
여기까지 Stomp Allocator에 대해 알아보았습니다.
Stomp Allocator를 통해 Use After Free, Overflow를 해결할 수 있었습니다.
다만 동적할당을 할 때마다 필요 이상의 용량을 요구하기에 실제 배포단계에서는 사용하지 말고 오류를 잡기 위해 디버그 모드에서만 사용하는 것이 좋을 것입니다.