Segmentation Fault 오류시 CallStack 확인 방법

|

Segmentation Fault

‘Segmentation Fault’ 오류는 잘못된 메모리 영역을 참조할 때 발생하는 오류입니다. 이 때 단순히 ‘Segmentation Fault’ 오류 메세지만 출력하기 때문에 어느 부분에서 오류가 발생했는지 디버그하기가 까다롭습니다. 물론, 디버깅 툴을 통해서 callstack을 보면서 디버그를 할 수 있으면 좋겠지만, 크로스 컴파일 환경에서는 디버깅이 쉽지 않은 경우가 대부분입니다.

다음 예제 코드는 라즈베리파이에서 돌아가는 Segmentation Fault시 Callstack을 츨력하는 코드입니다. ARM 계열 CPU를 타켓으로 했으며, x86이나 다른 계열 CPU에서는 코드에서

caller_address = (void *) uc->uc_mcontext.arm_pc;  // RIP: x86_64 specific     arm_pc: ARM

부분을 수정하면 됩니다.


예제 코드

#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <cxxabi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.h>

using namespace std;

typedef struct _sig_ucontext {
  unsigned long uc_flags;
  struct ucontext *uc_link;
  stack_t uc_stack;
  struct sigcontext uc_mcontext;
  sigset_t uc_sigmask;
} sig_ucontext_t;

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) {
  void * array[50];
  void * caller_address;
  char ** messages;
  int size, i;
  sig_ucontext_t * uc;

  uc = (sig_ucontext_t *) ucontext;

  /* Get the address at the time the signal was raised */
  caller_address = (void *) uc->uc_mcontext.arm_pc;  // RIP: x86_64 specific     arm_pc: ARM

  fprintf(stderr, "\n");

  if (sig_num == SIGSEGV)
    printf("signal %d (%s), address is %p from %p\n", sig_num, strsignal(sig_num), info->si_addr,
           (void *) caller_address);
  else
    printf("signal %d (%s)\n", sig_num, strsignal(sig_num));

  size = backtrace(array, 50);
  /* overwrite sigaction with caller's address */
  array[1] = caller_address;
  messages = backtrace_symbols(array, size);

  /* skip first stack frame (points here) */
  for (i = 1; i < size && messages != NULL; ++i) {
    printf("[bt]: (%d) %s\n", i, messages[i]);
  }

  free(messages);

  exit(EXIT_FAILURE);
}

void installSignal(int __sig) {
  struct sigaction sigact;
  sigact.sa_sigaction = crit_err_hdlr;
  sigact.sa_flags = SA_RESTART | SA_SIGINFO;
  if (sigaction(__sig, &sigact, (struct sigaction *) NULL) != 0) {
    fprintf(stderr, "error setting signal handler for %d (%s)\n", __sig, strsignal(__sig));
    exit(EXIT_FAILURE);
  }
}

int crash() {
  char *s = "hello world";
  *s = 'H';

  printf("Crash~!!");
  return 0;
}

int foo4() {
  crash();
  return 0;
}

int foo3() {
  foo4();
  return 0;
}

int foo2() {
  foo3();
  return 0;
}

int foo1() {
  foo2();
  return 0;
}

int main(int argc, char *argv[]) {

  // For crashes, SIGSEV should be enough.
  installSignal(SIGSEGV);

  // But, you can install handlers for other signals as well:
  //installSignal(SIGBUS);
  //installSignal(SIGFPE);
  //installSignal(SIGILL);
  //installSignal(SIGINT);
  //installSignal(SIGPIPE);
  //installSignal(SIGQUIT);
  //installSignal(SIGTERM);
  //installSignal(SIGTSTP); # This is Ctrl+Z, we need to ignore it. This is to send the process to the background (suspend).
  //installSignal(SIGTTIN);
  //installSignal(SIGTTOU);
  //installSignal(SIGSYS);
  //installSignal(SIGXCPU);
  //installSignal(SIGXFSZ);

  foo1();

  return 0;
}


빌드시 옵션

빌드시 옵션으로 -g 옵션을 줘야 합니다. Eclipe에서 Debug 모드로 빌드할 경우 기본적으로 -g3 옵션을 주기 때문에 크게 신경쓸 필요가 없습니다.


실행 결과

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

$ ./SegmentationFaultCapture

signal 11 (Segmentation fault), address is 0x10a64 from 0x108c8
[bt]: (1) ./SegmentationFaultCapture() [0x108c8]
[bt]: (2) ./SegmentationFaultCapture() [0x108c8]
[bt]: (3) ./SegmentationFaultCapture() [0x108f8]
[bt]: (4) ./SegmentationFaultCapture() [0x10910]
[bt]: (5) ./SegmentationFaultCapture() [0x10928]
[bt]: (6) ./SegmentationFaultCapture() [0x10940]
[bt]: (7) ./SegmentationFaultCapture() [0x1096c]
[bt]: (8) /lib/arm-linux-gnueabihf/libc.so.6(__libc_start_main+0x114) [0xb6c5f294]


addr2line으로 오류 위치 확인

위 결과창만으로는 아직 정확한 오류 위치를 알 수 없기 때문에 addr2line 명령어를 이용해서 결과를 조회합니다.

$ addr2line -f -e ./SegmentationFaultCapture 0x108c8
_Z5crashv
D:\Workspace\RobotLitoService\SegmentationFaultCapture\Debug/../Main.cpp:73

Ubuntu에 JDK 설치하는 방법

|

Ubuntu에 JDK 설치하는 방법

오라클 공식 사이트에서 다운받아 설치

오라클 공식 배포 사이트에서 JDK 설치 파일을 다운받아 설치하는 방법이 있습니다. 가장 확실한 방법 중의 하나입니다.

다만, 여기에서 내려 받는 파일은 확장자가 rpm으로 되어 있습니다. 이 경우, deb 확장자로 변경해주어야 설치를 할 수 있습니다.

rpm 파일을 deb 파일로 변경하고 나면 다음 명령어를 이용해서 JDK를 설치할 수 있습니다.

$ sudo dpkg -i jdk1.8.0-144_1.8.014401_amd64.deb


확장자 변환하지 않고 직접 설치

오라클 공식 사이트에서 tar.gz 형태의 파일을 내려 받은 뒤 직접 설치하는 방법입니다.

먼저 다음 명령어를 이용해서 압축을 해제하고 파일들을 /usr/lib/jvm 폴더 아래로 이동시켜줍니다.

$ tar zxvf jdk-8u144-linux-x64.tar.gz
$ sudo mkdir /usr/lib/jvm
$ sudo mv jdk1.8.0_144 /usr/lib/jvm

그리고 다음 명령어를 통해 명령어 등록을 해 줍니다.

$ sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk1.8.0_144/bin/java 1
$ sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk1.8.0_144/bin/javac 1
$ sudo update-alternatives --install /usr/bin/javaws javaws /usr/lib/jvm/jdk1.8.0_144/bin/javaws 1
$ sudo update-alternatives --config java
$ sudo update-alternatives --config javac
$ sudo update-alternatives --config javaws


PPA를 이용한 방법

PPA(Personal Package Archive)에서 JDK를 내려 받아 설치하는 방법입니다. apt-get 명령어를 이용한 설치라서 간편합니다.

$ sudo apt-add-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer

장소 자동 완성 예제

|

장소 검색 텍스트 박스를 자동으로 완성하는 방법입니다. 구글에서 해당 기능을 쉽게 사용할 수 있도록 컴포넌트를 제공하고 있습니다. Place Picker와 마찬가지로 ‘Google Places API for Android’ 라이브러리에대한 Google API Key 등록을 해야 합니다.

장소 자동 완성은 크게 Fragment를 사용하는 방법과 Intent 호출을 이용하는 방법의 두 가지가 있습니다.


Fragment 사용하는 코드

public class PlaceAutocompleteActivity extends AppCompatActivity {

  static final String TAG = "PlaceAutocomplete";
  PlaceAutocompleteFragment autocompleteFragment;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_place_autocomplete);

    autocompleteFragment = (PlaceAutocompleteFragment)
        getFragmentManager().findFragmentById(R.id.place_autocomplete_fragment);

    autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() {
      @Override
      public void onPlaceSelected(Place place) {
        Log.i(TAG, "Place: " + place.getName());
      }

      @Override
      public void onError(Status status) {
        Log.i(TAG, "An error occurred: " + status);
      }
    });
  }
}


Intent를 이용하는 방법

public class PlaceAutocompleteActivity extends AppCompatActivity {

  int PLACE_AUTOCOMPLETE_REQUEST_CODE = 1;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_place_autocomplete);

    findViewById(R.id.btn_autocomplete).setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        try {
          Intent intent = new PlaceAutocomplete.IntentBuilder(PlaceAutocomplete.MODE_FULLSCREEN)
              .build(PlaceAutocompleteActivity.this);
          startActivityForResult(intent, PLACE_AUTOCOMPLETE_REQUEST_CODE);
        } catch (GooglePlayServicesRepairableException e) {
          // TODO: Handle the error.
        } catch (GooglePlayServicesNotAvailableException e) {
          // TODO: Handle the error.
        }
      }
    });
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == PLACE_AUTOCOMPLETE_REQUEST_CODE) {
      if (resultCode == RESULT_OK) {
        Place place = PlacePicker.getPlace(data, this);
        String toastMsg = String.format("Place: %s", place.getName());
        Toast.makeText(this, toastMsg, Toast.LENGTH_LONG).show();
      }
    }
  }

}

Place Picker 예제

|

Place Picker를 사용하기 위해서는 Google API Key를 등록해야 합니다. Google 라이브러리들 중 ‘Google Places API for Android’를 등록하면 됩니다.


예제 코드

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.ui.PlacePicker;

public class MainActivity extends AppCompatActivity {

  int PLACE_PICKER_REQUEST = 1;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    findViewById(R.id.btn_place_picker).setOnClickListener(mOnClickListener);
  }

  View.OnClickListener mOnClickListener = new View.OnClickListener() {

    @Override
    public void onClick(View v) {
      switch (v.getId()) {
        case R.id.btn_place_picker:
          callPlacePicker();
          break;
      }
    }
  };

  void callPlacePicker() {
    PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
    try {
      startActivityForResult(builder.build(this), PLACE_PICKER_REQUEST);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == PLACE_PICKER_REQUEST) {
      if (resultCode == RESULT_OK) {
        Place place = PlacePicker.getPlace(data, this);
        String toastMsg = String.format("Place: %s", place.getName());
        Toast.makeText(this, toastMsg, Toast.LENGTH_LONG).show();
      }
    }
  }
}

Android Studio의 Debug에서의 SHA-1 값 알아내기

|

키스토어(Keystore)의 SHA-1 값이 필요한 경우가 있습니다. 보통 Keystore를 직접 생성하고 콘솔 창에서 SHA-1 값을 확인한다음 해당 Key를 이용해서 배포용 apk 파일을 만드는 경우가 많습니다.

하지만, 개발 도중에 Signed Key를 계속 적용하는 것은 번거롭기 때문에 여기서는 Android Sutdio에서 Debug 모드로 빌드할 때 사용하는 SHA-1 값을 확인하는 방법을 포스팅합니다.


Android Studio에서 SHA-1 값 확인하기

image -fullwidth

Android Studio 오른편에 보면 ‘Gradle’라는 버튼이 있습니다. Gradle 뷰안에서 SHA-1 값을 조회하기를 원하는 프로젝트를 선택한 다음 ‘Tasks → android → signingReport’를 더블 클릭하면 위 그림에서처럼 ‘Gradle Console’에 SHA-1 값이 출력됩니다.

Gradle 뷰 → 프로젝트 선택 → Tasks → android → signingReport


Google 공식 가이드

Google에서는 여기에서 디버그 모드에서의 SHA-1 값을 획득하는 방법을 잘 설명하고 있습니다.

디버그 모드에서 사용하는 Keystore는 debug.keystore 파일이며, 이 파일의 위치는

  • Windows : C:\Users\your_user_name.android\
  • MacOS 및 Linux : ~/.android/

입니다. 해당 위치로 이동하여 다음 커맨드를 입력하면 SHA-1 값을 획득할 수 있습니다.

  • Windows : keystore” -alias androiddebugkey -storepass android -keypass android
  • MacOS 및 Linux : keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android