Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 1 | # Debugging Chromium on macOS |
| 2 | |
| 3 | [TOC] |
| 4 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 5 | ## Debug vs. Release Builds |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 6 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 7 | Debug builds are the default configuration for Chromium and can be explicitly |
| 8 | specified with `is_debug=true` in the `args.gn` file of the out directory. Debug |
| 9 | builds are larger and non-portable because they default to |
| 10 | `is_component_build=true`, but they contain full debug information. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 11 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 12 | If you set `is_debug=false`, a release build will be created with no symbol |
| 13 | information, which cannot be used for effective debugging. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 14 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 15 | A middle-ground is to set `symbol_level=1`, which will produce a minimal symbol |
| 16 | table, capable of creating backtraces, but without frame-level local variables. |
| 17 | This is faster to build than a debug build, but it is less useful for debugging. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 18 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 19 | When doing an `is_official_build=true` build (which is meant for producing |
| 20 | builds with full compiler optimization suitable for shipping to users), |
| 21 | `enable_dsyms` and `enable_stripping` both get set to `true`. The binary itself |
| 22 | will be stripped of its symbols, but the debug information will be saved off |
| 23 | into a dSYM file. Producing a dSYM is rather slow, so it is uncommon for |
| 24 | developers to build with this configuration. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 25 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 26 | ### Chrome Builds |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 27 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 28 | The official Google Chrome build has published dSYMs that can be downloaded with |
| 29 | the script at `tools/mac/download_symbols.py` or by using the LLDB integration |
| 30 | at `tools/lldb/lldb_chrome_symbols.py`. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 31 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 32 | However, the official Chrome build is |
| 33 | [codesigned](../../chrome/installer/mac/signing/README.md) with the `restrict` |
| 34 | and `runtime` options, which generally prohibit debuggers from attaching. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 35 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 36 | In order to debug production/released Chrome, you need to do one of two things: |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 37 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 38 | 1. Disable [System Integrity |
| 39 | Protection](https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection), |
| 40 | by: |
| 41 | 1. Rebooting into macOS recovery mode |
| 42 | 2. Launching Terminal |
| 43 | 3. Running `csrutil enable --without debug` |
| 44 | 4. Rebooting |
| 45 | 2. Stripping or force-re-codesigning the binary to not use those options: |
| 46 | `codesign --force --sign - path/to/Google\ Chrome.app` |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 47 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 48 | If you will frequently debug official builds, (1) is recommended. Note that |
| 49 | disabling SIP reduces the overall security of the system, so your system |
| 50 | administrator may frown upon it. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 51 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 52 | ## The Debugger |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 53 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 54 | The debugger on macOS is `lldb` and it is included in both a full Xcode install |
| 55 | and the Command Line Tools package. There are two ways to use LLDB: either |
| 56 | launching Chromium directly in LLDB, or attaching to an existing process: |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 57 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 58 | lldb ./out/debug/Chromium.app/Contents/MacOS/Chromium |
| 59 | lldb -p <pid> |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 60 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 61 | LLDB has an extensive help system which you can access by typing `help` at the |
| 62 | `(lldb)` command prompt. The commands are organized into a functional hierarchy, |
| 63 | and you can explore the subcommands via `(lldb) help breakpoint`, etc. Commands |
| 64 | can take arguments in a command-line flag style. Many commands also have short |
| 65 | mnemonics that match the `gdb` equivalents. You can also just use enough letters |
| 66 | to form a unique prefix of the command hierarchy. E.g., these are equivalent: |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 67 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 68 | (lldb) help breakpoint set |
| 69 | (lldb) h br s |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 70 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 71 | When the program is running, you can use **Ctrl-C** to interrupt it and pause |
| 72 | the debugger. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 73 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 74 | ### Passing Arguments |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 75 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 76 | To pass arguments to LLDB when starting Chromium, use a `--`: |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 77 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 78 | lldb ./out/debug/Chromium.app/contents/MacOS/Chromium -- --renderer-startup-dialog |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 79 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 80 | ### Breakpoints |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 81 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 82 | Simple function-name breakpoints can be specified with a short mnemonic: |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 83 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 84 | (lldb) b BrowserWindow::Close |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 85 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 86 | But there are a range of other options for setting breakpoints using the, such |
| 87 | as: |
| 88 | * `-t` to limit the breakpoint to a specific thread |
| 89 | * `-s` to specify a specific shared library, if the same symbol name is exported |
| 90 | by multiple libraries |
| 91 | * `-o` for a one-shot breakpoint (delete after first hit) |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 92 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 93 | See `(lldb) help br set` for full details. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 94 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 95 | ### Navigating the Stack |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 96 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 97 | When the debugger is paused, you can get a backtrace by typing `bt`. To navigate |
| 98 | the stack by-1 type either `up` or `down`. You can also jump to a specific index |
| 99 | in the stack by typing `f #` (short for `frame select #`). |
| 100 | |
| 101 | To see local variables, type `v` (short for `frame variable`). |
| 102 | |
| 103 | ### Examining Execution |
| 104 | |
| 105 | To step through the program, use: |
| 106 | |
| 107 | * `s` or `si` for step-in |
| 108 | * `n` for step-over |
| 109 | * `c` to continue (resume or go to next breakpoint) |
| 110 | |
| 111 | ### Printing Values |
| 112 | |
| 113 | To print values, use the `p <value>` command, where `<value>` can be a variable, |
| 114 | a variable expression like `object->member_->sub_member_.value`, or an address. |
| 115 | |
| 116 | If `<value>` is a pointer to a structure, `p <value>` will usually just print |
| 117 | the address. To show the contents of the structure, dereference the value. E.g.: |
| 118 | |
| 119 | (lldb) p item |
| 120 | (HistoryMenuBridge::HistoryItem *) $3 = 0x0000000245ef5b30 |
| 121 | (lldb) p *item |
| 122 | (HistoryMenuBridge::HistoryItem) $4 = { |
| 123 | title = u"Google" |
| 124 | url = { |
| 125 | spec_ = "https://www.google.com/" |
| 126 | is_valid_ = true |
| 127 | … |
| 128 | |
| 129 | Note above that LLDB has also stored the results of the expressions passed to |
| 130 | `p` into the variables `$3` and `$4`, which can be referenced in other LLDB |
| 131 | expressions. |
| 132 | |
| 133 | Often (and always when printing addresses) there is not type information to |
| 134 | enable printing the full structure of the referenced memory. In these cases, use |
| 135 | a C-style cast: |
| 136 | |
| 137 | (lldb) p 0x0000000245ef5b30 # Does not have type information |
| 138 | (long) $5 = 9763248944 |
| 139 | (lldb) p (HistoryMenuBridge::HistoryItem*)0x0000000245ef5b30 |
| 140 | (HistoryMenuBridge::HistoryItem *) $6 = 0x0000000245ef5b30 |
| 141 | (lldb) p *$6 |
| 142 | (HistoryMenuBridge::HistoryItem) $7 = { |
| 143 | title = u"Google" |
| 144 | … |
| 145 | |
| 146 | * For printing Cocoa NSObjects, use the `po` command to get the `-[NSObject description]`. |
| 147 | * If `uptr` is a `std::unique_ptr`, the address it wraps is accessible as |
| 148 | `uptr.__ptr_.__value_`. |
| 149 | * To pretty-print `std::u16string`, follow the instructions [here](../lldbinit.md). |
| 150 | |
| 151 | ## Multi-Process Debugging |
| 152 | |
| 153 | Chrome is split into multiple processes, which can mean that the logic you want |
| 154 | to debug is in a different process than the main browser/GUI process. There are |
| 155 | a few ways to debug the multi-process architecture, discussed below. |
| 156 | |
| 157 | ### (a) Attach to a Running Process |
| 158 | |
| 159 | You can use Chrome's Task Manager to associate specific sites with their PID. |
| 160 | Then simply attach with LLDB: |
| 161 | |
| 162 | lldb -p <pid> |
| 163 | |
| 164 | Or, if you have already been debugging a Chrome process and want to retain your |
| 165 | breakpoints: |
| 166 | |
| 167 | (lldb) attach <pid> |
| 168 | |
| 169 | ### (b) Debug Process Startup |
| 170 | |
| 171 | If you need to attach early in the child process's lifetime, you can use one of |
| 172 | these startup-dialog switches for the relevant process type: |
| 173 | |
| 174 | * `--renderer-startup-dialog` |
| 175 | * `--utility-startup-dialog` |
Alex Gough | 2092674 | 2021-05-13 20:11:30 | [diff] [blame] | 176 | * `--utility-startup-dialog=data_decoder.mojom.DataDecoderService` |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 177 | |
| 178 | After the process launches, it will print a message like this to standard error: |
| 179 | |
| 180 | [80156:775:0414/130021.862239:ERROR:content_switches_internal.cc(112)] Renderer (80156) paused waiting for debugger to attach. Send SIGUSR1 to unpause. |
| 181 | |
| 182 | Then attach the the process like above in **(a)**, using the PID in parenthesis |
| 183 | (e.g. *80156* above). |
| 184 | |
| 185 | ### (c) Run Chrome in a single process |
| 186 | |
| 187 | > This option is not recommended. Single-process mode is not tested and is |
| 188 | > frequently broken. |
| 189 | |
| 190 | Chrome has an option to run all child processes as threads inside a single |
| 191 | process, using the `--single-process` command line flag. This can make debugging |
| 192 | easier. |
| 193 | |
| 194 | ## Debugging Out-of-Process Tests: |
| 195 | |
| 196 | Similar to debugging the renderer process, simply attaching LLDB to a |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 197 | out-of-process test like browser\_tests will not hit the test code. In order to |
| 198 | debug a browser test, you need to run the test binary with "`--single_process`" |
| 199 | (note the underscore in `single_process`). Because you can only run one browser |
| 200 | test in the same process, you're probably going to need to add `--gtest_filter` |
| 201 | as well. So your command will look like this: |
| 202 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 203 | /path/to/src/out/debug/browser_tests --single_process --gtest_filter=GoatTeleporterTest.DontTeleportSheep |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 204 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 205 | ## Working with Xcode |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 206 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 207 | If you'd prefer to use Xcode GUI to use the debugger, there are two options: |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 208 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 209 | ### (1) Empty Xcode Project |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 210 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 211 | This approach creates an empty Xcode project that only provides a GUI debugger: |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 212 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 213 | 1. Select **File** > **New** > **Project...** and make a new project. Dump it |
| 214 | anywhere, call it anything. It doesn't matter. |
| 215 | 2. Launch Chromium. |
| 216 | 3. In Xcode, select **Debug** > **Attach to Process** > *Chromium*. |
| 217 | 4. You can now pause the process and set breakpoints. The debugger will also |
| 218 | activate if a crash occurs. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 219 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 220 | ### (2) Use *gn* |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 221 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 222 | 1. Tell `gn` to generate an Xcode project for your out directory: |
| 223 | `gn gen --ide=xcode out/debug` |
| 224 | 2. Open *out/debug/all.xcodeproj* |
| 225 | 3. Have it automatically generate schemes for you |
| 226 | 4. You can now build targets from within Xcode, which will simply call out to |
| 227 | `ninja` via an Xcode script. But the resulting binaries are available as |
| 228 | debuggable targets in Xcode. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 229 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 230 | Note that any changes to the .xcodeproj will be overwritten; all changes to the |
| 231 | build system need to be done in GN. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 232 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 233 | ## Debugging the Sandbox |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 234 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 235 | See the page on [sandbox debugging](sandbox_debugging.md). |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 236 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 237 | ## System Permission Prompts; Transparency, Consent, and Control (TCC) |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 238 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 239 | When debugging issues with OS-mediated permissions (e.g. Location, Camera, |
| 240 | etc.), you need to launch Chromium with LaunchServices rather than through a |
| 241 | shell. If you launch Chromium as a subprocess of your terminal shell, the |
| 242 | permission requests get attributed to the terminal app rather than Chromium. |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 243 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 244 | To launch Chromium via launch services, use the `open(1)` command: |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 245 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 246 | open ./out/debug/Chromium.app |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 247 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 248 | To pass command line arguments: |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 249 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 250 | open ./out/debug/Chromium.app -- --enable-features=MyCoolFeature |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 251 | |
| 252 | ## Taking CPU Samples |
| 253 | |
| 254 | A quick and easy way to investigate slow or hung processes is to use the sample |
| 255 | facility, which will generate a CPU sample trace. This can be done either in the |
| 256 | Terminal with the sample(1) command or by using Activity Monitor: |
| 257 | |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 258 | 1. Open Activity Monitor |
| 259 | 2. Find the process you want to sample (for "Helper" processes, you may want to |
| 260 | consult the Chrome Task Manager) |
| 261 | 3. Double-click on the row |
| 262 | 4. Click the **Sample** button in the process's information window |
Robert Sesek | 92b004c | 2021-04-08 20:12:34 | [diff] [blame] | 263 | |
| 264 | After a few seconds, the sample will be completed. For official Google Chrome |
| 265 | builds, the sample should be symbolized using |
| 266 | [crsym](https://goto.google.com/crsym/). If you do not have access to crsym, |
| 267 | save the *entire* contents as a file and attach it to a bug report for later |
| 268 | analysis. |
| 269 | |
| 270 | See also [How to Obtain a Heap |
| 271 | Dump](../memory-infra/heap_profiler.md#how-to-obtain-a-heap-dump-m66_linux_macos_windows). |
Robert Sesek | 3b731b1 | 2021-04-14 21:54:03 | [diff] [blame] | 272 | |
| 273 | ## Working with Minidumps |
| 274 | |
| 275 | [See this |
| 276 | page.](https://sites.google.com/a/chromium.org/dev/developers/crash-reports) |
| 277 | |
| 278 | ## Disabling ReportCrash |
| 279 | |
| 280 | macOS helpfully tries to write a crash report every time a binary crashes – |
| 281 | which happens for example when a test in unit\_tests fails. Since Chromium's |
| 282 | debug binaries are huge, this takes forever. If this happens, "ReportCrash" will |
| 283 | be the top cpu consuming process in Activity Monitor. You should disable |
| 284 | ReportCrash while you work on Chromium. Run `man ReportCrash` to learn how to do |
| 285 | this on your version of macOS. On 10.15, the command is |
| 286 | |
| 287 | launchctl unload -w /System/Library/LaunchAgents/com.apple.ReportCrash.plist |
| 288 | sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.ReportCrash.Root.plist |
| 289 | |
| 290 | Yes, you need to run this for both the normal user and the admin user. |
| 291 | |
| 292 | ## Processing Apple Crash Reports |
| 293 | |
| 294 | If you get a Google Chrome crash report caught by ReportCrash/macOS, it will not |
| 295 | have symbols (every frame will be ChromeMain). To get a symbolized stack trace, |
| 296 | use the internal [crsym](httsp://goto.google.com/crsym) tool by simply pasting |
| 297 | the contents of an entire Apple crash report. |
| 298 | |
| 299 | ## Testing Other Locales |
| 300 | |
| 301 | To test Chrome in a different locale, change your system locale via the System |
| 302 | Preferences. (Keep the preferences window open so that you can change the |
| 303 | locale back without needing to navigate through menus in a language you may not |
| 304 | know.) |
| 305 | |
| 306 | ## Using DTrace |
| 307 | |
| 308 | DTrace is a powerful, kernel-level profiling and dynamic tracing utility. In |
| 309 | order to use DTrace, you need to (at least partially) disable System Integrity |
| 310 | Protection with ([see above](#Chrome-Builds)): |
| 311 | |
| 312 | csrutil enable --without dtrace |
| 313 | |
| 314 | Using DTrace is beyond the scope of this document, but the following resources |
| 315 | are useful: |
| 316 | |
| 317 | * [jgm's awesome introductory article](http://www.mactech.com/articles/mactech/Vol.23/23.11/ExploringLeopardwithDTrace/index.html) |
| 318 | * [Big Nerd Ranch's four-part series](https://www.bignerdranch.com/blog/hooked-on-dtrace-part-1/) |
| 319 | * [Defining static probes on macOS](http://www.macresearch.org/tuning-cocoa-applications-using-dtrace-custom-static-probes-and-instruments) |
| 320 | * [Examples](http://www.brendangregg.com/dtrace.html#Examples) |
| 321 | * [Tips from Sun](http://blogs.sun.com/bmc/resource/dtrace_tips.pdf) |
| 322 | |
| 323 | DTrace examples on macOS: `/usr/share/examples/DTTk` |
| 324 | |
| 325 | To get truss on macOS, use dtruss. That requires root, so use `sudo dtruss -p` |
| 326 | and to attach to a running non-root program. |
| 327 | |
| 328 | ## Memory/Heap Inspection |
| 329 | |
| 330 | Chrome has [built-in memory instrumentation](../memory-infra/README.md) that can |
| 331 | be used to identify allocations and potential leaks. |
| 332 | |
| 333 | MacOS also provides several low-level command-line tools that can be used to |
| 334 | inspect what's going on with memory inside a process. |
| 335 | |
| 336 | **`heap`** summarizes what's currently in the malloc heap(s) of a process. (It |
| 337 | only works with regular malloc, of course, but Mac Chrome still uses that.) It |
| 338 | shows a number of useful things: |
| 339 | |
| 340 | * How much of the heap is used or free |
| 341 | * The distribution of block sizes |
| 342 | * A listing of every C++, Objective-C and CoreFoundation class found in the |
| 343 | heap, with the number of instances, total size and average size. |
| 344 | |
| 345 | It identifies C++ objects by their vtables, so it can't identify vtable-less |
| 346 | classes, including a lot of the lower-level WebCore ones like StringImpl. To |
| 347 | work around, temporarily added the `virtual` keyword to `WTF::RefCounted`'s |
| 348 | destructor method, which forces every ref-counted object to include a vtable |
| 349 | pointer identifying its class. |
| 350 | |
| 351 | **`malloc_history`** identifies the stack backtrace that allocated every malloc |
| 352 | block in the heap. It lists every unique backtrace together with its number of |
| 353 | blocks and their total size. It requires that the process use malloc stack |
| 354 | logging, which is enabled if the environment variable MallocStackLogging is set |
| 355 | when it launches. The `env` command is handy for this: |
| 356 | |
| 357 | $ env MallocStackLogging=1 Chromium.app/Contents/MacOS/Chromium |
| 358 | |
| 359 | Then in another shell you run |
| 360 | |
| 361 | $ malloc_history <pid> -all_by_size |
| 362 | |
| 363 | Watch out: the output is *big*. |
| 364 | |
| 365 | **`leaks`** finds malloc blocks that have no pointers to them and are probably |
| 366 | leaked. It doesn't require MallocStackLogging, but it's more useful if it's on |
| 367 | because it can then show the backtrace that allocated each leaked block. |
| 368 | |
| 369 | **`vmmap`** shows all the virtual-memory regions in the process's address space. |
| 370 | This is less useful since it doesn't say anything about individual malloc blocks |
| 371 | (except huge ones) but it can be useful for looking at things like static data |
| 372 | size, mapped files, and how much memory is paged out. The "-resident" flag shows |
| 373 | how much of each allocation is currently paged into RAM. See the man page for |
| 374 | details. |
| 375 | |
| 376 | Notes: |
| 377 | |
| 378 | * These are not going to be very useful on stripped binaries, and they're less |
| 379 | useful in release builds. |
| 380 | * All of these except vmmap take several *minutes* to run, apparently because |
| 381 | of the number of symbols in Chrome. They spend most of their time pegging |
| 382 | one CPU down inside system code that's reading symbol tables from the |
| 383 | binary. Be patient. |
| 384 | * Instruments is an application bundled with Xcode that provides GUI interfaces |
| 385 | for many of these tools, including `sample` and `leaks`. Most Chromies prefer |
| 386 | the command line tools, but Instruments can be useful. If running Instruments |
| 387 | on a local build, expect to wait a few minutes for it to load symbols before |
| 388 | it starts recording useful data |
| 389 | |
| 390 | ## Resources |
| 391 | |
| 392 | The [Mac OS X Debugging Magic |
| 393 | Technote](https://developer.apple.com/technotes/tn2004/tn2124.html) contains a |
| 394 | wealth of (mostly outdated) information about various debugging options built in |
| 395 | to macOS. |