From 442945072ce4e325a77c308a867191ee8a366275 Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Mon, 8 Jun 2026 16:58:11 +0530 Subject: [PATCH 1/3] examples/elf: extend nxpkg validation coverage Extend the ELF package fixtures with both success and failure repository data so nxpkg validation paths are exercised from the same example flow. Format the helper generator with black so it matches the current apps lint checks. Signed-off-by: aviralgarg05 --- examples/elf/main/Makefile | 18 +++- examples/elf/main/mk_pkg_fixture.py | 130 ++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 examples/elf/main/mk_pkg_fixture.py diff --git a/examples/elf/main/Makefile b/examples/elf/main/Makefile index 76093ea5f88..5ad3d9f623c 100644 --- a/examples/elf/main/Makefile +++ b/examples/elf/main/Makefile @@ -69,8 +69,24 @@ ifeq ($(CONFIG_EXAMPLES_ELF_ROMFS),y) ROMFSIMG = elf_romfs.img ROMFSSRC = elf_romfs.c ROMFSOBJ = $(ROMFSSRC:.c=$(OBJEXT)) +ifdef CONFIG_SYSTEM_NXPKG +PKGINDEX = $(BINDIR)/index.json +PKGBADINDEX = $(BINDIR)/bad-index.json +PKGTEST = $(BINDIR)/pkgtest.nsh +PKGFAIL = $(BINDIR)/pkgfail.nsh +PKG_FIXTURE_GEN = $(APPDIR)/examples/elf/main/mk_pkg_fixture.py +PKG_FIXTURE_OUTPUTS = $(PKGINDEX) $(PKGBADINDEX) $(PKGTEST) $(PKGFAIL) +PKG_FIXTURE_INPUTS = $(BINDIR)/hello +PKG_FIXTURE_ARGS = \ + $(BINDIR)/hello $(PKGINDEX) $(PKGBADINDEX) $(PKGTEST) $(PKGFAIL) \ + $(CONFIG_ARCH) $(CONFIG_ARCH_BOARD) + +$(PKG_FIXTURE_OUTPUTS) &: $(PKG_FIXTURE_INPUTS) $(PKG_FIXTURE_GEN) + $(Q) python3 $(PKG_FIXTURE_GEN) \ + $(PKG_FIXTURE_ARGS) +endif -$(ROMFSIMG): +$(ROMFSIMG): $(PKG_FIXTURE_OUTPUTS) $(Q) genromfs -d $(BINDIR) -f $@ $(ROMFSSRC): $(ROMFSIMG) diff --git a/examples/elf/main/mk_pkg_fixture.py b/examples/elf/main/mk_pkg_fixture.py new file mode 100644 index 00000000000..da8b395c077 --- /dev/null +++ b/examples/elf/main/mk_pkg_fixture.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: Apache-2.0 + +import hashlib +import json +import pathlib +import sys + + +def sha256_file(path: pathlib.Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as infile: + while True: + chunk = infile.read(4096) + if not chunk: + break + digest.update(chunk) + + return digest.hexdigest() + + +def write_index( + path: pathlib.Path, + arch: str, + compat: str, + artifact: str, + digest: str, +) -> None: + payload = { + "packages": [ + { + "name": "hello", + "version": "1.0.0", + "arch": arch, + "compat": compat, + "artifact": artifact, + "sha256": digest, + "type": "elf", + }, + { + "name": "hello", + "version": "9.9.9", + "arch": "arm", + "compat": "stm32f4discovery", + "artifact": artifact, + "sha256": digest, + "type": "elf", + }, + ] + } + + path.write_text(json.dumps(payload, separators=(",", ":")) + "\n", encoding="utf-8") + + +def write_bad_index(path: pathlib.Path, arch: str, compat: str) -> None: + payload = { + "packages": [ + { + "name": "hello-missing", + "version": "1.0.0", + "arch": arch, + "compat": compat, + "artifact": "/mnt/elf/romfs/hello-missing", + "sha256": "0" * 64, + "type": "elf", + } + ] + } + + path.write_text(json.dumps(payload, separators=(",", ":")) + "\n", encoding="utf-8") + + +def write_script(path: pathlib.Path) -> None: + script = "\n".join( + [ + "mount -t tmpfs /etc", + "mount -t tmpfs /var", + "mkdir /etc/nxpkg", + "cp /mnt/elf/romfs/index.json /etc/nxpkg/index.json", + "nxpkg install hello", + "nxpkg list", + "", + ] + ) + path.write_text(script, encoding="utf-8") + + +def write_fail_script(path: pathlib.Path) -> None: + script = "\n".join( + [ + "mount -t tmpfs /etc", + "mount -t tmpfs /var", + "mkdir /etc/nxpkg", + "cp /mnt/elf/romfs/bad-index.json /etc/nxpkg/index.json", + "nxpkg install hello-missing", + "", + ] + ) + path.write_text(script, encoding="utf-8") + + +def main() -> int: + if len(sys.argv) != 8: + print( + "usage: mk_pkg_fixture.py " + " ", + file=sys.stderr, + ) + return 1 + + hello = pathlib.Path(sys.argv[1]) + index = pathlib.Path(sys.argv[2]) + bad_index = pathlib.Path(sys.argv[3]) + script = pathlib.Path(sys.argv[4]) + fail_script = pathlib.Path(sys.argv[5]) + arch = sys.argv[6] + compat = sys.argv[7] + artifact = "/mnt/elf/romfs/hello" + + digest = sha256_file(hello) + write_index(index, arch, compat, artifact, digest) + write_bad_index(bad_index, arch, compat) + write_script(script) + write_fail_script(fail_script) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 7f20e7e3ac1f7a6f9c37d684080c1590be6f0643 Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Mon, 22 Jun 2026 19:14:27 +0530 Subject: [PATCH 2/3] examples/elf: Add CMake nxpkg fixture generation. Add the matching CMake fixture generation for the ELF nxpkg\nvalidation flow so the example is covered by both supported\nbuild systems.\n\nThis generates the symtab and ROMFS sources in the CMake path\nand keeps the fixture behavior aligned with the existing Makefile\nimplementation. Signed-off-by: aviralgarg05 --- examples/elf/main/CMakeLists.txt | 85 +++++++++++++++++++++++++- examples/elf/main/gen_romfs_source.py | 58 ++++++++++++++++++ examples/elf/main/gen_symtab.py | 87 +++++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 examples/elf/main/gen_romfs_source.py create mode 100644 examples/elf/main/gen_symtab.py diff --git a/examples/elf/main/CMakeLists.txt b/examples/elf/main/CMakeLists.txt index 3ae5a347e50..69b7f3a7e2f 100644 --- a/examples/elf/main/CMakeLists.txt +++ b/examples/elf/main/CMakeLists.txt @@ -19,7 +19,90 @@ # ############################################################################## if(CONFIG_EXAMPLES_ELF) - nuttx_add_application(NAME elf SRCS elf_main.c) + set(ELF_SYMTAB ${CMAKE_CURRENT_BINARY_DIR}/test_symtab.c) + set(ELF_ROMFS_IMG ${CMAKE_CURRENT_BINARY_DIR}/elf_romfs.img) + set(ELF_ROMFS_SRC ${CMAKE_CURRENT_BINARY_DIR}/elf_romfs.c) + set(ELF_GEN_SYMTAB ${CMAKE_CURRENT_SOURCE_DIR}/gen_symtab.py) + set(ELF_GEN_ROMFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gen_romfs_source.py) + + set(ELFNAME_BASE hello) + + set(ELF_BINARY_FILES) + set(ELF_BINARY_TARGETS) + foreach(ELFNAME IN LISTS ELFNAME_BASE) + list(APPEND ELF_BINARY_FILES ${CMAKE_BINARY_DIR}/bin/${ELFNAME}) + + if(CMAKE_C_ELF_COMPILER) + list(APPEND ELF_BINARY_TARGETS ${ELFNAME}) + else() + list(APPEND ELF_BINARY_TARGETS ELF_${ELFNAME}) + endif() + endforeach() + + add_custom_command( + OUTPUT ${ELF_SYMTAB} + COMMAND ${Python3_EXECUTABLE} ${ELF_GEN_SYMTAB} ${ELF_BINARY_FILES} g_elf + ${ELF_SYMTAB} + DEPENDS ${ELF_BINARY_TARGETS} ${ELF_GEN_SYMTAB} + VERBATIM) + + set_source_files_properties(${ELF_SYMTAB} PROPERTIES GENERATED TRUE) + + set(ELF_GENERATED_OUTPUTS ${ELF_SYMTAB}) + set(ELF_SRCS elf_main.c ${ELF_SYMTAB}) + + if(CONFIG_EXAMPLES_ELF_ROMFS) + set(ELF_ROMFS_DEPS ${ELF_BINARY_TARGETS}) + + if(CONFIG_SYSTEM_NXPKG) + set(ELF_INDEX_JSON ${CMAKE_BINARY_DIR}/bin/index.json) + set(ELF_BAD_INDEX_JSON ${CMAKE_BINARY_DIR}/bin/bad-index.json) + set(ELF_PKGTEST ${CMAKE_BINARY_DIR}/bin/pkgtest.nsh) + set(ELF_PKGFAIL ${CMAKE_BINARY_DIR}/bin/pkgfail.nsh) + set(ELF_HELLO_BIN ${CMAKE_BINARY_DIR}/bin/hello) + set(ELF_HELLO_TARGET hello) + + if(NOT CMAKE_C_ELF_COMPILER) + set(ELF_HELLO_TARGET ELF_hello) + endif() + + add_custom_command( + OUTPUT ${ELF_INDEX_JSON} ${ELF_BAD_INDEX_JSON} ${ELF_PKGTEST} + ${ELF_PKGFAIL} + COMMAND + ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/mk_pkg_fixture.py + ${ELF_HELLO_BIN} ${ELF_INDEX_JSON} ${ELF_BAD_INDEX_JSON} + ${ELF_PKGTEST} ${ELF_PKGFAIL} ${CONFIG_ARCH} ${CONFIG_ARCH_BOARD} + DEPENDS ${ELF_HELLO_TARGET} + ${CMAKE_CURRENT_SOURCE_DIR}/mk_pkg_fixture.py + VERBATIM) + + list(APPEND ELF_ROMFS_DEPS ${ELF_INDEX_JSON} ${ELF_BAD_INDEX_JSON} + ${ELF_PKGTEST} ${ELF_PKGFAIL}) + endif() + + add_custom_command( + OUTPUT ${ELF_ROMFS_IMG} + COMMAND genromfs -f ${ELF_ROMFS_IMG} -d ${CMAKE_BINARY_DIR}/bin + DEPENDS ${ELF_ROMFS_DEPS} + VERBATIM) + + add_custom_command( + OUTPUT ${ELF_ROMFS_SRC} + COMMAND ${Python3_EXECUTABLE} ${ELF_GEN_ROMFS_SRC} ${ELF_ROMFS_IMG} + ${ELF_ROMFS_SRC} + DEPENDS ${ELF_ROMFS_IMG} ${ELF_GEN_ROMFS_SRC} + VERBATIM) + + set_source_files_properties(${ELF_ROMFS_SRC} PROPERTIES GENERATED TRUE) + + list(APPEND ELF_GENERATED_OUTPUTS ${ELF_ROMFS_SRC}) + list(APPEND ELF_SRCS ${ELF_ROMFS_SRC}) + endif() + + add_custom_target(elf_artifacts DEPENDS ${ELF_GENERATED_OUTPUTS}) + + nuttx_add_application(NAME elf SRCS ${ELF_SRCS} DEPENDS elf_artifacts) # TODO: tests diff --git a/examples/elf/main/gen_romfs_source.py b/examples/elf/main/gen_romfs_source.py new file mode 100644 index 00000000000..b7965d0864a --- /dev/null +++ b/examples/elf/main/gen_romfs_source.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: Apache-2.0 + +import pathlib +import sys + + +def usage() -> int: + print("usage: gen_romfs_source.py ", file=sys.stderr) + return 1 + + +def symbol_from_path(path: pathlib.Path) -> str: + return path.name.replace(".", "_") + + +def format_bytes(data: bytes) -> str: + rows = [] + for index in range(0, len(data), 12): + chunk = data[index : index + 12] + rows.append(" " + ", ".join(f"0x{byte:02x}" for byte in chunk)) + return ",\n".join(rows) + + +def write_output(input_path: pathlib.Path, output_path: pathlib.Path) -> None: + symbol = symbol_from_path(input_path) + data = input_path.read_bytes() + text = "\n".join( + [ + "#include ", + "", + f"const unsigned char aligned_data(4) {symbol}[] = {{", + format_bytes(data), + "};", + f"const unsigned int {symbol}_len = {len(data)};", + "", + ] + ) + + if output_path.exists() and output_path.read_text(encoding="utf-8") == text: + return + + output_path.write_text(text, encoding="utf-8") + + +def main() -> int: + if len(sys.argv) != 3: + return usage() + + input_path = pathlib.Path(sys.argv[1]) + output_path = pathlib.Path(sys.argv[2]) + write_output(input_path, output_path) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/examples/elf/main/gen_symtab.py b/examples/elf/main/gen_symtab.py new file mode 100644 index 00000000000..cc7c6f0da88 --- /dev/null +++ b/examples/elf/main/gen_symtab.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: Apache-2.0 + +import pathlib +import subprocess +import sys + + +def usage() -> int: + print( + "usage: gen_symtab.py [ ...] ", + file=sys.stderr, + ) + return 1 + + +def symbol_name(line: str) -> str: + return line.split()[-1].replace('"', "") + + +def collect_symbols(paths): + undefined: set[str] = set() + defined: set[str] = set() + + for path in paths: + result = subprocess.run( + ["nm", str(path)], + check=False, + text=True, + capture_output=True, + ) + if result.returncode not in (0, 1): + raise RuntimeError(result.stderr.strip() or f"nm failed for {path}") + + for line in result.stdout.splitlines(): + if " U " in line: + undefined.add(symbol_name(line)) + elif line and not line.endswith(":"): + defined.add(symbol_name(line)) + + return sorted(undefined - defined) + + +def write_output(path: pathlib.Path, prefix: str, symbols) -> None: + lines = [ + "#include ", + "#include ", + "", + ] + + for symbol in symbols: + lines.append(f"extern void *{symbol};") + + lines.append("") + lines.append(f"const struct symtab_s {prefix}_exports[] = ") + lines.append("{") + + for symbol in symbols: + lines.append(f' {{"{symbol}", &{symbol}}},') + + lines.append("};") + lines.append("") + lines.append( + f"const int {prefix}_nexports = sizeof({prefix}_exports) / sizeof(struct symtab_s);" + ) + lines.append("") + + text = "\n".join(lines) + if path.exists() and path.read_text(encoding="utf-8") == text: + return + + path.write_text(text, encoding="utf-8") + + +def main() -> int: + if len(sys.argv) < 4: + return usage() + + *input_paths, prefix, output = sys.argv[1:] + symbols = collect_symbols([pathlib.Path(item) for item in input_paths]) + write_output(pathlib.Path(output), prefix, symbols) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 781be36cafff4d0289e648677cb79ed20f87b2a8 Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Wed, 8 Jul 2026 00:19:57 +0530 Subject: [PATCH 3/3] examples/elf: replace Python fixture generators Replace the Python-based ELF fixture helper with a small host-side C tool so the generated package fixtures do not depend on Python at build time.\n\nKeep the generated helper and CMake rules formatted to match the current apps lint checks. Signed-off-by: aviralgarg05 --- examples/elf/main/CMakeLists.txt | 36 +- examples/elf/main/Makefile | 16 +- examples/elf/main/gen_romfs_source.py | 58 ---- examples/elf/main/gen_symtab.py | 87 ----- examples/elf/main/host/CMakeLists.txt | 26 ++ examples/elf/main/mk_pkg_fixture.c | 454 ++++++++++++++++++++++++++ examples/elf/main/mk_pkg_fixture.py | 130 -------- 7 files changed, 515 insertions(+), 292 deletions(-) delete mode 100644 examples/elf/main/gen_romfs_source.py delete mode 100644 examples/elf/main/gen_symtab.py create mode 100644 examples/elf/main/host/CMakeLists.txt create mode 100644 examples/elf/main/mk_pkg_fixture.c delete mode 100644 examples/elf/main/mk_pkg_fixture.py diff --git a/examples/elf/main/CMakeLists.txt b/examples/elf/main/CMakeLists.txt index 69b7f3a7e2f..49b86cdf748 100644 --- a/examples/elf/main/CMakeLists.txt +++ b/examples/elf/main/CMakeLists.txt @@ -22,8 +22,6 @@ if(CONFIG_EXAMPLES_ELF) set(ELF_SYMTAB ${CMAKE_CURRENT_BINARY_DIR}/test_symtab.c) set(ELF_ROMFS_IMG ${CMAKE_CURRENT_BINARY_DIR}/elf_romfs.img) set(ELF_ROMFS_SRC ${CMAKE_CURRENT_BINARY_DIR}/elf_romfs.c) - set(ELF_GEN_SYMTAB ${CMAKE_CURRENT_SOURCE_DIR}/gen_symtab.py) - set(ELF_GEN_ROMFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gen_romfs_source.py) set(ELFNAME_BASE hello) @@ -41,10 +39,9 @@ if(CONFIG_EXAMPLES_ELF) add_custom_command( OUTPUT ${ELF_SYMTAB} - COMMAND ${Python3_EXECUTABLE} ${ELF_GEN_SYMTAB} ${ELF_BINARY_FILES} g_elf + COMMAND ${NUTTX_APPS_DIR}/tools/mksymtab.sh ${ELF_BINARY_FILES} g_elf > ${ELF_SYMTAB} - DEPENDS ${ELF_BINARY_TARGETS} ${ELF_GEN_SYMTAB} - VERBATIM) + DEPENDS ${ELF_BINARY_TARGETS}) set_source_files_properties(${ELF_SYMTAB} PROPERTIES GENERATED TRUE) @@ -60,21 +57,34 @@ if(CONFIG_EXAMPLES_ELF) set(ELF_PKGTEST ${CMAKE_BINARY_DIR}/bin/pkgtest.nsh) set(ELF_PKGFAIL ${CMAKE_BINARY_DIR}/bin/pkgfail.nsh) set(ELF_HELLO_BIN ${CMAKE_BINARY_DIR}/bin/hello) + set(ELF_FIXTURE_HOST_DIR ${CMAKE_CURRENT_BINARY_DIR}/host) + set(ELF_FIXTURE_HOST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/host/CMakeLists.txt) + set(ELF_FIXTURE_TOOL + ${ELF_FIXTURE_HOST_DIR}/mk_pkg_fixture${CMAKE_EXECUTABLE_SUFFIX}) set(ELF_HELLO_TARGET hello) if(NOT CMAKE_C_ELF_COMPILER) set(ELF_HELLO_TARGET ELF_hello) endif() + add_custom_command( + OUTPUT ${ELF_FIXTURE_TOOL} + COMMAND ${CMAKE_COMMAND} -S ${CMAKE_CURRENT_SOURCE_DIR}/host -B + ${ELF_FIXTURE_HOST_DIR} + COMMAND ${CMAKE_COMMAND} --build ${ELF_FIXTURE_HOST_DIR} --target + mk_pkg_fixture + DEPENDS ${ELF_FIXTURE_HOST_SRC} + ${CMAKE_CURRENT_SOURCE_DIR}/mk_pkg_fixture.c + VERBATIM) + add_custom_command( OUTPUT ${ELF_INDEX_JSON} ${ELF_BAD_INDEX_JSON} ${ELF_PKGTEST} ${ELF_PKGFAIL} COMMAND - ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/mk_pkg_fixture.py - ${ELF_HELLO_BIN} ${ELF_INDEX_JSON} ${ELF_BAD_INDEX_JSON} - ${ELF_PKGTEST} ${ELF_PKGFAIL} ${CONFIG_ARCH} ${CONFIG_ARCH_BOARD} - DEPENDS ${ELF_HELLO_TARGET} - ${CMAKE_CURRENT_SOURCE_DIR}/mk_pkg_fixture.py + ${ELF_FIXTURE_TOOL} ${ELF_HELLO_BIN} ${ELF_INDEX_JSON} + ${ELF_BAD_INDEX_JSON} ${ELF_PKGTEST} ${ELF_PKGFAIL} ${CONFIG_ARCH} + ${CONFIG_ARCH_BOARD} + DEPENDS ${ELF_HELLO_TARGET} ${ELF_FIXTURE_TOOL} VERBATIM) list(APPEND ELF_ROMFS_DEPS ${ELF_INDEX_JSON} ${ELF_BAD_INDEX_JSON} @@ -89,10 +99,8 @@ if(CONFIG_EXAMPLES_ELF) add_custom_command( OUTPUT ${ELF_ROMFS_SRC} - COMMAND ${Python3_EXECUTABLE} ${ELF_GEN_ROMFS_SRC} ${ELF_ROMFS_IMG} - ${ELF_ROMFS_SRC} - DEPENDS ${ELF_ROMFS_IMG} ${ELF_GEN_ROMFS_SRC} - VERBATIM) + COMMAND xxd -i ${ELF_ROMFS_IMG} > ${ELF_ROMFS_SRC} + DEPENDS ${ELF_ROMFS_IMG}) set_source_files_properties(${ELF_ROMFS_SRC} PROPERTIES GENERATED TRUE) diff --git a/examples/elf/main/Makefile b/examples/elf/main/Makefile index 5ad3d9f623c..a147d165cda 100644 --- a/examples/elf/main/Makefile +++ b/examples/elf/main/Makefile @@ -70,19 +70,29 @@ ROMFSIMG = elf_romfs.img ROMFSSRC = elf_romfs.c ROMFSOBJ = $(ROMFSSRC:.c=$(OBJEXT)) ifdef CONFIG_SYSTEM_NXPKG +HOSTOBJSEXT ?= hobj PKGINDEX = $(BINDIR)/index.json PKGBADINDEX = $(BINDIR)/bad-index.json PKGTEST = $(BINDIR)/pkgtest.nsh PKGFAIL = $(BINDIR)/pkgfail.nsh -PKG_FIXTURE_GEN = $(APPDIR)/examples/elf/main/mk_pkg_fixture.py +PKG_FIXTURE_GEN = mk_pkg_fixture$(HOSTEXEEXT) +PKG_FIXTURE_SRCS = mk_pkg_fixture.c +PKG_FIXTURE_OBJS = $(PKG_FIXTURE_SRCS:.c=.$(HOSTOBJSEXT)) PKG_FIXTURE_OUTPUTS = $(PKGINDEX) $(PKGBADINDEX) $(PKGTEST) $(PKGFAIL) PKG_FIXTURE_INPUTS = $(BINDIR)/hello PKG_FIXTURE_ARGS = \ $(BINDIR)/hello $(PKGINDEX) $(PKGBADINDEX) $(PKGTEST) $(PKGFAIL) \ $(CONFIG_ARCH) $(CONFIG_ARCH_BOARD) +$(PKG_FIXTURE_OBJS): %.$(HOSTOBJSEXT): %.c + @echo "CC: $<" + $(Q) $(HOSTCC) -c $(HOSTCFLAGS) $< -o $@ + +$(PKG_FIXTURE_GEN): $(PKG_FIXTURE_OBJS) + $(Q) $(HOSTCC) $(HOSTLDFLAGS) $(PKG_FIXTURE_OBJS) -o $@ + $(PKG_FIXTURE_OUTPUTS) &: $(PKG_FIXTURE_INPUTS) $(PKG_FIXTURE_GEN) - $(Q) python3 $(PKG_FIXTURE_GEN) \ + $(Q) ./$(PKG_FIXTURE_GEN) \ $(PKG_FIXTURE_ARGS) endif @@ -122,7 +132,7 @@ postinstall:: $(ROMFSOBJ) $(SYMTABOBJ) $(FSIMG_OBJ) $(call ARLOCK, $(call CONVERT_PATH,$(BIN)), $^) distclean:: - $(Q) $(call DELFILE, $(SYMTABSRC) $(SYMTABOBJ) $(ROMFSSRC) $(ROMFSIMG) $(ROMFSOBJ) $(MODLUE_NAME)) + $(Q) $(call DELFILE, $(SYMTABSRC) $(SYMTABOBJ) $(ROMFSSRC) $(ROMFSIMG) $(ROMFSOBJ) $(MODLUE_NAME) $(PKG_FIXTURE_GEN) $(PKG_FIXTURE_OBJS)) include $(APPDIR)/Application.mk diff --git a/examples/elf/main/gen_romfs_source.py b/examples/elf/main/gen_romfs_source.py deleted file mode 100644 index b7965d0864a..00000000000 --- a/examples/elf/main/gen_romfs_source.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -# -# SPDX-License-Identifier: Apache-2.0 - -import pathlib -import sys - - -def usage() -> int: - print("usage: gen_romfs_source.py ", file=sys.stderr) - return 1 - - -def symbol_from_path(path: pathlib.Path) -> str: - return path.name.replace(".", "_") - - -def format_bytes(data: bytes) -> str: - rows = [] - for index in range(0, len(data), 12): - chunk = data[index : index + 12] - rows.append(" " + ", ".join(f"0x{byte:02x}" for byte in chunk)) - return ",\n".join(rows) - - -def write_output(input_path: pathlib.Path, output_path: pathlib.Path) -> None: - symbol = symbol_from_path(input_path) - data = input_path.read_bytes() - text = "\n".join( - [ - "#include ", - "", - f"const unsigned char aligned_data(4) {symbol}[] = {{", - format_bytes(data), - "};", - f"const unsigned int {symbol}_len = {len(data)};", - "", - ] - ) - - if output_path.exists() and output_path.read_text(encoding="utf-8") == text: - return - - output_path.write_text(text, encoding="utf-8") - - -def main() -> int: - if len(sys.argv) != 3: - return usage() - - input_path = pathlib.Path(sys.argv[1]) - output_path = pathlib.Path(sys.argv[2]) - write_output(input_path, output_path) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/examples/elf/main/gen_symtab.py b/examples/elf/main/gen_symtab.py deleted file mode 100644 index cc7c6f0da88..00000000000 --- a/examples/elf/main/gen_symtab.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 -# -# SPDX-License-Identifier: Apache-2.0 - -import pathlib -import subprocess -import sys - - -def usage() -> int: - print( - "usage: gen_symtab.py [ ...] ", - file=sys.stderr, - ) - return 1 - - -def symbol_name(line: str) -> str: - return line.split()[-1].replace('"', "") - - -def collect_symbols(paths): - undefined: set[str] = set() - defined: set[str] = set() - - for path in paths: - result = subprocess.run( - ["nm", str(path)], - check=False, - text=True, - capture_output=True, - ) - if result.returncode not in (0, 1): - raise RuntimeError(result.stderr.strip() or f"nm failed for {path}") - - for line in result.stdout.splitlines(): - if " U " in line: - undefined.add(symbol_name(line)) - elif line and not line.endswith(":"): - defined.add(symbol_name(line)) - - return sorted(undefined - defined) - - -def write_output(path: pathlib.Path, prefix: str, symbols) -> None: - lines = [ - "#include ", - "#include ", - "", - ] - - for symbol in symbols: - lines.append(f"extern void *{symbol};") - - lines.append("") - lines.append(f"const struct symtab_s {prefix}_exports[] = ") - lines.append("{") - - for symbol in symbols: - lines.append(f' {{"{symbol}", &{symbol}}},') - - lines.append("};") - lines.append("") - lines.append( - f"const int {prefix}_nexports = sizeof({prefix}_exports) / sizeof(struct symtab_s);" - ) - lines.append("") - - text = "\n".join(lines) - if path.exists() and path.read_text(encoding="utf-8") == text: - return - - path.write_text(text, encoding="utf-8") - - -def main() -> int: - if len(sys.argv) < 4: - return usage() - - *input_paths, prefix, output = sys.argv[1:] - symbols = collect_symbols([pathlib.Path(item) for item in input_paths]) - write_output(pathlib.Path(output), prefix, symbols) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/examples/elf/main/host/CMakeLists.txt b/examples/elf/main/host/CMakeLists.txt new file mode 100644 index 00000000000..a2474cb7a83 --- /dev/null +++ b/examples/elf/main/host/CMakeLists.txt @@ -0,0 +1,26 @@ +# ############################################################################## +# apps/examples/elf/main/host/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +cmake_minimum_required(VERSION 3.16) +project(elf_fixture_host LANGUAGES C) + +add_executable(mk_pkg_fixture ../mk_pkg_fixture.c) diff --git a/examples/elf/main/mk_pkg_fixture.c b/examples/elf/main/mk_pkg_fixture.c new file mode 100644 index 00000000000..0e69b7ee998 --- /dev/null +++ b/examples/elf/main/mk_pkg_fixture.c @@ -0,0 +1,454 @@ +/**************************************************************************** + * apps/examples/elf/main/mk_pkg_fixture.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define SHA256_BLOCK_SIZE 64 +#define SHA256_DIGEST_SIZE 32 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct sha256_state_s +{ + uint32_t state[8]; + uint64_t bitcount; + uint8_t buffer[SHA256_BLOCK_SIZE]; + size_t buffer_len; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const uint32_t g_sha256_init[8] = +{ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +}; + +static const uint32_t g_sha256_k[64] = +{ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint32_t sha256_rotr(uint32_t value, unsigned int bits) +{ + return (value >> bits) | (value << (32 - bits)); +} + +static uint32_t sha256_ch(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) ^ (~x & z); +} + +static uint32_t sha256_maj(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) ^ (x & z) ^ (y & z); +} + +static uint32_t sha256_bs0(uint32_t x) +{ + return sha256_rotr(x, 2) ^ sha256_rotr(x, 13) ^ sha256_rotr(x, 22); +} + +static uint32_t sha256_bs1(uint32_t x) +{ + return sha256_rotr(x, 6) ^ sha256_rotr(x, 11) ^ sha256_rotr(x, 25); +} + +static uint32_t sha256_ss0(uint32_t x) +{ + return sha256_rotr(x, 7) ^ sha256_rotr(x, 18) ^ (x >> 3); +} + +static uint32_t sha256_ss1(uint32_t x) +{ + return sha256_rotr(x, 17) ^ sha256_rotr(x, 19) ^ (x >> 10); +} + +static uint32_t sha256_load32(const uint8_t *src) +{ + return ((uint32_t)src[0] << 24) | ((uint32_t)src[1] << 16) | + ((uint32_t)src[2] << 8) | (uint32_t)src[3]; +} + +static void sha256_store32(uint8_t *dst, uint32_t value) +{ + dst[0] = (uint8_t)(value >> 24); + dst[1] = (uint8_t)(value >> 16); + dst[2] = (uint8_t)(value >> 8); + dst[3] = (uint8_t)value; +} + +static void sha256_transform(struct sha256_state_s *ctx, + const uint8_t block[SHA256_BLOCK_SIZE]) +{ + uint32_t schedule[64]; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint32_t e; + uint32_t f; + uint32_t g; + uint32_t h; + int i; + + for (i = 0; i < 16; i++) + { + schedule[i] = sha256_load32(block + i * 4); + } + + for (; i < 64; i++) + { + schedule[i] = sha256_ss1(schedule[i - 2]) + schedule[i - 7] + + sha256_ss0(schedule[i - 15]) + schedule[i - 16]; + } + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + f = ctx->state[5]; + g = ctx->state[6]; + h = ctx->state[7]; + + for (i = 0; i < 64; i++) + { + uint32_t t1 = h + sha256_bs1(e) + sha256_ch(e, f, g) + g_sha256_k[i] + + schedule[i]; + uint32_t t2 = sha256_bs0(a) + sha256_maj(a, b, c); + + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; +} + +static void sha256_init(struct sha256_state_s *ctx) +{ + memcpy(ctx->state, g_sha256_init, sizeof(g_sha256_init)); + ctx->bitcount = 0; + ctx->buffer_len = 0; +} + +static void sha256_update(struct sha256_state_s *ctx, const uint8_t *data, + size_t len) +{ + ctx->bitcount += (uint64_t)len * 8; + + while (len > 0) + { + size_t space = SHA256_BLOCK_SIZE - ctx->buffer_len; + size_t chunk = len < space ? len : space; + + memcpy(ctx->buffer + ctx->buffer_len, data, chunk); + ctx->buffer_len += chunk; + data += chunk; + len -= chunk; + + if (ctx->buffer_len == SHA256_BLOCK_SIZE) + { + sha256_transform(ctx, ctx->buffer); + ctx->buffer_len = 0; + } + } +} + +static void sha256_final(struct sha256_state_s *ctx, + uint8_t digest[SHA256_DIGEST_SIZE]) +{ + uint8_t length_bytes[8]; + uint8_t padding[SHA256_BLOCK_SIZE] = + { + 0x80 + }; + + int i; + + for (i = 0; i < 8; i++) + { + length_bytes[7 - i] = (uint8_t)(ctx->bitcount >> (i * 8)); + } + + if (ctx->buffer_len < 56) + { + sha256_update(ctx, padding, 56 - ctx->buffer_len); + } + else + { + sha256_update(ctx, padding, SHA256_BLOCK_SIZE - ctx->buffer_len); + sha256_update(ctx, padding + 1, 56); + } + + sha256_update(ctx, length_bytes, sizeof(length_bytes)); + + for (i = 0; i < 8; i++) + { + sha256_store32(digest + i * 4, ctx->state[i]); + } +} + +static int sha256_file(const char *path, char *digest_hex, size_t digest_len) +{ + struct sha256_state_s ctx; + uint8_t digest[SHA256_DIGEST_SIZE]; + uint8_t buffer[4096]; + static const char g_hex[] = "0123456789abcdef"; + FILE *stream; + size_t nread; + int i; + + if (digest_len < SHA256_DIGEST_SIZE * 2 + 1) + { + fprintf(stderr, "digest buffer is too small\n"); + return -1; + } + + stream = fopen(path, "rb"); + if (stream == NULL) + { + fprintf(stderr, "failed to open %s: %s\n", path, strerror(errno)); + return -1; + } + + sha256_init(&ctx); + + while ((nread = fread(buffer, 1, sizeof(buffer), stream)) > 0) + { + sha256_update(&ctx, buffer, nread); + } + + if (ferror(stream) != 0) + { + fprintf(stderr, "failed to read %s: %s\n", path, strerror(errno)); + fclose(stream); + return -1; + } + + fclose(stream); + sha256_final(&ctx, digest); + + for (i = 0; i < SHA256_DIGEST_SIZE; i++) + { + digest_hex[i * 2] = g_hex[digest[i] >> 4]; + digest_hex[i * 2 + 1] = g_hex[digest[i] & 0x0f]; + } + + digest_hex[SHA256_DIGEST_SIZE * 2] = '\0'; + return 0; +} + +static int write_text_file(const char *path, const char *content) +{ + FILE *stream = fopen(path, "w"); + + if (stream == NULL) + { + fprintf(stderr, "failed to open %s: %s\n", path, strerror(errno)); + return -1; + } + + if (fputs(content, stream) == EOF) + { + fprintf(stderr, "failed to write %s: %s\n", path, strerror(errno)); + fclose(stream); + return -1; + } + + if (fclose(stream) != 0) + { + fprintf(stderr, "failed to close %s: %s\n", path, strerror(errno)); + return -1; + } + + return 0; +} + +static int write_index_file(const char *path, const char *arch, + const char *compat, const char *digest) +{ + const char *artifact = "/mnt/elf/romfs/hello"; + FILE *stream = fopen(path, "w"); + + if (stream == NULL) + { + fprintf(stderr, "failed to open %s: %s\n", path, strerror(errno)); + return -1; + } + + if (fprintf(stream, + "{\"packages\":[" + "{\"name\":\"hello\",\"version\":\"1.0.0\",\"arch\":\"%s\"," + "\"compat\":\"%s\",\"artifact\":\"%s\",\"sha256\":\"%s\"," + "\"type\":\"elf\"}," + "{\"name\":\"hello\",\"version\":\"9.9.9\",\"arch\":\"arm\"," + "\"compat\":\"stm32f4discovery\",\"artifact\":\"%s\"," + "\"sha256\":\"%s\",\"type\":\"elf\"}" + "]}\n", + arch, compat, artifact, digest, artifact, digest) < 0) + { + fprintf(stderr, "failed to write %s: %s\n", path, strerror(errno)); + fclose(stream); + return -1; + } + + if (fclose(stream) != 0) + { + fprintf(stderr, "failed to close %s: %s\n", path, strerror(errno)); + return -1; + } + + return 0; +} + +static int write_bad_index_file(const char *path, const char *arch, + const char *compat) +{ + FILE *stream = fopen(path, "w"); + + if (stream == NULL) + { + fprintf(stderr, "failed to open %s: %s\n", path, strerror(errno)); + return -1; + } + + if (fprintf(stream, + "{\"packages\":[" + "{\"name\":\"hello-missing\",\"version\":\"1.0.0\"," + "\"arch\":\"%s\",\"compat\":\"%s\"," + "\"artifact\":\"/mnt/elf/romfs/hello-missing\"," + "\"sha256\":\"" + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "\",\"type\":\"elf\"}" + "]}\n", + arch, compat) < 0) + { + fprintf(stderr, "failed to write %s: %s\n", path, strerror(errno)); + fclose(stream); + return -1; + } + + if (fclose(stream) != 0) + { + fprintf(stderr, "failed to close %s: %s\n", path, strerror(errno)); + return -1; + } + + return 0; +} + +static void usage(const char *progname) +{ + fprintf(stderr, + "usage: %s " + " \n", + progname); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, char *argv[]) +{ + char digest[SHA256_DIGEST_SIZE * 2 + 1]; + static const char g_pkgtest[] = + "mount -t tmpfs /etc\n" + "mount -t tmpfs /var\n" + "mkdir /etc/nxpkg\n" + "cp /mnt/elf/romfs/index.json /etc/nxpkg/index.json\n" + "nxpkg install hello\n" + "nxpkg list\n"; + static const char g_pkgfail[] = + "mount -t tmpfs /etc\n" + "mount -t tmpfs /var\n" + "mkdir /etc/nxpkg\n" + "cp /mnt/elf/romfs/bad-index.json /etc/nxpkg/index.json\n" + "nxpkg install hello-missing\n"; + + if (argc != 8) + { + usage(argv[0]); + return EXIT_FAILURE; + } + + if (sha256_file(argv[1], digest, sizeof(digest)) < 0 || + write_index_file(argv[2], argv[6], argv[7], digest) < 0 || + write_bad_index_file(argv[3], argv[6], argv[7]) < 0 || + write_text_file(argv[4], g_pkgtest) < 0 || + write_text_file(argv[5], g_pkgfail) < 0) + { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/examples/elf/main/mk_pkg_fixture.py b/examples/elf/main/mk_pkg_fixture.py deleted file mode 100644 index da8b395c077..00000000000 --- a/examples/elf/main/mk_pkg_fixture.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python3 -# -# SPDX-License-Identifier: Apache-2.0 - -import hashlib -import json -import pathlib -import sys - - -def sha256_file(path: pathlib.Path) -> str: - digest = hashlib.sha256() - with path.open("rb") as infile: - while True: - chunk = infile.read(4096) - if not chunk: - break - digest.update(chunk) - - return digest.hexdigest() - - -def write_index( - path: pathlib.Path, - arch: str, - compat: str, - artifact: str, - digest: str, -) -> None: - payload = { - "packages": [ - { - "name": "hello", - "version": "1.0.0", - "arch": arch, - "compat": compat, - "artifact": artifact, - "sha256": digest, - "type": "elf", - }, - { - "name": "hello", - "version": "9.9.9", - "arch": "arm", - "compat": "stm32f4discovery", - "artifact": artifact, - "sha256": digest, - "type": "elf", - }, - ] - } - - path.write_text(json.dumps(payload, separators=(",", ":")) + "\n", encoding="utf-8") - - -def write_bad_index(path: pathlib.Path, arch: str, compat: str) -> None: - payload = { - "packages": [ - { - "name": "hello-missing", - "version": "1.0.0", - "arch": arch, - "compat": compat, - "artifact": "/mnt/elf/romfs/hello-missing", - "sha256": "0" * 64, - "type": "elf", - } - ] - } - - path.write_text(json.dumps(payload, separators=(",", ":")) + "\n", encoding="utf-8") - - -def write_script(path: pathlib.Path) -> None: - script = "\n".join( - [ - "mount -t tmpfs /etc", - "mount -t tmpfs /var", - "mkdir /etc/nxpkg", - "cp /mnt/elf/romfs/index.json /etc/nxpkg/index.json", - "nxpkg install hello", - "nxpkg list", - "", - ] - ) - path.write_text(script, encoding="utf-8") - - -def write_fail_script(path: pathlib.Path) -> None: - script = "\n".join( - [ - "mount -t tmpfs /etc", - "mount -t tmpfs /var", - "mkdir /etc/nxpkg", - "cp /mnt/elf/romfs/bad-index.json /etc/nxpkg/index.json", - "nxpkg install hello-missing", - "", - ] - ) - path.write_text(script, encoding="utf-8") - - -def main() -> int: - if len(sys.argv) != 8: - print( - "usage: mk_pkg_fixture.py " - " ", - file=sys.stderr, - ) - return 1 - - hello = pathlib.Path(sys.argv[1]) - index = pathlib.Path(sys.argv[2]) - bad_index = pathlib.Path(sys.argv[3]) - script = pathlib.Path(sys.argv[4]) - fail_script = pathlib.Path(sys.argv[5]) - arch = sys.argv[6] - compat = sys.argv[7] - artifact = "/mnt/elf/romfs/hello" - - digest = sha256_file(hello) - write_index(index, arch, compat, artifact, digest) - write_bad_index(bad_index, arch, compat) - write_script(script) - write_fail_script(fail_script) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main())