Skip to content
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

[uprobe] support for pid targeting for shared libs #2830

Merged
merged 1 commit into from
Nov 29, 2023

Conversation

jordalgo
Copy link
Contributor

@jordalgo jordalgo commented Nov 17, 2023

This adds support for specifying the pid even when targeting a uprobe/uretprobe in a library shared by multiple pids.

Example:

sudo bpftrace -p 1899508 -e 'uprobe:libc:getaddrinfo {
  print((comm, pid));
}

Issue 2817

Checklist
  • Language changes are updated in man/adoc/bpftrace.adoc and if needed in docs/reference_guide.md
  • User-visible and non-trivial changes updated in CHANGELOG.md
  • The new behaviour is covered by tests

@jordalgo
Copy link
Contributor Author

Note: I looked into making a runtime test for this but it seems pretty tricky in that you'd have to run two programs that use the same shared library and assert you weren't getting samples for one of them.

@ajor
Copy link
Member

ajor commented Nov 17, 2023

I'm a bit conflicted about this feature. I can definitely see it being useful to retrofit PID-filtering into existing scripts, but I feel like the better way of doing this would be to have the script include a PID predicate itself:

uprobe:libc:getaddrinfo
/ $# == 0 || pid == $1 /
{
  print((comm, pid));
}

If we sort out script-level argument parsing properly then this could be made more powerful by filtering on multiple PIDs, e.g. --pid 123 --pid 124 or --pids 123,124 and / @cli_pids[pid] == 1 /.

@ajor
Copy link
Member

ajor commented Nov 17, 2023

Seems reasonable to put in at least until we do have proper argument parsing support though.

This new feature applies to all uprobes, not just on shared libs, right?
A runtime test could be done as a program which takes a command line argument, and which is invoked multiple times with different arguments. If we trace this program and print the argument then we can check we've attached to the right PID.

@jordalgo
Copy link
Contributor Author

If we sort out script-level argument parsing properly then this could be made more powerful by filtering on multiple PIDs

It's definitely more powerful doing it this way but I think this change is (at least for me) about matching expected behavior. Because we already support pid filtering for USDTs and uprobes/uretprobes (when there is a wildcard involved) I feel like it makes sense to do this as well for all uprobe/uretprobe invocations.

This new feature applies to all uprobes, not just on shared libs, right?

Yeah I believe so.

A runtime test could be done as a program which takes a command line argument, and which is invoked multiple times with different arguments. If we trace this program and print the argument then we can check we've attached to the right PID.

Maybe I'm confused but I think the problem is not so much attaching to the right pid but testing that we're not attaching to the other pids (or maybe you're saying I should write a NOT_EXPECT predicate). I could write a simple test that passing a pid argument doesn't affect attaching, which ensures we're not breaking existing behavior, but is not testing the new behavior. Maybe an example would help my brain 🧠

@ajor
Copy link
Member

ajor commented Nov 17, 2023

If we sort out script-level argument parsing properly then this could be made more powerful by filtering on multiple PIDs

It's definitely more powerful doing it this way but I think this change is (at least for me) about matching expected behavior. Because we already support pid filtering for USDTs and uprobes/uretprobes (when there is a wildcard involved) I feel like it makes sense to do this as well for all uprobe/uretprobe invocations.

Yep, makes sense.

For the tests, I was imagining something like this:

BEFORE ./a.out 123
BEFORE ./a.out 456 # SOMEHOW PICK THIS ONE
BEFORE ./a.out 789
RUN bpftrace -p $PID -e 'BEGIN { print("BEGIN") } uprobe:a.out:func { print(arg0) }' END { print("END") }'
EXPECT "BEGIN\n456\nEND"
TIMEOUT 5

Although I don't know how well the test framework will cope with this, particularly getting the PID of a process.

@jordalgo
Copy link
Contributor Author

@ajor Thanks for the suggestion on the test. I got around the limitation of having multiple BEFOREs but needing the BEFORE_PID by just spinning up a program that forks. I confirmed that this test fails on master.

tests/testprogs/uprobe_fork_loop.c Dismissed Show dismissed Hide dismissed
Copy link
Contributor

@viktormalik viktormalik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we sort out script-level argument parsing properly then this could be made more powerful by filtering on multiple PIDs

It's definitely more powerful doing it this way but I think this change is (at least for me) about matching expected behavior. Because we already support pid filtering for USDTs and uprobes/uretprobes (when there is a wildcard involved) I feel like it makes sense to do this as well for all uprobe/uretprobe invocations.

Agreed with @jordalgo here, this is the expected behaviour to me. I don't see a situation where I would want to attach binary uprobes to a PID but attach library uprobes system-wide.

Just a couple of remarks, otherwise looks good, thanks!

@@ -191,7 +191,7 @@ AttachedProbe::AttachedProbe(Probe &probe,
// If BPF_PROG_TYPE_RAW_TRACEPOINT is available, no need to attach prog
// to anything -- we will simply BPF_PROG_RUN it
if (!feature.has_raw_tp_special())
attach_uprobe(safe_mode);
attach_uprobe(0, safe_mode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that this is correct. IIUC, we use process' PID in this case, set in bpftrace.cpp:1090. This should probably use the same approach.

src/attached_probe.cpp Show resolved Hide resolved
tests/runtime/scripts/uprobe_pid_check.bt Show resolved Hide resolved
tests/testprogs/uprobe_fork_loop.c Outdated Show resolved Hide resolved
Comment on lines 26 to 28
if (fork() == 0)
{
spin();
}
else
{
spin();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spin() is run in both the true and false cases - can the if-statement be removed entirely?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. It can. This is what I get from just copying from the interwebs

tests/testprogs/uprobe_fork_loop.c Fixed Show resolved Hide resolved
tests/testprogs/uprobe_fork_loop.c Fixed Show resolved Hide resolved
@jordalgo
Copy link
Contributor Author

Updated based on comments.

Copy link
Contributor

@viktormalik viktormalik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just two nits (see the CodeQL warnings).

tests/testprogs/uprobe_fork_loop.c Fixed Show resolved Hide resolved
tests/testprogs/uprobe_fork_loop.c Fixed Show resolved Hide resolved
@jordalgo
Copy link
Contributor Author

Lint fixes.

@@ -30,7 +30,8 @@ class AttachedProbe
BpfProgram &&prog,
int pid,
BPFfeature &feature,
BTF &btf);
BTF &btf,
bool safe_mode = false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why default to non safe mode?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This default isn't being used by anything checking safe_mode so fine to make it default to true.


int main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
{
pid_t p = fork();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we wait for the child? Otherwise zombie process right

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want them to be running at the same time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah. Thought spin() had finite iterations. If we rely on runtime test runner to kill the process group this should be fine

@jordalgo
Copy link
Contributor Author

make safe_mode default to true

This adds support for specifying the pid even when targeting
a uprobe/uretprobe in a library shared by multiple pids.

Example:
```
sudo bpftrace -p 1899508 -e 'uprobe:libc:getaddrinfo {
  print((comm, pid));
}
```

[Issue 2817](bpftrace#2817)
@danobi danobi merged commit 06aa4aa into bpftrace:master Nov 29, 2023
18 checks passed
@jordalgo jordalgo deleted the uprobe-pid-targeting branch December 18, 2023 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants