mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'fix/harden_empty_toolchain_output' into 'master'
fix: harden build against empty toolchain output Closes IDFGH-17820 See merge request espressif/esp-idf!50136
This commit is contained in:
@@ -4,11 +4,7 @@
|
|||||||
idf_build_get_property(target IDF_TARGET)
|
idf_build_get_property(target IDF_TARGET)
|
||||||
|
|
||||||
if(NOT (${target} STREQUAL "linux" OR CMAKE_C_COMPILER_ID MATCHES "Clang"))
|
if(NOT (${target} STREQUAL "linux" OR CMAKE_C_COMPILER_ID MATCHES "Clang"))
|
||||||
execute_process(
|
__compiler_query(toolchain_name ${CMAKE_C_COMPILER} -dumpmachine)
|
||||||
COMMAND ${CMAKE_C_COMPILER} -dumpmachine
|
|
||||||
OUTPUT_VARIABLE toolchain_name
|
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
||||||
ERROR_QUIET)
|
|
||||||
check_expected_tool_version(${toolchain_name} ${CMAKE_C_COMPILER})
|
check_expected_tool_version(${toolchain_name} ${CMAKE_C_COMPILER})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -7,20 +7,13 @@ endif()
|
|||||||
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
|
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
|
||||||
# without '--target' option 'clang -dumpmachine' prints default target arch and it might be not Xtensa
|
# without '--target' option 'clang -dumpmachine' prints default target arch and it might be not Xtensa
|
||||||
# so use `-print-targets` option
|
# so use `-print-targets` option
|
||||||
execute_process(
|
__compiler_query(dump_machine ${CMAKE_C_COMPILER} -print-targets)
|
||||||
COMMAND ${CMAKE_C_COMPILER} -print-targets
|
|
||||||
OUTPUT_VARIABLE dump_machine
|
|
||||||
)
|
|
||||||
else()
|
else()
|
||||||
execute_process(
|
__compiler_query(dump_machine ${CMAKE_C_COMPILER} -dumpmachine)
|
||||||
COMMAND ${CMAKE_C_COMPILER} -dumpmachine
|
|
||||||
OUTPUT_VARIABLE dump_machine
|
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
message(STATUS "Compiler supported targets: ${dump_machine}")
|
message(STATUS "Compiler supported targets: ${dump_machine}")
|
||||||
|
|
||||||
if(NOT (${CMAKE_SYSTEM_NAME} STREQUAL "Generic" AND ${dump_machine} MATCHES xtensa))
|
if(NOT ("${CMAKE_SYSTEM_NAME}" STREQUAL "Generic" AND "${dump_machine}" MATCHES "xtensa"))
|
||||||
# Sub-projects (e.g. ULP RISC-V on an xtensa target) may use a
|
# Sub-projects (e.g. ULP RISC-V on an xtensa target) may use a
|
||||||
# non-IDF toolchain provided by the parent build. Skip validation
|
# non-IDF toolchain provided by the parent build. Skip validation
|
||||||
# when a custom toolchain is explicitly declared.
|
# when a custom toolchain is explicitly declared.
|
||||||
|
|||||||
40
tools/cmake/compiler_query.cmake
Normal file
40
tools/cmake/compiler_query.cmake
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
# __compiler_query
|
||||||
|
#
|
||||||
|
# Run a compiler query command (e.g. "<compiler> -dumpmachine") given in ARGN
|
||||||
|
# and store its trimmed stdout in the variable named by "output_var".
|
||||||
|
#
|
||||||
|
# Fails with an actionable error if the command fails or returns no output. On
|
||||||
|
# some Windows systems, antivirus, endpoint-security or DLP/encryption software
|
||||||
|
# intercepts short-lived toolchain processes and strips their stdout when it is
|
||||||
|
# captured by the build system, while the same command works when run directly
|
||||||
|
# in a terminal. Without this guard the empty result collapses the callers'
|
||||||
|
# parsing into a cryptic CMake error (see
|
||||||
|
# https://github.com/espressif/esp-idf/issues/18727).
|
||||||
|
#
|
||||||
|
# This module is included by both the cmakev1 and cmakev2 utilities, because the
|
||||||
|
# esp_common and xtensa project_include.cmake files that call __compiler_query
|
||||||
|
# are shared by both build systems.
|
||||||
|
function(__compiler_query output_var)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${ARGN}
|
||||||
|
OUTPUT_VARIABLE query_output
|
||||||
|
RESULT_VARIABLE query_result
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
|
||||||
|
if(NOT query_result EQUAL 0 OR query_output STREQUAL "")
|
||||||
|
string(REPLACE ";" " " query_command "${ARGN}")
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"Failed to query the compiler: '${query_command}' (result: ${query_result}).\n"
|
||||||
|
"The command produced no output when run by the build system. This is usually caused "
|
||||||
|
"by antivirus, endpoint-security or DLP/encryption software intercepting the compiler "
|
||||||
|
"process and discarding its output; the same command often works when run directly in "
|
||||||
|
"a terminal.\n"
|
||||||
|
"Add an exclusion for the ESP-IDF tools directory in that software (on a managed "
|
||||||
|
"machine you may need your IT department), then run 'idf.py fullclean' and build again.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(${output_var} "${query_output}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
@@ -509,3 +509,9 @@ function(add_deprecated_target_alias old_target new_target)
|
|||||||
)
|
)
|
||||||
add_dependencies(${old_target} ${new_target})
|
add_dependencies(${old_target} ${new_target})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
|
||||||
|
# __compiler_query is defined in a standalone module so it can also be included
|
||||||
|
# by the cmakev2 utilities; the esp_common and xtensa project_include.cmake
|
||||||
|
# files that call it are shared by both build systems.
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/compiler_query.cmake)
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
# used by toolchain CMake files.
|
# used by toolchain CMake files.
|
||||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/deduplicate_flags.cmake)
|
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/deduplicate_flags.cmake)
|
||||||
|
|
||||||
|
# __compiler_query is shared with cmakev1. The esp_common and xtensa
|
||||||
|
# project_include.cmake files that call it are used by both build systems.
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/compiler_query.cmake)
|
||||||
|
|
||||||
# Note: CMake does not support nested lists. The functions idf_die, idf_warn,
|
# Note: CMake does not support nested lists. The functions idf_die, idf_warn,
|
||||||
# idf_msg, and idf_dbg use ARGV# values because this is the only way to prevent
|
# idf_msg, and idf_dbg use ARGV# values because this is the only way to prevent
|
||||||
# arguments from being altered by CMake. ARGV and ARGN contain a flattened list
|
# arguments from being altered by CMake. ARGV and ARGN contain a flattened list
|
||||||
|
|||||||
@@ -998,7 +998,25 @@ class IDFTool:
|
|||||||
f'non-zero exit code ({e.returncode}) with message: {e.stderr.decode("utf-8", errors="ignore")}'
|
f'non-zero exit code ({e.returncode}) with message: {e.stderr.decode("utf-8", errors="ignore")}'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
return self.parse_tool_version(version_cmd_result.decode('utf-8'))
|
version_str = version_cmd_result.decode('utf-8')
|
||||||
|
if not version_str.strip():
|
||||||
|
# The tool ran and exited successfully, but produced no output when its
|
||||||
|
# output was captured. On some Windows systems, antivirus, endpoint-security
|
||||||
|
# or DLP/encryption software intercepts short-lived toolchain processes and
|
||||||
|
# strips their stdout when it is captured through a pipe, while the same
|
||||||
|
# command works when run directly in a terminal. Surface an actionable hint
|
||||||
|
# instead of silently reporting the version as 'unknown', which otherwise
|
||||||
|
# sends users into a fruitless reinstall loop.
|
||||||
|
# See https://github.com/espressif/esp-idf/issues/18727
|
||||||
|
warn(
|
||||||
|
f'tool {self.name} ran but returned no version output. This is usually caused by '
|
||||||
|
'antivirus, endpoint-security or DLP/encryption software stripping the output of '
|
||||||
|
'toolchain processes; the same command often works when run directly in a terminal. '
|
||||||
|
'Add an exclusion for the ESP-IDF tools directory in that software. If the problem '
|
||||||
|
'persists, run the tool manually to check for a missing DLL.'
|
||||||
|
)
|
||||||
|
return UNKNOWN_VERSION
|
||||||
|
return self.parse_tool_version(version_str)
|
||||||
|
|
||||||
def get_version_from_file(self, version: str) -> str:
|
def get_version_from_file(self, version: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -145,6 +145,40 @@ def _update_environment(env, env_file):
|
|||||||
os.environ.update(env_vars)
|
os.environ.update(env_vars)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_objdump(objdump, library):
|
||||||
|
"""Run ``objdump -h`` on a library and return its output.
|
||||||
|
|
||||||
|
On some Windows systems, antivirus, endpoint-security or DLP/encryption
|
||||||
|
software intercepts short-lived toolchain processes and strips their output
|
||||||
|
when the build captures it, so objdump exits successfully but returns empty
|
||||||
|
output, while the same command works when run by hand. The output is read
|
||||||
|
through a pipe so that this empty result is reliably detectable: it is
|
||||||
|
rejected here with an actionable error instead of being fed to the parser
|
||||||
|
(which would otherwise report it as a confusing pyparsing error). Capturing
|
||||||
|
to a file is deliberately avoided: it would not be guaranteed complete
|
||||||
|
either, and a truncated-but-non-empty result could be parsed into a wrong
|
||||||
|
linker script instead of failing. See
|
||||||
|
https://github.com/espressif/esp-idf/issues/18665 and
|
||||||
|
https://github.com/espressif/esp-idf/issues/18727.
|
||||||
|
"""
|
||||||
|
new_env = os.environ.copy()
|
||||||
|
# Force the C locale so objdump emits the English 'In archive' header that
|
||||||
|
# the section parser expects, regardless of the host locale (see
|
||||||
|
# https://github.com/espressif/esp-idf/issues/7903).
|
||||||
|
new_env['LC_ALL'] = 'C'
|
||||||
|
|
||||||
|
output = subprocess.check_output([objdump, '-h', library], env=new_env).decode()
|
||||||
|
if not output.strip():
|
||||||
|
raise LdGenFailure(
|
||||||
|
f"'{objdump} -h {library}' ran successfully but returned no output. The toolchain ran "
|
||||||
|
'but its output was empty when captured by the build system. This is usually caused by '
|
||||||
|
'antivirus, endpoint-security or DLP/encryption software stripping the output of '
|
||||||
|
'toolchain processes; the same command often works when run directly in a terminal. '
|
||||||
|
'Add an exclusion for the ESP-IDF tools directory in that software, then build again.'
|
||||||
|
)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
def _run(
|
def _run(
|
||||||
input_file,
|
input_file,
|
||||||
fragments,
|
fragments,
|
||||||
@@ -184,11 +218,22 @@ def _run(
|
|||||||
for library in libraries_file:
|
for library in libraries_file:
|
||||||
library = library.strip()
|
library = library.strip()
|
||||||
if library:
|
if library:
|
||||||
new_env = os.environ.copy()
|
dump = StringIO(_run_objdump(objdump, library))
|
||||||
new_env['LC_ALL'] = 'C'
|
|
||||||
dump = StringIO(subprocess.check_output([objdump, '-h', library], env=new_env).decode())
|
|
||||||
dump.name = library
|
dump.name = library
|
||||||
sections_infos.add_sections_info(dump)
|
try:
|
||||||
|
sections_infos.add_sections_info(dump)
|
||||||
|
except ParseException as e:
|
||||||
|
# Non-empty but unparsable section info (for example truncated or
|
||||||
|
# corrupted toolchain output) is reported here rather than allowed to
|
||||||
|
# propagate as a raw pyparsing traceback. The same root cause as the
|
||||||
|
# empty case in _run_objdump applies.
|
||||||
|
raise LdGenFailure(
|
||||||
|
f'failed to parse section information from {library}. The toolchain output '
|
||||||
|
'is incomplete or corrupted. This can be caused by antivirus, '
|
||||||
|
'endpoint-security or DLP/encryption software tampering with the output of '
|
||||||
|
'toolchain processes; the same command often works when run directly in a '
|
||||||
|
f'terminal. Add an exclusion for the ESP-IDF tools directory, then build again.\n{e}'
|
||||||
|
)
|
||||||
|
|
||||||
# Check if we can skip generation entirely — section names and other
|
# Check if we can skip generation entirely — section names and other
|
||||||
# inputs unchanged since last run.
|
# inputs unchanged since last run.
|
||||||
|
|||||||
Reference in New Issue
Block a user