Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -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': {
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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': [
{
Expand Down
6 changes: 5 additions & 1 deletion node.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
],
}
],
Expand Down
36 changes: 29 additions & 7 deletions test/addons/dlopen-ping-pong/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const char* dlopen_pong(void) {

namespace {

using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
Expand All @@ -24,20 +25,41 @@ 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<Value>& 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");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first dlerror call here clears any existing error on linux and it looks like same applies on AIX:
https://www.ibm.com/docs/en/aix/7.1.0?topic=d-dlerror-subroutine

Which makes the second call ? dlerror() problematic.

return;
}
ping_func = reinterpret_cast<ping>(dlsym(handle, "dlopen_ping"));
assert(ping_func != nullptr);
if (ping_func == nullptr) {
dlclose(handle);
ThrowError(isolate,
dlerror() != nullptr ? dlerror() : "dlsym dlopen_ping failed");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same issue as above, also calling dlerror after dlclose can return incorrect information, from the same subroutine link above.

return;
}
}

void Ping(const FunctionCallbackInfo<Value>& 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<Object> exports) {
Expand Down
8 changes: 5 additions & 3 deletions test/addons/dlopen-ping-pong/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
16 changes: 16 additions & 0 deletions test/addons/dlopen-ping-pong/ping.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
#ifndef _WIN32

#if defined(_AIX)
#include <stddef.h>
#include <dlfcn.h>
#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
98 changes: 52 additions & 46 deletions tools/create_expfile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,59 +29,65 @@
# 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.
#
# 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.

# This is to bypass the lint-sh job from saying "Expressions don't expand in
# single quotes, use double quotes for that." which is invalid for this case
# shellcheck disable=SC2016
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"
Loading