Skip to content
Merged
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
28 changes: 27 additions & 1 deletion dd-java-agent/agent-tooling/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ minimumBranchCoverage = 0.6
excludedClassesCoverage += ['datadog.trace.agent.tooling.*']

sourceSets {
register("main_java25") {
java {
srcDirs = [file('src/main/java25')]
}
}
register("test_java11") {
java {
srcDirs = [file('src/test/java11')]
Expand All @@ -19,11 +24,17 @@ sourceSets {
srcDirs = [file('src/test/java21')]
}
}
named("main_java25") {
compileClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.main.output
}
named("test") {
compileClasspath += sourceSets.test_java11.output
runtimeClasspath += sourceSets.test_java11.output
compileClasspath += sourceSets.test_java21.output
runtimeClasspath += sourceSets.test_java21.output
compileClasspath += sourceSets.main_java25.output
runtimeClasspath += sourceSets.main_java25.output
}
}

Expand All @@ -39,6 +50,7 @@ configurations {
}
}


dependencies {
api(project(':dd-java-agent:agent-bootstrap')) {
exclude group: 'com.datadoghq', module: 'agent-logging'
Expand All @@ -53,13 +65,15 @@ dependencies {
implementation group: 'net.java.dev.jna', name: 'jna-platform', version: '5.8.0'

api project(':dd-trace-core')

implementation project(':dd-java-agent:agent-crashtracking')

main_java25Implementation project(':dd-trace-core')

testImplementation project(':dd-java-agent:testing')
testImplementation libs.bytebuddy
testImplementation group: 'com.google.guava', name: 'guava-testlib', version: '20.0'


jmhImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.3.5.RELEASE'
}

Expand All @@ -68,6 +82,10 @@ jmh {
includeTests = true
}

tasks.named("jar") {
from(sourceSets.main_java25.output)
}

tasks.named("compileJava") { dependsOn 'generateClassNameTries' }
tasks.named("sourcesJar") { dependsOn 'generateClassNameTries' }

Expand All @@ -88,6 +106,14 @@ tasks.named("compileTestGroovy") {
" otherwise anonymous class has one `loadClass` accessor's signature has `java.lang.Module`"
)
}
tasks.named("compileMain_java25Java") {
configureCompiler(
it,
25,
JavaVersion.VERSION_1_8,
"Java 25 sourceset for Foreign Function & Memory API, compiled with Java 25 but targeting Java 8 bytecode."
)
}
tasks.named("compileTest_java11Java") {
configureCompiler(it, 11, JavaVersion.VERSION_11)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration;
import datadog.trace.core.CoreTracer;
import datadog.trace.core.servicediscovery.ForeignMemoryWriter;
import datadog.trace.core.servicediscovery.ForeignMemoryWriterFactory;
import datadog.trace.core.servicediscovery.ServiceDiscovery;
import datadog.trace.core.servicediscovery.ServiceDiscoveryFactory;
import de.thetaphi.forbiddenapis.SuppressForbidden;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -57,20 +57,12 @@ private static ServiceDiscoveryFactory serviceDiscoveryFactory() {
return TracerInstaller::initServiceDiscovery;
}

@SuppressForbidden // intentional use of Class.forName
private static ServiceDiscovery initServiceDiscovery() {
try {
// use reflection to load MemFDUnixWriter so it doesn't get picked up when we
// transitively look for all tracer class dependencies to install in GraalVM via
// VMRuntimeInstrumentation
Class<?> memFdClass =
Class.forName("datadog.trace.agent.tooling.servicediscovery.MemFDUnixWriter");
ForeignMemoryWriter memFd = (ForeignMemoryWriter) memFdClass.getConstructor().newInstance();
return new ServiceDiscovery(memFd);
} catch (Throwable e) {
log.debug("service discovery not supported", e);
return null;
final ForeignMemoryWriter writer = new ForeignMemoryWriterFactory().get();
if (writer != null) {
return new ServiceDiscovery(writer);
}
return null;
}

public static void installGlobalTracer(final CoreTracer tracer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,23 @@

import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;

import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import datadog.environment.OperatingSystem;
import datadog.environment.SystemProperties;
import datadog.trace.core.servicediscovery.ForeignMemoryWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MemFDUnixWriter implements ForeignMemoryWriter {
abstract class MemFDUnixWriter implements ForeignMemoryWriter {
private static final Logger log = LoggerFactory.getLogger(MemFDUnixWriter.class);

private interface LibC extends Library {
int syscall(int number, Object... args);
protected abstract long syscall(long number, String name, int flags);

NativeLong write(int fd, Pointer buf, NativeLong count);
protected abstract long write(int fd, byte[] payload);

int fcntl(int fd, int cmd, int arg);
}
protected abstract int fcntl(int fd, int cmd, int arg);

protected abstract int getLastError();

// https://elixir.bootlin.com/linux/v6.17.1/source/include/uapi/linux/memfd.h#L8-L9
private static final int MFD_CLOEXEC = 0x0001;
Expand All @@ -36,36 +33,33 @@ private interface LibC extends Library {
private static final int F_SEAL_GROW = 0x0004;

@Override
public void write(String fileName, byte[] payload) {
final LibC libc = Native.load("c", LibC.class);

public final void write(String fileName, byte[] payload) {
OperatingSystem.Architecture arch = OperatingSystem.architecture();
int memfdSyscall = getMemfdSyscall(arch);
if (memfdSyscall <= 0) {
log.debug(SEND_TELEMETRY, "service discovery not supported for arch={}", arch);
log.debug(
SEND_TELEMETRY,
"service discovery not supported for arch={}",
SystemProperties.get("os.arch"));
return;
}
int memFd = libc.syscall(memfdSyscall, fileName, MFD_CLOEXEC | MFD_ALLOW_SEALING);
int memFd = (int) syscall(memfdSyscall, fileName, MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (memFd < 0) {
log.warn("{} memfd create failed, errno={}", fileName, Native.getLastError());
log.warn("{} memfd create failed, errno={}", fileName, getLastError());
return;
}

log.debug("{} memfd created (fd={})", fileName, memFd);

Memory buf = new Memory(payload.length);
buf.write(0, payload, 0, payload.length);

NativeLong written = libc.write(memFd, buf, new NativeLong(payload.length));
if (written.longValue() != payload.length) {
long written = write(memFd, payload);
if (written != payload.length) {
log.warn("write to {} memfd failed errno={}", fileName, Native.getLastError());
return;
}
log.debug("wrote {} bytes to memfd {}", written.longValue(), memFd);
int returnCode = libc.fcntl(memFd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL);
log.debug("wrote {} bytes to memfd {}", written, memFd);
int returnCode = fcntl(memFd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL);
if (returnCode == -1) {
log.warn("failed to add seal to {} memfd errno={}", fileName, Native.getLastError());
return;
}
// memfd is not closed to keep it readable for the lifetime of the process.
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package datadog.trace.agent.tooling.servicediscovery;

import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;

public final class MemFDUnixWriterJNA extends MemFDUnixWriter {
private final LibC libc = Native.load("c", LibC.class);

private interface LibC extends Library {
long syscall(long number, Object... args);

NativeLong write(int fd, Pointer buf, NativeLong count);

int fcntl(int fd, int cmd, int arg);
}

@Override
protected long syscall(long number, String name, int flags) {
return libc.syscall(number, name, flags);
}

@Override
protected long write(int fd, byte[] payload) {
Memory buf = new Memory(payload.length);
buf.write(0, payload, 0, payload.length);
return libc.write(fd, buf, new NativeLong(payload.length)).longValue();
}

@Override
protected int fcntl(int fd, int cmd, int arg) {
return libc.fcntl(fd, cmd, arg);
}

@Override
protected int getLastError() {
return Native.getLastError();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package datadog.trace.agent.tooling.servicediscovery;

import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.StructLayout;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MemFDUnixWriterFFM extends MemFDUnixWriter {
private static final Logger log = LoggerFactory.getLogger(MemFDUnixWriterFFM.class);

// Captured call state layout for errno
private static final StructLayout CAPTURE_STATE_LAYOUT = Linker.Option.captureStateLayout();
private static final long ERRNO_OFFSET =
CAPTURE_STATE_LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("errno"));

// Function handles - initialized once
private final MethodHandle syscallMH;
private final MethodHandle writeMH;
private final MethodHandle fcntlMH;

private final MemorySegment captureState;

public MemFDUnixWriterFFM() {
final Linker linker = Linker.nativeLinker();
final SymbolLookup LIBC = linker.defaultLookup();

// Allocate memory for capturing errno (need to be alive until the class instance is collected)
this.captureState = Arena.ofAuto().allocate(CAPTURE_STATE_LAYOUT);

// long syscall(long number, ...)
// Note: variadic functions require special handling, we'll use a fixed signature
syscallMH =
linker.downcallHandle(
LIBC.find("syscall").orElseThrow(),
FunctionDescriptor.of(
ValueLayout.JAVA_LONG, // return type: long
ValueLayout.JAVA_LONG, // syscall number
ValueLayout.ADDRESS, // const char* name
ValueLayout.JAVA_INT // int flags
),
Linker.Option.captureCallState("errno"));

// ssize_t write(int fd, const void *buf, size_t count)
writeMH =
linker.downcallHandle(
LIBC.find("write").orElseThrow(),
FunctionDescriptor.of(
ValueLayout.JAVA_LONG, // return type: ssize_t
ValueLayout.JAVA_INT, // int fd
ValueLayout.ADDRESS, // const void* buf
ValueLayout.JAVA_LONG // size_t count
),
Linker.Option.captureCallState("errno"));

// int fcntl(int fd, int cmd, ... /* arg */)
fcntlMH =
linker.downcallHandle(
LIBC.find("fcntl").orElseThrow(),
FunctionDescriptor.of(
ValueLayout.JAVA_INT, // return type: int
ValueLayout.JAVA_INT, // int fd
ValueLayout.JAVA_INT, // int cmd
ValueLayout.JAVA_INT // int arg
),
Linker.Option.captureCallState("errno"));
}

@Override
protected long syscall(long number, String name, int flags) {
try (Arena arena = Arena.ofConfined()) {
// Allocate native string for file name
MemorySegment fileNameSegment = arena.allocateFrom(name);
// Call memfd_create via syscall, passing captureState as first arg
return (long) syscallMH.invoke(captureState, (long) number, fileNameSegment, flags);
} catch (Throwable t) {
log.error("Unable to make a syscall through FFM", t);
return -1;
}
}

@Override
protected long write(int fd, byte[] payload) {
try (Arena arena = Arena.ofConfined()) {
// Allocate native memory for payload
MemorySegment buffer = arena.allocate(payload.length);
MemorySegment.copy(payload, 0, buffer, ValueLayout.JAVA_BYTE, 0, payload.length);

// Write payload to memfd, passing captureState as first arg
return (long) writeMH.invoke(captureState, fd, buffer, (long) payload.length);
} catch (Throwable t) {
log.error("Unable to make a write call through FFM", t);
return -1;
}
}

@Override
protected int fcntl(int fd, int cmd, int arg) {
try {
return (int) fcntlMH.invoke(captureState, fd, cmd, arg);
} catch (Throwable t) {
log.error("Unable to make a fcntl call through FFM", t);
return -1;
}
}

@Override
protected int getLastError() {
try {
// Read errno from the captured state memory segment
return captureState.get(ValueLayout.JAVA_INT, ERRNO_OFFSET);
} catch (Throwable t) {
log.error("Unable to read errno from captured state", t);
return -1;
}
}
}
Loading
Loading