Skip to content

Commit 82582a7

Browse files
committed
src: add GetBuildId helper function
To allow us to read the Build-Id from ELF headers in a specific binary.
1 parent 9958117 commit 82582a7

7 files changed

Lines changed: 197 additions & 1 deletion

File tree

node.gyp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,12 @@
593593
}, {
594594
'use_openssl_def%': 0,
595595
}],
596+
[ 'OS=="linux"', {
597+
'nsolid_sources': [
598+
'src/nsolid/nsolid_elf_utils.cc',
599+
'src/nsolid/nsolid_elf_utils.h',
600+
]
601+
}],
596602
],
597603
},
598604

node.gypi

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,11 +550,19 @@
550550
[ 'OS=="sunos"', {
551551
'ldflags': [ '-Wl,-M,/usr/lib/ld/map.noexstk' ],
552552
}],
553-
[ '(OS=="linux" and not nsolid_use_librt) or OS=="openharmony"', {
553+
[ 'OS=="openharmony"', {
554554
'libraries!': [
555555
'-lrt'
556556
],
557557
}],
558+
[ 'OS=="linux"', {
559+
'libraries': [ '-lelf' ],
560+
'conditions': [
561+
[ 'not nsolid_use_librt', {
562+
'libraries!': [ '-lrt' ],
563+
}]
564+
]
565+
}],
558566
[ 'OS in "freebsd linux openharmony"', {
559567
'ldflags': [ '-Wl,-z,relro',
560568
'-Wl,-z,now' ]

src/nsolid/nsolid_elf_utils.cc

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#include "nsolid_elf_utils.h"
2+
#include "nsolid_util.h"
3+
4+
#include <fcntl.h>
5+
#include <libelf.h>
6+
#include <gelf.h>
7+
#include <unistd.h>
8+
#include <cstring>
9+
#include <map>
10+
#include "uv.h"
11+
12+
namespace node {
13+
namespace nsolid {
14+
namespace elf_utils {
15+
16+
17+
int GetBuildId(const std::string& path, std::string* build_id) {
18+
static std::map<std::string, std::string> build_id_cache_;
19+
20+
Elf* e;
21+
Elf_Scn* scn = nullptr;
22+
GElf_Shdr shdr;
23+
24+
if (build_id_cache_.find(path) != build_id_cache_.end()) {
25+
*build_id = build_id_cache_[path];
26+
return 0;
27+
}
28+
29+
int ret;
30+
31+
ret = 0;
32+
if (elf_version(EV_CURRENT) == EV_NONE) {
33+
return elf_errno();
34+
}
35+
36+
int fd = open(path.c_str(), O_RDONLY);
37+
if (fd < 0) {
38+
return -errno;
39+
}
40+
41+
e = elf_begin(fd, ELF_C_READ, nullptr);
42+
if (!e) {
43+
ret = elf_errno();
44+
goto error;
45+
}
46+
47+
size_t shstrndx;
48+
if (elf_getshdrstrndx(e, &shstrndx) != 0) {
49+
ret = elf_errno();
50+
goto end_error;
51+
}
52+
53+
*build_id = std::string("");
54+
while ((scn = elf_nextscn(e, scn)) != nullptr) {
55+
if (gelf_getshdr(scn, &shdr) != &shdr) {
56+
ret = elf_errno();
57+
goto end_error;
58+
}
59+
60+
char* name = elf_strptr(e, shstrndx, shdr.sh_name);
61+
if (name && strcmp(name, ".note.gnu.build-id") == 0) {
62+
Elf_Data* data = elf_getdata(scn, nullptr);
63+
if (data && data->d_size >= 16) {
64+
// ELF Note header: namesz(4), descsz(4), type(4) + name padding
65+
// Compute offset to build-id properly
66+
uint32_t* note = reinterpret_cast<uint32_t*>(data->d_buf);
67+
uint32_t namesz = note[0];
68+
uint32_t descsz = note[1];
69+
// Name starts at offset 12
70+
// Descriptor (build-id) starts at next aligned offset
71+
size_t name_end = 12 + ((namesz + 3) & ~3);
72+
uint8_t* id = reinterpret_cast<uint8_t*>(data->d_buf) + name_end;
73+
*build_id = utils::buffer_to_hex(id, descsz);
74+
break;
75+
}
76+
}
77+
}
78+
79+
if (build_id->empty()) {
80+
ret = UV_ENOENT;
81+
} else {
82+
build_id_cache_[path] = *build_id;
83+
}
84+
85+
end_error:
86+
elf_end(e);
87+
88+
error:
89+
close(fd);
90+
91+
return ret;
92+
}
93+
94+
} // namespace elf_utils
95+
} // namespace nsolid
96+
} // namespace node
97+

src/nsolid/nsolid_elf_utils.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#ifndef SRC_NSOLID_NSOLID_ELF_UTILS_H_
2+
#define SRC_NSOLID_NSOLID_ELF_UTILS_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
#include <string>
7+
8+
namespace node {
9+
namespace nsolid {
10+
11+
namespace elf_utils {
12+
int GetBuildId(const std::string& path, std::string* build_id);
13+
} // namespace elf_utils
14+
} // namespace nsolid
15+
} // namespace node
16+
17+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
18+
19+
#endif // SRC_NSOLID_NSOLID_ELF_UTILS_H_
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#include <node.h>
2+
#include <v8.h>
3+
#include <cassert>
4+
#include <string>
5+
#if defined(__linux__)
6+
#include "../../../src/nsolid/nsolid_elf_utils.h"
7+
#endif
8+
9+
using v8::FunctionCallbackInfo;
10+
using v8::Isolate;
11+
using v8::String;
12+
using v8::Value;
13+
14+
static void GetBuildId(const FunctionCallbackInfo<Value>& args) {
15+
#if defined(__linux__)
16+
Isolate* isolate = args.GetIsolate();
17+
assert(args[0]->IsString());
18+
v8::String::Utf8Value path_utf8(isolate, args[0]);
19+
std::string path(*path_utf8, path_utf8.length());
20+
std::string build_id;
21+
int res = node::nsolid::elf_utils::GetBuildId(path, &build_id);
22+
if (res != 0) {
23+
return;
24+
}
25+
26+
args.GetReturnValue().Set(
27+
String::NewFromUtf8(isolate, build_id.c_str()).ToLocalChecked());
28+
#endif
29+
}
30+
31+
NODE_MODULE_INIT(/* exports, module, context */) {
32+
NODE_SET_METHOD(exports, "getBuildId", GetBuildId);
33+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
'defines': [ 'NODE_WANT_INTERNALS=1' ],
8+
}
9+
]
10+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
const common = require('../../common');
3+
const assert = require('assert');
4+
const { execSync } = require('child_process');
5+
const process = require('process');
6+
7+
// Only run on Linux
8+
if (process.platform !== 'linux') {
9+
console.log('Skipping: nsolid-elf-utils only supported on Linux');
10+
process.exit(0);
11+
}
12+
13+
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
14+
const binding = require(bindingPath);
15+
16+
const expected =
17+
execSync(`readelf -n ${process.execPath} | awk '/Build ID/ { print $3 }'`,
18+
{ encoding: 'utf8' }).trim();
19+
20+
const buildId = binding.getBuildId(process.execPath);
21+
assert.strictEqual(buildId,
22+
expected,
23+
`Mismatch: addon='${buildId}', readelf='${expected}'`);

0 commit comments

Comments
 (0)