ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [우분투] libcurl 빌드 에러 수정 (--with-ssl, --with-zlib)
    C and C++ 2019. 11. 18. 13:59

    프로젝트에서 libcurl을 static library 형태로 포함해 사용하고 있다.
    이전에는 프로젝트를 우분투 14.04 그리고 osx에서 빌드하고 테스트를 했었는데, 
    이번에 우분투 18.04에서 프로젝트를 빌드하니 다음과 같은 링킹 에러가 나타났다.

    ... 
    md5.o:md5.c:(.rdata+0x0): undefined reference to `MD5_Init' 
    md5.o:md5.c:(.rdata+0x4): undefined reference to `MD5_Update' 
    md5.o:md5.c:(.rdata+0x8): undefined reference to `MD5_Final' 
    md5.o:md5.c:(.rdata+0x14): undefined reference to `MD5_Init' 
    md5.o:md5.c:(.rdata+0x18): undefined reference to `MD5_Update' 
    ... 


    SSL 심볼들이 없다는 에러인데...
    이해가 안되는건 libcurl을 빌드할 때 분명 --with-ssl--with-zlib 옵션을 주었다는 점이다.
    상식적으로 static library인 libcurl안에 필요한 심볼들이 포함되어 있어야 하는데...
    이상해서 한참을 찾다보니 다음 스레드를 발견할 수 있었다.

    Static Linking to openssl does not work. #567

    몇 년 전, Deli-Zhang이란 사람이 나와 같은 이슈로 전투를 벌인 흔적이었다.
    결론은 "--with-ssl, --with-zlib을 설정해도 libcurl은 필요한 라이브러리를 static으로 물고 가지 않는다."
    libcurl 개발자로 보이는 Daniel Stanberg는 끝까지 curl 이슈는 아니라고 하지만,
    누가 봐도 --with-ssl, --with-zlib 옵션은 오해의 소지가 충분히 있다.

    이전에 osx와 우분투 14.04에서 문제 없이 빌드를 했던 이유는 
    빌드하던 시스템에 이미 libssl, libcrypto, libz가 들어 있었으며, 
    타겟 빌드시 내 의도와는 달리 시스템에 존재하는 라이브러리들을 물고 들어와 빌드가 되었던 것으로 보인다.
    버전 디펜던시가 없어서 다행이지 만약 나도 Deli-Zhang과 같은 상황이었으면 재앙이 왔을 것이다.

    문제 해결을 위해 원래 libcurl에 포함었을 것으로 기대했던 
    libssl, libcrypto, libz를 타겟 바이너리 링크시 명시적으로 포함했다.
    cmake가 만들어낸 최종 링크 구문은 간단히 예를 들어 다음과 같다.

    g++ src1.cpp.o src2.cpp.o -o mytarget ./lib/libssl.a ./lib/libcrypto.a ./lib/libcurl.a ./lib/libz.a


    기대를 가지고 돌렸는데... 이번에도 링킹에 실패했다.
    황당한건 이번 빌드스크립트도 우분투 14.04에서는 잘 된다는 점이었다.
    (osx에서는 해보지 않았지만, 돌이켜보면 역시 잘 되었을 것이다.)

    공황에 빠져 원점에서 여러군데를 찾다보니 컴파일러가 업그레이드 되면서 나타난 다른 원인2가 있었다.
    빌드시스템상 gcc 커맨드라인에 libssl, libcrypto, libcurl, libz 순으로 라이브러리가 추가되고 있었다.
    그런데, 최근 gcc는 커맨드라인에 들어가는 오브젝트나 라이브러리의 순서에 따라 결과가 달라진다고 한다!!!

     

    Greedy한 방식으로 첫 오브젝트나 lib에서 미싱된 심볼을 테이블에 기록하고, 
    다음에 들어오는 오브젝트나 lib에 해당 심볼이 있으면 업데이트하고, 
    모든 처리가 끝난 후에도 미싱된 심볼에 대해 에러를 출력하는 형태이다.
    gcc쪽에서는 이를 "새로운 기능"이라고 주장하는데.. 할 말이 없다.

    다시 프로젝트로 돌아가보면 순서대로 처리가 되다가 libcurl 차례가 오면 SSL 함수들을 미싱테이블에 기록한다.
    이후 libz가 들어오면 libz 함수들이 클리어된다.
    이후 아무 입력이 없으므로 SSL함수는 비어있는 것으로 인지하고 에러가 출력되는 것이다.

    로마법을 따르기 위해 라이브러리 임포트 순서를 libcurl, libssl, libcrypto, libz 순으로 맞췄다.

    g++ src1.cpp.o src2.cpp.o -o mytarget ./lib/libcurl.a ./lib/libssl.a ./lib/libcrypto.a ./lib/libz.a


    그런데... 이번엔 다음과 같은 에러가 출력된다.

    dso_dlfcn.c:(.text+0x11): undefined reference to `dlopen' 
    dso_dlfcn.c:(.text+0x24): undefined reference to `dlsym' 
    dso_dlfcn.c:(.text+0x2f): undefined reference to `dlclose' 


    어차피 static이라 쓰지도 않는 dso에 포함된 dl류 함수들이 없다고 한다.
    라이브러리 관련 함수는 libc에 있는 기본 함수인데 이게 없다니 왜 그럴까 싶었는데, 
    삽질 끝에 컴파일러가 업그레이드 되면서 나온 원인3이 발견되었다.
    /usr/local/libdl.so를 사용하기 위해서는 명시적으로 -ldl 옵션을 주어야 한다고 한다!!!
    원인3을 반영해 수정된 링크 명령은 다음과 같다.

    g++ -ldl src1.cpp.o src2.cpp.o -o mytarget ./lib/libcurl.a ./lib/libssl.a ./lib/libcrypto.a ./lib/libz.a


    이제 되었다고 생각하며 빌드를 시도했다.
    그러나... 이번에도 여전히 dl류 관련 에러가 출력되었다.
    분명 라이브러리 순서도 맞고 dl 함수 옵션도 주었는데...
    그냥 14.04로 돌아갈까 하는 유혹을 참으며, 무엇이 문제일까 또 한참을 헤맸다.

    이번엔 링커가 업데이트되며 발생한 변경사항인 원인4를 발견하였다.
    최신 링커는 이전과 다르게 --as-needed 옵션이 기본으로 적용된다고 한다!!!

     

    이 옵션에 대한 설명은 이 man page에서 찾을 수 있다.
    DT_NEEDED 태그를 붙이는 방법에 대한 옵션인데, 
    짧게 줄이면 링크 시점에 non-weak reference로 참조되는 경우에만 DT_NEEDED를 붙인다는 것이다.

    이 방식으로 하면 확실히 결과물 크기를 최적화할 수 있다는데...
    지금이 2000년대 초도 아니고 바이너리 사이즈가 그렇게 크리티컬한지 모르겠다.
    날이 갈수록 비둔해져가는 안드로이드를 보면 의미가 있을런지 모르지만 스토리지가 커지는 속도가 더 빠르지 않을까.

     

    아무튼 이 경우에는 임베디드용 크로스컴파일을 하는 것도 아니니 과감하게 끄는 것이 해결책이다.
    끄기 위해서는 링커 옵션으로 --no-as-needed를 주면 된다.

    g++ Wl,--no-as-needed -ldl src1.cpp.o src2.cpp.o -o mytarget ./lib/libcurl.a ./lib/libssl.a ./lib/libcrypto.a ./lib/libz.a


    드디어 정상적인 빌드에 성공했다!!!
    세월에 적응하느라 실험과정까지 꼬박 하루를 소모하고 나니 기운이 빠지긴 했지만 아무튼 해결되었다.

    원인들을 다시 정리해보면:
    1) libcurl의 --with-ssl이나 --with-libz는 스탠드얼론 static lib을 만들지 못함
    2) 최신 gcc는 입력되는 오브젝트의 순서를 의존성 순서로 맞춰야함
    3) 최신 gcc는 dl계열 함수 사용시 -ldl 옵션 필요함
    4) 최신 gcc는 --as-needed 링커 옵션이 기본 적용됨

    이에 대한 해결책은:
    1) 최종 타겟 빌드시 필요한 버전의 libz, libssl, libcrypto 링크
    2) 오브젝트 순서 정렬
    3) -ldl gcc 옵션 적용
    4) Wl,--no-as-needed 링커 옵션 적용

    참고로 실험에 사용된 환경은 다음과 같다.
    - Ubuntu 18.04 LTS
    - g++ 5.4.0
    - ld 2.26.1

    짧은 소회는 리눅스 진영은 compatibility에 너무 신경을 안쓰는 것 같다.
    능력되면 고쳐 쓰던지 식의 문화가 세월이 흘러도 변하지 않는다.
    개인적으로 네이티브 개발을 즐겨하지만 이것도 과거 유물로 전락할 날이 얼마 남지 않은 것 같다.

    미래의 대중적인 개발 플랫폼은 결국 유니티와 같은 형태가 되지 않을까 생각한다.


    Fin.

    반응형

    댓글

Calvin's Memo