diff --git a/libs/estdlib/src/init.erl b/libs/estdlib/src/init.erl index 73a8031b32..772407bade 100644 --- a/libs/estdlib/src/init.erl +++ b/libs/estdlib/src/init.erl @@ -39,7 +39,36 @@ %% @doc Entry point. %% @end %%----------------------------------------------------------------------------- --spec boot([binary() | atom()]) -> any(). +-spec boot([binary() | atom() | string()]) -> any(). +boot([<<"-s">>, escript, <<"--">>, _Filename | Args]) -> + % Escript mode: check for start beam and main/1 function before starting kernel + case atomvm:get_start_beam(escript) of + {ok, ModuleNameBinary} -> + % Remove .beam suffix if present (check last 5 bytes) + Size = byte_size(ModuleNameBinary), + ModuleName = + if + Size > 5 -> + case binary:part(ModuleNameBinary, Size - 5, 5) of + <<".beam">> -> binary:part(ModuleNameBinary, 0, Size - 5); + _ -> ModuleNameBinary + end; + true -> + ModuleNameBinary + end, + Module = binary_to_atom(ModuleName), + case erlang:function_exported(Module, main, 1) of + true -> + {ok, _KernelPid} = kernel:start(boot, []), + Module:main(Args); + false -> + io:format("Function ~s:main/1 is not exported~n", [Module]), + error + end; + _ -> + io:format("start_beam not found~n"), + error + end; boot([<<"-s">>, StartupModule]) when is_atom(StartupModule) -> % Until we have boot scripts, we just start kernel application. {ok, _KernelPid} = kernel:start(boot, []), diff --git a/src/libAtomVM/globalcontext.c b/src/libAtomVM/globalcontext.c index ad1a2d0287..3f317af6ed 100644 --- a/src/libAtomVM/globalcontext.c +++ b/src/libAtomVM/globalcontext.c @@ -29,6 +29,7 @@ #include "context.h" #include "defaultatoms.h" #include "erl_nif_priv.h" +#include "interop.h" #include "list.h" #include "mailbox.h" #include "posix_nifs.h" @@ -720,21 +721,62 @@ Module *globalcontext_get_module_by_index(GlobalContext *global, int index) return result; } -run_result_t globalcontext_run(GlobalContext *glb, Module *startup_module, FILE *out_f) +run_result_t globalcontext_run(GlobalContext *glb, Module *startup_module, FILE *out_f, int argc, char **argv) { Context *ctx = context_new(glb); ctx->leader = 1; Module *init_module = globalcontext_get_module(glb, INIT_ATOM_INDEX); if (IS_NULL_PTR(init_module)) { + if (IS_NULL_PTR(startup_module)) { + fprintf(stderr, "Unable to locate entrypoint.\n"); + return RUN_NO_ENTRY_POINT; + } context_execute_loop(ctx, startup_module, "start", 0); } else { - if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(2) + LIST_SIZE(2, 0)) != MEMORY_GC_OK)) { - fprintf(stderr, "Unable to allocate arguments.\n"); - return RUN_MEMORY_FAILURE; + // Build boot arguments based on whether we're in embedded mode + if (argc > 0 && argv != NULL) { + // Embedded mode: ["-s", escript, "--" | argv_strings] + // Calculate heap size needed + size_t heap_needed = term_binary_heap_size(2) + // "-s" + term_binary_heap_size(2) + // "--" + LIST_SIZE(3, 0); // list for ["-s", escript, "--"] + + // Calculate space for argv strings + for (int i = 0; i < argc; i++) { + size_t arg_len = strlen(argv[i]); + heap_needed += CONS_SIZE * arg_len + LIST_SIZE(1, 0); + } + + if (UNLIKELY(memory_ensure_free(ctx, heap_needed) != MEMORY_GC_OK)) { + fprintf(stderr, "Unable to allocate arguments.\n"); + return RUN_MEMORY_FAILURE; + } + + // Build the argv list in reverse: [argvn, ..., argv0, "--", escript, "-s"] + term args_list = term_nil(); + for (int i = argc - 1; i >= 0; i--) { + term arg = interop_chars_to_list(argv[i], strlen(argv[i]), &ctx->heap); + args_list = term_list_prepend(arg, args_list, &ctx->heap); + } + + term separator = term_from_literal_binary("--", strlen("--"), &ctx->heap, glb); + args_list = term_list_prepend(separator, args_list, &ctx->heap); + + term escript_atom = globalcontext_make_atom(glb, ATOM_STR("\x7", "escript")); + args_list = term_list_prepend(escript_atom, args_list, &ctx->heap); + + term s_opt = term_from_literal_binary("-s", strlen("-s"), &ctx->heap, glb); + ctx->x[0] = term_list_prepend(s_opt, args_list, &ctx->heap); + } else { + // Non-embedded mode: ["-s", startup_module] + if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(2) + LIST_SIZE(2, 0)) != MEMORY_GC_OK)) { + fprintf(stderr, "Unable to allocate arguments.\n"); + return RUN_MEMORY_FAILURE; + } + term s_opt = term_from_literal_binary("-s", strlen("-s"), &ctx->heap, glb); + term list = term_list_prepend(module_get_name(startup_module), term_nil(), &ctx->heap); + ctx->x[0] = term_list_prepend(s_opt, list, &ctx->heap); } - term s_opt = term_from_literal_binary("-s", strlen("-s"), &ctx->heap, glb); - term list = term_list_prepend(module_get_name(startup_module), term_nil(), &ctx->heap); - ctx->x[0] = term_list_prepend(s_opt, list, &ctx->heap); context_execute_loop(ctx, init_module, "boot", 1); } diff --git a/src/libAtomVM/globalcontext.h b/src/libAtomVM/globalcontext.h index 3501195c18..6d5a9433af 100644 --- a/src/libAtomVM/globalcontext.h +++ b/src/libAtomVM/globalcontext.h @@ -95,6 +95,7 @@ typedef enum run_result_t RUN_SUCCESS = 0, RUN_MEMORY_FAILURE = 1, RUN_RESULT_NOT_OK = 2, + RUN_NO_ENTRY_POINT = 3, } run_result_t; struct GlobalContext @@ -530,9 +531,11 @@ Module *globalcontext_load_module_from_avm(GlobalContext *global, const char *mo * @param global the global context * @param start_module the start module * @param out_f file to print the result to, or NULL + * @param argc number of command-line arguments (0 for non-embedded mode) + * @param argv command-line arguments (NULL for non-embedded mode) * @returns RUN_SUCCESS or an error code */ -run_result_t globalcontext_run(GlobalContext *global, Module *start_module, FILE *out_f); +run_result_t globalcontext_run(GlobalContext *global, Module *start_module, FILE *out_f, int argc, char **argv); #ifndef __cplusplus static inline uint64_t globalcontext_get_ref_ticks(GlobalContext *global) diff --git a/src/platforms/emscripten/src/main.c b/src/platforms/emscripten/src/main.c index 27e02c3a67..26ce01b7ce 100644 --- a/src/platforms/emscripten/src/main.c +++ b/src/platforms/emscripten/src/main.c @@ -96,7 +96,7 @@ static int start(void) return EXIT_FAILURE; } - run_result_t ret_value = globalcontext_run(global, main_module, stdout); + run_result_t ret_value = globalcontext_run(global, main_module, stdout, 0, NULL); int status; if (ret_value == RUN_SUCCESS) { diff --git a/src/platforms/esp32/main/main.c b/src/platforms/esp32/main/main.c index bc25c82c64..c40fb12ae9 100644 --- a/src/platforms/esp32/main/main.c +++ b/src/platforms/esp32/main/main.c @@ -123,7 +123,7 @@ void app_main() ESP_LOGI(TAG, "Starting %s...", startup_module_name); fprintf(stdout, "---\n"); - run_result_t result = globalcontext_run(glb, mod, stdout); + run_result_t result = globalcontext_run(glb, mod, stdout, 0, NULL); bool reboot_on_not_ok = #if defined(CONFIG_REBOOT_ON_NOT_OK) diff --git a/src/platforms/generic_unix/main.c b/src/platforms/generic_unix/main.c index f45fd7f14f..4abcf86267 100644 --- a/src/platforms/generic_unix/main.c +++ b/src/platforms/generic_unix/main.c @@ -26,6 +26,16 @@ #include #include +#ifdef __APPLE__ +#include +#include +#elif defined(__linux__) +#include +#include +#include +#include +#endif + #include "atom.h" #include "avm_version.h" #include "avmpack.h" @@ -40,6 +50,38 @@ #include "term.h" #include "utils.h" +#ifdef __linux__ +// On Linux (using ELF), the embedded avm binary is added as a section +// and the following symbols are patched. +extern uint64_t __atomvm_avm_offset; +extern uint64_t __atomvm_avm_length; + +__asm__( + ".pushsection .atomvm_avm_info, \"a\", @progbits\n" + ".global __atomvm_avm_offset\n" + ".global __atomvm_avm_length\n" + "__atomvm_avm_offset:\n" + ".quad 0x0\n" // 8 bytes for offset + "__atomvm_avm_length:\n" + ".quad 0x0\n" // 8 bytes for length + ".popsection\n"); +#endif + +/** + * @brief Destructor for embedded AVM pack data + * @details Embedded AVM data is part of the executable and should not be freed + */ +static void embedded_avm_pack_destructor(struct AVMPackData *obj, GlobalContext *global) +{ + UNUSED(global); + // Embedded data is part of the executable, so we only free the pack structure itself + free(obj); +} + +static const struct AVMPackInfo embedded_avm_pack_info = { + .destructor = embedded_avm_pack_destructor +}; + void print_help(const char *program_name) { printf( @@ -62,92 +104,174 @@ void print_help(const char *program_name) program_name, program_name); } -int main(int argc, char **argv) +/** + * @brief Try to extract embedded AVM data from the executable itself + * @param data pointer to store the embedded AVM data (if found) + * @param size pointer to store the size of the embedded AVM data + * @return true if embedded AVM data was found, false otherwise + */ +bool get_embedded_avm(const void **data, size_t *size) { - int c; - while ((c = getopt(argc, argv, "hv")) != -1) { - switch (c) { - case 'h': - print_help(argv[0]); - return EXIT_SUCCESS; - - case 'v': - printf(ATOMVM_VERSION "\n"); - return EXIT_SUCCESS; - - default: - break; +#ifdef __APPLE__ + // On macOS, look for the __ATOMVM,__avm_data section + unsigned long section_size = 0; + const void *section_data = getsectiondata(&_mh_execute_header, "__ATOMVM", "__avm_data", §ion_size); + + if (section_data && section_size > 0) { + *data = section_data; + *size = (size_t) section_size; + return true; + } +#elif defined(__linux__) + // On Linux, check if symbols were added by objcopy during escriptize + if (__atomvm_avm_offset != 0 && __atomvm_avm_length != 0) { + int fd = open("/proc/self/exe", O_RDONLY); + if (fd == -1) { + fprintf(stderr, "Cannot open /proc/self/exe (errno = %d)\n", (int) errno); + return false; + } + long page_size = sysconf(_SC_PAGESIZE); + + // Round down the offset to the nearest page boundary + off_t page_aligned_offset = (__atomvm_avm_offset / page_size) * page_size; + + // Calculate the extra bytes needed to cover the target region + size_t extra = __atomvm_avm_offset - page_aligned_offset; + size_t map_length = __atomvm_avm_length + extra; + + void *map = mmap(NULL, map_length, PROT_READ, MAP_PRIVATE, fd, page_aligned_offset); + if (map == MAP_FAILED) { + fprintf(stderr, "Failed to mmap current executable (errno = %d)\n", (int) errno); + close(fd); + return false; } + *data = (const void *) ((const char *) map + extra); + *size = __atomvm_avm_length; + return true; } +#endif + + return false; +} + +int main(int argc, char **argv) +{ + // Check for embedded AVM data first + const void *embedded_data = NULL; + size_t embedded_size = 0; + bool has_embedded_avm = get_embedded_avm(&embedded_data, &embedded_size); + + // Only process getopt if not in embedded mode + // In embedded mode, all arguments (including -h, -v) are passed to the script + if (!has_embedded_avm) { + int c; + while ((c = getopt(argc, argv, "hv")) != -1) { + switch (c) { + case 'h': + print_help(argv[0]); + return EXIT_SUCCESS; + + case 'v': + printf(ATOMVM_VERSION "\n"); + return EXIT_SUCCESS; + + default: + break; + } + } - if (argc < 2) { - printf("Syntax Error! Missing .beam or .avm files.\n"); - print_help(argv[0]); - return EXIT_FAILURE; + if (argc < 2) { + printf("Syntax Error! Missing .beam or .avm files.\n"); + print_help(argv[0]); + return EXIT_FAILURE; + } } GlobalContext *glb = globalcontext_new(); Module *startup_module = NULL; - for (int i = 1; i < argc; ++i) { - const char *ext = strrchr(argv[i], '.'); - if (ext && strcmp(ext, ".avm") == 0) { - struct AVMPackData *avmpack_data; - if (UNLIKELY(sys_open_avm_from_file(glb, argv[i], &avmpack_data) != AVM_OPEN_OK)) { - fprintf(stderr, "Failed opening %s.\n", argv[i]); - return EXIT_FAILURE; - } - synclist_append(&glb->avmpack_data, &avmpack_data->avmpack_head); - - if (IS_NULL_PTR(startup_module)) { - const void *startup_beam = NULL; - const char *startup_module_name; - uint32_t startup_beam_size; - avmpack_find_section_by_flag(avmpack_data->data, 1, &startup_beam, &startup_beam_size, &startup_module_name); - - if (startup_beam) { - avmpack_data->in_use = true; - startup_module = module_new_from_iff_binary(glb, startup_beam, startup_beam_size); - if (IS_NULL_PTR(startup_module)) { - fprintf(stderr, "Cannot load startup module: %s\n", startup_module_name); - return EXIT_FAILURE; + if (has_embedded_avm) { + struct AVMPackData *avmpack_data = malloc(sizeof(struct AVMPackData)); + if (!avmpack_data) { + fprintf(stderr, "Failed to allocate memory for embedded AVM pack.\n"); + globalcontext_destroy(glb); + return EXIT_FAILURE; + } + + avmpack_data_init(avmpack_data, &embedded_avm_pack_info); + avmpack_data->data = embedded_data; + // Set the name for the embedded AVM pack so it can be found by atomvm:get_start_beam/1 + term escript_atom = globalcontext_make_atom(glb, ATOM_STR("\x7", "escript")); + avmpack_data->name_atom_id = term_to_atom_index(escript_atom); + + synclist_append(&glb->avmpack_data, &avmpack_data->avmpack_head); + } + + // Process command-line AVM/BEAM files only if not in embedded mode + // In embedded mode, all remaining arguments are passed to the script + if (!has_embedded_avm) { + for (int i = 1; i < argc; ++i) { + const char *ext = strrchr(argv[i], '.'); + if (ext && strcmp(ext, ".avm") == 0) { + struct AVMPackData *avmpack_data; + if (UNLIKELY(sys_open_avm_from_file(glb, argv[i], &avmpack_data) != AVM_OPEN_OK)) { + fprintf(stderr, "Failed opening %s.\n", argv[i]); + return EXIT_FAILURE; + } + synclist_append(&glb->avmpack_data, &avmpack_data->avmpack_head); + + if (IS_NULL_PTR(startup_module)) { + const void *startup_beam = NULL; + const char *startup_module_name; + uint32_t startup_beam_size; + avmpack_find_section_by_flag(avmpack_data->data, 1, &startup_beam, &startup_beam_size, &startup_module_name); + + if (startup_beam) { + avmpack_data->in_use = true; + startup_module = module_new_from_iff_binary(glb, startup_beam, startup_beam_size); + if (IS_NULL_PTR(startup_module)) { + fprintf(stderr, "Cannot load startup module: %s\n", startup_module_name); + return EXIT_FAILURE; + } + globalcontext_insert_module(glb, startup_module); + startup_module->module_platform_data = NULL; } - globalcontext_insert_module(glb, startup_module); - startup_module->module_platform_data = NULL; } - } - } else if (ext && (strcmp(ext, ".beam") == 0)) { - MappedFile *mapped_file = mapped_file_open_beam(argv[i]); - if (!iff_is_valid_beam(mapped_file->mapped)) { - fprintf(stderr, "%s has invalid beam format.\n", argv[i]); - return EXIT_FAILURE; - } - Module *mod = module_new_from_iff_binary(glb, mapped_file->mapped, mapped_file->size); - if (IS_NULL_PTR(mod)) { - fprintf(stderr, "Cannot load module: %s\n", argv[i]); + } else if (ext && (strcmp(ext, ".beam") == 0)) { + MappedFile *mapped_file = mapped_file_open_beam(argv[i]); + if (!iff_is_valid_beam(mapped_file->mapped)) { + fprintf(stderr, "%s has invalid beam format.\n", argv[i]); + return EXIT_FAILURE; + } + Module *mod = module_new_from_iff_binary(glb, mapped_file->mapped, mapped_file->size); + if (IS_NULL_PTR(mod)) { + fprintf(stderr, "Cannot load module: %s\n", argv[i]); + return EXIT_FAILURE; + } + globalcontext_insert_module(glb, mod); + mod->module_platform_data = NULL; + if (IS_NULL_PTR(startup_module) && module_search_exported_function(mod, START_ATOM_INDEX, 0) != 0) { + startup_module = mod; + } + + } else { + fprintf(stderr, "%s is not an AVM or a BEAM file.\n", argv[i]); return EXIT_FAILURE; } - globalcontext_insert_module(glb, mod); - mod->module_platform_data = NULL; - if (IS_NULL_PTR(startup_module) && module_search_exported_function(mod, START_ATOM_INDEX, 0) != 0) { - startup_module = mod; - } - - } else { - fprintf(stderr, "%s is not an AVM or a BEAM file.\n", argv[i]); - return EXIT_FAILURE; } } - if (IS_NULL_PTR(startup_module)) { - fprintf(stderr, "Unable to locate entrypoint.\n"); - return EXIT_FAILURE; + // Pass command-line arguments to escript in embedded mode + run_result_t result; + if (has_embedded_avm) { + // Don't print return value in embedded mode (pass NULL instead of stderr) + result = globalcontext_run(glb, startup_module, NULL, argc, argv); + } else { + result = globalcontext_run(glb, startup_module, stderr, 0, NULL); } - run_result_t result = globalcontext_run(glb, startup_module, stderr); - int status; if (result == RUN_SUCCESS) { status = EXIT_SUCCESS; diff --git a/src/platforms/rp2/src/main.c b/src/platforms/rp2/src/main.c index e25e1398d0..8da02cc36e 100644 --- a/src/platforms/rp2/src/main.c +++ b/src/platforms/rp2/src/main.c @@ -133,7 +133,7 @@ static int app_main() globalcontext_insert_module(glb, mod); mod->module_platform_data = NULL; - run_result_t result = globalcontext_run(glb, mod, stdout); + run_result_t result = globalcontext_run(glb, mod, stdout, 0, NULL); nif_collection_destroy_all(glb); globalcontext_destroy(glb); diff --git a/src/platforms/stm32/src/main.c b/src/platforms/stm32/src/main.c index 7febe37717..048bd26350 100644 --- a/src/platforms/stm32/src/main.c +++ b/src/platforms/stm32/src/main.c @@ -271,7 +271,7 @@ int main() AVM_LOGI(TAG, "Starting: %s...\n", startup_module_name); fprintf(stdout, "---\n"); - run_result_t result = globalcontext_run(glb, mod, stdout); + run_result_t result = globalcontext_run(glb, mod, stdout, 0, NULL); bool reboot_on_not_ok = #if defined(CONFIG_REBOOT_ON_NOT_OK)