Mục tiêu C tìm người gọi phương thức


90

Có cách nào để xác định dòng mã nhất định methodđược gọi từ đó không?


tại sao bạn muốn làm việc này? Nếu đó là để gỡ lỗi, có khá một bộ khác nhau của câu trả lời hơn nếu bạn muốn làm điều đó trong sản xuất (mà câu trả lời là nhiều khả năng "không".)
Nicholas Riley

4
Tôi sẽ lấy gỡ lỗi câu trả lời
ennuikiller

3
Có câu trả lời sản xuất không?
Hari Karam Singh

Câu trả lời:


188

Tôi hy vọng rằng điều này sẽ giúp:

    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]);

1
Cũng tạo một macro trong tệp -Prefix.pch và sau đó chạy nó từ đại biểu ứng dụng. Điều thú vị là lớp người gọi là: "<redacted>"
Melvin Sovereign

4
trong trường hợp của tôi, không có gì ở chỉ mục 5. Do đó mã này đã làm hỏng ứng dụng của tôi. nó hoạt động sau khi xóa dòng cuối cùng. Tuy nhiên, nó vẫn tuyệt vời đến mức đáng giá +1!
Brian

1
Điều này hoạt động tốt, nhưng làm thế nào để chúng tôi giải thích "người gọi đường dây"? Trong trường hợp của tôi, nó hiển thị một số, ví dụ 91, nhưng tại sao lại là 91? Nếu tôi di chuyển cuộc gọi một hướng dẫn bên dưới, nó sẽ hiển thị 136 ... Vậy số này được tính như thế nào?
Maxim Chetrusca

@ Pétur Nếu có ảnh hưởng đến hiệu suất là không đáng kể, NSThread đã có thông tin đó, về cơ bản bạn chỉ đang truy cập một mảng và tạo một mảng mới.
Oscar Gomez

Nó đang hoạt động ở chế độ gỡ lỗi, nhưng sau khi lưu trữ nó vào gói IPA, ngăn xếp cuộc gọi không hoạt động như mong đợi. Tôi vừa nhận được "callStackSymbols = 1 SimpleApp 0x00000001002637a4 _Z8isxdigiti + 63136", "_Z8isxdigiti" phải là "Ứng dụng AAMAgentAppDelegate: didFinishLaunchingWithOptions:"
Alanc Liu

50

Trong mã được tối ưu hóa hoàn toàn, không có cách nào chắc chắn 100% để xác định người gọi đến một phương pháp nhất định. Trình biên dịch có thể sử dụng tối ưu hóa cuộc gọi đuôi trong khi trình biên dịch sử dụng lại một cách hiệu quả khung ngăn xếp của người gọi cho callee.

Để xem một ví dụ về điều này, hãy đặt một điểm ngắt trên bất kỳ phương thức nhất định nào bằng cách sử dụng gdb và nhìn vào dấu vết. Lưu ý rằng bạn không thấy objc_msgSend () trước mỗi lần gọi phương thức. Đó là bởi vì objc_msgSend () thực hiện một cuộc gọi đuôi đối với việc triển khai của mỗi phương thức.

Mặc dù bạn có thể biên dịch ứng dụng của mình không được tối ưu hóa, nhưng bạn sẽ cần các phiên bản không được tối ưu hóa của tất cả các thư viện hệ thống để tránh chỉ một vấn đề này.

Và đây chỉ là một vấn đề; thực tế, bạn đang hỏi "làm cách nào để phát minh lại CrashTracer hoặc gdb?". Một vấn đề rất khó mà các nghề nghiệp được tạo ra. Trừ khi bạn muốn "công cụ gỡ lỗi" trở thành sự nghiệp của mình, tôi khuyên bạn không nên đi theo con đường này.

Bạn thực sự đang cố gắng trả lời câu hỏi nào?


3
ÔI CHÚA ƠI. Điều này đã đưa tôi trở lại trái đất. Gần như theo nghĩa đen. Tôi đang giải quyết một vấn đề hoàn toàn, không liên quan. Cảm ơn ngài!
nimeshdesai

11

Sử dụng câu trả lời được cung cấp bởi intropedro , tôi đã nghĩ ra điều này:

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

mà sẽ chỉ trả lại cho tôi lớp và chức năng gốc:

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

ps - nếu hàm được gọi bằng hàm PerformSelector, kết quả sẽ là:

Origin: [NSObject performSelector:withObject:]

2
* Nhưng lưu ý rằng trong một số trường hợp, nó không chứa tên hàm, không thực hiện bộ chọn, và do đó - Việc gọi CALL_ORIGIN gặp sự cố. (SO, tôi khuyên - nếu bạn đang gonna sử dụng ví dụ này, sử dụng nó tạm thời và sau đó loại bỏ nó.)
Guntis Treulands

6

Chỉ cần viết một phương pháp sẽ làm điều này cho bạn:

- (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;
}

6

Câu trả lời của @ Intropedro phiên bản Swift 2.0 để tham khảo;

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])")

5

Nếu đó là vì lợi ích gỡ lỗi, hãy tập thói quen đặt NSLog(@"%s", __FUNCTION__);

Là dòng đầu tiên bên trong mỗi phương thức trong các lớp của bạn. Sau đó, bạn luôn có thể biết thứ tự của các lệnh gọi phương thức khi nhìn vào trình gỡ lỗi.


Bằng cách nào đó mã không xuất hiện chính xác. Có hai dấu gạch dưới trước và sau FUNCTION
Giovanni

hãy thử sử dụng thoát backtick ( `) gửi kèm mã của bạn để nó có thể được hiển thị đúng
howanghk

3
Hoặc tốt hơn hãy sử dụng __PRETTY_FUNCTION__ cũng hỗ trợ Objective-C và hiển thị tên đối tượng cùng với phương thức.
Ben Sinclair

4

Bạn có thể truyền selfdưới dạng một trong các đối số cho hàm và sau đó lấy tên lớp của đối tượng người gọi bên trong:

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

//...

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

Bằng cách này, bạn có thể chuyển nó vào bất kỳ đối tượng nào có thể giúp bạn xác định vị trí của vấn đề.


3

Một phiên bản được tối ưu hóa một chút của câu trả lời tuyệt vời của @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! :(";
}

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];

Trong cửa sổ đầu ra, bạn sẽ thấy một cái gì đó như sau.

Người gọi: 2 MyApp 0x0004e8ae - [IINClassroomInit buildMenu] + 86

Bạn cũng có thể phân tích cú pháp chuỗi này để trích xuất thêm dữ liệu về khung ngăn xếp.

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

Nó được lấy từ Phương thức gọi điện xác định trong iOS .


2

Phiên bản Swift 4 của câu trả lời @Geoff H để sao chép và dán ;]

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])")

0

Phiên bản Swift 3 của câu trả lời @Geoff H để tham khảo:

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])")
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.