x86: Use PTRACE_GET_THREAD_AREA instead of sys_get_thread_area()

To minimize things done in parasite, PTRACE_GET_THREAD_AREA can be
used to get remote tls. That also removes an additional compat stack
(de)allocation in the parasite (also asm-coded syscall).

In order to use PTRACE_GET_THREAD_AREA, the dumpee should be stopped.
So, let's move this from criu to compel to non-seized state and put tls
into thread info on x86.

Signed-off-by: Dmitry Safonov <dima@arista.com>
This commit is contained in:
Dmitry Safonov 2020-11-12 03:10:52 +00:00 committed by Andrei Vagin
parent 72dc328502
commit ffb848e6d9
15 changed files with 164 additions and 85 deletions

View file

@ -28,6 +28,10 @@ lib-y += src/lib/infect-util.o
lib-y += src/lib/infect.o
lib-y += src/lib/ptrace.o
ifeq ($(ARCH),x86)
lib-y += arch/$(ARCH)/src/lib/thread_area.o
endif
# handle_elf() has no support of ELF relocations on ARM (yet?)
ifneq ($(filter arm aarch64,$(ARCH)),)
CFLAGS += -DNO_RELOCS

View file

@ -18,6 +18,11 @@
typedef struct user_pt_regs user_regs_struct_t;
typedef struct user_fpsimd_state user_fpregs_struct_t;
#define __compel_arch_fetch_thread_area(tid, th) 0
#define compel_arch_fetch_thread_area(tctl) 0
#define compel_arch_get_tls_task(ctl, tls)
#define compel_arch_get_tls_thread(tctl, tls)
#define REG_RES(r) ((uint64_t)(r).regs[0])
#define REG_IP(r) ((uint64_t)(r).pc)
#define REG_SP(r) ((uint64_t)((r).sp))

View file

@ -17,6 +17,11 @@ typedef struct {
long uregs[18];
} user_regs_struct_t;
#define __compel_arch_fetch_thread_area(tid, th) 0
#define compel_arch_fetch_thread_area(tctl) 0
#define compel_arch_get_tls_task(ctl, tls)
#define compel_arch_get_tls_thread(tctl, tls)
typedef struct user_vfp user_fpregs_struct_t;
#define ARM_cpsr uregs[16]

View file

@ -54,6 +54,10 @@ static inline bool user_regs_native(user_regs_struct_t *pregs)
return true;
}
#define __compel_arch_fetch_thread_area(tid, th) 0
#define compel_arch_fetch_thread_area(tctl) 0
#define compel_arch_get_tls_task(ctl, tls)
#define compel_arch_get_tls_thread(tctl, tls)
#define REG_RES(regs) ((regs).MIPS_v0)
#define REG_IP(regs) ((regs).cp0_epc)

View file

@ -83,4 +83,9 @@ typedef struct {
#define __NR(syscall, compat) ({ (void)compat; __NR_##syscall; })
#define __compel_arch_fetch_thread_area(tid, th) 0
#define compel_arch_fetch_thread_area(tctl) 0
#define compel_arch_get_tls_task(ctl, tls)
#define compel_arch_get_tls_thread(tctl, tls)
#endif /* UAPI_COMPEL_ASM_TYPES_H__ */

View file

@ -84,4 +84,9 @@ struct mmap_arg_struct {
unsigned long offset;
};
#define __compel_arch_fetch_thread_area(tid, th) 0
#define compel_arch_fetch_thread_area(tctl) 0
#define compel_arch_get_tls_task(ctl, tls)
#define compel_arch_get_tls_thread(tctl, tls)
#endif /* UAPI_COMPEL_ASM_TYPES_H__ */

View file

@ -9,6 +9,29 @@
#define SIGMAX 64
#define SIGMAX_OLD 31
#define ARCH_HAS_PTRACE_GET_THREAD_AREA
/*
* Linux preserves three TLS segments in GDT.
* Offsets in GDT differ between 32-bit and 64-bit machines.
* For 64-bit x86 those GDT offsets are the same
* for native and compat tasks.
*/
#define GDT_ENTRY_TLS_MIN 12
#define GDT_ENTRY_TLS_MAX 14
#define GDT_ENTRY_TLS_NUM 3
typedef struct {
user_desc_t desc[GDT_ENTRY_TLS_NUM];
} tls_t;
struct thread_ctx;
struct parasite_ctl;
struct parasite_thread_ctl;
extern int __compel_arch_fetch_thread_area(int tid, struct thread_ctx *th);
extern int compel_arch_fetch_thread_area(struct parasite_thread_ctl *tctl);
extern void compel_arch_get_tls_thread(struct parasite_thread_ctl *tctl, tls_t *out);
extern void compel_arch_get_tls_task(struct parasite_ctl *ctl, tls_t *out);
typedef struct {
uint64_t r15;
uint64_t r14;

View file

@ -0,0 +1,88 @@
#include <errno.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include "log.h"
#include "asm/infect-types.h"
#include "infect.h"
#include "infect-priv.h"
#ifndef PTRACE_GET_THREAD_AREA
# define PTRACE_GET_THREAD_AREA 25
#endif
/*
* For 64-bit applications, TLS (fs_base for Glibc) is in MSR,
* which are dumped with the help of ptrace() and restored with
* arch_prctl(ARCH_SET_FS/ARCH_SET_GS).
*
* But SET_FS_BASE will update GDT if base pointer fits in 4 bytes.
* Otherwise it will set only MSR, which allows for mixed 64/32-bit
* code to use: 2 MSRs as TLS base _and_ 3 GDT entries.
* Having in sum 5 TLS pointers, 3 of which are four bytes and
* other two eight bytes:
* struct thread_struct {
* struct desc_struct tls_array[3];
* ...
* #ifdef CONFIG_X86_64
* unsigned long fsbase;
* unsigned long gsbase;
* #endif
* ...
* };
*
* Most x86_64 applications don't use GDT, but mixed code (i.e. Wine)
* can use it. Be pessimistic and dump it for 64-bit applications too.
*/
int __compel_arch_fetch_thread_area(int tid, struct thread_ctx *th)
{
bool native_mode = user_regs_native(&th->regs);
tls_t *ptls = &th->tls;
int err, i;
/* Initialise as not present by default */
for (i = 0; i < GDT_ENTRY_TLS_NUM; i++) {
user_desc_t *d = &ptls->desc[i];
memset(d, 0, sizeof(user_desc_t));
d->seg_not_present = 1;
d->entry_number = GDT_ENTRY_TLS_MIN + i;
}
for (i = 0; i < GDT_ENTRY_TLS_NUM; i++)
{
user_desc_t *d = &ptls->desc[i];
err = ptrace(PTRACE_GET_THREAD_AREA, tid,
GDT_ENTRY_TLS_MIN + i, d);
/*
* Ignoring absent syscall on !CONFIG_IA32_EMULATION
* where such mixed code can't run.
* XXX: Add compile CONFIG_X86_IGNORE_64BIT_TLS
* (for x86_64 systems with CONFIG_IA32_EMULATION)
*/
if (err == -EIO && native_mode)
return 0;
if (err) {
pr_perror("get_thread_area failed for %d\n", tid);
return err;
}
}
return 0;
}
int compel_arch_fetch_thread_area(struct parasite_thread_ctl *tctl)
{
return __compel_arch_fetch_thread_area(tctl->tid, &tctl->th);
}
void compel_arch_get_tls_task(struct parasite_ctl *ctl, tls_t *out)
{
memcpy(out, &ctl->orig.tls, sizeof(tls_t));
}
void compel_arch_get_tls_thread(struct parasite_thread_ctl *tctl, tls_t *out)
{
memcpy(out, &tctl->th.tls, sizeof(tls_t));
}

View file

@ -8,6 +8,9 @@
struct thread_ctx {
k_rtsigset_t sigmask;
user_regs_struct_t regs;
#ifdef ARCH_HAS_PTRACE_GET_THREAD_AREA
tls_t tls;
#endif
};
/* parasite control block */

View file

@ -681,6 +681,11 @@ static int parasite_start_daemon(struct parasite_ctl *ctl)
return -1;
}
if (__compel_arch_fetch_thread_area(pid, &ctl->orig)) {
pr_err("Can't get thread area of %d\n", pid);
return -1;
}
if (ictx->make_sigframe(ictx->regs_arg, ctl->sigframe, ctl->rsigframe, &ctl->orig.sigmask))
return -1;

View file

@ -1,6 +1,7 @@
#ifndef __ASM_PARASITE_H__
#define __ASM_PARASITE_H__
/* kuser_get_tls() kernel-provided user-helper, the address is emulated */
static inline void arch_get_tls(tls_t *ptls)
{
*ptls = ((tls_t (*)(void))0xffff0fe0)();

View file

@ -1,77 +1,10 @@
#ifndef __ASM_PARASITE_H__
#define __ASM_PARASITE_H__
#include <compel/plugins/std/string.h>
#include <compel/plugins/std/syscall-codes.h>
#include "asm/compat.h"
static int arch_get_user_desc(user_desc_t *desc)
{
int ret = __NR32_get_thread_area;
/*
* For 64-bit applications, TLS (fs_base for Glibc) is
* in MSR, which are dumped with the help of arch_prctl().
*
* But SET_FS_BASE will update GDT if base pointer fits in 4 bytes.
* Otherwise it will set only MSR, which allows for mixed 64/32-bit
* code to use: 2 MSRs as TLS base _and_ 3 GDT entries.
* Having in sum 5 TLS pointers, 3 of which are four bytes and
* other two bigger than four bytes:
* struct thread_struct {
* struct desc_struct tls_array[3];
* ...
* #ifdef CONFIG_X86_64
* unsigned long fsbase;
* unsigned long gsbase;
* #endif
* ...
* };
*/
asm volatile (
" mov %0,%%eax \n"
" mov %1,%%rbx \n"
" int $0x80 \n"
" mov %%eax,%0 \n"
: "+m"(ret)
: "m"(desc)
: "rax", "rbx", "r8", "r9", "r10", "r11", "memory");
if (ret)
pr_err("Failed to dump TLS descriptor #%d: %d\n",
desc->entry_number, ret);
return ret;
}
static void arch_get_tls(tls_t *ptls)
{
void *syscall_mem;
int i;
syscall_mem = alloc_compat_syscall_stack();
if (!syscall_mem) {
pr_err("Failed to allocate memory <4Gb for compat syscall\n");
for (i = 0; i < GDT_ENTRY_TLS_NUM; i++) {
user_desc_t *d = &ptls->desc[i];
d->seg_not_present = 1;
d->entry_number = GDT_ENTRY_TLS_MIN + i;
}
return;
}
for (i = 0; i < GDT_ENTRY_TLS_NUM; i++)
{
user_desc_t *d = syscall_mem;
memset(d, 0, sizeof(user_desc_t));
d->seg_not_present = 1;
d->entry_number = GDT_ENTRY_TLS_MIN + i;
arch_get_user_desc(d);
memcpy(&ptls->desc[i], d, sizeof(user_desc_t));
}
free_compat_syscall_stack(syscall_mem);
}
/*
* TLS is accessed through PTRACE_GET_THREAD_AREA,
* see compel_arch_fetch_thread_area().
*/
static inline void arch_get_tls(tls_t *ptls) { (void)ptls; }
#endif

View file

@ -3,6 +3,7 @@
#include "asm/types.h"
#include <compel/asm/fpu.h>
#include <compel/asm/infect-types.h>
#include "images/core.pb-c.h"
#include <compel/plugins/std/syscall-codes.h>
#include <compel/asm/sigframe.h>

View file

@ -36,17 +36,4 @@ static inline void *decode_pointer(u64 v) { return (void*)(long)v; }
#define AT_VECTOR_SIZE 44
typedef uint64_t auxv_t;
/*
* Linux preserves three TLS segments in GDT.
* Offsets in GDT differ between 32-bit and 64-bit machines.
* For 64-bit x86 those GDT offsets are the same
* for native and compat tasks.
*/
#define GDT_ENTRY_TLS_MIN 12
#define GDT_ENTRY_TLS_MAX 14
#define GDT_ENTRY_TLS_NUM 3
typedef struct {
user_desc_t desc[GDT_ENTRY_TLS_NUM];
} tls_t;
#endif /* __CR_ASM_TYPES_H__ */

View file

@ -156,6 +156,8 @@ int parasite_dump_thread_leader_seized(struct parasite_ctl *ctl, int pid, CoreEn
return -1;
}
compel_arch_get_tls_task(ctl, &args->tls);
return dump_thread_core(pid, core, args);
}
@ -190,6 +192,14 @@ int parasite_dump_thread_seized(struct parasite_thread_ctl *tctl,
goto err_rth;
}
ret = compel_arch_fetch_thread_area(tctl);
if (ret) {
pr_err("Can't obtain thread area of %d\n", pid);
goto err_rth;
}
compel_arch_get_tls_thread(tctl, &args->tls);
ret = compel_run_in_thread(tctl, PARASITE_CMD_DUMP_THREAD);
if (ret) {
pr_err("Can't init thread in parasite %d\n", pid);