mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 10:40:47 +03:00
Merge branch 'fix/harden_empty_toolchain_output_v5.5' into 'release/v5.5'
fix: harden build against empty toolchain output (v5.5) See merge request espressif/esp-idf!51645
This commit is contained in:
@@ -2,11 +2,7 @@
|
||||
# Warn if the toolchain version doesn't match
|
||||
#
|
||||
if(NOT (${target} STREQUAL "linux" OR CMAKE_C_COMPILER_ID MATCHES "Clang"))
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -dumpmachine
|
||||
OUTPUT_VARIABLE toolchain_name
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET)
|
||||
__compiler_query(toolchain_name ${CMAKE_C_COMPILER} -dumpmachine)
|
||||
check_expected_tool_version(${toolchain_name} ${CMAKE_C_COMPILER})
|
||||
endif()
|
||||
|
||||
|
||||
@@ -2,20 +2,13 @@
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
|
||||
# without '--target' option 'clang -dumpmachine' prints default target arch and it might be not Xtensa
|
||||
# so use `-print-targets` option
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -print-targets
|
||||
OUTPUT_VARIABLE dump_machine
|
||||
)
|
||||
__compiler_query(dump_machine ${CMAKE_C_COMPILER} -print-targets)
|
||||
else()
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -dumpmachine
|
||||
OUTPUT_VARIABLE dump_machine
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
__compiler_query(dump_machine ${CMAKE_C_COMPILER} -dumpmachine)
|
||||
endif()
|
||||
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"))
|
||||
message(FATAL_ERROR "Internal error, toolchain has not been set correctly by project "
|
||||
"(or an invalid CMakeCache.txt file has been generated somehow)")
|
||||
endif()
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import argparse
|
||||
import subprocess
|
||||
from sys import exit
|
||||
import sys
|
||||
|
||||
try:
|
||||
from typing import List
|
||||
from typing import List # noqa: F401
|
||||
except ImportError:
|
||||
# Only used for type annotations
|
||||
pass
|
||||
@@ -29,7 +29,7 @@ def types_valid_ignored_rules(file_name): # type: (str) -> bool
|
||||
"""
|
||||
Run Mypy check with rules for ignore list on the given file, return TRUE if Mypy check passes
|
||||
"""
|
||||
mypy_exit_code = subprocess.call('mypy {} --python-version 3.8 --allow-untyped-defs'.format(file_name), shell=True)
|
||||
mypy_exit_code = subprocess.call('mypy {} --python-version 3.9 --allow-untyped-defs'.format(file_name), shell=True)
|
||||
return not bool(mypy_exit_code)
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ def main(): # type: () -> None
|
||||
print('mypy check failed for:')
|
||||
for file_name in type_issues:
|
||||
print('\t', file_name)
|
||||
exit(1)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -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 the build system utilities, so that
|
||||
# __compiler_query is available to the esp_common and xtensa
|
||||
# project_include.cmake files that call it.
|
||||
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()
|
||||
@@ -393,3 +393,9 @@ function(remove_duplicated_flags FLAGS UNIQFLAGS)
|
||||
# Return that string to the caller
|
||||
set(${UNIQFLAGS} "${FLAGS_LIST}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
|
||||
# __compiler_query is defined in a standalone module, included here so it is
|
||||
# available to the esp_common and xtensa project_include.cmake files that call
|
||||
# it.
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/compiler_query.cmake)
|
||||
|
||||
+19
-1
@@ -1004,7 +1004,25 @@ class IDFTool(object):
|
||||
f'non-zero exit code ({e.returncode}) with message: {e.stderr.decode("utf-8", errors="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:
|
||||
"""
|
||||
|
||||
+88
-57
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
import argparse
|
||||
@@ -23,7 +23,7 @@ from pyparsing import ParseFatalException
|
||||
|
||||
|
||||
def _update_environment(args):
|
||||
env = [(name, value) for (name,value) in (e.split('=',1) for e in args.env)]
|
||||
env = [(name, value) for (name, value) in (e.split('=', 1) for e in args.env)]
|
||||
for name, value in env:
|
||||
value = ' '.join(value.split())
|
||||
os.environ[name] = value
|
||||
@@ -33,72 +33,90 @@ def _update_environment(args):
|
||||
os.environ.update(env)
|
||||
|
||||
|
||||
def main():
|
||||
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 main():
|
||||
argparser = argparse.ArgumentParser(description='ESP-IDF linker script generator')
|
||||
|
||||
argparser.add_argument(
|
||||
'--input', '-i',
|
||||
help='Linker template file',
|
||||
type=argparse.FileType('r'))
|
||||
argparser.add_argument('--input', '-i', help='Linker template file', type=argparse.FileType('r'))
|
||||
|
||||
fragments_group = argparser.add_mutually_exclusive_group()
|
||||
|
||||
fragments_group.add_argument(
|
||||
'--fragments', '-f',
|
||||
type=argparse.FileType('r'),
|
||||
help='Input fragment files',
|
||||
nargs='+'
|
||||
'--fragments', '-f', type=argparse.FileType('r'), help='Input fragment files', nargs='+'
|
||||
)
|
||||
|
||||
fragments_group.add_argument(
|
||||
'--fragments-list',
|
||||
help='Input fragment files as a semicolon-separated list',
|
||||
type=str
|
||||
'--fragments-list', help='Input fragment files as a semicolon-separated list', type=str
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
'--libraries-file',
|
||||
'--libraries-file', type=argparse.FileType('r'), help='File that contains the list of libraries in the build'
|
||||
)
|
||||
|
||||
argparser.add_argument('--output', '-o', help='Output linker script', type=str)
|
||||
|
||||
argparser.add_argument('--config', '-c', help='Project configuration')
|
||||
|
||||
argparser.add_argument('--kconfig', '-k', help='IDF Kconfig file')
|
||||
|
||||
argparser.add_argument(
|
||||
'--check-mapping', help='Perform a check if a mapping (archive, obj, symbol) exists', action='store_true'
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
'--check-mapping-exceptions', help='Mappings exempted from check', type=argparse.FileType('r')
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
'--env',
|
||||
'-e',
|
||||
action='append',
|
||||
default=[],
|
||||
help='Environment to set when evaluating the config file',
|
||||
metavar='NAME=VAL',
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
'--env-file',
|
||||
type=argparse.FileType('r'),
|
||||
help='File that contains the list of libraries in the build')
|
||||
|
||||
argparser.add_argument(
|
||||
'--output', '-o',
|
||||
help='Output linker script',
|
||||
type=str)
|
||||
|
||||
argparser.add_argument(
|
||||
'--config', '-c',
|
||||
help='Project configuration')
|
||||
|
||||
argparser.add_argument(
|
||||
'--kconfig', '-k',
|
||||
help='IDF Kconfig file')
|
||||
|
||||
argparser.add_argument(
|
||||
'--check-mapping',
|
||||
help='Perform a check if a mapping (archive, obj, symbol) exists',
|
||||
action='store_true'
|
||||
help='Optional file to load environment variables from. Contents '
|
||||
'should be a JSON object where each key/value pair is a variable.',
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
'--check-mapping-exceptions',
|
||||
help='Mappings exempted from check',
|
||||
type=argparse.FileType('r')
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
'--env', '-e',
|
||||
action='append', default=[],
|
||||
help='Environment to set when evaluating the config file', metavar='NAME=VAL')
|
||||
|
||||
argparser.add_argument('--env-file', type=argparse.FileType('r'),
|
||||
help='Optional file to load environment variables from. Contents '
|
||||
'should be a JSON object where each key/value pair is a variable.')
|
||||
|
||||
argparser.add_argument(
|
||||
'--objdump',
|
||||
help='Path to toolchain objdump')
|
||||
argparser.add_argument('--objdump', help='Path to toolchain objdump')
|
||||
|
||||
args = argparser.parse_args()
|
||||
|
||||
@@ -126,11 +144,22 @@ def main():
|
||||
for library in libraries_file:
|
||||
library = library.strip()
|
||||
if library:
|
||||
new_env = os.environ.copy()
|
||||
new_env['LC_ALL'] = 'C'
|
||||
dump = StringIO(subprocess.check_output([objdump, '-h', library], env=new_env).decode())
|
||||
dump = StringIO(_run_objdump(objdump, 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}'
|
||||
)
|
||||
|
||||
generation_model = Generation(check_mapping, check_mapping_exceptions)
|
||||
|
||||
@@ -165,7 +194,9 @@ def main():
|
||||
if exc.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
with open(output_path, 'w', encoding='utf-8') as f: # only create output file after generation has succeeded
|
||||
with open(
|
||||
output_path, 'w', encoding='utf-8'
|
||||
) as f: # only create output file after generation has succeeded
|
||||
f.write(output.read())
|
||||
except LdGenFailure as e:
|
||||
print('linker script generation failed for %s\nERROR: %s' % (input_file.name, e))
|
||||
|
||||
Reference in New Issue
Block a user