static void write_context(OutputStream * out, int pid) {
    Context * ctx = context_find_from_pid(pid);
    ChildProcess * prs = find_process(pid);

    write_stream(out, '{');

    json_write_string(out, "Name");
    write_stream(out, ':');
    json_write_string(out, prs ? prs->name : pid2id(pid, 0));
    write_stream(out, ',');

    json_write_string(out, "CanTerminate");
    write_stream(out, ':');
    json_write_boolean(out, 1);
    write_stream(out, ',');

    if (ctx != NULL) {
        json_write_string(out, "Attached");
        write_stream(out, ':');
        json_write_boolean(out, 1);
        write_stream(out, ',');
    }

    if (prs != NULL) {
        if (*prs->inp_id) {
            json_write_string(out, "StdInID");
            write_stream(out, ':');
            json_write_string(out, prs->inp_id);
            write_stream(out, ',');
        }
        if (*prs->out_id) {
            json_write_string(out, "StdOutID");
            write_stream(out, ':');
            json_write_string(out, prs->out_id);
            write_stream(out, ',');
        }
        if (*prs->err_id) {
            json_write_string(out, "StdErrID");
            write_stream(out, ':');
            json_write_string(out, prs->err_id);
            write_stream(out, ',');
        }
    }

    json_write_string(out, "ID");
    write_stream(out, ':');
    json_write_string(out, pid2id(pid, 0));

    write_stream(out, '}');
}
Exemple #2
0
int context_attach(pid_t pid, ContextAttachCallBack * done, void * data, int mode) {
    Context * ctx = NULL;

    assert(done != NULL);
    trace(LOG_CONTEXT, "context: attaching pid %d", pid);
    if ((mode & CONTEXT_ATTACH_SELF) == 0 && ptrace(PT_ATTACH, pid, 0, 0) < 0) {
        int err = errno;
        trace(LOG_ALWAYS, "error: ptrace(PT_ATTACH) failed: pid %d, error %d %s",
            pid, err, errno_to_str(err));
        errno = err;
        return -1;
    }
    add_waitpid_process(pid);
    ctx = create_context(pid2id(pid, 0));
    ctx->mem = ctx;
    ctx->mem_access |= MEM_ACCESS_INSTRUCTION;
    ctx->mem_access |= MEM_ACCESS_DATA;
    ctx->mem_access |= MEM_ACCESS_USER;
    ctx->big_endian = big_endian_host();
    EXT(ctx)->pid = pid;
    EXT(ctx)->attach_callback = done;
    EXT(ctx)->attach_data = data;
    list_add_first(&ctx->ctxl, &pending_list);
    /* TODO: context_attach works only for main task in a process */
    return 0;
}
Exemple #3
0
static void event_attach_done(void * x) {
    AttachDoneArgs * args = (AttachDoneArgs *)x;
    if (context_find_from_pid(args->pid, 0) != NULL) {
        args->done(ERR_ALREADY_ATTACHED, NULL, args->data);
    }
    else {
        Context * ctx = NULL;
        if (parent_ctx == NULL) {
            pid_t pid = taskIdSelf();
            parent_ctx = create_context(pid2id(pid, 0));
            EXT(parent_ctx)->pid = pid;
            parent_ctx->mem = parent_ctx;
            parent_ctx->mem_access |= MEM_ACCESS_INSTRUCTION;
            parent_ctx->mem_access |= MEM_ACCESS_DATA;
            parent_ctx->big_endian = big_endian_host();
            link_context(parent_ctx);
            send_context_created_event(parent_ctx);
        }
        assert(parent_ctx->ref_count > 0);
        ctx = create_context(pid2id(args->pid, EXT(parent_ctx)->pid));
        EXT(ctx)->pid = args->pid;
        EXT(ctx)->regs = (REG_SET *)loc_alloc(sizeof(REG_SET));
        ctx->mem = parent_ctx;
        ctx->big_endian = parent_ctx->big_endian;
        (ctx->parent = parent_ctx)->ref_count++;
        list_add_last(&ctx->cldl, &parent_ctx->children);
        link_context(ctx);
        trace(LOG_CONTEXT, "context: attached: ctx %#lx, id %#x", ctx, EXT(ctx)->pid);
        send_context_created_event(ctx);
        args->done(0, ctx, args->data);
        if (taskIsStopped(args->pid)) {
            struct event_info * info;
            ctx->pending_intercept = 1;
            info = event_info_alloc(EVENT_HOOK_STOP);
            if (info != NULL) {
                info->stopped_ctx.ctxId = args->pid;
                event_info_post(info);
            }
        }
    }
    loc_free(x);
}
static void write_process_input(ChildProcess * prs) {
    ProcessInput * inp = prs->inp_struct = loc_alloc_zero(sizeof(ProcessInput));
    inp->prs = prs;
    inp->req.client_data = inp;
    inp->req.done = write_process_input_done;
    inp->req.type = AsyncReqWrite;
    inp->req.u.fio.fd = prs->inp;
    virtual_stream_create(PROCESSES, pid2id(prs->pid, 0), 0x1000, VS_ENABLE_REMOTE_WRITE,
        process_input_streams_callback, inp, &inp->vstream);
    virtual_stream_get_id(inp->vstream, prs->inp_id, sizeof(prs->inp_id));
}
static void send_event_process_exited(OutputStream * out, ChildProcess * prs) {
    write_stringz(out, "E");
    write_stringz(out, PROCESSES);
    write_stringz(out, "exited");

    json_write_string(out, pid2id(prs->pid, 0));
    write_stream(out, 0);

    json_write_long(out, prs->exit_code);
    write_stream(out, 0);

    write_stream(out, MARKER_EOM);
}
static ProcessOutput * read_process_output(ChildProcess * prs, int fd, char * id, size_t id_size) {
    ProcessOutput * out = loc_alloc_zero(sizeof(ProcessOutput));
    out->prs = prs;
    out->req.client_data = out;
    out->req.done = read_process_output_done;
    out->req.type = AsyncReqRead;
    out->req.u.fio.bufp = out->buf;
    out->req.u.fio.bufsz = sizeof(out->buf);
    out->req.u.fio.fd = fd;
    virtual_stream_create(PROCESSES, pid2id(prs->pid, 0), 0x1000, VS_ENABLE_REMOTE_READ,
        process_output_streams_callback, out, &out->vstream);
    virtual_stream_get_id(out->vstream, id, id_size);
    out->req_posted = 1;
    async_req_post(&out->req);
    return out;
}
Exemple #7
0
static void write_context(OutputStream * out, int tid) {
    Terminal * term = find_terminal(tid);
    const char * id = NULL;

    write_stream(out, '{');

    if (term != NULL) {
        unsigned ws_col = 0;
        unsigned ws_row = 0;
        get_process_tty_win_size(term->prs, &ws_col, &ws_row);

        json_write_string(out, "ProcessID");
        write_stream(out, ':');
        json_write_string(out, pid2id(get_process_pid(term->prs), 0));
        write_stream(out, ',');

        if (*term->pty_type) {
            json_write_string(out, "PtyType");
            write_stream(out, ':');
            json_write_string(out, term->pty_type);
            write_stream(out, ',');
        }

        if (*term->encoding) {
            json_write_string(out, "Encoding");
            write_stream(out, ':');
            json_write_string(out, term->encoding);
            write_stream(out, ',');
        }

        json_write_string(out, "Width");
        write_stream(out, ':');
        json_write_ulong(out, ws_col);
        write_stream(out, ',');

        json_write_string(out, "Height");
        write_stream(out, ':');
        json_write_ulong(out, ws_row);
        write_stream(out, ',');

        id = get_process_stream_id(term->prs, 0);
        if (id) {
            json_write_string(out, "StdInID");
            write_stream(out, ':');
            json_write_string(out, id);
            write_stream(out, ',');
        }
        id = get_process_stream_id(term->prs, 1);
        if (id) {
            json_write_string(out, "StdOutID");
            write_stream(out, ':');
            json_write_string(out, id);
            write_stream(out, ',');
        }
        id = get_process_stream_id(term->prs, 2);
        if (id) {
            json_write_string(out, "StdErrID");
            write_stream(out, ':');
            json_write_string(out, id);
            write_stream(out, ',');
        }
    }

    json_write_string(out, "ID");
    write_stream(out, ':');
    json_write_string(out, tid2id(tid));

    write_stream(out, '}');
}
Exemple #8
0
static void event_handler(void * arg) {
    struct event_info * info = (struct event_info *)arg;
    Context * current_ctx = context_find_from_pid(info->current_ctx.ctxId, 1);
    Context * stopped_ctx = context_find_from_pid(info->stopped_ctx.ctxId, 1);

    switch (info->event) {
    case EVENT_HOOK_BREAKPOINT:
        if (stopped_ctx == NULL) break;
        assert(!stopped_ctx->stopped);
        assert(!EXT(stopped_ctx)->regs_dirty);
        if (EXT(stopped_ctx)->regs_error) {
            release_error_report(EXT(stopped_ctx)->regs_error);
            EXT(stopped_ctx)->regs_error = NULL;
        }
        memcpy(EXT(stopped_ctx)->regs, &info->regs, sizeof(REG_SET));
        EXT(stopped_ctx)->event = 0;
        stopped_ctx->signal = SIGTRAP;
        stopped_ctx->stopped = 1;
        stopped_ctx->stopped_by_bp = info->bp_info_ok;
        stopped_ctx->stopped_by_exception = 0;
        assert(get_regs_PC(stopped_ctx) == info->addr);
        if (stopped_ctx->stopped_by_bp && !is_breakpoint_address(stopped_ctx, info->addr)) {
            /* Break instruction that is not planted by us */
            stopped_ctx->stopped_by_bp = 0;
            stopped_ctx->pending_intercept = 1;
        }
        EXT(stopped_ctx)->bp_info = info->bp_info;
        if (current_ctx != NULL) EXT(stopped_ctx)->bp_pid = EXT(current_ctx)->pid;
        assert(taskIsStopped(EXT(stopped_ctx)->pid));
        trace(LOG_CONTEXT, "context: stopped by breakpoint: ctx %#lx, id %#x",
                stopped_ctx, EXT(stopped_ctx)->pid);
        send_context_stopped_event(stopped_ctx);
        break;
    case EVENT_HOOK_STEP_DONE:
        if (current_ctx == NULL) break;
        assert(!current_ctx->stopped);
        assert(!EXT(current_ctx)->regs_dirty);
        if (EXT(current_ctx)->regs_error) {
            release_error_report(EXT(current_ctx)->regs_error);
            EXT(current_ctx)->regs_error = NULL;
        }
        memcpy(EXT(current_ctx)->regs, &info->regs, sizeof(REG_SET));
        EXT(current_ctx)->event = TRACE_EVENT_STEP;
        current_ctx->signal = SIGTRAP;
        current_ctx->stopped = 1;
        current_ctx->stopped_by_bp = 0;
        current_ctx->stopped_by_exception = 0;
        assert(taskIsStopped(EXT(current_ctx)->pid));
        trace(LOG_CONTEXT, "context: stopped by end of step: ctx %#lx, id %#x",
                current_ctx, EXT(current_ctx)->pid);
        send_context_stopped_event(current_ctx);
        break;
    case EVENT_HOOK_STOP:
        if (stopped_ctx == NULL) break;
        assert(!stopped_ctx->exited);
        if (stopped_ctx->stopped) break;
        if (EXT(stopped_ctx)->regs_error) {
            release_error_report(EXT(stopped_ctx)->regs_error);
            EXT(stopped_ctx)->regs_error = NULL;
        }
        if (taskRegsGet(EXT(stopped_ctx)->pid, EXT(stopped_ctx)->regs) != OK) {
            EXT(stopped_ctx)->regs_error = get_error_report(errno);
            assert(EXT(stopped_ctx)->regs_error != NULL);
        }
        EXT(stopped_ctx)->event = 0;
        stopped_ctx->signal = SIGSTOP;
        stopped_ctx->stopped = 1;
        stopped_ctx->stopped_by_bp = 0;
        stopped_ctx->stopped_by_exception = 0;
        assert(taskIsStopped(EXT(stopped_ctx)->pid));
        trace(LOG_CONTEXT, "context: stopped by sofware request: ctx %#lx, id %#x",
                stopped_ctx, EXT(stopped_ctx)->pid);
        send_context_stopped_event(stopped_ctx);
        break;
    case EVENT_HOOK_TASK_ADD:
        if (current_ctx == NULL) break;
        assert(stopped_ctx == NULL);
        stopped_ctx = create_context(pid2id((pid_t)info->stopped_ctx.ctxId, EXT(current_ctx->parent)->pid));
        EXT(stopped_ctx)->pid = (pid_t)info->stopped_ctx.ctxId;
        EXT(stopped_ctx)->regs = (REG_SET *)loc_alloc(sizeof(REG_SET));
        stopped_ctx->mem = current_ctx->mem;
        stopped_ctx->big_endian = current_ctx->mem->big_endian;
        (stopped_ctx->creator = current_ctx)->ref_count++;
        (stopped_ctx->parent = current_ctx->parent)->ref_count++;
        assert(stopped_ctx->mem == stopped_ctx->parent->mem);
        list_add_last(&stopped_ctx->cldl, &stopped_ctx->parent->children);
        link_context(stopped_ctx);
        trace(LOG_CONTEXT, "context: created: ctx %#lx, id %#x",
                stopped_ctx, EXT(stopped_ctx)->pid);
        send_context_created_event(stopped_ctx);
        break;
    default:
        assert(0);
        break;
    }
    loc_free(info);
    SPIN_LOCK_ISR_TAKE(&events_lock);
    events_cnt--;
    SPIN_LOCK_ISR_GIVE(&events_lock);
}
Exemple #9
0
static void event_pid_stopped(pid_t pid, int signal, int event, int syscall) {
    int stopped_by_exception = 0;
    unsigned long msg = 0;
    Context * ctx = NULL;
    Context * ctx2 = NULL;

    trace(LOG_EVENTS, "event: pid %d stopped, signal %d", pid, signal);

    ctx = context_find_from_pid(pid, 1);

    if (ctx == NULL) {
        ctx = find_pending(pid);
        if (ctx != NULL) {
            Context * prs = ctx;
            assert(prs->ref_count == 0);
            ctx = create_context(pid2id(pid, pid));
            EXT(ctx)->pid = pid;
            EXT(ctx)->regs = (REG_SET *)loc_alloc(sizeof(REG_SET));
            ctx->pending_intercept = 1;
            ctx->mem = prs;
            ctx->parent = prs;
            ctx->big_endian = prs->big_endian;
            prs->ref_count++;
            list_add_last(&ctx->cldl, &prs->children);
            link_context(prs);
            link_context(ctx);
            send_context_created_event(prs);
            send_context_created_event(ctx);
            if (EXT(prs)->attach_callback) {
                EXT(prs)->attach_callback(0, prs, EXT(prs)->attach_data);
                EXT(prs)->attach_callback = NULL;
                EXT(prs)->attach_data = NULL;
            }
        }
    }

    if (ctx == NULL) return;

    assert(!ctx->exited);
    assert(!EXT(ctx)->attach_callback);

    if (signal != SIGSTOP && signal != SIGTRAP) {
        sigset_set(&ctx->pending_signals, signal, 1);
        if (sigset_get(&ctx->sig_dont_stop, signal) == 0) {
            ctx->pending_intercept = 1;
            stopped_by_exception = 1;
        }
    }

    if (ctx->stopped) {
        send_context_changed_event(ctx);
    }
    else {
        thread_state_t state;
        unsigned int state_count;
        ContextAddress pc0 = 0;
        ContextAddress pc1 = 0;

        assert(!EXT(ctx)->regs_dirty);

        EXT(ctx)->end_of_step = 0;
        EXT(ctx)->ptrace_event = event;
        ctx->signal = signal;
        ctx->stopped_by_bp = 0;
        ctx->stopped_by_exception = stopped_by_exception;
        ctx->stopped = 1;

        if (EXT(ctx)->regs_error) {
            release_error_report(EXT(ctx)->regs_error);
            EXT(ctx)->regs_error = NULL;
        }
        else {
            pc0 = get_regs_PC(ctx);
        }

        if (thread_get_state(EXT(ctx)->pid, x86_THREAD_STATE32, EXT(ctx)->regs, &state_count) != KERN_SUCCESS) {
            assert(errno != 0);
            EXT(ctx)->regs_error = get_error_report(errno);
            trace(LOG_ALWAYS, "error: thread_get_state failed; id %s, error %d %s",
                ctx->id, errno, errno_to_str(errno));
        }
        else {
            pc1 = get_regs_PC(ctx);
        }

        if (!EXT(ctx)->syscall_enter || EXT(ctx)->regs_error || pc0 != pc1) {
            EXT(ctx)->syscall_enter = 0;
            EXT(ctx)->syscall_exit = 0;
            EXT(ctx)->syscall_id = 0;
            EXT(ctx)->syscall_pc = 0;
        }
        trace(LOG_EVENTS, "event: pid %d stopped at PC = %#lx", pid, pc1);

        if (signal == SIGTRAP && event == 0 && !syscall) {
            size_t break_size = 0;
            get_break_instruction(ctx, &break_size);
            ctx->stopped_by_bp = !EXT(ctx)->regs_error && is_breakpoint_address(ctx, pc1 - break_size);
            EXT(ctx)->end_of_step = !ctx->stopped_by_bp && EXT(ctx)->pending_step;
            if (ctx->stopped_by_bp) set_regs_PC(ctx, pc1 - break_size);
        }
        EXT(ctx)->pending_step = 0;
        send_context_stopped_event(ctx);
    }
}
static void write_context(OutputStream * out, Context * ctx, int is_thread) {
    assert(!ctx->exited);

    write_stream(out, '{');

    json_write_string(out, "ID");
    write_stream(out, ':');
    json_write_string(out, is_thread ? thread_id(ctx) : container_id(ctx));

    if (is_thread) {
        write_stream(out, ',');
        json_write_string(out, "ParentID");
        write_stream(out, ':');
        json_write_string(out, container_id(ctx));
    }

#if !defined(_WRS_KERNEL)
    write_stream(out, ',');
    json_write_string(out, "ProcessID");
    write_stream(out, ':');
    json_write_string(out, pid2id(ctx->mem, 0));
#endif

#if !defined(WIN32) && !defined(_WRS_KERNEL)
    if (!ctx->exiting && !is_thread) {
        write_stream(out, ',');
        json_write_string(out, "File");
        write_stream(out, ':');
        json_write_string(out, get_executable(ctx->pid));
    }
#endif

    if (is_thread) {
        write_stream(out, ',');
        json_write_string(out, "CanSuspend");
        write_stream(out, ':');
        json_write_boolean(out, 1);

        write_stream(out, ',');
        json_write_string(out, "CanResume");
        write_stream(out, ':');
        json_write_long(out, (1 << RM_RESUME) | (1 << RM_STEP_INTO));

        write_stream(out, ',');
        json_write_string(out, "HasState");
        write_stream(out, ':');
        json_write_boolean(out, 1);
    }

#ifdef WIN32
    if (!is_thread)
#endif
    {
        write_stream(out, ',');
        json_write_string(out, "CanTerminate");
        write_stream(out, ':');
        json_write_boolean(out, 1);
    }

    write_stream(out, '}');
}
static void command_get_children(char * token, Channel * c) {
    char id[256];
    int attached_only;

    json_read_string(&c->inp, id, sizeof(id));
    if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
    attached_only = json_read_boolean(&c->inp);
    if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
    if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    if (id[0] != 0) {
        write_errno(&c->out, 0);
        write_stringz(&c->out, "null");
    }
    else {
#if defined(WIN32)
    DWORD err = 0;
    HANDLE snapshot;
    PROCESSENTRY32 pe32;

    snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snapshot == INVALID_HANDLE_VALUE) err = set_win32_errno(GetLastError());
    memset(&pe32, 0, sizeof(pe32));
    pe32.dwSize = sizeof(PROCESSENTRY32);
    if (!err && !Process32First(snapshot, &pe32)) {
        err = set_win32_errno(GetLastError());
        CloseHandle(snapshot);
    }
    write_errno(&c->out, err);
    if (err) {
        write_stringz(&c->out, "null");
    }
    else {
        int cnt = 0;
        write_stream(&c->out, '[');
        do {
            if (!attached_only || context_find_from_pid(pe32.th32ProcessID) != NULL) {
                if (cnt > 0) write_stream(&c->out, ',');
                json_write_string(&c->out, pid2id(pe32.th32ProcessID, 0));
                cnt++;
            }
        }
        while (Process32Next(snapshot, &pe32));
        write_stream(&c->out, ']');
        write_stream(&c->out, 0);
    }
    if (snapshot != INVALID_HANDLE_VALUE) CloseHandle(snapshot);
#elif defined(_WRS_KERNEL)
        int i = 0;
        int cnt = 0;
        int ids_cnt = 0;
        int ids_max = 500;
        int * ids = (int *)loc_alloc(ids_max * sizeof(int));
        for (;;) {
            ids_cnt = taskIdListGet(ids, ids_max);
            if (ids_cnt < ids_max) break;
            loc_free(ids);
            ids_max *= 2;
            ids = (int *)loc_alloc(ids_max * sizeof(int));
        }
        write_errno(&c->out, 0);
        write_stream(&c->out, '[');
        for (i = 0; i < ids_cnt; i++) {
            if (!attached_only || context_find_from_pid(ids[i]) != NULL) {
                if (cnt > 0) write_stream(&c->out, ',');
                json_write_string(&c->out, pid2id(ids[i], 0));
                cnt++;
            }
        }
        write_stream(&c->out, ']');
        write_stream(&c->out, 0);
#elif defined(__APPLE__)
#else
        DIR * proc = opendir("/proc");
        if (proc == NULL) {
            write_errno(&c->out, errno);
            write_stringz(&c->out, "null");
        }
        else {
            int cnt = 0;
            write_errno(&c->out, 0);
            write_stream(&c->out, '[');
            for (;;) {
                struct dirent * ent = readdir(proc);
                if (ent == NULL) break;
                if (ent->d_name[0] >= '1' && ent->d_name[0] <= '9') {
                    pid_t pid = atol(ent->d_name);
                    if (!attached_only || context_find_from_pid(pid) != NULL) {
                        if (cnt > 0) write_stream(&c->out, ',');
                        json_write_string(&c->out, pid2id(pid, 0));
                        cnt++;
                    }
                }
            }
            write_stream(&c->out, ']');
            write_stream(&c->out, 0);
            closedir(proc);
        }
#endif
    }

    write_stream(&c->out, MARKER_EOM);
}
Exemple #12
0
static void event_pid_stopped(pid_t pid, int signal, int event, int syscall) {
    int stopped_by_exception = 0;
    Context * ctx = NULL;

    trace(LOG_EVENTS, "event: pid %d stopped, signal %d, event %s", pid, signal, event_name(event));

    ctx = context_find_from_pid(pid, 1);

    if (ctx == NULL) {
        ctx = find_pending(pid);
        if (ctx != NULL) {
            Context * prs = ctx;
            assert(prs->ref_count == 0);
            ctx = create_context(pid2id(pid, pid));
            EXT(ctx)->pid = pid;
            EXT(ctx)->regs = (REG_SET *)loc_alloc(sizeof(REG_SET));
            ctx->pending_intercept = 1;
            ctx->mem = prs;
            ctx->parent = prs;
            ctx->big_endian = prs->big_endian;
            prs->ref_count++;
            list_add_last(&ctx->cldl, &prs->children);
            link_context(prs);
            link_context(ctx);
            send_context_created_event(prs);
            send_context_created_event(ctx);
            if (EXT(prs)->attach_callback) {
                EXT(prs)->attach_callback(0, prs, EXT(prs)->attach_data);
                EXT(prs)->attach_callback = NULL;
                EXT(prs)->attach_data = NULL;
            }
        }
    }

    if (ctx == NULL) return;

    assert(!ctx->exited);
    assert(!EXT(ctx)->attach_callback);

    if (signal != SIGSTOP && signal != SIGTRAP) {
        assert(signal < 32);
        ctx->pending_signals |= 1 << signal;
        if ((ctx->sig_dont_stop & (1 << signal)) == 0) {
            ctx->pending_intercept = 1;
            stopped_by_exception = 1;
        }
    }

    if (ctx->stopped) {
        send_context_changed_event(ctx);
    }
    else {
        ContextAddress pc0 = 0;
        ContextAddress pc1 = 0;

        assert(!EXT(ctx)->regs_dirty);

        EXT(ctx)->end_of_step = 0;
        EXT(ctx)->ptrace_event = event;
        ctx->signal = signal;
        ctx->stopped_by_bp = 0;
        ctx->stopped_by_exception = stopped_by_exception;
        ctx->stopped = 1;

        if (EXT(ctx)->regs_error) {
            release_error_report(EXT(ctx)->regs_error);
            EXT(ctx)->regs_error = NULL;
        }
        else {
            pc0 = get_regs_PC(ctx);
        }

        if (ptrace(PTRACE_GETREGS, EXT(ctx)->pid, 0, (int)EXT(ctx)->regs) < 0) {
            assert(errno != 0);
            if (errno == ESRCH) {
                /* Racing condition: somebody resumed this context while we are handling stop event.
                 *
                 * One possible cause: main thread has exited forcing children to exit too.
                 * I beleive it is a bug in PTRACE implementation - PTRACE should delay exiting of
                 * a context while it is stopped, but it does not, which causes a nasty racing.
                 *
                 * Workaround: Ignore current event, assume context is running.
                 */
                ctx->stopped = 0;
                return;
            }
            EXT(ctx)->regs_error = get_error_report(errno);
            trace(LOG_ALWAYS, "error: ptrace(PTRACE_GETREGS) failed; id %s, error %d %s",
                ctx->id, errno, errno_to_str(errno));
        }
        else {
            pc1 = get_regs_PC(ctx);
        }

        trace(LOG_EVENTS, "event: pid %d stopped at PC = %#lx", pid, pc1);

        if (signal == SIGTRAP && event == 0 && !syscall) {
            size_t break_size = 0;
            get_break_instruction(ctx, &break_size);
            ctx->stopped_by_bp = !EXT(ctx)->regs_error && is_breakpoint_address(ctx, pc1 - break_size);
            EXT(ctx)->end_of_step = !ctx->stopped_by_bp && EXT(ctx)->pending_step;
            if (ctx->stopped_by_bp) set_regs_PC(ctx, pc1 - break_size);
        }
        EXT(ctx)->pending_step = 0;
        send_context_stopped_event(ctx);
    }
}