Egloos | Log-in


안드로이드 네이티브 크래쉬 덤프 (ARM cpu)

안드로이드 역시 안정성 있는 앱의 출시를 위해서는 테스터들 혹은 사용자들로부터 크래쉬 덤프를 보고받을 필요가 있다.
순수하게 Java 플랫폼으로 개발한 앱이라면 ACRA등을 이용하여 크래쉬 덤프 문제를 쉽게 해결 가능하나, C++ 등의 코드로 작성된 네이티브 크래쉬 덤프에 대해서는 아쉽게도 NDK 플랫폼에서 제대로 지원해 주는 바가 없다.
구글링을 통해 여러 방법들을 찾아보았으나, 아쉽게도 안드로이드 플랫폼을 위한 제대로 된 정보가 없는 것으로 파악되어, 그 방법을 직접 실험하여 정리해 보았다.

콜스택 덤프
NDK 라이브러리에서 __builtin_frame_address와 같은 C 함수가 지원되기는 하나, 마지막 frame pointer 외에 상위 call stack을 찾아가는 것은 지원하지 않는다. (ARM cpu에는 frame pointer라는 명시적인 개념이 없기 때문에 컴파일러에서 이 기본 라이브러리 기능을 제대로 지원하지 않는 것으로 예상된다)

ICS 이전 버전에서는 Android OS에서 시스템 로그에 출력해주는 크래쉬 덤프를 캡춰하는 방법이 가능하였으나, 그 이후 버전에서는 Android의 보안 정책 변경으로 인해 이 방법도 불가능하다.

그런데, Android Native Development Kit (r8)에 포함된 ARM 컴파일러의 경우 실제로는 frame pointer를 생성하여 사용하는 것을 발견할 수 있었으며, 이 버전 컴파일러의 규칙을 가정하면 
__builtin_frame_address를 부분적으로 사용하여 전체 call stack을 덤프하는 것이 가능하다. (NDK의 컴파일러가 생성하는 frame pointer는 ARM에서 제공하는 기본 컴파일러와 다소 다른 규칙을 사용하는 것으로 보임)

NDK 환경에서는 아래와 같은 코드를 사용하면 call stack 덤프가 가능하다.

void __backtrace( void* fp, char* trace )
{

if (fp == 0)

return;


void* funcAddr = (void*) *((int*)fp); // NDK 컴파일러에 의한 코드는 fp의 위치에 호출된 함수의 주소 저장


Dl_info info;

if( dladdr( funcAddr, &info ) )

{

funcAddr = (void*) ( (unsigned int)funcAddr - (unsigned int)info.dli_fbase ); // funcAddr은 프로세스의 메모리 상의 주소이므로, 라이브러리상의 함수 주소로 변환

char buffer[256];

sprintf( buffer, "%s %p\n", info.dli_fname, funcAddr);

strcat( trace, buffer );

}

__backtrace( (void*)(*((int*)fp - 1)), trace ); // fp 위치의 다음 필드가 이전의 fp

}


// 현재 context의 stack dump를 trace에 텍스트로 저장

void GetBacktraces( char* trace )

{

__backtrace( __builtin_frame_address(0), trace );

}


이 방법은 NDK 컴파일러의 구현에 의존적이므로, 미래의 NDK 컴파일러에서는 제대로 작동하지 않을 수도 있다.


또한, NDK의 기본 옵션에서는 디버그 빌드에서만 frame pointer가 생성되고, Application.mk에서 APP_OPTIM := release 으로 설정하여 릴리즈 빌드를 컴파일 하는 경우, NDK의 기본 옵션은 frame pointer를 생성하지 않는 것이므로 위의 구현이 정상 작동하지 않는다.


릴리즈 빌드의 경우, APP_CFLAGS := -marm -fno-omit-frame-pointer 옵션을 추가해주면 강제로 frame pointer가 생성되도록 컴파일 할 수 있다. 그러나 약간의 실행성능 희생이 필요할 것이다.



크래쉬(Signal) 핸들러 설정


NDK에도 포함되어 있는 signal 관련 함수들을 이용하여 크래쉬 핸들러를 설정할 수 있다. 앱의 초기화 부분에서 아래의 AddSignalHandlers()를 호출해 주면 SignalHandler가 등록된다.


// 크래쉬(signal) 발생시 호출됨

void SignalHandler(int signal, siginfo_t *info, void *reserved)

{

char backtraces[1024];

GetBacktraces( backtraces ); // 위에서 구현했던 콜스택 덤프 기능

// 콜스택 덤프를 외부로 전송하는 등 선호에 따른 구현

}


// 크래쉬 핸들러 설정

void AddSignalHandlers()

{

struct sigaction handler;

memset(&handler, 0, sizeof(struct sigaction));


handler.sa_sigaction = SignalHandler;

handler.sa_flags = SA_RESETHAND;

#define CATCHSIG(X) sigaction(X, &handler, &old_sigactions[X])

CATCHSIG(SIGILL);

CATCHSIG(SIGABRT);

CATCHSIG(SIGBUS);

CATCHSIG(SIGFPE);

CATCHSIG(SIGSEGV);

CATCHSIG(SIGSTKFLT);

CATCHSIG(SIGPIPE);

}



크래쉬덤프의 심볼 해석 (Symbol resolution)

NDK에서는 위에서 덤프된 콜스택의 함수 주소를 소스코드의 위치로 변환해주는 androideabi-addr2line 이라는 툴이 제공된다.

쉘에서 아래와 같이 명령하면 함수 주소에 해당하는 소스코드의 위치를 보여 준다.


androideabi-addr2line -C -f -e <앱라이브러리.so> <함수 주소>


단, 여기에 입력으로 사용되는 so 파일은 디버그 심볼을 포함하고 있어야 한다. 안드로이드 기기에 올라가는 파일에는 기본으로 디버그 심볼이 포함되어 있지 않으므로, 컴파일 당시에 생성된 bin 폴더 내의 so 파일을 입력해 주어야 한다.

위 명령은 한번에 하나의 함수에 대한 소스코드만을 알려주므로, 크래쉬덤프 전체를 일괄로 해석해 주는 기능을 스크립트 등으로 구현함이 좋을 것이다.



by 김성균 | 2013/05/09 20:12 | 일(Work) | 트랙백 | 덧글(0)

트랙백 주소 : http://littles.egloos.com/tb/3413938
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]

:         :

:

비공개 덧글

◀ 이전 페이지          다음 페이지 ▶