Cel C znajdź wywołującego metodę

90

Czy istnieje sposób na określenie wiersza kodu, z którego methodzostał wywołany określony ?

ennuikiller
źródło
Dlaczego chcesz to zrobić? Jeśli chodzi o debugowanie, istnieje całkiem inny zestaw odpowiedzi niż w przypadku, gdy chcesz to zrobić w środowisku produkcyjnym (w przypadku których bardziej prawdopodobne jest, że odpowiedź brzmi „nie”).
Nicholas Riley
4
Przyjmę odpowiedź dotyczącą debugowania
ennuikiller,
3
Czy istnieje odpowiedź produkcyjna?
Hari Karam Singh

Odpowiedzi:

188

Mam nadzieję, że to pomoże:

    NSString *sourceString = [[NSThread callStackSymbols] objectAtIndex:1];
    // Example: 1   UIKit                               0x00540c89 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163
    NSCharacterSet *separatorSet = [NSCharacterSet characterSetWithCharactersInString:@" -[]+?.,"];
    NSMutableArray *array = [NSMutableArray arrayWithArray:[sourceString  componentsSeparatedByCharactersInSet:separatorSet]];
    [array removeObject:@""];

    NSLog(@"Stack = %@", [array objectAtIndex:0]);
    NSLog(@"Framework = %@", [array objectAtIndex:1]);
    NSLog(@"Memory address = %@", [array objectAtIndex:2]);
    NSLog(@"Class caller = %@", [array objectAtIndex:3]);
    NSLog(@"Function caller = %@", [array objectAtIndex:4]);
intropedro
źródło
1
Utworzono również makro w pliku -Prefix.pch, a następnie uruchomiono je z poziomu delegata aplikacji. Co ciekawe, osobą dzwoniącą do klasy była: „<redacted>”
Melvin Sovereign
4
w moim przypadku nie ma nic pod indeksem 5. W ten sposób ten kod zawiesił moją aplikację. zadziałało po usunięciu ostatniej linii. Mimo to jest tak niesamowity, że jest wart +1!
Brian
1
To działa świetnie, ale jak interpretujemy „osobę dzwoniącą”? W moim przypadku pokazuje liczbę, na przykład 91, ale dlaczego to 91? Jeśli przeniosę wywołanie o jedną instrukcję poniżej, pokaże ona 136 ... Jak więc obliczana jest ta liczba?
Maxim Chetrusca
@ Pétur Jeśli wpływ na wydajność jest pomijalny, NSThread ma już te informacje, po prostu uzyskujesz dostęp do tablicy i tworzysz nową.
Oscar Gomez
Działa w trybie debugowania, ale po zarchiwizowaniu go do pakietu IPA stos wywołań nie działa zgodnie z oczekiwaniami. Właśnie otrzymałem „callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136”, „_Z8isxdigiti” powinno mieć wartość „AAMAgentAppDelegate application: didFinishLaunchingWithOptions:”
Alanc Liu
50

W całkowicie zoptymalizowanym kodzie nie ma w 100% pewnego sposobu na określenie wywołującego określoną metodę. Kompilator może zastosować optymalizację wywołań końcowych, podczas gdy kompilator efektywnie ponownie wykorzystuje ramkę stosu wywołującego dla wywoływanego.

Aby zobaczyć przykład tego, ustaw punkt przerwania dla dowolnej metody za pomocą gdb i spójrz na ślad. Zauważ, że nie widzisz objc_msgSend () przed każdym wywołaniem metody. Dzieje się tak, ponieważ objc_msgSend () wykonuje wywołanie tail do implementacji każdej metody.

Chociaż możesz skompilować aplikację niezoptymalizowaną, potrzebujesz niezoptymalizowanych wersji wszystkich bibliotek systemowych, aby uniknąć tylko tego jednego problemu.

A to tylko jeden problem; w efekcie pytasz „jak ponownie wynaleźć CrashTracer lub gdb?”. Bardzo trudny problem, na którym opiera się kariera. Jeśli nie chcesz, aby „narzędzia do debugowania” były Twoją karierą, odradzałbym pójście tą drogą.

Na jakie pytanie naprawdę próbujesz odpowiedzieć?

bbum
źródło
3
O MÓJ BOŻE. To sprowadziło mnie z powrotem na ziemię. Prawie dosłownie. Rozwiązałem zupełnie, niepowiązany problem. Dziękuję Panu!
nimeshdesai
11

Korzystając z odpowiedzi udzielonej przez intropedro , wymyśliłem to:

#define CALL_ORIGIN NSLog(@"Origin: [%@]", [[[[NSThread callStackSymbols] objectAtIndex:1] componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"[]"]] objectAtIndex:1])

co po prostu zwróci mi oryginalną klasę i funkcję:

2014-02-04 16:49:25.384 testApp[29042:70b] Origin: [LCallView addDataToMapView]

ps - jeśli funkcja zostanie wywołana przy użyciu performSelector, wynikiem będzie:

Origin: [NSObject performSelector:withObject:]
Guntis Treulands
źródło
2
* Należy jednak pamiętać, że w niektórych przypadkach nie zawiera on nazwy funkcji, ani nie wykonuje selektora, a co za tym idzie - Zawieszenie wywołania CALL_ORIGIN. (Tak więc radzę - jeśli zamierzasz użyć tego przykładu, użyj go tymczasowo, a następnie usuń.)
Guntis Treulands
6

Właśnie napisałem metodę, która zrobi to za Ciebie:

- (NSString *)getCallerStackSymbol {

    NSString *callerStackSymbol = @"Could not track caller stack symbol";

    NSArray *stackSymbols = [NSThread callStackSymbols];
    if(stackSymbols.count >= 2) {
        callerStackSymbol = [stackSymbols objectAtIndex:2];
        if(callerStackSymbol) {
            NSMutableArray *callerStackSymbolDetailsArr = [[NSMutableArray alloc] initWithArray:[callerStackSymbol componentsSeparatedByString:@" "]];
            NSUInteger callerStackSymbolIndex = callerStackSymbolDetailsArr.count - 3;
            if (callerStackSymbolDetailsArr.count > callerStackSymbolIndex && [callerStackSymbolDetailsArr objectAtIndex:callerStackSymbolIndex]) {
                callerStackSymbol = [callerStackSymbolDetailsArr objectAtIndex:callerStackSymbolIndex];
                callerStackSymbol = [callerStackSymbol stringByReplacingOccurrencesOfString:@"]" withString:@""];
            }
        }
    }

    return callerStackSymbol;
}
Roy K.
źródło
6

Wersja Swift 2.0 odpowiedzi @ Intropedro w celach informacyjnych;

let sourceString: String = NSThread.callStackSymbols()[1]

let separatorSet :NSCharacterSet = NSCharacterSet(charactersInString: " -[]+?.,")
let array = NSMutableArray(array: sourceString.componentsSeparatedByCharactersInSet(separatorSet))
array.removeObject("")

print("Stack: \(array[0])")
print("Framework:\(array[1])")
print("Memory Address:\(array[2])")
print("Class Caller:\(array[3])")
print("Method Caller:\(array[4])")
Geoff H.
źródło
5

Jeśli jest to ze względu na debbugowanie, przywyknij do umieszczania pliku NSLog(@"%s", __FUNCTION__);

Jako pierwsza linia w każdej metodzie w Twoich klasach. Wtedy zawsze możesz poznać kolejność wywołań metod, patrząc na debuger.

Giovanni
źródło
W jakiś sposób kod nie wyświetla się poprawnie. Istnieją dwa podkreślenia przed i po FUNCTION
Giovanni,
spróbuj użyć znaku ucieczki (`), aby
zawrzeć
3
Lub lepiej użyj __PRETTY_FUNCTION__, który obsługuje również Objective-C i wyświetla nazwę obiektu wraz z metodą.
pronebird
4

Możesz przekazać selfjako jeden z argumentów do funkcji, a następnie pobrać nazwę klasy obiektu wywołującego wewnątrz:

+(void)log:(NSString*)data from:(id)sender{
    NSLog(@"[%@]: %@", NSStringFromClass([sender class]), data);
}

//...

-(void)myFunc{
    [LoggerClassName log:@"myFunc called" from:self];
}

W ten sposób możesz przekazać mu dowolny obiekt, który pomoże ci określić, gdzie może być problem.

pckill
źródło
3

Nieco zoptymalizowana wersja fantastycznej odpowiedzi @Roy Kronenfeld:

- (NSString *)findCallerMethod
{
    NSString *callerStackSymbol = nil;

    NSArray<NSString *> *callStackSymbols = [NSThread callStackSymbols];

    if (callStackSymbols.count >= 2)
    {
        callerStackSymbol = [callStackSymbols objectAtIndex:2];
        if (callerStackSymbol)
        {
            // Stack: 2   TerribleApp 0x000000010e450b1e -[TALocalDataManager startUp] + 46
            NSInteger idxDash = [callerStackSymbol rangeOfString:@"-" options:kNilOptions].location;
            NSInteger idxPlus = [callerStackSymbol rangeOfString:@"+" options:NSBackwardsSearch].location;

            if (idxDash != NSNotFound && idxPlus != NSNotFound)
            {
                NSRange range = NSMakeRange(idxDash, (idxPlus - idxDash - 1)); // -1 to remove the trailing space.
                callerStackSymbol = [callerStackSymbol substringWithRange:range];

                return callerStackSymbol;
            }
        }
    }

    return (callerStackSymbol) ?: @"Caller not found! :(";
}
Andrzej
źródło
2

@ennuikiller

//Add this private instance method to the class you want to trace from
-(void)trace
{
  //Go back 2 frames to account for calling this helper method
  //If not using a helper method use 1
  NSArray* stack = [NSThread callStackSymbols];
  if (stack.count > 2)
    NSLog(@"Caller: %@", [stack objectAtIndex:2]);
}

//Add this line to the method you want to trace from
[self trace];

W oknie wyjściowym zobaczysz coś takiego jak poniżej.

Dzwoniący: 2 MyApp 0x0004e8ae - [IINClassroomInit buildMenu] + 86

Możesz również przeanalizować ten ciąg, aby wyodrębnić więcej danych o ramce stosu.

2 = Thread id
My App = Your app name
0x0004e8ae = Memory address of caller
-[IINClassroomInit buildMenu] = Class and method name of caller
+86 = Number of bytes from the entry point of the caller that your method was called

Został zaczerpnięty z Identify Calling Method w iOS .

DannyBios
źródło
2

Odpowiedź @Geoff H w wersji Swift 4 do kopiowania i wklejania ;]

let sourceString: String = Thread.callStackSymbols[1]
let separatorSet :CharacterSet = CharacterSet(charactersIn: " -[]+?.,")
var array = Array(sourceString.components(separatedBy: separatorSet))
array = array.filter { $0 != "" }

print("Stack: \(array[0])")
print("Framework:\(array[1])")
print("Memory Address:\(array[2])")
print("Class Caller:\(array[3])")
print("Method Caller:\(array[4])")
Andy Obusek
źródło
0

Odpowiedź @Geoff H w wersji Swift 3 w celach informacyjnych:

let sourceString: String = Thread.callStackSymbols[1]
let separatorSet: CharacterSet = CharacterSet(charactersIn: " -[]+?.,")
let array = NSMutableArray(array: sourceString.components(separatedBy: separatorSet))
array.remove("")

print("Stack: \(array[0])")
print("Framework:\(array[1])")
print("Memory Address:\(array[2])")
print("Class Caller:\(array[3])")
print("Method Caller:\(array[4])")
Carmelo Gallo
źródło