Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Omówienie
Czasami błędy programowania mogą powodować awarie w natywnym Objective-C środowisku uruchomieniowym. W przeciwieństwie do wyjątków języka C#, nie wskazują one określonego wiersza w kodzie, który można naprawić. Czasami mogą być trywialne, aby znaleźć i naprawić, a inne czasy mogą być niezwykle trudne do wytropić.
Przyjrzyjmy się kilku rzeczywistym przykładom awarii natywnych.
Przykład 1. Niepowodzenie asercji
Oto kilka pierwszych wierszy awarii w prostej aplikacji testowej (te informacje będą znajdować się w okienku danych wyjściowych aplikacji):
2014-10-15 16:18:02.364 NSOutlineViewHottness[79111:1304993] *** Assertion failure in -[NSTableView _uncachedRectHeightOfRow:], /SourceCache/AppKit/AppKit-1343.13/TableView.subproj/NSTableView.m:1855
2014-10-15 16:18:02.364 NSOutlineViewHottness[79111:1304993] NSTableView variable rowHeight error: The value must be > 0 for row 0, but the delegate <NSOutlineViewHottness_HotnessViewDelegate: 0xaa01860> gave -1.000.
2014-10-15 16:18:02.378 NSOutlineViewHottness[79111:1304993] *** Assertion failure in -[NSTableView _uncachedRectHeightOfRow:], /SourceCache/AppKit/AppKit-1343.13/TableView.subproj/NSTableView.m:1855
2014-10-15 16:18:02.378 NSOutlineViewHottness[79111:1304993] NSTableView variable rowHeight error: The value must be > 0 for row 0, but the delegate <NSOutlineViewHottness_HotnessViewDelegate: 0xaa01860> gave -1.000.
2014-10-15 16:18:02.381 NSOutlineViewHottness[79111:1304993] (
0 CoreFoundation 0x91888343 __raiseError + 195
1 libobjc.A.dylib 0x9a5e6a2a objc_exception_throw + 276
2 CoreFoundation 0x918881ca +[NSException raise:format:arguments:] + 138
3 Foundation 0x950742b1 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 118
4 AppKit 0x975db476 -[NSTableView _uncachedRectHeightOfRow:] + 373
5 AppKit 0x975db2f8 -[_NSTableRowHeightStorage _uncachedRectHeightOfRow:] + 143
6 AppKit 0x975db206 -[_NSTableRowHeightStorage _cacheRowHeights] + 167
7 AppKit 0x975db130 -[_NSTableRowHeightStorage _createRowHeightsArray] + 226
8 AppKit 0x975b5851 -[_NSTableRowHeightStorage _ensureRowHeights] + 73
9 AppKit 0x975b5790 -[_NSTableRowHeightStorage computeTableHeightForNumberOfRows:] + 89
10 AppKit 0x975b4c38 -[NSTableView _totalHeightOfTableView] + 220
Wiersze poprzedzone liczbami są natywnym śladem stosu. Z tego można zobaczyć, że awaria wystąpiła gdzieś wewnątrz NSTableView obsługi wysokości wierszy. Następnie NSAssertionHandler zostanie wyzwolony element NSException (objc_exception_throw) i zobaczymy błąd asercji:
rowHeight error: The value must be > 0 for row 0, but the delegate
<NSOutlineView_ViewDelegate: 0xaa01860> gave -1.000
Gdy to zobaczysz, jest dość jasne, że niektóre NSOutlineViewDelegate metody zwracają liczbę ujemną. To był problem:
public override nfloat GetRowHeight (NSTableView tableView, nint row)
{
return -1;
}
Przykład 2: Wywołanie zwrotne przeskoczyło do środka nigdzie
Stacktrace:
at <unknown> <0xffffffff>
at (wrapper managed-to-native) MonoMac.AppKit.NSApplication.NSApplicationMain (int,string[]) <IL 0x000a4, 0xffffffff>
at MonoMac.AppKit.NSApplication.Main (string[]) [0x00041] in /Users/donblas/Programming/xamcore-master/src/AppKit/NSApplication.cs:107
at NSOutlineViewHottness.MainClass.Main (string[]) [0x00007] in /Users/donblas/Programming/Local/NSOutlineViewHottness/NSOutlineViewHottness/Main.cs:14
at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object (object,intptr,intptr,intptr) <IL 0x00050, 0xffffffff>
Native stacktrace:
Debug info from gdb:
(lldb) command source -s 0 '/tmp/mono-gdb-commands.qrHllW'
Executing commands in '/private/tmp/mono-gdb-commands.qrHllW'.
(lldb) process attach --pid 79229
Process 79229 stopped
Executable module set to "/Users/donblas/Programming/Local/NSOutlineViewHottness/NSOutlineViewHottness/bin/Debug/NSOutlineViewHottness.app/Contents/MacOS/NSOutlineViewHottness".
Architecture set to: i386-apple-macosx.
(lldb) thread list
Process 79229 stopped
* thread #1: tid = 0x142776, 0x9af75e1a libsystem_kernel.dylib`__wait4 + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
thread #2: tid = 0x142790, 0x9af768d2 libsystem_kernel.dylib`kevent64 + 10, queue = 'com.apple.libdispatch-manager'
thread #3: tid = 0x142792, 0x9af75e6e libsystem_kernel.dylib`__workq_kernreturn + 10
thread #4: tid = 0x142794, 0x9af6fa6a libsystem_kernel.dylib`semaphore_wait_trap + 10
thread #5: tid = 0x142795, 0x9af75772 libsystem_kernel.dylib`__recvfrom + 10
thread #6: tid = 0x142799, 0x9af75e6e libsystem_kernel.dylib`__workq_kernreturn + 10
thread #7: tid = 0x14279a, 0x9af75e6e libsystem_kernel.dylib`__workq_kernreturn + 10
thread #8: tid = 0x14279b, 0x9af75e6e libsystem_kernel.dylib`__workq_kernreturn + 10
thread #9: tid = 0x1427f8, 0x9af75e6e libsystem_kernel.dylib`__workq_kernreturn + 10
thread #10: tid = 0x1427fe, 0x9af6fa2e libsystem_kernel.dylib`mach_msg_trap + 10
(lldb) thread backtrace all
* thread #1: tid = 0x142776, 0x9af75e1a libsystem_kernel.dylib`__wait4 + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
* frame #0: 0x9af75e1a libsystem_kernel.dylib`__wait4 + 10
frame #1: 0x986bfb25 libsystem_c.dylib`waitpid$UNIX2003 + 48
frame #2: 0x028ba36d libmono-2.0.dylib`mono_handle_native_sigsegv(signal=11, ctx=0x03115fe0) + 541 at mini-exceptions.c:2323
frame #3: 0x0290a8bb libmono-2.0.dylib`mono_arch_handle_altstack_exception(sigctx=<unavailable>, fault_addr=<unavailable>, stack_ovf=0) + 155 at exceptions-x86.c:1159
frame #4: 0x0280b4fd libmono-2.0.dylib`mono_sigsegv_signal_handler(_dummy=<unavailable>, info=<unavailable>, context=<unavailable>) + 445 at mini.c:6861
frame #5: 0x91ef403b libsystem_platform.dylib`_sigtramp + 43
frame #6: 0x9a5dd0bd libobjc.A.dylib`objc_msgSend + 45
frame #7: 0x96bcec03 libsystem_trace.dylib`_os_activity_initiate + 89
frame #8: 0x9773ba91 AppKit`-[NSApplication sendAction:to:from:] + 548
frame #9: 0x9773b82d AppKit`-[NSControl sendAction:to:] + 102
frame #10: 0x97934d36 AppKit`__26-[NSCell _sendActionFrom:]_block_invoke + 176
frame #11: 0x96bcec03 libsystem_trace.dylib`_os_activity_initiate + 89
frame #12: 0x97787975 AppKit`-[NSCell _sendActionFrom:] + 161
frame #13: 0x979188ea AppKit`-[NSButtonCell _sendActionFrom:] + 55
frame #14: 0x979366e6 AppKit`__48-[NSCell trackMouse:inRect:ofView:untilMouseUp:]_block_invoke965 + 43
frame #15: 0x96bcec03 libsystem_trace.dylib`_os_activity_initiate + 89
frame #16: 0x977a3d00 AppKit`-[NSCell trackMouse:inRect:ofView:untilMouseUp:] + 2815
frame #17: 0x977a2df4 AppKit`-[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 524
frame #18: 0x977a233b AppKit`-[NSControl mouseDown:] + 762
frame #19: 0x97cbc112 AppKit`-[_NSThemeWidget mouseDown:] + 378
frame #20: 0x97d36d74 AppKit`-[NSWindow _reallySendEvent:] + 12353
frame #21: 0x977201f9 AppKit`-[NSWindow sendEvent:] + 409
frame #22: 0x976cdc67 AppKit`-[NSApplication sendEvent:] + 4679
frame #23: 0x9754807c AppKit`-[NSApplication run] + 1003
Jest to problem, który był o wiele trudniej wytropić. Gdy zobaczysz at <unknown> <0xffffffff> lub MonoMac.ObjCRuntime.Runtime.GetNSObject (IntPtr ptr) w górnej części śladu zarządzanego stosu, sugeruje to, że próbujemy wykonać kod zarządzany z obiektem, który został odebrany jako bezużyteczny. Natywne śledzenie stosu jest wyświetlane trackMouse:inRect:ofView:untilMouseUp w NSCell _sendActionFrom taki sposób, abyśmy gdzieś obsługiwali zdarzenie kliknięcia, próbując wrócić do języka C# i umierać.
Ogólnie rzecz biorąc, błędy takie jak te są trudne do śledzenia. GC.Collect(2) Dodano do procedury obsługi przycisków, aby pomóc w śledzeniu tego problemu (wymuszanie odzyskiwania pamięci), aby problem był powtarzalny.
mainWindowController.Window.StandardWindowButton (NSWindowButton.CloseButton).Activated += HandleActivated;
Zwrócony NSButton przez StandardWindowButton() element został zebrany, mimo że zdarzenie zostało do niego zarejestrowane (to jest usterka). Gdy spróbujemy wywołać to zdarzenie, klikając przycisk, który został usunięty, ulega awarii.
Chociaż nie była to główna przyczyna tego konkretnego problemu, takie ślady stosu mogą być również spowodowane przez nieprawidłowe podpisy metody w funkcjach [Export]ed do Objective-C. Jeśli na przykład metoda oczekuje, że parametr będzie parametrem out string i wpiszesz go jako string, możemy ulec awarii w taki sam sposób.
Przykład 3. Wywołania zwrotne i obiekty zarządzane
Wiele interfejsów API cocoa obejmuje "wywołanie z powrotem" przez bibliotekę, gdy wystąpi pewne zdarzenie, dając szansę na odpowiedź lub gdy niektóre dane są potrzebne do wykonania zadania. Chociaż można myśleć przede wszystkim o wzorcach Delegat i DataSource , istnieje wiele interfejsów API, które działają w ten sposób. Jeśli na przykład zastąpisz metody elementu NSView , a następnie wstawisz je do drzewa wizualnego, możesz oczekiwać, że zestaw AppKit wywoła cię z powrotem po wystąpieniu niektórych zdarzeń.
W prawie wszystkich przypadkach środowisko Xamarin.Mac poprawnie uniemożliwi obiektowi zarządzanemu obiekt docelowy tych wywołań zwrotnych z pamięci, podczas gdy nadal mogą być wywoływane z powrotem. Jednak rzadko usterki w powiązaniu mogą to zakłócić. W takim przypadku można zobaczyć nieprzyjemne awarie podobne do następujących:
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libsystem_kernel.dylib 0x98c2f69a __pthread_kill + 10
1 libsystem_pthread.dylib 0x90341f19 pthread_kill + 101
2 libsystem_c.dylib 0x9453feee abort + 156
3 libmonosgen-2.0.dylib 0x020bfba5 mono_handle_native_sigsegv + 757
4 libmonosgen-2.0.dylib 0x0210b812 mono_arch_handle_altstack_exception + 162
5 libmonosgen-2.0.dylib 0x0200c55e mono_sigsegv_signal_handler + 446
6 libsystem_platform.dylib 0x9513003b _sigtramp + 43
7 ??? 0xffffffff 0 + 4294967295
8 libmonosgen-2.0.dylib 0x0200c3a0 mono_sigill_signal_handler + 48
9 com.apple.AppKit 0x99f76041 -[NSView setFrame:] + 448
10 com.apple.AppKit 0x9a1fd4ea -[NSToolbarView adjustToWindow:attachedToEdge:] + 198
11 com.apple.AppKit 0x9a1fd414 -[NSToolbar _adjustViewToWindow] + 68
12 com.apple.AppKit 0x9a01eb0d -[NSToolbar _windowWillShowToolbar] + 79
Ten przewodnik pomoże Ci w śledzeniu usterek tego rodzaju, jeśli zostaną one wyświetlone, poprawnie zgłosić je, aby można je było naprawić i obejść je w kodzie do tego czasu.
Lokalizowanie
W prawie każdym przypadku z usterkami tego rodzaju, głównym objawem są awarie natywne, zwykle z czymś podobnym do mono_sigsegv_signal_handler, lub _sigtrap w górnej ramce stosu. Cocoa próbuje wrócić do kodu C#, uderzając w bezużyteczny obiekt i ulegając awarii. Jednak nie każda awaria z tymi symbolami jest spowodowana przez problem z powiązaniem, taki jak ten, należy wykonać dodatkowe kopanie, aby potwierdzić, że jest to problem.
To, co sprawia, że te usterki są trudne do śledzenia, jest to, że występują one tylko po usunięciu obiektu, którego dotyczy problem. Jeśli uważasz, że wystąpił jeden z tych usterek, dodaj następujący kod gdzieś w sekwencji uruchamiania:
new System.Threading.Thread (() =>
{
while (true) {
System.Threading.Thread.Sleep (1000);
GC.Collect ();
}
}).Start ();
Spowoduje to wymusie uruchomienie modułu odśmiecającego śmieci co sekundę przez aplikację. Uruchom ponownie aplikację i spróbuj odtworzyć usterkę. Jeśli natychmiast ulegniesz awarii lub konsekwentnie zamiast losowo, jesteś na właściwej drodze.
Raportowanie
Następnym krokiem jest zgłoszenie problemu do platformy Xamarin, aby można było rozwiązać powiązanie dla przyszłych wersji. Jeśli jesteś właścicielem licencji firmowej lub przedsiębiorstwa, otwórz bilet pod adresem
visualstudio.microsoft.com/vs/support/
W przeciwnym razie wyszukaj istniejący problem:
- Sprawdzanie bieżących usterek
- Przeszukaj repozytorium problemów
- Jeśli nie możesz znaleźć pasującego problemu, zgłoś nowy problem w repozytorium problemów z usługą GitHub.
Problemy z usługą GitHub są publiczne. Nie można ukryć komentarzy ani załączników.
Dołącz jak najwięcej z następujących elementów:
- Prosty przykład odtworzenia problemu. Jest to bezcenne, jeśli to możliwe.
- Pełny ślad stosu awarii.
- Kod języka C# otaczający awarię.
Praca w pobliżu
Po śledzeniu problemu poprawek problemu z obejściem do momentu rozwiązania powiązania może być proste. Celem jest uniemożliwienie obiektu (View, Delegate, DataSource), który jest niepoprawnie usuwany z opuszczania pamięci przez przechowywanie otwartego odwołania.
W prostych przypadkach, w których istnieje tylko jedno wystąpienie obiektu, zmień kod z następującego:
void AddObject ()
{
item.View = new MyView ();
...
}
Aby użyć zmiennej statycznej, takiej jak:
static NSObject view;
...
void AddObject ()
{
view = new MyView ();
item.View = view;
...
}
W przypadkach, w których można utworzyć wiele wystąpień, można użyć statycznego HashSet :
static HashSet<NSObject> collection = new HashSet<NSObject> ();
...
void AddObject ()
{
item.View = new MyView ();
collection.Add (item.View );
...
}
Po naprawieniu powiązania i uaktualnieniu do wersji platformy Xamarin.Mac, która zawiera poprawkę, można usunąć kod obejścia.
Wyjątek bubbling i Objective-C
Nigdy nie należy zezwalać na używanie wyjątku w języku C# do "ucieczki" kodu zarządzanego do metody wywołującej Objective-C . Jeśli tak, wyniki są niezdefiniowane, ale zazwyczaj obejmują awarię. Ogólnie rzecz biorąc, robimy wszystko, co w stanie bąbelkować przydatne informacje zarówno dla awarii natywnych, jak i zarządzanych, aby pomóc w szybkim rozwiązywaniu problemów.
Bez zbyt pochyłania się z przyczyn technicznych, skonfigurowanie infrastruktury w celu przechwytywania wyjątków zarządzanych na każdej granicy zarządzanej/natywnej jest nie trywialnie kosztowne i istnieje wiele przejść, które występują w wielu typowych operacjach. Wiele operacji, w szczególności tych, które obejmują wątek interfejsu użytkownika musi zakończyć się szybko lub aplikacja będzie zacinać się i mieć niedopuszczalne właściwości wydajności. Wiele z tych wywołań zwrotnych robi bardzo proste rzeczy, które rzadko mają możliwość rzucania, więc to obciążenie byłoby zarówno kosztowne, jak i niepotrzebne w tych przypadkach.
W związku z tym nie skonfigurujemy tych prób / połowów dla Ciebie. W przypadku miejsc, w których kod wykonuje nietrywialne rzeczy (poza zwróceniem wartości logicznej lub prostej matematyki), możesz spróbować złapać siebie.