我已经设置了我的应用程序,可以将调试输出发送到控制台或日志文件。现在,我想在代码中决定:
- 它是在调试器(或模拟器)中运行,并且有一个控制台窗口,我想直接读取输出,还是
- 没有控制台窗口,因此输出应该被重定向到文件。
是否有一种方法可以确定应用程序是否在调试器中运行?
我已经设置了我的应用程序,可以将调试输出发送到控制台或日志文件。现在,我想在代码中决定:
是否有一种方法可以确定应用程序是否在调试器中运行?
苹果公司提供了一个函数来检测程序是否正在被调试,可以在 Technical Q&A 1361 中找到该函数的相关内容(Mac 库中的条目 和 iOS 库中的条目;它们是相同的)。
以下是 Technical Q&A 中的代码:
#include <assert.h>
#include <stdbool.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/sysctl.h>
static bool AmIBeingDebugged(void)
// Returns true if the current process is being debugged (either
// running under the debugger or has a debugger attached post facto).
{
int junk;
int mib[4];
struct kinfo_proc info;
size_t size;
// Initialize the flags so that, if sysctl fails for some bizarre
// reason, we get a predictable result.
info.kp_proc.p_flag = 0;
// Initialize mib, which tells sysctl the info we want, in this case
// we're looking for information about a specific process ID.
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
// Call sysctl.
size = sizeof(info);
junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
assert(junk == 0);
// We're being debugged if the P_TRACED flag is set.
return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
}
还要注意以下这篇问答末尾的说明:
重要提示:由于
kinfo_proc
结构体(在<sys/sysctl.h>
中)的定义是由__APPLE_API_UNSTABLE
进行条件编译的,因此您应该将上述代码的使用限制为您程序的调试版本。
调试器可以在启动要调试的进程时设置环境变量。在Xcode中,可以通过转到菜单项Product->Edit Scheme来实现此操作。然后,在Debug方案的Arguments选项卡下添加一个新的环境变量。该变量的名称应为“debugger”,值为“true”。接下来,可以使用以下代码片段来确定调试器是否启动了您的进程:
NSDictionary* env = [NSProcessInfo processInfo].environment;
if ([env[@"debugger"] isEqual:@"true"]) {
NSLog(@"debugger yes");
}
else {
NSLog(@"debugger no");
}
对于那些寻求更简单解决方案的人 - 这个解决方案非常完美:
func isDebuggerAttached() -> Bool {
return getppid() != 1
}
最简单的解决方案实际上是
_isDebugging = isatty(STDERR_FILENO);
虽然它并不完全等同于告诉我们应用程序是否在调试器下运行,但足够好(甚至更好?)以确定是否应将日志写入磁盘。
基于一个重复的帖子中的答案,该答案同样适用于Objective-C,展示了HockeyApp-iOS如何实现, 这是Swift 5版本:
let isDebuggerAttached: Bool = {
var debuggerIsAttached = false
var name: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
var info: kinfo_proc = kinfo_proc()
var info_size = MemoryLayout<kinfo_proc>.size
let success = name.withUnsafeMutableBytes { (nameBytePtr: UnsafeMutableRawBufferPointer) -> Bool in
guard let nameBytesBlindMemory = nameBytePtr.bindMemory(to: Int32.self).baseAddress else { return false }
return -1 != sysctl(nameBytesBlindMemory, 4, &info/*UnsafeMutableRawPointer!*/, &info_size/*UnsafeMutablePointer<Int>!*/, nil, 0)
}
// The original HockeyApp code checks for this; you could just as well remove these lines:
if !success {
debuggerIsAttached = false
}
if !debuggerIsAttached && (info.kp_proc.p_flag & P_TRACED) != 0 {
debuggerIsAttached = true
}
return debuggerIsAttached
}()
总是很好有不同的解决方案,所以这是我的建议:
想法是检查stderr文件句柄(这是NSLog
打印到的地方)。自iOS 4以来,这个解决方案一直可靠地工作,并在模拟器和设备上持续运行,包括iOS 9。
#import <sys/ioctl.h>
#import <sys/param.h>
#if TARGET_IPHONE_SIMULATOR
#import <sys/conf.h>
#else
// Not sure why <sys/conf.h> is missing on the iPhoneOS.platform.
// It's there on iPhoneSimulator.platform, though. We need it for D_DISK, only:
#if ! defined(D_DISK)
#define D_DISK 2
#endif
#endif
BOOL isDebuggerAttatchedToConsole(void)
{
// We use the type of the stderr file descriptor
// to guess if a debugger is attached.
int fd = STDERR_FILENO;
// is the file handle open?
if (fcntl(fd, F_GETFD, 0) < 0) {
return NO;
}
// get the path of stderr's file handle
char buf[MAXPATHLEN + 1];
if (fcntl(fd, F_GETPATH, buf ) >= 0) {
if (strcmp(buf, "/dev/null") == 0)
return NO;
if (strncmp(buf, "/dev/tty", 8) == 0)
return YES;
}
// On the device, without attached Xcode, the type is D_DISK (otherwise it's D_TTY)
int type;
if (ioctl(fd, FIODTYPE, &type) < 0) {
return NO;
}
return type != D_DISK;
}
我通常会选择更简单的解决方案;二进制文件是否使用了优化编译?
调试版本没有进行优化,而且日志很好用。发布版本应该进行了优化,并且不需要那么多日志。您可以通过检查__OPTIMIZE__
符号来确定这一点。
对于日志记录,我使用这个设置来记录函数:
#ifdef __OPTIMIZE__
#define CWLog(...)
#define CWLogDebug(...)
#define CWLogInfo(...)
#else
#define CWLog(...) NSLog(__VA_ARGS__)
#define CWLogDebug( s, ... ) NSLog( @"DEBUG <%p %@:(%d)> %@", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#ifndef LOG_INFO
#define CWLogInfo(...)
#else
#define CWLogInfo( s, ... ) NSLog( @"INFO <%p %@:(%d)> %@", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#endif
#endif
#define CWLogWarning( s, ... ) NSLog( @"WARNING <%p %@:(%d)> %@", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#define CWLogError( s, ... ) NSLog( @"ERROR <%p %@:(%d)> %@", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#if DEBUG
// Do something.
#endif
有任何异议吗?
你可以定义是否需要运行时常量
#if DEBUG
public let IS_RUNNING_IN_DEBUGGER: Bool = true
#else
public let IS_RUNNING_IN_DEBUGGER: Bool = false
#endif
相同的方法可以在Objc和更多领域中使用。