[완료]Linux pthread 라이브러리 메모리 누수

체스맨의 이미지

Fedora 8, kernel 2.6, glibc-2.7-2 입니다.
다음 코드에서 메모리릭이 발생하는데, 다른 분들은 어떤 결과를 나타내는지 알고 싶습니다. 동일한 현상을 언급하는 2007년 10월경에 올라온 다음 글이 있습니다.

http://www.linuxquestions.org/questions/programming-9/memory-leak-in-pthread-591263/

void*
func( void * p )
{
    puts( "thread function called." );
    /* if pthread_exit is called, memory leaks will be increased */
    /*  pthread_exit( NULL ); */
    return NULL;
}
 
int
main( void )
{
    void* res;
    pthread_t t;
    pthread_attr_t ta;
    pthread_attr_init( &ta );
    pthread_attr_setdetachstate( &ta, PTHREAD_CREATE_JOINABLE );
    pthread_create( &t, &ta,  func, NULL );
    pthread_attr_destroy( &ta );
    pthread_join( t, &res );
 
    if( malloc_cnt>0 )
        printf( "%d memory leak(s)\n", malloc_cnt );
    return 0;
}

전체 소스는 첨부 파일에 있고, malloc hook 을 이용해서, 의미있는 malloc, free 에 대해 카운터를 증감시키는 방법으로 누수 갯수만 간단히 판단합니다. valgrind 등을 사용하실 수 있는 분들은 그것을 이용하셔도 검출이 될 겁니다.

제 실행 결과는 다음과 같습니다.

malloc 0x804a008, 136
thread function called.
free (nil)
free (nil)
1 memory leak(s)

이 메모리 누수는 실행중 계속 누적되는 것은 아닌 것 같습니다. 아무튼 메모리 누수가 발생하지 않는 분들이 계신지 알고 싶구요, glibc 버젼 등을 알고싶네요. 아니면, 제 리눅스 설치에 문제가 있는지도 모르겠습니다...

File attachments: 
첨부파일 크기
Package icon ptleak.zip902바이트
irondog의 이미지

2006년도에도 누군가 같은 내용을 올렸었네요.
시원한 답변은 아닙니다만 한번 해보시기 바랍니다.

http://kldp.org/node/74907

체스맨의 이미지

pthread_detach 에 관한 거라면, 이미 테스트를 해봤습니다. pthread_detach 또는 pthread_join 중 하나를 실행하면 되는데, 제가 테스트한 예제는 pthread_join을 호출합니다... 그리고, detach 해도 결과는 마찬가지였습니다.

검색을 해보면, pthread_detach로 해결이 되더라하는 분들도 있긴 한데, 같은 문제가 아닌 것 같다는 생각이 듭니다.

gnu ftp 사이트에 가보면, glibc-linuxthread 라이브러리의 최종 수정일이 2006년 경입니다. 그 때부터 이 문제가 해결되지 않은 듯 싶네요.

혹시 다른 분들 중에 누수가 발생하지 않는 분이 계시면, 그 분의 시스템 설정을 따라갈까 생각했었습니다....

지금으로선 일단, malloc_hook 에서 해제되지 않을 포인터를 기억해뒀다가 강제로 해제할 수 있는지 테스트해볼 생각입니다. 누수가 남으면, 혹시 다른 곳에서 새고 있는지 자꾸 신경이 쓰여서요...

Orion Project : http://orionids.org

mithrandir의 이미지

NPTL에선 어떤가요?

언제나 삽질 - http://tisphie.net/typo/

언제나 삽질 - http://tisphie.net/typo/
프로그래밍 언어 개발 - http://langdev.net

체스맨의 이미지

커널 2.6 에서 스레드 관련해서는 일단 NPTL관련 문제 입니다.

Orion Project : http://orionids.org

modestcode의 이미지

메모리 hook을 통해서 메모리 누수를 판단하는 것이 쓰레드 라이브러와 같은 유저에게는 맞지 않다는 것을 보여주는 것 같습니다. 라이브러리 입장에서도 기본적인 패턴인데 다시 해제하고 할당할 필요가 없을 것 같습니다. 커널의 도움을 받을 수도 있고요. 숫자가 찝찝하면 malloc_cnt 할 때, free에서 메모리 검사하는 부분을 빼면 잘 맞습니다.
new_realloc_hook에서는,

if (p == NULL)
    malloc_cnt--
이 추가 되야 좀 더 맞을 것 같습니다.
valgrind 로 테스트 했을 경우 possibly lost 부분은 FAQ에 이렇게 나와있습니다.
"possibly lost" means your program is probably leaking memory, unless you're doing funny things with pointers.
말그대로 glibc 개발자가 재밌는 뭔갈 했는 모양입니다. 메모리 누수라고 말하려면 더 확실한 근거가 필요할 것 같습니다.
체스맨의 이미지

메모리 hook을 통해서 메모리 누수를 판단하는 것이 쓰레드 라이브러와 같은 유저에게는 맞지 않다는 것을 보여주는 것 같습니다. 

어떤 경우 그런가요? 첨부한 코드는 간단하게 카운터 검사만 하지만, 제가 실제로 사용하는 malloc_hook은 일정 크기만큼 더 할당해서, 할당시 연결 리스트에 등록시키고, 해제시 연결리스트에서 빼낸뒤, 프로그램 종료시 리스트에 남아있는 노드들을 출력하는 방법을 사용합니다. 할당시마다 유일한 id 값을 지정해놓으면, 누수되는 메모리의 id 를 판별해서 할당이 일어나는 위치에 break 를 걸 수도 있구요. 이 방법은 glibc 뿐 아니라, visual c 같은 경우도 crtdbg.h 에서 유사한 방법을 지원하고 있습니다. valgrind 를 별도로 실행하는 경우보다, 개발 및 실행 중에 즉시 알아차릴 수 있기때문에, 1차적인 방법으로 선호하고 있습니다.

free 에서 메모리 검사하는 부분을 빼면 잘 맞는다

라는 말씀이 정확히 무엇을 의미하는 지 모르겠군요. 인자 포인터가 NULL 인 경우를 걸러냈을 뿐입니다. 이건 당연히 걸러내야하는 경우라고 생각됩니다.

그리고, new_realloc_hook 에 대해 언급하신 부분은, NULL 인 경우 malloc_cnt 를 증가시키는 게 맞겠죠... 아닌가요? 그래서 이미 다음과 같이 하고 있습니다만...

if( p==NULL && newp!=NULL ) malloc_cnt++

p가 NULL 이 아닌 경우는 이미 new_malloc_hook 에서 한번 증가시켰을테니, 굳이 고려할 필요가 없구요. 게다가 이 문제에서는 realloc 은 호출이되지 않습니다.

malloc hook 을 구현해놓은 건, 이 문제를 valgrind 없이도 다른 분들이 쉽게 테스트 해보실 수 있도록 그렇게 했을 뿐입니다. 그리고, 제 경험상 memory hook 으로 누수를 판단하는 것이 잘못된 결과를 주는 경우는 없었습니다. 만일 그런 경우가 있다면 어떤 경우인지 알고 싶습니다.

'메모리 누수라 말하려면 더 확실한 근거가 필요하다'는 말씀도 잘 이해하기 어렵습니다. 일단 누수가 검출되면 누수는 누수인거라 생각합니다. 단지 반복 실행 중에 계속 누적되는 누수가 아니라면 크게 염려할 것은 아닐뿐이죠.

Orion Project : http://orionids.org

modestcode의 이미지

Quote:

어떤 경우 그런가요? 첨부한 코드는 간단하게 카운터 검사만 하지만, 제가 실제로 사용하는 malloc_hook은 일정 크기만큼 더 할당해서, 할당시 연결 리스트에 등록시키고, 해제시 연결리스트에서 빼낸뒤, 프로그램 종료시 리스트에 남아있는 노드들을 출력하는 방법을 사용합니다. 할당시마다 유일한 id 값을 지정해놓으면, 누수되는 메모리의 id 를 판별해서 할당이 일어나는 위치에 break 를 걸 수도 있구요. 이 방법은 glibc 뿐 아니라, visual c 같은 경우도 crtdbg.h 에서 유사한 방법을 지원하고 있습니다. valgrind 를 별도로 실행하는 경우보다, 개발 및 실행 중에 즉시 알아차릴 수 있기때문에, 1차적인 방법으로 선호하고 있습니다.

첨부한 코드로 가정했을 뿐입니다. 실제 사용하신다는 코드도 쓰레드에 잘 맞다것을 보여줄 수 있는지요?

Quote:

라는 말씀이 정확히 무엇을 의미하는 지 모르겠군요. 인자 포인터가 NULL 인 경우를 걸러냈을 뿐입니다. 이건 당연히 걸러내야하는 경우라고 생각됩니다.

그리고, new_realloc_hook 에 대해 언급하신 부분은, NULL 인 경우 malloc_cnt 를 증가시키는 게 맞겠죠... 아닌가요? 그래서 이미 다음과 같이 하고 있습니다만...


쓰레드 라이브러가 다르게 처리한다면 최소한 카운트를 맞추기 위해서는 그렇게 하면 될 것 같다는 것이었습니다.

Quote:

p가 NULL 이 아닌 경우는 이미 new_malloc_hook 에서 한번 증가시켰을테니, 굳이 고려할 필요가 없구요. 게다가 이 문제에서는 realloc 은 호출이되지 않습니다.

realloc 이 free 할 수 있기 때문에 그렇게 하는 것이 맞다는 것입니다. man realloc 을 참고하시기 바랍니다. 그리고 이미 하고 있다는 부분은 free했을 경우 다시 할당하는 경우를 고려하고 있을 뿐입니다.
다음은 제가 실행한 결과입니다.
malloc 0x502010, 272
realloc (nil), 2048
free (nil)
thread function called.
2 memory leak(s)

Quote:

malloc hook 을 구현해놓은 건, 이 문제를 valgrind 없이도 다른 분들이 쉽게 테스트 해보실 수 있도록 그렇게 했을 뿐입니다. 그리고, 제 경험상 memory hook 으로 누수를 판단하는 것이 잘못된 결과를 주는 경우는 없었습니다. 만일 그런 경우가 있다면 어떤 경우인지 알고 싶습니다.

쓰레드와 관련된 얘기를 했을 뿐입니다.
체스맨의 이미지

예, 아래와 같이 결론에 도달함으로써, 제가 사용하는 malloc_hook 방법은 여전히 누수를 검출하는데 전혀 문제가 없는 상태입니다. 공개 프로젝트인 coral 라이브러리에 있는 코드이니, 나중에 코드가 업데이트되면 보실 수 있을 겁니다. 첨부한 코드 역시 단순화한 것이지만, 문제가 있는 방법은 아닙니다.

쓰레드 라이브러가 다르게 처리한다면 최소한 카운트를 맞추기 위해서는 그렇게 하면 될 것 같다는 것이었습니다.

NULL 을 걸러내지 않아서 카운터를 맞춘다는건 전혀 이치에 맞지 않고, 제 실행 결과를 잘 보시면 free(NULL) 이 두번 호출돼서, NULL 을 거르지 않으면 카운터는 -1 이 됩니다. 단지, 현재 첨부 코드가 count>0 일 때만 출력하기 때문에 출력이 안됐을 뿐이겠죠.

realloc의 그 경우는 알고 있지만, free 하는 경우는 size 가 0 인 경우까지 같이 체크해야 됩니다. 이건 테스트 코드 작성이었기때문에 누락된 것으로 이해해 주세요.

Orion Project : http://orionids.org

modestcode의 이미지

위 실행한 결과에서도 나와 있지만 size 가 0 이 아니기 때문에 malloc count만 올려놓습니다. 그리고 라이브러리가 free를 호출했다고 해서 진짜 쓰레드 라이브러리가 free한 것이 아닐 수 있기 때문에 역시 count를 맞추기 위해서 그냥 검사를 안 한 것입니다. 제가 실행 하고 테스한 결과는 0으로 잘 나왔습니다. 이건 억지로 맞추려고 한 것일 뿐 malloc hook으로 쓰레드 내부 메모리까지 추적한다는 것은 이렇듯 의미가 없을 뿐입니다. malloc hook을 glibc 라이브러리 개발자를 위해 디자인 했다고 생각하진 않습니다.

체스맨의 이미지

아... 이거 하나 여쭤보겠습니다.

실행 결과를 올리신게 realloc 을 임의로 호출하지 않고, 제가 올려놓은 소스 그대로 실행해서 올리신 것인가요?

그렇다면, 지금 쓰고 계신 pthread 라이브러리는 pthread_create 에서 realloc 을 호출하나보네요. 제 경우, malloc-free 호출만 있어서, 윗글에서 realloc 은 관여하지 않아서 별로 문제가 되지 않는다고 얘기했었습니다. 사실 p!=NULL && size==0 을 고려하지 않은 것도 realloc 쪽은 별로 신경쓰지 않았습니다만, realloc 을 쓰는 pthread 버젼도 있는가보군요...

Orion Project : http://orionids.org

modestcode의 이미지

그대로 한 것이고요, 버젼에 따라 다른 것 같습니다.

체스맨의 이미지

음...
제 valgrind 테스트에 실수가 있었던 모양이네요. 현재 소스 상태로는 valgrind 로 leak 이 검출되지 않는군요. 깊은 밤이라 정신이 없었는지 제가 뭔가 실수를 한 모양입니다. 테스트를 제대로 하지 않고 글을 올려 죄송합니다.

하지만, 발제글의 소스에 주석처리돼있는 pthread_exit 부분의 주석을 없애면, 제 경우 valgrind 에서 5개의 누수가 검출됩니다. malloc_hook 은 여섯개가 되고요.

그렇다면, modestcode 님께서 말씀하신, 메모리 hook을 통해서 메모리 누수를 판단하는 것이 스레드 라이브러와는 맞지 않는다는 것은 가능성이 있을 것 같습니다. 지금 예상으론, glibc 에서 사용자의 malloc_hook 을 제거한 다음(실제 그런 작업이 glibc 내에서 일어나는지는 잘 모르겠습니다...)에 pthread 가 할당한 메모리가 해제되는 것으로 봐야할 것 같습니다. ( 사실 이렇게 구현돼 있다는 것 역시 잘 이해가 안됩니다. )

그런데, 위와 같이 생각할 경우 이해가 잘 안되는 것은, 제가 해본 다음과 같은 테스트들 때문입니다.

테스트 1 : malloc_hook 에서 pthread_create 시 할당한 메모리를 기억해두었다가 process 종료시 강제해제
이것이 가능합니다. 그리고 아무런 오류를 내지 않습니다. 실제로 free 된다면 double free 오류가 나야할텐데요.

테스트 2 : malloc_hook 에서 일정 크기로 선언한 static 배열을 리턴해봤습니다. 이 경우도 문제가 나지 않습니다. 실제로 free 된다면 segfault 가 되겠지요.

테스트 3 : malloc_hook 에서 일정 부분 더 할당해서 그만큼 옵셋을 더해 리턴합니다. 이 경우도 malloc_hook 이 제거된 상태에서 free 가 실제로 된다면 segfault 겠지요.

그래서 valgrind 가 뭔가 잘 못 판단하고 있거나, 아니면 pthread 라이브러리가 뭔가 트릭(또는 버그)을 쓰고 있다고 여겨지는군요.

아무튼 malloc_hook 에서는 누수를 발생시키는 호출 부분에서 할당되는 메모리들을 표시해뒀다가, 최종 print 에서 배제하는 방법으로 문제를 해결했습니다. 문제는 해결했지만, 다른 분들의 테스트 결과도 알고 싶군요... leak 이 나지 않는다면, pthread_exit 주석을 풀고 다시 테스트 해보셨으면 합니다.

Orion Project : http://orionids.org

modestcode의 이미지

Quote:

하지만, 발제글의 소스에 주석처리돼있는 pthread_exit 부분의 주석을 없애면, 제 경우 valgrind 에서 5개의 누수가 검출됩니다. malloc_hook 은 여섯개가 되고요.

return NULL은 묵시적으로 pthread_exit을 호출한다고 알고 있습니다. 결과적으로 두 번 호출한 것이 됩니다.
체스맨의 이미지

무엇을 지적하고 싶으신 건지 모르겠습니다. pthread_exit 는 호출 가능한 함수입니다. 이걸 호출한게 문제라는 걸 말씀하시는 건가요? 그리고, 아래 언급한 것처럼 pthread_exit 에 의한 누수도 누적되는 누수가 아닙니다.

Orion Project : http://orionids.org

modestcode의 이미지

Quote:

pthread_exit 에 의한 누수도 누적되는 누수가 아닙니다.

불필요한 걸 호출했다는 말이고요,
malloc hook에서 누수가 나왔다고 해서 누수가 아니라는 것입니다. valgrind 결과는 마찬가지로 possibly lost입니다. 누적되지 않는 누수라는 말은 여기서 말하는 문맥과는 거리가 있어 보입니다. 나중에 수거하는 부분이 있는 것을 누수라는 말로 표현할 수는 없는 것입니다.
체스맨의 이미지

예, 나중에 수거하는 부분이 있다면 당연히 누수로 볼 수 없습니다.
그래서 __libc_freeres 가 수거하는, pthread_create 가 할당한 1개의 메모리 블럭은 누수가 아니었습니다.

그러나, pthread_exit 가 발생시키는 것은 현재로서는 누수로 판단됩니다. __libc_freeres 를 호출해도 이들 누수는 사라지지 않습니다. valgrind 테스트에서도 마찬가지구요.

불필요한 호출을 말씀하시는데, 제가 한 건 테스트입니다. 실제 프로그램 안에서 작동하는 코드가 아니라, 실험을 위한 코드일 뿐입니다. 또한, pthread_exit 를 호출하는 예제 코드들도 많이 있습니다.

그리고 possibly lost 에 대해서는, pthread_exit 호출 상태에서 valgrind 실행하시면, 사용하시는 시스템에서는 그렇게 출력되시는 것 같습니다. 하지만 제 경우는 다음과 같이 출력됩니다.

==13750== LEAK SUMMARY:
==13750==    definitely lost: 0 bytes in 0 blocks.
==13750==      possibly lost: 0 bytes in 0 blocks.
==13750==    still reachable: 908 bytes in 5 blocks.
==13750==         suppressed: 0 bytes in 0 blocks.

저는 실제로는 pthread_exit 를 잘 안쓰기 때문에, __libc_freeres 로 pthread_create 가 할당한 메모리가 해제된 것만으로, 만족하고 있습니다.

Orion Project : http://orionids.org

modestcode의 이미지

pthread_exit의 경우 glibc 2.6.1, valgrind 3.2.3에서 ERROR SUMMARY 에서 하나 잡힙니다. 근데 c++ 라이브러리로 링크하면 안 생깁니다. 이건 확실히 의심스런운 데가 있습니다.

체스맨의 이미지

음... 확실히 버젼별로 차이가 있군요.
저는 glibc-2.7 이고, pthread_exit 시 c 소스로 하면 5개, c++ 로하면 1개가 남습니다. 아무튼 이 부분은 문제라고 보는게 맞을 것 같네요...

Orion Project : http://orionids.org

modestcode의 이미지

valgrind를 믿느냐 glibc를 믿는냐는 사실 좀 나중 문제입니다. 실제로 프로그램이 문제를 일으키면 그 땐 두 그룹 중 어느 그룹에 확신을 시키기 위해서 테스트 케이스를 작성하거나, 대화로 답을 끌어 내도록 해야겠지요.

체스맨의 이미지

결론에 도달했습니다.

우선 위의 제 글에서 malloc_hook 작동에 대한 가정은 틀렸고, modestcode 님께서 말씀하신 malloc_hook 을 통한 누수 검출이 스레드 라이브러리와 맞지 않는다는 말씀도 사실이 아닙니다.

1. valgrind 역시 --run-libc-freeres=no 옵션을 사용함으로써, malloc_hook 과 동일하게 누수를 검출시킬 수 있습니다.

2. 이에 근거해서, 개발자가 임의로 __libc_freeres 라는 glibc 내부 함수를 호출할 수 있습니다. 이것을 호출한 뒤에는 malloc_hook 역시 leak 을 내지 않습니다. 단, 이렇게 할 경우 어떤 glibc 버젼인 경우는 segfault 를 낼 수 있다고 합니다. 그래서, 디버깅 목적 정도로 사용하는 것이 바람직 하겠습니다.

3. 제 테스트에서 valgrind 가 double free 나 segfault 를 내지 않았던 것은, 위에서 언급한 특정 버젼의 glibc 에 대한 segfault 문제 때문에, 실제로 free 는 행하지 않기 때문인 것으로 생각됩니다.

4. pthread_exit 에 의한 누수는 __libc_freeres 도 해제를 시키지 않고 있는 누수에 해당합니다.

5. 이 누수는 반복 실행시 누적되는 누수가 아닙니다.

6. 결론적으로, malloc_hook 에서는 제가 위에 언급한 해결책보다는, __libc_freeres 를 호출하되, 이 호출시에는 free_hook 에서 valgrind 와 마찬가지로 실제 free 는 하지 않는 방법을 사용하는 것이 바람직하다고 봅니다.

참고
http://gcc.gnu.org/onlinedocs/libstdc++/debug.html
http://gd4.tuwien.ac.at/.vhost/linuxcommand.org/man_pages/valgrind1.html

Orion Project : http://orionids.org

modestcode의 이미지

Quote:

1. valgrind 역시 --run-libc-freeres=no 옵션을 사용함으로써, malloc_hook 과 동일하게 누수를 검출시킬 수 있습니다.

쓰레드 내부적으로 사용하는 메모리는 체크하지 않는 옵션으로 어떻게 쓰레드 프로그램에서 동일하게 검출할 수 있다는 말씀이세요?

Quote:

3. 제 테스트에서 valgrind 가 double free 나 segfault 를 내지 않았던 것은, 위에서 언급한 특정 버젼의 glibc 에 대한 segfault 문제 때문에, 실제로 free 는 행하지 않기 때문인 것으로 생각됩니다.

위 링크 매뉴얼에서 언급한 특정 버젼은 아주 고대적 redhat 7.1입니다. 그리고 프로그램이 exit에서 segfault 날 때 끄고 하라는 것입니다. 지금 경우와 맞지 않습니다.

Quote:

6. 결론적으로, malloc_hook 에서는 제가 위에 언급한 해결책보다는, __libc_freeres 를 호출하되, 이 호출시에는 free_hook 에서 valgrind 와 마찬가지로 실제 free 는 하지 않는 방법을 사용하는 것이 바람직하다고 봅니다.

--run-libc-freeres 옵션은 완변주의자들을 위한 추가적인 옵션일 뿐입니다. 더구나 제대로 동작한다는 보장을 valgrind 개발자나 glibc 개발자가 하지도 않았고요. possibly lost 가 뜻하는 것이 바로 그것입니다.
체스맨의 이미지

--run-libc-freeres=no 는 쓰레드 내부적으로 사용하는 메모리를 체크하지 않는 옵션이 아니라, 언급한 것처럼, 단지 glibc 의 __libc_freeres 함수를 호출하지 않는 옵션입니다. 저 역시 이 함수를 미처 몰랐기 때문에, malloc_hook을 쓸 때 호출하지 않았고, 결국은 이 옵션을 사용함으로써, valgrind 와 malloc_hook 의 결과를 실제로 일치시켰다는 얘기입니다.

위 링크 매뉴얼에서 언급한 특정 버젼은 아주 고대적 redhat 7.1입니다. 그리고 프로그램이 exit에서 segfault 날 때 끄고 하라는 것입니다. 지금 경우와 맞지 않습니다.

그리고 자꾸 핀트를 빗나가시는 것 같은데, 제가 한 테스트는 pthread_create 가 malloc 을 호출할 때, 제가 malloc_hook 에서 임으로 static 배열을 리턴해주거나, 일정부분 옵셋을 주어 리턴하거나, 아니면 할당된 포인터를 기억해뒀다가 종료시 강제 해제테스트를 한 것입니다. 그럼에도 불구하고 valgrind 는 malloc/free 각 1회씩으로 판단했고, GPF 는 일어나지 않았습니다.

GPF 가 일어나지 않은 원인은, valgrind 가 free 를 가로채서, 그 __lib_freeres 가 호출될 때에는 실제로 free 는 호출하지 않고, 누수 검출 관련 부분만 실행했기 때문입니다. 제가 언급한 GPF 문제는 __libc_freeres 를 호출할 때 glibc 특정 버젼에서 GPF 가 날 수 있다는 것과는 '전혀' 관계가 없는 얘기입니다.

그리고, 덧붙여,특정 버젼이 구버젼 얘기라 하셨지만, 위와 같이 valgrind 도 누수 검출은 행하지만, 그것을 실제로 free 해버리지는 않았습니다. 구버젼 얘기라도 반영을 했다는 얘깁니다. 저라도 valgrind 개발자였다면 호환성을 위해 그렇게 했을 겁니다.

--run-libs-freeres 는 제가 쓰고있는 3.2.3 버젼에서 디폴트가 yes 입니다. 그래서, no 로 지정해서 테스트를 한 것이구요. 완벽주의자를 위한 옵션이려면, 디폴트가 no 이겠죠.

Orion Project : http://orionids.org

modestcode의 이미지

Quote:

--run-libc-freeres=no 는 쓰레드 내부적으로 사용하는 메모리를 체크하지 않는 옵션이 아니라, 언급한 것처럼, 단지 glibc 의 __libc_freeres 함수를 호출하지 않는 옵션입니다. 저 역시 이 함수를 미처 몰랐기 때문에, malloc_hook을 쓸 때 호출하지 않았고, 결국은 이 옵션을 사용함으로써, valgrind 와 malloc_hook 의 결과를 실제로 일치시켰다는 얘기입니다.

__libc_freeres 함수 glibc에서 내부적으로 사용하는 메모리를 valgrind와 같은 툴을 위해 성능을 희생하며 해제하는 역할을 합니다. glibc 안에 쓰레드 라이브러리가 있고요. 제가 너무 일반화 했나요? _libc_thread_freeres 함수가 있는 걸로 봐서는 구분하는 것 같기도 하지만 제가 테스트한 결과로는 __lib_freeres만 호출해도 되더군요. 결과적으로 틀린 말이 아닙니다.

Quote:

그리고 자꾸 핀트를 빗나가시는 것 같은데, 제가 한 테스트는 pthread_create 가 malloc 을 호출할 때, 제가 malloc_hook 에서 임으로 static 배열을 리턴해주거나, 일정부분 옵셋을 주어 리턴하거나, 아니면 할당된 포인터를 기억해뒀다가 종료시 강제 해제테스트를 한 것입니다. 그럼에도 불구하고 valgrind 는 malloc/free 각 1회씩으로 판단했고, GPF 는 일어나지 않았습니다.

음...다시 읽어 봐도 제가 한 답변이 핀트가 나갈 만큼이라고 생각하지 않습니다. --run-libc-freeres 관련해서 segfault가 날 때 no를 해라고 한 매뉴얼에서 설명한 상황과 맞지 않다고 했을 뿐입니다. 그 외 예제에 없는 코드에 대해서는 제가 뭐라고 할 상황도 아닙니다.
Quote:

Quote:

GPF 가 일어나지 않은 원인은, valgrind 가 free 를 가로채서, 그 __lib_freeres 가 호출될 때에는 실제로 free 는 호출하지 않고, 누수 검출 관련 부분만 실행했기 때문입니다. 제가 언급한 GPF 문제는 __libc_freeres 를 호출할 때 glibc 특정 버젼에서 GPF 가 날 수 있다는 것과는 '전혀' 관계가 없는 얘기입니다.

예제 프로그램에서 segfault가 안 일어났고 valgrind __lib_freeres 옵션과는 상관없이 possibly lost block이 하나 생겼는데(최근 버젼인 ubuntu gutsy에서는 안 생겼지만) segfault가 일어나지 않은 원인과 처음 제기한 문제가 어떻게 관련이 있는지 모르겠습니다.

Quote:

--run-libs-freeres 는 제가 쓰고있는 3.2.3 버젼에서 디폴트가 yes 입니다. 그래서, no 로 지정해서 테스트를 한 것이구요. 완벽주의자를 위한 옵션이려면, 디폴트가 no 이겠죠.

default가 yes인 것은 이미 링크하신 매뉴얼에도 나와있고요, 프로그램 옵션에서도 물론 확인한 것입니다. valgrind 개발자는 확실해 완벽주의자임이 틀림없습니다.
체스맨의 이미지

__libc_freeres 에 대해서 그런 관점으로 볼 수도 있다고 생각합니다. 아무튼 제 얘기는 이 호출을 valgrind 가 안하도록 함으로써, 기존에 malloc_hook과 valgrind 결과의 불일치를 해소했다는 얘기였구요.

이 옵션을 모르고 있었는데, 이 불일치를 근거로 valgrind 의 옵션을 찾다가, 저 함수에 대해서도 알게 됐습니다. 애초에 분명히 glibc 에 클리어 루틴이 있을 거라는 예측은 했는데, 그 쪽을 뭘로 검색할 지 생각이 없는 상태였거든요.

핀트를 빗나간 것 같다 말씀드린 것에 대해서는, 우선 글로 주고 받는 복잡한 토론에서는 의견을 정확히 전달하고, 잘 못 전달된 것을 수정하기가, 말로 하는 것보다는 덜 용이한 것 같다고 생각합니다. 굳이 이 부분에 더 반론할 생각은 없구요.

제가 테스트 한 것의 골자는, valgrind 가 정말로 물리적인 free 루틴을 호출한다면, 제가 임의로 그 free 에 문제가 생기도록 변조를 했을 때 segfault 가 나야한다 라는 생각에서 한 것이고, 제 테스트에서 segfault 가 나지 않았기 때문에, valgrind 경우는 __libc_freeres 를 호출하면서 leak 체크는 하지만, 그 메모리들에 대해 물리적인 free 는 하지 않는다 - 이것은, glibc 가 언급하는 __libc_freeres 를 호출할 때 segfault 나는 버젼도 있다. ( 구버젼일지라도 ), 해서 valgrind 개발자 입장에서는 그걸 고려하지 않았나 하고 예측한 것입니다. 그러니까, 나타나는 현상을 바탕으로 왜 그런지 생각을 해봤던 겁니다. 이 문제는 처음 제기한 문제와는 관련이 없고, 스레드 좀 뒤쪽에 제가 올린 글에, 첫 테스트에 일부 실수가 있었고... 하는 글에 나타난 이해하기 힘든 현상을 설명하는 과정이었습니다.

저도 이런부분에 있어서는 조금 완벽주의자 쪽에 치우쳐있지 않나 생각됩니다. 하지만, 뭐 개인적인 성향은 그렇지 않아요. ^^ 아무튼 관심을 갖고 테스트해주셔서 감사드립니다.

Orion Project : http://orionids.org

체스맨의 이미지

제가 실수가 많군요... -_-;

"valgrind 가 문제를 발생하도록 임의로 변조를 했을 때 valgrind 가 문제가 발생하지 않더라"

라고 위에서 제가 말한 것은 사실이 아닙니다. 왜냐면, valgrind 를 실행하면 제가 작성한 malloc_hook 이 무시되기 때문입니다.

그래서 valgrind 가 __libc_freeres 를 호출하면서 물리적인 free 를 하는지, 아니면 glibc 구버젼을 고려해서 안하는지에 대해서는 지금 상태로 판별하긴 어렵고, valgrind 소스를 봐야할 것 같습니다.

그래서 modestcode 님께서 말씀하신 것처럼 __libc_freeres 가 segfault 를 일으키는 glibc 버젼이 있다는 것은 구버젼얘기라, 실제로 valgrind 개발자가 그것을 고려하지 않았을 수도 있습니다. 이건 제가 판단을 하다가 엉뚱하게 새버린 것 같네요.

제 추론과정은 이런 것이었습니다.
1. valgrind 로 누수를 검출하니 누수가 안나타나는데, malloc_hook은 그렇지 않더라
2. 이경우, pthread 에 대해서는 malloc_hook 으로 누수를 검출할 수 없는 것이라면, 제가 작성한 malloc_hook 의 영향을 벗어난 상태에서 free 가 된다는 것이고
3. 그렇다면 malloc_hook 으로 free 에 문제가 생기도록 변조했을 때, segfault 가 나야한다. 하지만, segfault 가 나지 않았다.

여기까지 입니다. 이것을 얘기하다가, 토론 중에 생각이 꼬였는 모양입니다.

그래서 그 후에 valgrind 와 malloc_hook 의 불일치를 고려하다가, __lib_freeres 가 있구나까지 결론에 다다랐습니다.

본의 아니게 혼돈을 일으킨 게 있네요. 중간마다는 고치지 않고 그냥 두겠습니다. 테스트 해주신 분들 감사합니다.

Orion Project : http://orionids.org

체스맨의 이미지

valgrind 소스를 뒤지기는 너무 복잡하고, FAQ 에 다음 내용이 있네요.

3. Valgrind aborts unexpectedly
-----------------------------------------------------------
 
3.1. Programs run OK on Valgrind, but at exit produce a bunch of errors involving __libc_freeres() and then die with a segmentation fault. 
 
When the program exits, Valgrind runs the procedure __libc_freeres() in glibc. This is a hook for memory debuggers, so they can ask glibc to free up any memory it has used. Doing that is needed to ensure that Valgrind doesn't incorrectly report space leaks in glibc. 
 
Problem is that running __libc_freeres() in older glibc versions causes this crash. 
 
Workaround for 1.1.X and later versions of Valgrind: use the --run-libc-freeres=no flag. You may then get space leak reports for glibc allocations (please don't report these to the glibc people, since they are not real leaks), but at least the program runs.

그래서, 예상컨데 물리적인 free 를 valgrind 같은 경우는 하지 않나 생각됩니다.

제 경우는 __libc_freeres 호출 전에 특수한 상황이라는 표시를 해두고, 이경우 free_hook 호출시 할당된 메모리를 추가해두는 연결 리스트에서는 빼내지만, 실제로 물리적인 free 는 하지 않도록 했습니다.

제가 구현한 방법을 쓰면, 구버젼 glibc 에서라도 __libc_freeres 를 디버그 목적으로 호출하는 것이 segfault 를 일으키지 않을 거라 예상됩니다.

Orion Project : http://orionids.org

체스맨의 이미지

첨부 파일도 수정해서 올렸습니다.
위에 지적된 realloc_hook 에 문제있는 부분을 수정했고, 주석으로 막아놨지만, __libc_freeres 호출도 넣어뒀습니다. 주석을 풀면 pthread_create 가 할당한 메모리는 잘 해제될 것입니다.

반면에 pthread_exit 주석을 풀면 여전히 누수가 나타납니다. ( 제 경우 c build 시 5개, c++ build 시 1개 ). 굳이 pthread_exit 를 쓸 일이 없어서, 제 입장에서는 그다지 문제가 안되는 상태입니다.

Orion Project : http://orionids.org

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.