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 @@ -36,6 +39,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
11 changes: 11 additions & 0 deletions ddprof-lib/src/main/cpp/arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,17 @@ Error Arguments::parse(const char *args) {
}
}

CASE("wallscope")
if (value == NULL || value[0] == 0) {
msg = "wallscope must be 'context' or 'all'";
} else if (strcmp(value, "context") == 0) {
_wall_scope = WALL_SCOPE_CONTEXT;
} else if (strcmp(value, "all") == 0) {
_wall_scope = WALL_SCOPE_ALL;
} else {
msg = "wallscope must be 'context' or 'all'";
}

CASE("nativemem")
_nativemem = value == NULL ? 0 : parseUnits(value, BYTES);
if (_nativemem < 0) {
Expand Down
12 changes: 12 additions & 0 deletions ddprof-lib/src/main/cpp/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ enum WallclockSampler {
JVMTI
};

enum WallScope {
WALL_SCOPE_LEGACY,
WALL_SCOPE_CONTEXT,
WALL_SCOPE_ALL
};

enum Clock {
CLK_DEFAULT,
CLK_TSC,
Expand Down Expand Up @@ -161,6 +167,10 @@ class Arguments {
}

public:
bool wallScopeAllThreads() const {
return _wall_scope == WALL_SCOPE_ALL;
}

Action _action;
Ring _ring;
const char *_event;
Expand All @@ -171,6 +181,7 @@ class Arguments {
bool _wall_precheck;
int _wall_threads_per_tick;
WallclockSampler _wallclock_sampler;
WallScope _wall_scope;
long _memory;
bool _record_allocations;
bool _record_liveness;
Expand Down Expand Up @@ -212,6 +223,7 @@ class Arguments {
_wall_precheck(false),
_wall_threads_per_tick(DEFAULT_WALL_THREADS_PER_TICK),
_wallclock_sampler(ASGCT),
_wall_scope(WALL_SCOPE_CONTEXT),
_memory(-1),
_record_allocations(false),
_record_liveness(false),
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 @@ -9,6 +9,8 @@
#include "os.h"
#include "safeAccess.h"

#include <algorithm>
#include <new>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -59,7 +61,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 @@ -96,7 +100,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 @@ -299,36 +306,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 @@ -343,6 +372,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 @@ -352,6 +385,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 @@ -361,6 +398,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 @@ -371,46 +410,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 @@ -467,4 +557,3 @@ void CodeCache::setBuildId(const char* build_id, size_t build_id_len) {
}
}
}

Loading
Loading