From 4003d16d478d7b76f2a2182cca63798555eb6f8d Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 9 Mar 2026 11:12:39 -0700 Subject: [PATCH 1/7] sea: add --build-sea to generate SEA directly with Node.js binary Instead of relying on a WASM build of postject to perform the injection, add LIEF as dependency and generate the SEA directly from core via a new CLI option --build-sea which takes the SEA config. This simplifies SEA generation for users and makes it easier to debug/maintain the SEA building process. PR-URL: https://github.com/nodejs/node/pull/61167 Reviewed-By: Anna Henningsen Reviewed-By: Chengzhong Wu --- .github/CODEOWNERS | 2 + .gitignore | 2 + LICENSE | 3 +- configure.py | 14 + deps/LIEF/lief.gyp | 495 ++++++++++++++++++ doc/api/cli.md | 17 + doc/api/single-executable-applications.md | 235 +++++---- doc/node.1 | 3 + node.gyp | 5 + src/node.cc | 17 +- src/node_metadata.cc | 11 + src/node_metadata.h | 9 +- src/node_options.cc | 13 + src/node_options.h | 1 + src/node_sea.cc | 63 +-- src/node_sea.h | 22 +- src/node_sea_bin.cc | 431 ++++++++++++++- test/common/sea.js | 63 +++ test/fixtures/sea/already-exists/sea-2.js | 1 + .../sea/already-exists/sea-config-2.json | 6 + .../sea/already-exists/sea-config.json | 5 + test/fixtures/sea/already-exists/sea.js | 1 + test/fixtures/sea/basic/requirable.js | 3 + test/fixtures/sea/basic/sea-config.json | 6 + test/fixtures/sea/basic/sea.js | 64 +++ .../sea/executable-field/sea-config.json | 6 + test/fixtures/sea/executable-field/sea.js | 3 + test/parallel/test-process-versions.js | 9 + test/sea/sea.status | 5 + test/sea/test-build-sea-already-exists.js | 20 + test/sea/test-build-sea-config-not-found.js | 38 ++ test/sea/test-build-sea-executable-field.js | 47 ++ test/sea/test-build-sea-invalid-assets.js | 77 +++ .../test-build-sea-invalid-boolean-fields.js | 74 +++ test/sea/test-build-sea-invalid-exec-argv.js | 95 ++++ test/sea/test-build-sea-invalid-executable.js | 99 ++++ test/sea/test-build-sea-invalid-json.js | 23 + test/sea/test-build-sea-missing-main.js | 91 ++++ test/sea/test-build-sea-missing-output.js | 64 +++ test/sea/test-build-sea.js | 33 ++ 40 files changed, 2039 insertions(+), 137 deletions(-) create mode 100644 deps/LIEF/lief.gyp create mode 100644 test/fixtures/sea/already-exists/sea-2.js create mode 100644 test/fixtures/sea/already-exists/sea-config-2.json create mode 100644 test/fixtures/sea/already-exists/sea-config.json create mode 100644 test/fixtures/sea/already-exists/sea.js create mode 100644 test/fixtures/sea/basic/requirable.js create mode 100644 test/fixtures/sea/basic/sea-config.json create mode 100644 test/fixtures/sea/basic/sea.js create mode 100644 test/fixtures/sea/executable-field/sea-config.json create mode 100644 test/fixtures/sea/executable-field/sea.js create mode 100644 test/sea/test-build-sea-already-exists.js create mode 100644 test/sea/test-build-sea-config-not-found.js create mode 100644 test/sea/test-build-sea-executable-field.js create mode 100644 test/sea/test-build-sea-invalid-assets.js create mode 100644 test/sea/test-build-sea-invalid-boolean-fields.js create mode 100644 test/sea/test-build-sea-invalid-exec-argv.js create mode 100644 test/sea/test-build-sea-invalid-executable.js create mode 100644 test/sea/test-build-sea-invalid-json.js create mode 100644 test/sea/test-build-sea-missing-main.js create mode 100644 test/sea/test-build-sea-missing-output.js create mode 100644 test/sea/test-build-sea.js diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0164937307a770..6f2749a758891e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -153,6 +153,7 @@ /test/parallel/test-runner-* @nodejs/test_runner # Single Executable Applications +/deps/LIEF @nodejs/single-executable /deps/postject @nodejs/single-executable /doc/api/single-executable-applications.md @nodejs/single-executable /doc/contributing/maintaining/maintaining-single-executable-application-support.md @nodejs/single-executable @@ -160,6 +161,7 @@ /test/fixtures/postject-copy @nodejs/single-executable /test/sea @nodejs/single-executable /tools/dep_updaters/update-postject.sh @nodejs/single-executable +/tools/dep_updaters/update-lief.sh @nodejs/single-executable # Permission Model /doc/api/permissions.md @nodejs/security-wg diff --git a/.gitignore b/.gitignore index 221e4f4062486a..d283bce868da6c 100644 --- a/.gitignore +++ b/.gitignore @@ -145,6 +145,8 @@ tools/*/*.i.tmp # Ignore dependencies fetched by tools/v8/fetch_deps.py /deps/.cipd !deps/LIEF/** +deps/LIEF/*.vcxproj* +deps/LIEF/*.sln # === Rules for Windows vcbuild.bat === /temp-vcbuild diff --git a/LICENSE b/LICENSE index 403ea3c5717087..2837954aa89579 100644 --- a/LICENSE +++ b/LICENSE @@ -1262,7 +1262,8 @@ The externally maintained libraries used by Node.js are: SOFTWARE. """ -- postject, located at test/fixtures/postject-copy, is licensed as follows: +- postject, located at test/fixtures/postject-copy and used as a basis for + src/node_sea_bin.cc, is licensed as follows: """ Postject is licensed for use as follows: diff --git a/configure.py b/configure.py index 98a8b147e4cbfd..9073b170899cac 100755 --- a/configure.py +++ b/configure.py @@ -898,6 +898,12 @@ default=None, help='do not install the bundled Amaro (TypeScript utils)') +parser.add_argument('--without-lief', + action='store_true', + dest='without_lief', + default=None, + help='build without LIEF (Library for instrumenting executable formats)') + parser.add_argument('--without-npm', action='store_true', dest='without_npm', @@ -1689,6 +1695,14 @@ def configure_node(o): o['variables']['single_executable_application'] = b(not options.disable_single_executable_application) if options.disable_single_executable_application: o['defines'] += ['DISABLE_SINGLE_EXECUTABLE_APPLICATION'] + o['variables']['node_use_lief'] = 'false' + else: + if (options.without_lief is not None): + o['variables']['node_use_lief'] = b(not options.without_lief) + elif flavor in ('mac', 'linux', 'win'): + o['variables']['node_use_lief'] = 'true' + else: + o['variables']['node_use_lief'] = 'false' o['variables']['node_with_ltcg'] = b(options.with_ltcg) if flavor != 'win' and options.with_ltcg: diff --git a/deps/LIEF/lief.gyp b/deps/LIEF/lief.gyp new file mode 100644 index 00000000000000..3864c0e538a588 --- /dev/null +++ b/deps/LIEF/lief.gyp @@ -0,0 +1,495 @@ +{ + 'target_defaults': { + 'default_configurations': 'Release', + 'configurations': { + # Don't define DEBUG, it conflicts with the FILE_FLAGS enum. + 'Debug': { + 'defines!': [ 'DEBUG', 'NDEBUG' ], + }, + 'Release': { + 'defines!': [ 'DEBUG' ], + 'defines': [ 'NDEBUG' ], + }, + }, + }, + 'variables': { + 'lief_sources': [ + # Root + 'src/Object.cpp', + 'src/endianness_support.cpp', + 'src/Visitor.cpp', + 'src/errors.cpp', + 'src/hash_stream.cpp', + 'src/internal_utils.cpp', + 'src/iostream.cpp', + 'src/json_api.cpp', + 'src/logging.cpp', + 'src/paging.cpp', + 'src/utils.cpp', + 'src/range.cpp', + 'src/visitors/hash.cpp', + # BinaryStream + 'src/BinaryStream/ASN1Reader.cpp', + 'src/BinaryStream/BinaryStream.cpp', + 'src/BinaryStream/FileStream.cpp', + 'src/BinaryStream/MemoryStream.cpp', + 'src/BinaryStream/SpanStream.cpp', + 'src/BinaryStream/VectorStream.cpp', + # Abstract + 'src/Abstract/Binary.cpp', + 'src/Abstract/Symbol.cpp', + 'src/Abstract/Header.cpp', + 'src/Abstract/Section.cpp', + 'src/Abstract/Parser.cpp', + 'src/Abstract/Relocation.cpp', + 'src/Abstract/Function.cpp', + 'src/Abstract/hash.cpp', + 'src/Abstract/json_api.cpp', + 'src/Abstract/debug_info.cpp', + # Platforms + 'src/platforms/android/version.cpp', + # ELF core + 'src/ELF/Binary.cpp', + 'src/ELF/Builder.cpp', + 'src/ELF/endianness_support.cpp', + 'src/ELF/DataHandler/Handler.cpp', + 'src/ELF/DataHandler/Node.cpp', + 'src/ELF/DynamicEntry.cpp', + 'src/ELF/DynamicEntryArray.cpp', + 'src/ELF/DynamicEntryFlags.cpp', + 'src/ELF/DynamicEntryLibrary.cpp', + 'src/ELF/DynamicEntryRpath.cpp', + 'src/ELF/DynamicEntryRunPath.cpp', + 'src/ELF/DynamicSharedObject.cpp', + 'src/ELF/EnumToString.cpp', + 'src/ELF/GnuHash.cpp', + 'src/ELF/Header.cpp', + 'src/ELF/Layout.cpp', + 'src/ELF/Note.cpp', + 'src/ELF/Parser.cpp', + 'src/ELF/ProcessorFlags.cpp', + 'src/ELF/Relocation.cpp', + 'src/ELF/RelocationSizes.cpp', + 'src/ELF/RelocationStrings.cpp', + 'src/ELF/Section.cpp', + 'src/ELF/Segment.cpp', + 'src/ELF/Symbol.cpp', + 'src/ELF/SymbolVersion.cpp', + 'src/ELF/SymbolVersionAux.cpp', + 'src/ELF/SymbolVersionAuxRequirement.cpp', + 'src/ELF/SymbolVersionDefinition.cpp', + 'src/ELF/SymbolVersionRequirement.cpp', + 'src/ELF/SysvHash.cpp', + 'src/ELF/hash.cpp', + 'src/ELF/utils.cpp', + 'src/ELF/json_api.cpp', + # ELF NoteDetails + 'src/ELF/NoteDetails/NoteAbi.cpp', + 'src/ELF/NoteDetails/AndroidIdent.cpp', + 'src/ELF/NoteDetails/NoteGnuProperty.cpp', + 'src/ELF/NoteDetails/QNXStack.cpp', + 'src/ELF/NoteDetails/core/CoreAuxv.cpp', + 'src/ELF/NoteDetails/core/CoreFile.cpp', + 'src/ELF/NoteDetails/core/CorePrPsInfo.cpp', + 'src/ELF/NoteDetails/core/CorePrStatus.cpp', + 'src/ELF/NoteDetails/core/CoreSigInfo.cpp', + 'src/ELF/NoteDetails/properties/AArch64Feature.cpp', + 'src/ELF/NoteDetails/properties/AArch64PAuth.cpp', + 'src/ELF/NoteDetails/properties/StackSize.cpp', + 'src/ELF/NoteDetails/properties/X86Feature.cpp', + 'src/ELF/NoteDetails/properties/X86ISA.cpp', + # PE core + 'src/PE/Binary.cpp', + 'src/PE/Builder.cpp', + 'src/PE/CodeIntegrity.cpp', + 'src/PE/CodePage.cpp', + 'src/PE/DataDirectory.cpp', + 'src/PE/DelayImport.cpp', + 'src/PE/DelayImportEntry.cpp', + 'src/PE/DosHeader.cpp', + 'src/PE/EnumToString.cpp', + 'src/PE/ExceptionInfo.cpp', + 'src/PE/Export.cpp', + 'src/PE/ExportEntry.cpp', + 'src/PE/Factory.cpp', + 'src/PE/Header.cpp', + 'src/PE/Import.cpp', + 'src/PE/ImportEntry.cpp', + 'src/PE/OptionalHeader.cpp', + 'src/PE/Parser.cpp', + 'src/PE/ParserConfig.cpp', + 'src/PE/Relocation.cpp', + 'src/PE/RelocationEntry.cpp', + 'src/PE/ResourceData.cpp', + 'src/PE/ResourceDirectory.cpp', + 'src/PE/ResourceNode.cpp', + 'src/PE/ResourcesManager.cpp', + 'src/PE/RichEntry.cpp', + 'src/PE/RichHeader.cpp', + 'src/PE/Section.cpp', + 'src/PE/TLS.cpp', + 'src/PE/checksum.cpp', + 'src/PE/endianness_support.cpp', + 'src/PE/hash.cpp', + 'src/PE/json_api.cpp', + 'src/PE/layout_check.cpp', + 'src/PE/utils.cpp', + # PE signature + 'src/PE/signature/Attribute.cpp', + 'src/PE/signature/ContentInfo.cpp', + 'src/PE/signature/GenericContent.cpp', + 'src/PE/signature/OIDToString.cpp', + 'src/PE/signature/PKCS9TSTInfo.cpp', + 'src/PE/signature/RsaInfo.cpp', + 'src/PE/signature/Signature.cpp', + 'src/PE/signature/SignatureParser.cpp', + 'src/PE/signature/SignerInfo.cpp', + 'src/PE/signature/SpcIndirectData.cpp', + 'src/PE/signature/x509.cpp', + 'src/PE/signature/attributes/ContentType.cpp', + 'src/PE/signature/attributes/GenericType.cpp', + 'src/PE/signature/attributes/MsCounterSign.cpp', + 'src/PE/signature/attributes/MsSpcNestedSignature.cpp', + 'src/PE/signature/attributes/MsSpcStatementType.cpp', + 'src/PE/signature/attributes/MsManifestBinaryID.cpp', + 'src/PE/signature/attributes/PKCS9AtSequenceNumber.cpp', + 'src/PE/signature/attributes/PKCS9CounterSignature.cpp', + 'src/PE/signature/attributes/PKCS9MessageDigest.cpp', + 'src/PE/signature/attributes/PKCS9SigningTime.cpp', + 'src/PE/signature/attributes/SpcSpOpusInfo.cpp', + 'src/PE/signature/attributes/SpcRelaxedPeMarkerCheck.cpp', + 'src/PE/signature/attributes/SigningCertificateV2.cpp', + # PE LoadConfigurations + 'src/PE/LoadConfigurations/EnclaveConfiguration.cpp', + 'src/PE/LoadConfigurations/EnclaveImport.cpp', + 'src/PE/LoadConfigurations/LoadConfiguration.cpp', + 'src/PE/LoadConfigurations/VolatileMetadata.cpp', + 'src/PE/LoadConfigurations/CHPEMetadata/Metadata.cpp', + 'src/PE/LoadConfigurations/CHPEMetadata/MetadataARM64.cpp', + 'src/PE/LoadConfigurations/CHPEMetadata/MetadataX86.cpp', + 'src/PE/LoadConfigurations/DynamicRelocation/DynamicFixup.cpp', + 'src/PE/LoadConfigurations/DynamicRelocation/DynamicFixupARM64Kernel.cpp', + 'src/PE/LoadConfigurations/DynamicRelocation/DynamicFixupARM64X.cpp', + 'src/PE/LoadConfigurations/DynamicRelocation/DynamicFixupControlTransfer.cpp', + 'src/PE/LoadConfigurations/DynamicRelocation/DynamicFixupGeneric.cpp', + 'src/PE/LoadConfigurations/DynamicRelocation/DynamicRelocationBase.cpp', + 'src/PE/LoadConfigurations/DynamicRelocation/DynamicRelocationV1.cpp', + 'src/PE/LoadConfigurations/DynamicRelocation/DynamicRelocationV2.cpp', + 'src/PE/LoadConfigurations/DynamicRelocation/FunctionOverride.cpp', + 'src/PE/LoadConfigurations/DynamicRelocation/FunctionOverrideInfo.cpp', + # PE debug/exceptions + 'src/PE/debug/CodeView.cpp', + 'src/PE/debug/CodeViewPDB.cpp', + 'src/PE/debug/Debug.cpp', + 'src/PE/debug/ExDllCharacteristics.cpp', + 'src/PE/debug/FPO.cpp', + 'src/PE/debug/PDBChecksum.cpp', + 'src/PE/debug/Pogo.cpp', + 'src/PE/debug/PogoEntry.cpp', + 'src/PE/debug/Repro.cpp', + 'src/PE/debug/VCFeature.cpp', + 'src/PE/exceptions_info/RuntimeFunctionX64.cpp', + 'src/PE/exceptions_info/RuntimeFunctionAArch64.cpp', + 'src/PE/exceptions_info/UnwindCodeX64.cpp', + 'src/PE/exceptions_info/UnwindAArch64Decoder.cpp', + 'src/PE/exceptions_info/AArch64/PackedFunction.cpp', + 'src/PE/exceptions_info/AArch64/UnpackedFunction.cpp', + # PE resources + 'src/PE/resources/AcceleratorCodes.cpp', + 'src/PE/resources/ResourceAccelerator.cpp', + 'src/PE/resources/ResourceDialog.cpp', + 'src/PE/resources/ResourceDialogExtended.cpp', + 'src/PE/resources/ResourceDialogRegular.cpp', + 'src/PE/resources/ResourceIcon.cpp', + 'src/PE/resources/ResourceStringFileInfo.cpp', + 'src/PE/resources/ResourceStringTable.cpp', + 'src/PE/resources/ResourceVar.cpp', + 'src/PE/resources/ResourceVarFileInfo.cpp', + 'src/PE/resources/ResourceVersion.cpp', + # COFF (required by PE) + 'src/COFF/Binary.cpp', + 'src/COFF/utils.cpp', + 'src/COFF/Parser.cpp', + 'src/COFF/Header.cpp', + 'src/COFF/Section.cpp', + 'src/COFF/Relocation.cpp', + 'src/COFF/BigObjHeader.cpp', + 'src/COFF/RegularHeader.cpp', + 'src/COFF/Symbol.cpp', + 'src/COFF/AuxiliarySymbol.cpp', + 'src/COFF/AuxiliarySymbols/AuxiliarybfAndefSymbol.cpp', + 'src/COFF/AuxiliarySymbols/AuxiliaryCLRToken.cpp', + 'src/COFF/AuxiliarySymbols/AuxiliaryFile.cpp', + 'src/COFF/AuxiliarySymbols/AuxiliaryFunctionDefinition.cpp', + 'src/COFF/AuxiliarySymbols/AuxiliarySectionDefinition.cpp', + 'src/COFF/AuxiliarySymbols/AuxiliaryWeakExternal.cpp', + # Mach-O + 'src/MachO/AtomInfo.cpp', + 'src/MachO/Binary.cpp', + 'src/MachO/BinaryParser.cpp', + 'src/MachO/BindingInfo.cpp', + 'src/MachO/BindingInfoIterator.cpp', + 'src/MachO/BuildToolVersion.cpp', + 'src/MachO/BuildVersion.cpp', + 'src/MachO/Builder.cpp', + 'src/MachO/ChainedBindingInfo.cpp', + 'src/MachO/ChainedBindingInfoList.cpp', + 'src/MachO/ChainedFixup.cpp', + 'src/MachO/ChainedPointerAnalysis.cpp', + 'src/MachO/CodeSignature.cpp', + 'src/MachO/CodeSignatureDir.cpp', + 'src/MachO/DataCodeEntry.cpp', + 'src/MachO/DataInCode.cpp', + 'src/MachO/DyldBindingInfo.cpp', + 'src/MachO/DyldChainedFixups.cpp', + 'src/MachO/DyldChainedFixupsCreator.cpp', + 'src/MachO/DyldChainedFormat.cpp', + 'src/MachO/DyldEnvironment.cpp', + 'src/MachO/DyldExportsTrie.cpp', + 'src/MachO/DyldInfo.cpp', + 'src/MachO/DylibCommand.cpp', + 'src/MachO/DylinkerCommand.cpp', + 'src/MachO/DynamicSymbolCommand.cpp', + 'src/MachO/EncryptionInfo.cpp', + 'src/MachO/EnumToString.cpp', + 'src/MachO/ExportInfo.cpp', + 'src/MachO/FatBinary.cpp', + 'src/MachO/FilesetCommand.cpp', + 'src/MachO/FunctionStarts.cpp', + 'src/MachO/FunctionVariants.cpp', + 'src/MachO/FunctionVariantFixups.cpp', + 'src/MachO/Header.cpp', + 'src/MachO/IndirectBindingInfo.cpp', + 'src/MachO/LinkEdit.cpp', + 'src/MachO/LinkerOptHint.cpp', + 'src/MachO/LoadCommand.cpp', + 'src/MachO/MainCommand.cpp', + 'src/MachO/NoteCommand.cpp', + 'src/MachO/Parser.cpp', + 'src/MachO/ParserConfig.cpp', + 'src/MachO/RPathCommand.cpp', + 'src/MachO/Relocation.cpp', + 'src/MachO/RelocationDyld.cpp', + 'src/MachO/RelocationFixup.cpp', + 'src/MachO/RelocationObject.cpp', + 'src/MachO/Routine.cpp', + 'src/MachO/Section.cpp', + 'src/MachO/SegmentCommand.cpp', + 'src/MachO/SegmentSplitInfo.cpp', + 'src/MachO/SourceVersion.cpp', + 'src/MachO/Stub.cpp', + 'src/MachO/SubClient.cpp', + 'src/MachO/SubFramework.cpp', + 'src/MachO/Symbol.cpp', + 'src/MachO/SymbolCommand.cpp', + 'src/MachO/ThreadCommand.cpp', + 'src/MachO/TrieNode.cpp', + 'src/MachO/TwoLevelHints.cpp', + 'src/MachO/UUIDCommand.cpp', + 'src/MachO/UnknownCommand.cpp', + 'src/MachO/VersionMin.cpp', + 'src/MachO/endianness_support.cpp', + 'src/MachO/exports_trie.cpp', + 'src/MachO/hash.cpp', + 'src/MachO/json_api.cpp', + 'src/MachO/layout_check.cpp', + 'src/MachO/utils.cpp', + # Stubs when extended features are disabled + 'src/DWARF/dwarf.cpp', + 'src/PDB/pdb.cpp', + 'src/ObjC/objc.cpp', + 'src/dyld-shared-cache/dyldsc.cpp', + 'src/asm/asm.cpp', + ], + 'lief_third_party_sources': [ + # mbedTLS sources (extracted and checked in under third-party) + 'third-party/mbedtls/library/aes.c', + 'third-party/mbedtls/library/aesce.c', + 'third-party/mbedtls/library/aesni.c', + 'third-party/mbedtls/library/aria.c', + 'third-party/mbedtls/library/asn1parse.c', + 'third-party/mbedtls/library/asn1write.c', + 'third-party/mbedtls/library/base64.c', + 'third-party/mbedtls/library/bignum.c', + 'third-party/mbedtls/library/bignum_core.c', + 'third-party/mbedtls/library/bignum_mod.c', + 'third-party/mbedtls/library/bignum_mod_raw.c', + 'third-party/mbedtls/library/block_cipher.c', + 'third-party/mbedtls/library/camellia.c', + 'third-party/mbedtls/library/ccm.c', + 'third-party/mbedtls/library/chacha20.c', + 'third-party/mbedtls/library/chachapoly.c', + 'third-party/mbedtls/library/cipher.c', + 'third-party/mbedtls/library/cipher_wrap.c', + 'third-party/mbedtls/library/cmac.c', + 'third-party/mbedtls/library/constant_time.c', + 'third-party/mbedtls/library/ctr_drbg.c', + 'third-party/mbedtls/library/debug.c', + 'third-party/mbedtls/library/des.c', + 'third-party/mbedtls/library/dhm.c', + 'third-party/mbedtls/library/ecdh.c', + 'third-party/mbedtls/library/ecdsa.c', + 'third-party/mbedtls/library/ecjpake.c', + 'third-party/mbedtls/library/ecp.c', + 'third-party/mbedtls/library/ecp_curves.c', + 'third-party/mbedtls/library/ecp_curves_new.c', + 'third-party/mbedtls/library/entropy.c', + 'third-party/mbedtls/library/entropy_poll.c', + 'third-party/mbedtls/library/error.c', + 'third-party/mbedtls/library/gcm.c', + 'third-party/mbedtls/library/hkdf.c', + 'third-party/mbedtls/library/hmac_drbg.c', + 'third-party/mbedtls/library/lmots.c', + 'third-party/mbedtls/library/lms.c', + 'third-party/mbedtls/library/md.c', + 'third-party/mbedtls/library/md5.c', + 'third-party/mbedtls/library/memory_buffer_alloc.c', + 'third-party/mbedtls/library/mps_reader.c', + 'third-party/mbedtls/library/mps_trace.c', + 'third-party/mbedtls/library/net_sockets.c', + 'third-party/mbedtls/library/nist_kw.c', + 'third-party/mbedtls/library/oid.c', + 'third-party/mbedtls/library/padlock.c', + 'third-party/mbedtls/library/pem.c', + 'third-party/mbedtls/library/pk.c', + 'third-party/mbedtls/library/pk_ecc.c', + 'third-party/mbedtls/library/pk_wrap.c', + 'third-party/mbedtls/library/pkcs12.c', + 'third-party/mbedtls/library/pkcs5.c', + 'third-party/mbedtls/library/pkcs7.c', + 'third-party/mbedtls/library/pkparse.c', + 'third-party/mbedtls/library/pkwrite.c', + 'third-party/mbedtls/library/platform.c', + 'third-party/mbedtls/library/platform_util.c', + 'third-party/mbedtls/library/poly1305.c', + 'third-party/mbedtls/library/psa_crypto.c', + 'third-party/mbedtls/library/psa_crypto_aead.c', + 'third-party/mbedtls/library/psa_crypto_cipher.c', + 'third-party/mbedtls/library/psa_crypto_client.c', + 'third-party/mbedtls/library/psa_crypto_driver_wrappers_no_static.c', + 'third-party/mbedtls/library/psa_crypto_ecp.c', + 'third-party/mbedtls/library/psa_crypto_ffdh.c', + 'third-party/mbedtls/library/psa_crypto_hash.c', + 'third-party/mbedtls/library/psa_crypto_mac.c', + 'third-party/mbedtls/library/psa_crypto_pake.c', + 'third-party/mbedtls/library/psa_crypto_rsa.c', + 'third-party/mbedtls/library/psa_crypto_se.c', + 'third-party/mbedtls/library/psa_crypto_slot_management.c', + 'third-party/mbedtls/library/psa_crypto_storage.c', + 'third-party/mbedtls/library/psa_its_file.c', + 'third-party/mbedtls/library/psa_util.c', + 'third-party/mbedtls/library/ripemd160.c', + 'third-party/mbedtls/library/rsa.c', + 'third-party/mbedtls/library/rsa_alt_helpers.c', + 'third-party/mbedtls/library/sha1.c', + 'third-party/mbedtls/library/sha256.c', + 'third-party/mbedtls/library/sha3.c', + 'third-party/mbedtls/library/sha512.c', + 'third-party/mbedtls/library/ssl_cache.c', + 'third-party/mbedtls/library/ssl_ciphersuites.c', + 'third-party/mbedtls/library/ssl_client.c', + 'third-party/mbedtls/library/ssl_cookie.c', + 'third-party/mbedtls/library/ssl_debug_helpers_generated.c', + 'third-party/mbedtls/library/ssl_msg.c', + 'third-party/mbedtls/library/ssl_ticket.c', + 'third-party/mbedtls/library/ssl_tls.c', + 'third-party/mbedtls/library/ssl_tls12_client.c', + 'third-party/mbedtls/library/ssl_tls12_server.c', + 'third-party/mbedtls/library/ssl_tls13_client.c', + 'third-party/mbedtls/library/ssl_tls13_generic.c', + 'third-party/mbedtls/library/ssl_tls13_keys.c', + 'third-party/mbedtls/library/ssl_tls13_server.c', + 'third-party/mbedtls/library/threading.c', + 'third-party/mbedtls/library/timing.c', + 'third-party/mbedtls/library/version.c', + 'third-party/mbedtls/library/version_features.c', + 'third-party/mbedtls/library/x509.c', + 'third-party/mbedtls/library/x509_create.c', + 'third-party/mbedtls/library/x509_crl.c', + 'third-party/mbedtls/library/x509_crt.c', + 'third-party/mbedtls/library/x509_csr.c', + 'third-party/mbedtls/library/x509write.c', + 'third-party/mbedtls/library/x509write_crt.c', + 'third-party/mbedtls/library/x509write_csr.c', + ] + }, + 'targets': [ + { + 'target_name': 'liblief', + 'toolsets': ['host', 'target'], + 'type': 'static_library', + 'includes': [], + 'include_dirs': [ + '.', + #include + #include + 'include', + 'src', + # Extracted third-party (checked into source by update script) + #include "mbedtls/private_access.h" + 'third-party/mbedtls/include', + 'third-party/mbedtls/library', + #include "spdlog/fmt/..." + 'third-party/spdlog/include', + #include + 'third-party/frozen/include', + #include + "include/LIEF/third-party", + #include "utf8/unchecked.h" + "include/LIEF/third-party/internal", + ], + 'direct_dependent_settings': { + 'include_dirs': [ 'include' ], + }, + 'defines': [ + 'LIEF_STATIC', + 'testtttt' + 'MBEDTLS_CONFIG_FILE="config/mbedtls/config.h"', + 'MBEDTLS_NO_PLATFORM_ENTROPY', + 'SPDLOG_DISABLE_DEFAULT_LOGGER', + 'SPDLOG_NO_EXCEPTIONS', + 'SPDLOG_FUNCTION=""', + '_GLIBCXX_USE_CXX11_ABI=1', + ], + 'cflags': [ + '-fPIC' + ], + # We need c++17 to compile without std::format and avoid conflicts with spdlog. + 'msvs_settings': { + 'VCCLCompilerTool': { + 'LanguageStandard': 'stdcpp17', + }, + }, + 'cflags_cc': [ + '-std=gnu++17', + '-fPIC', + '-fvisibility=hidden', + '-fvisibility-inlines-hidden', + '-Wall', + '-Wextra', + '-Wpedantic', + '-Wno-expansion-to-defined', + '-Werror=return-type', + '-fno-exceptions' + ], + 'xcode_settings': { + 'OTHER_CPLUSPLUSFLAGS': [ + '-std=gnu++17', + '-fPIC', + '-fvisibility=hidden', + '-fvisibility-inlines-hidden', + '-Wall', + '-Wextra', + '-Wpedantic', + '-Wno-expansion-to-defined', + '-Werror=return-type', + '-fno-exceptions' + ], + }, + 'sources': [ + '<@(lief_sources)', + '<@(lief_third_party_sources)', + ], + } + ] +} diff --git a/doc/api/cli.md b/doc/api/cli.md index f05686608297e5..f31963a9d5cb94 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -363,6 +363,23 @@ Error: Access to this API has been restricted } ``` +### `--build-sea=config` + + + +> Stability: 1.1 - Active development + +Generates a [single executable application][] from a JSON +configuration file. The argument must be a path to the configuration file. If +the path is not absolute, it is resolved relative to the current working +directory. + +For configuration fields, cross-platform notes, and asset APIs, see +the [single executable application][] documentation. + ### `--build-snapshot` + +When using `"mainFormat": "module"`, `import()` can be used to dynamically +load built-in modules. Attempting to use `import()` to load modules from +the file system will throw an error. + ### Using native addons in the injected main script Native addons can be bundled as assets into the single-executable application @@ -597,6 +639,7 @@ start a discussion at to help us document them. [CommonJS]: modules.md#modules-commonjs-modules +[ECMAScript Modules]: esm.md#modules-ecmascript-modules [ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format [Generating single executable preparation blobs]: #generating-single-executable-preparation-blobs [Mach-O]: https://en.wikipedia.org/wiki/Mach-O diff --git a/src/node_sea.cc b/src/node_sea.cc index bffdc72d1d1791..85f5eb118c3845 100644 --- a/src/node_sea.cc +++ b/src/node_sea.cc @@ -84,6 +84,11 @@ size_t SeaSerializer::Write(const SeaResource& sea) { static_cast(sea.exec_argv_extension)); written_total += WriteArithmetic(static_cast(sea.exec_argv_extension)); + + Debug("Write SEA main code format %u\n", + static_cast(sea.main_code_format)); + written_total += + WriteArithmetic(static_cast(sea.main_code_format)); DCHECK_EQ(written_total, SeaResource::kHeaderSize); Debug("Write SEA code path %p, size=%zu\n", @@ -161,6 +166,11 @@ SeaResource SeaDeserializer::Read() { SeaExecArgvExtension exec_argv_extension = static_cast(extension_value); Debug("Read SEA resource exec argv extension %u\n", extension_value); + + uint8_t format_value = ReadArithmetic(); + CHECK_LE(format_value, static_cast(ModuleFormat::kModule)); + ModuleFormat main_code_format = static_cast(format_value); + Debug("Read SEA main code format %u\n", format_value); CHECK_EQ(read_total, SeaResource::kHeaderSize); std::string_view code_path = @@ -219,6 +229,7 @@ SeaResource SeaDeserializer::Read() { exec_argv_extension, code_path, code, + main_code_format, code_cache, assets, exec_argv}; @@ -501,6 +512,25 @@ std::optional ParseSingleExecutableConfig( config_path); return std::nullopt; } + } else if (key == "mainFormat") { + std::string_view format_str; + if (field.value().get_string().get(format_str)) { + FPrintF(stderr, + "\"mainFormat\" field of %s is not a string\n", + config_path); + return std::nullopt; + } + if (format_str == "commonjs") { + result.main_format = ModuleFormat::kCommonJS; + } else if (format_str == "module") { + result.main_format = ModuleFormat::kModule; + } else { + FPrintF(stderr, + "\"mainFormat\" field of %s must be one of " + "\"commonjs\" or \"module\"\n", + config_path); + return std::nullopt; + } } } @@ -512,6 +542,23 @@ std::optional ParseSingleExecutableConfig( "\"useCodeCache\" is redundant when \"useSnapshot\" is true\n"); } + // TODO(joyeecheung): support ESM with useSnapshot and useCodeCache. + if (result.main_format == ModuleFormat::kModule && + static_cast(result.flags & SeaFlags::kUseSnapshot)) { + FPrintF(stderr, + "\"mainFormat\": \"module\" is not supported when " + "\"useSnapshot\" is true\n"); + return std::nullopt; + } + + if (result.main_format == ModuleFormat::kModule && + static_cast(result.flags & SeaFlags::kUseCodeCache)) { + FPrintF(stderr, + "\"mainFormat\": \"module\" is not supported when " + "\"useCodeCache\" is true\n"); + return std::nullopt; + } + if (result.main_path.empty()) { FPrintF(stderr, "\"main\" field of %s is not a non-empty string\n", @@ -709,6 +756,7 @@ ExitCode GenerateSingleExecutableBlob( builds_snapshot_from_main ? std::string_view{snapshot_blob.data(), snapshot_blob.size()} : std::string_view{main_script.data(), main_script.size()}, + config.main_format, optional_sv_code_cache, assets_view, exec_argv_view}; @@ -792,20 +840,25 @@ void GetAssetKeys(const FunctionCallbackInfo& args) { } MaybeLocal LoadSingleExecutableApplication( - const StartExecutionCallbackInfo& info) { + const StartExecutionCallbackInfoWithModule& info) { // Here we are currently relying on the fact that in NodeMainInstance::Run(), // env->context() is entered. - Local context = Isolate::GetCurrent()->GetCurrentContext(); - Environment* env = Environment::GetCurrent(context); + Environment* env = info.env(); + Local context = env->context(); SeaResource sea = FindSingleExecutableResource(); CHECK(!sea.use_snapshot()); // TODO(joyeecheung): this should be an external string. Refactor UnionBytes // and make it easy to create one based on static content on the fly. Local main_script = - ToV8Value(env->context(), sea.main_code_or_snapshot).ToLocalChecked(); - return info.run_cjs->Call( - env->context(), Null(env->isolate()), 1, &main_script); + ToV8Value(context, sea.main_code_or_snapshot).ToLocalChecked(); + Local kind = + v8::Integer::New(env->isolate(), static_cast(sea.main_code_format)); + Local resource_name = + ToV8Value(context, env->exec_path()).ToLocalChecked(); + Local args[] = {main_script, kind, resource_name}; + return info.run_module()->Call( + env->context(), Null(env->isolate()), arraysize(args), args); } bool MaybeLoadSingleExecutableApplication(Environment* env) { @@ -821,7 +874,7 @@ bool MaybeLoadSingleExecutableApplication(Environment* env) { // this check is just here to guard against the unlikely case where // the SEA preparation blob has been manually modified by someone. CHECK(!env->snapshot_deserialize_main().IsEmpty()); - LoadEnvironment(env, StartExecutionCallback{}); + LoadEnvironment(env, StartExecutionCallbackWithModule{}); return true; } diff --git a/src/node_sea.h b/src/node_sea.h index 34596972b60219..dd0b89db841eed 100644 --- a/src/node_sea.h +++ b/src/node_sea.h @@ -11,6 +11,7 @@ #include #include +#include "node.h" #include "node_exit_code.h" namespace node { @@ -43,6 +44,7 @@ struct SeaConfig { std::string executable_path; SeaFlags flags = SeaFlags::kDefault; SeaExecArgvExtension exec_argv_extension = SeaExecArgvExtension::kEnv; + ModuleFormat main_format = ModuleFormat::kCommonJS; std::unordered_map assets; std::vector exec_argv; }; @@ -52,6 +54,7 @@ struct SeaResource { SeaExecArgvExtension exec_argv_extension = SeaExecArgvExtension::kEnv; std::string_view code_path; std::string_view main_code_or_snapshot; + ModuleFormat main_code_format = ModuleFormat::kCommonJS; std::optional code_cache; std::unordered_map assets; std::vector exec_argv; @@ -59,8 +62,9 @@ struct SeaResource { bool use_snapshot() const; bool use_code_cache() const; - static constexpr size_t kHeaderSize = - sizeof(kMagic) + sizeof(SeaFlags) + sizeof(SeaExecArgvExtension); + static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags) + + sizeof(SeaExecArgvExtension) + + sizeof(ModuleFormat); }; bool IsSingleExecutable(); diff --git a/test/fixtures/sea/esm/sea-config.json b/test/fixtures/sea/esm/sea-config.json new file mode 100644 index 00000000000000..e5ee27ff7f4c85 --- /dev/null +++ b/test/fixtures/sea/esm/sea-config.json @@ -0,0 +1,6 @@ +{ + "main": "sea.mjs", + "output": "sea", + "mainFormat": "module", + "disableExperimentalSEAWarning": true +} diff --git a/test/fixtures/sea/esm/sea.mjs b/test/fixtures/sea/esm/sea.mjs new file mode 100644 index 00000000000000..c8c9fe0ca1d571 --- /dev/null +++ b/test/fixtures/sea/esm/sea.mjs @@ -0,0 +1,24 @@ +import assert from 'node:assert'; +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; +import { dirname } from 'node:path'; + +// Test createRequire with process.execPath. +const assert2 = createRequire(process.execPath)('node:assert'); +assert.strictEqual(assert2.strict, assert.strict); + +// Test import.meta properties. This should be in sync with the CommonJS entry +// point's corresponding values. +assert.strictEqual(import.meta.url, pathToFileURL(process.execPath).href); +assert.strictEqual(import.meta.filename, process.execPath); +assert.strictEqual(import.meta.dirname, dirname(process.execPath)); +assert.strictEqual(import.meta.main, true); +// TODO(joyeecheung): support import.meta.resolve when we also support +// require.resolve in CommonJS entry points, the behavior of the two +// should be in sync. + +// Test import() with a built-in module. +const { strict } = await import('node:assert'); +assert.strictEqual(strict, assert.strict); + +console.log('ESM SEA executed successfully'); diff --git a/test/sea/test-single-executable-application-esm.js b/test/sea/test-single-executable-application-esm.js new file mode 100644 index 00000000000000..9f7366cb0e2405 --- /dev/null +++ b/test/sea/test-single-executable-application-esm.js @@ -0,0 +1,33 @@ +'use strict'; + +require('../common'); + +const { + buildSEA, + skipIfBuildSEAIsNotSupported, +} = require('../common/sea'); + +skipIfBuildSEAIsNotSupported(); + +// This tests the creation of a single executable application with an ESM +// entry point using the "mainFormat": "module" configuration. + +const tmpdir = require('../common/tmpdir'); +const fixtures = require('../common/fixtures'); +const { spawnSyncAndExitWithoutError } = require('../common/child_process'); + +tmpdir.refresh(); + +const outputFile = buildSEA(fixtures.path('sea', 'esm')); + +spawnSyncAndExitWithoutError( + outputFile, + { + env: { + NODE_DEBUG_NATIVE: 'SEA', + ...process.env, + }, + }, + { + stdout: /ESM SEA executed successfully/, + }); From fb276ad20ed0762ae75f128b3862d298b3b03aa7 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 10 Mar 2026 11:49:32 -0700 Subject: [PATCH 6/7] src: support import() and import.meta in embedder-run modules This adds a embedder_module_hdo for identifying embedder-run modules in the dynamic import handler and import.meta initializer, and a SourceTextModuleTypes for customizing source text module compilation in the JS land via compileSourceTextModule(). Also, refactors the existing embedder module compilation code to reuse the builtin resolution logic. PR-URL: https://github.com/nodejs/node/pull/61654 Reviewed-By: Chengzhong Wu Reviewed-By: Aditi Singh --- lib/internal/main/embedding.js | 60 ++++--------------------------- lib/internal/modules/esm/utils.js | 42 +++++++++++++++++++++- lib/internal/modules/helpers.js | 39 ++++++++++++++++++++ src/env_properties.h | 1 + 4 files changed, 88 insertions(+), 54 deletions(-) diff --git a/lib/internal/main/embedding.js b/lib/internal/main/embedding.js index 91a12f755e6abc..fd0cc810363c2a 100644 --- a/lib/internal/main/embedding.js +++ b/lib/internal/main/embedding.js @@ -15,16 +15,12 @@ const { const { isExperimentalSeaWarningNeeded, isSea } = internalBinding('sea'); const { emitExperimentalWarning } = require('internal/util'); const { emitWarningSync } = require('internal/process/warning'); -const { BuiltinModule } = require('internal/bootstrap/realm'); -const { normalizeRequirableId } = BuiltinModule; const { Module } = require('internal/modules/cjs/loader'); const { compileFunctionForCJSLoader } = internalBinding('contextify'); const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache'); -const { codes: { - ERR_UNKNOWN_BUILTIN_MODULE, -} } = require('internal/errors'); const { pathToFileURL } = require('internal/url'); -const { loadBuiltinModule } = require('internal/modules/helpers'); +const { loadBuiltinModuleForEmbedder } = require('internal/modules/helpers'); +const { compileSourceTextModuleForEmbedder } = require('internal/modules/esm/utils'); const { moduleFormats } = internalBinding('modules'); const assert = require('internal/assert'); const path = require('path'); @@ -34,7 +30,6 @@ const path = require('path'); prepareMainThreadExecution(false, true); const isLoadingSea = isSea(); -const isBuiltinWarningNeeded = isLoadingSea && isExperimentalSeaWarningNeeded(); if (isExperimentalSeaWarningNeeded()) { emitExperimentalWarning('Single executable application'); } @@ -103,28 +98,8 @@ function embedderRunCjs(content, filename) { ); } -let warnedAboutBuiltins = false; -function warnNonBuiltinInSEA() { - if (isBuiltinWarningNeeded && !warnedAboutBuiltins) { - emitWarningSync( - 'Currently the require() provided to the main script embedded into ' + - 'single-executable applications only supports loading built-in modules.\n' + - 'To load a module from disk after the single executable application is ' + - 'launched, use require("module").createRequire().\n' + - 'Support for bundled module loading or virtual file systems are under ' + - 'discussions in https://github.com/nodejs/single-executable'); - warnedAboutBuiltins = true; - } -} - function embedderRequire(id) { - const normalizedId = normalizeRequirableId(id); - - if (!normalizedId) { - warnNonBuiltinInSEA(); - throw new ERR_UNKNOWN_BUILTIN_MODULE(id); - } - return require(normalizedId); + return loadBuiltinModuleForEmbedder(id).exports; } function embedderRunESM(content, filename) { @@ -134,31 +109,10 @@ function embedderRunESM(content, filename) { } else { resourceName = filename; } - const { compileSourceTextModule } = require('internal/modules/esm/utils'); - // TODO(joyeecheung): support code cache, dynamic import() and import.meta. - const wrap = compileSourceTextModule(resourceName, content); - // Cache the source map for the module if present. - if (wrap.sourceMapURL) { - maybeCacheSourceMap(resourceName, content, wrap, false, undefined, wrap.sourceMapURL); - } - const requests = wrap.getModuleRequests(); - const modules = []; - for (let i = 0; i < requests.length; ++i) { - const { specifier } = requests[i]; - const normalizedId = normalizeRequirableId(specifier); - if (!normalizedId) { - warnNonBuiltinInSEA(); - throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier); - } - const mod = loadBuiltinModule(normalizedId); - if (!mod) { - throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier); - } - modules.push(mod.getESMFacade()); - } - wrap.link(modules); - wrap.instantiate(); - wrap.evaluate(-1, false); + // TODO(joyeecheung): allow configuration from node::ModuleData, + // either via a more generic context object, or something like import.meta extensions. + const context = { isMain: true, __proto__: null }; + const wrap = compileSourceTextModuleForEmbedder(resourceName, content, context); // TODO(joyeecheung): we may want to return the v8::Module via a vm.SourceTextModule // when vm.SourceTextModule stablizes, or put it in an out parameter. diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js index 0af25ebbf6c3f2..aa8e1c1b7e25e2 100644 --- a/lib/internal/modules/esm/utils.js +++ b/lib/internal/modules/esm/utils.js @@ -14,6 +14,7 @@ const { }, } = internalBinding('util'); const { + embedder_module_hdo, source_text_module_default_hdo, vm_dynamic_import_default_internal, vm_dynamic_import_main_context_default, @@ -43,6 +44,7 @@ const { const assert = require('internal/assert'); const { normalizeReferrerURL, + loadBuiltinModuleForEmbedder, } = require('internal/modules/helpers'); let defaultConditions; @@ -200,7 +202,8 @@ function defaultInitializeImportMetaForModule(meta, wrap) { * @param {ModuleWrap} wrap - The ModuleWrap of the SourceTextModule where `import.meta` is referenced. */ function initializeImportMetaObject(symbol, meta, wrap) { - if (symbol === source_text_module_default_hdo) { + if (symbol === source_text_module_default_hdo || + symbol === embedder_module_hdo) { defaultInitializeImportMetaForModule(meta, wrap); return; } @@ -266,6 +269,10 @@ async function importModuleDynamicallyCallback(referrerSymbol, specifier, phase, if (referrerSymbol === source_text_module_default_hdo) { return defaultImportModuleDynamicallyForModule(specifier, phase, attributes, referrerName); } + // For embedder entry point ESM, only allow built-in modules. + if (referrerSymbol === embedder_module_hdo) { + return loadBuiltinModuleForEmbedder(specifier).getESMFacade().getNamespace(); + } if (moduleRegistries.has(referrerSymbol)) { const { importModuleDynamically, callbackReferrer } = moduleRegistries.get(referrerSymbol); @@ -334,6 +341,37 @@ function compileSourceTextModule(url, source, cascadedLoader, context = kEmptyOb } +/** + * Compile, link, instantiate and evaluate a SourceTextModule for embedder ESM entry point. + * This resolves only built-in modules and uses the embedder_module_hdo for import.meta + * and dynamic import() support. + * @param {string} url URL of the module. + * @param {string} source Source code of the module. + * @param {{ isMain?: boolean }|undefined} context - context object containing module metadata. + * @returns {ModuleWrap} + */ +function compileSourceTextModuleForEmbedder(url, source, context = kEmptyObject) { + const wrap = new ModuleWrap(url, undefined, source, 0, 0, embedder_module_hdo); + + const { isMain } = context; + if (isMain) { + wrap.isMain = true; + } + + // Cache the source map for the module if present. + if (wrap.sourceMapURL) { + maybeCacheSourceMap(url, source, wrap, false, wrap.sourceURL, wrap.sourceMapURL); + } + + // For embedder ESM, handle linking and evaluation. + const requests = wrap.getModuleRequests(); + const modules = requests.map(({ specifier }) => loadBuiltinModuleForEmbedder(specifier).getESMFacade()); + wrap.link(modules); + wrap.instantiate(); + wrap.evaluate(-1, false); + return wrap; +} + const kImportInImportedESM = Symbol('kImportInImportedESM'); const kImportInRequiredESM = Symbol('kImportInRequiredESM'); const kRequireInImportedCJS = Symbol('kRequireInImportedCJS'); @@ -344,11 +382,13 @@ const kRequireInImportedCJS = Symbol('kRequireInImportedCJS'); const requestTypes = { kImportInImportedESM, kImportInRequiredESM, kRequireInImportedCJS }; module.exports = { + embedder_module_hdo, registerModule, initializeESM, getDefaultConditions, getConditionsSet, shouldSpawnLoaderHookWorker, compileSourceTextModule, + compileSourceTextModuleForEmbedder, requestTypes, }; diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index e2cdc0c5bba74b..209e6f800cd564 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -15,6 +15,7 @@ const { const { ERR_INVALID_ARG_TYPE, ERR_INVALID_RETURN_PROPERTY_VALUE, + ERR_UNKNOWN_BUILTIN_MODULE, } = require('internal/errors').codes; const { BuiltinModule } = require('internal/bootstrap/realm'); @@ -126,6 +127,43 @@ function loadBuiltinModule(id) { return mod; } +let isSEABuiltinWarningNeeded_; +function isSEABuiltinWarningNeeded() { + if (isSEABuiltinWarningNeeded_ === undefined) { + const { isExperimentalSeaWarningNeeded, isSea } = internalBinding('sea'); + isSEABuiltinWarningNeeded_ = isSea() && isExperimentalSeaWarningNeeded(); + } + return isSEABuiltinWarningNeeded_; +} + +let warnedAboutBuiltins = false; +/** + * Load a built-in module for embedder/SEA modules. + * @param {string} id + * @returns {import('internal/bootstrap/realm.js').BuiltinModule} + */ +function loadBuiltinModuleForEmbedder(id) { + const normalized = BuiltinModule.normalizeRequirableId(id); + if (normalized) { + const mod = loadBuiltinModule(normalized); + if (mod) { + return mod; + } + } + if (isSEABuiltinWarningNeeded() && !warnedAboutBuiltins) { + const { emitWarningSync } = require('internal/process/warning'); + emitWarningSync( + 'Currently the require() provided to the main script embedded into ' + + 'single-executable applications only supports loading built-in modules.\n' + + 'To load a module from disk after the single executable application is ' + + 'launched, use require("module").createRequire().\n' + + 'Support for bundled module loading or virtual file systems are under ' + + 'discussions in https://github.com/nodejs/single-executable'); + warnedAboutBuiltins = true; + } + throw new ERR_UNKNOWN_BUILTIN_MODULE(id); +} + /** @type {Module} */ let $Module = null; /** @@ -470,6 +508,7 @@ module.exports = { getCompileCacheDir, initializeCjsConditions, loadBuiltinModule, + loadBuiltinModuleForEmbedder, makeRequireFunction, normalizeReferrerURL, stringify, diff --git a/src/env_properties.h b/src/env_properties.h index 454750db0113d2..b0854f22962ca2 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -57,6 +57,7 @@ V(onpskexchange_symbol, "onpskexchange") \ V(resource_symbol, "resource_symbol") \ V(trigger_async_id_symbol, "trigger_async_id_symbol") \ + V(embedder_module_hdo, "embedder_module_hdo") \ V(source_text_module_default_hdo, "source_text_module_default_hdo") \ V(vm_context_no_contextify, "vm_context_no_contextify") \ V(vm_dynamic_import_default_internal, "vm_dynamic_import_default_internal") \ From 99b98d713c397e10c98be2c4d970164ccd41cf14 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 26 Jan 2026 16:08:45 +0100 Subject: [PATCH 7/7] build: add `--shared-lief` configure flag PR-URL: https://github.com/nodejs/node/pull/61536 Reviewed-By: Chengzhong Wu Reviewed-By: Colin Ihrig Reviewed-By: Joyee Cheung --- configure.py | 31 +++++++++++++++++++++++++++++++ node.gyp | 6 +++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/configure.py b/configure.py index 9073b170899cac..e52cb28fa0cf5c 100755 --- a/configure.py +++ b/configure.py @@ -349,6 +349,28 @@ dest='shared_libuv_libpath', help='a directory to search for the shared libuv DLL') +shared_optgroup.add_argument('--shared-lief', + action='store_true', + dest='shared_lief', + default=None, + help='link to a shared lief DLL instead of static linking') + +shared_optgroup.add_argument('--shared-lief-includes', + action='store', + dest='shared_lief_includes', + help='directory containing lief header files') + +shared_optgroup.add_argument('--shared-lief-libname', + action='store', + dest='shared_lief_libname', + default='LIEF', + help='alternative lib name to link to [default: %(default)s]') + +shared_optgroup.add_argument('--shared-lief-libpath', + action='store', + dest='shared_lief_libpath', + help='a directory to search for the shared lief DLL') + shared_optgroup.add_argument('--shared-nbytes', action='store_true', dest='shared_nbytes', @@ -1947,6 +1969,14 @@ def without_ssl_error(option): configure_library('openssl', o) +def configure_lief(o): + if options.without_lief: + if options.shared_lief: + error('--without-lief is incompatible with --shared-lief') + return + + configure_library('lief', o, pkgname='LIEF') + def configure_sqlite(o): o['variables']['node_use_sqlite'] = b(not options.without_sqlite) if options.without_sqlite: @@ -2404,6 +2434,7 @@ def make_bin_override(): configure_library('nghttp2', output, pkgname='libnghttp2') configure_library('nghttp3', output, pkgname='libnghttp3') configure_library('ngtcp2', output, pkgname='libngtcp2') +configure_lief(output); configure_sqlite(output); configure_library('uvwasi', output) configure_library('zstd', output, pkgname='libzstd') diff --git a/node.gyp b/node.gyp index 12a385c7f207cf..4dc42995d921c8 100644 --- a/node.gyp +++ b/node.gyp @@ -18,6 +18,7 @@ 'node_shared_hdr_histogram%': 'false', 'node_shared_http_parser%': 'false', 'node_shared_libuv%': 'false', + 'node_shared_lief%': 'false', 'node_shared_merve%': 'false', 'node_shared_nbytes%': 'false', 'node_shared_nghttp2%': 'false', @@ -996,10 +997,13 @@ '<@(node_quic_sources)', ], }], - [ 'node_use_lief=="true"', { + [ 'node_use_lief=="true" and node_shared_lief=="false"', { 'defines': [ 'HAVE_LIEF=1' ], 'dependencies': [ 'deps/LIEF/lief.gyp:liblief' ], }], + [ 'node_use_lief=="true" and node_shared_lief=="true"', { + 'defines': [ 'HAVE_LIEF=1' ], + }], [ 'node_use_sqlite=="true"', { 'sources': [ '<@(node_sqlite_sources)',