summaryrefslogblamecommitdiffstats
path: root/debugger.c
blob: 6a1cb9d4ec6de6d9db4b6f3e176fc79a221006a1 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
                   
                
                   
                  
                   
                   





                              


                     

                                 
                         
                          
 



                                                                           
 




                                                                                

         

                
 





                                                             

 

                                                    
                                                                              

                               
                                                                     


                                                    
                                                              
                             



         

                                                      
                                                                              
                           
                                                                 
                             
         
                             







                                                                               
         


     




























                                                                              
     

               

 
                                            

















                                               






                                                                                
 

























                                                                                 
 

                                                
 
                             
             


         
 








                                                                                
         

     
 











                                                                                


     











                                                                                
         


     
                                                         
                                                  
                  
                        
                                  

                                                  


                
                                                                  




                                             
                


                    

                                  


              
                                                                                
                                               
                         


                     



                   
                                          
             
 
 
                                                                                


                                                                              
                     

         
                

 
                                                  
                                                                        
 

                                                                      
 

                                     
                    
     
 
                        

                                                   
                                       
                                                   
 
                      
                                                              
                                  

                                 
             

                                                     


         
                      
 
                                
                        
                

 






                                                                                
                                

                               
                                
 
                                         


                                
 

                                                     

                                      
                                                            
                       
         
                     

     
                           

                      

 











                                              
                  

                             

     

                                                          


                 
                            
                   


                                         

                                     
                 

     
                              
                   


                                      

                                         
                 

     
                                         
                           

                                                        
                                       

                              
                                                


                                                          
                                              



                                

                                            
                                 
 

                                                    
                                              

                                             
                 
 
                         
 
                                                    







                                                             




                                

                                             
                                 
 

                                                    
                                              

                                             

                 





                                                    
                                              
 

                                                    
                                              

                                             

                 
                                   




                                

                                              
                                 
 

                                                    
                                              

                                             

                 




                                
 




                                                                 
 




                                                    

                 
                                   




                                              

                                                      
                                 
 

                                                    
                                              

                                             

                 




                         

 
                                 
                                     



                                                 
                       


             

                                      
                  

     








                                                        
 


                                      
     
 






                                                        


             

                                   


                  



                                       
 

                      

     






                                                        


             
                                     
                       

                  
 




                                       
                      

                  
 


                                              




                                                   








                                                                 
 
                           
            
                             

     
                     

                      
 
 
                                     
                       


                  





                                       

 

                                                                    
               
 


                     
 
                                         
                                                                   

                                                                   




                




 
 
















                                                                                   
#include <dirent.h>
#include <elf.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/uio.h>
#include <sys/wait.h>

#include <capstone/capstone.h>

#include "debugger.h"
#include "helpers.h"

static const int PTRACE_OPTIONS =
    PTRACE_O_TRACECLONE |
    PTRACE_O_TRACEEXEC  |
    PTRACE_O_TRACESYSGOOD;

static const int PTRACE_CHILD_OPTIONS = PTRACE_OPTIONS | PTRACE_O_EXITKILL;
static const int STOP_ALL_ON_EVENT = 1;
static const useconds_t SCHEDULER_DELAY = 100000;
static const unsigned int BREAKPOINT_INSN = 0xcc;

static struct thread *thread_by_id(struct process *proc, pid_t id) {
    struct list *threads = &proc->threads;
    for (struct thread *th = threads->head; th != threads->end; th = th->next) {
        if (th->id == id) {
            return th;
        }
    }
    return NULL;
}

static void free_breakpoints(struct process *proc) {
    while (proc->breakpoints.head != proc->breakpoints.end) {
        struct breakpoint *b = proc->breakpoints.head;
        list_remove(b);
        free(b);
    }
}

static void install_breakpoints(struct thread *th) {
    struct list *breaks = &th->proc->breakpoints;
    for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) {
        if (!b->installed) {
            unsigned long word;
            word = ptrace(PTRACE_PEEKTEXT, th->id, b->address, NULL);
            b->text = word;

            word = (word & ~0xff) | BREAKPOINT_INSN;
            ptrace(PTRACE_POKETEXT, th->id, b->address, word);
            b->installed = 1;
        }
    }
}

static void uninstall_breakpoints(struct thread *th) {
    struct list *breaks = &th->proc->breakpoints;
    for (struct breakpoint *b = breaks->tail; b != breaks->end; b = b->prev) {
        if (b->installed) {
            ptrace(PTRACE_POKETEXT, th->id, b->address, b->text);
            b->installed = 0;
        }
        if (b->enabled < 0) {
            struct thread *t;
            if (b->tid == 0 ||
                    ((t = thread_by_id(th->proc, b->tid)) && !t->shouldcont)) {
                struct breakpoint *del = b;
                b = b->next;
                list_remove(del);
                free(del);
            }
        }
    }
}

static int detect_breakpoint(struct thread *th, int *restart) {
    int ret = 0;
    *restart = 0;

    struct user_regs_struct regs;
    struct iovec ivregs = { &regs, sizeof(regs) };
    ptrace(PTRACE_GETREGSET, th->id, NT_PRSTATUS, &ivregs);

    /* implement with get_breakpoint? */
    struct list *breaks = &th->proc->breakpoints;
    for (struct breakpoint *b = breaks->tail; b != breaks->end; b = b->prev) {
        if (b->installed && (regs.rip - 1 == b->address)) {
            regs.rip--;
            ptrace(PTRACE_SETREGSET, th->id, NT_PRSTATUS, &ivregs);
            b->hits++;
            ret = b->user;

            if (b->stack != 0 && b->stack != regs.rsp) {
                *restart = 1;
            }
            if (b->tid != 0 && b->tid != th->id) {
                *restart = 1;
            }
            if (!b->enabled) {
                *restart = 1;
            }

            break;
        }
    }

    return ret;
}

static void free_states(struct thread *th) {
    while (th->states.head != th->states.end) {
        struct state *s = th->states.head;

        while (s->maps.head != s->maps.end) {
            struct map *m = s->maps.head;
            list_remove(m);
            free(m->data);
            free(m);
        }

        list_remove(s);
        free(s);
    }

    th->state = NULL;
    th->clearstates = 0;
}

static void capture_state(struct process *proc) {
    struct list *threads = &proc->threads;
    for (struct thread *th = threads->head; th != threads->end; th = th->next) {
        if (!th->shouldcont) {
            if (th->clearstates) {
                free_states(th);
            }

            struct state *s = xmalloc(sizeof(*s));
            struct iovec regs = { &s->regs, sizeof(s->regs) };
            ptrace(PTRACE_GETREGSET, th->id, NT_PRSTATUS, &regs);
            list_init(&s->maps);

            list_insert(th->states.end, s);
            th->state = s;

            char mapspath[32], entry[512];
            snprintf(mapspath, sizeof(mapspath), "/proc/%li/maps", (long)th->id);

            FILE *maps = fopen(mapspath, "r");
            if (maps) {
                while (fgets(entry, sizeof(entry), maps)) {
                    struct map *m = xmalloc(sizeof(*m));
                    sscanf(entry, "%lx-%lx ", &m->start, &m->end);
                    size_t size = m->end - m->start;
                    m->data = xmalloc(size);

                    struct iovec loc = { m->data, size };
                    struct iovec rem = { (void *)m->start, size };
                    if (process_vm_readv(th->id, &loc, 1, &rem, 1, 0) < 0) {
                        free(m->data);
                        free(m);
                        continue;
                    }

                    list_insert(s->maps.end, m);
                }

                fclose(maps);
            }
        }
    }
}

static void interrupt_all_threads(struct process *proc) {
    struct list *threads = &proc->threads;
    for (struct thread *th = threads->head; th != threads->end; th = th->next) {
        if (!th->stopped) {
            ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL);
            while (!dbg_wait(th, 0)) {}
        }
        if (STOP_ALL_ON_EVENT) {
            th->shouldcont = 0;
        }
    }
}

static void continue_all_threads(struct process *proc) {
    struct list *threads = &proc->threads;
    for (struct thread *th = threads->head; th != threads->end; th = th->next) {
        if (th->id > 0) {
            ptrace(PTRACE_CONT, th->id, NULL, th->signal);
            th->stopped = 0;
            th->signal = 0;
            th->cont = 0;
            th->shouldcont = 2;
            strcpy(th->status, "RUNNING");
            th->clearstates = 1;
        }
    }
}

/* 0: stop  1: singlestep  2: cont  3: syscall */
/* this is simplified, surely incorrect, and needs updated once testable */
static void resume_threads(struct process *proc) {
    struct list *threads = &proc->threads;
    for (struct thread *th = threads->head; th != threads->end; th = th->next) {
        if (th->shouldcont) {
            install_breakpoints(th);
            ptrace(PTRACE_CONT, th->id, NULL, th->signal);
            th->stopped = 0;
            th->signal = 0;
            th->cont = 0;
            strcpy(th->status, "RUNNING");
        }
    }
}

static struct process *new_process(pid_t id, int child) {
    struct process *proc = xmalloc(sizeof(*proc));
    proc->id = id;
    proc->child = child;
    list_init(&proc->breakpoints);
    list_init(&proc->threads);
    memset(proc->status, 0, sizeof(proc->status));
    return proc;
}

static struct thread *new_thread(struct process *proc, pid_t id) {
    struct thread *th = xmalloc(sizeof(*th));
    th->proc = proc;
    list_init(&th->states);
    th->state = NULL;
    th->clearstates = 0;
    th->id = id;
    th->stopped = 0;
    th->signal = 0;
    th->cont = 0;
    th->shouldcont = 0;
    strcpy(th->status, "RUNNING");
    return th;
}

struct breakpoint *add_breakpoint(struct process *proc, unsigned long address) {
    struct breakpoint *b = xmalloc(sizeof(*b));
    b->address = address;
    b->text = 0;
    b->installed = 0;
    b->hits = 0;
    b->user = 1;
    b->stack = 0;
    b->tid = 0;
    b->enabled = 1;
    list_insert(proc->breakpoints.end, b);
    return b;
}

struct breakpoint *get_breakpoint(struct process *proc, unsigned long address) {
    struct list *breaks = &proc->breakpoints;
    for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) {
        if (b->address == address) {
            return b;
        }
    }
    return NULL;
}

struct process *dbg_attach(pid_t pid, int child) {
    const int OPTIONS = (child ? PTRACE_CHILD_OPTIONS : PTRACE_OPTIONS);

    char taskpath[32];
    snprintf(taskpath, sizeof(taskpath), "/proc/%li/task", (long)pid);

    DIR *taskdir = opendir(taskpath);
    if (!taskdir) {
        return NULL;
    }

    struct dirent *task;
    struct process *proc = new_process(pid, child);

    while ((task = readdir(taskdir))) {
        pid_t id = strict_strtoul(task->d_name, 0);

        if (id != 0) {
            if (ptrace(PTRACE_SEIZE, id, NULL, OPTIONS) < 0) {
                closedir(taskdir);
                dbg_detach(proc);
                return NULL;
            }
            struct thread *th = new_thread(proc, id);
            list_insert(proc->threads.end, th);
        }
    }

    closedir(taskdir);

    interrupt_all_threads(proc);
    capture_state(proc);
    return proc;
}

void dbg_detach(struct process *proc) {
    /* ptrace requires that threads be stopped before calling PTRACE_DETACH.
     * In some cases, a blocked thread can possibly have a pending trap which
     * would crash the process if encountered after ptracing.  We cycle an extra
     * continue/interrupt to consume this trap event ourselves before detaching.
     * Threads must actually get scheduled for this to be effective, which is
     * the reason for the usleep.  A better approach is welcome here. */
    interrupt_all_threads(proc);
    continue_all_threads(proc);
    usleep(SCHEDULER_DELAY);
    interrupt_all_threads(proc);

    /* Supplement to PTRACE_O_EXITKILL */
    if (proc->child) {
        kill(proc->id, SIGKILL);
    }

    while (proc->threads.head != proc->threads.end) {
        struct thread *th = proc->threads.head;
        if (th->id > 0) {
            uninstall_breakpoints(th);
            ptrace(PTRACE_DETACH, th->id, NULL, th->signal);
            th->id = 0;
        }
        dbg_free(th);
    }

    free_breakpoints(proc);
    list_remove(proc);
    free(proc);
}

int dbg_free(struct thread *th) {
    if (th->id <= 0) {
        free_states(th);
        list_remove(th);
        free(th);
        return 0;
    }
    return -1;
}

int dbg_wait(struct thread *th, int primary) {
    if (th->id <= 0) {
        return -1;
    } else if (th->stopped) {
        return 1;
    }

    int status;
    if (waitpid(th->id, &status, __WALL | WNOHANG) <= 0) {
        return 0;
    }

    if (WIFEXITED(status)) {
        th->id = 0;
        th->stopped = 1;
        th->signal = WEXITSTATUS(status);
        th->cont = 0;
        th->shouldcont = 0;
        strcpy(th->status, "EXITED");
        return 1;
    }

    if (WIFSIGNALED(status)) {
        th->id = 0;
        th->stopped = 1;
        th->signal = WTERMSIG(status);
        th->cont = 0;
        th->shouldcont = 0;
        strcpy(th->status, "TERMINATED");
        return 1;
    }

    struct thread *newth; // event thread
    unsigned long eventmsg;
    ptrace(PTRACE_GETEVENTMSG, th->id, NULL, &eventmsg);

    /* todo: process status messages */
    if (WIFSTOPPED(status)) {
        switch (status >> 8) {
            /* todo: other ptrace event stops */
            case SIGTRAP | (PTRACE_EVENT_CLONE << 8):
                newth = new_thread(th->proc, eventmsg);
                list_insert(th->proc->threads.end, newth);
                while (!dbg_wait(newth, 0)) {}

                th->stopped = 1;
                th->signal = 0;
                th->cont = 0;
                th->shouldcont = 0;
                strcpy(th->status, "CLONE");
                th->state = NULL;

                if (primary) {
                    interrupt_all_threads(th->proc);
                    uninstall_breakpoints(th);
                    capture_state(th->proc);
                    resume_threads(th->proc);
                }

                return 1;

            case SIGTRAP | (PTRACE_EVENT_EXEC << 8):
                if ((pid_t)eventmsg != th->id) {
                    newth = thread_by_id(th->proc, eventmsg);
                    newth->id = 0;
                    newth->stopped = 1;
                    newth->signal = 0;
                    newth->cont = 0;
                    newth->shouldcont = 0;
                    strcpy(newth->status, "EXITED");
                }

                th->stopped = 1;
                th->signal = 0;
                th->cont = 0;
                th->shouldcont = 0;
                strcpy(th->status, "EXECVE");
                th->state = NULL;

                if (primary) {
                    interrupt_all_threads(th->proc);
                    uninstall_breakpoints(th);
                    capture_state(th->proc);
                    resume_threads(th->proc);
                }

                return 1;

            case SIGTRAP | (PTRACE_EVENT_STOP << 8):
                th->stopped = 1;
                th->signal = 0;
                th->cont = 0;
                strcpy(th->status, "STOPPED");

                if (primary) {
                    interrupt_all_threads(th->proc);
                    uninstall_breakpoints(th);
                    capture_state(th->proc);
                    resume_threads(th->proc);
                }

                return th->stopped;

            case SIGTRAP | 0x80:
                th->stopped = 1;
                th->signal = 0;
                th->cont = 0;
                th->shouldcont = 0;
                strcpy(th->status, "SYSCALL");
                th->state = NULL;

                if (primary) {
                    interrupt_all_threads(th->proc);
                    uninstall_breakpoints(th);
                    capture_state(th->proc);
                    resume_threads(th->proc);
                }

                return 1;

            case SIGTRAP:
                th->stopped = 1;
                th->signal = 0;

                int restart;
                int bp = detect_breakpoint(th, &restart);
                strcpy(th->status, (bp ? "BREAKPOINT" : "STEP"));
                //th->shouldcont = (b && b->enabled == 0);
                th->shouldcont = (restart ? 2 : 0);

                if (primary) {
                    interrupt_all_threads(th->proc);
                    uninstall_breakpoints(th);
                    capture_state(th->proc);
                    resume_threads(th->proc);
                }

                return th->stopped;

            default:
                th->stopped = 1;
                th->signal = WSTOPSIG(status);
                th->cont = 0;
                th->shouldcont = 0;
                strcpy(th->status, "SIGNAL DELIVERY");
                th->state = NULL;

                if (primary) {
                    interrupt_all_threads(th->proc);
                    uninstall_breakpoints(th);
                    capture_state(th->proc);
                    resume_threads(th->proc);
                }

                return 1;
        }
    }

    return -1;
}

int dbg_intr(struct thread *th) {
    if (th->id <= 0 || th->stopped) {
        return -1;
    }

    ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL);
    th->shouldcont = 0;
    return 0;
}

int dbg_cont(struct thread *th) {
    if (th->id <= 0 || !th->stopped) {
        return -1;
    }

    ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal);
    th->stopped = 0;
    th->signal = 0;
    th->cont = PTRACE_CONT;
    th->shouldcont = 1;
    strcpy(th->status, "RUNNING");
    th->clearstates = 1;
    return 0;
}

int dbg_syscall(struct thread *th) {
    if (th->id <= 0 || !th->stopped) {
        return -1;
    }

    ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal);
    th->stopped = 0;
    th->signal = 0;
    th->cont = PTRACE_SYSCALL;
    th->shouldcont = 1;
    strcpy(th->status, "RUNNING");
    th->clearstates = 1;
    return 0;
}

int dbg_stepin(struct thread *th) {
    if (!th->stopped) {
        return -1;
    }

    if (th->state != th->states.tail) {
        th->state = th->state->next;
        return 0;
    }

    if (th->id <= 0) {
        return -1;
    }

    ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal);
    th->stopped = 0;
    th->signal = 0;
    th->cont = PTRACE_SINGLESTEP;
    th->shouldcont = 1;
    strcpy(th->status, "RUNNING");
    th->clearstates = 0;
    return 0;
}

int dbg_stepover(struct thread *th) {
    if (!th->stopped) {
        return -1;
    }

    if (th->state != th->states.tail) {
        th->state = th->state->next;
        return 0;
    }

    if (th->id <= 0) {
        return -1;
    }

    csh handle;
    cs_open(CS_ARCH_X86, CS_MODE_64, &handle);
    cs_insn *insn = cs_malloc(handle);

    uint64_t address = th->state->regs.rip;
    size_t size = 128;
    const uint8_t *code = deref(th, address, size);

    cs_disasm_iter(handle, &code, &size, &address, insn);

    int ret = -1;
    if (insn->id == X86_INS_CALL) {
        struct breakpoint *b = add_breakpoint(th->proc, address);
        b->user = 0;
        b->stack = th->state->regs.rsp;
        b->tid = th->id;
        b->enabled = -1;

        ret = dbg_cont(th);
    } else {
        ret = dbg_stepin(th);
    }

    cs_free(insn, 1);
    cs_close(&handle);
    return ret;
}

int dbg_stepback(struct thread *th) {
    if (!th->stopped) {
        return -1;
    }

    if (th->state != th->states.head) {
        th->state = th->state->prev;
        return 0;
    }

    return -1;
}

void *deref(struct thread *th, unsigned long address, size_t size) {
    /* todo: size, and mapping breaks */
    (void)size;

    if (!th->state) {
        return NULL;
    }

    struct list *maps = &th->state->maps;
    for (struct map *m = maps->head; m != maps->end; m = m->next) {
        if (m->start <= address && address < m->end) {
            return (unsigned char *)m->data + (address - m->start);
        }
    }

    return NULL;
}






//int dbg_stepout(struct tracee *dbg) {
//    unsigned long bp = dbg->state->regs.rbp;
//    unsigned long ra = *(unsigned long *)deref(dbg, bp+8, sizeof(unsigned long));
//
//    struct breakpoint *b = xmalloc(sizeof(*b));
//    b->address = ra;
//    b->stack = 0;
//    b->enabled = -1;
//    b->active = 0;
//    list_insert(dbg->breaks.end, b);
//
//    ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL);
//    //clear_states(dbg); // keep this?
//    dbg->stopped = 0;
//    dbg->cont = PTRACE_CONT;
//    return 0;
//}