From 8160928bd70ad477d29c76cb993bb69f8e043dd2 Mon Sep 17 00:00:00 2001 From: Franciszek Kubis Date: Mon, 6 Oct 2025 09:54:00 +0200 Subject: [PATCH] Implement code:get_object_code/1 --- libs/estdlib/src/code.erl | 14 +++- src/libAtomVM/module.c | 2 + src/libAtomVM/module.h | 2 + src/libAtomVM/nifs.c | 48 ++++++++++++++ src/libAtomVM/nifs.gperf | 1 + tests/erlang_tests/CMakeLists.txt | 2 + .../test_code_get_object_code.erl | 65 +++++++++++++++++++ tests/test.c | 1 + 8 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 tests/erlang_tests/test_code_get_object_code.erl diff --git a/libs/estdlib/src/code.erl b/libs/estdlib/src/code.erl index 06f7227516..7c6c960cd1 100644 --- a/libs/estdlib/src/code.erl +++ b/libs/estdlib/src/code.erl @@ -31,7 +31,8 @@ load_binary/3, ensure_loaded/1, which/1, - is_loaded/1 + is_loaded/1, + get_object_code/1 ]). %%----------------------------------------------------------------------------- @@ -132,3 +133,14 @@ which(Module) -> {error, _} -> non_existing end. + +%%----------------------------------------------------------------------------- +%% @param Module module to get object code from +%% @returns Tuple `{Module, Binary, Filename}' if successful, otherwise `error'. +%% @doc Return module binary of a given module. +%% @end +%%----------------------------------------------------------------------------- +-spec get_object_code(Module) -> {Module, Binary, Filename} | error when + Module :: atom(), Binary :: binary(), Filename :: string(). +get_object_code(_Module) -> + erlang:nif_error(undefined). diff --git a/src/libAtomVM/module.c b/src/libAtomVM/module.c index 18d987f5e7..baa2225c09 100644 --- a/src/libAtomVM/module.c +++ b/src/libAtomVM/module.c @@ -328,6 +328,8 @@ Module *module_new_from_iff_binary(GlobalContext *global, const void *iff_binary mod->fun_table = beam_file + offsets[FUNT]; mod->str_table = beam_file + offsets[STRT]; mod->str_table_len = sizes[STRT]; + mod->binary = beam_file; + mod->binary_size = size; #ifndef AVM_NO_JIT if (offsets[AVMN]) { NativeCodeChunk *native_code = (NativeCodeChunk *) (beam_file + offsets[AVMN]); diff --git a/src/libAtomVM/module.h b/src/libAtomVM/module.h index 2f7b56321c..53123aafe3 100644 --- a/src/libAtomVM/module.h +++ b/src/libAtomVM/module.h @@ -124,6 +124,8 @@ struct Module const uint8_t *line_refs_table; size_t locations_count; const uint8_t *locations_table; + uint8_t *binary; + size_t binary_size; #ifndef AVM_NO_JIT ModuleNativeEntryPoint native_code; #endif diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 52e621ca48..f08f7bd495 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -196,6 +196,7 @@ static term nif_code_all_loaded(Context *ctx, int argc, term argv[]); static term nif_code_load_abs(Context *ctx, int argc, term argv[]); static term nif_code_load_binary(Context *ctx, int argc, term argv[]); static term nif_code_ensure_loaded(Context *ctx, int argc, term argv[]); +static term nif_code_get_object_code(Context *ctx, int argc, term argv[]); static term nif_code_server_is_loaded(Context *ctx, int argc, term argv[]); static term nif_code_server_resume(Context *ctx, int argc, term argv[]); #ifndef AVM_NO_JIT @@ -747,6 +748,11 @@ static const struct Nif code_ensure_loaded_nif = { .nif_ptr = nif_code_ensure_loaded }; +static const struct Nif code_get_object_code_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_code_get_object_code +}; + static const struct Nif code_server_is_loaded_nif = { .base.type = NIFFunctionType, .nif_ptr = nif_code_server_is_loaded @@ -5509,6 +5515,48 @@ static term nif_code_ensure_loaded(Context *ctx, int argc, term argv[]) return result; } +static term nif_code_get_object_code(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + term module_atom = argv[0]; + VALIDATE_VALUE(module_atom, term_is_atom); + + size_t module_name_len; + const uint8_t *module_name = atom_table_get_atom_string(ctx->global->atom_table, term_to_atom_index(module_atom), &module_name_len); + + size_t filename_len = module_name_len + 6; + char *module_file_name = malloc(filename_len); + if (IS_NULL_PTR(module_file_name)) { + return ERROR_ATOM; + } + memcpy(module_file_name, module_name, module_name_len); + memcpy(module_file_name + module_name_len, ".beam", 6); + Module *module = globalcontext_get_module(ctx->global, term_to_atom_index(module_atom)); + + if (UNLIKELY(!module)) { + free(module_file_name); + return ERROR_ATOM; + } + size_t result_size = TUPLE_SIZE(3) + term_binary_heap_size(module->binary_size) + LIST_SIZE(filename_len, 1); + if (UNLIKELY(memory_ensure_free_with_roots(ctx, result_size, 1, &module_atom, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + free(module_file_name); + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + // Note: this assumes constness of module->binary and could be use-after-free if we allowed changing module bitcode at runtime. + // TODO: update this code when module unloading will be supported. + term binary = term_from_literal_binary((void *) module->binary, module->binary_size, &ctx->heap, ctx->global); + // TODO: this code has to be changed to return the complete path + term filename_term = term_from_string((const uint8_t *) module_file_name, filename_len, &ctx->heap); + term result = term_alloc_tuple(3, &ctx->heap); + + term_put_tuple_element(result, 0, module_atom); + term_put_tuple_element(result, 1, binary); + term_put_tuple_element(result, 2, filename_term); + + free(module_file_name); + return result; +} + static term nif_code_server_is_loaded(Context *ctx, int argc, term argv[]) { UNUSED(argc); diff --git a/src/libAtomVM/nifs.gperf b/src/libAtomVM/nifs.gperf index a647c1de04..dfa87fa670 100644 --- a/src/libAtomVM/nifs.gperf +++ b/src/libAtomVM/nifs.gperf @@ -177,6 +177,7 @@ code:load_binary/3, &code_load_binary_nif code:all_available/0, &code_all_available_nif code:all_loaded/0, &code_all_loaded_nif code:ensure_loaded/1, &code_ensure_loaded_nif +code:get_object_code/1, &code_get_object_code_nif code_server:is_loaded/1, &code_server_is_loaded_nif code_server:resume/2, &code_server_resume_nif code_server:code_chunk/1, IF_HAVE_JIT(&code_server_code_chunk_nif) diff --git a/tests/erlang_tests/CMakeLists.txt b/tests/erlang_tests/CMakeLists.txt index 7bb3df40de..cfacf4f7b4 100644 --- a/tests/erlang_tests/CMakeLists.txt +++ b/tests/erlang_tests/CMakeLists.txt @@ -542,6 +542,7 @@ compile_erlang(test_code_all_available_loaded) compile_erlang(test_code_load_binary) compile_erlang(test_code_load_abs) compile_erlang(test_code_ensure_loaded) +compile_erlang(test_code_get_object_code) compile_erlang(test_add_avm_pack_binary) compile_erlang(test_add_avm_pack_file) compile_erlang(test_close_avm_pack) @@ -1068,6 +1069,7 @@ set(erlang_test_beams test_code_all_available_loaded.beam test_code_load_binary.beam + test_code_get_object_code.beam test_code_load_abs.beam test_code_ensure_loaded.beam test_add_avm_pack_binary.beam diff --git a/tests/erlang_tests/test_code_get_object_code.erl b/tests/erlang_tests/test_code_get_object_code.erl new file mode 100644 index 0000000000..f411441b72 --- /dev/null +++ b/tests/erlang_tests/test_code_get_object_code.erl @@ -0,0 +1,65 @@ +% +% This file is part of AtomVM. +% +% Copyright 2025 Franciszek Kubis +% +% Licensed 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. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +-module(test_code_get_object_code). + +-export([start/0, get_object_wrong_argument/1]). + +-include("code_load/export_test_module_data.hrl"). + +start() -> + ok = get_object_from_export_test_module(), + ok = get_object_from_already_loaded_test_module(), + ok = get_object_from_non_existing_module(), + ok = ?MODULE:get_object_wrong_argument("a string"), + ok = ?MODULE:get_object_wrong_argument(123), + ok = ?MODULE:get_object_wrong_argument({1, "a"}), + ok = ?MODULE:get_object_wrong_argument([1, b, 3]), + 0. + +get_object_from_already_loaded_test_module() -> + {test_code_get_object_code, Bin, _Filename} = code:get_object_code(?MODULE), + {module, ?MODULE} = code:load_binary( + ?MODULE, atom_to_list(?MODULE) ++ ".beam", Bin + ), + {module, ?MODULE} = code:ensure_loaded(?MODULE), + ok. + +get_object_from_export_test_module() -> + Bin = ?EXPORT_TEST_MODULE_DATA, + error = code:get_object_code(export_test_module), + {module, export_test_module} = code:load_binary( + export_test_module, "export_test_module.beam", Bin + ), + {module, export_test_module} = code:ensure_loaded(export_test_module), + error = code:get_object_code(export_test_module), + 24 = export_test_module:exported_func(4), + ok. + +get_object_from_non_existing_module() -> + error = code:get_object_code(non_existing_module), + ok. + +get_object_wrong_argument(Argument) -> + try code:get_object_code(Argument) of + _ -> not_raised + catch + _:_ -> ok + end. diff --git a/tests/test.c b/tests/test.c index 14dea8fb1b..9830fa3346 100644 --- a/tests/test.c +++ b/tests/test.c @@ -544,6 +544,7 @@ struct Test tests[] = { TEST_CASE(test_code_all_available_loaded), TEST_CASE_EXPECTED(test_code_load_binary, 24), + TEST_CASE(test_code_get_object_code), TEST_CASE_EXPECTED(test_code_load_abs, 24), TEST_CASE(test_code_ensure_loaded), TEST_CASE_ATOMVM_ONLY(test_add_avm_pack_binary, 24),