处理 SIGCHLD NASM

Handling SIGCHLD NASM

编辑 请参阅下面我自己的回答


我一直在尝试在 NASM

中复制这个 C 程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

static void handle(int sig) {
    int status;
    wait(&status);
}

int main(int argc, char* argv[]) {
    struct sigaction act;
    bzero(&act, sizeof(act));
    act.sa_handler = &handle;
    sigaction(SIGCLD, &act, NULL);
    pid_t pid;
    if ( (pid = fork()) == 0) {
        printf("message from child\n");
        exit(0);
    }
    printf("message from parent\n");
    pause();
    exit(0);
}

我的 NASM 代码如下所示:

USE64

STRUC sigact
    .handler        resq 1
    .mask           resq 16
    .flag           resd 1
    .restorer       resq 1
    .pad            resb 4
ENDSTRUC

section .text
global _start
_start:
    ; register SIGCHLD handler
    mov     rdi, act
    mov     rsi, sigact_size
    call    bzero
    mov     QWORD [act], handle
    ; still need to figure out what these mean
    ; yanked out of gdb right before the same syscall
    ; and the act struct had these set :\
    mov     QWORD [act+8], 0x4000000
    mov     DWORD [act+16], 0xf7a434a0
    mov     DWORD [act+20], 0x7fff
    mov     rax, 13
    mov     rdi, 17
    lea     rsi, [act]
    mov     rdx, 0x0
    mov     r10, 0x8
    syscall
    cmp     rax, 0
    jne     sigaction_fail
    mov     rax, 57
    syscall
    cmp     rax, 0
    je      child
    mov     rax, parentmsg
    call    print
    mov     rax, 34
    syscall
    mov     rax, parentexit
    call    print
    mov     rax, 60
    mov     rdi, 0
    syscall


sigaction_fail:
    enter   0, 0
    mov     rax, safailed
    call    print
    mov     rax, 60
    mov     rdi, -1
    syscall

handle:
    enter   0x10, 0
    push    rax
    push    rsi
    push    rdi
    push    rdx
    push    r10
    lea     rsi, [rbp-0x10]
    mov     rax, 61
    mov     rdi, -1
    xor     rdx, rdx
    xor     r10, r10
    syscall
    cmp     rax, -1
    jne     .handle_success
    mov     rax, hdfailed
    call    print
    mov     rax, 60
    mov     rdi, -1
    syscall
.handle_success:
    mov     rax, hdsuccess
    call    print
    pop     r10
    pop     rdx
    pop     rdi
    pop     rsi
    pop     rax
    leave
    ret

child:
    mov     rax, childmsg
    call    print
    mov     rax, 60
    mov     rdi, 0
    syscall

; print a null terminated string stored in rax
print:
    enter   0, 0
    push    rbx
    push    rdx
    push    rdi
    push    rsi
    mov     rbx, rax
    call    strlen
    mov     rdx, rax
    mov     rax, 1
    mov     rdi, 1 ; stdout
    mov     rsi, rbx
    syscall
    pop     rsi
    pop     rdi
    pop     rdx
    pop     rbx
    leave
    ret

bzero:
    ; rdi pointer to uint8_t
    ; uint32_t rsi length
    enter   0, 0
    mov     rcx, rsi
    dec     rcx ; err..
.bzeroloop:
    lea     rax, [rdi + rcx]
    xor     rax, rax
    cmp     rcx, 0
    je      .bzerodone
    dec     rcx
    jmp     .bzeroloop
.bzerodone:
    leave
    ret

strlen:
    enter   0, 0
    push    rbx
    mov     rbx, rax
.strlen_countchar:
    cmp     BYTE [rax], 0 ; compare it to null byte
    jz      .strlen_exit
    inc     rax
    jmp     .strlen_countchar
.strlen_exit:
    sub     rax, rbx
    pop     rbx
    leave
    ret



section .data
    childmsg:   db  "from child", 0xa, 0 ; null terminated
    parentmsg   db  "from parent", 0xa, 0
    handlemsg   db  "in handle", 0xa, 0
    safailed    db  "failed to set signal handler", 0xa, 0
    hdfailed    db  "failed waiting for child", 0xa, 0
    hdsuccess   db  "successfully waited on child", 0xa, 0
    parentexit  db  "parent exiting", 0xa, 0

section .bss
    act:            resb    sigact_size
    status:         resq    1

它在发送信号时成功等待子进程,但立即在 return 上出现段错误。我已经尝试阅读越来越多的关于信号和信号处理的文章,但在这一点上,所有 运行 都在我的脑海中变得一团糟。抱歉 NASM 代码丑陋或不标准。我不仅在学习,而且我可能每个部分至少重写了 25 次(handle 可能超过 100 次)。

信号处理程序是普通函数。 Return 与 ret,而不是 iret。 (你已经编辑了你的问题来解决这个问题,所以我猜你还有其他问题)。

看看gcc如何编译handler(),在Godbolt compiler explorer.

static void handle(int sig) {
    int status;
    wait(&status);
}
    sub     rsp, 24
    xor     eax, eax           # you forgot to include sys/wait.h, so the compiler has no prototype for wait(), so has to follow the ABI for variadic functions (al = number of FP args in xmm regs)
    lea     rdi, [rsp+12]
    call    wait
    add     rsp, 24
    ret

将库调用变成 wait4(2) 的内联调用并不难。

请注意,手册页上说 wait4 已过时,新程序应使用 waitpid or waitid。但是,如果您不需要更多功能,wait4 就可以了。 glibc 在 wait4 Linux 系统调用之上实现 wait(3),而不是 waitid。如果使用 wait4 有任何问题,glibc 将直接使用 waitid

handle:
    mov     eax, 61          ; __NR_wait4 from /usr/include/x86_64-linux-gnu/asm/unistd_64.h
    mov     edi, -1          ; pid_t is a 32bit type, so we don't need to sign-extend into rdi.
    lea     rsi, [rsp-4]     ; status = a pointer into the red zone below rsp.
    xor     edx,edx          ; options = 0
    xor     r10d,r10d        ; rusage = NULL
    syscall
    ; eax = pid waited for, or -1 to indicate error
    ; dword [rsp-4] = status.  unlike function calls, syscalls don't clobber the stack
    ret

要使用 wait4 中的 return 值,请执行以下操作:

    cmp     rax, -1         ;;;; THIS WAS A BUG: pid_t is a 32bit type; expect garbage or zeros in the upper 32 bits.

    cmp     eax, -1
    je   .error
    ...

如果要调试,请在handle中设置断点。这比在 asm 中使用调试打印 容易得多。

如果在您 ret 时仍然崩溃,则可能 return 成功但实际上在您的主程序中崩溃了。使用调试器找出答案。


您的字符串常量应放在 .rodata 部分。你不需要在运行时修改它们,所以不要把它们放在 .data.

您也不需要 call bzero,因为您的 act 在 bss 中。如果你想在堆栈上分配它,而不是静态分配,你应该用 rep stosq 将它归零,就像 gcc 5.3 在你的 main() 中所做的那样。 (内联 bzero,as you can see on Godbolt)。


顺便说一句,问题中的 NASM 结构在错误的位置填充。可能对你未来的冒险值得注意,即使事实证明它不是这个问题的答案的一部分。 (您的代码在定义后甚至没有使用 NASM 结构语法。)

实际的struct sigaction定义在/usr/include/x86_64-linux-gnu/bits/sigaction.h,你可以在echo '#include <signal.h>' | gcc -E - | less.

中搜索找到。
struct sigaction {
    union {
      __sighandler_t sa_handler;    // your code uses this one, because it leaves SA_SIGINFO unset in sa_flags
      void (*sa_sigaction) (int, siginfo_t *, void *);
    };  // with some macros to sort this out
    __sigset_t sa_mask;   // 1024 bits = 128B
    int sa_flags; 
    void (*sa_restorer) (void);
};

您的 NASM 结构在错误的位置填充:

STRUC sigact
    .handler        resq 1    ; 64bit pointer: correct
    .mask           resq 16   ; 16 qwords for sigset_t: correct, it's 128 bytes
    .flag           resd 1    ; 32bit flags: correct
    ;; The padding goes here, to align the 64bit member that follows
    .pad            resb 4
    .restorer       resq 1    ; 64bit
    ;; There's no padding here
ENDSTRUC

戳了好久终于搞明白了!问题是在 sigact 结构中正确设置恢复器。

当我检查 sigaction(2) 以获取结构定义时,它最终完全不是我想的那样。我明白了:

struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

但那是结构的 C 定义(不完全是,正如手册页提到的,前两个可能是联合,我就是这种情况)。

然而,经过更多的探索,我发现我需要构建的结构看起来更像这样:

struct asm_sigaction {
    void                  (*sa_handler)(int);
    [unsigned?] long      sa_flags;
    void                  (*sa restorer)(void);
    sigset_t              sa_mask; 
};

我通过深入了解我的 C 代码的实际用途发现了这一点。我找到了我正在制作的相同系统调用的位置,并转储了它们为 sigaction 结构传递的字节:

(gdb) x/38wx $rsi
0x7fffffffddc0: 0x004007f5  0x00000000  0x14000000  0x00000000
0x7fffffffddd0: 0xf7a434a0  0x00007fff  0x00000000  0x00000000
0x7fffffffdde0: 0x00000000  0x00000000  0x00000000  0x00000000
0x7fffffffddf0: 0x00000000  0x00000000  0x00000000  0x00000000
0x7fffffffde00: 0x00000000  0x00000000  0x00000000  0x00000000
0x7fffffffde10: 0x00000000  0x00000000  0x00000000  0x00000000
0x7fffffffde20: 0x00000000  0x00000000  0x00000000  0x00000000
0x7fffffffde30: 0x00000000  0x00000000  0x00000000  0x00000000
0x7fffffffde40: 0x00000000  0x00000000  0x00000000  0x00000000

0x7fffffffddd0 的部分对我来说像是一个地址,所以我检查了一下:

(gdb) disas 0x00007ffff7a434a0
Dump of assembler code for function __restore_rt:
   0x00007ffff7a434a0 <+0>: mov    rax,0xf
   0x00007ffff7a434a7 <+7>: syscall 
   0x00007ffff7a434a9 <+9>: nop    DWORD PTR [rax+0x0]

果然他们正在设置正在调用 sigreturn(在我的例子中是 rt_sigreturn)系统调用的恢复器!手册页说应用程序通常不会搞砸,但我猜这是针对典型的 C 程序的。所以我继续将这个函数复制到恢复器标签中,并将它放在我的结构中的适当位置,哇哦它起作用了。

这是现在工作的 NASM,我用一个新的 C 程序稍微改变了一些东西,我试图让它的外观和行为更像我的 NASM 程序,然后关闭 pause 对于 nanosleep.

新的 C 程序:

#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>

const char *parentmsg = "from parent\n[=14=]";
const char *childmsg = "from child\n[=14=]";
const char *handlemsg = "in handle\n[=14=]";
const char *forkfailed = "fork failed\n[=14=]";
const char *parentexit = "parent exiting\n[=14=]";
const char *sleepfailed = "sleep failed\n[=14=]";
const char *sleepinterrupted = "sleep interrupted\n[=14=]";

void print(const char *msg) {
    write(STDIN_FILENO, msg, strlen(msg));
}

static void handle(int sig) {
    print(handlemsg);
    waitid(P_ALL, -1, NULL, WEXITED|WSTOPPED|WCONTINUED);
}

int main(int argc, char* argv[]) {
    struct timespec tsreq;
    struct timespec tsrem;
    tsreq.tv_sec = 2;
    struct sigaction act;
    act.sa_handler = &handle;
    sigaction(SIGCHLD, &act, NULL);
    pid_t pid;
    if ( (pid = fork()) == 0 ) {
        print(childmsg);
        exit(0);
    }
    print(parentmsg);
    if (nanosleep((const struct timespec*)&tsreq, &tsrem) == -1) {
        if (errno == EINTR) {
            print(sleepinterrupted);
            nanosleep((const struct timespec*)&tsrem, NULL);
        } else {
            print(sleepfailed);
        }
    }
    print(parentexit);
    exit(0);
}

以及新的工作 NASM(在 Peter 的帮助下希望它看起来和功能更好)

USE64

STRUC sigact
    .handler        resq 1
    .flag           resq 1
    .restorer       resq 1
    .mask           resq 16
ENDSTRUC

STRUC timespec
    .tv_sec         resq 1
    .tv.nsec        resq 1
ENDSTRUC

section .text
global _start
_start:
    ; register SIGCHLD handler
    mov     DWORD [act+sigact.handler], handle
    mov     QWORD [act+sigact.restorer], restorer
    mov     DWORD [act+sigact.flag], 0x04000000
    mov     rax, 13
    mov     rdi, 17
    lea     rsi, [act]
    xor     rdx, rdx
    mov     r10, 0x8
    syscall
    cmp     eax, 0
    jne     sigaction_fail
    mov     rax, 57
    syscall
    cmp     eax, -1
    je      fork_failed
    cmp     eax, 0
    je      child
    mov     rax, parentmsg
    call    print
    mov     rax, 35
    mov     QWORD [tsreq+timespec.tv_sec], 2
    lea     rdi, [tsreq]
    lea     rsi, [tsrem]
    syscall
    cmp     eax, -1
    je      .exit
    mov     rax, sleepagain
    call    print
    mov     rax, 35
    mov     rdi, tsrem
    xor     rsi, rsi
    syscall
.exit:
    mov     rax, parentexit
    call    print
    mov     rax, 60
    xor     rdi, rdi
    syscall


restorer:
    mov     rax, 15
    syscall

fork_failed:
    mov     rax, forkfailed
    call    print
    mov     rax, 60
    mov     rdi, -1
    syscall

sigaction_fail:
    mov     rax, safailed
    call    print
    mov     rax, 60
    mov     rdi, -1
    syscall

handle:
    mov     rax, handlemsg
    call    print
    lea     rsi, [rsp-0x4]
    mov     rax, 247
    xor     rdi, rdi
    xor     rdx, rdx
    mov     r10, 14
    syscall
    cmp     eax, -1
    jne     .success
    mov     rax, hdfailed
    call    print
    mov     rax, 60
    mov     rdi, -1
    syscall
.success:
    mov     rax, hdsuccess
    call    print
    ret

child:
    mov     rax, childmsg
    call    print
    mov     rax, 60
    xor     rdi, rdi
    syscall

; print a null terminated string stored in rax
print:
    push    rbx
    push    rdx
    push    rdi
    push    rsi
    mov     rbx, rax
    call    strlen
    mov     rdx, rax
    mov     rax, 1
    mov     rdi, 1 ; stdout
    mov     rsi, rbx
    syscall
    pop     rsi
    pop     rdi
    pop     rdx
    pop     rbx
    ret

strlen:
    push    rbp
    mov     rbp, rsp
    push    rbx
    mov     rbx, rax
.countchar:
    cmp     BYTE [rax], 0 ; compare it to null byte
    jz      .exit
    inc     rax
    jmp     .countchar
.exit:
    sub     rax, rbx
    pop     rbx
    mov     rsp, rbp
    pop     rbp
    ret



section .data
    childmsg:   db  "from child", 0xa, 0 ; null terminated
    parentmsg   db  "from parent", 0xa, 0
    handlemsg   db  "in handle", 0xa, 0
    safailed    db  "failed to set signal handler", 0xa, 0
    hdfailed    db  "failed waiting for child", 0xa, 0
    hdsuccess   db  "successfully waited on child", 0xa, 0
    parentexit  db  "parent exiting", 0xa, 0
    forkfailed  db  "fork failed", 0xa, 0
    sleepagain  db  "sleeping again", 0xa, 0

section .bss
    tsreq:          resb    timespec_size
    tsrem:          resb    timespec_size
    act:            resb    sigact_size