How a thread can commit suicide with syscall only in C Linux - c

I wrote the following code, the goal is to make thread to suicide with syscall (without calling pthread_exit()).
So I create two threads and select one that get its thread id, and send SIGKILL to itself with tkill()
The problem is that all the process terminates, not only the selected thread.
Why is that? How can I makes thread to suicide with syscall only?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
void *myThreadFun(void *vargp)
{
int tid = syscall(SYS_gettid);
if(tid %2 ==0)
{
printf("kill thread id ! = %ld \n",tid);
syscall(SYS_tkill,tid,9);
}
while(1)
{
printf("Thread ID: %d\n", tid);
sleep(2);
}
}
int main()
{
int i;
pthread_t tid;
printf("main thread id = %ld \n",syscall(SYS_gettid));
for (i = 0; i < 2; i++)
pthread_create(&tid, NULL, myThreadFun, NULL);
char tmp;
scanf("%c",&tmp);
return 0;
}
output:
main thread id = 11911
kill thread id ! = 11912
Killed

There is no "kill this thread only" signal disposition; there are only "ignore", "terminate the entire process", "terminate the entire process and dump a core file", "stop (pause) the entire process", and "continue the entire process if it is stopped (paused)". You cannot even choose the disposition per se; only between the default disposition for that particular signal, "ignore", or an userspace signal handler function.
There is really only one option: the SYS_exit syscall.
In C, you can do this via
#include <unistd.h>
#include <sys/syscall.h>
static inline void exit_thread(void) __attribute__((noreturn));
static inline void exit_thread(void)
{
syscall(SYS_exit, 0);
}
A real-world example always beats a snippet, I think. Consider the following program:
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
void *thread_function(void *payload)
{
#ifdef DO_EXIT
const int exit_code = (int)(intptr_t)payload;
fprintf(stderr, "Running thread_function(%p); calling syscall(SYS_exit, %d).\n", payload, exit_code);
fflush(stderr);
syscall(SYS_exit, exit_code);
#else
fprintf(stderr, "Running thread_function(%p); calling pthread_exit(%p).\n", payload, payload);
fflush(stderr);
pthread_exit(payload);
#endif
return NULL; /* Never reached */
}
int main(void)
{
void *const payload = thread_function; /* Just some random pointer value */
pthread_t thread_id;
void *thread_status;
int err;
printf("Calling pthread_create(&thread_id, NULL, thread_function, %p): ", payload);
fflush(stdout);
err = pthread_create(&thread_id, NULL, thread_function, payload);
if (err) {
printf("Failed: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
printf("Success.\n");
printf("Calling pthread_join(thread_id, &thread_status): ");
fflush(stdout);
err = pthread_join(thread_id, &thread_status);
if (err) {
printf("Failed: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
printf("Success; thread_status == %p.\n", thread_status);
return EXIT_SUCCESS;
}
Save it as example.c, then compile one version, ex1, that uses pthread_exit():
gcc -Wall -Wextra -O2 example.c -pthread -o ex1
and another, ex2, that uses syscall(SYS_exit,):
gcc -Wall -Wextra -DDO_EXIT -O2 example.c -pthread -o ex2
To see exactly what is happening, run each example under strace, logging each clone, write, futex, exit, and exit_group syscalls the program makes:
strace -f -e clone,write,futex,exit,exit_group -o ex1.log ./ex1
strace -f -e clone,write,futex,exit,exit_group -o ex2.log ./ex2
On my machine, ex1.log looks like
28703 write(1, "Calling pthread_create(&thread_i"..., 75) = 75
28703 clone(child_stack=0x7f11f9c14fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f11f9c159d0, tls=0x7f11f9c15700, child_tidptr=0x7f11f9c159d0) = 28704
28703 write(1, "Success.\n", 9) = 9
28703 write(1, "Calling pthread_join(thread_id, "..., 49) = 49
28703 futex(0x7f11f9c159d0, FUTEX_WAIT, 28704, NULL <unfinished ...>
28704 write(2, "Running thread_function(0x563f0c"..., 79) = 79
28704 futex(0x7f11f94141a0, FUTEX_WAKE_PRIVATE, 2147483647) = 0
28704 exit(0) = ?
28703 <... futex resumed> ) = 0
28704 +++ exited with 0 +++
28703 write(1, "Success; thread_status == 0x563f"..., 42) = 42
28703 exit_group(0) = ?
28703 +++ exited with 0 +++
and ex2.log looks like
28707 write(1, "Calling pthread_create(&thread_i"..., 75) = 75
28707 clone(child_stack=0x7f42c7db8fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f42c7db99d0, tls=0x7f42c7db9700, child_tidptr=0x7f42c7db99d0) = 28708
28707 write(1, "Success.\n", 9) = 9
28708 write(2, "Running thread_function(0x556b57"..., 80 <unfinished ...>
28707 write(1, "Calling pthread_join(thread_id, "..., 49 <unfinished ...>
28708 <... write resumed> ) = 80
28707 <... write resumed> ) = 49
28708 exit(1472596720 <unfinished ...>
28707 futex(0x7f42c7db99d0, FUTEX_WAIT, 28708, NULL <unfinished ...>
28708 <... exit resumed>) = ?
28707 <... futex resumed> ) = 0
28708 +++ exited with 240 +++
28707 write(1, "Success; thread_status == (nil)."..., 33) = 33
28707 exit_group(0) = ?
28707 +++ exited with 0 +++
From their differences, and the fact that this is GNU C library version 2.27 running on x86-64 architecture, we can make a couple of important observations:
The GNU pthreads implementation uses a futex to pass the thread function return value, when the thread exits and is reaped (using pthread_join()) by another thread.
Aside from the futex used to pass the return value, pthread_exit() calls the exit syscall with exit status 0.
(In fact, the man 3 pthread_exit man page says so explicitly.)
When the thread exits (using the exit syscall), the exit status code is irrelevant.
If our thread function uses the syscall directly, cleanup functions registered by atexit() and pthread_push() will not be called, and if pthread_join() is called on this thread, the return value will essentially be (void *)0 (== NULL in Linux, printed as (nil) by printf()/fprintf() %p format specifier).

Related

linux execute shell script which echo something to a file [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I have a shell scripts ,which will execute something like below:
file:example.sh
#!/bin/sh
#some other code
echo "someconfig">config_file
I hope the config_file just contain someconfig ,but strange thing happen with config_file,It has a single 'c' in the first line. I found no printf('c') in the parent process who execute the example.sh
My process will call the linux c function to execute the script this way:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/types.h>
int execute_shell(const char *shell)
{
pid_t pid;
int iRet = 0;
if((pid = fork()) < 0)
{
return -1;
}
else if (pid == 0)
{
execlp("sh", "sh", "-c", shell, (char*)0);
exit(127);
}
else
{
while (waitpid(pid, &iRet, 0) < 0) {
if (errno != EINTR) {
iRet = -1;
break;
}
}
}
if(WIFEXITED(iRet) == 0)
{
return -1;
}
if(WEXITSTATUS(iRet) != 0)
{
return -1;
}
return 0;
}
int main()
{
char shell_cmd[1024]="./example.sh";
if( execute_shell(shell_cmd) == -1 )
{
// handle error
}
/*other code blew,may be will write to stdout*/
return 0;
}
Sometimes the config file looks strange,not what the shell scripts echo.
I use the cmd to analysis the possibility:
strace -f ./fork
[pid 12235] open("config_file", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
[pid 12235] fcntl(1, F_GETFD) = 0
[pid 12235] fcntl(1, F_DUPFD, 10) = 10
[pid 12235] fcntl(1, F_GETFD) = 0
[pid 12235] fcntl(10, F_SETFD, FD_CLOEXEC) = 0
[pid 12235] dup2(3, 1) = 1
[pid 12235] close(3) = 0
[pid 12235] fstat(1, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
[pid 12235] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbabb409000
[pid 12235] write(1, "someconfig\n", 11) = 11
[pid 12235] dup2(10, 1) = 1
[pid 12235] fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
[pid 12235] close(10) = 0
[pid 12235] rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
[pid 12235] read(255, "", 52) = 0
[pid 12235] exit_group(0) = ?
[pid 12235] +++ exited with 0 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 12235
I do not understand what the meaning of function = num?
I will appreciate if someone analysis what the meaning of the strace output.
I suspect the parent and child write to the stdout,leading to strange output in config.
In my project we just use the linux c code execute a shell scripts which would echo someconfig to config_file,It run 6 months normally,but one day the config looks strange(two machine,with the same error,first line with c not what it echo to).
I just want to talk if there any possibility this happen,to have a direction to fix the problem.
After analysis the strace output ,child process execute some fd operation,wich make sure child and parent echo to differnet fd.So I think there is no possibility to make the config mess.
the following proposed code:
cleanly compiles
performs the desired functionality
properly handles any failures seen in the sub function
properly calls the function: execlp()
Here is what I fixed:
the call (and results) of execlp()
the handling of the call to fork() when it fails
the call to waitpid() when it returns an error.
added include statements for: <sys/types.h> and <sys/wait.h> for the function: waitpid()
replaced any calls to _exit() with exit() since most beginning programmers have no idea about the result of using _exit() rather than exit()
added place to handle the returned value from the call to: execute_shell()
and now, the proposed code:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
int execute_shell(const char *shell)
{
pid_t pid;
pid = fork();
if( pid < 0 )
{
perror( "fork failed" );
return -1;
}
else if (pid == 0)
{ // child process
execlp("sh", "sh", "-c", shell, (char*)0);
perror( "execlp failed" );
return -2;
}
else
{ // parent process
int iRet;
if( waitpid(pid, &iRet, 0) == -1 )
{
return -3;
}
if (errno != EINTR)
{
return -4;
}
if(WIFEXITED(iRet) == 0)
{
return -5;
}
if(WEXITSTATUS(iRet) != 0)
{
return -6;
}
}
return 0;
}
int main( void )
{
char shell_cmd[1024]="./example.sh";
if( execute_shell(shell_cmd) )
{
// handle error
}
/*other code blew,may be will write to stdout*/
return 0;
}
I created a file: example.sh that contained:
#!/bin/sh
#some other code
echo "someconfig" > config_file
then ran the command:
chmod 777 example.sh
to make it executable, then ran the program I posted.
The result is nothing displayed on the terminal, however, now there is a new file: config_file that contains:
someconfig
I ran the code I posted several times and the file config_file contents did not change
I.E. no stray character 'c'
s
What have you not told us?

Will wait and waitpid block SIGCHLD and unblock it when they return in Linux?

Here is my code to examine this:
void handler(int n) {
printf("handler %d\n", n);
int status;
if (wait(&status) < 0)
printf("%s\n", strerror(errno));
}
int main() {
struct sigaction sig;
sigemptyset(&sig.sa_mask);
sig.sa_handler = handler;
sig.sa_flags = 0;
sig.sa_restorer = NULL;
struct sigaction sigold;
sigaction(SIGCHLD, &sig, &sigold);
pid_t pid;
int status;
printf("before fork\n");
if ((pid = fork()) == 0) {
_exit(127);
} else if (pid > 0) {
printf("before waitpid\n");
if (waitpid(pid, &status, 0) < 0)
printf("%s\n", strerror(errno));
printf("after waitpid\n");
}
printf("after fork\n");
return 0;
}
The output is:
before fork
before waitpid
handler 17
No child processes
after waitpid
after fork
So, I think waitpid will block SIGCHLD and wait for child to terminate, once the child terminates, it will do something and the unblock the SIGCHLD before it returns, that's why we see "No child processes" error and "after waitpid" is after "handler 17", am I right? if not, what is the truth? How to explain the output sequence? Is there a specification for Linux or something like that to check?
The exit information for a process can only be collected once. Your output shows the signal handler being called while your code is in waitpid(), but the handler calls wait() and that collects the information of the child (which you throw away without reporting). Then when you get back to waitpid(), the child exit status has been collected, so there's nothing left for waitpid() to report on, hence the `no child processes' error.
Here's an adaptation of your program. It abuses things by using printf() inside the signal handler function, but it seems to work despite that, testing on a Mac running macOS Sierra 10.12.4 (compiling with GCC 7.1.0).
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
static void handler(int n)
{
printf("handler %d\n", n);
int status;
int corpse;
if ((corpse = wait(&status)) < 0)
printf("%s: %s\n", __func__, strerror(errno));
else
printf("%s: child %d exited with status 0x%.4X\n", __func__, corpse, status);
}
int main(void)
{
struct sigaction sig = { 0 };
sigemptyset(&sig.sa_mask);
sig.sa_handler = handler;
sig.sa_flags = 0;
sigaction(SIGCHLD, &sig, NULL);
pid_t pid;
printf("before fork\n");
if ((pid = fork()) == 0)
{
_exit(127);
}
else if (pid > 0)
{
printf("before waitpid\n");
int status;
int corpse;
while ((corpse = waitpid(pid, &status, 0)) > 0 || errno == EINTR)
{
if (corpse < 0)
printf("loop: %s\n", strerror(errno));
else
printf("%s: child %d exited with status 0x%.4X\n", __func__, corpse, status);
}
if (corpse < 0)
printf("%s: %s\n", __func__, strerror(errno));
printf("after waitpid loop\n");
}
printf("after fork\n");
return 0;
}
Sample output:
before fork
before waitpid
handler 20
handler: child 29481 exited with status 0x7F00
loop: Interrupted system call
main: No child processes
after waitpid loop
after fork
The status value 0x7F00 is the normal encoding for _exit(127). The signal number is different for macOS from Linux; that's perfectly permissible.
To get the code to compile on Linux (Centos 7 and Ubuntu 16.04 LTS used for the test), using GCC 4.8.5 (almost antediluvian — the current version is GCC 7.1.0) and 5.4.0 respectively, using the command line:
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
> -Wstrict-prototypes -Wold-style-definition sg59.c -o sg59
$
I added #define _XOPEN_SOURCE 800 before the first header, and used:
struct sigaction sig;
memset(&sig, '\0', sizeof(sig));
to initialize the structure with GCC 4.8.5. That sort of shenanigan is occasionally a painful necessity to avoid compiler warnings. I note that although the #define was necessary to expose POSIX symbols, the initializer (struct sigaction sig = { 0 };) was accepted by GCC 5.4.0 without problems.
When I then run the program, I get very similar output to what cong reports getting in a comment:
before fork
before waitpid
handler 17
handler: No child processes
main: child 101681 exited with status 0x7F00
main: No child processes
after waitpid loop
after fork
It is curious indeed that on Linux, the process is sent a SIGCHLD signal and yet wait() cannot wait for it in the signal handler. That is at least counter-intuitive.
We can debate how much it matters that the first argument to waitpid() is pid rather than 0; the error is inevitable on the second iteration of the loop since the first collected the information from the child. In practice, it doesn't matter here. In general, it would be better to be using waitpid(0, &status, WNOHANG) or thereabouts — depending on context, 0 instead of WNOHANG might be better.

pthead_create causes error status 0x7F00

I'm trying to get a pthread example to run on my Windows 10 64-bit machine
I'm compiling using the most recent version of cygwin (64-bit), compiling the code as
gcc thread.c -pthread -o main.exe
which compiles with no error. thread.c looks like so:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
void *worker_thread(void *arg)
{
printf("This is worker_thread()\n");
pthread_exit(NULL);
}
int main()
{
int a = fork();
if (a == 0) {
printf("child a=%d\n", a);
pthread_t my_thread;
int ret = pthread_create(&my_thread, NULL, &worker_thread, NULL);
printf("ret = %d\n", ret);
} else {
printf("parent a=%d\n", a);
int status;
wait(&status);
printf("status = 0x%X (%d)\n", status, status);
}
return 0;
}
On my machine, the output looks like so:
parent a=10868
child a=0
status = 0x7F00 (32512)
which is happening because pthread_create is causing my code to exit.
According to this, this error means:
"It didn't die from a signal, a core dump wasn't produced, and it exited with code 127 (0x7F).
What 127 means is unclear, which is why it should accompanied by an error message."
On my windows 8 64-bit machine, the code runs as expected, with the same build of cygwin (64-bit) installed, and returns this output.
parent a=26744
child a=0
ret = 0
this is worker_thread()
status 0x0 (0)
Any help would be much appreciated.

reading from stdin after execl() bash return an eio(Input/output error)

The following code can act as expected if executed by a shell.
But if I set this program as a user's shell and ssh into the host to execute this program as a shell, the read(0, &buf123, 1); will return an EIO(Input/output error):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <regex.h>
#include <curl/curl.h>
#include <readline/readline.h>
int main() {
char *shell = "/bin/bash";
pid_t child;
if ((child = fork()) < 0) {
perror("vfork");
return;
}
if (child == 0) {
execl(shell, shell + 5, "-c", "exec /bin/bash --login", NULL);
perror("execl");
return;
}
wait(NULL);
char buf123[1024];
read(0, &buf123, 1);
printf("::%s::\n", buf123);
}
But if a change execl(bash) into non-interactive bash execl(bash -c "id") or other program rather than bash, the read(0, &buf123, 1); will success.
So to reproduce this error, two conditions need to be met:
1. execvl() an interactive bash(system() can also reproduce this error)
2. run as a user's shell using ssh
Could anyone help me figure out why and how to avoid this?
The following is the strace result:
wait4(-1, NULL, 0, NULL) = 2
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2, si_status=0, si_utime=0, si_stime=0} ---
read(0, 0x7fff7a4c8cb0, 1) = -1 EIO (Input/output error)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcd281f3000
write(1, "::Xv\37(\315\177::\n", 11) = 11
exit_group(11) = ?
+++ exited with 11 +++
Thanks in advance!
I happens because your sub-shell is a login interactive shell and so it took control over the terminal (set it as a its session control terminal). Then your process is disconnected from the terminal and cannot read on it anymore.
Of course if you use a non interactive shell, it don't need the control over the terminal leaving it as is for your process.
Read about POSIX Terminal, sessions and processes group.

Futexes and blocking in the kernel

I was reading some documentation and trying out a few samples of code that issue the futex system call in Linux. I read that if a mutex is acquired by thread_a using FUTEX_LOCK_PI, and say if another thread thread_b tries to acquire the same, the latter(thread_b) is blocked in the kernel.
What exactly is meant by "blocked in the kernel"? Is it that the execution of thread_b in userspace does not resume? In the following example, the second thread seems to issue the printf after the syscall as well. Would that not suggest that it is not blocked in the kernel? What am I missing?
/*
* This example demonstrates that when futex_lock_pi is called twice, the
* second call is blocked inside the kernel.
*/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
int mutex = 0;
#define __NR_futex 240
#define FUTEX_WAIT 0
#define FUTEX_WAKE 1
#define FUTEX_FD 2
#define FUTEX_REQUEUE 3
#define FUTEX_CMP_REQUEUE 4
#define FUTEX_WAKE_OP 5
#define FUTEX_LOCK_PI 6
#define FUTEX_UNLOCK_PI 7
#define FUTEX_TRYLOCK_PI 8
#define FUTEX_WAIT_REQUEUE_PI 11
#define FUTEX_CMP_REQUEUE_PI 12
void *thread(void *arg) {
int ret = 0;
pid_t tid = gettid();
printf("Entering thread[%d]\n", tid);
ret = syscall(__NR_futex, &mutex, FUTEX_LOCK_PI, 1, NULL, NULL, 0);
printf("Thread %d returned from kernel\n", tid);
printf("Value of mutex=%d\n", mutex);
printf("Return value from syscall=%d\n", ret);
}
int main(int argc, const char *argv[]) {
pthread_t t1, t2;
if (pthread_create(&t1, NULL, thread, NULL)) {
printf("Could not create thread 1\n");
return -1;
}
if (pthread_create(&t2, NULL, thread, NULL)) {
printf("Could not create thread 2\n");
return -1;
}
// Loop infinitely
while ( 1 ) { }
return 0;
}
The output can be found below :-
Entering thread[952]
Thread 952 returned from kernel
Entering thread[951]
Value of mutex=-2147482696
Return value from syscall=0
Thread 951 returned from kernel
Value of mutex=-1073740873
Return value from syscall=0
blocked in the kernel means that the thread is put to sleep state until an events wakes it up, in your case the event is the mutex to get available.
I changed a little bit your code to illustrate:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *thread(void *unused) {
printf("Entering thread %d\n", syscall(SYS_gettid));
pthread_mutex_lock(&mutex);
sleep(2);
pthread_mutex_unlock(&mutex);
return NULL;
}
int main(int argc, const char *argv[]) {
pthread_t t1, t2;
printf("using mutex #%p\n", &mutex);
if (pthread_create(&t1, NULL, thread, &t1)) {
printf("Could not create thread 1\n");
return -1;
}
if (pthread_create(&t2, NULL, thread, &t2)) {
printf("Could not create thread 2\n");
return -1;
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
The code is basically the same but I use explicitly a mutex of the pthread lib.
If I run this code with strace, I get:
using mutex #0x6010a0
Process 19688 attached
Entering thread 19688
Process 19689 attached
Entering thread 19689
[pid 19689] futex(0x6010a0, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 19688] futex(0x6010a0, FUTEX_WAKE_PRIVATE, 1) = 1
[pid 19689] <... futex resumed> ) = 0
[pid 19688] +++ exited with 0 +++
[pid 19689] futex(0x6010a0, FUTEX_WAKE_PRIVATE, 1) = 0
[pid 19689] +++ exited with 0 +++
+++ exited with 0 +++
You may notice that we do not see the first thread taking the mutex, BUT we can see the next one caling futex for waiting (FUTEX_WAIT_PRIVATE). This is due to the fact that futex doesn't get called when the mutex is being taken.
But then you can see that the first thread (id 19688 here) then eventually calls futex(FUTEX_WAKE_PRIVATE) which tells the kernel to wake the other thread now the utex is free.
You may have noticed that the first call
[pid 19689] futex(0x6010a0, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
is unfinished, which means that the process was hanged waiting for the kernel to finish the job and give back the hand.
Then [pid 19689] <... futex resumed> ) = 0 sketches that the call is eventually finished (obviously because the mutex was released)

Resources