aboutsummaryrefslogtreecommitdiff
path: root/sys/amd64/linux/linux_sysvec.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/amd64/linux/linux_sysvec.c')
-rw-r--r--sys/amd64/linux/linux_sysvec.c161
1 files changed, 131 insertions, 30 deletions
diff --git a/sys/amd64/linux/linux_sysvec.c b/sys/amd64/linux/linux_sysvec.c
index bcc8cbf0b0bd..f13526b00d85 100644
--- a/sys/amd64/linux/linux_sysvec.c
+++ b/sys/amd64/linux/linux_sysvec.c
@@ -50,6 +50,7 @@ __FBSDID("$FreeBSD$");
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/resourcevar.h>
+#include <sys/stddef.h>
#include <sys/signalvar.h>
#include <sys/syscallsubr.h>
#include <sys/sysctl.h>
@@ -72,6 +73,7 @@ __FBSDID("$FreeBSD$");
#include <machine/specialreg.h>
#include <machine/trap.h>
+#include <x86/linux/linux_x86.h>
#include <amd64/linux/linux.h>
#include <amd64/linux/linux_proto.h>
#include <compat/linux/linux_emul.h>
@@ -85,11 +87,24 @@ __FBSDID("$FreeBSD$");
MODULE_VERSION(linux64, 1);
+#define LINUX_VDSOPAGE_SIZE PAGE_SIZE * 2
+#define LINUX_VDSOPAGE_LA48 (VM_MAXUSER_ADDRESS_LA48 - \
+ LINUX_VDSOPAGE_SIZE)
+#define LINUX_SHAREDPAGE_LA48 (LINUX_VDSOPAGE_LA48 - PAGE_SIZE)
+ /*
+ * PAGE_SIZE - the size
+ * of the native SHAREDPAGE
+ */
+#define LINUX_USRSTACK_LA48 LINUX_SHAREDPAGE_LA48
+#define LINUX_PS_STRINGS_LA48 (LINUX_USRSTACK_LA48 - \
+ sizeof(struct ps_strings))
+
static int linux_szsigcode;
-static vm_object_t linux_shared_page_obj;
-static char *linux_shared_page_mapping;
-extern char _binary_linux_locore_o_start;
-extern char _binary_linux_locore_o_end;
+static vm_object_t linux_vdso_obj;
+static char *linux_vdso_mapping;
+extern char _binary_linux_vdso_so_o_start;
+extern char _binary_linux_vdso_so_o_end;
+static vm_offset_t linux_vdso_base;
extern struct sysent linux_sysent[LINUX_SYS_MAXSYSCALL];
@@ -102,10 +117,12 @@ static int linux_fixup_elf(uintptr_t *stack_base,
static bool linux_trans_osrel(const Elf_Note *note, int32_t *osrel);
static void linux_vdso_install(void *param);
static void linux_vdso_deinstall(void *param);
+static void linux_vdso_reloc(char *mapping, Elf_Addr offset);
static void linux_set_syscall_retval(struct thread *td, int error);
static int linux_fetch_syscall_args(struct thread *td);
static void linux_exec_setregs(struct thread *td, struct image_params *imgp,
uintptr_t stack);
+static void linux_exec_sysvec_init(void *param);
static int linux_on_exec_vmspace(struct proc *p,
struct image_params *imgp);
static int linux_vsyscall(struct thread *td);
@@ -151,6 +168,8 @@ static int _bsd_to_linux_trapcode[] = {
LINUX_VDSO_SYM_INTPTR(linux_rt_sigcode);
LINUX_VDSO_SYM_CHAR(linux_platform);
+LINUX_VDSO_SYM_INTPTR(kern_timekeep_base);
+LINUX_VDSO_SYM_INTPTR(kern_tsc_selector);
/*
* If FreeBSD & Linux have a difference of opinion about what a trap
@@ -263,8 +282,7 @@ linux_copyout_auxargs(struct image_params *imgp, uintptr_t base)
M_WAITOK | M_ZERO);
issetugid = p->p_flag & P_SUGID ? 1 : 0;
- AUXARGS_ENTRY(pos, LINUX_AT_SYSINFO_EHDR,
- imgp->proc->p_sysent->sv_shared_page_base);
+ AUXARGS_ENTRY(pos, LINUX_AT_SYSINFO_EHDR, linux_vdso_base);
AUXARGS_ENTRY(pos, LINUX_AT_HWCAP, cpu_feature);
AUXARGS_ENTRY(pos, AT_PAGESZ, args->pagesz);
AUXARGS_ENTRY(pos, LINUX_AT_CLKTCK, stclohz);
@@ -732,7 +750,7 @@ struct sysentvec elf_linux_sysvec = {
.sv_transtrap = linux_translate_traps,
.sv_fixup = linux_fixup_elf,
.sv_sendsig = linux_rt_sendsig,
- .sv_sigcode = &_binary_linux_locore_o_start,
+ .sv_sigcode = &_binary_linux_vdso_so_o_start,
.sv_szsigcode = &linux_szsigcode,
.sv_name = "Linux ELF64",
.sv_coredump = elf64_coredump,
@@ -743,8 +761,8 @@ struct sysentvec elf_linux_sysvec = {
.sv_minsigstksz = LINUX_MINSIGSTKSZ,
.sv_minuser = VM_MIN_ADDRESS,
.sv_maxuser = VM_MAXUSER_ADDRESS_LA48,
- .sv_usrstack = USRSTACK_LA48,
- .sv_psstrings = PS_STRINGS_LA48,
+ .sv_usrstack = LINUX_USRSTACK_LA48,
+ .sv_psstrings = LINUX_PS_STRINGS_LA48,
.sv_psstringssz = sizeof(struct ps_strings),
.sv_stackprot = VM_PROT_ALL,
.sv_copyout_auxargs = linux_copyout_auxargs,
@@ -753,11 +771,11 @@ struct sysentvec elf_linux_sysvec = {
.sv_fixlimit = NULL,
.sv_maxssiz = NULL,
.sv_flags = SV_ABI_LINUX | SV_LP64 | SV_SHP | SV_SIG_DISCIGN |
- SV_SIG_WAITNDQ,
+ SV_SIG_WAITNDQ | SV_TIMEKEEP,
.sv_set_syscall_retval = linux_set_syscall_retval,
.sv_fetch_syscall_args = linux_fetch_syscall_args,
.sv_syscallnames = NULL,
- .sv_shared_page_base = SHAREDPAGE_LA48,
+ .sv_shared_page_base = LINUX_SHAREDPAGE_LA48,
.sv_shared_page_len = PAGE_SIZE,
.sv_schedtail = linux_schedtail,
.sv_thread_detach = linux_thread_detach,
@@ -771,47 +789,130 @@ struct sysentvec elf_linux_sysvec = {
static int
linux_on_exec_vmspace(struct proc *p, struct image_params *imgp)
{
+ int error;
- linux_on_exec(p, imgp);
- return (0);
+ error = linux_map_vdso(p, linux_vdso_obj, linux_vdso_base,
+ LINUX_VDSOPAGE_SIZE, imgp);
+ if (error == 0)
+ linux_on_exec(p, imgp);
+ return (error);
}
static void
-linux_vdso_install(void *param)
+linux_exec_sysvec_init(void *param)
{
+ l_uintptr_t *ktimekeep_base, *ktsc_selector;
+ struct sysentvec *sv;
+ ptrdiff_t tkoff;
+
+ sv = param;
+ amd64_lower_shared_page(sv);
+ /* Fill timekeep_base */
+ exec_sysvec_init(sv);
+
+ tkoff = kern_timekeep_base - linux_vdso_base;
+ ktimekeep_base = (l_uintptr_t *)(linux_vdso_mapping + tkoff);
+ *ktimekeep_base = sv->sv_timekeep_base;
+
+ tkoff = kern_tsc_selector - linux_vdso_base;
+ ktsc_selector = (l_uintptr_t *)(linux_vdso_mapping + tkoff);
+ *ktsc_selector = linux_vdso_tsc_selector_idx();
+ if (bootverbose)
+ printf("Linux x86-64 vDSO tsc_selector: %lu\n", *ktsc_selector);
+}
+SYSINIT(elf_linux_exec_sysvec_init, SI_SUB_EXEC, SI_ORDER_ANY,
+ linux_exec_sysvec_init, &elf_linux_sysvec);
- amd64_lower_shared_page(&elf_linux_sysvec);
-
- linux_szsigcode = (&_binary_linux_locore_o_end -
- &_binary_linux_locore_o_start);
+static void
+linux_vdso_install(void *param)
+{
+ char *vdso_start = &_binary_linux_vdso_so_o_start;
+ char *vdso_end = &_binary_linux_vdso_so_o_end;
- if (linux_szsigcode > elf_linux_sysvec.sv_shared_page_len)
- panic("Linux invalid vdso size\n");
+ linux_szsigcode = vdso_end - vdso_start;
+ MPASS(linux_szsigcode <= LINUX_VDSOPAGE_SIZE);
- __elfN(linux_vdso_fixup)(&elf_linux_sysvec);
+ linux_vdso_base = LINUX_VDSOPAGE_LA48;
+ if (hw_lower_amd64_sharedpage != 0)
+ linux_vdso_base -= PAGE_SIZE;
- linux_shared_page_obj = __elfN(linux_shared_page_init)
- (&linux_shared_page_mapping);
+ __elfN(linux_vdso_fixup)(vdso_start, linux_vdso_base);
- __elfN(linux_vdso_reloc)(&elf_linux_sysvec);
+ linux_vdso_obj = __elfN(linux_shared_page_init)
+ (&linux_vdso_mapping, LINUX_VDSOPAGE_SIZE);
+ bcopy(vdso_start, linux_vdso_mapping, linux_szsigcode);
- bcopy(elf_linux_sysvec.sv_sigcode, linux_shared_page_mapping,
- linux_szsigcode);
- elf_linux_sysvec.sv_shared_page_obj = linux_shared_page_obj;
+ linux_vdso_reloc(linux_vdso_mapping, linux_vdso_base);
}
-SYSINIT(elf_linux_vdso_init, SI_SUB_EXEC, SI_ORDER_ANY,
+SYSINIT(elf_linux_vdso_init, SI_SUB_EXEC, SI_ORDER_FIRST,
linux_vdso_install, NULL);
static void
linux_vdso_deinstall(void *param)
{
- __elfN(linux_shared_page_fini)(linux_shared_page_obj,
- linux_shared_page_mapping);
+ __elfN(linux_shared_page_fini)(linux_vdso_obj,
+ linux_vdso_mapping, LINUX_VDSOPAGE_SIZE);
}
SYSUNINIT(elf_linux_vdso_uninit, SI_SUB_EXEC, SI_ORDER_FIRST,
linux_vdso_deinstall, NULL);
+static void
+linux_vdso_reloc(char *mapping, Elf_Addr offset)
+{
+ const Elf_Ehdr *ehdr;
+ const Elf_Shdr *shdr;
+ Elf64_Addr *where, val;
+ Elf_Size rtype, symidx;
+ const Elf_Rela *rela;
+ Elf_Addr addr, addend;
+ int relacnt;
+ int i, j;
+
+ MPASS(offset != 0);
+
+ relacnt = 0;
+ ehdr = (const Elf_Ehdr *)mapping;
+ shdr = (const Elf_Shdr *)(mapping + ehdr->e_shoff);
+ for (i = 0; i < ehdr->e_shnum; i++)
+ {
+ switch (shdr[i].sh_type) {
+ case SHT_REL:
+ printf("Linux x86_64 vDSO: unexpected Rel section\n");
+ break;
+ case SHT_RELA:
+ rela = (const Elf_Rela *)(mapping + shdr[i].sh_offset);
+ relacnt = shdr[i].sh_size / sizeof(*rela);
+ }
+ }
+
+ for (j = 0; j < relacnt; j++, rela++) {
+ where = (Elf_Addr *)(mapping + rela->r_offset);
+ addend = rela->r_addend;
+ rtype = ELF_R_TYPE(rela->r_info);
+ symidx = ELF_R_SYM(rela->r_info);
+
+ switch (rtype) {
+ case R_X86_64_NONE: /* none */
+ break;
+
+ case R_X86_64_RELATIVE: /* B + A */
+ addr = (Elf_Addr)(offset + addend);
+ val = addr;
+ if (*where != val)
+ *where = val;
+ break;
+ case R_X86_64_IRELATIVE:
+ printf("Linux x86_64 vDSO: unexpected ifunc relocation, "
+ "symbol index %ld\n", symidx);
+ break;
+ default:
+ printf("Linux x86_64 vDSO: unexpected relocation type %ld, "
+ "symbol index %ld\n", rtype, symidx);
+ }
+ }
+}
+
static char GNULINUX_ABI_VENDOR[] = "GNU";
static int GNULINUX_ABI_DESC = 0;