Skip to content

Instantly share code, notes, and snippets.

@devbug
Last active August 29, 2015 14:05
Show Gist options
  • Save devbug/8bc45586d90f00706ef4 to your computer and use it in GitHub Desktop.
Save devbug/8bc45586d90f00706ef4 to your computer and use it in GitHub Desktop.
트윅으로 인한 크래쉬 발생 시 대처

트윅으로 인한 크래쉬 발생 시 대처

이 문서는 트윅으로 인한 크래쉬만 다룹니다.
내부 파일 변경 시 발생하는 무한 사과 등에 대해서는 다루지 않습니다.

만약 내부 파일 변경으로 인해 시스템이 멈췄으면 다 포기하고 아무 생각없이 DFU를 하시면 됩니다.
So easy.

  1. 상황 파악
    • 무한 사과
      • 무한 리스프링
      • 무한 재부팅
      • 그냥 암전
    • 특정 작업 시 안전모드
    • 특정 앱이 크래쉬
    • 특정 작업이 올바르게 작동하지 않을 시
  2. 일단 부팅
    • 그냥 부팅이 잘 된다면
    • 그냥 부팅이 안 될 경우
  3. 원인 찾기 1
    • 마지막으로 설치/업데이트한 트윅
    • 그런거 없을 때 (뜬금없이 발생)
  4. 원인 찾기 2
    • 원인을 찾지 못했을 때
      • 커뮤니티에 질문
    • 원인을 찾았을 때
      • 개발자에게 보고 및 도움 요청
  5. 문제 해결을 위해 알아두면 좋은 것들
    • 필요한 프로그램
      • CrashReporter + syslogd
    • Crash report 보는 방법
    • syslog?
    • 안전모드
      • CydiaSubstrate (MobileSubstrate)
      • Tweak
    • 개발자를 이해하기


간혹가다 중간 중간에 *이텔릭*으로 작성된 문구들이 있을 수 있습니다.
그 부분은 제가 개발자의 입장 혹은 관점에서 작성한 글이라고 생각하시면 됩니다.
제 사견들이 잔뜩 들어간 부분이라 생각하셔도 됩니다.

  1. 상황 파악 ==============

갑자기 문제가 발생했을 때.

어떤 문제가 발생했는지 파악이 필요함.

파악이고 자시고 아무 생각 없이 가장 쉽게 해결하는 방법은 DFU 하면 됩니다.
아는 여자 사람이 '이거 안 돼' 그러면 업체로 가라고 하면 됩니다.
So easy.

내 능력을 보여주겠어! 하며 헛된 짓을 해봤자 돌아오는 건 욕 뿐입니다.
마음을 비우세요.

하지만 내 소중한 데이터들을 잃을 순 없다는 간절하신 분들은 저런 선택이 불가능합니다.
이런 분들은 아래 내용을 보시기 바랍니다.

그냥 커뮤니티에 물어볼래!


네, 그러셔도 됩니다.
그런 분들은 이 글을 보지 마시기 바랍니다.


무한 사과


갑자기 무한 사과가 발생할 수 있죠.
당장은 이유를 모릅니다.

무한 사과란, 부팅 시 보이는 애플 마크가 부팅이 정상 종료되지 않아 끊임없이 보이는 겁니다.
영어로는 bootloop, 일본어로는 リンゴループ 정도로 부릅니다.

근데 사실 무한 사과도 종류가 있습니다.

  • 무한 리스프링
  • 무한 재부팅


이렇게 두가지가 있죠.

어라 이게 무슨 차이지? 싶겠습니다만, 전혀 다릅니다.

무한 리스프링


iOS 5 이전까지 iOS의 겉껍데기 및 각종 통로 역할을 하던 프로그램이 있습니다.
바로 SpringBoard 입니다.
트윅을 설치하고 나면 나오는 Respring이라는 말이 바로 이 SpringBoard를 재시작(restart) 하겠다는 의미이죠.

그리고 iOS 5 이후부터 시스템과 SpringBoard 사이에 backboardd라는 녀석이 하나 더 추가됐습니다.
이후 리스프링은 backboardd를 재시작하는 것과 SpringBoard를 재시작하는 두가지가 생기게 되었죠.
좀 아는 개발자들은 backboardd를 재시작하는데 멋도 모르는 개발자들은 SpringBoard만 재시작합니다.
..는 사실 필요하지 않다면 굳이 backboardd를 재시작 안 할 수도 있긴 하죠.

자 아무튼 리스프링은 저런 겁니다.
무한 리스프링은 그럼 당연히 SpringBoard가 계속 뜨다가 죽고 뜨다가 죽고 하는거겠죠?

하지만 iOS 5 이후로 추가된 backboardd로 인해서, SpringBoard가 아니라 backboardd가 뜨다가 죽고 뜨다가 죽고 해도 비슷한 증상이 나타납니다.
이건 사용자분들이 그냥 봐서는 구분하기 힘듭니다.
저도 그냥 보면 구분 못 합니다.
명백하게 차이가 나는 상황이라면 알 수도 있겠지만 대다수의 상황에서는 알 수 없습니다.

그럼 어떻게 구분하느냐?
로그를 보면 알 수 있습니다.
로그는 어떻게 보느냐?
Xcode를 이용하면 쉽게 볼 수 있습니다.
Xcode를 이용하려면 어떻게 하느냐?
맥이 있고 그 맥에 Xcode를 설치하고, 기기를 USB로 맥에 연결하면 됩니다.
참 쉽죠?

즉 보통의 경우 저 로그는 볼 수 없습니다.
맥을 가진 사용자가 얼마나 될지는 안 봐도 뻔하죠.

아무튼 가장 중요한건 안전모드로 진입이 가능한 에러라는 점입니다.

무한 재부팅


부팅을 할때 여러 과정을 거칩니다.
전부 생략하고 필요한 것만 간단하게 써보면,

간소한 부팅 과정
부트로더 진입 = 사과마크 뜸
커널 읽기
완탈툴 실행
CydiaSubstrate를 붙여 각종 데몬 실행
대충 이즈음 사과 마크 없앰
그러던 중 backboardd 데몬 실행
SpringBoard 실행 = 사과 마크 뜸
사과 마크 없앰 = 앱들의 정보 로드
자동 실행 앱 실행
락스크린 뜸


진짜 대충 적어본거니까 대충 이런식이라는 것만 보도록 합시다.

즉, `backboardd`나 `SpringBoard`를 실행하던 중 크래쉬가 나면 무한 리스프링에 걸리게 되는 것입니다.

결국 무한 재부팅은 `backboardd`가 뜨기 전에 크래쉬가 나는 경우를 의미하죠.
이 경우 재수없으면 안전모드로 진입이 안 됩니다.
근데 트윅으로 이런 경우는 거의 발생하지 않습니다.

이런 경우가 발생하는 거는 보통 폰트와 같은 시스템 파일을 무분별하게 수정하다 잘못하였을 때 발생합니다.
진짜 이 경우는 답이 없어요. DFU ㄱㄱ

다른 경우라면 탈옥툴이 잘못된 경우입니다.
네 이 경우도 답이 없습니다.

사실.. 무한 재부팅에 걸리면 답이 없습니다.
안전모드로 진입하려면 일단 `CydiaSubstrate`가 로드가 되어야하는데 이게 로드되기 전에 크래쉬가 나기 때문에 답이 없는거죠.
`CydiaSubstrate`가 로드 됐는데 크래쉬가 난다는게 바로 트윅으로 인해 크래쉬가 난다는 의미거든요.
반면 `CydiaSubstrate`가 로드 안 됐는데 크래쉬가 난다면 트윅으로 인한 크래쉬는 절대 아니라는 의미죠.
그래서 답이 없습니다.
무슨 짓을 했는지는 몰라도 아무생각 없이 다 포기하고 DFU 하시면 됩니다.

##### 그냥 암전


무슨 짓을 하신건가요?
라는 의문을 먼저 던져 볼 필요가 있겠죠.

커널이 로드되기도 전에 멈춰버리는 현상입니다.
부트로더도 여러 단계가 있는데 거기 어딘가에서 멈추는거죠.

보통 칩이 나갔을 때 이런 증상이 나타납니다.
운이 좋으면 화면만 나간 걸지도 모르구요.

DFU 복원을 해본 뒤 복원조차 안 된다, 센터로 가시면 됩니다.
돈이 얼마나 깨질지는 저도 잘 모르겠군요.


특정 작업 시 안전모드


차라리 이렇게 안전모드로 빠지면 별로 걱정 안 해도 됩니다.

간단하게 리스타트 해서 리스프링이 정상적으로 되는지 일단 봅니다.

마지막에 내가 무슨 짓을 했는지 곰곰히 생각을 해보도록 합니다.
그리고 그걸 반복적으로 실행해보도록 하죠.

다시 안전모드에 빠지면 좀 더 단계를 단순화해보며 정확하게 크래쉬가 나는 원인이 되는 행동을 찾아내 봅니다.

이를테면

  1. 리스프링 한 후 설정 앱을 실행
  2. 홈 버튼을 눌러 홈화면으로 나간 뒤 앱스토어 실행
  3. 홈 버튼을 두번 눌러 앱스위처를 띄우고 설정 앱을 탭
  4. 홈 버튼을 두번 눌러 앱스위처를 띄우고 설정 앱을 종료하자마자 앱스토어 앱을 탭
  5. 안전모드


이런 현상을 발견했다고 칩니다.
이 과정을 단순화하기 위해 한 단계를 빼보죠.
3번 과정을 빼봅시다. 자연스럽게 4번 과정도 바뀌겠군요.

  1. 리스프링 후 설정 앱 실행
  2. 홈 버튼 눌러 홈화면으로 나간 뒤 앱스토어 실행
  3. 홈 버튼 두번 눌러 앱스위처를 띄우고 앱스토어 앱 종료하자마자 설정 앱을 탭
  4. 안전모드


이렇게 한단계가 줄었습니다.
이런 식으로 키가 되는 단계를 찾아보면 됩니다.
이 과정은 추후 개발자에게 보고할 때 재현할 수 있는 단서가 됩니다.

종료 하자마자라는 언급 또한 매우 중요합니다.
이 타이밍이 중요하다고 언급을 해야하는지 말아야하는지도 확인을 해봐야겠죠.
3번 과정에서 앱스토어 앱 종료하고 잠시 기다렸다 설정 앱을 탭해서 크래쉬가 나는지 살펴보면 됩니다.

이렇게 단서를 하나씩 찾아갑니다.

또한, 안전모드로 빠졌다는 것은 크래쉬가 났다는 것이고, 당연히 크래쉬 리포트 역시 남겨졌다는 의미입니다.
이걸 먼저 살펴보는 것도 많은 도움이 될 것입니다.

다른 예를 들어보죠.

  1. 리스프링 후 설정 앱을 실행
  2. Background Manager의 액티베이터 액션 실행
  3. 안전모드


너무도 명백해보이네요.
Activator와 Background Manager 둘 중 하나겠죠.
Activator의 다른 액션을 실행해서 문제가 있는지 살펴보고 문제가 없으면 BM을 보도록 합니다.

이런 경우는 BM은 BM의 설정을 바꿔가면서 어떤 설정일 때 크래쉬가 나는지 살펴보면 됩니다.
그리고 이 경우는 정확하게 어떤 경우에서든지 간에 무조건 크래쉬가 날겁니다.
아무튼 크래쉬가 났으므로 크래쉬 리포트가 생성 됐을테니까 그걸 잘 갖고 있어야 해요.

여러분이 CrashReportersyslogd를 함께 설치해뒀다면 얘들이 자동으로 데이터를 모아서 처리해줍니다.
설치해두지 않았다면 얘네를 꼭 설치한 뒤에 다시 크래쉬를 내도록 하세요.
그래야 유의미한 데이터가 수집됩니다.


특정 앱이 크래쉬


어떤 앱이 어떤 상황에서 어떻게 하니까 크래쉬가 났는지만 살펴보면 됩니다.

또한 위에서 예를 들었듯이 그처럼 단계를 줄여나가며 살펴보면 됩니다.


특정 작업이 올바르게 작동하지 않을 시


똑같습니다.
어려울 거 하나도 없어요... 아니 있을지도..

어떤 작업이 어떤 상황에서 어떻게 하니까 기대하던 작동과 다른 어떤 결과를 내는지 살펴보면 됩니다.
만약 여기서 크래쉬가 난다면 바로 위 항목과 완전 동일해지죠.
backboarddSpringBoard가 크래쉬 난다면 위위 항목과 동일해지구요. 안전모드에 빠질테니까.

어쨌건 이것도 예를 들어보죠.

  1. 홈 화면에서 뒹굴 뒹굴 구르고 있습니다.
  2. 갑자기 전화가 옵니다.
  3. 전화를 받았습니다.
  4. 근데 받은 전화 화면이 이상합니다. 막 투명하고 홈 화면과 배경화면이 다 보입니다.


이런 경우는 참 난감합니다.
크래쉬가 안 나서 힌트가 없거든요.

이럴 때엔 일단 리스프링이나 재부팅을 해서 깨끗한 상황을 만듭니다.
다른 전화로 역시 홈화면에 있을 때 전화를 걸어봅니다.
같은 상황이 발생하는지 봅니다.

다음은 잠금화면에서 어떻게 되는지 살펴봅니다.
그리고 그 다음은 아예 화면을 꺼둔 상태에서 어떻게 되는지 역시 살펴보구요.
여기선 홈화면에서만 이 증상이 나타났다고 하도록 하죠.

자 일단 여기까지 살펴봅니다.


아무리해도 모르겠을 때


정말 답이 없죠.
분명 똑같은 상황에 똑같은 행동을 했는데 재현이 안 될때가 많습니다.
뭔가 내부적인 상황이 다른거라던가, 아무튼 분명 뭔가 다른건데 티가 안 나서 도저히 찾을 수가 없는겁니다.
이 경우는 정말 어쩔 수가 없습니다.
다음 번에 같은 증상이 나타날 때를 찾아야합니다.
크래쉬가 났다면 크래쉬 리포트가 남기 때문에 한결 수월하지만 그것조차 없으면 다음번에 우연찮게 다시 발생하길 바랄 수 밖에 없습니다.
아님 아예 그대로 그 증상이 사라지면 더 좋구요.

  1. 일단 부팅 =================

증상이 어떤건지 파악은 대충 끝냈습니다.
확신은 못하지만 아무래도 가능성이 높은 상태이죠.

무한 재부팅이 아닌 상태라면 일단 정상 부팅 상태를 만들어야 합니다.
이전 과정에서 여러번 했을 수도 있습니다.
이전 과정에서 재부팅을 시도했으나 무한 리스프링 상태에서 빠져나올 수 없었을 수도 있구요.

그냥 부팅이 잘 된다면


걱정하지 마세요.
그냥 넘어가시면 됩니다.
다음 파일을 보시기 바랍니다.

그냥 부팅이 안 될 경우


이전 파일에서 적었듯이 무한 재부팅이 아니라 무한 리스프링일 경우입니다.
안전모드로 들어가도록 해야겠죠?

안전모드에 대한 설명은 나중에 따로 하도록 하겠습니다.
지금은 일단 부팅이 되도록 하는게 우선이니까요.

강제 재부팅은 이렇게 하시면 됩니다.

홈버튼 + 전원버튼을 최소 7초 이상 눌러줍니다.
화면이 까맣게 나갔으면 두 버튼에서 모두 손을 땝니다.


이게 강제 재부팅 방법이구요.

이대로 두면 다시 무한 사과에 빠지겠죠?
그냥 간단하게 다시 켜진 직후부터 볼륨 버튼 중 + 버튼을 누르고 계세요.
제대로 켜질 때 까지 계속 누르고 있으면 됩니다.

네, 축하합니다. 무사히 안전모드 진입에 성공하였습니다.

안전모드 부팅에 실패할 경우


이럴 수도 있죠.
그냥 계속 무한 사과에 빠지는 겁니다.

안타깝지만 복원을 고려해보시는게 좋을거 같습니다.

OpenSSHafc2를 설치했다면 무한 사과 중에도 접근이 가능할 수 있습니다.

/Library/MobileSubstrate/DynamicLibraries


접근 가능하다면 위 폴더를 통째로 이름을 바꿔버리고 기다려보세요.
트윅으로 인한 문제가 맞다면 정상 부팅이 가능할 수도 있습니다.

OpenSSH도, afc2도 안 깔았다..
이젠 복원 말곤 답이 없습니다.

  1. 원인 찾기 1 =================

부팅이 되어서 이제 다시 시스템을 자유롭게 만질 수 있게 되었습니다.
시디아도 켜지고 말이죠!

이제 본격적으로 원인 탐색을 시작해보도록 하자구요.


앞서 사용했던 예제들을 재활용하도록 하죠.

  1. 리스프링 후 설정 앱 실행
  2. 홈 버튼 눌러 홈화면으로 나간 뒤 앱스토어 실행
  3. 홈 버튼 두번 눌러 앱스위처를 띄우고 앱스토어 앱 종료하자마자 설정 앱을 탭
  4. 안전모드


우선 언제부터 이런 증상이 나타났는지 기억을 반추해보도록 합시다.
.......
기억이 날 리가 없습니다.
뜬금없이 발생했고 발견한 현상이니까요.
기존부터 있던 버그 일지도 모릅니다.
어쩔 수 없죠. 다음 단계로 넘어갑니다.

최근에 설치한 혹은 업데이트 한 트윅들을 살펴봅니다.
시디아도 한 100만년만에 한번 켜볼까 말까해서 최근에 업데이트 한게 없군요.
역시 넘어가도록 합니다.

안전모드에 빠졌다는건 크래쉬가 났다는거고 즉 크래쉬 리포트가 남겨져 있다는 거군요.
크래쉬 리포트를 살펴보도록 합시다.

그 전에 우리는 CrashReportersyslogd를 먼저 반드시 설치해둬야 합니다.
만약 설치해두지 않았다면 먼저 설치를 하도록 하세요.
자 그리고 다시 크래쉬를 내도록 합니다.

CrashReporter가 푸시알림을 보내는군요?

[그림 1]그림 1
[그림 2]그림 2
[그림 3]그림 3

요즘은 세상이 참 좋아져서 이렇게 보기 편하게 잘 나옵니다.
ashikase 씨가 참 부지런해요.


근데 여러분은 이걸 봐도 뭔 소린지 이해가 안 갈겁니다.
이해 하시는 분도 물론 있을텐데 극소수잖아요. 넘어갑시다.

중요한건 아무튼 CrashReporter를 보니 Main suspect(주 의심 항목)에 SwitcherM.dylib라는 녀석이 보인다는 겁니다.
그걸 눌러보니 아래처럼 나오는군요.

[그림 4]그림 4

어떤 트윅인지 이름과 함께 할 수 있는 행동들이 보이는군요.
Contact author를 누르면 제작자에게 메일을 보낼 수 있습니다.
이걸 통해 메일을 보내시면 현재 크래쉬 리포트, syslog, UDID를 보낼 수 있습니다.
이걸 이용해서 메일을 보내도록 합시다.

이제 개발자에게 바통이 넘겨졌습니다.
여러분들은 개발자의 답변을 기다리시면 됩니다.

좀 부지런하신 분들은 증상이 나타나는 과정을 스크린샷으로 찍어서 첨부해도 되고, 그게 아니면 동영상 촬영을 한 뒤에 첨부해서 보내주셔도 됩니다.
이런 추가 자료들은 개발자들이 상황 파악 하는데에 큰 도움이 됩니다.

참고로 이 경우엔 syslog가 약간 도움이 됩니다.
하도 개발자들이 crash report만 부르짖어 놓으니 사용자분들이 crash report만 달랑 보내고 syslog는 빼는 경우가 많은데요.
절대 그러지 마세요.
정작 crash report는 전혀 도움이 안 되고 syslog만으로 해결이 가능한 경우도 많습니다.
둘 다 있으면 시너지 효과까지!
잊지 말고 꼭 다 보내주시기 바랍니다.

UDID를 전송하길 꺼려하시는 분들도 많으신데요.
개발자들이 그 UDID를 어따 팔아먹는 것도 아니고 갖고 있어봤자 하등의 이익이 없습니다.
근데 왜 UDID를 요구하느냐..

이는 정품 사용자인지 여부 파악을 위해 주로 쓰입니다.
모든 개발자들이 그러는건 아니겠지만 저 같은 경우 유료 트윅에 UDID만 쏙 지우고 메일을 보내면 바로 휴지통으로 보내버립니다.
내용 안 봅니다.
99%는 그렇게 처리하고 1% 가끔 어쩌다 마음이 동할때가 있습니다.
그날 왠지 기분이 좋아서 읽어서 답장까지 보낼 때가 가뭄에 콩나듯 있는데 보통은 안 그래요.

그리고 많은 개발자들이 비정품 사용자의 의견을 받아주지 않습니다.
쓸데없는 힘 빼지 마시고 정품 구매하신 뒤에 의견을 보내시기 바랍니다.

그리고 Cydia로 메일을 보낼 때 UDID 바로 앞에 해당 기기의 종류와 iOS 버전도 적혀있는데 그거까지 같이 지워서 보내는 분들도 있습니다.
역시 지웁니다.
기기의 종류와 iOS 버전도 매우 중요한 정보입니다.
그런거 지워서 보내면 다시 답장으로 그거 적어달라고 보내야하고, 아주 속터집니다.
그나마 crash report를 보내셨다면 거기에 그게 적혀있으니 괜찮긴 한데, 일부러 지우는 만행은 저지르지 마시길 바랍니다.


다른 예제를 살펴보도록 하죠.

  1. 리스프링 후 설정 앱을 실행
  2. Background Manager의 액티베이터 액션 실행
  3. 안전모드


어떤 앱을 실행하든지 간에, 어떻게 설정을 하든지 간에 저 현상이 나타나고 있다고 칩니다.
대충 봐도 Background Manager의 문제라고 밖에 생각되지 않습니다.
실제로도 그렇고 말이죠.
다른 Activator 액션은 정상 실행 되거든요.

앞서 했듯이 언제부터 이런 증상이 나타났는지 기억을 반추해보도록 합시다.
아무래도 BM의 최신버전 업데이트를 한 이후부터 인거 같군요.
1.0.1을 설치한거 같습니다.
Activator는 아주 오래 전에 업데이트를 했지요.

Cydia를 켜봅니다.
설치 완료 -> 최근을 눌러보면 가장 최근 설치한 것부터 차례대로 나타납니다.

[그림 5]그림 5

역시 BM 밖에 안 보이네요.
대충 봐서 생각한 BM이 결정적인 원인이 맞는거 같습니다.

아무튼 크래쉬가 났으니 리포트가 남았습니다.
이런 syslog가 안 잡혔군요.

[그림 6]그림 6
[그림 7]그림 7

이 크래쉬는 무조건 syslog가 있어야 정확한 원인을 알 수 있습니다.
그 정확한 원인이 syslog에만 찍히고 crash report에는 대략적인 이유만 나오거든요.

뭐 전 이미 아니까 상관없지만 개발자에게 보낼 때 개발자는 syslog를 다시 요구하는 메일을 보낼겁니다.
미리미리 준비해두도록 합시다.

자 아무튼 준비된 리포트를 아까처럼 개발자에게 보내도록 합니다.
아 근데 좀 꺼려지는게 UDID가 적혀있어요.
왜 꺼려지냐구요?
사실 이 Background Manager는 insanelyi에서 설치한 크랙버전이거든요.
그래서 UDID를 지워서 보냈습니다.
(어머 스샷엔 빅보스판이 설치되어 있네요. 하하하..)
(전 크랙 소스 추가하고 해서 제 기기 더럽히는 행위를 매우 혐오하거든요.)


이 이메일을 받은 제가 하필 이날 100만년에 한번쯤 갑자기 기분이 좋아져서 답장을 보냈습니다.
UDID 내놔 라고 말이죠.

그래서 보내줬더니 이후로 답장이 없습니다.
아 욕나오네요.

그리고 메일을 받은 저도 욕 나옵니다. ㅎㅎ
크랙판을 쓰고 있다면 먼저 크랙 때문인지 의심을 해보세요.
그 트윅의 버그인지부터 생각해보는건 바보짓입니다.

이 트윅을 구매하기 전에 정상 작동하는지 확인하고 싶어서 크랙판을 설치해본다는 사람이 많아요.
멍청한 짓입니다.
크랙 대책이 된 트윅은 이유도 모른채 최신 펌웨어를 지원하지 않는다며 욕을 쳐먹죠.
멍청한 사용자 탓인데 말이죠.

암튼 이걸로 이 문제도 완벽 해결!


다른 예제를 또 보도록 합니다.

  1. 홈 화면에서 뒹굴 뒹굴 구르고 있습니다.
  2. 갑자기 전화가 옵니다.
  3. 전화를 받았습니다.
  4. 근데 받은 전화 화면이 이상합니다. 막 투명하고 홈 화면과 배경화면이 다 보입니다.


우선 언제부터 이런 증상이 나타났는지 기억을 반추해보도록 합시다.
100만년에 한번씩 전화가 와서 도저히 모르겠는데.. 하실 저같은 분들도 있을 겁니다.
어쩔 수 없죠. 다음 단계로 넘어갑니다.

최근에 설치한 혹은 업데이트 한 트윅들을 살펴봅니다.
BM 말곤 없군요.
또 너냐..

구버전 설치가 가능하다면 구버전을 설치해서 비교를 해보고 싶은데 유료 트윅이라 안 됩니다.
뭐 같지요.
개발자에게 구 버전 deb를 요구하지만 씹혔습니다.
네 전 씹습니다.

귀찮으니 BM을 지운 뒤에 테스트를 해봅니다.
허 잘 되네요.

BM이 원인이 맞는거 같습니다.

증상이 나타난 화면을 스크린샷으로 찍습니다.
설명하기가 힘들잖아요?

한국인 개발자라 편합니다. 영어로 메일 안 보내도 돼요!

deVbug는 한국인입니다. 영어로 좀 보내지 마세요 한국인분들.. ;ㅁ;..

Cydia를 킵니다.
Background Manager를 찾아갑니다.
제작자를 눌러보세요.
Author에 보면 개발자의 닉네임이 보입니다.
그게 개발자에게 메일을 보낼 수 있는 버튼입니다.
그걸 누르세요!

증상을 재현할 수 있는 방법과 아까 찍은 스크린샷을 첨부합니다.

미리 첨부되어 있는 파일 2개와 UDID, 기기 종류 및 iOS 버전을 절대 지우지 마세요.
이전에 적었지만 전 UDID 지워져 있으면 그냥 메일 삭제합니다.

다른 개발자들도 썩 좋은 기분으로 보진 않습니다.

답장을 기다리도록 합니다.

개발자가 트위터도 하면 트위터로도 연락을 해보세요.
저같은 잉여는 메일보다 빠르게 답변할 수도 있어요.

네 그리고 증상 확인 후 고치겠다는 답장을 받습니다.

よし 이걸로 이것도 해결.


마지막 예제를 살펴보도록 하죠.

  1. iPad에 iOS 7.1.2를 업데이트 하고 Pangu로 탈옥을 했어요.
  2. RetinaPad, iFile에 Auxo 2도 깔고 CCToggles도 깔았어요.
  3. 아고 힘들어라, 이것저것 하고 CCToggles로 리스프링을 했어요.
  4. 어라 왜 안전모드지?


늘 했듯이 살펴보도록 하죠.
자 일단 정상 리스프링이 되는지 살펴봅니다.
잘 되네요.

다시 CCToggles로 리스프링을 해봅니다.
그리고 안전모드..

다른 리스프링 수단이 없어요.
재부팅을 해보도록 하죠.
어라 재부팅도 아니고 그냥 안전모드.
어..어라라?

아 생각해보니 Flipswitch의 리스프링이 있었어요.
안전모드.

다른 토글도 눌러보니 안전모드.

왠지 CCToggles가 7.1.2를 지원하지 않는거 같아요.

근데 내 7.1.2가 설치된 아이폰에서는 아무 문제도 없어요.
7.1.2가 설치된 아이패드에서만 발생하나봐요.

일단 대충 파악했으니 다음 단계로 가죠.

언제부터.. 음.. 탈옥을 방금 전에 했으니 방금 전부터네요.

제일 최근에 설치하거나 업데이트 한건..
역시 방금 전에 탈옥해서 한참 이것저것 까는 중이라 알 리가 없습니다.
답이 없군요.

안전모드니까, 믿을건 크래쉬 리포트 뿐이군요.
이..이런 젠장 CrashReporter를 아직 안 깔았어요.
깔도록 합니다.

다시 리스프링을 해서 리포트를 얻어냅니다.
syslog도 반드시 있어야겠죠?

따로 스크린샷은 안 둘게요.
제 아이패드는 7.0.4거든요.

아무튼 얻어내어 보니 역시 CCToggles와, Auxo 2가 의심되는군요?
잘 모르겠으니 CCToggles의 개발자에게 리포트와 로그를 첨부하여 메일을 보내봅니다.

제가 그걸 받아 보고 '아 또 왔냐..'라는 장탄식과 함께 내용을 읽지도 않고 답장을 씁니다.

Auxo 2를 지우고 다시 해보세요.라고 말이죠.

Auxo 2를 지우고 다시 해보니까 잘 되네요.

Auxo 2가 7.1.x가 설치된 iPad를 아직 지원하지 않아서 발생한 일이었어요.
이런, 제가 아직 내공이 딸려서 미처 파악하지 못했던거군요.
왠지 CCToggles 개발자에게 미안해요.

아무튼 해결을 위해 Auxo 2 개발자에게 메일을 보내도록 합시다.

"iPad에서 CCToggles와 호환이 안 돼요!"

이렇게 보내시면 절대 안 됩니다.
사실이 아니잖아요?

"7.1.2를 설치한 iPad에서 아무 토글을 눌렀더니 안전모드에 빠집니다."
혹은
"7.1.2를 설치한 iPad에 CCToggles와 함께 사용 중인데 아무 토글을 눌렀더니 안전모드에 빠집니다."
라고 보내셔야 합니다.

휴우.. 큰 실수를 저지를뻔 했네요!

이제 크래쉬 리포트와 syslog 등을 첨부해서 메일을 전송하시면 됩니다.
처음부터 CrashReporter를 이용했다면 이미 첨부되어 있겠네요.


이 예제에서 우리가 빼먹었던 과정을 알아보죠.

Auxo 2를 지워서 해봤더니 잘 됐죠?
그럼 이제 반대로 Auxo 2를 설치하고 CCToggles를 지운 뒤에 다른 토글들을 탭했을 때 어떻게 작동하는지도 살펴봤어야 합니다.
이게 Auxo 2가 CCToggles와 상호작용해서 발생한 일인지, Auxo 2 혼자서 자살한 것인지 알아볼 필요가 있었던 것이죠.

그리고 그 전에 먼저 "잘 모르겠으니"라고 하고 메일부터 보낼게 아니라, CCToggles가 의심이 됐으면 이걸 지운 다음에 테스트를 해봤어야 했죠.

  1. 원인 찾기 2 ================

앞서 예제를 들어가며 해결을 시도해보았습니다.
요번엔 다른 관점으로 살펴보도록 하죠.

원인을 찾지 못했을 때


CrashReporter를 살펴 봤는데 main suspect가 비었어요.
그래서 crash report를 직접 봤는데 욕 나와요.
대체 이게 어느나라 말?

상황은 대충 파악했는데 어떤 놈이 주적인지 판단을 못해서 대책이 안 서요.

이럴 땐 커뮤니티를 이용하세요.

상황을 또 예를 들어보죠.

  1. 최근 이것저것 설치 및 업데이트하며 리스프링을 했어요.
  2. 어.. 아무 앱이나 실행하고 홈버튼 눌러 나갔더니 모든 앱이 다 종료되어 버렸어요.


아무리 생각해도 원인을 알 수 없어요.

최근 설치/업데이트 한거는 한두개가 아니에요.
나오는거 다 깔아보고 있거든요.
유료는 크랙 쓰고 말이죠.
xxx이 있으면 웬만한건 다 돼요!

근데 왜 이럴까요..

결국 커뮤니티의 힘을 빌리기로 했어요.
근데 요즘 xxx라던가 크랙 언급하면 온통 욕 바가지로 먹는 선비집단소가 되어서 마음놓고 쓸데가 디씨나 일베 정도 밖에 없어요.

그래서 써봤더니 너나할거 없이 다 같은 증상이래요.
대체 원인이 뭘까요?

크래쉬 리포트도 혹시나 살펴봤는데 남은게 없어요.
망했어요..

결국 하나씩 지워가며 직접 찾아보기로 했어요.

범인은 Background Manager 였어요.

어라 이상하다.. 분명 xxx 먹여서 꿀 빨던건데..

최신 버전도 안 되고 좀 낮은 버전 깔아도 안 되고 온갖 크랙판 동원해봤는데 어떤건 아예 무한 사과가 됐어요.
이게 대체 무슨 일인지.
돈독이 올랐나봐요. 미친놈.
0.99 달러짜리 팔아서 무슨 영광을 보겠다고 이 지랄인지 모르겠어요.

외국 사이트 뒤져봤는데 전부 패닉 상태예요.
0.9-26으로 내렸어요.
아 새 뱃지 쓰고 싶은데..

이걸로 해결!

참고로 적지만, 1200원을 훔치는 행위나 1200억을 훔치는 행위나 똑같은 절도입니다.
만약 규모가 다르니 행위조차 다르다고 생각하신다면, 어느 초등학교 재학 중인지 감히 묻고 싶군요.


그리고 크랙 쓰시는 분들, 잘 되던게 갑자기 안 되고 그러면 참 짜증나죠?
그리고 크랙이라고 배척당하고 싫은 소리 들으니 짜증나죠?
그 싫은 소리가 심지어 거칠고 막 그러면 돌아버리겠죠?
그래서 정상적인 분들을 선비라고 비꼬고 하니 기분 참 좋으시겠어요?
ㅎㅎㅎ



원인을 찾았을 때


원인을 찾았을 때는 간단해요.
이미 설명을 잔뜩 해왔잖아요?
그대로 하면 됩니다.

정보를 최대한 얻고 그걸 원인 트윅 개발자에게 통째로 다 보내버리면 되는거예요!

그럼 개발자가 그걸 보고 '내 탓이오'하고 해결 약속을 하든 예정을 잡든 어떻게든 할거예요.
만약 아니라면 다른 의심되는 녀석을 대신 알려줄 겁니다.


개발자에게 보고를 할때엔 다음 내용을 반드시 함께 첨부해주세요.

  • Device 종류 및 iOS 버전
  • UDID
  • cydia log
  • dpkg log
  • crash report (만약 있다면)
  • syslog (만약 있다면)
  • 상황 설명 및 재현 방법
  • 테스트 해본 방법 및 기타 개발자에게 알려야 할만하다 싶은 사항 전부
  • 가능하다면 스크린샷이나 동영상으로 상황 설명


저 정보들을 어디서 얻을 수 있는지 알려드릴게요.

항목 위치
Device 종류 Cydia & CrashReporter
iOS 버전 Cydia & CrashReporter
UDID Cydia & CrashReporter
cydia log Cydia
dpkg log Cydia & CrashReporter
crash report CrashReporter
syslog CrashReporter


즉 크래쉬 리포트가 있다면 CrashReporter로 메일 한번 보내면 되는거고, 크래쉬 리포트가 없다면 Cydia로 메일 한번 보내면 모든게 해결됩니다.

그리고 여러분들이 별거 아니다 싶은 정보가 어쩌면 키포인트가 될 수도 있어요.
그게 뭐일지는 저도 모릅니다.
그때그때 달라질 거니까요.
의심된다 싶은건 다 적어 보내주시면 돼요.

제일 좋은 건 깨끗한 기기에서 작동하는 완벽한 재현 방법을 서술해주시는 겁니다.
동영상과 함께 보내어진 설명은 정말 강력한 힘을 발휘합니다.

근데 기껏 열심히 작성해서 보냈는데 개발자가 재현 안 됨이라 답변을 보내오면 참 허탈해요.
하지만 이것도 어쩔 수가 없어요.
정말 재현이 안 되는걸요.
죽어도 안 됩니다.
그런 경우가 정말 엄청나게 많아요.
사용자들은 미치겠다고 아우성인데 제 기기들에선 멀쩡합니다.

안된다고 하시는 분들의 기기를 보면 온갖 크랙 저장소에 크랙판 트윅들이 수십개가 설치되어 있는 경우가 많아요.

그런 메일 정말 많이 받는데, 네 제 프로그램 사주셔서 감사해요.
하지만 이런 상황에서는 저도 어쩔 수가 없어요.

이에 대한 자세한 이야기는 나중에 다시 적도록 하죠.

  1. 문제 해결을 위해 알아두면 좋은 것들 =============================

사실 여태 적은게 문제 해결을 위해 알아두면 좋은 것들이죠.
이 글에선 좀 더 쓸데없이 상세한 이야기들을 해볼까 합니다.

CrashReporter 라던가 거기서 볼 수 있는 crash report, syslog를 보는 방법.
안전모드에 대한 설명
개발자가 어떤 생물이고 어떻게 하면 개발자에게 문제 해결을 위한 유익한 정보를 줄 수 있는지..
뭐 그런 것들을 적으려 합니다.

앞에 두가지는 사실 딱히 직접 알지 못해도 상관은 없어요.
ashikase 씨가 열심히 한 덕택에 CrashReportermain suspect를 (정확하진 않지만) 아무튼 알려주긴 하기에 정확하게 crash report를 보는 방법을 알 필요가 없어지긴 했거든요.
안전모드에 대한 것 역시 사실 별로 필요는 없을거 같습니다.

하지만 마지막 정보는 문제 해결에 즉각 도움을 청하고 완벽하게 받을 수 있는 유일한 존재라는 점에서 알아두셨으면 좋을 거 같습니다.


필요한 프로그램


을 설명하기에 앞서 먼저 crash report와 syslog가 뭔지부터 써보도록 하죠.

iOS는 시스템에 기본적으로 크래쉬 발생 시 리포트가 작성되게 되어있습니다.
iOS 뿐만 아니라 대다수의 OS가 그와 같은 시스템을 갖고 있죠.

크래쉬가 났을 때 바로 그때의 CPU 상황, 크래쉬 난 프로세스의 상황 등이 적혀 있어서 크래쉬 난 순간을 다시 재현하는 데에 도움을 주도록 하는 정보를 담고 있는 파일이 바로 crash report 입니다.

그리고 여러분은 이것에 이미 엄청나게 익숙합니다.
어째서냐구요?

죽음의 블루스크린.

많이들 경험하셨을텐데 그게 바로 크래쉬 리포트입니다.
시스템에 치명적인 문제가 발생하여 크래쉬가 났고, 그래서 그 상황에 대한 에러코드와 함께 레지스터, 스택 정보 등을 담아 뒀던 화면이었죠.
문제는 갑자기 하던게 먹통되고, 시퍼런 화면에 알아먹지 못할 글자가 잔뜩 적혀있었기 때문에 일반 유저에게 그저 공포스러웠을 뿐.

iOS의 크래쉬 리포트는 그저 흰 화면에 검은 글씨라는 점이 다를 뿐, 유저 입장에선 블루스크린과 하등의 차이가 없죠.

syslog는 시스템 로그를 의미합니다.
역시 OS에 본래 기본적으로 담겨 있는 것이고 syslogd를 설치하지 않아도 Xcode를 통해 직접 접근하여 확인할 수 있습니다.

디버그 정보를 바로 확인할 수 있는 개발자에게 정말 소중한 자원이죠.


이런 정보를 좀 더 유의미한 데이터로 변환하여 바로 확인, 리포팅 할 수 있게 도와주는 툴이 있습니다.
그걸 소개하기 위해 이렇게 장황하게 글을 쓰고 있는 것입니다.
그리고 사실 이미 여러번 소개했죠.

#####Crash Reporter


본래 kennytm이라는 유명한 사람이 제작하던건데 어느시점에서 지원이 끊겼습니다.

그리고 ashikase라는 역시 유명한 사람이 symbolicate를 제작하였죠.
symbolicate는 개발자들에게 유의미한 디버깅 정보를 제공해주는 필수 툴입니다.

이게 iOS 7.1에 접어들면서 crash report의 포맷이 살짝 바뀌면서 전부 지원이 끊겨버렸습니다.
그리고 이참에 라는듯이 ashikase가 칼을 뽑아 무를 썰기 시작했죠.
지원이 끊겼던 CrashReporter를 들고 와서 symbolicate와 합치는 작업을 시작했습니다.

그렇게 한참 작업을 하더니 여러개의 서브 프로젝트로 나뉜 거대한 CrashReporter가 완성되었습니다.
사실 iOS 7에 접어들면서 콘솔툴로 크래쉬 리포트를 인식하여 주적을 파악해내는 툴이 있었습니다.
전 써보질 않아서 이름이 기억이 안 나는데, 아무튼 그런게 있었죠.
근데 이번에 CrashReporter가 환골탈태 하면서 버로우를 타게 된거 같군요.

CrashReporter는 크래쉬 발생 시 크래쉬를 인식하여 symbolicate 한 뒤 크래쉬를 일으킨 당사자를 [추측]하여 후보를 뽑아 보여주고, 그 후보들의 정보와 해당 제작자에게 메일 전송 등을 가능케 해주는 툴입니다.

사실 iolate님이 ReportTool이라는 비슷한 툴도 개인적으로 제작하여 개인 레포에 공개를 해두셨었는데, 네 CrashReporter로 대체 가능합니다.

여러분은 다른 생각하실거 없이 그냥 이거 하나 딱 설치해두시면 크래쉬 정보는 끝난다고 보시면 됩니다.
그리고 트윅 제작자 입장에서도 제발 저거 설치해서 써주시길 간절히 앙망합니다.

#####syslogd to /var/log/syslog


외부에서 syslog를 자유롭게 접근하기 위해 포워딩을 해주는 툴입니다.

이걸 설치해야 CrashReporter가 크래쉬 난 순간의 log를 캐치해낼 수 있습니다.

매우 중요한 툴인 셈이죠.

이것도 그냥 제발 설치해서 써주세요.
별로 어려울 것도 없잖아요..


crash report 보는 방법


간단하게 나마 보는 방법을 알려드리려고 합니다.
뭐가 어떻게 돌아가고 그런건 저도 잘 모르고 여러분도 알 필요 없어요.
그냥 딱 보고 어떤 놈이 범인인지만 알아보면 되잖아요. 그렇죠? ㅎㅎ

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>AutoSubmitted</key>
	<true/>
	<key>SysInfoCrashReporterKey</key>
	<string>307726c7340a816f1d118fbfb7f1aba0640bb778</string>
	<key>bug_type</key>
	<string>109</string>
	<key>bundleID</key>
	<string>com.apple.springboard</string>
	<key>description</key>
	<string>Incident Identifier: 96CD8E88-96C5-4AD5-AF47-8A4ED0B3364B
CrashReporter Key:   307726c7340a816f1d118fbfb7f1aba0640bb778
Hardware Model:      iPhone5,1
Process:             SpringBoard [27]
Path:                /System/Library/CoreServices/SpringBoard.app/SpringBoard
Identifier:          com.apple.springboard
Version:             50 (1.0)
Code Type:           ARM (Native)
Parent Process:      launchd [1]

Date/Time:           2014-02-25 08:22:49.218 -0600
OS Version:          iOS 7.0.6 (11B651)
Report Version:      104

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000009</string>
	<key>bundleID</key>
	<string>com.apple.springboard</string>
	<key>description</key>
	<string>Incident Identifier: 96CD8E88-96C5-4AD5-AF47-8A4ED0B3364B
CrashReporter Key:   307726c7340a816f1d118fbfb7f1aba0640bb778
Hardware Model:      iPhone5,1
Process:             SpringBoard [27]
Path:                /System/Librarb34 0x14be02 0x2f6cae6c 0x2f63eaac 0x30024ec0 0x153322 0x15395c 0x2b610c 0xb2f58 0x1e87458 0x1ec0498 0x1f0b51a 0x31efdb0a 0x31efd538 0x31ef7b3c 0x31ef651c 0x31ef1840 0x1f4eb8c 0xb0e4e 0x39f5eab2)

Thread 0 Crashed:
0   libsystem_kernel.dylib        	0x3a0151fc 0x3a002000 + 78332
1   libsystem_pthread.dylib       	0x3a07ca4e 0x3a079000 + 14926
2   libsystem_c.dylib             	0x39fc6028 0x39f7c000 + 303144
3   libc++abi.dylib               	0x3941498a 0x39414000 + 2442
4   libc++abi.dylib               	0x3942d6e2 0x39414000 + 104162
5   libobjc.A.dylib               	0x39a65936 0x39a5d000 + 35126
6   libc++abi.dylib               	0x3942b1b0 0x39414000 + 94640
7   libc++abi.dylib               	0x3942aa04 0x39414000 + 92676
8   libobjc.A.dylib               	0x39a65796 0x39a5d000 + 34710
9   CoreFoundation                	0x2f708d50 0x2f634000 + 871760
10  Foundation                    	0x300b10aa 0x3001f000 + 598186
11  SpringBoard                   	0x001ef370 0xad000 + 1319792
12  SevenFolder4x4.dylib          	0x01f0e1f2 0x1f08000 + 25074
13  SpringBoard                   	0x00262c38 0xad000 + 1793080
14  SpringBoard                   	0x00262fae 0xad000 + 1793966
15  SpringBoard                   	0x00197592 0xad000 + 959890
16  SpringBoard                   	0x001977c0 0xad000 + 960448
17  SpringBoard                   	0x001962c0 0xad000 + 955072
18  SpringBoard                   	0x001cd1ca 0xad000 + 1180106
19  Springtomize3.dylib           	0x01f259c4 0x1f21000 + 18884
20  SpringBoard                   	0x001a0276 0xad000 + 995958
21  SpringBoard                   	0x002bbae8 0xad000 + 2157288
22  SpringBoard                   	0x002bbdd4 0xad000 + 2158036
23  SpringBoard                   	0x002a4da2 0xad000 + 2063778
24  SpringBoard                   	0x002d25f0 0xad000 + 2250224
25  SpringBoard                   	0x002d1a96 0xad000 + 2247318
26  SpringBoard                   	0x0013786c 0xad000 + 567404
27  SpringBoard                   	0x0014b9c0 0xad000 + 649664
28  CoreFoundation                	0x2f6cae6e 0x2f634000 + 618094
29  CoreFoundation                	0x2f63eaac 0x2f634000 + 43692
30  Foundation                    	0x30024ec0 0x3001f000 + 24256
31  SpringBoard                   	0x00156a72 0xad000 + 694898
32  SpringBoard                   	0x00150b34 0xad000 + 670516
33  SpringBoard                   	0x0014be02 0xad000 + 650754
34  CoreFoundation                	0x2f6cae6e 0x2f634000 + 618094
35  CoreFoundation                	0x2f63eaac 0x2f634000 + 43692
36  Foundation                    	0x30024ec0 0x3001f000 + 24256
37  SpringBoard                   	0x00153322 0xad000 + 680738
38  SpringBoard                   	0x0015395c 0xad000 + 682332
39  SpringBoard                   	0x002b610c 0xad000 + 2134284
40  SpringBoard                   	0x000b2f58 0xad000 + 24408
41  SpringBoard.dylib             	0x01e87458 0x1e85000 + 9304
42  IconSupport.dylib             	0x01ec0498 0x1ebd000 + 13464
43  SevenFolder4x4.dylib          	0x01f0b51c 0x1f08000 + 13596
44  UIKit                         	0x31efdb0a 0x31e82000 + 506634
45  UIKit                         	0x31efd538 0x31e82000 + 505144
46  UIKit                         	0x31ef7b3c 0x31e82000 + 482108
47  UIKit                         	0x31ef651c 0x31e82000 + 476444
48  UIKit                         	0x31ef1840 0x31e82000 + 456768
49  SpringtomizeUbiquitous.dylib  	0x01f4eb8e 0x1f4e000 + 2958
50  SpringBoard                   	0x000b0e4e 0xad000 + 15950
51  libdyld.dylib                 	0x39f5eab4 0x39f5d000 + 6836

Thread 1:
0   libsystem_kernel.dylib        	0x3a002838 0x3a002000 + 2104
1   libdispatch.dylib             	0x39f510d0 0x39f49000 + 32976
2   libdispatch.dylib             	0x39f4b61e 0x39f49000 + 9758

Thread 2:
0   libsystem_kernel.dylib        	0x3a015c7c 0x3a002000 + 81020
1   libsystem_pthread.dylib       	0x3a079e06 0x3a079000 + 3590
2   libsystem_pthread.dylib       	0x3a079cc0 0x3a079000 + 3264

(중략)

Thread 23 name:  JavaScriptCore::Marking
Thread 23:
0   libsystem_kernel.dylib        	0x3a014f38 0x3a002000 + 77624
1   libsystem_pthread.dylib       	0x3a07b262 0x3a079000 + 8802
2   libsystem_pthread.dylib       	0x3a07c03c 0x3a079000 + 12348
3   JavaScriptCore                	0x307feaea 0x3064f000 + 1768170
4   JavaScriptCore                	0x307feb44 0x3064f000 + 1768260
5   JavaScriptCore                	0x3065da68 0x3064f000 + 60008
6   libsystem_pthread.dylib       	0x3a07bc5a 0x3a079000 + 11354
7   libsystem_pthread.dylib       	0x3a07bbca 0x3a079000 + 11210
8   libsystem_pthread.dylib       	0x3a079ccc 0x3a079000 + 3276

Thread 0 crashed with ARM Thread State (32-bit):
   r0: 0x00000000    r1: 0x00000000      r2: 0x00000000      r3: 0x39fb8aa9
   r4: 0x00000006    r5: 0x3be4218c      r6: 0x00000000      r7: 0x27d514a4
   r8: 0x16704ce0    r9: 0x00000001     r10: 0x42340000     r11: 0x431d0000
   ip: 0x00000148    sp: 0x27d51498      lr: 0x3a07ca53      pc: 0x3a0151fc
 cpsr: 0x00000010

Binary Images:
0xad000 - 0x3a6fff SpringBoard armv7s  &lt;6e39582fe76d3291bfe5dec522e1cf5c&gt; /System/Library/CoreServices/SpringBoard.app/SpringBoard
0x492000 - 0x492fff MobileSubstrate.dylib armv6  &lt;ad3e6cb9e915360ebc71ccbf27bc4ea7&gt; /Library/MobileSubstrate/MobileSubstrate.dylib
0x4e0000 - 0x4e2fff SubstrateLoader.dylib armv6  &lt;974e4b1ab6e6397db859d79f37b7ab37&gt; /Library/Frameworks/CydiaSubstrate.framework/Libraries/SubstrateLoader.dylib
... (생략)
</string>
	<key>displayName</key>
	<string>SpringBoard</string>
	<key>name</key>
	<string>SpringBoard</string>
	<key>os_version</key>
	<string>iPhone OS 7.0.6 (11B651)</string>
	<key>system_ID</key>
	<string></string>
	<key>version</key>
	<string>50 (1.0)</string>
<key>blame</key><array>
</array>
</dict>
</plist>


캬.. 참 더럽게 기네요.
이것도 나름 중간에 많이 잘라낸겁니다.

이 정보들 중 개발자에게 필요한 정보와 간단하게 살펴볼 사용자에게 필요한 정보는 양에서 차이가 납니다.
양에서 차이가 나는거지 종류가 다른게 아닙니다.
어쨌건, 이거가지고 이제 뭐가 문제인지 살펴보도록 하죠.

초반에 다른건 다 필요 없구요.
SpringBoard 어쩌구가 많이 보이는군요.
SpringBoard가 크래쉬 났던 로그인가 봅니다.

기기는 iPhone 5이고 iOS 7.0.6이군요.
크래쉬는 2014년 2월 25일에 발생했던건가 봅니다.

죽 내려보니 Thread 0 부터 Thread 1, Thread 2 ... Thread 23까지 잔뜩 있었습니다.
제가 생략해서 불필요한건 잘라냈지요.
이 경우엔 불필요했는데 다른 경우는 또 다르니까 무조건 뒷부분이라고 무시하면 안 됩니다.

왜 불필요 했냐?
크래쉬난 쓰레드만 보면 되거든요.
근데 크래쉬는 Thread 0에서 났잖아요?
Thread 0 Crashed:라고 말이죠.
그러니 다른건 무시해버리는 거죠.
근데 이것도 무조건 믿으면 안 돼요.
그 예는 이따 따로 보여드리죠.

아무튼 이번은 Thread 0에서 크래쉬 난게 맞고 이거만 보면 됩니다.
길잖아요 Thread 0.
그러니까 얘 맞아요.
길고, 보면 낯익은이름.dylib 어쩌구 저쩌구도 많고..

자 근데 이걸 보고 확인하려니 영 뭐가 뭔지 잘 모르겠어요.
가뜩이나 뭐가 뭔지 모르겠는데 숫자만 잔뜩 있으니 이거 원..
숫자도 숫자 같잖은 이상한 숫자가 말이죠. (사실 16진수)

그래서 이때 힘을 빌리는 녀석이 symbolicate 입니다.

이름만 봐도 뭐하는 녀석인지 알거 같죠?

아.. 잘 모르겠으신가요..
심볼을 찾아서 붙여주는 녀석입니다.
심볼요? 음.. 이름이랄까..

백문이 불여일견!
그냥 일단 symbolicateThread 0의 모습을 봅시다!

Thread 0 Crashed:
0       libsystem_kernel.dylib        	0x3a0151fc 0x3a002000 + 0x131fc 	// ___pthread_kill + 0x8
1       libsystem_pthread.dylib       	0x3a07ca4e 0x3a079000 + 0x3a4e  	// _pthread_kill + 0x36
2       libsystem_c.dylib             	0x39fc6028 0x39f7c000 + 0x4a028 	// _abort + 0x48
3       libc++abi.dylib               	0x3941498a 0x39414000 + 0x98a   	// _abort_message + 0x46
4       libc++abi.dylib               	0x3942d6e2 0x39414000 + 0x196e2 	// default_terminate_handler() + 0xfa
5       libobjc.A.dylib               	0x39a65936 0x39a5d000 + 0x8936  	// _objc_terminate() + 0xbe
6       libc++abi.dylib               	0x3942b1b0 0x39414000 + 0x171b0 	// std::__terminate(void (*)()) + 0x4c
7       libc++abi.dylib               	0x3942aa04 0x39414000 + 0x16a04 	// ___cxa_throw + 0x70
8       libobjc.A.dylib               	0x39a65796 0x39a5d000 + 0x8796  	// _objc_exception_throw + 0xf6
9       CoreFoundation                	0x2f708d50 0x2f634000 + 0xd4d50 	// +[NSException raise:format:arguments:] + 0x64
10      Foundation                    	0x300b10aa 0x3001f000 + 0x920aa 	// -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 0x56
11      SpringBoard (*)               	0x001ef370 0x000ad000 + 0x142370	// 0x00142209 + 0x167
12    + SevenFolder4x4.dylib          	0x01f0e1f2 0x01f08000 + 0x61f2  
13      SpringBoard (*)               	0x00262c38 0x000ad000 + 0x1b5c38	// +[SBIconGridImage _gridImageForNumberOfCells:withPreviousGridImage:cellImageProviderBlock:] + 0x1b7
14      SpringBoard (*)               	0x00262fae 0x000ad000 + 0x1b5fae	// +[SBIconGridImage gridImageForNumberOfCells:withCellImageProviderBlock:] + 0x15
15      SpringBoard (*)               	0x00197592 0x000ad000 + 0xea592 	// -[SBFolderIcon _miniIconGridForPage:] + 0xc9
16      SpringBoard (*)               	0x001977c0 0x000ad000 + 0xea7c0 	// -[SBFolderIcon gridImages] + 0xb7
17      SpringBoard (*)               	0x001962c0 0x000ad000 + 0xe92c0 	// -[SBFolderIcon generateIconImage:] + 0x13
18      SpringBoard (*)               	0x001cd1ca 0x000ad000 + 0x1201ca	// -[SBIcon getIconImage:] + 0x31
19    + Springtomize3.dylib           	0x01f259c4 0x01f21000 + 0x49c4  
20      SpringBoard (*)               	0x001a0276 0x000ad000 + 0xf3276 	// -[SBIconListModel warmUpIconImages] + 0xf9
21      SpringBoard (*)               	0x002bbae8 0x000ad000 + 0x20eae8	// -[SBFolderView _addIconListView:] + 0x6f
22      SpringBoard (*)               	0x002bbdd4 0x000ad000 + 0x20edd4	// -[SBFolderView _addIconListViewsForModels:] + 0x9f
23      SpringBoard (*)               	0x002a4da2 0x000ad000 + 0x1f7da2	// -[SBRootFolderView resetIconListViews] + 0x25
24      SpringBoard (*)               	0x002d25f0 0x000ad000 + 0x2255f0	// -[SBFolderController _resetIconLists] + 0x1f
25      SpringBoard (*)               	0x002d1a96 0x000ad000 + 0x224a96	// -[SBFolderController initWithFolder:orientation:] + 0xdd
26      SpringBoard (*)               	0x0013786c 0x000ad000 + 0x8a86c 	// -[SBRootFolderController initWithFolder:orientation:] + 0x23
27      SpringBoard (*)               	0x0014b9c0 0x000ad000 + 0x9e9c0 	// -[SBIconController _resetRootIconLists] + 0x63
28      CoreFoundation                	0x2f6cae6e 0x2f634000 + 0x96e6e 	// ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 0xa
29      CoreFoundation                	0x2f63eaac 0x2f634000 + 0xaaac  	// __CFXNotificationPost + 0x6b4
30      Foundation                    	0x30024ec0 0x3001f000 + 0x5ec0  	// -[NSNotificationCenter postNotificationName:object:userInfo:] + 0x44
31      SpringBoard (*)               	0x00156a72 0x000ad000 + 0xa9a72 	// -[SBIconModel layout] + 0x11d
32      SpringBoard (*)               	0x00150b34 0x000ad000 + 0xa3b34 	// -[SBIconController relayout] + 0x9f
33      SpringBoard (*)               	0x0014be02 0x000ad000 + 0x9ee02 	// -[SBIconController _iconVisibilityChanged:] + 0x311
34      CoreFoundation                	0x2f6cae6e 0x2f634000 + 0x96e6e 	// ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 0xa
35      CoreFoundation                	0x2f63eaac 0x2f634000 + 0xaaac  	// __CFXNotificationPost + 0x6b4
36      Foundation                    	0x30024ec0 0x3001f000 + 0x5ec0  	// -[NSNotificationCenter postNotificationName:object:userInfo:] + 0x44
37      SpringBoard (*)               	0x00153322 0x000ad000 + 0xa6322 	// -[SBIconModel _postIconVisibilityChangedNotificationShowing:hiding:] + 0xd5
38      SpringBoard (*)               	0x0015395c 0x000ad000 + 0xa695c 	// -[SBIconModel setVisibilityOfIconsWithVisibleTags:hiddenTags:] + 0x617
39      SpringBoard (*)               	0x002b610c 0x000ad000 + 0x20910c	// -[SBApplicationRestrictionController _postRestrictionStateToObservers:] + 0x16b
40      SpringBoard (*)               	0x000b2f58 0x000ad000 + 0x5f58  	// -[SpringBoard applicationDidFinishLaunching:] + 0xa4f
41    + SpringBoard.dylib             	0x01e87458 0x01e85000 + 0x2458  
42    + IconSupport.dylib             	0x01ec0498 0x01ebd000 + 0x3498  
43    + SevenFolder4x4.dylib          	0x01f0b51c 0x01f08000 + 0x351c  
44      UIKit                         	0x31efdb0a 0x31e82000 + 0x7bb0a 	// -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 0x172
45      UIKit                         	0x31efd538 0x31e82000 + 0x7b538 	// -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 0x5b4
46      UIKit                         	0x31ef7b3c 0x31e82000 + 0x75b3c 	// -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 0x2cc
47      UIKit                         	0x31ef651c 0x31e82000 + 0x7451c 	// -[UIApplication _run] + 0x254
48      UIKit                         	0x31ef1840 0x31e82000 + 0x6f840 	// _UIApplicationMain + 0x46c
49    + SpringtomizeUbiquitous.dylib  	0x01f4eb8e 0x01f4e000 + 0xb8e   
50      SpringBoard (*)               	0x000b0e4e 0x000ad000 + 0x3e4e  	// 0x000036cd + 0x781
51      libdyld.dylib                 	0x39f5eab4 0x39f5d000 + 0x1ab4  	// 0x00001ab1 + 0x3


본래 있던 숫자 옆에 뭐 이상한게 많이 붙었죠?
왠지 알아먹을 수 있을 것만 같은 영어들이 말이에요.
저게 바로 심볼입니다.

사실 저 0x어쩌구저쩌구 숫자들이 메모리 주소를 의미하고, 그 메모리 주소에 어떤 기능을 하는 녀석이 있는지 하는 정보를 찾아서 연결해주는 프로그램이 `symbolicate`인 것이죠.
이게 없으면 저희들은 저 숫자가지고 아무것도 못해요.
이게 꼭 있어야 합니다.

자 아무튼 왠지 더 복잡해진거 같지만 사실 아무것도 아니에요.

일단 저 상태로 두고, 어떤 녀석이 `범인`인지 살펴보죠.

우선 Thread 아래쪽에 0부터 51까지 숫자가 보이는군요.
그 옆에 뭔가 파일 이름으로 예상되는 것들이 보이구요.
그 옆엔 예의 이상한 숫자.
다시 그 옆에 아까 말한 심볼들.

기능이 지정된 함수들은 차례차례 불리어지면서 되돌아가야하는 주소를 저장해둬야하는데요.
되돌아가기 편한 자료구조를 이용하여 차례차례 어디에서 불리었는지 하나하나 저장을 해둡니다.
즉, 그 순서는 `stack`이라는 저장소 안에 차례대로 들어가 있는데 바로 그걸 `callstack`이라 부르구요.
그 순서 그대로 보여준 결과가 바로 저건데 그걸 `backtrace`라고 부릅니다.
뭔지 잘 모르겠는데 그냥 넘어가시면 됩니다.

일단 먼저 말했던 0~51의 숫자를 설명해드리죠.
콜스택에 들어간 순서의 역순, 즉 나온 순서입니다.
뭔 소린지 모르겠죠?

`이삿짐`을 싼다고 생각해봅시다.
박스 하나를 준비했어요.
먼저 `겨울 파카`를 채워 넣구요.
다음 `바지`를 채워넣구요.
그 위에 `스웨터`를 채워 넣구요.
그 위에 `팬티`들을 채워 넣었다고 칩시다.

아 근데 이제보니 `팬티`를 먼저 넣고 `파카`를 맨 위에 넣어야 했던거 같아요.
이런 망할! *(사실 아무래도 관계없지만)*
여기서 크래쉬가 난겁니다.

그래서 순서를 반대로 하기 위해 하나하나 다 꺼내야 되겠죠? ``` Thread 이삿짐 Crashed: 0 팬티 0x3 // 서랍 셋째칸 1 스웨터 0x1 // 서랍 첫째칸 2 바지 0x2 // 서랍 둘째칸 3 겨울 파카 0x10 // 장롱 큰칸 ```
이런 정보가 되는 겁니다.

맨 밑에 있는 녀석이 맨 처음에 실행된 녀석인거구요.
맨 위에 있는 녀석이 맨 마지막에 실행된 녀석이에요.

당연히 맨 마지막에 실행된 녀석이 범인일 가능성이 제일 높겠죠?

그래서 아까 로그에서 맨 위의 녀석을 보니 `libsystem_kernel.dylib`라는게 있군요.
먼저 말씀드리면 얘는 아니에요.
얘는 시스템에 본래 들어있는 거거든요.
그래서 얘는 아닙니다.

그렇게 하나 하나 살펴보면 11번까지는 다 시스템 기본으로 들어있는 애들이에요.
그리고 12번이 `SevenFolder4x4.dylib`
이건 따로 설치한 tweak입니다.
얘가 범인일 가능성이 정말 매우 다분하군요.
> 시스템 기본과 트윅을 어떻게 구분하나요?


그거 참 어려운 질문입니다.
그냥 보면 아는 거라서..
직접 설치한 트윅이면 이름만 봐도 '아 얜 트윅이구나'하고 알 수 있잖아요?
그거 빼면 다 시스템 기본인셈이죠.

13번에서 실행된게

+[SBIconGridImage _gridImageForNumberOfCells:withPreviousGridImage:cellImageProviderBlock:]


입니다.
그리고 12번에서 뭔가 실행하고, 11번에서 또 뭔가를 실행했더니, 10번에서

-[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]


뭔가 실패했군요.
이름 보니 handleFailure.. 안 봐도 비디오입니다.

이렇게 12번의 SevenFolder4x4.dylib가 한층 용의선상에 가까워졌어요.

이런 식으로 범인을 추측할 수 있습니다.


이번엔 다른 예제를 살펴보죠.
다른건 다 빼고 Thread 0, 1, 2 만 가져왔습니다.

Thread 0:
0   SevenFolder4x4.dylib          	0x000000010202f170 0x102028000 + 29040
1   SpringBoard                   	0x00000001003329f4 0x1000dc000 + 2451956
2   UIKit                         	0x0000000189276d74 0x189268000 + 60788
3   QuartzCore                    	0x0000000188e730c8 0x188e64000 + 61640
4   QuartzCore                    	0x0000000188e6dc90 0x188e64000 + 40080
5   UIKit                         	0x000000018928e250 0x189268000 + 156240
6   SpringBoard                   	0x0000000100333360 0x1000dc000 + 2454368
7   SpringBoard                   	0x000000010034ce7c 0x1000dc000 + 2559612
8   SpringBoard                   	0x000000010034c00c 0x1000dc000 + 2555916
9   SpringBoard                   	0x000000010034f81c 0x1000dc000 + 2570268
10  SpringBoard                   	0x00000001001f5264 0x1000dc000 + 1151588
11  SpringBoard                   	0x00000001001f2774 0x1000dc000 + 1140596
12  SpringBoard                   	0x00000001001926f8 0x1000dc000 + 747256
13  SpringBoard                   	0x00000001002c370c 0x1000dc000 + 1996556
14  SpringBoard                   	0x00000001002c3068 0x1000dc000 + 1994856
15  SpringBoard                   	0x000000010019267c 0x1000dc000 + 747132
16  UIKit                         	0x000000018941f54c 0x189268000 + 1799500
17  UIKit                         	0x00000001892b00b8 0x189268000 + 295096
18  UIKit                         	0x00000001892aad50 0x189268000 + 273744
19  UIKit                         	0x000000018927c7b4 0x189268000 + 83892
20  UIKit                         	0x000000018927a9c0 0x189268000 + 76224
21  CoreFoundation                	0x0000000186367778 0x18629c000 + 833400
22  CoreFoundation                	0x0000000186366ad4 0x18629c000 + 830164
23  CoreFoundation                	0x0000000186364d6c 0x18629c000 + 822636
24  CoreFoundation                	0x00000001862a5b34 0x18629c000 + 39732
25  GraphicsServices              	0x000000018bccb82c 0x18bcc0000 + 47148
26  UIKit                         	0x00000001892e40e4 0x189268000 + 508132
27  SpringBoard                   	0x00000001000e1308 0x1000dc000 + 21256
28  libdyld.dylib                 	0x000000019290fa9c 0x19290c000 + 15004

Thread 1 Crashed:
0   libsystem_kernel.dylib        	0x00000001929f1ac8 0x1929f0000 + 6856
1   libdispatch.dylib             	0x00000001928f5d74 0x1928f0000 + 23924

Thread 2:
0   libsystem_kernel.dylib        	0x00000001929f1cc0 0x1929f0000 + 7360
1   CoreFoundation                	0x0000000186366ca8 0x18629c000 + 830632
2   CoreFoundation                	0x0000000186364e38 0x18629c000 + 822840
3   CoreFoundation                	0x00000001862a5b34 0x18629c000 + 39732
4   Foundation                    	0x0000000186e39590 0x186e28000 + 71056
5   Foundation                    	0x0000000186e97148 0x186e28000 + 454984
6   UIKit                         	0x00000001897056f4 0x189268000 + 4839156
7   Foundation                    	0x0000000186f2076c 0x186e28000 + 1017708
8   libsystem_pthread.dylib       	0x0000000192a8c1ac 0x192a88000 + 16812
9   libsystem_pthread.dylib       	0x0000000192a8c104 0x192a88000 + 16644
10  libsystem_pthread.dylib       	0x0000000192a897ac 0x192a88000 + 6060


Crashed라고 붙은건 Thread 1이네요.
근데 사용된 바이너리는 libsystem_kernel.dylib, libdispatch.dylib 달랑 두개군요.
딱 봐도 시스템 기본 파일입니다.
뭔가 이상하군요.

사실 이런 형태의 크래쉬 로그도 많습니다.
볼 것도 없어요.
바로 위에 보세요. Thread 0.
길잖아요?
게다가 낯익은 것도 있죠?

실제 크래쉬는 쟤한테서 난겁니다.
Thread 0만 파보면 되는 거예요.
근데 이 예제는 팔 것도 없군요.
Thread 0의 범인으로 추정 가능한 녀석은 단 하나 뿐이잖아요.
0번. SevenFolder4x4.dylib

전부 이렇게 쉬우면 얼마나 좋을까요.

아무튼 이런 식으로 전범을 추측하면 됩니다.


이번엔 약간 골치 아픈 녀석으로 해볼까요?

Last Exception Backtrace:
0      CoreFoundation                0x18ac6f100 0x18ab64000 + 0x10b100	// ___exceptionPreprocess + 0x84
1      libobjc.A.dylib              0x1971781fc 0x197170000 + 0x81fc	// _objc_exception_throw + 0x3c
2      CoreFoundation                0x18ac6f040 0x18ab64000 + 0x10b040	// -[NSException initWithCoder:] + 0x0
3      Foundation                    0x18b727620 0x18b6f8000 + 0x2f620	// -[NSPlaceholderString initWithString:] + 0x74
4    + RingerPlus                    0x1130d1c6c 0x1130d0000 + 0x1c6c	// 0x000019f4 + 0x278
5    + RingerPlus                    0x1130d366c 0x1130d0000 + 0x366c	// 0x00003604 + 0x68
6      libobjc.A.dylib              0x197179e64 0x197170000 + 0x9e64	// _call_load_methods + 0x29c
7      libobjc.A.dylib              0x19717a7ac 0x197170000 + 0xa7ac	// _load_images + 0x60
8      dyld                          0x1200fdde8 0x1200fc000 + 0x1de8	// dyld::notifySingle(dyld_image_states, ImageLoader const*) + 0x114
9      dyld                          0x120108574 0x1200fc000 + 0xc574	// ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&) + 0x128
10      dyld                          0x120108400 0x1200fc000 + 0xc400	// ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 0x44
11      dyld                          0x120100684 0x1200fc000 + 0x4684	// dyld::runInitializers(ImageLoader*) + 0x60
12      dyld                          0x120105200 0x1200fc000 + 0x9200	// _dlopen + 0x2f0
13      libdyld.dylib                0x19776b5d8 0x197768000 + 0x35d8	// _dlopen + 0x48
14      CoreFoundation                0x18ac0b8d8 0x18ab64000 + 0xa78d8	// __CFBundleDlfcnLoadFramework + 0x90
15      CoreFoundation                0x18abdbb28 0x18ab64000 + 0x77b28	// __CFBundleLoadExecutableAndReturnError + 0x274
16      Foundation                    0x18b767158 0x18b6f8000 + 0x6f158	// -[NSBundle loadAndReturnError:] + 0x1dc
17      Foundation                    0x18b779870 0x18b6f8000 + 0x81870	// -[NSBundle load] + 0x1c
18      Foundation                    0x18b779828 0x18b6f8000 + 0x81828	// -[NSBundle principalClass] + 0x38
19    + libFlipswitchSpringBoard.dylib	0x10267df20 0x102678000 + 0x5f20	// 0x00005ee4 + 0x3c
20    + libFlipswitchSpringBoard.dylib	0x10267e1a8 0x102678000 + 0x61a8	// 0x00006188 + 0x20
21    + libFlipswitchSpringBoard.dylib	0x10267b310 0x102678000 + 0x3310	// 0x000032a8 + 0x68
22    + CCToggles.dylib              0x102653f94 0x102644000 + 0xff94	// 0x0000fd58 + 0x23c
23    + CCToggles.dylib              0x102647094 0x102644000 + 0x3094	// 0x00002f24 + 0x170
24    + CCToggles.dylib              0x102647738 0x102644000 + 0x3738	// 0x00003514 + 0x224
25      CoreFoundation                0x18ac23764 0x18ab64000 + 0xbf764	// ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 0x14
26      CoreFoundation                0x18ab702b4 0x18ab64000 + 0xc2b4	// __CFXNotificationPost + 0x810
27      Foundation                    0x18b7643bc 0x18b6f8000 + 0x6c3bc	// _postQueueNotifications + 0x2d8
28      CoreFoundation                0x18ac2f858 0x18ab64000 + 0xcb858	// ___CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 0x20
29      CoreFoundation                0x18ac2cae0 0x18ab64000 + 0xc8ae0	// ___CFRunLoopDoObservers + 0x174
30      CoreFoundation                0x18ac2ce6c 0x18ab64000 + 0xc8e6c	// ___CFRunLoopRun + 0x2fc
31      CoreFoundation                0x18ab6ddd0 0x18ab64000 + 0x9dd0	// _CFRunLoopRunSpecific + 0x1c4
32      GraphicsServices              0x190855c0c 0x190848000 + 0xdc0c	// _GSEventRunModal + 0xa8
33      UIKit                        0x18dc9efc4 0x18dc24000 + 0x7afc4	// _UIApplicationMain + 0x484
34      SpringBoard (*)              0x100029780 0x100024000 + 0x5780	// 0x00004e64 + 0x91c
35      libdyld.dylib                0x19776baa0 0x197768000 + 0x3aa0	// _start + 0x4

Thread 0 Crashed:
0      libsystem_kernel.dylib        0x19786658c 0x19784c000 + 0x1a58c	// ___pthread_kill + 0x8
1      libsystem_c.dylib            0x1977fa804 0x197798000 + 0x62804	// _abort + 0x6c
2      libc++abi.dylib              0x196a20990 0x196a20000 + 0x990 // _abort_message + 0x54
3      libc++abi.dylib              0x196a3dc28 0x196a20000 + 0x1dc28	// default_terminate_handler() + 0x128
4      libobjc.A.dylib              0x1971784d0 0x197170000 + 0x84d0	// _objc_terminate() + 0x7c
5      libc++abi.dylib              0x196a3b164 0x196a20000 + 0x1b164	// std::__terminate(void (*)()) + 0xc
6      libc++abi.dylib              0x196a3aa7c 0x196a20000 + 0x1aa7c	// ___cxa_throw + 0x84
7      libobjc.A.dylib              0x197178314 0x197170000 + 0x8314	// _objc_exception_throw + 0x154
8      CoreFoundation                0x18ac6f03c 0x18ab64000 + 0x10b03c	// +[NSException raise:format:] + 0x7c
9      Foundation                    0x18b72761c 0x18b6f8000 + 0x2f61c	// -[NSPlaceholderString initWithString:] + 0x70
10    + RingerPlus                    0x1130d1c68 0x1130d0000 + 0x1c68	// 0x000019f4 + 0x274
11    + RingerPlus                    0x1130d3668 0x1130d0000 + 0x3668	// 0x00003604 + 0x64
12      libobjc.A.dylib              0x197179e60 0x197170000 + 0x9e60	// _call_load_methods + 0x298
13      libobjc.A.dylib              0x19717a7a8 0x197170000 + 0xa7a8	// _load_images + 0x5c
14      dyld                          0x1200fdde4 0x1200fc000 + 0x1de4	// dyld::notifySingle(dyld_image_states, ImageLoader const*) + 0x110
15      dyld                          0x120108570 0x1200fc000 + 0xc570	// ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&) + 0x124
16      dyld                          0x1201083fc 0x1200fc000 + 0xc3fc	// ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 0x40
17      dyld                          0x120100680 0x1200fc000 + 0x4680	// dyld::runInitializers(ImageLoader*) + 0x5c
18      dyld                          0x1201051fc 0x1200fc000 + 0x91fc	// _dlopen + 0x2ec
19      libdyld.dylib                0x19776b5d4 0x197768000 + 0x35d4	// _dlopen + 0x44
20      CoreFoundation                0x18ac0b8d4 0x18ab64000 + 0xa78d4	// __CFBundleDlfcnLoadFramework + 0x8c
21      CoreFoundation                0x18abdbb24 0x18ab64000 + 0x77b24	// __CFBundleLoadExecutableAndReturnError + 0x270
22      Foundation                    0x18b767154 0x18b6f8000 + 0x6f154	// -[NSBundle loadAndReturnError:] + 0x1d8
23      Foundation                    0x18b77986c 0x18b6f8000 + 0x8186c	// -[NSBundle load] + 0x18
24      Foundation                    0x18b779824 0x18b6f8000 + 0x81824	// -[NSBundle principalClass] + 0x34
25    + libFlipswitchSpringBoard.dylib	0x10267df1c 0x102678000 + 0x5f1c	// 0x00005ee4 + 0x38
26    + libFlipswitchSpringBoard.dylib	0x10267e1a4 0x102678000 + 0x61a4	// 0x00006188 + 0x1c
27    + libFlipswitchSpringBoard.dylib	0x10267b30c 0x102678000 + 0x330c	// 0x000032a8 + 0x64
28    + CCToggles.dylib              0x102653f90 0x102644000 + 0xff90	// 0x0000fd58 + 0x238
29    + CCToggles.dylib              0x102647090 0x102644000 + 0x3090	// 0x00002f24 + 0x16c
30    + CCToggles.dylib              0x102647734 0x102644000 + 0x3734	// 0x00003514 + 0x220
31      CoreFoundation                0x18ac23760 0x18ab64000 + 0xbf760	// ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 0x10
32      CoreFoundation                0x18ab702b0 0x18ab64000 + 0xc2b0	// __CFXNotificationPost + 0x80c
33      Foundation                    0x18b7643b8 0x18b6f8000 + 0x6c3b8	// _postQueueNotifications + 0x2d4
34      CoreFoundation                0x18ac2f854 0x18ab64000 + 0xcb854	// ___CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 0x1c
35      CoreFoundation                0x18ac2cadc 0x18ab64000 + 0xc8adc	// ___CFRunLoopDoObservers + 0x170
36      CoreFoundation                0x18ac2ce68 0x18ab64000 + 0xc8e68	// ___CFRunLoopRun + 0x2f8
37      CoreFoundation                0x18ab6ddcc 0x18ab64000 + 0x9dcc	// _CFRunLoopRunSpecific + 0x1c0
38      GraphicsServices              0x190855c08 0x190848000 + 0xdc08	// _GSEventRunModal + 0xa4
39      UIKit                        0x18dc9efc0 0x18dc24000 + 0x7afc0	// _UIApplicationMain + 0x480
40      SpringBoard (*)              0x10002977c 0x100024000 + 0x577c	// 0x00004e64 + 0x918
41      libdyld.dylib                0x19776ba9c 0x197768000 + 0x3a9c	// _start + 0x0

Thread 1:
0      libsystem_kernel.dylib        0x19784daa8 0x19784c000 + 0x1aa8	// _kevent64 + 0x8
1      libdispatch.dylib            0x197751998 0x19774c000 + 0x5998	// __dispatch_mgr_thread + 0x30


역시 일부분만 가져온 것입니다.

이번엔 또 특이한게 하나 먼저 등장했네요.
Last Exception Backtrace:
그냥 그대로 해석하셔도 됩니다.
마지막으로 예외(=에러)가 발생한 백트레이스.

여기선 Crashed:가 붙은 쓰레드를 갖고 와서 먼저 보여준다고 생각하시면 됩니다.
즉, Thread 0 내용과 똑같이 되어 있는거죠.

이건 CrashReporter를 통해 얻어온 크래쉬 리포트인데, main suspect를 찾아내지 못한 결과물입니다.
그래서 사용자분이 리스트에 보인 것 중 하나인 Background Manager 제작자인 저에게 메일을 보내오셨죠.

여태 해온것처럼 그럼 살펴보도록 합시다.

위에서부터 하나씩 보면 돼요.
그랬더니 RingerPlus, libFlipswitchSpringBoard.dylib, CCToggles.dylib 등이 보이는군요.
순서대로 해보면 RingerPlus가 가장 의심됩니다.

실제로 Exception은 8번에서 발생했거든요.

+[NSException raise:format:]


이렇게 말이죠.
9번 작업을 하던 중 예외(=에러)가 발생했고 9번은 10번이 호출한거니까 10번이 범인일 가능성이 제일 높은 거죠.

여기서는 RingerPlus범인입니다.
밑에 억울한 누명을 쓴 Flipswitch, CCToggles, 그리고 뜬금없이 등장한 Background Manager에게 심심한 위로를.


syslog 보는 방법


예부터 들고올게요.

Wed Aug 20 02:49:48 2014: SpringBoard (com.apple.springboard): NB ~~~ TRUE ON APPID: com.apple.mobilemail
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.springboard): -[SpringBoard toggleBackgroundModeNeedDelay:]: unrecognized selector sent to instance 0x135d20bf0
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.springboard): *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SpringBoard toggleBackgroundModeNeedDelay:]: unrecognized selector sent to instance 0x135d20bf0'
*** First throw call stack:
(0x186a3b100 0x192f441fc 0x186a3fdb4 0x186a3dae0 0x18695d78c 0x1022c9fc8 0x100738b44 0x10072d368 0x10072f3fc 0x19351c014 0x19351bfd4 0x19351f1dc 0x1869faddc 0x1869f911c 0x186939dd0 0x18c621c0c 0x189a6afc4 0x100061780 0x193537aa0)
Wed Aug 20 02:49:50 2014: ReportCrash (Crash Reporter): ReportCrash acting against PID 10900
Wed Aug 20 02:49:50 2014: ReportCrash (Crash Reporter): Formulating crash report for process SpringBoard[10900]
Wed Aug 20 02:49:50 2014: ReportCrash (Crash Reporter): Failed to open AC log file (/var/mobile/Library/Logs/AppleSupport/general.log) for writing. uid=501
Wed Aug 20 02:49:50 2014: ReportCrash (Crash Reporter): Saved crashreport to /var/mobile/Library/Logs/CrashReporter/SpringBoard_2014-08-20-024950_iPhone.ips using uid: 0 gid: 0, synthetic_euid: 501 egid: 0
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Notice: Injecting: com.apple.springboard [SpringBoard] (847.27)
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Warning: Entering Safe Mode
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Notice: Loading: /Library/MobileSubstrate/DynamicLibraries/MobileSafety.dylib
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Warning: nil class argument for selector touchesEnded:withEvent:
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Warning: nil class argument for selector mouseDown:
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Warning: nil class argument for selector _updateTimeString
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Warning: nil class argument for selector _finishUnlockWithSound:unlockSource:isAutoUnlock:
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Warning: nil class argument for selector _unlockWithSound:isAutoUnlock:unlockSource:
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Warning: nil class argument for selector _unlockWithSound:isAutoUnlock:unlockType:
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Warning: message not found [SBIconController showInfoAlertIfNeeded]
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Warning: nil class argument for selector maxIconColumns
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Warning: message not found [UIImage defaultDesktopImage]
Wed Aug 20 02:49:50 2014: SpringBoard (com.apple.console): MS:Warning: nil class argument for selector tile
Wed Aug 20 02:49:51 2014: SpringBoard (com.apple.springboard): Loaded logger: SBAppInstallationLog
Wed Aug 20 02:49:51 2014: SpringBoard (com.apple.springboard): Loaded logger: SBIconLog
Wed Aug 20 02:49:51 2014: SpringBoard (com.apple.springboard): Loaded logger: SBWorkspaceLogging
Wed Aug 20 02:49:51 2014: SpringBoard (com.apple.springboard): *** error reading settings archive file: <SBRootSettings: /var/mobile/Documents/com.apple.springboard.settings/RootSettings.plist> 


CrashReporter를 통해 syslog를 캐치하면 작은 분량의 로그만 이렇게 잡힙니다.
편하죠.

제일 중요한 부분은 ReportCrash 라는 프로세스가 등장한 순간입니다.
보통 그 위에 보면 에러문들이 적혀있거든요.
혹은 그 바로 아래나 말이죠.

그리고 특히 이 syslog가 바로 이전에 이야기했던, crash report만으로 해결이 불가능해서 syslog가 반드시 있어야 하는 경우 중 하나입니다.

어떤 녀석이 범인인지 알려면 crash report만 있어도 됩니다만, 그 문제를 해결하기 위한 정보를 얻기 위해선 개발자에게 반드시 이 syslog가 있어야 해요.
재수없으면 재현이 안 될 수 있는데 그땐 정말 이거 없으면 답이 없거든요.

이걸 보면 SpringBoardtoggleBackgroundModeNeedDelay: 라는 메소드가 없어서 발생한 에러라는 걸 아주 쉽게 파악할 수 있죠.

아무튼 이런 식으로 대충 보면 됩니다.
사용자 분들은 이 파일을 직접 봐야할 일은 거의 없어요.
게다가 보통의 경우 이처럼 도움이 되는 내용이 잘 담겨 있지 않기도 하구요.
봐도 이해하기 힘들기도 합니다.


안전모드


Windows를 오래 쓰신 분들은 안전모드라는 단어가 많이 익숙하실 겁니다.
사실 맥에도 안전모드 부팅은 있습니다.
윈도우에 비해 압도적으로 사용할 일이 없어서 잘 모를 뿐이죠.

그리고 탈옥이라는 불안정한 환경에서도 필수적인 존재이지요.

Windows의 안전모드는 보통 각종 드라이버 등을 읽지 않고 생짜 OS가 올라가는 형태입니다.
OS X도 비슷하구요.

iOS의 탈옥용 안전모드도 비슷합니다.
이 안전모드를 설명하기 전에 두가지 알아볼게 있습니다.

CydiaSubstrate (MobileSubstrate)


과거 이름이 MobileSubstrate인데 saurik(제작자)이 안드로이드용 substrate 모듈을 만들면서 이름을 CydiaSubstrate로 변경합니다.
이 이름은 iOS 7 탈옥이 공개된 후 정식 적용되게 되었습니다.

어쨌건 iOS용 CydiaSubstrate는 여전히 내부적으로 MobileSubstrate 상태입니다.
가장 중요한, 그리고 변경할 수 없는 경로부터 MobileSubstrate다보니 어쩔 수가 없죠.

이게 뭘 하는거냐 하면, 본래 실행되는 과정을 후려쳐서 새치기 한 다음에 다른 줄을 만들어 새로운 실행을 할 수 있도록 해주는 녀석입니다.
뭔 소린지 잘 모르겠죠?

1+1=2 라는 연산을 해주는 프로그램이 있는데 거길 끼어들어서 다른 일을 할 수 있도록 해주는 프로그램인 것입니다.
그 다른일이 무엇인지는 제작자 마음입니다.

그 다른일을 하도록 하는게 바로 Tweak입니다.

Tweak


Tweak은 이름 그대로의 의미대로 작동하는 녀석입니다.
본래 하는 일을 끼어들어 살짝 변경시켜주는 녀석이죠.

실제로 살짝만 변경시키는지 대거 변경시키는지는 제작자 마음이지만요.

이런 트윅은 99% MobileSubstrate를 이용하게 됩니다.
MobileSubstrate를 이용하지 않고 자체적으로 MS를 구현하는 녀석도 가끔 있긴 합니다.
보통 바이러스가 그렇게 작성되죠.
근데 여간 귀찮은 일이 아니라서 잘 안 그럽니다.

Cydia를 통해 유통되는 정상적인 트윅 제품은 100% MobileSubstrate를 이용한다고 보시면 됩니다.
그러니 정품을 쓰시는 분들은 큰 걱정 안 하셔도 됩니다.

이런 트윅은 아까 예로 든 1+1=2 라는 연산과 결과를 도출하는 프로그램에 끼어들어,
sqrt(2) = 1.414 라는 연산과 결과를 출력하도록 바꿀 수도 있구요.
전혀 엉뚱한 일을 시킨 뒤에 1+1=2 라는 연산과 결과를 출력해줄 수도 있습니다.

마음만 먹는다면 얼마든지 바이러스도 만들 수 있고, IAP 크랙도 만들 수 있고, 게임 핵도 간단하게 만들 수 있는 셈이지요.
나쁘게만 쓰겠다면 그렇게 쓰는거고 잘 쓰면 그렇게만 쓰진 않겠죠?

그리고 이게 나쁘게 쓰이거나 잘못 쓰이거나, 개발자가 실수를 하게 되면 시스템이 크래쉬를 하게 됩니다.
무한 사과에 걸리거나 하는 거죠.

이렇게 트윅에 의해 무한 사과가 발생하게 되면 MobileSubstrate에 들어있는 기능인 '안전모드'를 이용하여 무한 사과를 탈출 할 수 있게 됩니다.

앞서 정상적인 모든 트윅은 MobileSubstrate를 이용한다고 말씀드렸습니다.
하지만 트윅도 사람이 만드는 것, 당연히 실수가 발생할 수 있습니다.
그리고 무한 사과에 빠질 수 있는데, MobileSubstrate는 그런 경우 모든 트윅을 로드하지 않고 생짜 iOS를 불러오도록 하는 기능이 들어가 있습니다.
트윅에 의해 발생한 무한 사과는 바로 그 기능으로 트윅들을 무력화 시켜 무한 사과에서 탈출할 수 있게 되는것이죠.
바로 그게 MobileSubstrate의 안전모드 입니다.

앞선 글에서 적었지만 부팅이 시작될 때 볼륨의 +키를 누르고 있으면 간단하게 들어갈 수 있습니다.


개발자를 이해하기


거창하게 개발자를 이해하자는게 아니라, 문제 해결을 할 때 개발자의 입장을 이해해보자는 뜻입니다.

개발하는데 시간이 얼마나 들고 돈이 얼마나 들고 생활 사정이 어떻고, 그딴건 사실 여러분에게 아무 상관 없잖아요?

당장 내 문제를 해결하는데에 왜 이 개발자놈이 미적미적대고 뭐든 안 된다고 하고 씹고 하는지를 알아보자는 뜻입니다.

그게 이유가 있는 거였어?


네. 이유가 있죠 당연히.
이유없이 왜 씹고 그러겠나요..

개발자의 개발 환경


개발자의 개발 환경은 당연하지만 개발에 최적화 되어있습니다.
그리고 개발한 결과물을 테스트하는 환경 역시 개발과 테스트에 최적화 되어있습니다.
간단하게 말해서 마루타용 기기에 테스트를 하는 것이죠.

사용자분들이 사용하는 환경과는 완전 다릅니다.
설치된 트윅도 손에 꼽을만 하고 꼭 필요한거 아니면 웬만하면 설치하지 않습니다.

구 버전의 운영체제를 지원하기 위해서 웬만하면 판올림을 하지 않습니다.
하지만 넉넉한 자금 사정일 리가 없기 때문에 모든 기기를 다 갖고 있는 것도 아니고,
모든 iOS를 테스트 하기 위한 갯수 만큼 넉넉하게 기기를 보유하고 있는 것도 아닙니다.

iOS가 판올림을 하며 하나의 메이저 버전 내에서조차 굉장히 다양한 마이너 버전을 내보이게 되는데요.
개발자들은 그럴만한 여유가 없기 때문에 선택과 집중을 할 수 밖에 없습니다.

7.0.4가 나오면서 7.07.0.3은 당연히 포기를 하게 되는거고, 7.1.2가 나오면서 7.07.0.5 및 7.1, 7.1.1도 포기를 하게 됩니다.
6.1.2 이후 7.0으로 넘어가면서 6.1.3~6.1.6을 자연스럽게 포기하게 되고, 내부적 변경이 많이 이뤄졌지만 보통 해당 버전의 shsh를 보유하고 있지 않기 때문에 절대 지원할 수 없는 버전이 된 것입니다.

이런 선택과 집중의 결과에서 벗어나는 버전을 갖고 계신 사용자분들은 자연스럽게 버림받게 됩니다.
이런건 어디가서 하소연 하셔도 소용없습니다.

대세를 따르라고 밖에 조언이 불가능하죠.


사실 이런건 소소한 문제입니다.
아주 소수거든요.

대다수는 이런 문제보다 다른 문제를 안고 있습니다.
수많은 크랙 저장소와 크랙 프로그램의 설치 및 사용.

크랙만 전문적으로 다루는 애들은 그 크랙에서 발생하는 문제에 대해서 당연히 피드백을 받아줄겁니다.
하지만 메이저 레포에 서빙을 하는 개발자들은 크랙에 대해서 신경쓸 이유가 전혀 없죠.

제 프로그램에 대해서 정품을 이용하고 있다고 메일을 보내오는데, 설치한 다른 프로그램들 보면 전부 크랙인 분들이 엄청나게 많습니다.
전 하나하나 살펴보며 다른 트윅과의 충돌인가 알아봐야하는데 전부 크랙인거예요.
제가 어찌할거 같으신가요?

손을 놓습니다.

크랙은 대응할 이유가 없거든요.
크랙판으로 인해 충돌이 벌어져 크래쉬가 난다면, 저는 모르는 일인거죠.
제가 다른 트윅의 크랙을 깔아서 테스트를 해보며 대응을 할 이유가 없습니다.

근데 앞서 적었듯이 이런 경우가 정말 엄청나게 많습니다.

분명 뭔가 문제는 있을거 같은데 손 쓸 방법이 없어요.
개발자들은 손을 놓으려 하지만 찝찝함에 어쩔 수 없이 방법을 강구합니다.

감으로 찍어서 대충 해보는 거죠.
안 되면 할 수 없고.


자 아무튼 이렇게 사용자 기기와 개발용/테스트용 기기의 환경 차이가 압도적인데 같은 현상이 나타나면 그게 오히려 더 이상하겠죠.
개발자는 한탄합니다.

내 기기에선 멀쩡한데..


멀쩡하거든요. 말하는 대로 아무리 해봐도 재현이 안 됩니다.
될 턱이 없죠. 되면 그게 더 이상하다고 생각해요.

안 되는데 되게 하라고 아무런 정보도 안 주고 메일만 주구장창 보내는데 개발자가 어떻게 대답을 하겠나요.
당연히 미적미적 대는겁니다.
이유야 뻔하죠.
우선순위에서 밀렸다고 말이죠.
사실 그래요. 뭘 알아야 고칠텐데 내 기기에선 문제 없으니 '문제 아님' 처리하는 겁니다.
저도 그래요.
당연히 우선순위가 밀리는거죠.
아예 처리할 문제로 등록이 안 되는데 우선순위고 자시고 없는겁니다.

좋은 보고


여러분이 개발자에게 제대로 된 답변을 받고 싶으면 그래서 정확한 정보를 전달할 필요가 있는 겁니다.
솔직히 전 예의 그런거 필요없고 그냥 제발 넉넉하고 정확한 정보라도 잘 전달해주길 바라고 있을 뿐입니다.
예의 잘 지켜서 서론과 결론부분이 엄청 긴데 가운데에 상황 설명이 달랑 한줄 정도 밖에 안 됩니다.
근데 문단 구분을 안 해놔서 제 하찮은 독해실력으로는 도저히 이해가 안 되는 상황이 부지기수입니다.

'미안 이해가 안가. 니가 정확하게 원하는게 뭐야?'
매번 되묻습니다.

우리말로 오는 메일은 더 심각합니다.
일단 예의가 없어요. 하지만 앞서 적었듯이 내용만 충실하면 아무래도 좋아요.

내용은 아예 없어요.
내용이 없으면 예의라도 있던가 말이죠. ㅠㅠ

업데이트 후 작동 안 돼요. 고쳐주세요.


끝...
어..어쩌라고..
최소한의 예의는 지켜주셨네요. 감사합니다. (먼산)

거기다가 UDID마저 지워서, 혹은 사제 메일을 써서 보냅니다.
그대로 휴지통 직행입니다.

답장은 커녕 '읽었다'는 표시조차 안 생기니 커뮤니티에 온갖 호박씨가 난무합니다.
(읽었는데 답장을 안 하면 대놓고 씹었다고 생각하므로 아예 읽었다는 표시가 안 되도록 하면서 메일을 봅니다. 이정도는 기본 스킬이에요.
저번에 한번 HideJB 관련 메일을 읽었는데 답장 안 했더니 온갖 협박이 다 날아오더군요.
이거 원 더럽고 서러워서..)

뭐 그정도는 감수합니다만 저 메일 내용은 해도해도 너무하잖아요.


버그 보고 해주시는 분들 정말 대단히 감사합니다.
개발자가 대다수는 코딩과 결과물에 한정지어 완벽주의적인 성향이 있어요.
그래서 이런 하나하나의 보고 정말 기쁩니다.

근데 도움이 안 되는 보고는 그저 난감할 따름이에요.
뭔가 문제가 있나 보다. '저 사람 한정으로'.

분명 광범위한 문제가 있는거 같은데 전부 저런 형식의 보고만 날아온 적이 있습니다.
반년 넘게 그 버그를 방치해뒀었어요.
심지어 그 버그는 무한 사과에 빠지는 심각한 버그였어요.
하지만 제 기기에선 멀쩡했습니다.

외국에서 엄청나게 지탄을 받았는데 아무도 올바른 보고를 해주지 않았어요.
국내에는 보고 사례가 아예 없었고 말이죠.

그리고 결국 아랍권에서 누군가가 나서서 트위터로 제게 욕을 하고 나서서 뭔 소린지 묻고 나서야 상황 파악을 할 수 있었고 여전히 문제 없는 제 기기 상황을 알려줘서 그쪽도 상황을 이해할 수 있었죠.
이후 그 사람이 테스터 노릇을 해준 덕택에 그 버그를 고칠 수 있었습니다.
당시엔 CrashReporter가 제 역할을 못 해주던 때고 크래쉬 리포트에 대해 제대로 아는 사람도 드물었던 때라 더더욱 해결하기 힘들었었습니다.

그저 한두명만 제대로 보고했어도 제가 금방 해결했을지도 모를 문제였지만 그냥 욕만 해대니까 해결이 안 되는 거였죠.
그러고는 저한테 왜 해결 안 해주냐고 또 욕을 하는 겁니다.
해결이 되겠나요 그게. 그런 문제입니다.


또 다른 경우가 있습니다.

제가 만든 트윅에 문제가 있었나봅니다.
그리고 전 그걸 모른 채 열심히 판올림을 했구요.

앞서 적었지만 웬만하면 제 기기에선 멀쩡합니다.
하지만 문제가 있었나봐요.
판올림은 계속 되는데 수정이 안 됩니다.

어라 이렇게 명백하게 문제가 있는데 이걸 왜 안 고치는거야?


이렇게들 생각을 하십니다.

자, 개발자는 분명 자기 딴에 열심히 테스트까지 해서 작동 확인을 해서 올립니다.
물론 실수해서 테스트 케이스를 빼먹은 채 올릴 때도 있습니다.
급한 마음에 올리면 그런 일이 잦죠.

근데 웬만하면 테스트를 해서 올려보기 때문에 개발 기기에선 멀쩡하니까 올라가는 경우가 더 많습니다.
하지만 사용자 분들은 아무리 기다려도 해결이 안 됩니다.

명백한 문제인데 개발자가 의도적으로 쌩깐다, 라고 생각할 수도 있습니다.
혹은 '이 새끼 병신이네' 라고 생각하실 수도 있죠.

사실 이유는 간단합니다.
그런 버그가 있다는 사실 자체를 모르는 거예요.

왜 알거라고 생각하신거죠?
당연히 모르니까 방치하는 겁니다.

그냥 딱 켜보면 바로 티가 나는 엄청나게 큰 버그인데, 모릅니다.
실제로 개발 기기에서 발생하는데에도 불구하고 모르는 경우도 많습니다.

제가 모든 iOS의 기능을 다 섭렵하고 있진 않거든요.
iOS의 모든 숨겨진 부분을 다 완벽하게 알고 있는 것도 아니구요.


보고가 안 되면 절대 모르는겁니다.
그냥 어쩌다가 실수로 잘못했는데 문제가 떡하니 개발 기기에서 재현되어서 알게 될 수도 있습니다.
하지만 그건 정말 어쩌다가 한번씩 발생하는 일이죠.

그러니 보고가 필요합니다.
개발자에게 내용이 텅텅 빈 메일만큼 짜증나는게 없긴 하나, 내용이 충실한 메일은 허! 광명 오셨네! 하며 기쁘게 받아들입니다.

보고가 안 되면 절대 모릅니다.
'누군가는 해주겠지' 네, 그럴겁니다.
근데 그게 계속 방치되면 아무도 안 해줬다고 생각하시면 됩니다.

개발자는 아무것도 모른 채 그대로 끝까지 가는 겁니다.

크랙


마지막으로 크랙 버전에 대해서.

개발자들에 따라 크랙 사용자에 대한 대우가 다른 편입니다.
저 같은 경우엔 그냥 무시하는 타입이구요.
어느 분은 예비 구매자로 보시기도 합니다.

한번 크랙 써본 사람은 크랙 못 버린다는 생각을 갖고 있기 때문에 전 그냥 무시합니다.
물론 정품 사용자로 돌아오는 사람도 있습니다.
단지 극소수일뿐.
그 극소수를 위해서 제가 신경쓰고 하나하나 대우해줄 필요는 못 느끼거든요.
그럴 여력도 없고 말이죠.

그리고 마치 정품 사용자인거 마냥 질문이나 보고 메일을 보냅니다.
트위터로 자연스럽게 물어보거나 하고 말이죠.

누굴 호구로 아나, 싶습니다.
딱 보면 알거든요 이젠.

걍 정품 프리카피 내놔라 하는게 차라리 나아요.
뭐 전 그것도 무시합니다만.

그냥 자기가 정품 사용자인것마냥 속이고 제게 질문을 해옵니다.
근데 질문 내용은 크랙 대처 결과물이에요.
티가 안 날 수가 없는데 속이겠다고 UDID 지우고 dpkg.log 지우고 합니다.
그냥 일반 메일로 보내거나 하고 말이죠.

UDID 내놔라하니까 부랴부랴 구매한 뒤에 '구 버전 내놔라'라고 메일 다시 보냅니다.
deb 전송도 전 안 합니다.
(이건 해주는 개발자도 많습니다만 보통 구 버전이 아니라 신 버전을 줍니다.)

이렇게 눈가리고 아웅하는 사람이 많아요.
크랙 쓰는거야 뭐 할 수 없습니다.
근데 개발자들을 농락하시면 곤란하죠.

제 프로그램이 아니더라도, 다른 제작자의 프로그램이라도, 크랙 버전을 보면 기분이 나쁩니다.

이 시장 정말 작습니다.
마치 떼돈이라도 벌까 싶으신 분들도 있을지 모르겠는데, Cydia 시장도 거기서 거깁니다.
본래 작은 시장(전세계적으로 크게 잡아봤자 10만명이 안 되는 수준)에 껌 한통 가격으로 판매하는 거라 이걸로 뭘 어떻게 해볼 수가 없습니다.

돈 주고 사는 사람은 아무리 많이 잡아도 10만명, 실제론 1~2만명 수준 밖에 안 된다고 파악하는데 무료로 받아가는 사람은 못해도 20만명, 많게 잡으면 100만명 이상 수준입니다.

즉, 실제로 판매된 수는 500카피 정도 밖에 안 되는데 오는 메일은 수천통인 경우가 있습니다.
500카피가 무척 적어보일텐데 이정도는 아주 많은 수치입니다.

그냥 무료로 뿌린건 더 엄청나죠.
바로 20만 다운로드를 찍었고 그건 정말 짧은 시간에 1만통을 쏟아내더군요.
CCToggles가 그랬습니다.

돈도 안 되는데 많이 받아가니까 기쁜 마음에 설쳐댔습니다.

크랙 쓰셔도 됩니다. 괜찮아요.
귀찮아서 대충대충 크랙 대처하기도 하고, 어떤건 기를 쓰고 대처하기도 합니다.
그렇게 열심히 엿먹이려 노력하고 있으니까 쓰셔도 돼요.

근데 크랙 유저이면서 정품 유저인것처럼 절 속이려 들진 마세요.
다른 개발자들을 농락하지 마세요.

문제가 생기면 일단 '크랙이라 그런가?'라는 질문부터 먼저 해보시기 바랍니다.
이게 타당한 순서예요.

그 뒤 개발자에게 묻기 전에 정품의 문제 발생 여부를 파악한 뒤에 행동하세요.

안 그러면 어차피 씹힐테니까요.

왜 씹냐구요? 기분 나쁘니까 씹는겁니다.

결론


결론은 뭐다?

정품만 쓰면서, 문제 발생 시 보고만 제대로 하셔도 웬만한건 개발자가 다 해결해줍니다.

하지만 개발자는 신이 아닙니다.
문제를 해결해달라고 메일을 보내봤자 그게 뭔지 설명을 안 해주면 알 수가 없어요.

좋은 보고를 해주세요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment