处理SIGCHLD NASM

时间:2016-06-28 23:39:05

标签: segmentation-fault nasm x86-64

编辑请参阅下面的自我答案

我一直试图在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

在发送信号时成功等待孩子,但在返回时立即出现故障。我已经尝试过越来越多的关于信号和信号处理的阅读,但是在这一点上,它在我脑海里一直在一起运行。抱歉,NASM代码很丑或不标准。我不仅在学习,而且我可能至少重写了25次(handle可能超过100次)。

2 个答案:

答案 0 :(得分:2)

信号处理程序是正常功能。返回ret,而不是iret。 (你已经编辑了你的问题以解决这个问题,所以我猜你还有其他问题。)

查看gcc如何在Godbolt compiler explorer上编译handler()

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在wait(3) Linux系统调用之上实现wait4,而不是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的返回值,请执行以下操作:

    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时仍然崩溃,可能它会成功返回但实际上在你的主程序中崩溃了。使用调试器查找。

您的字符串常量应该放在.rodata部分。您无需在运行时修改它们,因此请勿将它们放在.data

您也不需要call bzero,因为您的act位于bss中。如果你想在堆栈上而不是静态地分配它,你应该使用rep stosq将其归零,就像你在main()中使用gcc 5.3那样。 (它内联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

答案 1 :(得分:1)

经过很长一段时间的努力,Ooooook终于想通了!问题是在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程序。所以我继续将这个函数复制到restorer标签中并将它放在我的struc中的适当位置,并且它工作正常。

这是现在正在使用的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\0";
const char *childmsg = "from child\n\0";
const char *handlemsg = "in handle\n\0";
const char *forkfailed = "fork failed\n\0";
const char *parentexit = "parent exiting\n\0";
const char *sleepfailed = "sleep failed\n\0";
const char *sleepinterrupted = "sleep interrupted\n\0";

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