如何从安装在iOS设备上的应用程序中获取打印语句?

7
我正在阅读一些有关开发iOS应用的良好实践,并考虑使用Console.app监视从App Store安装的iOS应用程序的日志记录(链接1)。因此,我在这里进行了测试,但我注意到print语句不会显示在Console.app中,只有NSLog才会显示。我的问题是:是否有可能查看在设备上安装的iOS应用程序中使用print命令生成的日志?使用Frida、Console.app或其他任何方法?

如果没有其他方法,这是否意味着print命令比NSLog更安全?这对我来说似乎非常违反直觉。


Xcode应该支持自己编译的应用程序,这是开箱即用的。而且9年前这个方法可行。我相信今天也有一些类似的方法可以实现。printf在防止他人读取其输出方面并不“安全”。 - Siguza
我尝试制作一个简单的应用程序来测试这个问题,但只有使用 NSLog 时才能获得日志。当使用 print 时没有任何日志出现,我认为这是因为它们的工作方式不同:显然,NSLog 将信息存储在某些特定于系统的文件中,而 print 只是将文本打印到标准 stdout 中。因此,我认为要访问 print 中的内容,您需要一些类似于 stdout 重定向之类的东西,但我还没有找到相关资料。 - Izabella Melo
打印输出到 "stdout"。可以在应用程序中添加代码,将 stdout 和 stderr 重定向到文件或 os_log 中。但是,如果您是该应用程序的所有者,我建议只需遵循 matt 的答案即可。 - CouchDeveloper
2个回答

5
在iOS应用中,print语句不会被记录到iOS上的任何[持久化]日志系统中,因此,如果这些语句曾经发生过,你就无法通过打印语句访问应用程序的输出。
默认情况下,您只能在XCode输出面板中看到print命令的输出。但是print命令本身始终包含在调试和发布版本中,并因此被执行。如果没有连接到XCode以检索它,则只会丢弃打印语句的输出。
我通过构建以下SwiftUI测试应用程序(请参见本答案末尾),确保存档配置文件设置为RELEASE并对项目进行存档,以构建IPA文件来测试这一点。 然后,在IdaPro中分析IPA文件以查看实际的ARM汇编代码。
在所有测试中使用不同的选项(例如“从位码重建”(de)激活/停用),代码始终存在。
因此,如果将Frida连接到应用程序,您可以挂钩打印方法print(_:separator:terminator:)以检索所有否则将被丢弃的输出。
struct ContentView: View {
    @State var number : Int = 1
    var body: some View {
        VStack {
            Button("  Print  ") {
                print("print test abcdefgh number %d", number)
            }.padding()
            Button("  os_log  ") {
                os_log("os_log test abcdefgh number %d", number)
            }.padding()
            Button("randomize") {
                self.number = Int.random(in: 1...11111)
            }.padding()
        }
    }
}

2
如果您希望在应用中使用print和printf来输出到文件或任何文件描述符,那么请注意:
import SwiftUI
import Darwin
import os.log

extension OSLog {
    private static var subsystem = Bundle.main.bundleIdentifier!
    static let `default` = OSLog(subsystem: subsystem, category: "default")
}

extension TestApp {
    func subscribeFileToStderrAndStdoutIfNotAttachedToDebugger() {
        if isatty(STDERR_FILENO) != 1 {
            let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
            let logfileUrl = documentsUrl.appendingPathComponent("out.log")
            logfileUrl.withUnsafeFileSystemRepresentation { path in
                guard let path = path else {
                    return
                }
                print("redirect stdout and stderr to: \(String(cString: path))")
                let file = fopen(path, "a")
                assert(file != nil, String(cString: strerror(errno)))
                let fd = fileno(file)
                assert(fd >= 0, String(cString: strerror(errno)))
                let result1 = dup2(fd, STDERR_FILENO)
                assert(result1 >= 0, String(cString: strerror(errno)))
                let result2 = dup2(fd, STDOUT_FILENO)
                assert(result2 >= 0, String(cString: strerror(errno)))
            }
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        subscribeFileToStderrAndStdoutIfNotAttachedToDebugger()
        return true
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接