Track whether a thread is currently vforked.
Our various fd debugging facilities get extremely confused by a vforked
process closing file descriptors in preparation to exec: fdsan can
abort, and fdtrack will delete backtraces for any file descriptors that
get closed. Keep track of whether we're in a vforked child in order to
be able to detect this.
Bug: http://b/153926671
Test: 32/64-bit bionic-unit-tests on blueline, x86_64 emulator
Change-Id: I8a082fd06bfdfef0e2a88dbce350b6f667f7df9f
diff --git a/libc/arch-arm/bionic/vfork.S b/libc/arch-arm/bionic/vfork.S
index 6855db7..a964be5 100644
--- a/libc/arch-arm/bionic/vfork.S
+++ b/libc/arch-arm/bionic/vfork.S
@@ -31,17 +31,27 @@
ENTRY(vfork)
__BIONIC_WEAK_ASM_FOR_NATIVE_BRIDGE(vfork)
- // __get_tls()[TLS_SLOT_THREAD_ID]->cached_pid_ = 0
+ // r3 = &__get_tls()[TLS_SLOT_THREAD_ID]
mrc p15, 0, r3, c13, c0, 3
ldr r3, [r3, #(TLS_SLOT_THREAD_ID * 4)]
- mov r0, #0
+
+ // Set cached_pid_ to 0, vforked_ to 1, and stash the previous value.
+ mov r0, #0x80000000
+ ldr r1, [r3, #12]
str r0, [r3, #12]
mov ip, r7
ldr r7, =__NR_vfork
swi #0
mov r7, ip
+
+ teq r0, #0
+ bxeq lr
+
+ // rc != 0: reset cached_pid_ and vforked_.
+ str r1, [r3, #12]
cmn r0, #(MAX_ERRNO + 1)
+
bxls lr
neg r0, r0
b __set_errno_internal
diff --git a/libc/arch-arm64/bionic/vfork.S b/libc/arch-arm64/bionic/vfork.S
index a76e9ca..5cfb8b0 100644
--- a/libc/arch-arm64/bionic/vfork.S
+++ b/libc/arch-arm64/bionic/vfork.S
@@ -36,10 +36,14 @@
ENTRY(vfork)
__BIONIC_WEAK_ASM_FOR_NATIVE_BRIDGE(vfork)
- // __get_tls()[TLS_SLOT_THREAD_ID]->cached_pid_ = 0
- mrs x0, tpidr_el0
- ldr x0, [x0, #(TLS_SLOT_THREAD_ID * 8)]
- str wzr, [x0, #20]
+ // x9 = __get_tls()[TLS_SLOT_THREAD_ID]
+ mrs x9, tpidr_el0
+ ldr x9, [x9, #(TLS_SLOT_THREAD_ID * 8)]
+
+ // Set cached_pid_ to 0, vforked_ to 1, and stash the previous value.
+ mov w0, #0x80000000
+ ldr w10, [x9, #20]
+ str w0, [x9, #20]
mov x0, #(CLONE_VM | CLONE_VFORK | SIGCHLD)
mov x1, xzr
@@ -50,6 +54,10 @@
mov x8, __NR_clone
svc #0
+ cbz x0, .L_exit
+
+ // rc != 0: reset cached_pid_ and vforked_.
+ str w10, [x9, #20]
cmn x0, #(MAX_ERRNO + 1)
cneg x0, x0, hi
b.hi __set_errno_internal
diff --git a/libc/arch-x86/bionic/vfork.S b/libc/arch-x86/bionic/vfork.S
index 663169c..231a36e 100644
--- a/libc/arch-x86/bionic/vfork.S
+++ b/libc/arch-x86/bionic/vfork.S
@@ -37,13 +37,25 @@
.cfi_adjust_cfa_offset 4
.cfi_rel_offset ecx, 0
- // __get_tls()[TLS_SLOT_THREAD_ID]->cached_pid_ = 0
+ // Set cached_pid_ to 0, vforked_ to 1, and stash the previous value.
movl %gs:0, %eax
movl (TLS_SLOT_THREAD_ID * 4)(%eax), %eax
- movl $0, 12(%eax)
+ movl 12(%eax), %edx
+ movl $0x80000000, 12(%eax)
movl $__NR_vfork, %eax
int $0x80
+
+ test %eax, %eax
+ jz 1f
+
+ // rc != 0: restore the previous cached_pid_/vforked_ values.
+ pushl %ecx
+ movl %gs:0, %ecx
+ movl (TLS_SLOT_THREAD_ID * 4)(%ecx), %ecx
+ movl %edx, 12(%ecx)
+ popl %ecx
+
cmpl $-MAX_ERRNO, %eax
jb 1f
negl %eax
diff --git a/libc/arch-x86_64/bionic/vfork.S b/libc/arch-x86_64/bionic/vfork.S
index 86c5db2..8cfcc36 100644
--- a/libc/arch-x86_64/bionic/vfork.S
+++ b/libc/arch-x86_64/bionic/vfork.S
@@ -35,14 +35,22 @@
__BIONIC_WEAK_ASM_FOR_NATIVE_BRIDGE(vfork)
popq %rdi // Grab the return address.
- // __get_tls()[TLS_SLOT_THREAD_ID]->cached_pid_ = 0
- mov %fs:0, %rax
- mov (TLS_SLOT_THREAD_ID * 8)(%rax), %rax
- movl $0, 20(%rax)
+ // Set cached_pid_ to 0, vforked_ to 1, and stash the previous value.
+ mov %fs:0, %r8
+ mov (TLS_SLOT_THREAD_ID * 8)(%r8), %r8
+ movl 20(%r8), %r9d
+ movl $0x80000000, 20(%r8)
movl $__NR_vfork, %eax
syscall
pushq %rdi // Restore the return address.
+
+ test %eax, %eax
+ jz 1f
+
+ // rc != 0: restore the previous cached_pid_/vforked_ values.
+ movl %r9d, 20(%r8)
+
cmpq $-MAX_ERRNO, %rax
jb 1f
negl %eax
diff --git a/libc/bionic/pthread_internal.h b/libc/bionic/pthread_internal.h
index ab8b955..1f055f5 100644
--- a/libc/bionic/pthread_internal.h
+++ b/libc/bionic/pthread_internal.h
@@ -70,9 +70,12 @@
pid_t tid;
private:
- pid_t cached_pid_;
+ uint32_t cached_pid_ : 31;
+ uint32_t vforked_ : 1;
public:
+ bool is_vforked() { return vforked_; }
+
pid_t invalidate_cached_pid() {
pid_t old_value;
get_cached_pid(&old_value);