-
-
Notifications
You must be signed in to change notification settings - Fork 815
[Question] Add logging around native method in Java agent #1789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
There are two approaches to this. The simplest solution would be to Member substitution where you can replace the method with a delegate. A more complex, you can use an agent and a native method prefix. This would allow you to instrument the method as if it was written in byte code, but it is a bit more challenging to setup |
Thanks @raphw, Method m;
try {
m = SleepSubstitution.class.getMethod("sleep2", long.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
agentBuilder = agentBuilder
.type(ElementMatchers.named("java.lang.Thread"))
.transform((builder, typeDescription, classLoader, module, isRedefinition) ->
builder.visit(MemberSubstitution.strict()
.method(ElementMatchers.named("sleep")
.and(ElementMatchers.takesArguments(long.class))
.and(ElementMatchers.returns(void.class)))
.replaceWith(m)
.on(ElementMatchers.any())));
return agentBuilder; my custom sleep2 method signature - public static void sleep2(long millis) throws InterruptedException I also confirmed that the builder correctly identifies the Interestingly, I observed the following error, even though I am not substituting the [Byte Buddy] ERROR java.lang.Thread [null, module java.base, Thread[main,5,main], loaded=true] |
You would need to add a custom |
My Agent already injects advices into java.lang classes, so I already have this configuration in place: new AgentBuilder
.Default()
.ignore(none())
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.RedefinitionStrategy.DiscoveryStrategy.Reiterating.INSTANCE)
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
.with(AgentBuilder.TypeStrategy.Default.REBASE); |
Of course, the exception is You are intercepting methods where no methods are substituted. |
@raphw agentBuilder = agentBuilder
.type(ElementMatchers.named("java.lang.Thread"))
.transform((builder, typeDescription, classLoader, module, isRedefinition) ->
builder.visit(MemberSubstitution.strict()
.method(ElementMatchers.named("sleep")
.and(ElementMatchers.takesArguments(long.class))
.and(ElementMatchers.returns(void.class)))
.replaceWith(m)
.on(ElementMatchers.any()))); with another specific method: m = SleepSubstitution.class.getMethod("sleep2", long.class); public class SleepSubstitution {
public static void sleep2(long millis) throws InterruptedException {
System.out.println("Intercepted sleep: " + millis);
}
} that has the same signature (the only difference is the native keyword). I didn't provide anything related to the |
I meant this method: https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/asm/MemberSubstitution.java#L273 I am however unsure if we misunderstand each other. Substitution is replacing method calls and field access within a method. So you can replace the call to
But you cannot instrument |
Yes, I added this: agentBuilder = agentBuilder
.type(ElementMatchers.named("java.lang.Thread"))
.transform((builder, typeDescription, classLoader, module, isRedefinition) ->
builder.visit(MemberSubstitution.strict()
.method(ElementMatchers.named("sleep")
.and(ElementMatchers.takesArguments(long.class))
.and(ElementMatchers.returns(void.class)))
.replaceWith(m)
.failIfNoMatch(false)
.on(ElementMatchers.any())));
return agentBuilder; but I don't know how it could help, as I don't understand the reason why I can see this error. |
But in your case, you are only applying the transformation to |
Hmm, I'm wondering - when I want to substitute Thread.sleep(long) calls (replace every Thread.sleep(long) call made in the monitored application) with my custom method, I need to transform Thread, but now I'm questioning whether this is correct. Would you say that I should target a different type in my scenario? |
You would need to transform any class that contains the method call, what is potentially any type. |
You're right! When I changed the monitoring to any type (excluding the try {
m = SleepSubstitution.class.getMethod("sleep2", long.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
agentBuilder = agentBuilder
.with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
.type(ElementMatchers.not(ElementMatchers.nameContains("org.threadmonitoring.SleepSubstitution")))
.transform((builder, typeDescription, classLoader, module, isRedefinition) ->
builder.visit(MemberSubstitution.strict()
.method(ElementMatchers.named("sleep")
.and(ElementMatchers.takesArguments(long.class))
.and(ElementMatchers.returns(void.class)))
.replaceWith(m)
.failIfNoMatch(false)
.on(ElementMatchers.any()))); However, my lambda is still not instrumented. Here’s the monitored code: ExecutorService executor = Executors.newFixedThreadPool(10);
executor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
);
executor.execute(() -> {
try {
System.out.println("Calling Sleep 2000");
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
executor.shutdown(); The
Isn't this a bug? The Java Agent is built with JDK 11, the monitored application is running on Java 17. |
Lambda expressions are just synthetic methods in the declaring class. Are you ignoring synthetics? |
Isn't |
The lambda strategy should not be relevant, as the code is contained elsewhere. But you are right, it should be instrumented. Could you create a simple reproducer? |
Yes, I have created a simple reproducer and attached the following files: monitored_application.zip |
You do not want to retransform, you want to decorate the class. Use
|
It works! Now the method call is replaced in lambdas. Why does the Decorate type strategy work, but Rebase doesn't? |
Rebase pretends like it is creating a subclass but then merges the subclass into the base class. This way, the method is not considered declared, and can therefore not be visited. With decoration, you run Byte Buddy in a different mode where this is not the case. |
Sure, thank you @raphw, you helped me a lot! |
Hi,
I’m working on a Java agent program and would like to add logging around certain native methods from java.lang.Thread and java.lang.Object. For example, I want to log the milliseconds argument when calling Thread.sleep(long milliseconds) or track how many times Object.notify() is called. Is there a way to achieve this kind of logging for native methods? I’ve tried several combinations of adding Advice at entry, but while it works for non-native methods, it doesn’t work for the native ones.
The text was updated successfully, but these errors were encountered: