diff --git a/node.gyp b/node.gyp index 9c56b09ca6..fba9fe4183 100644 --- a/node.gyp +++ b/node.gyp @@ -578,6 +578,12 @@ }, { 'use_openssl_def%': 0, }], + [ 'OS=="linux"', { + 'nsolid_sources': [ + 'src/nsolid/nsolid_elf_utils.cc', + 'src/nsolid/nsolid_elf_utils.h', + ] + }], ], }, diff --git a/node.gypi b/node.gypi index cc1fef589d..d1e89c7028 100644 --- a/node.gypi +++ b/node.gypi @@ -550,11 +550,19 @@ [ 'OS=="sunos"', { 'ldflags': [ '-Wl,-M,/usr/lib/ld/map.noexstk' ], }], - [ '(OS=="linux" and not nsolid_use_librt) or OS=="openharmony"', { + [ 'OS=="openharmony"', { 'libraries!': [ '-lrt' ], }], + [ 'OS=="linux"', { + 'libraries': [ '-lelf' ], + 'conditions': [ + [ 'not nsolid_use_librt', { + 'libraries!': [ '-lrt' ], + }] + ] + }], [ 'OS in "freebsd linux openharmony"', { 'ldflags': [ '-Wl,-z,relro', '-Wl,-z,now' ] diff --git a/src/nsolid/nsolid_elf_utils.cc b/src/nsolid/nsolid_elf_utils.cc new file mode 100644 index 0000000000..8b5e003073 --- /dev/null +++ b/src/nsolid/nsolid_elf_utils.cc @@ -0,0 +1,97 @@ +#include "nsolid_elf_utils.h" +#include "nsolid_util.h" + +#include +#include +#include +#include +#include +#include +#include "uv.h" + +namespace node { +namespace nsolid { +namespace elf_utils { + + +int GetBuildId(const std::string& path, std::string* build_id) { + static std::map build_id_cache_; + + Elf* e; + Elf_Scn* scn = nullptr; + GElf_Shdr shdr; + + if (build_id_cache_.find(path) != build_id_cache_.end()) { + *build_id = build_id_cache_[path]; + return 0; + } + + int ret; + + ret = 0; + if (elf_version(EV_CURRENT) == EV_NONE) { + return elf_errno(); + } + + int fd = open(path.c_str(), O_RDONLY); + if (fd < 0) { + return -errno; + } + + e = elf_begin(fd, ELF_C_READ, nullptr); + if (!e) { + ret = elf_errno(); + goto error; + } + + size_t shstrndx; + if (elf_getshdrstrndx(e, &shstrndx) != 0) { + ret = elf_errno(); + goto end_error; + } + + *build_id = std::string(""); + while ((scn = elf_nextscn(e, scn)) != nullptr) { + if (gelf_getshdr(scn, &shdr) != &shdr) { + ret = elf_errno(); + goto end_error; + } + + char* name = elf_strptr(e, shstrndx, shdr.sh_name); + if (name && strcmp(name, ".note.gnu.build-id") == 0) { + Elf_Data* data = elf_getdata(scn, nullptr); + if (data && data->d_size >= 16) { + // ELF Note header: namesz(4), descsz(4), type(4) + name padding + // Compute offset to build-id properly + uint32_t* note = reinterpret_cast(data->d_buf); + uint32_t namesz = note[0]; + uint32_t descsz = note[1]; + // Name starts at offset 12 + // Descriptor (build-id) starts at next aligned offset + size_t name_end = 12 + ((namesz + 3) & ~3); + uint8_t* id = reinterpret_cast(data->d_buf) + name_end; + *build_id = utils::buffer_to_hex(id, descsz); + break; + } + } + } + + if (build_id->empty()) { + ret = UV_ENOENT; + } else { + build_id_cache_[path] = *build_id; + } + +end_error: + elf_end(e); + +error: + close(fd); + + return ret; +} + +} // namespace elf_utils +} // namespace nsolid +} // namespace node + diff --git a/src/nsolid/nsolid_elf_utils.h b/src/nsolid/nsolid_elf_utils.h new file mode 100644 index 0000000000..d46400b9f2 --- /dev/null +++ b/src/nsolid/nsolid_elf_utils.h @@ -0,0 +1,19 @@ +#ifndef SRC_NSOLID_NSOLID_ELF_UTILS_H_ +#define SRC_NSOLID_NSOLID_ELF_UTILS_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include + +namespace node { +namespace nsolid { + +namespace elf_utils { + int GetBuildId(const std::string& path, std::string* build_id); +} // namespace elf_utils +} // namespace nsolid +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NSOLID_NSOLID_ELF_UTILS_H_ diff --git a/test/addons/nsolid-elf-utils/binding.cc b/test/addons/nsolid-elf-utils/binding.cc new file mode 100644 index 0000000000..1c72c3054a --- /dev/null +++ b/test/addons/nsolid-elf-utils/binding.cc @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#if defined(__linux__) +#include "../../../src/nsolid/nsolid_elf_utils.h" +#endif + +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::String; +using v8::Value; + +static void GetBuildId(const FunctionCallbackInfo& args) { +#if defined(__linux__) + Isolate* isolate = args.GetIsolate(); + assert(args[0]->IsString()); + v8::String::Utf8Value path_utf8(isolate, args[0]); + std::string path(*path_utf8, path_utf8.length()); + std::string build_id; + int res = node::nsolid::elf_utils::GetBuildId(path, &build_id); + if (res != 0) { + return; + } + + args.GetReturnValue().Set( + String::NewFromUtf8(isolate, build_id.c_str()).ToLocalChecked()); +#endif +} + +NODE_MODULE_INIT(/* exports, module, context */) { + NODE_SET_METHOD(exports, "getBuildId", GetBuildId); +} diff --git a/test/addons/nsolid-elf-utils/binding.gyp b/test/addons/nsolid-elf-utils/binding.gyp new file mode 100644 index 0000000000..cfe54914f2 --- /dev/null +++ b/test/addons/nsolid-elf-utils/binding.gyp @@ -0,0 +1,10 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'sources': [ 'binding.cc' ], + 'includes': ['../common.gypi'], + 'defines': [ 'NODE_WANT_INTERNALS=1' ], + } + ] +} diff --git a/test/addons/nsolid-elf-utils/nsolid-elf-utils.js b/test/addons/nsolid-elf-utils/nsolid-elf-utils.js new file mode 100644 index 0000000000..ca421f35ce --- /dev/null +++ b/test/addons/nsolid-elf-utils/nsolid-elf-utils.js @@ -0,0 +1,23 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const { execSync } = require('child_process'); +const process = require('process'); + +// Only run on Linux +if (process.platform !== 'linux') { + console.log('Skipping: nsolid-elf-utils only supported on Linux'); + process.exit(0); +} + +const bindingPath = require.resolve(`./build/${common.buildType}/binding`); +const binding = require(bindingPath); + +const expected = + execSync(`readelf -n ${process.execPath} | awk '/Build ID/ { print $3 }'`, + { encoding: 'utf8' }).trim(); + +const buildId = binding.getBuildId(process.execPath); +assert.strictEqual(buildId, + expected, + `Mismatch: addon='${buildId}', readelf='${expected}'`);