Skip to content
Draft
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
37 changes: 30 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,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<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");
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");
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
95 changes: 49 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,62 @@
# 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.

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"