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
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Copyright 2026, Datadog, Inc.
// SPDX-License-Identifier: Apache-2.0

package com.datadoghq.native.gtest

Expand Down Expand Up @@ -200,6 +202,11 @@ class GtestPlugin : Plugin<Project> {
val buildGtestConfigTask = project.tasks.register("buildGtest${config.capitalizedName()}") {
group = "build"
description = "Compile and link all Google Tests for the ${config.name} build (no run)"
if (extension.buildNativeLibs.get()) {
// CI executes these binaries directly, so the build-only task must also produce
// the native fixtures that the binaries load at runtime.
dependsOn("buildNativeLibs")
}
}

// Compile all library sources ONCE for this config. Each test
Expand Down
5 changes: 5 additions & 0 deletions ddprof-lib/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright 2026, Datadog, Inc.
// SPDX-License-Identifier: Apache-2.0

import com.datadoghq.native.model.Platform
import com.datadoghq.native.util.PlatformUtils
import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven
Expand Down Expand Up @@ -35,6 +38,8 @@ nativeBuild {
gtest {
testSourceDir.set(layout.projectDirectory.dir("src/test/cpp"))
mainSourceDir.set(layout.projectDirectory.dir("src/main/cpp"))
nativeLibsSourceDir.set(layout.projectDirectory.dir("src/test/resources/native-libs"))
nativeLibsOutputDir.set(rootProject.layout.buildDirectory.dir("test/resources/native-libs"))

// Include paths for compilation
val javaHome = PlatformUtils.javaHome()
Expand Down
163 changes: 126 additions & 37 deletions ddprof-lib/src/main/cpp/codeCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include "safeAccess.h"

#include <cassert>
#include <algorithm>
#include <new>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -60,7 +62,9 @@ CodeCache::CodeCache(const char *name, short lib_index,
_build_id_len = 0;
_load_bias = 0;

memset(_imports, 0, sizeof(_imports));
memset(_import_offsets, 0, sizeof(_import_offsets));
_incomplete_imports = 0;
_imports_finalized = true;
_imports_patchable = imports_patchable;

_dwarf_table = NULL;
Expand Down Expand Up @@ -99,7 +103,10 @@ void CodeCache::copyFrom(const CodeCache& other) {
}
_load_bias = other._load_bias;

memset(_imports, 0, sizeof(_imports));
_imports.clear();
memset(_import_offsets, 0, sizeof(_import_offsets));
_incomplete_imports = 0;
_imports_finalized = true;
_imports_patchable = other._imports_patchable;

_dwarf_table_length = other._dwarf_table_length;
Expand Down Expand Up @@ -355,36 +362,58 @@ void CodeCache::findSymbolsByPrefix(std::vector<const char *> &prefixes,
}

void CodeCache::saveImport(ImportId id, void** entry) {
for (int ty = 0; ty < NUM_IMPORT_TYPES; ty++) {
if (_imports[id][ty] == nullptr) {
_imports[id][ty] = entry;
return;
}
if (entry == nullptr || id < 0 || id >= NUM_IMPORTS) {
return;
}
try {
_imports.push_back({id, entry});
_imports_finalized = false;
} catch (const std::bad_alloc&) {
_incomplete_imports |= 1ULL << id;
}
}

void CodeCache::addImport(void **entry, const char *name) {
switch (name[0]) {
case 'a':
if (strcmp(name, "aligned_alloc") == 0) {
if (strcmp(name, "accept") == 0) {
saveImport(im_accept, entry);
} else if (strcmp(name, "accept4") == 0) {
saveImport(im_accept4, entry);
} else if (strcmp(name, "aligned_alloc") == 0) {
saveImport(im_aligned_alloc, entry);
}
break;
case 'c':
if (strcmp(name, "calloc") == 0) {
saveImport(im_calloc, entry);
} else if (strcmp(name, "close") == 0) {
saveImport(im_close, entry);
} else if (strcmp(name, "connect") == 0) {
saveImport(im_connect, entry);
}
break;
case 'd':
if (strcmp(name, "dlopen") == 0) {
saveImport(im_dlopen, entry);
} else if (strcmp(name, "dup2") == 0) {
saveImport(im_dup2, entry);
} else if (strcmp(name, "dup3") == 0) {
saveImport(im_dup3, entry);
}
break;
case 'f':
if (strcmp(name, "free") == 0) {
saveImport(im_free, entry);
}
break;
case 'e':
if (strcmp(name, "epoll_wait") == 0) {
saveImport(im_epoll_wait, entry);
} else if (strcmp(name, "epoll_pwait") == 0) {
saveImport(im_epoll_pwait, entry);
}
break;
case 'm':
if (strcmp(name, "malloc") == 0) {
saveImport(im_malloc, entry);
Expand All @@ -399,6 +428,10 @@ void CodeCache::addImport(void **entry, const char *name) {
saveImport(im_pthread_setspecific, entry);
} else if (strcmp(name, "poll") == 0) {
saveImport(im_poll, entry);
} else if (strcmp(name, "ppoll") == 0) {
saveImport(im_ppoll, entry);
} else if (strcmp(name, "pselect") == 0) {
saveImport(im_pselect, entry);
} else if (strcmp(name, "posix_memalign") == 0) {
saveImport(im_posix_memalign, entry);
}
Expand All @@ -408,6 +441,10 @@ void CodeCache::addImport(void **entry, const char *name) {
saveImport(im_realloc, entry);
} else if (strcmp(name, "recv") == 0) {
saveImport(im_recv, entry);
} else if (strcmp(name, "recvfrom") == 0) {
saveImport(im_recvfrom, entry);
} else if (strcmp(name, "recvmsg") == 0) {
saveImport(im_recvmsg, entry);
} else if (strcmp(name, "read") == 0) {
saveImport(im_read, entry);
}
Expand All @@ -417,6 +454,8 @@ void CodeCache::addImport(void **entry, const char *name) {
saveImport(im_send, entry);
} else if (strcmp(name, "sigaction") == 0) {
saveImport(im_sigaction, entry);
} else if (strcmp(name, "select") == 0) {
saveImport(im_select, entry);
}
break;
case 'w':
Expand All @@ -427,46 +466,97 @@ void CodeCache::addImport(void **entry, const char *name) {
}
}

void **CodeCache::findImport(ImportId id) {
if (!_imports_patchable) {
makeImportsPatchable();
_imports_patchable = true;
void CodeCache::finalizeImports() {
if (_imports_finalized) {
return;
}
return _imports[id][PRIMARY];
}

void CodeCache::patchImport(ImportId id, void *hook_func) {
if (!_imports_patchable) {
makeImportsPatchable();
_imports_patchable = true;
std::sort(_imports.begin(), _imports.end(), [](const ImportLocation& a,
const ImportLocation& b) {
if (a._id != b._id) {
return a._id < b._id;
}
return reinterpret_cast<uintptr_t>(a._location) <
reinterpret_cast<uintptr_t>(b._location);
});
_imports.erase(std::unique(_imports.begin(), _imports.end(),
[](const ImportLocation& a, const ImportLocation& b) {
return a._id == b._id && a._location == b._location;
}), _imports.end());

memset(_import_offsets, 0, sizeof(_import_offsets));
for (const ImportLocation& entry : _imports) {
_import_offsets[entry._id + 1]++;
}
for (int id = 0; id < NUM_IMPORTS; id++) {
_import_offsets[id + 1] += _import_offsets[id];
}
_imports_finalized = true;
}

for (int ty = 0; ty < NUM_IMPORT_TYPES; ty++) {void **entry = _imports[id][ty];
if (entry != NULL) {
*entry = hook_func;
}}
size_t CodeCache::importCount(ImportId id) {
if (id < 0 || id >= NUM_IMPORTS) {
return 0;
}
finalizeImports();
return _import_offsets[id + 1] - _import_offsets[id];
}

void CodeCache::makeImportsPatchable() {
void **min_import = (void **)-1;
void **max_import = NULL;
for (int i = 0; i < NUM_IMPORTS; i++) {
for (int j = 0; j < NUM_IMPORT_TYPES; j++) {
void** entry = _imports[i][j];
if (entry == NULL) continue;
if (entry < min_import)
bool CodeCache::importsComplete(ImportId id) const {
return id >= 0 && id < NUM_IMPORTS &&
(_incomplete_imports & (1ULL << id)) == 0;
}

bool CodeCache::prepareImportsForPatch() {
if (_imports_patchable) {
return true;
}
return makeImportsPatchable();
}

void **CodeCache::findImport(ImportId id, size_t index) {
if (id < 0 || id >= NUM_IMPORTS || index >= importCount(id)) {
return nullptr;
}
if (!prepareImportsForPatch()) {
return nullptr;
}
return _imports[_import_offsets[id] + index]._location;
}

bool CodeCache::patchImport(ImportId id, void *hook_func) {
if (!prepareImportsForPatch()) {
return false;
}
size_t count = importCount(id);
for (size_t index = 0; index < count; index++) {
*_imports[_import_offsets[id] + index]._location = hook_func;
}
return true;
}

bool CodeCache::makeImportsPatchable() {
finalizeImports();
uintptr_t min_import = UINTPTR_MAX;
uintptr_t max_import = 0;
for (const ImportLocation& import : _imports) {
uintptr_t entry = reinterpret_cast<uintptr_t>(import._location);
if (entry < min_import)
min_import = entry;
if (entry > max_import)
max_import = entry;
}
}

if (max_import != NULL) {
uintptr_t patch_start = (uintptr_t)min_import & ~OS::page_mask;
uintptr_t patch_end = (uintptr_t)max_import & ~OS::page_mask;
mprotect((void *)patch_start, patch_end - patch_start + OS::page_size,
PROT_READ | PROT_WRITE);
if (max_import != 0) {
uintptr_t patch_start = min_import & ~OS::page_mask;
uintptr_t patch_end = max_import & ~OS::page_mask;
if (mprotect((void *)patch_start, patch_end - patch_start + OS::page_size,
PROT_READ | PROT_WRITE) != 0) {
return false;
}
}
_imports_patchable = true;
return true;
}

void CodeCache::setDwarfTable(FrameDesc *table, int length, const FrameDesc &default_frame) {
Expand Down Expand Up @@ -527,4 +617,3 @@ void CodeCache::setBuildId(const char* build_id, size_t build_id_len) {
}
}
}

42 changes: 32 additions & 10 deletions ddprof-lib/src/main/cpp/codeCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include <atomic>
#include <jvmti.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
Expand All @@ -28,6 +29,8 @@ const int MAX_NATIVE_LIBS = 2048;

enum ImportId {
im_dlopen,
im_dup2,
im_dup3,
im_pthread_create,
im_pthread_exit,
im_pthread_setspecific,
Expand All @@ -43,15 +46,20 @@ enum ImportId {
im_recv,
im_write,
im_read,
im_close,
im_connect,
im_accept,
im_accept4,
im_recvfrom,
im_recvmsg,
im_epoll_wait,
im_epoll_pwait,
im_ppoll,
im_select,
im_pselect,
NUM_IMPORTS
};

enum ImportType {
PRIMARY,
SECONDARY,
NUM_IMPORT_TYPES
};

enum Mark {
MARK_VM_RUNTIME = 1,
MARK_INTERPRETER = 2,
Expand Down Expand Up @@ -137,6 +145,13 @@ class CodeBlob {

class CodeCache {
private:
static_assert(NUM_IMPORTS <= 64, "import completeness mask must cover every import");

struct ImportLocation {
ImportId _id;
void** _location;
};

char *_name;
short _lib_index;
const void *_min_address;
Expand All @@ -152,7 +167,10 @@ class CodeCache {
size_t _build_id_len; // Build-id length in bytes (raw, not hex string length)
uintptr_t _load_bias; // Load bias (image_base - file_base address)

void **_imports[NUM_IMPORTS][NUM_IMPORT_TYPES];
std::vector<ImportLocation> _imports;
size_t _import_offsets[NUM_IMPORTS + 1];
u64 _incomplete_imports;
bool _imports_finalized;
bool _imports_patchable;
bool _debug_symbols;

Expand All @@ -177,7 +195,8 @@ class CodeCache {
std::atomic<bool> _published;

void expand();
void makeImportsPatchable();
void finalizeImports();
bool makeImportsPatchable();
void saveImport(ImportId id, void** entry);
void copyFrom(const CodeCache& other);

Expand Down Expand Up @@ -264,8 +283,11 @@ class CodeCache {
}

void addImport(void **entry, const char *name);
void **findImport(ImportId id);
void patchImport(ImportId, void *hook_func);
size_t importCount(ImportId id);
bool importsComplete(ImportId id) const;
bool prepareImportsForPatch();
void **findImport(ImportId id, size_t index = 0);
bool patchImport(ImportId, void *hook_func);

CodeBlob *findBlob(const char *name);
CodeBlob *findBlobByAddress(const void *address);
Expand Down
Loading
Loading