Linux 디스 어셈블리 디버깅 방법
Linux 커널 모듈이나 응용 프로그램은 여러 가지 이유로 충돌하는 경우가 많으며 일반적으로 함수 호출 스택 정보가 인쇄되는데이 경우 문제를 어떻게 찾을 수 있습니까? 이 문서에서는 이러한 문제를 찾는 데 도움이되는 분해 방법을 소개합니다.
코드 예는 다음과 같습니다.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys / types.h>
#include <sys / stat.h>
#define PRINT_DEBUG
#define MAX_BACKTRACE_LEVEL 10
#define BACKTRACE_LOG_NAME "backtrace.log"
static void show_reason (int sig, siginfo_t * info, void * secret)
{
무효 * 배열 [MAX_BACKTRACE_LEVEL];
size_t 크기;
#ifdef PRINT_DEBUG
char ** 문자열;
size_t i;
크기 = 역 추적 (배열, MAX_BACKTRACE_LEVEL);
strings = backtrace_symbols (배열, 크기);
printf ( "% zd 스택 프레임을 얻습니다. \ n", 크기);
for (i = 0; i <크기; i ++)
printf ( "% s \ n", 문자열 [i]);
무료 (문자열);
#그밖에
int fd = open (BACKSTRACE_LOG_NAME, O_CREAT | O_WRONLY);
크기 = 역 추적 (배열, MAX_BACKTRACE_LEVEL);
backtrace_symbols_fd (배열, 크기, fd);
닫기 (fd);
#endif
exit (0);
}
void die () {
char * str1;
char * str2;
char * str3;
char * str4 = NULL;
strcpy (str4, "ab");
}
void let_it_die () {
그만큼();
}
int main (int argc, char ** argv) {
struct sigaction act;
act.sa_sigaction = show_reason;
sigemptyset (& act.sa_mask);
act.sa_flags = SA_RESTART | SA_SIGINFO;
sigaction (SIGSEGV, & act, NULL);
sigaction (SIGUSR1, & act, NULL);
sigaction (SIGFPE, & act, NULL);
sigaction (SIGILL, & act, NULL);
sigaction (SIGBUS, & act, NULL);
sigaction (SIGABRT, & act, NULL);
sigaction (SIGSYS, & act, NULL);
let_it_die ();
반환 0;
}
이 예제에서는 사용자 정의 신호 처리 함수를 사용하여 프로그램이 비정상 일 때 backtrace () 및 backtrace_symbols ()를 호출하여 함수 호출 스택 정보를 얻고 인쇄합니다. 다음으로 프로그램을 컴파일하고 실행합니다.
컴파일 할 때 주로 디버깅 정보를 추가하기 위해 -g 및 -rdynamic 옵션이 추가됩니다. 런타임 중에 예외가 발생하고 함수 호출 스택이 인쇄되었으며 스택 프레임이 7 개 계층임을 알 수 있습니다. 스택 프레임 정보에서 커널은 함수 호출 스택 정보를 획득 할 때 계층별로 반전하므로 함수 호출 순서가 반대로됩니다. 즉, main-> let_it_die ()-> die ()입니다.
함수 호출 스택의 각 줄에 표시되는 형식은 다음 과 같습니다. 문제가되는 코드가있는 실행 파일 (기호 + 상대 변위) [로드 주소]
호출 스택 정보 라인 "./backtrace(main+0xf7) [0x80488cd]"를 예로 들어 현재 실행 파일이 백 트레이스이고 코드 라인이 기본 심볼의 주소에서 0xf7 라인으로 오프셋되어 있음을 보여줍니다. 일반적으로 실행 파일에서 분해 된 어셈블리 코드에서 주 심볼의 주소에 상대 변위를 더한 값은 후속로드 주소와 같습니다. 때로는 버전 업데이트로 인해 코드를 다시 컴파일하여 실행 파일을 생성 한 다음 문제를 분해하고 분석 한 후 문제가 발생한 시간과 비교하여 코드가 업데이트되므로 main + 0xf7이 같지 않을 수 있습니다. 문제가 발생했을 때 인쇄 된 호출에 스택의로드 주소. 부호 값에 상대 변위를 더한 값을 계산 한 다음이를 부하 주소와 비교하여 코드가 문제와 일치하는지 확인할 수 있습니다.
다음으로 실행 파일을 분해합니다.
justin @ ubuntu : ~ / workspace / backtrace $ objdump -dS backtrace> backtrace.asm
다음으로 디스 어셈블 된 어셈블리 코드와 문제 발생시 호출 스택 정보를 분석하여 문제를 찾습니다. 먼저 맨 아래 호출 스택, 즉 ./backtrace () [0x804873d]에서 시작하여 0x804873d 주소 기호를 검색합니다. 다음과 같이 어셈블리 코드 :
해당 코드 라인 크기 = backtrace (array, MAX_BACKTRACE_LEVEL); 코드를 보면이 함수가 show_reason 함수에서 호출됨을 알 수 있습니다. 어셈블리 코드에서 show_reason 기호의 주소를 찾으십시오.
코드를 분석 한 결과, show_reason 함수는 예외 발생시 커스텀 예외 처리 함수라는 것을 알았습니다. 상위 계층의 호출 스택 정보에 인쇄 된 주소 0xb7707410을 찾아 보았습니다. 어셈블리 코드에없는 것으로 나타났습니다. , show_reason은 프로그램의 내부 예외이므로 외부 주소임을 나타냅니다. 프로그램이 비정상 일 때 호출되므로 내부 코드가 프로그램 예외의 원인이되어야하므로 다음 호출의 다음 줄을 분석합니다. 스택 정보, 즉 ./backtrace(die+0x18) [0x80487c0]. 먼저 어셈블리 코드에서 다이 기호를 찾으십시오. 주소는 0x80487a8입니다.
이 주소를 사용하여 0x80487c0과 같은 상대 변위 0x18을로드하고 함수 호출 스택에 표시된로드 주소는 동일합니다. 어셈블리 코드에서 주소 행을 찾으십시오.
문제가 strcpy (str4, "ab")에 있음을 알 수 있습니다.이 코드 줄에서는 이전에 정의 된 str4가 널 포인터라는 것을 분명히 알 수 있습니다. 널 포인터가 가리키는 영역에 문자열을 복사하면 널 포인터 예외가 발생합니다.
물론 문제가 발생했을 때 해당 코드를 찾을 수 없거나 내 보낸 실행 파일이 디버깅 옵션없이 컴파일되어 소스 코드와 어셈블리 코드의 디스 어셈블리 정보를 분해 할 수없는 경우도 많습니다. 전자의 경우 하중 번지는 참조 의미가없고 분해 정보에서 기호 만 찾아 상대 변위를 통해 오차 선을 찾아서 기존 코드와 문제를 비교 분석 할 수있다. 후자의 경우 오류 라인의 어셈블리 코드 만 찾은 다음 어셈블리 코드 스 니펫을 읽어 문제의 원인을 분석 할 수 있습니다.
일반적으로 objdump 디스 어셈블리를 사용하여 문제를 분석하지만 특히 유용한 두 가지 명령, 즉 nm 및 addr2line도 사용합니다.
nm는 실행 파일에서 기호 테이블을 내보내는 데 사용되며 그 기능은 readelf –s 또는 objdump –T (t)와 유사합니다.
nm 명령으로 검색된 die symbol 및 let_it_die symbol의 주소는 디스 어셈블 된 주소와 동일합니다.
addr2line 명령은 주소를 지정하여 실행 파일에서 기호, 실행 파일 및 오류 코드 줄을 인쇄 할 수 있습니다.
여기에서 ./backtrace(die+0x18) [0x80487c0]에서로드 주소를 찾으려고했는데 오류가 발생했을 때 호출되는 die 함수를 찾았습니다. 오류 라인은 80 행입니다.
addr2line이 die 함수에서 strcpy가 호출되는 줄을 찾는 것을 분명히 알 수 있습니다.