Skip to content

Commit

Permalink
Fix locking issues with CPU refactor
Browse files Browse the repository at this point in the history
There was a deadlock when cpu_run called cpu_step_to_interrupt with mem
read-locked, and it would write-lock mem to clean up jetsam.
  • Loading branch information
tbodt committed Jun 9, 2020
1 parent 269eb6e commit 6400172
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 75 deletions.
3 changes: 1 addition & 2 deletions emu/cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

struct cpu_state;
struct tlb;
void cpu_run(struct cpu_state *cpu);
int cpu_step_to_interrupt(struct cpu_state *cpu, struct tlb *tlb);
int cpu_run_to_interrupt(struct cpu_state *cpu, struct tlb *tlb);

union mm_reg {
qword_t qw;
Expand Down
1 change: 1 addition & 0 deletions emu/tlb.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
void tlb_init(struct tlb *tlb, struct mem *mem) {
tlb->mem = mem;
tlb->dirty_page = TLB_PAGE_EMPTY;
tlb->mem_changes = mem->changes;
tlb_flush(tlb);
}

Expand Down
1 change: 1 addition & 0 deletions emu/tlb.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct tlb_entry {
struct tlb {
struct mem *mem;
page_t dirty_page;
unsigned mem_changes;
struct tlb_entry entries[TLB_SIZE];
};

Expand Down
60 changes: 23 additions & 37 deletions jit/jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,12 @@ static inline size_t jit_cache_hash(addr_t ip) {
return (ip ^ (ip >> 12)) % JIT_CACHE_SIZE;
}

int cpu_step_to_interrupt(struct cpu_state *cpu, struct tlb *tlb) {
static int cpu_step_to_interrupt(struct cpu_state *cpu, struct tlb *tlb) {
struct jit *jit = cpu->mem->jit;
struct jit_block *cache[JIT_CACHE_SIZE] = {};
struct jit_frame frame = {.cpu = *cpu, .ret_cache = {}};

int interrupt = INT_NONE;

while (interrupt == INT_NONE) {
addr_t ip = frame.cpu.eip;
size_t cache_index = jit_cache_hash(ip);
Expand Down Expand Up @@ -219,24 +218,10 @@ int cpu_step_to_interrupt(struct cpu_state *cpu, struct tlb *tlb) {
interrupt = INT_TIMER;
}

jit = cpu->mem->jit;
lock(&jit->lock);
if (!list_empty(&jit->jetsam)) {
// write-lock the mem to wait until other jit threads get to
// this point, so they will all clear out their block pointers
// TODO: use RCU for better performance
unlock(&jit->lock);
write_wrlock(&cpu->mem->lock);
lock(&jit->lock);
jit_free_jetsam(jit);
write_wrunlock(&cpu->mem->lock);
}
unlock(&jit->lock);

return interrupt;
}

static int cpu_step(struct cpu_state *cpu, struct tlb *tlb) {
static int cpu_single_step(struct cpu_state *cpu, struct tlb *tlb) {
struct gen_state state;
gen_start(cpu->eip, &state);
gen_step32(&state, tlb);
Expand All @@ -245,34 +230,35 @@ static int cpu_step(struct cpu_state *cpu, struct tlb *tlb) {

struct jit_block *block = state.block;
struct jit_frame frame = {.cpu = *cpu};
read_wrlock(&cpu->mem->lock);
int interrupt = jit_enter(block, &frame, tlb);
read_wrunlock(&cpu->mem->lock);
*cpu = frame.cpu;
jit_block_free(NULL, block);
if (interrupt == INT_NONE && cpu->tf)
if (interrupt == INT_NONE)
interrupt = INT_DEBUG;
return interrupt;
}

void cpu_run(struct cpu_state *cpu) {
struct tlb tlb;
tlb_init(&tlb, cpu->mem);

int cpu_run_to_interrupt(struct cpu_state *cpu, struct tlb *tlb) {
read_wrlock(&cpu->mem->lock);
unsigned changes = cpu->mem->changes;

while (true) {
int interrupt = (cpu->tf ? cpu_step : cpu_step_to_interrupt)(cpu, &tlb);
if (interrupt != INT_NONE) {
read_wrunlock(&cpu->mem->lock);
handle_interrupt(cpu->trapno = interrupt);
read_wrlock(&cpu->mem->lock);
if (cpu->mem->changes != tlb->mem_changes)
tlb_init(tlb, cpu->mem);
int interrupt = (cpu->tf ? cpu_single_step : cpu_step_to_interrupt)(cpu, tlb);
cpu->trapno = interrupt;
read_wrunlock(&cpu->mem->lock);

tlb.mem = cpu->mem;
if (cpu->mem->changes != changes)
tlb_flush(&tlb);
changes = cpu->mem->changes;
}
struct jit *jit = cpu->mem->jit;
lock(&jit->lock);
if (!list_empty(&jit->jetsam)) {
// write-lock the mem to wait until other jit threads get to
// this point, so they will all clear out their block pointers
// TODO: use RCU for better performance
unlock(&jit->lock);
write_wrlock(&cpu->mem->lock);
lock(&jit->lock);
jit_free_jetsam(jit);
write_wrunlock(&cpu->mem->lock);
}
unlock(&jit->lock);

return interrupt;
}
1 change: 0 additions & 1 deletion kernel/calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ syscall_t syscall_table[] = {
#define NUM_SYSCALLS (sizeof(syscall_table) / sizeof(syscall_table[0]))

void handle_interrupt(int interrupt) {
TRACE_(instr, "\n");
struct cpu_state *cpu = &current->cpu;
if (interrupt == INT_SYSCALL) {
unsigned syscall_num = cpu->eax;
Expand Down
22 changes: 14 additions & 8 deletions kernel/task.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "kernel/calls.h"
#include "kernel/task.h"
#include "emu/memory.h"
#include "emu/tlb.h"

__thread struct task *current;

Expand Down Expand Up @@ -91,16 +92,21 @@ void task_destroy(struct task *task) {
free(task);
}

void (*task_run_hook)(void) = NULL;
void task_run_current() {
struct cpu_state *cpu = &current->cpu;
struct tlb tlb;
tlb_init(&tlb, current->mem);
while (true) {
int interrupt = cpu_run_to_interrupt(cpu, &tlb);
handle_interrupt(interrupt);
}
}

static void *task_run(void *task) {
static void *task_thread(void *task) {
current = task;
set_thread_name(current->comm);
if (task_run_hook)
task_run_hook();
else
cpu_run(&current->cpu);
die("task_run returned"); // above function call should never return
task_run_current();
die("task_thread returned"); // above function call should never return
}

static pthread_attr_t task_thread_attr;
Expand All @@ -110,7 +116,7 @@ __attribute__((constructor)) static void create_attr() {
}

void task_start(struct task *task) {
if (pthread_create(&task->thread, &task_thread_attr, task_run, task) < 0)
if (pthread_create(&task->thread, &task_thread_attr, task_thread, task) < 0)
die("could not create thread");
}

Expand Down
3 changes: 1 addition & 2 deletions kernel/task.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,9 @@ struct task *pid_get_task_zombie(dword_t id); // don't return null if the task e

#define MAX_PID (1 << 15) // oughta be enough

// When a thread is created to run a new process, this function is used.
extern void (*task_run_hook)(void);
// TODO document
void task_start(struct task *task);
void task_run_current();

extern void (*exit_hook)(struct task *task, int code);

Expand Down
2 changes: 1 addition & 1 deletion main.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ int main(int argc, char *const argv[]) {
}
do_mount(&procfs, "proc", "/proc", "", 0);
do_mount(&devptsfs, "devpts", "/dev/pts", "", 0);
cpu_run(&current->cpu);
task_run_current();
}
22 changes: 8 additions & 14 deletions tools/ptraceomatic.c
Original file line number Diff line number Diff line change
Expand Up @@ -258,21 +258,15 @@ static void pt_copy_to_real(int pid, addr_t start, size_t size) {
static void step_tracing(struct cpu_state *cpu, struct tlb *tlb, int pid, int sender, int receiver) {
// step fake cpu
cpu->tf = 1;
unsigned changes = cpu->mem->changes;
int interrupt = cpu_step_to_interrupt(cpu, tlb);
if (interrupt != INT_NONE) {
cpu->trapno = interrupt;
// hack to clean up before the exit syscall
if (interrupt == INT_SYSCALL && cpu->eax == 1) {
if (kill(pid, SIGKILL) < 0) {
perror("kill tracee during exit");
exit(1);
}
int interrupt = cpu_run_to_interrupt(cpu, tlb);
// hack to clean up before the exit syscall
if (interrupt == INT_SYSCALL && cpu->eax == 1) {
if (kill(pid, SIGKILL) < 0) {
perror("kill tracee during exit");
exit(1);
}
handle_interrupt(interrupt);
}
if (cpu->mem->changes != changes)
tlb_flush(tlb);
handle_interrupt(interrupt);

// step real cpu
// intercept cpuid, rdtsc, and int $0x80, though
Expand Down Expand Up @@ -501,7 +495,7 @@ int main(int argc, char *const argv[]) {
printk("failure: resetting cpu\n");
*cpu = old_cpu;
__asm__("int3");
cpu_step_to_interrupt(cpu, &tlb);
cpu_run_to_interrupt(cpu, &tlb);
}
undefined_flags = undefined_flags_mask(cpu, &tlb);
old_cpu = *cpu;
Expand Down
14 changes: 4 additions & 10 deletions tools/unicornomatic.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,14 +205,8 @@ static void _mem_sync(struct tlb *tlb, uc_engine *uc, addr_t addr, dword_t size)
void step_tracing(struct cpu_state *cpu, struct tlb *tlb, uc_engine *uc) {
// step ish
addr_t old_brk = current->mm->brk; // this is important
unsigned changes = cpu->mem->changes;
int interrupt = cpu_step32(cpu, tlb);
if (interrupt != INT_NONE) {
cpu->trapno = interrupt;
handle_interrupt(interrupt);
}
if (cpu->mem->changes != changes)
tlb_flush(tlb);
int interrupt = cpu_run_to_interrupt(cpu, tlb);
handle_interrupt(interrupt);

// step unicorn
uc_interrupt = -1;
Expand Down Expand Up @@ -344,7 +338,7 @@ void step_tracing(struct cpu_state *cpu, struct tlb *tlb, uc_engine *uc) {
break;

case 91: // munmap
if (cpu->eax >= 0)
if ((int) cpu->eax >= 0)
uc_unmap(uc, cpu->ebx, cpu->ecx);
break;

Expand Down Expand Up @@ -503,7 +497,7 @@ int main(int argc, char *const argv[]) {
printk("resetting cpu\n");
*cpu = old_cpu;
debugger;
cpu_step32(cpu, &tlb);
cpu_run_to_interrupt(cpu, &tlb);
}
undefined_flags = undefined_flags_mask(cpu, &tlb);
old_cpu = *cpu;
Expand Down

0 comments on commit 6400172

Please sign in to comment.