diff --git a/node.gyp b/node.gyp index 714609b03e66..38ea651d8978 100644 --- a/node.gyp +++ b/node.gyp @@ -667,6 +667,12 @@ }, }, }], + ['node_shared=="true" and OS in "aix os400"', { + 'dependencies': ['generate_node_exp'], + 'ldflags': [ + '-Wl,-bE:<(PRODUCT_DIR)/<(node_core_target_name).exp', + ], + }], [ 'node_shared=="false"', { # Keep this whole-archive section in sync with the `node_lib` target below. 'xcode_settings': { @@ -1165,6 +1171,23 @@ 'ldflags': [ '-Wl,-brtl' ], }, }], + ['OS in "aix os400"', { + 'conditions': [ + ['node_use_node_snapshot=="true"', { + 'variables': { + 'expfile_extra_inputs': [ + '<(obj_dir)/<(node_lib_target_name)/gen/node_snapshot.o', + ], + }, + }, { + 'variables': { + 'expfile_extra_inputs': [ + '<(obj_dir)/<(node_lib_target_name)/src/node_snapshot_stub.o', + ], + }, + }], + ], + }], [ 'node_shared=="true" and OS=="win"', { 'sources': [ 'src/res/node.rc', @@ -1712,6 +1735,33 @@ ], # end targets 'conditions': [ + ['OS in "aix os400" and node_shared=="true"', { + 'targets': [ + { + 'target_name': 'generate_node_exp', + 'type': 'none', + 'dependencies': [ + '<(node_lib_target_name)', + ], + 'actions': [ + { + 'action_name': 'generate_node_exp_action', + 'inputs': [ + '<(PRODUCT_DIR)/<(node_lib_target_name).exp', + ], + 'outputs': [ + '<(PRODUCT_DIR)/<(node_core_target_name).exp', + ], + 'action': [ + '<(python)', 'tools/copyfile.py', + '<@(_inputs)', + '<@(_outputs)', + ], + }, + ], + }, + ], + }], ['OS=="win" and node_shared=="true"', { 'targets': [ { diff --git a/node.gypi b/node.gypi index 32e7dca2fd94..a866ba57624e 100644 --- a/node.gypi +++ b/node.gypi @@ -300,20 +300,24 @@ [ 'force_load=="true"', { 'variables': { 'exp_filename': '<(PRODUCT_DIR)/<(_target_name).exp', + 'expfile_extra_inputs%': [], }, 'actions': [ { 'action_name': 'expfile', 'inputs': [ '<(obj_dir)', + '<@(expfile_extra_inputs)', ], 'outputs': [ '<(exp_filename)', ], 'action': [ 'sh', 'tools/create_expfile.sh', - '<@(_inputs)', + '<(obj_dir)', '<@(_outputs)', + '<(_target_name)', + '<@(expfile_extra_inputs)', ], } ], diff --git a/test/addons/dlopen-ping-pong/binding.cc b/test/addons/dlopen-ping-pong/binding.cc index be33708c9317..31869dcfc28c 100644 --- a/test/addons/dlopen-ping-pong/binding.cc +++ b/test/addons/dlopen-ping-pong/binding.cc @@ -13,6 +13,7 @@ const char* dlopen_pong(void) { namespace { +using v8::Exception; using v8::FunctionCallbackInfo; using v8::Isolate; using v8::Local; @@ -24,20 +25,42 @@ typedef const char* (*ping)(void); static ping ping_func; +void ThrowError(Isolate* isolate, const char* message) { + isolate->ThrowException(Exception::Error( + String::NewFromUtf8(isolate, message).ToLocalChecked())); +} + void LoadLibrary(const FunctionCallbackInfo& args) { - const String::Utf8Value filename(args.GetIsolate(), args[0]); + Isolate* isolate = args.GetIsolate(); + const String::Utf8Value filename(isolate, args[0]); void* handle = dlopen(*filename, RTLD_LAZY); - if (handle == nullptr) fprintf(stderr, "%s\n", dlerror()); - assert(handle != nullptr); + if (handle == nullptr) { + ThrowError(isolate, dlerror() != nullptr ? dlerror() : "dlopen failed"); + return; + } ping_func = reinterpret_cast(dlsym(handle, "dlopen_ping")); - assert(ping_func != nullptr); + if (ping_func == nullptr) { + dlclose(handle); + ThrowError(isolate, + dlerror() != nullptr ? dlerror() : "dlsym dlopen_ping failed"); + return; + } } void Ping(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); - assert(ping_func != nullptr); - args.GetReturnValue().Set(String::NewFromUtf8( - isolate, ping_func()).ToLocalChecked()); + if (ping_func == nullptr) { + ThrowError(isolate, "ping library was not loaded"); + return; + } + const char* answer = ping_func(); + if (answer == nullptr) { + ThrowError(isolate, + "dlopen_pong could not be resolved (RTLD_GLOBAL)"); + return; + } + args.GetReturnValue().Set( + String::NewFromUtf8(isolate, answer).ToLocalChecked()); } void init(Local exports) { diff --git a/test/addons/dlopen-ping-pong/binding.gyp b/test/addons/dlopen-ping-pong/binding.gyp index c3edcf0dbedc..ff6381d25788 100644 --- a/test/addons/dlopen-ping-pong/binding.gyp +++ b/test/addons/dlopen-ping-pong/binding.gyp @@ -10,10 +10,12 @@ 'xcode_settings': { 'OTHER_LDFLAGS': [ '-Wl,-undefined', '-Wl,dynamic_lookup' ] }}], - # Enable the shared object to be linked by runtime linker - ['OS in "aix os400"', { + # Deferred import of dlopen_pong (-Wl,-G) where ping.c links it directly. + # AIX uses dlsym(RTLD_DEFAULT) in ping.c instead of a link-time import. + ['OS=="os400"', { 'ldflags': [ '-Wl,-G' ] - }]], + }], + ], }, { 'target_name': 'binding', diff --git a/test/addons/dlopen-ping-pong/ping.c b/test/addons/dlopen-ping-pong/ping.c index c76f40580c41..1f5b1bff47ac 100644 --- a/test/addons/dlopen-ping-pong/ping.c +++ b/test/addons/dlopen-ping-pong/ping.c @@ -1,10 +1,26 @@ #ifndef _WIN32 +#if defined(_AIX) +#include +#include +#else const char* dlopen_pong(void); +#endif __attribute__((visibility("default"))) const char* dlopen_ping(void) { +#if defined(_AIX) + /* ping.so is built with -Wl,-G: link-time import of dlopen_pong is resolved + * when ping.so is loaded. On --shared libnode builds that resolution can fail + * even when dlopen_pong appears in binding's loader table (dump). Resolve + * at call time via the global symbol set after process.dlopen(RTLD_GLOBAL). */ + typedef const char* (*pong_fn)(void); + pong_fn pong = + (pong_fn)dlsym(RTLD_DEFAULT, "dlopen_pong"); + return pong != NULL ? pong() : NULL; +#else return dlopen_pong(); +#endif } #endif // _WIN32 diff --git a/tools/create_expfile.sh b/tools/create_expfile.sh index 977f509e2ec5..0a005fb5ff96 100755 --- a/tools/create_expfile.sh +++ b/tools/create_expfile.sh @@ -6,7 +6,7 @@ # by native add-ons. # # The raw symbol data is obtained by using dump -tov on -# the .a files which make up the node executable. +# the .a and .o files which make up the node executable. # # dump -tov flags: # -t: Display symbol table entries @@ -29,12 +29,17 @@ # Symbols for the gtest libraries are excluded as # they are not linked into the node executable. # -echo "Searching $1 to write out expfile to $2" +SEARCH_DIR="$1" +OUTPUT="$2" +TARGET_NAME="$3" +shift 3 || true +# Remaining arguments are explicit extra inputs for the build to order on. -# This special sequence must be at the start of the exp file. -echo "#!." > "$2.tmp" +echo "Searching $SEARCH_DIR to write out expfile to $OUTPUT" + +SYMFILE="${OUTPUT}.syms" +: > "$SYMFILE" -# Pull the symbols from the .a files. # Use dump -tov to get visibility information and exclude HIDDEN symbols. # This prevents AIX linker error 0711-407 when addons try to import symbols # with visibility attributes. @@ -42,46 +47,44 @@ echo "#!." > "$2.tmp" # IMPORTANT: AIX dump -tov output has a visibility column that contains either # a visibility keyword (HIDDEN/EXPORTED/PROTECTED) or blank spaces when no # visibility attribute is set. Since AWK splits fields on whitespace, this -# results in different field counts: -# -# With visibility keyword (8 fields after AWK splits): -# [137] m 0x00003290 .text 1 weak HIDDEN ._ZNKSt3__1... -# $1 $2 $3 $4 $5 $6 $7 $8 -# | | └─ Symbol name -# | └─ Visibility (HIDDEN/EXPORTED/PROTECTED) -# └─ Linkage (weak/extern) -# -# Without visibility keyword (7 fields after AWK splits, spaces collapsed): -# [77] m 0x00000000 .text 1 weak ._ZN8simdjson... -# $1 $2 $3 $4 $5 $6 $7 -# | └─ Symbol name -# └─ Linkage (weak/extern) -# -# The awk script handles both cases by using an associative array V[] -# to map field values to their export suffixes. For fields that don't match -# any key in V[], the lookup returns an empty string, which is perfect for -# handling the variable field positions caused by the blank visibility column. -# -find "$1" -name "*.a" | grep -v gtest | while read -r f; do - dump -tov -X 32_64 "$f" 2>/dev/null | \ - awk ' - BEGIN { - V["EXPORTED"]=" export" - V["PROTECTED"]=" protected" - V["HIDDEN"]=" hidden" - V["weak"]=" weak" - } - /^\[[0-9]+\]\tm +[^ ]+ +\.(text|data|tdata|bss) +[^ ]+ +(extern|weak)( +(EXPORTED|PROTECTED|HIDDEN))?/ { - # Exclude symbols starting with dot, __sinit, __sterm, __[0-9]+__ - # Also exclude HIDDEN symbols to avoid visibility attribute issues - if (!match($NF,/^(\.|__sinit|__sterm|__[0-9]+__)/)) { - # Skip if HIDDEN visibility (can be at $(NF-1) or $(NF-2)) - if ($(NF-1) != "HIDDEN" && $(NF-2) != "HIDDEN") { - print $NF V[$(NF-1)] V[$(NF-2)] - } - } +# results in different field counts. The awk script handles both cases by using +# an associative array V[] to map field values to their export suffixes. + +DUMP_AWK=' +BEGIN { + V["EXPORTED"]=" export" + V["PROTECTED"]=" protected" + V["HIDDEN"]=" hidden" + V["weak"]=" weak" +} +/^\[[0-9]+\]\tm +[^ ]+ +\.(text|data|tdata|bss) +[^ ]+ +(extern|weak)( +(EXPORTED|PROTECTED|HIDDEN))?/ { + if (!match($NF,/^(\.|__sinit|__sterm|__[0-9]+__)/)) { + if ($(NF-1) != "HIDDEN" && $(NF-2) != "HIDDEN") { + print $NF V[$(NF-1)] V[$(NF-2)] } - ' -done | sort -u >> "$2.tmp" + } +} +' + +find "$SEARCH_DIR" -name "*.a" | grep -v gtest | while read -r f; do + dump -tov -X 32_64 "$f" 2>/dev/null | awk "$DUMP_AWK" >> "$SYMFILE" +done + +# Objects linked directly into the target (not archived), e.g. node_snapshot.o. +if [ -n "$TARGET_NAME" ] && [ -d "$SEARCH_DIR/$TARGET_NAME" ]; then + find "$SEARCH_DIR/$TARGET_NAME" -name "*.o" | while read -r f; do + dump -tov -X 32_64 "$f" 2>/dev/null | awk "$DUMP_AWK" >> "$SYMFILE" + done +fi + +for extra in "$@"; do + if [ -f "$extra" ]; then + dump -tov -X 32_64 "$extra" 2>/dev/null | awk "$DUMP_AWK" >> "$SYMFILE" + fi +done -mv -f "$2.tmp" "$2" +{ + echo "#!." + sort -u "$SYMFILE" +} > "$OUTPUT" +rm -f "$SYMFILE"