2013년 4월 2일 화요일

Android상에서 native shared library loading시 dlsym 성공 여부 확인 방법

이전 블로그에서 이전 함 (원본 글 2013/04/02 작성)

이전 포스트와 관련해서 
다시 linux man page를 찾아보니 dlerror() 예제에서 
dlerror() 를 호출하여 error message를 clear 시키고 있었음.

Load the math library, and print the cosine of 2.0:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int
main(int argc, char **argv)
{
    void *handle;
    double (*cosine)(double);
    char *error;

   handle = dlopen("libm.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }

   dlerror();    /* Clear any existing error */

   /* Writing: cosine = (double (*)(double)) dlsym(handle, "cos");
       would seem more natural, but the C99 standard leaves
       casting from "void *" to a function pointer undefined.
       The assignment used below is the POSIX.1-2003 (Technical
       Corrigendum 1) workaround; see the Rationale for the
       POSIX specification of dlsym(). */

   *(void **) (&cosine) = dlsym(handle, "cos");

   if ((error = dlerror()) != NULL)  {
        fprintf(stderr, "%s\n", error);
        exit(EXIT_FAILURE);
    }

   printf("%f\n", (*cosine)(2.0));
    dlclose(handle);
    exit(EXIT_SUCCESS);
}

-----------------------------------------------------------------------------------------
이전 포스트 내용
-----------------------------------------------------------------------------------------

오늘 dlsym 관련 황당한 일이 있어 이를 글로 남김

일반적인 Linux에서 shared library를 loading하고자 할 경우 다음의 예제가 일반적이다.

  #include <stdlib.h>
    #include <stdio.h>
    #include <dlfcn.h>

    int main(int argc, char **argv) {
        void *handle;
        double (*cosine)(double);
        char *error;

        handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);
        if (!handle) {
            fputs (dlerror(), stderr);
            exit(1);
        }

        cosine = dlsym(handle, "cos");
        if ((error = dlerror()) != NULL)  {
            fputs(error, stderr);
            exit(1);
        }

        printf ("%f\n", (*cosine)(2.0));
        dlclose(handle);
    }


하지만 위의 예제를 가지고 Android NDK로 빌드하여 Android 상에서 실행하였지만
"Symbol not found: " 라는 메세지와 함께 종종 dlsym이 실패하는 경우를 확인할 수 있었다.
(Android NDK r8d, Android target의 version은 JBP)

생성된 shared library의 symbol을 확인하였을때는 
해당 함수는 naming mangling(http://en.wikipedia.org/wiki/Name_mangling) 문제 없이 export 되어 있어서 library의 open에 성공하여 symbol 찾는데는 문제가 없어 보였다.

이래 저래 검색하다 아래와 같은 Android bug report를 검색하게 되었다. dlsym에서 symbol을 못찾는 문제와 조금 다른 bug report지만 dlerror() 메세지 처리에서 있어 GetLastError()와 같은 thread unsafe 한 상황이 발생할 수 있음을 알 수 있다.

아래의 답변이 가장 정리가 잘된듯 하다.

 Some important rules:
  1. you should use ndk-build instead directly arm-linux-androideabi-gcc
  2. in Android.mk of library type next options: LOCAL_LDFLAGS += -Wl,--export-dynamic
  3. if dlerror() returns error it doesn't mean that dlsym() failed. Check if dlsym() realle returns null. Because it may return a valid address but dlerror() may return error "Symbol not found" at the same time %)) I had this stupid mistake!!! See code.
  4. You do not need call System.LoadLibrary(your_lib.so). Only dlopen(your_lib.so) from native code with RTLD_NOW or RTLD_LAZY no matter!
  5. Be sure that your_lib.so is sutiated in /data/data/app_name/libs
    func = (int (*)(int, int)) dlsym( dl_handle, "calculate" ); error = (char *) dlerror(); if (error != NULL) {//replace it with if ((func == NULL)&& (error != NULL))__android_log_print(ANDROID_LOG_INFO,"nativeCode",error); return -3; }
That's all :)
share|improve this answer

위에서 답변과 같이 아래와 같이 dlsym의 error check 부분을 수정하여 정상 동작 함을 확인.

 if ((error = dlerror()) != NULL) {

if ((error = dlerror()) != NULL) && (cosine == NULL)) {

하지만 Linux man page를 확인하니 다음의 메세지가 있었음.

 dlsym()
The function dlsym() takes a "handle" of a dynamic library returned by dlopen() and the null-terminated symbol name, returning the address where that symbol is loaded into memory. If the symbol is not found, in the specified library or any of the libraries that were automatically loaded by dlopen() when that library was loaded, dlsym() returns NULL. (The search performed by dlsym() is breadth first through the dependency tree of these libraries.) Since the value of the symbol could actually be NULL (so that a NULL return from dlsym() need not indicate an error), the correct way to test for an error is to call dlerror() to clear any old error conditions, then call dlsym(), and then call dlerror() again, saving its return value into a variable, and check whether this saved value is not NULL.


그래서 아래 처럼 수정하는게 맞을 것 같다...

dlerror(); // to clear dlerror message
cosine = dlsym(handle, "cos");
if ((error = dlerror()) != NULL) && (cosine == NULL)) {




[참고]  nm, readelf를 사용하여 shared library의  symbol확인

 The standard tool for listing symbols is nm, you can use it simply like this:
nm -g yourLib.so
If you want to see symbols of a C++ library, add the "-C" option which demangle the symbols (it's far more readable demangled).
nm -gC yourLib.so
If your .so file is in elf format, you will have to use readelf program to extract symbol information from the binary.
readelf -Ws /usr/lib/libexample.so
You only should extract those that are defined in this .so file, not in the libraries referenced by it. Seventh column should contain a number in this case. You can extract the corresponding lines withawk:인인
readelf -Ws /usr/lib/libstdc++.so.6 | awk '{print $8}';
Update: Thanks to Pavel Shved and Gaspin, I've updated the answer
share|improve this answer

댓글 2개:

  1. GetLastError()는 thread safety한 함수입니다. last error는 tls에 저장되고 있습니다.

    답글삭제