본문 바로가기

DreamHack: System Hacking/Heap Allocator Exploit(ptmalloc2)

__builtin_expect() ( __glibc_unlikely(), __glibc_likely() )

해당 글은 glibc를 분석할 때 자주 보게 되는 __builtin_expect(), __glibc_unlikely()가 무엇인지 설명하기 위해 작성한 글이다.

 

사전 지식

우리가 코드를 짜서 이를 실행할 때, CPU는 현재의 실행 흐름에 맞게 하나의 코드씩 순차적으로 연산하는 것이 아니다. CPU는 "분기 예측"을 통해 효율성을 극대화 시키는데, 이를 아주 짧게 설명해보자면 다음과 같다.

 

CPU는 앞에서 만나게 될 분기문의 결과를 예측하여 특정 경로를 타고가며 코드를 미리 연산해놓는다. 그리고 그 분기 예측이 맞았을 경우에는 미리 연산해뒀던 결과들을 이용하고, 분기 예측이 틀렸을 경우에는 미리 연산해뒀던 것들을 모두 버리고 분기문의 결과에 맞게 새로 분기 예측을 시작한다.

(분기 예측에 관해 몇 줄로 요약하여 설명하느라 생략되거나 왜곡된 부분이 많다. 이에 대해 자세히 알고 싶다면 "분기 예측"에 대해 찾아보면 많은 정보를 얻을 수 있을 것이다.)

 

__builtin_expect() 함수는 잘못된 분기 예측을 해서, 미리 연산해뒀던 결과를 모두 버리고 새롭게 분기 예측을 하는

비효율적인 연산을 줄일 수 있게 도와준다.

 

__builtin_expect()

__builtin_expect()는 GCC 내장 함수로서, 분기 예측에 대한 Hint를 줘서 컴파일러가 분기문 최적화를 할 수 있게 해준다. 이를 통해 프로그램의 실행 성능을 향상시킬 수 있다.

 

__builtin_expect()의 prototype

long __builtin_expect (long exp, long c);

exp에는 조건식을, c에는 그 조건식의 결과값으로 예상되는 값을 준다.

 

굳이 해석을 하자면, "exp라는 조건식의 결과는 c일 것이다."와 같이 해석하면 된다.

 

__builtin_expect()의 반환값

__builtin_expect()는 반환값으로 exp를 반환한다.

 

__builtin_expect()의 사용 예시

void* ptr = malloc(0x100);

if (__builtin_expect(ptr == NULL, 0)) {
	foo();
}

해석

ptr는 0x100 크기의 메모리를 동적으로 할당받아 이의 주소값을 가지게 된다. 대부분의 경우, ptr는 NULL을 가리키고 있지 않을 것이다.

 

따라서 if( __builtin_expect(ptr == NULL, 0) )을 주게 되면 이는,

컴파일러에게 "ptr == NULL인 경우는 거의 없을 거야"라는 힌트를 주게 되고, 컴파일러는 이를 토대로 분기 최적화를 통해 코드 수행의 성능을 높이게 된다.

 

그리고 __builtin_expect()의 리턴값은 안에 있는 조건식이므로, ptr == NULL인 경우에 foo()가 실행되게 될 것이다.

 

 

__glibc_unlikely, __glibc_likely ( unlikely, likely ) 매크로

# define __glibc_unlikely(cond)	__builtin_expect ((cond), 0)
# define __glibc_likely(cond)	__builtin_expect ((cond), 1)

__glibc_unlikely와 __glibc_likely 매크로는 __builtin_expect()를 간단히 표현하기 위해 쓰이는 매크로이다.

 

__glibc_unlikely(cond)은 "cond의 결과값은 대부분 0일 거야"라는 힌트를 컴파일러에게 주는 것이고,

__glibc_likely(cond)는 "cond의 결과값은 대부분 1일 거야"라는 힌트를 컴파일러에게 주는 것이다.

 

 

리눅스 커널에서는 likely와 unlikely 매크로를 사용하는데, 표현식은 __glibc_likely, __glibc_unlikely와 정확히 동일하다.