Version: 2021.1
언어: 한국어
소셜 API
iOS에서 크래시 버그 신고

iOS 기기 문제 해결

iOS의 경우 Unity 에디터에서 앱이 완벽하게 작동하지만 기기에서 작동하지 않거나 시작되지 않는 경우가 있습니다. 이러한 문제는 대개 코드 또는 콘텐츠 품질과 관련이 있습니다. 이 섹션에서는 가장 일반적인 시나리오에 대해 설명합니다.

게임이 잠시 후부터 반응하지 않습니다. Xcode의 상태 표시줄에 “인터럽트”라고 표시됩니다.

이 현상이 발생하는 이유는 다양합니다. 일반적인 원인은 다음과 같습니다.

  • 초기화하지 않은 변수 등의 스크립팅 오류
  • Thumb 컴파일한 타사 네이티브 라이브러리 사용. 이러한 라이브러리는 iOS SDK 링커에서 알려진 문제를 유발할 수 있으며 랜덤 충돌을 일으킬 수 있습니다.
  • 직렬화된 스크립트 프로퍼티에 대해 값 타입이 포함된 일반 타입을 파라미터(예: List<int>, List<SomeStruct>, List<SomeEnum>)로 사용
  • 관리되는 코드 스트리핑 활성화 시 리플렉션 사용
  • 네이티브 플러그인 인터페이스 내 오류(관리되는 코드 메서드 서명이 네이티브 코드 함수 서명과 일치하지 않음) XCode 디버거 콘솔의 정보를 통해 이러한 문제를 발견할 수 있는 경우가 많습니다(Xcode 메뉴: View > Debug Area > Activate Console).

Xcode 콘솔에서 "Program received signal: “SIGBUS” 또는 EXC_BAD_ACCESS 오류가 발생합니다.

일반적으로 이 메시지는 애플리케이션이 NullReferenceException을 수신할 때 iOS 기기에 나타납니다. 발생한 오류는 다음의 두 가지 방법으로 파악할 수 있습니다.

매니지드 스택 추적

Unity는 소프트웨어 기반 NullReferenceException 처리 기능을 제공합니다. AOT 컴파일러는 메서드 또는 변수가 오브젝트에서 액세스될 때마다 null 레퍼런스를 빠르게 검사할 수 있는 기능을 제공합니다. 이 기능은 스크립트 성능에 영향을 줄 수 있기 때문에 빌드 개발용으로만 활성화(Build Settings 다이얼로그에서 script debugging 옵션 활성화)합니다. 모든 작업을 올바르게 완료하고 오류가 실제로 .NET 코드에서 발생하는 경우 EXC_BAD_ACCESS가 더 이상 표시되지 않습니다. 대신에 .NET 예외 텍스트가 Xcode 콘솔에 출력(또는 코드가 “catch” 문으로 처리)됩니다. 일반적인 출력은 다음과 같습니다.

Unhandled Exception: System.NullReferenceException: A null value was found where an object instance was required.
  at DayController+$handleTimeOfDay$121+$.MoveNext () [0x0035a] in DayController.js:122

오류가 코루틴으로 동작하는 DayController 클래스의 handleTimeOfDay 메서드에서 발생했음을 나타납니다. 또한 스크립트 코드인 경우에는 보통 정확한 라인 번호(예: “DayController.js:122”)가 표시됩니다. 문제가 있는 라인은 다음과 같은 모습일 수 있습니다.

 Instantiate(_imgwww.assetBundle.mainAsset);

이 문제는 예를 들어 에셋 번들이 제대로 다운로드 되었는지 먼저 확인하지 않고 스크립트에서 에셋 번들에 액세스했을 때 발생할 수 있습니다.

네이티브 스택 추적

네이티브 스택은 오류 조사를 위한 매우 강력한 툴이지만, 약간의 전문 지식이 있어야 사용할 수 있습니다. 또한 이러한 네이티브(하드웨어 메모리 액세스) 오류가 일어난 후에는 대개 작업을 계속할 수 없습니다. 네이티브 스택 추적을 확인하려면 Xcode 디버거 콘솔에 bt all 을 입력하십시오. 출력된 스택 추적 내용에는 오류가 발생한 위치에 대한 힌트가 있으니 자세히 살펴봐야 합니다. 예를 들면 다음과 같습니다.

...
Thread 1 (thread 11523): 

1. 0 0x006267d0 in m_OptionsMenu_Start ()
1. 1 0x002e4160 in wrapper_runtime_invoke_object_runtime_invoke_void__this___object_intptr_intptr_intptr ()
1. 2 0x00a1dd64 in mono_jit_runtime_invoke (method=0x18b63bc, obj=0x5d10cb0, params=0x0, exc=0x2fffdd34) at /Users/mantasp/work/unity/unity-mono/External/Mono/mono/mono/mini/mini.c:4487
1. 3 0x0088481c in MonoBehaviour::InvokeMethodOrCoroutineChecked ()
...

먼저 메인 스레드인 “Thread 1”에 대한스택 추적을 찾아야 합니다. 스택 추적 내용의 첫 번째 라인은 오류가 발생한 위치를 알려줍니다. 이 예에서는 NullReferenceException이 OptionsMenu 스크립트의 Start 메서드 내에서 발생했습니다. 이 메서드 구현을 자세히 살펴보면 문제의 원인을 밝혀낼 수 있습니다. 일반적으로 NullReferenceException은 초기화 순서에 대한 잘못된 가정이 있을 때 Start 메서드 내에서 발생합니다. 일부의 경우 스택 추적 내용이 부분적으로만 디버거 콘솔에 표시됩니다.

Thread 1 (thread 11523): 

1. 0 0x0062564c in start ()

이 메시지는 애플리케이션의 릴리스 빌드 중에 네이티브 심볼이 삭제되었음을 의미합니다. 다음 과정을 통해 풀 스택 트레이스를 얻을 수 있습니다.

  • 기기에서 애플리케이션을 제거합니다.
  • 모든 타겟을 클린합니다.
  • 빌드 후 실행합니다.
  • 위에서 설명한 대로 스택 트레이스를 다시 얻습니다.

Unity iOS 애플리케이션에 외부 라이브러리를 링크할 때 EXC_BAD_ACCESS가 발생하기 시작합니다.

일반적으로 외부 라이브러리가 ARM Thumb 명령 집합으로 컴파일될 때 발생합니다. 현재 이러한 라이브러리는 Unity와 호환되지 않습니다. 이 문제는 Thumb 명령 없이 라이브러리를 다시 컴파일하면 쉽게 해결할 수 있습니다. 다음의 단계에 따라 라이브러리의 Xcode 프로젝트에 대해 이 작업을 수행할 수 있습니다.

  • Xcode의 메뉴에서 View > Navigators > Show Project Navigator를 선택합니다.
  • Unity-iPhone 프로젝트를 선택한 후 Build Settings 탭을 활성화합니다.
  • 검색 필드에 Other C Flags를 입력합니다.
  • 여기에서 -mno-thumb 플래그를 추가한 후 라이브러리를 다시 빌드합니다.

라이브러리 소스를 사용할 수 없다면 해당 라이브러리 공급자에게 thumb을 사용하지 않은 버전을 요청해야 합니다.

Xcode 콘솔에 “WARNING -> applicationDidReceiveMemoryWarning()”이라는 메시지가 나타나고 그 후 애플리케이션이 즉시 충돌합니다.

때때로 Program received signal: “0” 같은 메시지가 표시될 수 있습니다. 이 경고 메시지는 치명적이지는 않지만, iOS에 메모리가 부족함을 표시하고 애플리케이션이 추가 메모리를 확보하도록 요청합니다. 대개 메일 같은 백그라운드 프로세스의 메모리를 비우면 애플리케이션을 계속 실행할 수 있습니다. 하지만 애플리케이션이 메모리를 계속 사용하고 추가 메모리를 요구하면 OS는 현재 사용 중인 것을 포함하여 애플리케이션을 종료합니다. Apple은 안전한 메모리 사용에 관해 문서화하지 않았지만, 경험으로 비추어볼 때 전체 기기 RAM의 50% 미만을 사용하는 애플리케이션은 심각한 메모리 사용 문제를 겪지 않습니다. 이때 애플리케이션이 사용하는 RAM 양에 관한 지표를 사용해야 합니다. 애플리케이션 메모리 사용은 다음 세 가지 주요 컴포넌트로 구성됩니다.

  • 애플리케이션 코드(OS는 애플리케이션 코드를 RAM에 로드하고 유지해야 하지만, 필요한 경우 일부를 폐기할 수 있음)
  • 네이티브 힙(엔진이 상태, 에셋 등을 RAM에 저장하는 데 사용함)
  • 관리되는 힙(Mono 런타임이 C# 오브젝트를 유지하는 데 사용함)
  • GLES 드라이버 메모리 풀: 텍스처, 프레임버퍼, 컴파일된 셰이더 등. 애플리케이션 메모리 사용은 두 가지 Xcode Instruments 툴, 즉 Activity MonitorAllocations 로 추적할 수 있습니다. Xcode Run 메뉴에서 Product > Profile 로 이동하거나, 또는 Xcode > Open Developer Tools > Instruments 로 이동해서 특정 툴을 선택할 수 있습니다. Activity Monitor 툴은 애플리케이션에서 사용하는 RAM의 총량으로 간주될 수 있는 Real memory 를 비롯하여 모든 프로세스 통계를 보여줍니다. 참고: OS 및 기기 HW 버전 조합은 메모리 사용 수치에 상당한 영향을 줄 수 있으므로 다른 기기에서 얻은 수치를 비교할 때는 주의해야 합니다.

참고: 내부 프로파일러는 .NET 스크립트에서 할당한 힙만을 보여 줍니다. 전체 메모리 사용량은 위에 나타난 것처럼 Xcode Instruments를 통하여 판단할 수 있습니다. 이 수치는 애플리케이션 바이너리 파트, 일부 스탠다드 프레임워크 버퍼, Unity 엔진 내부 스테이트 버퍼, .NET 런타임 힙(내부 프로파일러에 의해 출력된 숫자), GLES 드라이버 힙, 그 외 일부 다른 것을 포함합니다.

다른 툴은 애플리케이션의 모든 할당량을 표시하며, 여기에는 네이티브 힙과 관리되는 힙 통계가 모두 포함됩니다. 중요한 통계는 Net bytes 값입니다.

메모리 사용량을 낮게 유지하려면

  • 가장 강력한 iOS 스트리핑 옵션을 사용하여 애플리케이션 바이너리 크기를 줄이고 서로 다른 .NET 라이브러리에 대한 불필요한 종속성을 피해야 합니다. 자세한 내용은 매뉴얼의 플레이어 설정 및 빌트된 iOS 플레이어 크기 최적화 페이지를 참조하십시오.
  • 콘텐츠 크기를 줄이십시오. 텍스처에는 PVRTC 압축을 사용하고 저 폴리곤 모델을 사용해야 합니다. 자세한 정보는 매뉴얼의 파일 크기 줄이기 페이지를 참조하십시오.
  • 스크립트에 필요 이상의 메모리를 할당하지 마십시오. 내부 프로파일러를 사용하여 Mono 힙 크기와 사용량을 추적하십시오.

사용 가능한 메모리 양을 OS에 쿼리하는 것은 애플리케이션 성능을 측정하는 데 있어 좋은 방법처럼 보입니다. 그러나 OS가 대량의 동적 버퍼와 캐시를 사용하기 때문에 사용 가능 메모리 통계를 신뢰하기 쉽지 않습니다. 신뢰할 수 있는 유일한 접근법은 작성한 애플리케이션의 메모리 소비량을 계속 추적하고 이를 주 기준으로 삼는 것입니다. 위에서 설명한 툴을 통해 그래프가 시간에 따라 어떻게 변하는지, 특히 새 레벨을 로드한 후의 변화에 주의를 기울이십시오.

Xcode에서 게임을 실행했을 때에는 제대로 실행되지만, 기기에서 수동으로 실행하면 첫 레벨 로드 중에 게임이 충돌합니다.

이에 대한 이유로는 여러 가지가 있습니다. 자세한 내용을 확인하려면 기기 로그를 살펴봐야 합니다. 기기를 Mac에 연결하고 Xcode를 실행한 후 메뉴에서 Window > Devices and Simulators 를 선택하십시오. 그런 다음 창의 왼쪽 툴바에서 기기를 선택하고 Show the device console 버튼을 클릭한 다음 최신 메시지를 신중히 검토하십시오. 또한 크래시 보고서를 조사해야 할 수도 있습니다. 크래시 보고서를 받는 방법은 http://developer.apple.com/iphone/library/technotes/tn2008/tn2151.html을 참조하십시오.

Xcode Organizer 콘솔에 “killed by SpringBoard” 메시지가 나타납니다.

정확히 문서화되어 있지는 않으나, iOS 애플리케이션의 경우 첫 프레임을 렌더링하고 입력을 프로세싱하는 데에 시간 제한이 있습니다. 작성한 애플리케이션이 이 시간 제한을 초과하면 SpringBoard가 이 애플리케이션을 강제 종료합니다. 이 문제는 예를 들어 첫 씬이 너무 큰 애플리케이션에서 발생할 수 있습니다. 이 문제를 피하기 위해 권장할 수 있는 방법은 스플래시 화면만을 보여 주는 작은 최초 씬을 만들고 yield 를 사용하여 한두 프레임 대기하고, 그 후 실제 씬을 로딩하기 시작하는 것입니다. 다음과 같은 간단한 코드를 사용하면 됩니다.

IEnumerator Start() {
    yield return new WaitForEndOfFrame();
// Do not forget using UnityEngine.SceneManagement directive
    SceneManager.LoadScene("Test");
}

Type.GetProperty() 또는 Type.GetValue()가 기기에 크래시를 일으킴

현재 Type.GetProperty()Type.GetValue().NET 2.0 Subset 프로파일에서만 지원됩니다. .NET API 호환성 레벨은 플레이어 설정에서 선택할 수 있습니다.

참고: Type.GetProperty()Type.GetValue() 는 관리되는 코드 스트리핑과 호환되지 않을 수 있으며 제외되어야 할 수도 있습니다(이를 위해 스트리핑 프로세스 중에 직접 작성한 커스텀 스트립 불가 타입 리스트를 사용할 수도 있습니다). 자세한 내용은 iOS 플레이어 크기 최적화 가이드를 참조하십시오.

“ExecutionEngineException: Attempting to JIT compile method ‘SometType`1<SomeValueType>:.ctor ()’ while running with –aot-only.” 메시지가 발생하면서 게임이 크래시합니다.

iOS의 Mono .NET 구현은 AOT(네이티브 코드로 사전 컴파일) 기술에 기반하는데, 여기에는 몇 가지 제한이 따릅니다. AOT 기술은 다른 코드에서 명시적으로 사용되는 일반 타입 메서드(값 타입이 일반 파라미터로 사용됨)만 컴파일합니다. 이러한 메서드가 리플렉션을 통해 또는 네이티브 코드(즉, 직렬화 시스템)에서 사용되는 경우 AOT 컴파일 과정을 건너뜁니다. AOT 컴파일러는 스크립트 코드 어딘가에 더미 메서드를 추가하여 코드를 포함하도록 나타낼 수 있습니다. 이렇게 하면 누락된 메서드를 참조하여 미리 컴파일할 수 있습니다.

void _unusedMethod() {
    var tmp = new SomeType<SomeValueType>();
}

참고: 값 타입은 기본 타입, 열거형 및 구조체입니다.

System.Security.Cryptography와 관리되는 코드 스트리핑을 함께 사용 시 장치에서 여러 가지 크래시가 발생합니다.

.NET Cryptography 서비스는 리플렉션에 강력히 의존하며 따라서 정적 코드 분석을 포함하는 관리되는 코드 스트리핑과 호환되지 않습니다. 경우에 따라 이러한 크래시에 대한 가장 쉬운 해결책은 System.Security.Crypography 네임스페이스 전체를 스트리핑 프로세스에서 제외하는 것입니다.

스트리핑 프로세스는 Unity 프로젝트의 Assets 폴더에 커스텀 link.xml 파일을 추가하여 커스터마이즈할 수 있습니다. 이렇게 하면 스트리핑에서 제외할 타입과 네임스페이스를 지정할 수 있습니다. 자세한 내용은 iOS 플레이어 크기 최적화 가이드를 참조하십시오.

link.xml

<linker>
       <assembly fullname="mscorlib">
               <namespace fullname="System.Security.Cryptography" preserve="all"/>
       </assembly>
</linker>

System.Security.Cryptography.MD5와 관리되는 코드 스트리핑을 함께 사용 시 애플리케이션이 충돌합니다.

위의 팁을 참고하거나, 다음과 같이 스크립트 코드에 특정 클래스에 대한 레퍼런스를 추가하여 이 문제를 해결할 수 있습니다.

object obj = new MD5CryptoServiceProvider();

“Ran out of trampolines of type 0/1/2” 런타임 오류

이 오류는 보통 많은 재귀적 제네릭을 사용하는 경우에 발생합니다. AOT 컴파일러에 타입 0, 타입 1 또는 타입 2의 트램펄린을 추가로 할당하도록 알릴 수 있습니다. 추가 AOT 컴파일러 커맨드 라인 옵션은 플레이어 설정의 Other Settings 섹션에서 지정할 수 있습니다. 타입 0 트램펄린에는 ntrampolines=ABCD를 지정하십시오. 여기서 ABCD는 필요한 새 트램펄린의 수(예: 4096)입니다. 타입 1 트램펄린에는nrgctx-trampolines=ABCD를 지정하고 타입 2 트램펄린에는 nimt-trampolines=ABCD를 지정하십시오.

Xcode Unity iOS를 업그레이드하면 런타임이 “Unity iPhone Basic을 사용 중입니다. 게임에서 Unity 스플래시 화면을 제거할 수 없습니다.”라는 메시지를 표시하며 실패합니다.

최신 Xcode가 릴리스되면서 PNG 압축 및 최적화 툴에 몇 가지 변경 사항이 적용되었습니다. 이러한 변경으로 인해 스플래시 화면 수정을 위한 Unity iOS 런타임 검사에서 긍정 오류(false positive)가 발생할 수 있습니다. 이러한 문제가 발생하면 Unity를 공개적으로 이용 가능한 최신 버전으로 업그레이드하십시오. 그래도 문제가 지속되면 다음 해결 방법을 고려하십시오.

  • Unity에서 빌드할 때 Xcode 프로젝트를 (덧붙이는 대신) 완전히 처음부터 교체해 보십시오.
  • 이미 설치된 프로젝트를 기기에서 삭제합니다.
  • Xcode에서 프로젝트를 정리합니다(Product > Clean).
  • Xcode의 파생 데이터 폴더를 비웁니다(Xcode > Preferences > Locations).

이 문제가 여전히 해결되지 않으면 Xcode에서 PNG 재압축을 비활성화할 수 있습니다.

  • Xcode 프로젝트를 엽니다.
  • Unity-iPhone 프로젝트를 선택합니다.
  • Build Settings 탭을 선택합니다.
  • Compress PNG files 옵션을 찾은 후 NO 로 설정합니다.

Unity 에디터와 Android에서는 WWW 다운로드가 제대로 작동하지만 iOS에서는 작동하지 않습니다.

가장 흔히 저지르는 실수는 WWW 다운로드가 항상 별도의 스레드에서 일어난다고 가정하는 것입니다. 일부 플랫폼에서는 맞을 수 있지만, 이를 당연한 사실로 받아들이면 안 됩니다. WWW 상태를 추적하는 가장 좋은 방법은 yield 문을 사용하거나 Update 메서드로 상태를 검사하는 것입니다. 이때 사용 중인 while 루프는 사용하지 마십시오.

스크립트에서 호출된 네이티브 함수를 통해 Cocoa를 사용할 때 “PlayerLoop called recursively!” 오류가 발생합니다.

UI에 대한 일부 작업을 수행하면 iOS가 창을 즉시 다시 그립니다. UIViewController가 있는 UIView를 메인 UIWindow에 추가하는 것이 가장 일반적인 예입니다. 스크립트에서 네이티브 함수를 호출하면 Unity의 PlayerLoop 내에서 발생하므로 PlayerLoop가 재귀적으로 호출됩니다. 이 경우 waitUntilDone 이 false로 설정된 performSelectorOnMainThread 메서드를 사용할 것을 고려해야 합니다. 그러면 Unity의 PlayerLoop 호출 간에 실행할 작업을 예약하도록 iOS에 알립니다.

프로파일러 또는 디버거가 iOS 기기에서 게임이 실행되는 것을 감지하지 못합니다.

  • 개발용 빌드를 빌드했는지 확인하고, 필요한 경우 Script DebuggingAutoconnect Profiler 체크박스를 선택했는지 확인하십시오.
  • 기기에서 실행되는 애플리케이션은 UDP 포트 54997에서 225.0.0.222로 멀티캐스트 브로드캐스트를 합니다. 네트워크 설정이 이 트래픽을 허용하는지 확인해야 합니다. 그러면 프로파일러는 55000–55511 범위의 포트에 있는 원격 기기에 연결하여 기기로부터 프로파일러 데이터를 가져올 것입니다. 이 포트는 TCP 액세스를 위해 열려 있어야 합니다.

DLL 누락

애플리케이션이 에디터에서 정상적으로 동작하지만 iOS 프로젝트에 오류가 발생하는 경우 DLL(예: I18N.dll, I19N.West.dll) 누락이 그 원인일 수 있습니다. 이 경우 Unity.app 내에서 해당 dll을 복사하여 프로젝트의 Assets\Plugins 폴더에 붙여넣으십시오. Unity 앱 내 DLL의 위치는 다음과 같습니다. Unity.app\Contents\Frameworks\Mono\lib\mono\unity 그런 다음 프로젝트의 스트리핑 레벨을 확인하여 빌드 최적화 시 DLL의 클래스가 제거되지 않도록 해야 합니다. iOS 스트리핑 레벨에 대한 자세한 내용은 iOS 최적화 페이지를 참조하십시오.

Xcode 디버거 콘솔이 다음 메시지를 출력합니다. ExecutionEngineException: Attempting to JIT compile method ‘(wrapper native-to-managed) Test:TestFunc (int)’ while running with –aot-only

일반적으로 이러한 메시지는 관리되는 함수 델리게이트가 네이티브 함수에 전달되었지만 애플리케이션이 빌드될 때 필수 래퍼 코드를 생성하지 않았을 경우에 수신됩니다. 이 경우 네이티브 코드에 델리게이트로 전달할 메서드를 AOT 컴파일러에 알려줄 수 있습니다. 이렇게 하려면 MonoPInvokeCallbackAttribute 커스텀 속성을 추가하십시오. 현재는 정적 메서드만 네이티브 코드에 델리게이트로 전달할 수 있습니다.

예시 코드:

using UnityEngine;
using System.Collections;
using System;
using System.Runtime.InteropServices;
using AOT;

public class NewBehaviourScript : MonoBehaviour {
    [DllImport ("__Internal")]
    private static extern void DoSomething (NoParamDelegate del1, StringParamDelegate del2);

    delegate void NoParamDelegate ();
    delegate void StringParamDelegate (string str);
    
    [MonoPInvokeCallback(typeof(NoParamDelegate))]
    public static void NoParamCallback() {
        Debug.Log ("Hello from NoParamCallback");
    }
    
    [MonoPInvokeCallback(typeof(StringParamDelegate))]
    public static void StringParamCallback(string str) {
        Debug.Log(string.Format("Hello from StringParamCallback {0}", str));
    }

    // Use this for initialization
    void Start() {
        DoSomething(NoParamCallback, StringParamCallback);
    }
}

Xcode에서 다음 컴파일 오류가 발생하였습니다. “ld : unable to insert branch island. No insertion point available. for architecture armv7”, “clang: error: linker command failed with exit code 1 (use -v to see invocation)”

이 오류는 단일 모듈에 코드가 너무 많다는 것을 의미하며, 일반적으로 빌드에 스크립트 코드 또는 큰 외부 .NET 어셈블리를 포함한 경우에 발생합니다. 스크립트 디버깅을 활성화하면 각 함수에 상당히 많은 명령을 추가하여 한도에 더 쉽게 도달하게 만들기 때문에 문제가 더 심각해질 수 있습니다.

플레이어 설정에서 관리되는 코드 스트리핑을 활성화하면 문제 해결에 도움이 됩니다. 특히 큰 외부 .NET 어셈블리가 포함된 경우는 더욱 그렇습니다. 그래도 문제가 지속되면 사용자 스크립트 코드를 여러 개의 어셈블리로 분할하는 것이 최선의 방법입니다. 이를 위한 가장 쉬운 방법은 일부 코드를 Plugins 폴더로 옮기는 것입니다. 이 위치의 코드는 다른 어셈블리로 들어갑니다. 또한 특수 폴더 이름이 스크립트 컴파일에 영향을 미치는 방식에 관한 정보도 참조하십시오.


  • 2018–06–14 페이지 수정됨
소셜 API
iOS에서 크래시 버그 신고