레이블이 디버깅인 게시물을 표시합니다. 모든 게시물 표시
레이블이 디버깅인 게시물을 표시합니다. 모든 게시물 표시

2015년 7월 23일 목요일

아두이노등의 임베디드 시스템의 변수 값 오버플로우 문제(Variable overflow problem in embedded systems)

아두이노에서 10ms 마다 스위치 상태를 검사하는 코드를 작성한다고 해 보자.

void loop() {
  static unsigned long last = 0;
  static boolean swPrev = HIGH;
  unsigned long now;

  now = millis();
  if ((now-last)>=10) {
    digitalWrite(13,digitalRead(10));
    last = millis();
  }
}






위와 같은 코드가 될텐데 동작시켜 보면 생각한대로 아무 문제 없이 동작한다. 하지만 이 코드의 경우 치명적인 버그가 숨어있다. millis() 함수는 unsigned long 타입의 값을 리턴해 준다. unsigned long 타입의 경우 0 ~ 4,294,967,295 까지의 숫자가 들어갈 수 있다. 4,294,967,295 ms면 엄청나게 긴 시간(대략 50일)임에는 틀림없다. 하지만 임베디드 시스템의 경우 한번 켜 놓으면 전원을 끄지 않고 몇년씩 계속 동작시키는 경우도 흔하다. 위의 코드를 사용하면 처음부터 약 50일간은 문제없이 동작하지만 약 50일 정도가 지나 4,294,967,295 -> 0으로 변한 이후부터는 동작하지 않는 문제가 발생한다.

예를 들어 마지막으로 스위치를 감지했을 때 시간값이 last 변수에 들어가게 되는데 그 값이 4,294,967,290이었다고 해 보자. 그 때 부터 5 ms 후에는 4,294,967,295가 되고 다시 1ms가 더 지나면 4,294,967,256이 아니고 0이 되어 버린다. 즉 now-last 값이 6이 아니고 -4,294,967,290이 되어 버리는 것이다. 즉 그때 부터는 (now-last) > 10 이라는 조건이 만족될 수가 없게 되어 버리기 때문에 스위치가 눌렸는지 검사를 하지 않게 된다. 


이런 타입의 에러가 임베디드 시스템에서 아주 악성인 이유는 프로그램을 만들어 테스트 해 보면 정상적으로 잘 동작한다는 것이다. 그래서 제품을 출시해도 아무 문제가 없는데 출시 시작한지 약 두달 정도가 지나면 문제가 발견되기 시작한다는 것이다. 그리고 디버깅을 위해 버그를 재현하기 위해 개발실에서 다시 테스트 해 보면 전원을 넣은지 50일이 지나지 않았기 때문에 버그를 재현할 수 없다. 개발자가 버그를 재현할 수 없으면 버그의 원인을 찾기 힘들기 때문에 디버깅이 매우 어렵다. 

처음부터 이런 부분을 생각해 변수 값이 overflow 되는 경우에도 동작할 수 있도록 프로그램을 작성하는 연습을 해야만 한다.

위의 코드라면 이런식으로 수정해 주면 된다.

#define MAX 0xFFFFFFFF

boolean diff(unsigned long now, unsigned long prev, unsigned long d)
{
  if (now>prev) return ((now-prev)>=d);
  else return (((MAX-prev)+now+1)>=d);
}

void loop() {
  static unsigned long last = 0;
  static boolean swPrev = HIGH;
  unsigned long now;

  now = millis();
  if (diff(now, last, 10)) {
    digitalWrite(13,digitalRead(10));
    swPrev = LOW;
    last = millis();
  }

}


이제 now 변수의 값이 overflow 되어 0으로 돌아가도 정상적으로 비교되어 위에서 이야기 한 버그가 발생하지 않는걸 확인할 수 있다. 

2010년 8월 4일 수요일

안드로이드 서비스 디버깅 하기 (Attach debugger for service in Android)

서비스만 있는 어플리케이션을 디버깅 할 때 서비스에는 브레이크 포인트를 잡아 놔도 실제로 그 부분이 실행될 때 브레이크가 걸리지 않는다.




액티비티를 debug run 하면 어플리케이션이 시작될 때 화면에 Waiting for Debugger라는 메시지가 잠시 나타났다 사라지고 이클립스에 Debug perspective가 열리게 된다.


액티비티의 브레이크 포인트에서 실행이 멈추고 개발자의 다음 동작을 기다리게 된다.


하지만 서비스만 있는 어플리케이션의 경우 debug run을 해도 위와 같은 화면이 나오지도 않고 서비스에 잡아 놓은 브레이크 포인트는 멈추지 않고 그냥 지나쳐 버린다. 해결책은 명식적으로 서비스를 디버거에 붙이도록 선언해 주는 것이다. android.os.Debug.waitForDebugger(); 를 호출해 주면 된다.

SoftKeyboard 튜토리얼에 디버거를 사용하려면 아래와 같이 수정해주면 된다.

public class SoftKeyboard extends InputMethodService implements KeyboardView.OnKeyboardActionListener {
  @Override
  public void onConfigurationChanged(Configuration newConfig) {
    Log.d("SoftKeyboard", "onConfigurationChanged()");

    android.os.Debug.waitForDebugger();

    super.onConfigurationChanged(newConfig);

    // do something useful....

  }

onConfigurationChanged의 두번째 줄에서 서비스를 명시적으로 디버거에 연결시켜주기 때문에 브레이크 포인트에서 실행이 멈추게 된다.

* 어플리케이션에 액티비티와 서비스가 같이 들어있는 경우에는 액티비티가 먼저 실행되면서 디버거에 연결하기 때문에 서비스에 따로 waitForDebugger()를 호출하지 않아도 서비스에도 브레이크 포인트를 바로 사용할 수 있다.