1. 程式人生 > >TN2124 Mac OS X Debugging Magic 除錯魔法

TN2124 Mac OS X Debugging Magic 除錯魔法

TN2124 Mac OS X Debugging Magic 除錯魔法

原文地址:

https://developer.apple.com/library/archive/technotes/tn2124/_index.html#//apple_ref/doc/uid/DTS10003391-CH1-SECSEEINGOUTPUT



Important: This document is no longer being updated. For the latest information about Apple SDKs, visit the documentation website.

這份文件不再更新。最新的 Apple SDK 文件,建議訪問 documentation website.

Mac OS X contains a number of ‘secret’ debugging facilities, including environment variables, preferences, routines callable from GDB, and so on. This technotes describes these facilities. If you’re developing for Mac OS X, you should look through this list to see if you’re missing out on something that will make your life easier.

Mac OS X 包含一系列的祕密除錯工具,包含環境變數設定、偏好設定、從GDB呼叫例程等。

這份文件描述這些工具,如果你在開發 Mac OS X,你可以通過看這份列表來幫助你的開發。

一、Introduction 介紹

All Apple systems include debugging facilities added by Apple engineering teams to help develop and debug specific subsystems. Many of these facilities remain in released system software and you can use them to debug your code. This technote describes some of the more broadly useful ones. In cases where a debugging facility is documented in another place, there’s a short overview of the facility and a link to the existing documentation.

這裡列舉了一些廣為傳播的除錯方法。

Important: This is not an exhaustive list: not all debugging facilities are, or will be, documented.

Many of the details covered in this technote vary from platform to platform and release to release. As such, you may encounter minor variations between platforms, and on older or newer systems. Known significant variations are called out in the text.

This technote was written with reference to Mac OS X 10.6.

Warning: The debugging facilities described in this technote are unsupported. Apple reserves the right to change or eliminate each facility as dictated by the evolution of the OS; this has happened in the past, and is very likely to happen again in the future. These facilities are for debugging only: you must not ship a product that relies on the existence or functionality of the facilities described in this technote.

Note: If you also develop for iOS, you may want to read Technical Note TN2239, ‘iOS Debugging Magic’.

如果是你是開發 iOS,可以閱讀文件 Technical Note TN2239, ‘iOS Debugging Magic’.

This technote covers advanced debugging techniques. If you’re just getting started, you should consult the following material:

  • GDB is the system’s primary debugging tool. For a full description of GDB, see Debugging with GDB.

    GDB 是系統首選的除錯工具。檢視更多關於GDB 的描述,可以訪問 Debugging with GDB.

  • Xcode is Apple’s integrated development environment (IDE). It includes a sophisticated graphical debugger, implemented as a wrapper around GDB. For more information about Xcode, see the “Tools & Languages” section of the Apple developer Reference Library.

    Xcode 是蘋果的完整的IDE開發環境,包含圖形除錯工具,作為一個GDB 的包。可以檢視文件 Reference Library 中的”Tools & Languages” 部分來更多瞭解 xcode。

This technote does not cover performance debugging. If you’re trying to debug a performance problem, the best place to start is the Getting Started with Performance document.

二、Basics 基礎

The later sections of this technote describe the debugging facilities in detail. Many of these facilities use similar techniques to enable and disable the facility, and to see its output. This section describes these common techniques.

本文件後續的章節,將會描述除錯工具的細節。許多除錯工具都是用同樣的方法來使 功能有效或者無效,並檢視輸出。本章節描述這些通用的技巧。

1、Enabling Debugging Facilities

Some debugging facilities are enabled by default. However, most facilities must be enabled using one of the techniques described in the following sections.

許多debug 設定預設有效,但更多是需要設定。

1.1 Environment Variables 環境變數

In many cases you can enable a debugging facility by setting a particular environment variable. You can do this using the executable inspector in Xcode. Figure 1 shows an example of this.

許多情況下,你需要通過配置一個特別的環境變數,來使一個除錯工具有效。

Figure 1 Setting environment variables in Xcodeimg


xcode 9.3

境變數

境變數


In addition, there are a numerous other ways to set an environment variable. The first involves running your application from Terminal and specifying the environment variable on the command line. Listing 1 shows how to set an environment variable in the sh shell; Listing 2 shows the equivalent in csh.


Listing 1 Setting an environment variable in an sh-compatible shell

過shell 檔案配置環境變數。

$ MallocScribble=1 /Applications/TextEdit.app/Contents/MacOS/TextEdit  TextEdit(13506) malloc: enabling scribbling to detect mods to free blocks […]

Listing 2 Setting an environment variable in a csh-compatible shell

通過 csh 檔案配置同樣的環境變數。

% env MallocScribble=1 /Applications/TextEdit.app/Contents/MacOS/TextEdit TextEdit(13512) malloc: enabling scribbling to detect mods to free blocks […]

Note: The default shell in Mac OS X 10.3 and later is bash, an sh-compatible shell.

The default shell in Mac OS X 10.2.x and earlier was tcsh, a csh-compatible shell. Unless otherwise noted, the remainder of this technote assumes that you’re using bash.

In addition, you can set environment variables in GDB, as shown in Listing 3.


Listing 3 Setting an environment variable in GDB

$ gdb /Applications/TextEdit.app
GNU gdb 6.3.50-20050815 […]
(gdb) set env MallocScribble 1
(gdb) r
Starting program: /Applications/TextEdit.app/Contents/MacOS/TextEdit 
bash(13587) malloc: enabling scribbling to detect mods to free blocks
[…]

Technical Q&A QA1067, ‘Setting environment variables for user processes’ describes a mechanism for setting environment variables for all processes launched by a specific user.

Finally, you can use launchctl to set environment variables inherited by all processes run by launchd; see launchd for details.


1.2、Preferences

Some debugging facilities are enabled by setting a special preference. You can set such a debugging preference by configuring a command line argument in Xcode. Figure 2 shows how this is done.

Figure 2 Setting command line arguments in Xcodeimg

You can also do this by running your program from the command line, and supplying the preference in the arguments. Listing 4 shows an example.


Listing 4 Temporarily setting a preference

$ /Applications/TextEdit.app/Contents/MacOS/TextEdit -NSTraceEvents YES
2004-10-25 17:28:41.143 TextEdit[5774] timeout = 62993575878.857864 seco…
2004-10-25 17:28:41.179 TextEdit[5774] got apple event of class 61657674…
[…]

In addition to the temporary techniques described above, you can set preferences permanently using the defaults command line tool. Listing 5 shows how to do this. This listing sets the NSTraceEvents preference for the TextEdit application (whose bundle identifier is com.apple.TextEdit) to YES.


Listing 5 Setting a preference using defaults

$ defaults write com.apple.TextEdit NSTraceEvents YES
$ /Applications/TextEdit.app/Contents/MacOS/TextEdit 
2010-02-01 13:32:36.260 TextEdit[18687:903] timeout = 62827180043.739540 seconds[…]
2010-02-01 13:32:36.273 TextEdit[18687:903] got apple event of class 61657674, I[…]
[…]

Once you’re finished debugging, you should delete the preference, also using defaults, as shown in Listing 6.


Listing 6 Deleting a preference using defaults

$ defaults delete com.apple.TextEdit NSTraceEvents

For more information about defaults, consult its man page.


1.3 Callable Routines

Many system frameworks include routines that print debugging information to stderr.

These routines may be specifically designed to be callable from within GDB, or they may just be existing API routines that are useful while debugging.

Listing 7 shows an example of how to call a debugging routine from GDB; specifically, it calls CFBundleGetAllBundles to get a list of all the bundles loaded in the application, and then prints that list by calling the CFShow routine.


Listing 7 Calling a debugging routine from GDB

(gdb) call (void)CFShow((void *)CFBundleGetAllBundles())
<CFArray 0x10025c010 [0x7fff701faf20]>{type = mutable-small, count = 59, values = (
    0 : CFBundle 0x100234d00 </System/Library/Frameworks/CoreData.framework> …
    […]
    12 : CFBundle 0x100237790 </System/Library/Frameworks/Security.framework> …
    […]
    23 : CFBundle 0x100194eb0 </System/Library/Frameworks/CoreFoundation.framework> …
    […]
)}

If you don’t see the output from the routine, you may need to look at the console log, as described in the Seeing Debug Output section.

Important: If you use this technique for your own code, be warned that it doesn’t always work for routines that are declared static. The compiler’s interprocedural optimizations may cause a static routine to deviate from the standard function call ABI. In that case, it can’t be reliably called by GDB.

In practice, this only affects Intel 32-bit code.

1.4 Files

Certain debugging facilities are enabled by the existence of specific files within the file system. Listing 8 shows an example of this: creating the file /var/log/do_dnserver_log causes the CFNotificationCenter server (distnoted) to record information about all notifications to the system log.


Listing 8 Create a specific file to enable debugging

$ # IMPORTANT: This example is for Mac OS X 10.6 and later.
$ # See the "CFNotificationCenter" section for details on how 
$ # to do this on earlier systems.
$
$ sudo touch /var/log/do_dnserver_log

[… now restart …]

$ tail -f /var/log/system.log
Virtual-Victim:~ quinn$ tail -f /var/log/system.log
[…] distnoted[16]: checking_client "quicklookd" [43]
[…] distnoted[16]: checking_client "Dock" [43]
[…] distnoted[16]: checking_client "mds" [1]
[…] distnoted[16]: checking_client "mDNSResponder" [1]
[…] distnoted[16]: checking_client "fontd" [43]
[…]

For more information about this specific example, see CFNotificationCenter.


1.5 Signals

Many Mac OS X subsystems, especially those with a BSD heritage, implement debugging facilities that you access via signals. For example, if you send the SIGINFO signal to mDNSResponder, it will dump its current state to the system log.

You can send signals programmatically or from the command line. To send a signal programmatically, use the kill routine. To send a signal from the command line, use either the killcommand (if you know the process ID) or the killall command (if you know the process name).

When using these commands line tools, be aware that this technote uses the signal identifier (for example, SIGINFO) whereas these commands line tools use the signal name (INFO). So, to send a SIGINFO to mDNSResponder, you would use the command shown in Listing 9.

Listing 9 Sending a signal

$ sudo killall -INFO mDNSResponder

2、Seeing Debug Output

Programs that generate debug output generally do so using one of following mechanisms:

  • NSLog
  • printing to stderr
  • system log
  • kernel trace facility

NSLog is a high-level API for logging which is used extensively by Objective-C code. The exact behaviour of NSLog is surprisingly complex, and has changed significantly over time, making it beyond the scope of this document. However, it’s sufficient to know that NSLog prints to stderr, or logs to the system log, or both. So, if you understand those two mechanisms, you can see anything logged via NSLog.

Printing to stderr is one of the most commonly used output mechanism. Given this topic’s importance, it is covered in depth in the next section.

You can view the system log using the Console application (in /Applications/Utilities). You can learn more about the system log facility in the syslog man pages (asl, syslog, syslog.conf, and syslogd).

The kernel trace facility is a specialized low-latency, high-availability logging mechanism. In most cases a program that logs to the kernel trace facility also includes a way to view the log (for example, the fs_usage tool both enables file system usage logging and prints the results).


2.1 Console Output

Many programs, and indeed many system frameworks, print debugging messages to stderr. The destination for this output is ultimately controlled by the program: it can redirect stderr to whatever destination it chooses. However, in most cases a program does not redirect stderr, so the output goes to the default destination inherited by the program from its launch environment. This is typically one of the following:

  • If you launch a GUI application as it would be launched by a normal user, the system redirects any messages printed on stderr to the system log. You can view these messages using the techniques described earlier.
  • If you run a program from within Xcode, you can see its stderr output in Xcode’s debugger Console window (choose the Console menu item from the Run menu to see this window).
  • Finally, if you run a program (be it GUI or non-GUI) from within Terminal, its stderr is connected to your Terminal window; anything the program prints will appear in this window. This also applies to programs run from GDB within a Terminal window.

Console messages (that is, system log records created in response to an application printing to stderr) are specially tagged to make them easy to find in the system log. There are two techniques for viewing these messages:

  • In the Console application (in /Applications/Utilities), there’s a side bar item labelled Console Messages. If you select that item, the window displays only this type of message.
  • You can query ASL for console messages with the command shown in Listing 10.

Listing 10 Querying ASL for console messages

Note: Prior to Mac OS X 10.5, console messages were recorded in a file. You can view these messages using the Console application, or using the commands shown in Listing 11.

$ # This command just does a query and prints the results.
$ syslog -C
[…]
[…].com.apple.TextEdit[4373] <Notice>: Hello Cruel World!
$ # This command does the query, prints the results, and then waits 
$ # for any new results. Press ^C to stop it.
$ syslog -w -C
[…]
[…].com.apple.TextEdit[4373] <Notice>: Hello Cruel World!
[…].com.apple.TextEdit[4373] <Notice>: Goodbye Cruel World!
^C

Listing 11 Viewing console messages prior to Mac OS X 10.5

Attaching to a running program (using Xcode’s Attach to Process menu, or the attach command in GDB) does not automatically connect the program’s stderr to your GDB window. You can do this from within GDB using the trick described in the “Seeing stdout and stderr After Attaching” section of Technical Note TN2030, ‘GDB for MacsBug Veterans’.

$ # This command prints the entire console log.
$ cat /Library/Logs/Console/`id -u`/console.log
[…]
Hello Cruel World!
$ # This command prints the last few lines of the console log 
$ # and then waits for any new results. Press ^C to stop it.
$ tail -f /Library/Logs/Console/`id -u`/console.log
[…]
Hello Cruel World!
Goodbye Cruel World!
^C

三、Some Assembly Required

While it’s very unusual to write a significant amount of code in assembly language these days, it’s still useful to have a basic understanding of that dark art.

This is particularly true when you’re debugging, especially when you’re debugging crashes that occur in libraries or frameworks for which you don’t have the source code.

This section covers some of the most basic techniques necessary to debug programs at the assembly level. Specifically, it describe how to set breakpoints, access parameters, and access the return address on all supported architectures.

All the assembly-level debugging examples shown in this technote are from 64-bit programs running on an Intel-based Macintosh computer. However, it’s relatively easy to adapt these examples to other architectures. The most significant differences are:

  • accessing parameters
  • getting the return address

And these are exactly the items covered by the following architecture-specific sections.

Important: The following architecture-specific sections contain rules of thumb. If the routine has any non-standard parameters, or a non-standard function result, these rules of thumb do not apply, and you should consult the documentation for the details.

In this context, standard parameters are integers (that fit in a single register), enumerations, and pointers (including pointers to arrays and pointers to functions). Non-standard parameters are floating point numbers, vectors, structures, integers bigger than a register, and any parameter after the last fixed parameter of a routine that takes a variable number of arguments.

For a detailed description of the calling conventions for all Mac OS X architectures, see Mac OS X ABI Function Call Guide.

Before you read the following sections, it’s critical that you understand one GDB subtlety. Because GDB is, at heart, a source-level debugger, when you set a breakpoint on a routine, GDB does not set the breakpoint on the first instruction of the routine; rather, it sets the breakpoint at the first instruction after the routine’s prologue. From a source-level debugging point of view this make perfect sense. In a source-level debugger you never want to step through the routine’s prologue. However, when doing assembly-level debugging, it’s easier to access parameters before the prologue runs. That’s because the location of the parameters at the first instruction of a routine is determined by the function call ABI, but the prologue is allowed to shuffle things around at its discretion. Moreover, each prologue can do this in a slightly different way. So the only way to access parameters after the prologue has executed is to disassemble the prologue and work out where everything went. This is typically, but not always, quite easy, but it’s still extra work.

The best way to tell GDB to set a breakpoint at the first instruction of a routine is to prefix the routine name with a “*”. Listing 12 shows an example of this.


Listing 12 Before and after the prologue

$ gdb
GNU gdb 6.3.50-20050815 (Apple version gdb-1346) […]
(gdb) attach Finder
[…]
(gdb) b CFStringCreateWithFormat
Breakpoint 1 at 0x7fff86098290
(gdb) info break
Num Type        Disp Enb Address            What
1   breakpoint  keep y   0x00007fff86098290 <CFStringCreateWithFormat+32>
(gdb) # The breakpoint is not at the first instruction.
(gdb) # Disassembling the routine shows that GDB has skipped the prologue.
(gdb) x/7i CFStringCreateWithFormat
0x7fff86098270 <CFStringCreateWithFormat>: push   %rbp
0x7fff86098271 <CFStringCreateWithFormat+1>: mov    %rsp,%rbp
0x7fff86098274 <CFStringCreateWithFormat+4>: sub    $0xd0,%rsp
0x7fff8609827b <CFStringCreateWithFormat+11>: mov    %rcx,-0x98(%rbp)
0x7fff86098282 <CFStringCreateWithFormat+18>: mov    %r8,-0x90(%rbp)
0x7fff86098289 <CFStringCreateWithFormat+25>: mov    %r9,-0x88(%rbp)
0x7fff86098290 <CFStringCreateWithFormat+32>: movzbl %al,%ecx
(gdb) # So we use a "*" prefix to disable GDB's 'smarts'.
(gdb) b *CFStringCreateWithFormat
Breakpoint 2 at 0x7fff86098270
(gdb) info break
Num Type        Disp Enb Address            What
1   breakpoint  keep y   0x00007fff86098290 <CFStringCreateWithFormat+32>
2   breakpoint  keep y   0x00007fff86098270 <CFStringCreateWithFormat>
Finally, if you're looking for information about specific instructions, be aware that the Help menu in Shark (included in the Xcode developer tools) has an instruction set reference for ARM, Intel and PowerPC architectures.

Finally, if you’re looking for information about specific instructions, be aware that the Help menu in Shark (included in the Xcode developer tools) has an instruction set reference for ARM, Intel and PowerPC architectures.

1、Intel 64-Bit

In 64-bit Intel programs the first six parameters are passed in registers. The return address is on the stack, but keep in mind that it is a 64-bit value. Table 1 shows how to access these values from GDB when you’ve stopped at the first instruction of the function.

What GDB Syntax
return address (long)$esp
first parameter $rdi
second parameter $rsi
third parameter $rdx
fourth parameter $rcx
fifth parameter $r8
sixth parameter $r9

On return from a function the result is in register RAX ($rax).

Because parameters are passed in registers, there’s no straightforward way to access parameters after the prologue.

Listing 13 shows an example of how to use this information to access parameters in GDB.

Listing 13 Parameters on Intel 64-Bit

$ # Use the -arch x86_64 argument to GDB to get it to run the 
$ # 64-bit Intel binary. Obviously this will only work on 64-bit capable 
$ # hardware.
$ gdb -arch x86_64 /Applications/TextEdit.app
GNU gdb 6.3.50-20050815 (Apple version gdb-1346) […]
(gdb) fb CFStringCreateWithFormat
Breakpoint 1 at 0x624dd2f1959290
(gdb) r
Starting program: /Applications/TextEdit.app/Contents/MacOS/TextEdit 
Reading symbols for shared libraries […]
Breakpoint 1, 0x00007fff86098290 in CFStringCreateWithFormat ()
(gdb) # We've stopped after the prologue.
(gdb) p/a $pc
$1 = 0x7fff86098290 <CFStringCreateWithFormat+32>
(gdb) # We have to check whether the prologue 
(gdb) # has messed up the locations of the parameters.
(gdb) x/7i $pc-32
0x7fff86098270 <CFStringCreateWithFormat>: push   %rbp
0x7fff86098271 <CFStringCreateWithFormat+1>: mov    %rsp,%rbp
0x7fff86098274 <CFStringCreateWithFormat+4>: sub    $0xd0,%rsp
0x7fff8609827b <CFStringCreateWithFormat+11>: mov    %rcx,-0x98(%rbp)
0x7fff86098282 <CFStringCreateWithFormat+18>: mov    %r8,-0x90(%rbp)
0x7fff86098289 <CFStringCreateWithFormat+25>: mov    %r9,-0x88(%rbp)
0x7fff86098290 <CFStringCreateWithFormat+32>: movzbl %al,%ecx
(gdb) # Prologue hasn't messed up parameter registers, 
(gdb) # so we can just print them directly.
(gdb) #
(gdb) # first parameter is "alloc"
(gdb) p/a $rdi
$2 = 0x7fff70b8bf20 <__kCFAllocatorSystemDefault>
(gdb) # second parameter is "formatOptions"
(gdb) p/a $rsi
$3 = 0x0
(gdb) # third parameter is "format"
(gdb) call (void)CFShow($rdx)
%@
(gdb) # return address is at RBP+8
(gdb) p/a *(long*)($rbp+8)
$4 = 0x7fff8609a474 <__CFXPreferencesGetNamedVolatileSourceForBundleID+36>
(gdb) # Now clear the breakpoint and set a new one before the prologue.
(gdb) del 1
(gdb) b *CFStringCreateWithFormat
Breakpoint 2 at 0x7fff86098270
(gdb) c
Continuing.

Breakpoint 2, 0x00007fff86098270 in CFStringCreateWithFormat ()
(gdb) # We're at the first instruction. We can 
(gdb) # access parameters without checking the prologue.
(gdb) p/a $pc
$5 = 0x7fff86098270 <CFStringCreateWithFormat>
(gdb) # first parameter is "alloc"
(gdb) p/a $rdi
$6 = 0x7fff70b8bf20 <__kCFAllocatorSystemDefault>
(gdb) # second parameter is "formatOptions"
(gdb) p/a $rsi
$7 = 0x0
(gdb) # third parameter is "format"
(gdb) call (void)CFShow($rdx)
managed/%@/%@
(gdb) # return address is on top of the stack
(gdb) p/a *(long*)$rsp
$8 = 0x7fff8609806a <__CFXPreferencesGetManagedSourceForBundleIDAndUser+74>
(gdb) # Set a breakpoint on the return address.
(gdb) b *0x7fff8609806a
Breakpoint 3 at 0x7fff8609806a
(gdb) c
Continuing.

Breakpoint 3, 0x00007fff8609806a in __CFXPreferencesGetManagedSourceForBundleIDAndUser ()
(gdb) # function result
(gdb) p/a $rax