일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- OSR
- LLVM
- tracing
- LLVM 난독화
- Android
- anti debugging
- initial-exec
- android inject
- linux thread
- TLS
- v8 tracing
- Linux packer
- tracerpid
- apm
- on-stack replacement
- LLVM Obfuscator
- so inject
- uftrace
- linux debugging
- 난독화
- Injection
- Obfuscator
- pthread
- thread local storage
- custom packer
- Linux custom packer
- 안티디버깅
- v8 optimizing
- on stack replacement
- pinpoint
- Today
- Total
Why should I know this?
C++ new 연산자 null 체크는 필요한가? 본문
최근에 면접을 보러 다니는데 제목과 같은 내용을 질문받아서 흥미롭다는 생각을 했습니다.
이 글은 해당 내용에 대해 다루고자 합니다.
1. LLVM libcxx 에 구현되어 있는 new
_LIBCPP_WEAK
void *
operator new(std::size_t size) _THROW_BAD_ALLOC
{
if (size == 0)
size = 1;
void* p;
while ((p = ::malloc(size)) == nullptr)
{
// If malloc fails and there is a new_handler,
// call it to try free up memory.
std::new_handler nh = std::get_new_handler();
if (nh)
nh();
else
#ifndef _LIBCPP_NO_EXCEPTIONS
throw std::bad_alloc();
#else
break;
#endif
}
return p;
}
2. C++ 코드 + IR 로 보는 new 연산자의 호출
보시다시피 new 는 내부적으로 malloc을 호출합니다..!? 이런 구조가 어떻게 가능한지 생각해봅니다.
초간단 C++코드 입니다.
class cls {
int a;
};
int main()
{
cls *c;
c = new cls();
}
그리고 그 IR 입니다.
; Function Attrs: mustprogress noinline norecurse optnone uwtable
define dso_local noundef i32 @main() #0 {
%1 = alloca ptr, align 8
%2 = call noalias noundef nonnull ptr @_Znwm(i64 noundef 4) #3
call void @llvm.memset.p0.i64(ptr align 4 %2, i8 0, i64 4, i1 false)
store ptr %2, ptr %1, align 8
ret i32 0
}
_Znwm = new 연산자의 mangled name 입니다.
new cls(); 의 호출은 말 그대로 new 라는 함수를 호출하게 됩니다.
이 함수가 바로 1에서 봤던 libcxx 에 포함된 operator new(std::size_t size) _THROW_BAD_ALLOC 입니다.
호출 과정을 디버거로 한번 보죠.
(gdb) disas main
Dump of assembler code for function main():
0x00000000000007d4 <+0>: sub sp, sp, #0x20
0x00000000000007d8 <+4>: stp x29, x30, [sp, #16]
0x00000000000007dc <+8>: add x29, sp, #0x10
0x00000000000007e0 <+12>: mov x0, #0x4 // #4
0x00000000000007e4 <+16>: bl 0x670 <_Znwm@plt>
Breakpoint 1, main () at test.cpp:8
8 c = new cls();
Breakpoint 1, main () at test.cpp:9
9 c = new cls();
(gdb) si
0x0000aaaaaec60670 in operator new(unsigned long)@plt ()
(gdb)
0x0000aaaaaec60674 in operator new(unsigned long)@plt ()
(gdb)
0x0000aaaaaec60678 in operator new(unsigned long)@plt ()
(gdb)
0x0000aaaaaec6067c in operator new(unsigned long)@plt ()
(gdb)
0x0000ffff8d7232b0 in operator new(unsigned long) () from /lib/aarch64-linux-gnu/libstdc++.so.6
(gdb)
보시는 것처럼 libstdc++.so.6 에 구현되어 있는 new 를 호출합니다.
우선 정리하자면 operator new는 단순히 지정된 class 의 사이즈를 malloc 하고 결과를 반환합니다.
단 메모리 할당에 문제가 생겼을 때는 "지정된 작업"을 하게 됩니다.
몇 번의 재할당 시도 후에 "일반적인 경우 = 예외를 사용하도록 설정된 경우" 에는 std::bad_alloc(); 을 throw하게 되어 있죠.
3. 모든 C++ 라이브러리가 같을까?
여기서 std::bad_alloc()의 throw는 구현하기 나름이라는 겁니다. 그래서 아마 gnu c++과 mscv c++의 동작은 미묘하지만 다를 수 있습니다. 다음은 안드로이드 올드 버전에 구현되어 있는 new 연산자입니다.
void __libc_fatal(const char* format, ...) {
va_list args;
va_start(args, format);
__libc_fatal(format, args);
va_end(args);
abort();
}
void* operator new(std::size_t size) {
void* p = malloc(size);
if (p == NULL) {
__libc_fatal("new failed to allocate %zu bytes", size);
}
return p;
}
보시다시피 new 연산자가 실패할 경우 abort() 시켜버립니다. (뭐지 이건?)
결론을 내자면 new 연산자는 OS에 패키지로 포함된 라이브러리 (libstdc++ 같은)의 new 함수를 호출하게 되어 있으며
우리가 사용하는 일반적인 범용 환경에서는 null 체크를 할 필요 없이 bad_alloc 에 대한 예외처리를 해야 합니다.
하지만, 이는 환경마다 차이가 있으므로 주의가 필요합니다.
또한 C++의 특성상 new와 같은 연산자는 오버로드 가능하죠.
ps1. 이제야 왜 android에서 작성된 C++코드 중에 일부에 항상 메모리 관리자가 함께 구현되어 있는지 의문이 풀렸네요.
ps2. 메모리 관리가 엄격한 환경 (특히 모바일/Embedded)에서는 자체 메모리 관리자를 함께 갖추는걸 고려하는게 좋은 방향일지도 모르겠습니다. (ps1을 생각했을때)
'Study' 카테고리의 다른 글
Program Analysis (??) (0) | 2023.05.18 |
---|---|
백업 - Nodejs Uftrace (0) | 2023.03.13 |
Shuffler Fast and Deployable Continuous (0) | 2023.02.05 |
Control Flow Integrity for COTS Binaries (0) | 2023.02.04 |
Class linking 메커니즘 - jvm (0) | 2022.09.30 |