354 lines
16 KiB
Markdown
354 lines
16 KiB
Markdown
# Debugging On macOS
|
|
|
|
This document explains how to debug Gecko-based applications such as
|
|
Firefox, Thunderbird, and SeaMonkey on macOS using Xcode. If you want to
|
|
debug from the terminal see {ref}`Debugging Mozilla with
|
|
lldb <Debugging Firefox with LLDB>`. For specific
|
|
information on a way to debug hangs, see {ref}`Debugging a hang on macOS <Debugging A Hang On macOS>`.
|
|
|
|
## Creating a debuggable build
|
|
|
|
First, you need to build the application you're going to debug using
|
|
this in your .mozconfig
|
|
|
|
```
|
|
ac_add_options --disable-optimize
|
|
ac_add_options --enable-debug-symbols
|
|
```
|
|
|
|
you can also add this flag if you want assertions etc. compiled in
|
|
|
|
```
|
|
ac_add_options --enable-debug
|
|
```
|
|
|
|
See {ref}`Building Firefox for macOS <Building Firefox On MacOS>`
|
|
if you need help creating your own build.
|
|
|
|
## Debugging Firefox on macOS 10.14+
|
|
|
|
macOS 10.14 introduced Notarization and Hardened Runtime features for
|
|
improved application security. macOS 10.15 went further, requiring
|
|
applications to be Notarized with Hardened Runtime enabled in order to
|
|
launch (ignoring workarounds). When run on earlier macOS versions,
|
|
Notarization and Hardened Runtime settings have no effect.
|
|
|
|
### Official Builds
|
|
|
|
At this time, official builds of Firefox 69 and later are Notarized.
|
|
**As a result, it is not possible to attach a debugger to these official
|
|
Firefox releases on macOS 10.14+ without disabling System Integrity
|
|
Protection (SIP).** This is due to Notarization requiring Hardened
|
|
Runtime to be enabled with the `com.apple.security.get-task-allow`
|
|
entitlement disallowed. **Rather than disabling SIP (which has security
|
|
implications), it is recommended to debug with try builds or local
|
|
builds. The differences are explained below.**
|
|
|
|
### try Server Builds
|
|
|
|
In most cases, developers needing to debug a build as close as possible
|
|
to the production environment should use a {ref}`try
|
|
build <Pushing to Try>`. These
|
|
builds enable Hardened Runtime and only differ from production builds in
|
|
that they are not Notarized which should not otherwise affect
|
|
functionality, (other than the ability to easily launch the browser on
|
|
macOS 10.15+ -- see quarantine note below). At this time, developers can
|
|
obtain a Hardened Runtime build with the
|
|
`com.apple.security.get-task-allow` entitlement allowed by submitting
|
|
a try build and downloading the dmg generated by the "Rpk" shippable
|
|
build job. A debugger can be attached to Firefox processes of these
|
|
builds. try builds use the developer entitlement files from the
|
|
source tree (allowing debugger attach) while production builds use
|
|
the production versions (which must meet notarization requirements).
|
|
**On macOS 10.15+, downloaded try builds will not launch by default
|
|
because Notarization is required. To workaround this problem, remove the
|
|
quarantine extended attribute from the downloaded Nightly:**
|
|
|
|
`$ xattr -r -d com.apple.quarantine /Path/to/Nightly.app`
|
|
|
|
### Local Builds
|
|
|
|
Local builds of mozilla-central do not enable Hardened Runtime and hence
|
|
do not have debugging restrictions. As a result, some functionality will
|
|
be permitted on local builds, but blocked on production builds which
|
|
have Hardened Runtime enabled. [Bug
|
|
1522409](https://bugzilla.mozilla.org/show_bug.cgi?id=1522409) was
|
|
filed to automate codesigning local builds to enable Hardened Runtime by
|
|
default and eliminate this discrepancy.
|
|
|
|
### Disabling System Integrity Protection (SIP)
|
|
|
|
If debugging a production build is required, follow Apple's documented
|
|
steps for disabling System Integrity Protection (SIP). Note that
|
|
disabling SIP bypasses Hardened Runtime restrictions which can mask some
|
|
bugs that only occur with Hardened Runtime so it is recommended to test
|
|
fixes with SIP enabled. **Disabling SIP has system security implications
|
|
that should be understood before taking this step.**
|
|
|
|
## Creating an Xcode project
|
|
|
|
Note also that since Xcode 7.3.1 it doesn't seem to be possible to
|
|
have the Xcode project live outside the source tree. If you try to do
|
|
that then Xcode will simply **copy** the source files under the
|
|
project directory rather than link to them which breaks debugging and the
|
|
possibility to modify-rebuild-relaunch from inside Xcode.
|
|
|
|
These steps were last updated for Xcode 26.5:
|
|
|
|
Initial setup:
|
|
|
|
1. Open Xcode, and create a new Project with File > New > Project.
|
|
Select the "Other" tab then select the "Empty" project type, then
|
|
click Next.
|
|
2. Name the project and click Next. Select the root directory of the
|
|
Mozilla source code, ensure that "Create Git repository" is not
|
|
checked, then click Next again.
|
|
3. Xcode will create a subdirectory with the name that you gave as the
|
|
project name. It will contain a single file, again with the same
|
|
project name along with the `.xcodeproj` file name extension.
|
|
4. Optional: if you want the `.xcodeproj` file directly in your source
|
|
directory, exit Xcode, move the file there, and delete the now
|
|
empty project directory. Reopen the project by double clicking the
|
|
`.xcodeproj` file.
|
|
5. In the left-hand pane in Xcode you should see a tree item where the
|
|
root item has the project name. Right click on the root item,
|
|
select 'Add files to "\<project-name>"', select all the directories
|
|
in your root source directory (or the ones you care about) and
|
|
click "Add".
|
|
6. At the "Choose options for adding these files" window that opens,
|
|
make sure that "Action" is set to "Reference files in place", then
|
|
click "Finish".
|
|
7. Xcode may take a few minutes to process and add all the files to
|
|
the project, and may appear to hang, pegged at 100% CPU usage.
|
|
8. Xcode will continue to process files in the background once it
|
|
becomes responsive again, but while it's doing that the directories
|
|
shown in the project tree in the left hand pane may be slow to
|
|
expand.
|
|
9. You should be able to open any file quickly by hitting
|
|
Cmd-Shift-O and typing in part of the name of a file.
|
|
|
|
Setting up debugging:
|
|
|
|
1. Make sure you've built the source, since you need to point to the
|
|
`.app` in the steps below.
|
|
2. In the Product menu, select `Scheme > New Scheme...`. When prompted,
|
|
enter a name for your scheme (for example, "Debug"). If you're
|
|
object directory is inside your source directory, then select the
|
|
`.app` file that you want to debug an the Target. If your object
|
|
directory lives outside your source directory, then for now select
|
|
a random binary from the Target list (Xcode can't at this point
|
|
reference a binary outside the source directory, but we'll fix that
|
|
below). Click OK.
|
|
3. If Xcode doesn't open the Scheme Editor window, then open it via
|
|
`Product > Scheme > Edit Scheme...` menu.
|
|
4. If you were not able to select the binary you wanted previously:
|
|
|
|
1. Select "Run" on the left-hand side of the settings window, then
|
|
select the "Info" tab.
|
|
2. Expand the Executable field and select `Other...`. Navigate to the
|
|
`.app` that you want to debug in your object directory and select
|
|
it. (The `.app` file is typically found inside the `dist` folder
|
|
in your build directory, for example `Firefox.app`.)
|
|
3. Select `Profile` on the left hand side and repeat this process.
|
|
4. Select `Build` on the left hand side and under Targets remove
|
|
the random dummy target you added previously. Make sure the new
|
|
one you just added has all its checkboxes checked.
|
|
|
|
5. If you are debugging Firefox, Thunderbird, or some other application
|
|
that supports multiple profiles, using a separate profile for
|
|
debugging purposes is recommended. See "Having a profile for
|
|
debugging purposes" below. Select "Run" from the left hand side and
|
|
in the "Arguments" tab in the scheme editor, and click the '+' below
|
|
the "Arguments passed on launch" field. Add "-P *profilename*", where
|
|
*profilename* is the name of a profile you created previously.
|
|
6. Also in the "Arguments" panel, you may want to add an environment
|
|
variable MOZ_DEBUG_CHILD_PROCESS set to the value 1 to help with
|
|
debugging.
|
|
7. Click "Close" to close the scheme editor.
|
|
|
|
At this point you can run and debug the Mozilla application from Xcode
|
|
(if the application doesn't appear to open, check whether it opened in
|
|
the background). When it hits breakpoints, Xcode should open the
|
|
correct source file at the correct line.
|
|
|
|
## Setting up lldb
|
|
|
|
`lldb` is the debugger Xcode provides/uses.
|
|
|
|
:::{warning}
|
|
One important issue that the Mozilla .lldbinit file fixes is that by
|
|
default some breakpoints will be listed as "pending", and Xcode will
|
|
not stop at them. If you don't include the Mozilla's .lldbinit, you
|
|
must at least put
|
|
`settings set target.inline-breakpoint-strategy always` in your
|
|
`$HOME/.lldbinit` as recommended on {ref}`Debugging Firefox with
|
|
lldb <Debugging Firefox with LLDB>`.
|
|
:::
|
|
|
|
The
|
|
{searchfox}`.lldbinit`
|
|
file in the source tree imports many useful {searchfox}`Mozilla specific lldb settings, commands and formatters <python/lldbutils/README.txt>`
|
|
into `lldb`, but you may need to take one of the following steps to
|
|
make sure this file is used.
|
|
|
|
If you are using `lldb` on the command line (independently of Xcode)
|
|
and you will always run it from either the top source directory, the
|
|
object directory or else the dist/bin subdirectory of the object
|
|
directory, then adding the following setting to your `$HOME/.lldbinit`
|
|
is sufficient:
|
|
|
|
```
|
|
settings set target.load-cwd-lldbinit true
|
|
```
|
|
|
|
*However*, if you will run lldb from a different directory, or if you
|
|
will be running it indirectly by debugging in Xcode (Xcode always runs
|
|
lldb from "/"), then this setting will not help you. Instead, add the
|
|
following to your `$HOME/.lldbinit`:
|
|
|
|
```
|
|
# This automatically sources the Mozilla project's .lldbinit as soon as lldb
|
|
# starts or attaches to a Mozilla app (that's in an object directory).
|
|
#
|
|
# This is mainly a workaround for Xcode not providing a way to specify that
|
|
# lldb should be run from a given directory. (Xcode always runs lldb from "/",
|
|
# regardless of what directory Xcode was started from, and regardless of the
|
|
# value of the "Custom working directory" field in the Scheme's Run options.
|
|
# Therefore setting `settings set target.load-cwd-lldbinit true` can't help us
|
|
# without Xcode providing that functionality.)
|
|
#
|
|
# The following works by setting a one-shot breakpoint to break on a function
|
|
# that we know will both run early (which we want when we start first start the
|
|
# app) and run frequently (which we want so that it will trigger ASAP if we
|
|
# attach to an already running app). The breakpoint runs some commands to
|
|
# figure out the object directory path from the attached target and then
|
|
# sources the .lldbinit from there.
|
|
#
|
|
# NOTE: This scripts actions take a few seconds to complete, so the custom
|
|
# formatters, commands etc. that are added may not be immediately available.
|
|
#
|
|
breakpoint set --name nsThread::ProcessNextEvent --thread-index 1 --auto-continue true --one-shot true
|
|
breakpoint command add -s python
|
|
# This script that we run does not work if we try to use the global 'lldb'
|
|
# object, since it is out of date at the time that the script runs (for
|
|
# example, `lldb.target.executable.fullpath` is empty). Therefore we must
|
|
# get the following objects from the 'frame' object.
|
|
target = frame.GetThread().GetProcess().GetTarget()
|
|
debugger = target.GetDebugger()
|
|
|
|
# Delete our breakpoint (not actually necessary with `--one-shot true`):
|
|
target.BreakpointDelete(bp_loc.GetBreakpoint().GetID())
|
|
|
|
# For completeness, find and delete the dummy breakpoint (the breakpoint
|
|
# lldb creates when it can't initially find the method to set the
|
|
# breakpoint on):
|
|
# BUG WORKAROUND! GetID() on the *dummy* breakpoint appears to be returning
|
|
# the breakpoint index instead of its ID. We have to add 1 to correct for
|
|
# that! :-(
|
|
dummy_bp_list = lldb.SBBreakpointList(target)
|
|
debugger.GetDummyTarget().FindBreakpointsByName("nsThread::ProcessNextEvent", dummy_bp_list)
|
|
dummy_bp_id = dummy_bp_list.GetBreakpointAtIndex(0).GetID() + 1
|
|
debugger.GetDummyTarget().BreakpointDelete(dummy_bp_id)
|
|
|
|
# "source" the Mozilla project .lldbinit:
|
|
os.chdir(target.executable.fullpath.split("/dist/")[0])
|
|
debugger.HandleCommand("command source -s true " + os.path.join(os.getcwd(), ".lldbinit"))
|
|
DONE
|
|
```
|
|
|
|
see {ref}`Debugging Mozilla with
|
|
lldb <Debugging Firefox with LLDB>`. for more information.
|
|
|
|
## Having a profile for debugging purposes
|
|
|
|
It is recommended to create a separate profile to debug with, whatever
|
|
your task, so that you don't lose precious data like Bookmarks, saved
|
|
passwords, etc. So that you're not bothered with the profile manager
|
|
every time you start to debug, expand the "Executables" branch of the
|
|
"Groups & Files" list and double click on the Executable you added for
|
|
Mozilla. Click the plus icon under the "Arguments" list and type "-P
|
|
\<profile name>" (e.g. "-P MozillaDebug"). Close the window when you're
|
|
done.
|
|
|
|
## Running a debug session
|
|
|
|
Make sure breakpoints are active (which implies running under the
|
|
debugger) by opening the Product menu and selecting "Debug / Activate
|
|
Breakpoints" (also shown by the "Breakpoints" button in the top right
|
|
section of the main window). Then click the "Run" button or select "Run"
|
|
from the Product menu.
|
|
|
|
### Setting breakpoints
|
|
|
|
Setting a breakpoint is easy. Just open the source file you want to
|
|
debug in Xcode, and click in the margin to the left of the line of code
|
|
where you want to break.
|
|
|
|
During the debugging session, each time that line is executed, the
|
|
debugger will break there, and you will be able to debug it.
|
|
|
|
:::{warning}
|
|
Note that with the default configuration, some breakpoints will be
|
|
listed as "pending", and Xcode will not stop at them. If you don't
|
|
include the Mozilla's .lldbinit, you must at least put
|
|
`settings set target.inline-breakpoint-strategy always` in your
|
|
`$HOME/.lldbinit` as recommended on {ref}`Debugging Mozilla with
|
|
lldb <Debugging Firefox with LLDB>`.
|
|
:::
|
|
|
|
### Using Firefox-specific lldb commands
|
|
|
|
If you included the .lldbinit when [Setting up
|
|
lldb](#setting-up-lldb), you can use Mozilla-specific lldb commands
|
|
in the console, located in the Debug area of Xcode. For example, type
|
|
`js` to see the JavaScript stack. For more information, see {ref}`Debugging
|
|
Mozilla with lldb <Debugging Firefox with LLDB>`.
|
|
|
|
### Debugging e10s child processes
|
|
|
|
Using Xcode to debug child processes created by an e10s-enabled browser
|
|
is a little trickier than debugging a single-process browser, but it can
|
|
be done. These directions were written using Xcode 6.3.1
|
|
|
|
1. Complete all the steps above under "Creating the Project"
|
|
2. From the "Product" menu, ensure the scheme you created is selected
|
|
under "Scheme", then choose "Scheme > Edit Scheme"
|
|
3. In the resulting popup, click "Duplicate Scheme"
|
|
4. Give the resulting scheme a more descriptive name than "Copy of
|
|
Scheme"
|
|
5. Select "Run" on the left-hand side of the settings window, then
|
|
select the "Info" tab. Set the Executable by clicking on the
|
|
"Executable" drop-down, and selecting the `plugin-container.app`
|
|
that is inside the app bundle of the copy of Firefox you want to
|
|
debug.
|
|
6. On the same tab, under "Launch" select "Wait for executable to be
|
|
launched"
|
|
7. On the "Arguments" tab, remove all arguments passed on launch.
|
|
|
|
Now you're ready to start debugging:
|
|
|
|
1. From the "Product" menu, ensure the scheme you created above is
|
|
selected under "Scheme"
|
|
2. Click the "Run" button. The information area at the top of the window
|
|
will show "Waiting for plugin-container to launch"
|
|
3. From a command line, run your build of Firefox. When that launches a
|
|
child process (for example, when you start to load a webpage), Xcode
|
|
will notice and attach to that child process. You can then debug the
|
|
child process like you would any other process.
|
|
4. When you are done debugging, click the "Stop" button and quit the
|
|
instance of Firefox that you were debugging in the normal way.
|
|
|
|
For some help on using lldb see {ref}`Debugging Mozilla with
|
|
lldb <Debugging Firefox with LLDB>`.
|
|
|
|
## Other resources
|
|
|
|
Apple has an extensive list of [debugging tips and
|
|
techniques](https://developer.apple.com/library/mac/#technotes/tn2124/_index.html).
|
|
|
|
### Questions? Problems?
|
|
|
|
Try asking in our Element channels
|
|
[#developers](https://chat.mozilla.org/#/room/#developers:mozilla.org) or
|
|
[#macdev](https://chat.mozilla.org/#/room/#macdev:mozilla.org).
|