Проблемы с ptrace(PTRACE_ME,) и последующее ожидание

Я переношу отладчик «pi» («инспектор процессов») в Linux и работаю над кодом для fork/exec дочернего процесса, чтобы проверить его. Я следую стандартной процедуре (я полагаю), но ожидание висит. «hang» — это процедура, которая выполняет работу, аргумент «cmd» — это имя двоичного файла (a.out) для трассировки:

int Hostfunc::hang(char *cmd){
    char *argv[10], *cp;
    int i;
    Localproc *p;
    struct exec exec;
    struct rlimit rlim;
    
    i = strlen(cmd);
    if (++i > sizeof(procbuffer)) {
        i = sizeof(procbuffer) - 1;
        procbuffer[i] = 0;
    }
    bcopy(cmd, procbuffer, i);
    argv[0] = cp = procbuffer;
    for(i = 1;;) {
        while(*cp && *cp != ' ')
            cp++;
        if (!*cp) {
            argv[i] = 0;
            break;
        } else {
            *cp++ = 0;
            while (*cp == ' ')
                cp++;
            if (*cp)
                argv[i++] = cp;
        }
    }
    hangpid = fork();
    if (!hangpid){
        int fd, nfiles = 20;
        if(getrlimit(RLIMIT_NOFILE, &rlim))
            nfiles = rlim.rlim_cur;
        for( fd = 0; fd < nfiles; ++fd )
            close(fd);
        open("/dev/null", 2);
        dup2(0, 1);
        dup2(0, 2);
        setpgid(0, 0);
        ptrace(PTRACE_TRACEME, 0, 0, 0);
        execvp(argv[0], argv);
        exit(0);
    }
    if (hangpid < 0)
        return 0;
    p = new Localproc;
    if (!p) {
        kill(9, hangpid);
        return 0;
    }
    p->sigmsk = sigmaskinit();
    p->pid = hangpid;
    if (!procwait(p, 0)) {
        delete p;
        return 0;
    }
    if (p->state.state == UNIX_BREAKED)
        p->state.state = UNIX_HALTED;
    p->opencnt = 0;
    p->next = phead;
    phead = p;
    return hangpid;
}

Я вставил 'abort()', чтобы поймать ненулевой возврат из ptrace, но этого не происходит. Вызов 'raise' кажется обычной практикой, но беглый взгляд на код gdb показывает, что он там не используется. В любом случае на результат это не влияет. procwait выглядит следующим образом:

int Hostfunc::procwait(Localproc *p, int flag){
    int tstat;
    int cursig;

again:
    if (p->pid != waitpid(p->pid, &tstat, (flag&WAIT_POLL)? WNOHANG: 0))
        return 0;
    if (flag & WAIT_DISCARD)
        return 1;
    if (WIFSTOPPED(tstat)) {
        cursig = WSTOPSIG(tstat);
        if (cursig == SIGSTOP)
            p->state.state = UNIX_HALTED;
        else if (cursig == SIGTRAP)
            p->state.state = UNIX_BREAKED;
        else {
            if (p->state.state == UNIX_ACTIVE &&
                !(p->sigmsk&bit(cursig))) {
                ptrace(PTRACE_CONT, p->pid, 1, cursig, 0);
                goto again;
            }
            else {
                p->state.state = UNIX_PENDING;
                p->state.code = cursig;
            }
        }
    } else {
        p->state.state = UNIX_ERRORED;
        p->state.code = WEXITSTATUS(tstat) & 0xFFFF;
    }
    return 1;
}

«waitpid» в «procwait» просто зависает. Если я запущу программу с приведенным выше кодом и запущу «ps», я увижу, что «pi» разветвлено, но еще не вызвало exec, потому что в командной строке все еще «pi», а не имя двоичный файл, который я разветвляю. Я обнаружил, что если я удаляю «поднять», «пи» все еще висит, но «ps» теперь показывает, что разветвленная программа имеет имя проверяемого двоичного файла, что предполагает, что она выполнила exec.

Итак, насколько я вижу, я следую задокументированным процедурам, чтобы взять под контроль разветвленный процесс, но он не работает.

Ноэль Хант


person N. Hunt    schedule 25.06.2020    source источник
comment
Можете ли вы превратить это в минимально воспроизводимый пример, который кто-нибудь сможет скомпилировать, запустить и протестировать?   -  person Nate Eldredge    schedule 25.06.2020
comment
Я не вижу raise() в этой версии кода. Обратите внимание, что waitpid вернется только тогда, когда дочерний процесс остановится, и, поскольку код стоит, я не вижу ничего, что могло бы его остановить. Когда вы надеялись, что это остановится?   -  person Nate Eldredge    schedule 25.06.2020
comment
Чтобы ответить на вопрос о превращении этого в минимальный воспроизводимый пример, проблема в том, что очень простые примеры, такие как те, которые можно найти в сети, работают. То есть родитель разветвляется, в дочернем он вызывает 'ptrace(PTRACE_TRACEME,0,0) затем exec's something , скажем, 'ls -l'. С другой стороны, родитель ждет. Когда ожидание возвращается, родитель теперь находится под контролем, может устанавливать регистры и т. д. и т. д.   -  person N. Hunt    schedule 25.06.2020
comment
Должно быть, я написал версию без 'raise()', но, как я уже сказал, это не меняет результат, за исключением того, что с 'raise' дочерний argv[] является именем самого 'pi', что означает, что ребенок не выполнил; если я уберу «поднять», ожидание все равно зависнет, но ps показывает, что argv в дочернем элементе теперь имеет имя программы, выполняемой в дочернем. Это говорит о том, что SIGSTOP не дает разветвленному дочернему элементу даже добраться до exec, но я видел, что в примерах в сети в этом контексте используется слово «поднять». Тем не менее, проблема в том, чтобы подождать; почему не возвращается?   -  person N. Hunt    schedule 25.06.2020
comment
Что касается вашего комментария о том, что вы не видите ничего, что могло бы остановить его, насколько я понимаю, PTRACE_TRACEME фактически заставляет дочерний элемент останавливаться при первом выполнении.   -  person N. Hunt    schedule 25.06.2020
comment
Из ручной записи на ptrace: Процесс может инициировать трассировку, вызвав fork(2) и заставив результирующий потомок выполнить PTRACE_TRACEME, за которым (обычно) следует execve(2). Хм, я не использую execve, но не понимаю, почему это имеет значение.   -  person N. Hunt    schedule 25.06.2020
comment
Я вижу, вы правы, он должен остановиться на exec. Я спрашиваю о минимальном воспроизводимом примере, потому что боюсь, что проблема может быть в коде, который вы не показали.   -  person Nate Eldredge    schedule 25.06.2020
comment
В нынешнем виде я разветвил ребенка, и ожидание висит. Я мало что знаю обо всех файлах в /proc, но файл «wchan» показывает, что разветвленный дочерний процесс находится в «ptrace_stop». Я ожидал, что родитель сможет получить информацию об остановленном дочернем элементе через ожидание, но по какой-то причине он зависает.   -  person N. Hunt    schedule 25.06.2020
comment
Наверное, я не знаю, как помочь вам с кодом, который я не вижу и не могу протестировать. Как вы заметили, когда мы пишем тестовую программу, которая делает то, что, как вы утверждаете, делает ваш код, она работает. Следовательно, я подозреваю, что ваш код не делает то, что вы утверждаете, хотя я пока не знаю, почему. Поэтому я бы предложил еще раз попытаться сделать минимальный пример, но с другой стороны: начать с вашей неработающей программы и удалить или заглушить все, что не связано с вопросом.   -  person Nate Eldredge    schedule 25.06.2020
comment
Проблема становится все более туманной. Я заменил вызов ожидания простым «ожиданием (0)» и сохранил возврат ( int rv = wait (0); ), таким образом, я могу просматривать код во время его запуска под dbx (я использую Oracle Linux , их компиляторы и dbx; gdb тоже подойдет). Я ставлю точку останова на вызов ожидания, затем запускаю отладчик. Если я попытаюсь выполнить вызов ожидания в отладчике, он зависнет, как и ожидалось. Затем я отправляю SIGKILL ребенку из другого окна, и... ожидание все еще зависает. Так что я в тупике. Я чувствую, что есть какая-то приятная маленькая функция Linux, которую мне нужно включить.   -  person N. Hunt    schedule 25.06.2020
comment
Давайте продолжим обсуждение в чате.   -  person N. Hunt    schedule 25.06.2020
comment
Мои извинения; имело место вмешательство обработчика SIGCHLD, перезапустившего системный вызов ожидания. Спасибо за ваши различные комментарии.   -  person N. Hunt    schedule 25.06.2020
comment
А, обычная история: баг всегда там, где его меньше всего ожидаешь :-) Рад, что ты его решил.   -  person Nate Eldredge    schedule 25.06.2020


Ответы (1)


Я нашел проблему (с моим собственным кодом, как указал Нейт), но причина была неясна, пока я не запустил «strace pi». Из этого было ясно, что есть обработчик SIGCHLD, и он выполняет ожидание. Родитель входит в ожидание, SIGCHLD доставляется, обработчик ждет и, таким образом, получает статус дочернего элемента, затем ожидание перезапускается в родительском и зависает, потому что больше нет никакого изменения состояния. Обработчик SIGCHLD имеет смысл, потому что pi хочет получать информацию об изменениях состояния в дочернем элементе. Первая версия «pi», с которой я работал, была версией Solaris, и она использовала / proc для управления процессом, поэтому не было необходимости «wait» для получения дочернего статуса, поэтому я не видел эту проблему в версии Solaris.

person N. Hunt    schedule 25.06.2020