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

utils: Add helpers for runtime dynamic patching #1705

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
116 changes: 116 additions & 0 deletions utils/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <stdio.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <syscall.h>
#include <unistd.h>

#ifdef HAVE_LIBUNWIND
Expand Down Expand Up @@ -1032,6 +1033,121 @@ int copy_file(const char *path_in, const char *path_out)
return 0;
}

#ifndef gettid
/**
* gettid - gettid syscall wrapper (glibc < 2.30)
* @return - thread id
*/
pid_t gettid(void)
{
return syscall(__NR_gettid);
}
#endif

#ifndef tgkill
/**
* tgkill - tgkill syscall wrapper (glibc < 2.30)
* @tgid - thread group id
* @tid - thread id
* @signal - signal to send
* @return - 0 on success, -1 on error
*/
int tgkill(pid_t tgid, pid_t tid, int signal)
{
return syscall(SYS_tgkill, tgid, tid, signal);
}
#endif

/**
* find_unused_sigrt - find a real-time signal with no associated action
* @return - unused RT signal, or -1 if none found
*/
int find_unused_sigrt(void)
{
int sig;
struct sigaction oldact;

for (sig = SIGRTMIN; sig <= SIGRTMAX; sig++) {
if (sigaction(sig, NULL, &oldact) < 0) {
pr_dbg3("failed to check RT signal handler\n");
continue;
}

if (!oldact.sa_handler)
Copy link
Owner

Choose a reason for hiding this comment

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

Also this can be racy if other thread installs a new sighandler at the same time. But there's not much we can do..

Copy link

@azharivs azharivs Aug 1, 2023

Choose a reason for hiding this comment

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

True, and furthermore, there is no guarantee that we're going to keep this signal to ourselves even if we don't get into a race condition here. The application can change our signal's disposition at any other point without notice. However, this function is only triggered upon dynamic runtime patching and unpatching via (do_dynamic_update) which we expect to be 1) infrequent 2) not happening at any particular time, e.g., application startup. Hence, the likelihood of this condition is low.

Copy link
Owner

Choose a reason for hiding this comment

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

Right, then we need to take care of releasing the rt signal after use.

return sig;
}

pr_dbg2("failed to find unused SIGRT\n");
return -1;
}

/**
* thread_broadcast_signal - send a signal to all the other running threads in
* the process
* @sig - signal to send
* @return - number of signals sent, -1 on error
*/
int thread_broadcast_signal(int sig)
{
char path[32];
DIR *dir;
struct dirent *dirent;
pid_t pid, tid, task;
int signal_count = 0;

pid = getpid();
tid = gettid();

snprintf(path, 32, "/proc/%u/task", pid);
dir = opendir(path);
if (!dir) {
pr_dbg("failed to open directory '%s'\n", path);
goto fail_open_dir;
}

errno = 0;
for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir)) {
/* skip "." and ".." directories */
if (dirent->d_name[0] == '.')
continue;

task = strtol(dirent->d_name, NULL, 10);
if (errno != 0 || task < 0) {
pr_dbg("failed to parse TID '%s'\n", dirent->d_name);
continue;
}

/* ignore our TID */
if (task == tid)
continue;

/*
* By reading /proc/<pid>/task directory, there is the possibility of
* a race condition where a thread exits before we send the signal.
*/
pr_dbg4("send SIGRT%d to %u\n", sig, task);
if (tgkill(pid, task, sig) < 0) {
if (errno != ESRCH) {
pr_dbg2("cannot signal thread %u: %s\n", task, strerror(errno));
errno = 0;
}
Copy link
Owner

Choose a reason for hiding this comment

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

It seems we should not take ESRCH as an error. Something like this?

if (tgkill(pid, task, sig) < 0) {
    if (errno != ESRCH)
        pr_dbg2(...);
    errno = 0;
}

}
else
signal_count++;
}

if (errno != 0)
pr_dbg("failed to read directory entry\n");

if (closedir(dir) < 0)
pr_dbg2("failed to close directory\n");

return signal_count;

fail_open_dir:
return -1;
}

#ifdef UNIT_TEST
TEST_CASE(utils_parse_cmdline)
{
Expand Down
6 changes: 6 additions & 0 deletions utils/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -431,4 +431,10 @@ void stacktrace(void);

int copy_file(const char *path_in, const char *path_out);

pid_t gettid(void);
int tgkill(pid_t tgid, pid_t tid, int signal);

int find_unused_sigrt(void);
int thread_broadcast_signal(int sig);

#endif /* UFTRACE_UTILS_H */