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