From a154421f99f8c5c76949474f7b83dcfb94a2511d Mon Sep 17 00:00:00 2001
From: Mike Hommey <mh+mozilla@glandium.org>
Date: Tue, 23 Nov 2021 02:28:18 +0000
Subject: [PATCH] Bug 1740042 - Use llvm-readelf instead of readelf when
 available. r=firefox-build-system-reviewers,mhentges

Differential Revision: https://phabricator.services.mozilla.com/D130949
---
 build/moz.configure/checks.configure          |  8 ++
 build/unix/elfhack/Makefile.in                |  2 +-
 moz.configure                                 | 83 ++++++++++++++-----
 .../mozbuild/mozbuild/action/check_binary.py  |  2 +-
 toolkit/library/build/dependentlibs.py        |  2 +-
 5 files changed, 72 insertions(+), 25 deletions(-)

diff --git a/build/moz.configure/checks.configure b/build/moz.configure/checks.configure
index a8ac81676ae38..6634933fbb808 100644
--- a/build/moz.configure/checks.configure
+++ b/build/moz.configure/checks.configure
@@ -95,6 +95,10 @@ def checking(what, callback=None):
 #   passing $PATH (for example) as an element.
 # - `bootstrap` is a path relative to the bootstrap root path (e.g ~/.mozbuild)
 #   where to find the program if it's bootstrapped.
+# - `validate` is a callback function that takes a path and returns True if
+#   the program at that location is appropriate or not, or False if not.
+#   when the callback returns False, check_prog ignores the program and goes
+#   on to the next from the `progs` list.
 #
 # - `bootstrap_search_path` is not an argument that users of the template are
 #   supposed to pass. See the override of check_prog in top-level moz.configure.
@@ -115,6 +119,7 @@ def check_prog(
     paths=None,
     bootstrap=None,
     when=None,
+    validate=None,
     bootstrap_search_path=None,
 ):
     if input is not None:
@@ -186,6 +191,9 @@ def check_prog(
         for prog in value or progs:
             log.debug("%s: Looking for %s", var.lower(), quote(prog))
             result = find_program(prog, paths)
+            if validate and result and not validate(result):
+                log.debug("%s: %s found but didn't work", var.lower(), quote(result))
+                continue
             if result:
                 return result
 
diff --git a/build/unix/elfhack/Makefile.in b/build/unix/elfhack/Makefile.in
index 08cc3d6852956..57ad54f1134e0 100644
--- a/build/unix/elfhack/Makefile.in
+++ b/build/unix/elfhack/Makefile.in
@@ -14,7 +14,7 @@ test-array$(DLL_SUFFIX) test-ctors$(DLL_SUFFIX): %$(DLL_SUFFIX): %.$(OBJ_SUFFIX)
 	@echo === Use --disable-elf-hack until this is fixed.
 	@echo ===
 	# Fail if the library doesn't have $(DT_TYPE) .dynamic info
-	$(TOOLCHAIN_PREFIX)readelf -d $@ | grep '($(DT_TYPE))'
+	$(READELF) -d $@ | grep '($(DT_TYPE))'
 	@rm -f $@.bak
 	$(CURDIR)/elfhack -b -f $@
 	# Fail if the backup file doesn't exist
diff --git a/moz.configure b/moz.configure
index 5a103d844cc15..46795bd4fe4d5 100755
--- a/moz.configure
+++ b/moz.configure
@@ -946,32 +946,36 @@ check_prog("7Z", ("7z", "7za"), allow_missing=True, when=target_is_windows)
 check_prog("UPX", ("upx",), allow_missing=True, when=target_is_windows)
 
 
-@depends(host_c_compiler, c_compiler, bindgen_config_paths)
-def llvm_objdump(host_c_compiler, c_compiler, bindgen_config_paths):
-    clang = None
-    for compiler in (host_c_compiler, c_compiler):
-        if compiler and compiler.type == "clang":
-            clang = compiler.compiler
-            break
-        elif compiler and compiler.type == "clang-cl":
-            clang = os.path.join(os.path.dirname(compiler.compiler), "clang")
-            break
-
-    if not clang and bindgen_config_paths:
-        clang = bindgen_config_paths.clang_path
-    llvm_objdump = "llvm-objdump"
-    if clang:
-        out = check_cmd_output(
-            clang, "--print-prog-name=llvm-objdump", onerror=lambda: None
-        )
-        if out:
-            llvm_objdump = out.rstrip()
-    return (llvm_objdump,)
+@template
+def llvm_tool(name):
+    @depends(host_c_compiler, c_compiler, bindgen_config_paths)
+    def llvm_tool(host_c_compiler, c_compiler, bindgen_config_paths):
+        clang = None
+        for compiler in (host_c_compiler, c_compiler):
+            if compiler and compiler.type == "clang":
+                clang = compiler.compiler
+                break
+            elif compiler and compiler.type == "clang-cl":
+                clang = os.path.join(os.path.dirname(compiler.compiler), "clang")
+                break
+
+        if not clang and bindgen_config_paths:
+            clang = bindgen_config_paths.clang_path
+        tool = name
+        if clang:
+            out = check_cmd_output(
+                clang, "--print-prog-name=%s" % tool, onerror=lambda: None
+            )
+            if out:
+                tool = out.rstrip()
+        return (tool,)
+
+    return llvm_tool
 
 
 llvm_objdump = check_prog(
     "LLVM_OBJDUMP",
-    llvm_objdump,
+    llvm_tool("llvm-objdump"),
     what="llvm-objdump",
     when="--enable-compile-environment",
     paths=clang_search_path,
@@ -980,6 +984,41 @@ llvm_objdump = check_prog(
 add_old_configure_assignment("LLVM_OBJDUMP", llvm_objdump)
 
 
+@depends(llvm_tool("llvm-readelf"), toolchain_prefix)
+def readelf(llvm_readelf, toolchain_prefix):
+    commands = [llvm_readelf[0], "readelf"]
+    for prefix in toolchain_prefix or ():
+        commands.insert(1, "%sreadelf" % prefix)
+    return tuple(commands)
+
+
+def validate_readelf(path):
+    # llvm-readelf from llvm < 8 doesn't support the GNU binutils-compatible `-d`
+    # flag. We could try running `$path -d $some_binary` but we might be cross
+    # compiling and not have a binary at hand to run that against. `$path -d` alone
+    # would fail whether the flag is supported or not. So we resort to look for the
+    # option in the `--help` output, which fortunately, s compatible between
+    # llvm-readelf and readelf.
+    retcode, stdout, stderr = get_cmd_output(path, "--help")
+    return retcode == 0 and any(l.startswith("  -d ") for l in stdout.splitlines())
+
+
+@depends("--enable-compile-environment", target, host)
+def readelf_when(compile_env, target, host):
+    return compile_env and any(
+        x.kernel not in ("Darwin", "WINNT") for x in (target, host)
+    )
+
+
+check_prog(
+    "READELF",
+    readelf,
+    what="readelf",
+    when=readelf_when,
+    paths=clang_search_path,
+    validate=validate_readelf,
+)
+
 option("--enable-dtrace", help="Build with dtrace support")
 
 dtrace = check_header(
diff --git a/python/mozbuild/mozbuild/action/check_binary.py b/python/mozbuild/mozbuild/action/check_binary.py
index a2a566df6fe62..44362126210e5 100644
--- a/python/mozbuild/mozbuild/action/check_binary.py
+++ b/python/mozbuild/mozbuild/action/check_binary.py
@@ -27,7 +27,7 @@ HOST = {"platform": buildconfig.substs["HOST_OS_ARCH"], "readelf": "readelf"}
 
 TARGET = {
     "platform": buildconfig.substs["OS_TARGET"],
-    "readelf": "{}readelf".format(buildconfig.substs.get("TOOLCHAIN_PREFIX", "")),
+    "readelf": buildconfig.substs.get("READELF", "readelf"),
 }
 
 ADDR_RE = re.compile(r"[0-9a-f]{8,16}")
diff --git a/toolkit/library/build/dependentlibs.py b/toolkit/library/build/dependentlibs.py
index 974f5d7a70b9c..d7559844d40d2 100644
--- a/toolkit/library/build/dependentlibs.py
+++ b/toolkit/library/build/dependentlibs.py
@@ -44,7 +44,7 @@ def dependentlibs_win32_objdump(lib):
 def dependentlibs_readelf(lib):
     """Returns the list of dependencies declared in the given ELF .so"""
     proc = subprocess.Popen(
-        [substs.get("TOOLCHAIN_PREFIX", "") + "readelf", "-d", lib],
+        [substs.get("READELF", "readelf"), "-d", lib],
         stdout=subprocess.PIPE,
         universal_newlines=True,
     )
-- 
GitLab