Skip to content

Commit c9cf391

Browse files
krkjbachorik
authored andcommitted
Parse both .rela.dyn and .rela.plt of libraries. (#1074)
1 parent 34ff5f0 commit c9cf391

File tree

16 files changed

+225
-65
lines changed

16 files changed

+225
-65
lines changed

ddprof-lib/gtest/build.gradle

+24-29
Original file line numberDiff line numberDiff line change
@@ -37,42 +37,37 @@ tasks.withType(StripSymbols).configureEach { task ->
3737
}
3838
}
3939

40-
def buildResourcesTask = tasks.register("buildResources", Exec) {
40+
def buildNativeLibsTask = tasks.register("buildNativeLibs") {
4141
group = 'build'
42-
description = "Build the resources for the Google Tests"
42+
description = "Build the native libs for the Google Tests"
4343

4444
onlyIf {
4545
hasGtest && !project.hasProperty('skip-native') && os().isLinux()
4646
}
4747

48-
def targetDir = project(':ddprof-lib').file('build/test/resources/unresolved-functions')
49-
50-
commandLine "sh", "-c", "cd ${project(':ddprof-lib').projectDir}/src/test/resources/unresolved-functions && make TARGET_DIR=${targetDir}"
51-
52-
inputs.files project(':ddprof-lib').files('src/test/resources/unresolved-functions')
53-
outputs.file "${targetDir}/main"
54-
}
55-
56-
def buildSmallLibTask = tasks.register("buildSmallLib", Exec) {
57-
group = 'build'
58-
description = "Build the small-lib shared library for the Google Tests"
59-
60-
onlyIf {
61-
hasGtest && !project.hasProperty('skip-native') && os().isLinux()
48+
def srcDir = project(':ddprof-lib').file('src/test/resources/native-libs')
49+
def targetDir = project(':ddprof-lib').file('build/test/resources/native-libs/')
50+
51+
// Move the exec calls to the execution phase
52+
doLast {
53+
srcDir.eachDir { dir ->
54+
def libName = dir.name
55+
def libDir = file("${targetDir}/${libName}")
56+
def libSrcDir = file("${srcDir}/${libName}")
57+
58+
exec {
59+
commandLine "sh", "-c", """
60+
echo "Processing library: ${libName} @ ${libSrcDir}"
61+
mkdir -p ${libDir}
62+
cd ${libSrcDir}
63+
make TARGET_DIR=${libDir}
64+
"""
65+
}
66+
}
6267
}
6368

64-
def sourceDir = project(':ddprof-lib').file('src/test/resources/small-lib')
65-
def targetDir = project(':ddprof-lib').file('build/test/resources/small-lib')
66-
67-
commandLine "sh", "-c", """
68-
mkdir -p ${targetDir}
69-
cd ${sourceDir} && g++ -fPIC -shared -o ${targetDir}/libsmall-lib.so *.cpp
70-
"""
71-
72-
inputs.files fileTree(sourceDir) {
73-
include '**/*.cpp'
74-
}
75-
outputs.file "${targetDir}/libsmall-lib.so"
69+
inputs.files project(':ddprof-lib').files('src/test/resources/native-libs/**/*')
70+
outputs.dir "${targetDir}"
7671
}
7772

7873
def gtestAll = tasks.register("gtest") {
@@ -201,7 +196,7 @@ tasks.whenTaskAdded { task ->
201196
gtestTask.get().dependsOn gtestExecuteTask.get()
202197
if (os().isLinux()) {
203198
// custom binaries for tests are built only on linux
204-
gtestExecuteTask.get().dependsOn(buildResourcesTask, buildSmallLibTask)
199+
gtestExecuteTask.get().dependsOn(buildNativeLibs)
205200
}
206201
gtestAll.get().dependsOn gtestExecuteTask.get()
207202
}

ddprof-lib/src/main/cpp/symbols_linux.cpp

+17-25
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
11
/*
2-
* Copyright 2017 Andrei Pangin
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
2+
* Copyright The async-profiler authors
3+
* SPDX-License-Identifier: Apache-2.0
154
*/
165

176
#ifdef __linux__
@@ -192,21 +181,25 @@ void ElfParser::parseDynamicSection() {
192181
loadSymbolTable(symtab, syment * nsyms, syment, strtab);
193182
}
194183

184+
if (rel != NULL && relsz != 0) {
185+
// If a shared library is built without PLT (-fno-plt), relocation entries for imports
186+
// can be found in .rela.dyn. However, if both sections exist, .rela.plt entries
187+
// should take precedence, that's why we parse .rela.dyn first.
188+
for (size_t offs = relcount * relent; offs < relsz; offs += relent) {
189+
ElfRelocation *r = (ElfRelocation *)(rel + offs);
190+
if (ELF_R_TYPE(r->r_info) == R_GLOB_DAT || ELF_R_TYPE(r->r_info) == R_ABS64) {
191+
ElfSymbol *sym = (ElfSymbol *)(symtab + ELF_R_SYM(r->r_info) * syment);
192+
if (sym->st_name != 0) {
193+
_cc->addImport((void **)(_base + r->r_offset), strtab + sym->st_name);
194+
}
195+
}
196+
}
197+
}
198+
195199
if (jmprel != NULL && pltrelsz != 0) {
196200
// Parse .rela.plt table
197201
for (size_t offs = 0; offs < pltrelsz; offs += relent) {
198202
ElfRelocation *r = (ElfRelocation *)(jmprel + offs);
199-
ElfSymbol *sym = (ElfSymbol *)(symtab + ELF_R_SYM(r->r_info) * syment);
200-
if (sym->st_name != 0) {
201-
_cc->addImport((void **)(_base + r->r_offset), strtab + sym->st_name);
202-
}
203-
}
204-
} else if (rel != NULL && relsz != 0) {
205-
// Shared library was built without PLT (-fno-plt)
206-
// Relocation entries have been moved from .rela.plt to .rela.dyn
207-
for (size_t offs = relcount * relent; offs < relsz; offs += relent) {
208-
ElfRelocation *r = (ElfRelocation *)(rel + offs);
209-
if (ELF_R_TYPE(r->r_info) == R_GLOB_DAT) {
210203
ElfSymbol *sym =
211204
(ElfSymbol *)(symtab + ELF_R_SYM(r->r_info) * syment);
212205
if (sym->st_name != 0) {
@@ -216,7 +209,6 @@ void ElfParser::parseDynamicSection() {
216209
}
217210
}
218211
}
219-
}
220212
}
221213

222214
void ElfParser::parseDwarfInfo() {

ddprof-lib/src/main/cpp/symbols_linux.h

+21-6
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,32 @@ typedef Elf32_Dyn ElfDyn;
8888
#endif // __LP64__
8989

9090
#if defined(__x86_64__)
91-
#define R_GLOB_DAT R_X86_64_GLOB_DAT
91+
# define R_GLOB_DAT R_X86_64_GLOB_DAT
92+
# define R_ABS64 R_X86_64_64
9293
#elif defined(__i386__)
93-
#define R_GLOB_DAT R_386_GLOB_DAT
94+
# define R_GLOB_DAT R_386_GLOB_DAT
95+
# define R_ABS64 -1
9496
#elif defined(__arm__) || defined(__thumb__)
95-
#define R_GLOB_DAT R_ARM_GLOB_DAT
97+
# define R_GLOB_DAT R_ARM_GLOB_DAT
98+
# define R_ABS64 -1
9699
#elif defined(__aarch64__)
97-
#define R_GLOB_DAT R_AARCH64_GLOB_DAT
100+
# define R_GLOB_DAT R_AARCH64_GLOB_DAT
101+
# define R_ABS64 R_AARCH64_ABS64
98102
#elif defined(__PPC64__)
99-
#define R_GLOB_DAT R_PPC64_GLOB_DAT
103+
# define R_GLOB_DAT R_PPC64_GLOB_DAT
104+
# define R_ABS64 -1
105+
#elif defined(__riscv) && (__riscv_xlen == 64)
106+
// RISC-V does not have GLOB_DAT relocation, use something neutral,
107+
// like the impossible relocation number.
108+
# define R_GLOB_DAT -1
109+
# define R_ABS64 -1
110+
#elif defined(__loongarch_lp64)
111+
// LOONGARCH does not have GLOB_DAT relocation, use something neutral,
112+
// like the impossible relocation number.
113+
# define R_GLOB_DAT -1
114+
# define R_ABS64 -1
100115
#else
101-
#error "Compiling on unsupported arch"
116+
# error "Compiling on unsupported arch"
102117
#endif
103118

104119
#ifdef __musl__

ddprof-lib/src/test/cpp/elfparser_ut.cpp

+55-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#ifdef __linux__
22

3+
#include <gmock/gmock.h>
34
#include <gtest/gtest.h>
45

56
#include "codeCache.h"
7+
#include "libraries.h"
68
#include "symbols_linux.h"
79
#include "log.h"
810

@@ -27,7 +29,7 @@ TEST(Elf, readSymTable) {
2729
exit(1);
2830
}
2931
char path[PATH_MAX];
30-
snprintf(path, sizeof(path) - 1, "%s/../build/test/resources/unresolved-functions/main", cwd);
32+
snprintf(path, sizeof(path) - 1, "%s/../build/test/resources/native-libs/unresolved-functions/main", cwd);
3133
if (access(path, R_OK) != 0) {
3234
fprintf(stdout, "Missing test resource %s. Skipping the test\n", path);
3335
exit(0);
@@ -36,6 +38,57 @@ TEST(Elf, readSymTable) {
3638
ElfParser::parseFile(&cc, nullptr, path, false);
3739
}
3840

41+
class ElfReladyn : public ::testing::Test {
42+
protected:
43+
Libraries* _libs = nullptr;
44+
CodeCache* _libreladyn = nullptr;
45+
46+
// This method is called before each test.
47+
void SetUp() override {
48+
char cwd[PATH_MAX - 64];
49+
if (getcwd(cwd, sizeof(cwd)) == nullptr) {
50+
exit(1);
51+
}
52+
char path[PATH_MAX];
53+
snprintf(path, sizeof(path) - 1, "%s/../build/test/resources/native-libs/reladyn-lib/libreladyn.so", cwd);
54+
if (access(path, R_OK) != 0) {
55+
fprintf(stdout, "Missing test resource %s. Skipping the test\n", path);
56+
exit(0);
57+
}
58+
void* handle = dlopen(path, RTLD_NOW);
59+
ASSERT_THAT(handle, ::testing::NotNull());
60+
61+
_libs = Libraries::instance();
62+
_libs->updateSymbols(false);
63+
_libreladyn = _libs->findLibraryByName("libreladyn");
64+
ASSERT_THAT(_libreladyn, ::testing::NotNull());
65+
}
66+
67+
// This method is called after each test.
68+
void TearDown() override {
69+
// Clean up resources.
70+
}
71+
72+
CodeCache* libreladyn() {
73+
return _libreladyn;
74+
}
75+
};
76+
77+
TEST_F(ElfReladyn, resolveFromRela_plt) {
78+
void* sym = libreladyn()->findImport(im_pthread_create);
79+
ASSERT_THAT(sym, ::testing::NotNull());
80+
}
81+
82+
TEST_F(ElfReladyn, resolveFromRela_dyn_R_GLOB_DAT) {
83+
void* sym = libreladyn()->findImport(im_pthread_setspecific);
84+
ASSERT_THAT(sym, ::testing::NotNull());
85+
}
86+
87+
TEST_F(ElfReladyn, resolveFromRela_dyn_R_ABS64) {
88+
void* sym = libreladyn()->findImport(im_pthread_exit);
89+
ASSERT_THAT(sym, ::testing::NotNull());
90+
}
91+
3992
class ElfTest : public ::testing::Test {
4093
protected:
4194
void SetUp() override {
@@ -247,7 +300,7 @@ TEST_P(ElfTestParam, invalidElfSmallMappingAfterUnmap) {
247300

248301
// Construct the path to the test resource
249302
char path[PATH_MAX];
250-
snprintf(path, sizeof(path) - 1, "%s/../build/test/resources/small-lib/libsmall-lib.so", cwd);
303+
snprintf(path, sizeof(path) - 1, "%s/../build/test/resources/native-libs/small-lib/libsmall-lib.so", cwd);
251304
if (access(path, R_OK) != 0) {
252305
fprintf(stdout, "Missing test resource %s. Skipping the test\n", path);
253306
exit(1);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
TARGET_DIR = ../build/test/resources/native-libs/reladyn-lib
2+
all:
3+
g++ -fPIC -shared -o $(TARGET_DIR)/libreladyn.so reladyn.c
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright The async-profiler authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
#include <pthread.h>
6+
#include <stdio.h>
7+
// Force pthread_setspecific into .rela.dyn with R_X86_64_GLOB_DAT.
8+
int (*indirect_pthread_setspecific)(pthread_key_t, const void*);
9+
// Force pthread_exit into .rela.dyn with R_X86_64_64.
10+
void (*static_pthread_exit)(void*) = pthread_exit;
11+
void* thread_function(void* arg) {
12+
printf("Thread running\n");
13+
return NULL;
14+
}
15+
// Not indended to be executed.
16+
int reladyn() {
17+
pthread_t thread;
18+
pthread_key_t key;
19+
pthread_key_create(&key, NULL);
20+
// Direct call, forces into .rela.plt.
21+
pthread_create(&thread, NULL, thread_function, NULL);
22+
// Assign to a function pointer at runtime, forces into .rela.dyn as R_X86_64_GLOB_DAT.
23+
indirect_pthread_setspecific = pthread_setspecific;
24+
indirect_pthread_setspecific(key, "Thread-specific value");
25+
// Use pthread_exit via the static pointer, forces into .rela.dyn as R_X86_64_64.
26+
static_pthread_exit(NULL);
27+
return 0;
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
TARGET_DIR = ../build/test/resources/native-libs/small-lib
2+
all:
3+
g++ -fPIC -shared -o $(TARGET_DIR)/libsmall-lib.so small_lib.cpp

ddprof-lib/src/test/resources/small-lib/Makefile

-3
This file was deleted.

test/native/libs/reladyn.c

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright The async-profiler authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include <pthread.h>
7+
#include <stdio.h>
8+
9+
// Force pthread_setspecific into .rela.dyn with R_X86_64_GLOB_DAT.
10+
int (*indirect_pthread_setspecific)(pthread_key_t, const void*);
11+
12+
// Force pthread_exit into .rela.dyn with R_X86_64_64.
13+
void (*static_pthread_exit)(void*) = pthread_exit;
14+
15+
void* thread_function(void* arg) {
16+
printf("Thread running\n");
17+
return NULL;
18+
}
19+
20+
// Not indended to be executed.
21+
int reladyn() {
22+
pthread_t thread;
23+
pthread_key_t key;
24+
25+
pthread_key_create(&key, NULL);
26+
27+
// Direct call, forces into .rela.plt.
28+
pthread_create(&thread, NULL, thread_function, NULL);
29+
30+
// Assign to a function pointer at runtime, forces into .rela.dyn as R_X86_64_GLOB_DAT.
31+
indirect_pthread_setspecific = pthread_setspecific;
32+
indirect_pthread_setspecific(key, "Thread-specific value");
33+
34+
// Use pthread_exit via the static pointer, forces into .rela.dyn as R_X86_64_64.
35+
static_pthread_exit(NULL);
36+
37+
return 0;
38+
}

test/native/symbolsLinuxTest.cpp

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright The async-profiler authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#ifdef __linux__
7+
8+
#include "codeCache.h"
9+
#include "profiler.h"
10+
#include "testRunner.hpp"
11+
#include <dlfcn.h>
12+
13+
#define ASSERT_RESOLVE(id) \
14+
{ \
15+
void* result = dlopen("libreladyn.so", RTLD_NOW); /* see reladyn.c */ \
16+
ASSERT(result); \
17+
Profiler::instance()->updateSymbols(false); \
18+
CodeCache* libreladyn = Profiler::instance()->findLibraryByName("libreladyn"); \
19+
ASSERT(libreladyn); \
20+
void* sym = libreladyn->findImport(id); \
21+
ASSERT(sym); \
22+
}
23+
24+
TEST_CASE(ResolveFromRela_plt) {
25+
ASSERT_RESOLVE(im_pthread_create);
26+
}
27+
28+
TEST_CASE(ResolveFromRela_dyn_R_GLOB_DAT) {
29+
ASSERT_RESOLVE(im_pthread_setspecific);
30+
}
31+
32+
TEST_CASE(ResolveFromRela_dyn_R_ABS64) {
33+
ASSERT_RESOLVE(im_pthread_exit);
34+
}
35+
36+
#endif // __linux__

0 commit comments

Comments
 (0)