Output a linker map file for official builds
I intend to use the map file for binary size analysis.
Map file is gzipped, and is about 20MB for Android.
Adds ~2.5 seconds onto a 30 second link on my z620.
BUG=681694
Review-Url: https://codereview.chromium.org/2726983004
Cr-Original-Commit-Position: refs/heads/master@{#455482}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 7d275da6c61c065510dcbcbd237636891d4b435a
diff --git a/android/BUILD.gn b/android/BUILD.gn
index ad7b762..9b3b162 100644
--- a/android/BUILD.gn
+++ b/android/BUILD.gn
@@ -25,6 +25,9 @@
toolchain_args = invoker.toolchain_args
toolchain_args.current_os = "android"
+ # Output linker map files for binary size analysis.
+ enable_linker_map = true
+
# Make our manually injected libs relative to the build dir.
_ndk_lib =
rebase_path(invoker.sysroot + "/" + invoker.lib_dir, root_build_dir)
diff --git a/gcc_link_wrapper.py b/gcc_link_wrapper.py
index c589fe3..0e256fa 100755
--- a/gcc_link_wrapper.py
+++ b/gcc_link_wrapper.py
@@ -15,6 +15,8 @@
import subprocess
import sys
+import wrapper_utils
+
# When running on a Windows host and using a toolchain whose tools are
# actually wrapper scripts (i.e. .bat files on Windows) rather than binary
@@ -37,9 +39,12 @@
help='The strip binary to run',
metavar='PATH')
parser.add_argument('--unstripped-file',
- required=True,
help='Executable file produced by linking command',
metavar='FILE')
+ parser.add_argument('--map-file',
+ help=('Use --Wl,-Map to generate a map file. Will be '
+ 'gzipped if extension ends with .gz'),
+ metavar='FILE')
parser.add_argument('--output',
required=True,
help='Final output executable file',
@@ -51,7 +56,8 @@
# Work-around for gold being slow-by-default. http://crbug.com/632230
fast_env = dict(os.environ)
fast_env['LC_ALL'] = 'C'
- result = subprocess.call(CommandToRun(args.command), env=fast_env)
+ result = wrapper_utils.RunLinkWithOptionalMapFile(args.command, env=fast_env,
+ map_file=args.map_file)
if result != 0:
return result
diff --git a/gcc_solink_wrapper.py b/gcc_solink_wrapper.py
index 426f9d6..7efc490 100755
--- a/gcc_solink_wrapper.py
+++ b/gcc_solink_wrapper.py
@@ -78,6 +78,10 @@
required=True,
help='Output table-of-contents file',
metavar='FILE')
+ parser.add_argument('--map-file',
+ help=('Use --Wl,-Map to generate a map file. Will be '
+ 'gzipped if extension ends with .gz'),
+ metavar='FILE')
parser.add_argument('--output',
required=True,
help='Final output shared object file',
@@ -99,8 +103,10 @@
whitelist_candidates, args.resource_whitelist)
# First, run the actual link.
- result = subprocess.call(
- wrapper_utils.CommandToRun(args.command), env=fast_env)
+ command = wrapper_utils.CommandToRun(args.command)
+ result = wrapper_utils.RunLinkWithOptionalMapFile(command, env=fast_env,
+ map_file=args.map_file)
+
if result != 0:
return result
diff --git a/gcc_toolchain.gni b/gcc_toolchain.gni
index b319806..21985f8 100644
--- a/gcc_toolchain.gni
+++ b/gcc_toolchain.gni
@@ -214,6 +214,9 @@
extra_ldflags = ""
}
+ enable_linker_map =
+ defined(invoker.enable_linker_map) && invoker.enable_linker_map
+
# These library switches can apply to all tools below.
lib_switch = "-l"
lib_dir_switch = "-L"
@@ -319,18 +322,27 @@
link_command = "$ld -shared {{ldflags}}${extra_ldflags} -o \"$unstripped_sofile\" -Wl,-soname=\"$soname\" @\"$rspfile\""
+ # Generate a map file to be used for binary size analysis.
+ # Map file adds ~10% to the link time on a z620.
+ # With target_os="android", libchrome.so.map.gz is ~20MB.
+ map_switch = ""
+ if (enable_linker_map && is_official_build) {
+ map_file = "$unstripped_sofile.map.gz"
+ map_switch = " --map-file \"$map_file\""
+ }
+
assert(defined(readelf), "to solink you must have a readelf")
assert(defined(nm), "to solink you must have an nm")
strip_switch = ""
if (defined(invoker.strip)) {
- strip_switch = "--strip=${invoker.strip}"
+ strip_switch = "--strip=${invoker.strip} "
}
# This needs a Python script to avoid using a complex shell command
# requiring sh control structures, pipelines, and POSIX utilities.
# The host might not have a POSIX shell and utilities (e.g. Windows).
solink_wrapper = rebase_path("//build/toolchain/gcc_solink_wrapper.py")
- command = "$python_path \"$solink_wrapper\" --readelf=\"$readelf\" --nm=\"$nm\" $strip_switch --sofile=\"$unstripped_sofile\" --tocfile=\"$tocfile\" --output=\"$sofile\"$whitelist_flag -- $link_command"
+ command = "$python_path \"$solink_wrapper\" --readelf=\"$readelf\" --nm=\"$nm\" $strip_switch--sofile=\"$unstripped_sofile\" --tocfile=\"$tocfile\"$map_switch --output=\"$sofile\"$whitelist_flag -- $link_command"
rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive $solink_libs_section_prefix {{libs}} $solink_libs_section_postfix"
@@ -365,6 +377,9 @@
if (sofile != unstripped_sofile) {
outputs += [ unstripped_sofile ]
}
+ if (defined(map_file)) {
+ outputs += [ map_file ]
+ }
link_output = sofile
depend_output = tocfile
}
@@ -433,12 +448,25 @@
unstripped_outfile = "{{root_out_dir}}/exe.unstripped/$exename"
}
- command = "$ld {{ldflags}}${extra_ldflags} -o \"$unstripped_outfile\" -Wl,--start-group @\"$rspfile\" {{solibs}} -Wl,--end-group $libs_section_prefix {{libs}} $libs_section_postfix"
- if (defined(invoker.strip)) {
- link_wrapper =
- rebase_path("//build/toolchain/gcc_link_wrapper.py", root_build_dir)
- command = "$python_path \"$link_wrapper\" --strip=\"${invoker.strip}\" --unstripped-file=\"$unstripped_outfile\" --output=\"$outfile\" -- $command"
+ # Generate a map file to be used for binary size analysis.
+ # Map file adds ~10% to the link time on a z620.
+ # With target_os="android", libchrome.so.map.gz is ~20MB.
+ map_switch = ""
+ if (enable_linker_map && is_official_build) {
+ map_file = "$unstripped_outfile.map.gz"
+ map_switch = " --map-file \"$map_file\""
}
+
+ link_command = "$ld {{ldflags}}${extra_ldflags} -o \"$unstripped_outfile\" -Wl,--start-group @\"$rspfile\" {{solibs}} -Wl,--end-group $libs_section_prefix {{libs}} $libs_section_postfix"
+
+ strip_switch = ""
+ if (defined(invoker.strip)) {
+ strip_switch = " --strip=\"${invoker.strip}\" --unstripped-file=\"$unstripped_outfile\""
+ }
+
+ link_wrapper =
+ rebase_path("//build/toolchain/gcc_link_wrapper.py", root_build_dir)
+ command = "$python_path \"$link_wrapper\" --output=\"$outfile\"$strip_switch$map_switch -- $link_command"
description = "LINK $outfile"
rspfile_content = "{{inputs}}"
outputs = [
@@ -450,6 +478,9 @@
if (defined(invoker.link_outputs)) {
outputs += invoker.link_outputs
}
+ if (defined(map_file)) {
+ outputs += [ map_file ]
+ }
}
# These two are really entirely generic, but have to be repeated in
@@ -511,7 +542,11 @@
ar = "${toolprefix}ar"
nm = "${toolprefix}nm"
- forward_variables_from(invoker, [ "strip" ])
+ forward_variables_from(invoker,
+ [
+ "enable_linker_map",
+ "strip",
+ ])
toolchain_args = {
if (defined(invoker.toolchain_args)) {
diff --git a/linux/BUILD.gn b/linux/BUILD.gn
index 86cd7da..3be5c36 100644
--- a/linux/BUILD.gn
+++ b/linux/BUILD.gn
@@ -58,6 +58,9 @@
}
clang_toolchain("clang_x86") {
+ # Output linker map files for binary size analysis.
+ enable_linker_map = true
+
toolchain_args = {
current_cpu = "x86"
current_os = "linux"
@@ -89,6 +92,9 @@
ar = "ar"
ld = cxx
+ # Output linker map files for binary size analysis.
+ enable_linker_map = true
+
toolchain_args = {
current_cpu = "x86"
current_os = "linux"
@@ -97,6 +103,9 @@
}
clang_toolchain("clang_x64") {
+ # Output linker map files for binary size analysis.
+ enable_linker_map = true
+
toolchain_args = {
current_cpu = "x64"
current_os = "linux"
@@ -128,6 +137,9 @@
ar = "ar"
ld = cxx
+ # Output linker map files for binary size analysis.
+ enable_linker_map = true
+
toolchain_args = {
current_cpu = "x64"
current_os = "linux"
diff --git a/wrapper_utils.py b/wrapper_utils.py
index 467d85d..f76192e 100644
--- a/wrapper_utils.py
+++ b/wrapper_utils.py
@@ -4,16 +4,31 @@
"""Helper functions for gcc_toolchain.gni wrappers."""
+import gzip
import os
import re
import subprocess
import shlex
+import shutil
import sys
+import threading
_BAT_PREFIX = 'cmd /c call '
_WHITELIST_RE = re.compile('whitelisted_resource_(?P<resource_id>[0-9]+)')
+def _GzipThenDelete(src_path, dest_path):
+ # Results for Android map file with GCC on a z620:
+ # Uncompressed: 207MB
+ # gzip -9: 16.4MB, takes 8.7 seconds.
+ # gzip -1: 21.8MB, takes 2.0 seconds.
+ # Piping directly from the linker via -print-map (or via -Map with a fifo)
+ # adds a whopping 30-45 seconds!
+ with open(src_path, 'rb') as f_in, gzip.GzipFile(dest_path, 'wb', 1) as f_out:
+ shutil.copyfileobj(f_in, f_out)
+ os.unlink(src_path)
+
+
def CommandToRun(command):
"""Generates commands compatible with Windows.
@@ -36,6 +51,37 @@
return command
+def RunLinkWithOptionalMapFile(command, env=None, map_file=None):
+ """Runs the given command, adding in -Wl,-Map when |map_file| is given.
+
+ Also takes care of gzipping when |map_file| ends with .gz.
+
+ Args:
+ command: List of arguments comprising the command.
+ env: Environment variables.
+ map_file: Path to output map_file.
+
+ Returns:
+ The exit code of running |command|.
+ """
+ tmp_map_path = None
+ if map_file and map_file.endswith('.gz'):
+ tmp_map_path = map_file + '.tmp'
+ command.append('-Wl,-Map,' + tmp_map_path)
+ elif map_file:
+ command.append('-Wl,-Map,' + map_file)
+
+ result = subprocess.call(command, env=env)
+
+ if tmp_map_path and result == 0:
+ threading.Thread(
+ target=lambda: _GzipThenDelete(tmp_map_path, map_file)).start()
+ elif tmp_map_path and os.path.exists(tmp_map_path):
+ os.unlink(tmp_map_path)
+
+ return result
+
+
def ResolveRspLinks(inputs):
"""Return a list of files contained in a response file.