The change makes the user and kernel address spaces on i386 independent, giving each almost the full 4G of usable virtual addresses except for one PDE at top used for trampoline and per-CPU trampoline stacks, and system structures that must be always mapped, namely IDT, GDT, common TSS and LDT, and process-private TSS and LDT if allocated. By using 1:1 mapping for the kernel text and data, it appeared possible to eliminate assembler part of the locore.S which bootstraps initial page table and KPTmap. The code is rewritten in C and moved into the pmap_cold(). The comment in vmparam.h explains the KVA layout. There is no PCID mechanism available in protected mode, so each kernel/user switch forth and back completely flushes the TLB, except for the trampoline PTD region. The TLB invalidations for userspace becomes trivial, because IPI handlers switch page tables. On the other hand, context switches no longer need to reload %cr3. copyout(9) was rewritten to use vm_fault_quick_hold(). An issue for new copyout(9) is compatibility with wiring user buffers around sysctl handlers. This explains two kind of locks for copyout ptes and accounting of the vslock() calls. The vm_fault_quick_hold() AKA slow path, is only tried after the 'fast path' failed, which temporary changes mapping to the userspace and copies the data to/from small per-cpu buffer in the trampoline. If a page fault occurs during the copy, it is short-circuit by exception.s to not even reach C code. The change was motivated by the need to implement the Meltdown mitigation, but instead of KPTI the full split is done. The i386 architecture already shows the sizing problems, in particular, it is impossible to link clang and lld with debugging. I expect that the issues due to the virtual address space limits would only exaggerate and the split gives more liveness to the platform. Tested by: pho Discussed with: bde Sponsored by: The FreeBSD Foundation MFC after: 1 month Differential revision: https://reviews.freebsd.org/D14633
419 lines
11 KiB
C
419 lines
11 KiB
C
/*
|
|
* Copyright (c) 2004 Marcel Moolenaar
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/proc.h>
|
|
#include <vm/vm.h>
|
|
#include <vm/pmap.h>
|
|
#include <machine/pcb.h>
|
|
#include <machine/frame.h>
|
|
#include <machine/segments.h>
|
|
#include <machine/tss.h>
|
|
#include <err.h>
|
|
#include <kvm.h>
|
|
#include <string.h>
|
|
|
|
#include <defs.h>
|
|
#include <target.h>
|
|
#include <gdbthread.h>
|
|
#include <inferior.h>
|
|
#include <regcache.h>
|
|
#include <frame-unwind.h>
|
|
#include <i386-tdep.h>
|
|
|
|
#include "kgdb.h"
|
|
|
|
static int ofs_fix;
|
|
|
|
CORE_ADDR
|
|
kgdb_trgt_core_pcb(u_int cpuid)
|
|
{
|
|
return (kgdb_trgt_stop_pcb(cpuid, sizeof(struct pcb)));
|
|
}
|
|
|
|
void
|
|
kgdb_trgt_fetch_registers(int regno __unused)
|
|
{
|
|
struct kthr *kt;
|
|
struct pcb pcb;
|
|
|
|
kt = kgdb_thr_lookup_tid(ptid_get_pid(inferior_ptid));
|
|
if (kt == NULL)
|
|
return;
|
|
if (kvm_read(kvm, kt->pcb, &pcb, sizeof(pcb)) != sizeof(pcb)) {
|
|
warnx("kvm_read: %s", kvm_geterr(kvm));
|
|
memset(&pcb, 0, sizeof(pcb));
|
|
}
|
|
supply_register(I386_EBX_REGNUM, (char *)&pcb.pcb_ebx);
|
|
supply_register(I386_ESP_REGNUM, (char *)&pcb.pcb_esp);
|
|
supply_register(I386_EBP_REGNUM, (char *)&pcb.pcb_ebp);
|
|
supply_register(I386_ESI_REGNUM, (char *)&pcb.pcb_esi);
|
|
supply_register(I386_EDI_REGNUM, (char *)&pcb.pcb_edi);
|
|
supply_register(I386_EIP_REGNUM, (char *)&pcb.pcb_eip);
|
|
}
|
|
|
|
void
|
|
kgdb_trgt_store_registers(int regno __unused)
|
|
{
|
|
fprintf_unfiltered(gdb_stderr, "XXX: %s\n", __func__);
|
|
}
|
|
|
|
void
|
|
kgdb_trgt_new_objfile(struct objfile *objfile)
|
|
{
|
|
|
|
/*
|
|
* In revision 1.117 of i386/i386/exception.S trap handlers
|
|
* were changed to pass trapframes by reference rather than
|
|
* by value. Detect this by seeing if the first instruction
|
|
* at the 'calltrap' label is a "push %esp" which has the
|
|
* opcode 0x54.
|
|
*/
|
|
if (kgdb_parse("((char *)calltrap)[0]") == 0x54)
|
|
ofs_fix = 4;
|
|
else
|
|
ofs_fix = 0;
|
|
}
|
|
|
|
struct kgdb_tss_cache {
|
|
CORE_ADDR pc;
|
|
CORE_ADDR sp;
|
|
CORE_ADDR tss;
|
|
};
|
|
|
|
static int kgdb_trgt_tss_offset[15] = {
|
|
offsetof(struct i386tss, tss_eax),
|
|
offsetof(struct i386tss, tss_ecx),
|
|
offsetof(struct i386tss, tss_edx),
|
|
offsetof(struct i386tss, tss_ebx),
|
|
offsetof(struct i386tss, tss_esp),
|
|
offsetof(struct i386tss, tss_ebp),
|
|
offsetof(struct i386tss, tss_esi),
|
|
offsetof(struct i386tss, tss_edi),
|
|
offsetof(struct i386tss, tss_eip),
|
|
offsetof(struct i386tss, tss_eflags),
|
|
offsetof(struct i386tss, tss_cs),
|
|
offsetof(struct i386tss, tss_ss),
|
|
offsetof(struct i386tss, tss_ds),
|
|
offsetof(struct i386tss, tss_es),
|
|
offsetof(struct i386tss, tss_fs)
|
|
};
|
|
|
|
/*
|
|
* If the current thread is executing on a CPU, fetch the common_tss
|
|
* for that CPU.
|
|
*
|
|
* This is painful because 'struct pcpu' is variant sized, so we can't
|
|
* use it. Instead, we lookup the GDT selector for this CPU and
|
|
* extract the base of the TSS from there.
|
|
*/
|
|
static CORE_ADDR
|
|
kgdb_trgt_fetch_tss(void)
|
|
{
|
|
struct kthr *kt;
|
|
struct segment_descriptor sd;
|
|
uintptr_t addr, cpu0prvpage, tss;
|
|
|
|
kt = kgdb_thr_lookup_tid(ptid_get_pid(inferior_ptid));
|
|
if (kt == NULL || kt->cpu == NOCPU || kt->cpu < 0)
|
|
return (0);
|
|
|
|
addr = kgdb_lookup("gdt");
|
|
if (addr == 0)
|
|
return (0);
|
|
addr += (kt->cpu * NGDT + GPROC0_SEL) * sizeof(sd);
|
|
if (kvm_read(kvm, addr, &sd, sizeof(sd)) != sizeof(sd)) {
|
|
warnx("kvm_read: %s", kvm_geterr(kvm));
|
|
return (0);
|
|
}
|
|
if (sd.sd_type != SDT_SYS386BSY) {
|
|
warnx("descriptor is not a busy TSS");
|
|
return (0);
|
|
}
|
|
tss = sd.sd_hibase << 24 | sd.sd_lobase;
|
|
|
|
/*
|
|
* In SMP kernels, the TSS is stored as part of the per-CPU
|
|
* data. On older kernels, the CPU0's private page
|
|
* is stored at an address that isn't mapped in minidumps.
|
|
* However, the data is mapped at the alternate cpu0prvpage
|
|
* address. Thus, if the TSS is at the invalid address,
|
|
* change it to be relative to cpu0prvpage instead.
|
|
*/
|
|
if (trunc_page(tss) == 0xffc00000) {
|
|
addr = kgdb_lookup("cpu0prvpage");
|
|
if (addr == 0)
|
|
return (0);
|
|
if (kvm_read(kvm, addr, &cpu0prvpage, sizeof(cpu0prvpage)) !=
|
|
sizeof(cpu0prvpage)) {
|
|
warnx("kvm_read: %s", kvm_geterr(kvm));
|
|
return (0);
|
|
}
|
|
tss = cpu0prvpage + (tss & PAGE_MASK);
|
|
}
|
|
return ((CORE_ADDR)tss);
|
|
}
|
|
|
|
static struct kgdb_tss_cache *
|
|
kgdb_trgt_tss_cache(struct frame_info *next_frame, void **this_cache)
|
|
{
|
|
char buf[MAX_REGISTER_SIZE];
|
|
struct kgdb_tss_cache *cache;
|
|
|
|
cache = *this_cache;
|
|
if (cache == NULL) {
|
|
cache = FRAME_OBSTACK_ZALLOC(struct kgdb_tss_cache);
|
|
*this_cache = cache;
|
|
cache->pc = frame_func_unwind(next_frame);
|
|
frame_unwind_register(next_frame, SP_REGNUM, buf);
|
|
cache->sp = extract_unsigned_integer(buf,
|
|
register_size(current_gdbarch, SP_REGNUM));
|
|
cache->tss = kgdb_trgt_fetch_tss();
|
|
}
|
|
return (cache);
|
|
}
|
|
|
|
static void
|
|
kgdb_trgt_dblfault_this_id(struct frame_info *next_frame, void **this_cache,
|
|
struct frame_id *this_id)
|
|
{
|
|
struct kgdb_tss_cache *cache;
|
|
|
|
cache = kgdb_trgt_tss_cache(next_frame, this_cache);
|
|
*this_id = frame_id_build(cache->sp, cache->pc);
|
|
}
|
|
|
|
static void
|
|
kgdb_trgt_dblfault_prev_register(struct frame_info *next_frame,
|
|
void **this_cache, int regnum, int *optimizedp, enum lval_type *lvalp,
|
|
CORE_ADDR *addrp, int *realnump, void *valuep)
|
|
{
|
|
char dummy_valuep[MAX_REGISTER_SIZE];
|
|
struct kgdb_tss_cache *cache;
|
|
int ofs, regsz;
|
|
|
|
regsz = register_size(current_gdbarch, regnum);
|
|
|
|
if (valuep == NULL)
|
|
valuep = dummy_valuep;
|
|
memset(valuep, 0, regsz);
|
|
*optimizedp = 0;
|
|
*addrp = 0;
|
|
*lvalp = not_lval;
|
|
*realnump = -1;
|
|
|
|
ofs = (regnum >= I386_EAX_REGNUM && regnum <= I386_FS_REGNUM)
|
|
? kgdb_trgt_tss_offset[regnum] : -1;
|
|
if (ofs == -1)
|
|
return;
|
|
|
|
cache = kgdb_trgt_tss_cache(next_frame, this_cache);
|
|
if (cache->tss == 0)
|
|
return;
|
|
*addrp = cache->tss + ofs;
|
|
*lvalp = lval_memory;
|
|
target_read_memory(*addrp, valuep, regsz);
|
|
}
|
|
|
|
static const struct frame_unwind kgdb_trgt_dblfault_unwind = {
|
|
UNKNOWN_FRAME,
|
|
&kgdb_trgt_dblfault_this_id,
|
|
&kgdb_trgt_dblfault_prev_register
|
|
};
|
|
|
|
struct kgdb_frame_cache {
|
|
int frame_type;
|
|
CORE_ADDR pc;
|
|
CORE_ADDR sp;
|
|
};
|
|
#define FT_NORMAL 1
|
|
#define FT_INTRFRAME 2
|
|
#define FT_INTRTRAPFRAME 3
|
|
#define FT_TIMERFRAME 4
|
|
|
|
static int kgdb_trgt_frame_offset[15] = {
|
|
offsetof(struct trapframe, tf_eax),
|
|
offsetof(struct trapframe, tf_ecx),
|
|
offsetof(struct trapframe, tf_edx),
|
|
offsetof(struct trapframe, tf_ebx),
|
|
offsetof(struct trapframe, tf_esp),
|
|
offsetof(struct trapframe, tf_ebp),
|
|
offsetof(struct trapframe, tf_esi),
|
|
offsetof(struct trapframe, tf_edi),
|
|
offsetof(struct trapframe, tf_eip),
|
|
offsetof(struct trapframe, tf_eflags),
|
|
offsetof(struct trapframe, tf_cs),
|
|
offsetof(struct trapframe, tf_ss),
|
|
offsetof(struct trapframe, tf_ds),
|
|
offsetof(struct trapframe, tf_es),
|
|
offsetof(struct trapframe, tf_fs)
|
|
};
|
|
|
|
static struct kgdb_frame_cache *
|
|
kgdb_trgt_frame_cache(struct frame_info *next_frame, void **this_cache)
|
|
{
|
|
char buf[MAX_REGISTER_SIZE];
|
|
struct kgdb_frame_cache *cache;
|
|
char *pname;
|
|
CORE_ADDR pcx;
|
|
uintptr_t addr, setidt_disp;
|
|
|
|
cache = *this_cache;
|
|
if (cache == NULL) {
|
|
cache = FRAME_OBSTACK_ZALLOC(struct kgdb_frame_cache);
|
|
*this_cache = cache;
|
|
pcx = frame_pc_unwind(next_frame);
|
|
if (pcx >= PMAP_TRM_MIN_ADDRESS) {
|
|
addr = kgdb_lookup("setidt_disp");
|
|
if (addr != 0) {
|
|
if (kvm_read(kvm, addr, &setidt_disp,
|
|
sizeof(setidt_disp)) !=
|
|
sizeof(setidt_disp))
|
|
warnx("kvm_read: %s", kvm_geterr(kvm));
|
|
else
|
|
pcx -= setidt_disp;
|
|
}
|
|
}
|
|
cache->pc = pcx;
|
|
find_pc_partial_function(cache->pc, &pname, NULL, NULL);
|
|
if (pname[0] != 'X')
|
|
cache->frame_type = FT_NORMAL;
|
|
else if (strcmp(pname, "Xtimerint") == 0)
|
|
cache->frame_type = FT_TIMERFRAME;
|
|
else if (strcmp(pname, "Xcpustop") == 0 ||
|
|
strcmp(pname, "Xrendezvous") == 0 ||
|
|
strcmp(pname, "Xipi_intr_bitmap_handler") == 0 ||
|
|
strcmp(pname, "Xlazypmap") == 0)
|
|
cache->frame_type = FT_INTRTRAPFRAME;
|
|
else
|
|
cache->frame_type = FT_INTRFRAME;
|
|
frame_unwind_register(next_frame, SP_REGNUM, buf);
|
|
cache->sp = extract_unsigned_integer(buf,
|
|
register_size(current_gdbarch, SP_REGNUM));
|
|
}
|
|
return (cache);
|
|
}
|
|
|
|
static void
|
|
kgdb_trgt_trapframe_this_id(struct frame_info *next_frame, void **this_cache,
|
|
struct frame_id *this_id)
|
|
{
|
|
struct kgdb_frame_cache *cache;
|
|
|
|
cache = kgdb_trgt_frame_cache(next_frame, this_cache);
|
|
*this_id = frame_id_build(cache->sp, cache->pc);
|
|
}
|
|
|
|
static void
|
|
kgdb_trgt_trapframe_prev_register(struct frame_info *next_frame,
|
|
void **this_cache, int regnum, int *optimizedp, enum lval_type *lvalp,
|
|
CORE_ADDR *addrp, int *realnump, void *valuep)
|
|
{
|
|
char dummy_valuep[MAX_REGISTER_SIZE];
|
|
struct kgdb_frame_cache *cache;
|
|
int ofs, regsz;
|
|
|
|
regsz = register_size(current_gdbarch, regnum);
|
|
|
|
if (valuep == NULL)
|
|
valuep = dummy_valuep;
|
|
memset(valuep, 0, regsz);
|
|
*optimizedp = 0;
|
|
*addrp = 0;
|
|
*lvalp = not_lval;
|
|
*realnump = -1;
|
|
|
|
ofs = (regnum >= I386_EAX_REGNUM && regnum <= I386_FS_REGNUM)
|
|
? kgdb_trgt_frame_offset[regnum] + ofs_fix : -1;
|
|
if (ofs == -1)
|
|
return;
|
|
|
|
cache = kgdb_trgt_frame_cache(next_frame, this_cache);
|
|
switch (cache->frame_type) {
|
|
case FT_NORMAL:
|
|
break;
|
|
case FT_INTRFRAME:
|
|
ofs += 4;
|
|
break;
|
|
case FT_TIMERFRAME:
|
|
break;
|
|
case FT_INTRTRAPFRAME:
|
|
ofs -= ofs_fix;
|
|
break;
|
|
default:
|
|
fprintf_unfiltered(gdb_stderr, "Correct FT_XXX frame offsets "
|
|
"for %d\n", cache->frame_type);
|
|
break;
|
|
}
|
|
*addrp = cache->sp + ofs;
|
|
*lvalp = lval_memory;
|
|
target_read_memory(*addrp, valuep, regsz);
|
|
}
|
|
|
|
static const struct frame_unwind kgdb_trgt_trapframe_unwind = {
|
|
UNKNOWN_FRAME,
|
|
&kgdb_trgt_trapframe_this_id,
|
|
&kgdb_trgt_trapframe_prev_register
|
|
};
|
|
|
|
const struct frame_unwind *
|
|
kgdb_trgt_trapframe_sniffer(struct frame_info *next_frame)
|
|
{
|
|
char *pname;
|
|
CORE_ADDR pc;
|
|
|
|
pc = frame_pc_unwind(next_frame);
|
|
if (pc >= PMAP_TRM_MIN_ADDRESS)
|
|
return (&kgdb_trgt_trapframe_unwind);
|
|
pname = NULL;
|
|
find_pc_partial_function(pc, &pname, NULL, NULL);
|
|
if (pname == NULL)
|
|
return (NULL);
|
|
if (strcmp(pname, "dblfault_handler") == 0)
|
|
return (&kgdb_trgt_dblfault_unwind);
|
|
if (strcmp(pname, "calltrap") == 0 ||
|
|
(pname[0] == 'X' && pname[1] != '_'))
|
|
return (&kgdb_trgt_trapframe_unwind);
|
|
/* printf("%s: %llx =%s\n", __func__, pc, pname); */
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* This function ensures, that the PC is inside the
|
|
* function section which is understood by GDB.
|
|
*
|
|
* Return 0 when fixup is necessary, -1 otherwise.
|
|
*/
|
|
int
|
|
kgdb_trgt_pc_fixup(CORE_ADDR *pc __unused)
|
|
{
|
|
|
|
return (-1);
|
|
}
|