【问题标题】:Handling SIGCHLD NASM处理 SIGCHLD NASM
【发布时间】:2016-10-31 11:25:11
【问题描述】:

编辑在下面查看我的自我回答


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

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

【问题讨论】:

  • 你查看了handle 的 gcc 的 asm 输出吗?这是pretty simple。另外,永远不要使用enter 指令。它慢得可怕。 (参见 Agner Fog 的指南,链接自 x86 tag wiki。)
  • 另外,您是否使用调试器查看实际出错的指令,并检查 rsp 是否正确(即指向返回地址,就像它在函数入口处一样)

标签: segmentation-fault nasm x86-64


【解决方案1】:

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

Godbolt compiler explorer上看看gcc是如何编译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 在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 的返回值,请执行以下操作:

    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 将其归零,就像 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 &lt;signal.h&gt;' | 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

【讨论】:

  • 嘿,彼得,iret 只是我尝试的另一个迭代。我也带着ret回来了。不过感谢您的提示。
  • 另外我没有可用的等待功能,所以我使用wait4(2) 的系统调用来完成,这就是为什么我的看起来与那个不同(我开始疯狂地保留寄存器堆栈原因我想也许我正在摧毁它们或其他东西)
  • @user3591723:我用不使用任何库函数的handler 更新了我的答案。不要在 asm 中使用调试打印,这就是像 gdb 这样的调试器的用途。我没有查看您的 asm _start 是否正确安装了信号处理程序,也没有非常仔细地检查您的 asm handler。如果你真的在ret 上崩溃了,也许你破坏了堆栈。就像我说的,使用调试器来检查。
  • 是的,我一直在使用调试器。我错过了手册页中说它已过时的部分。我使用它是因为我的系统上只有 waitid 和 wait4 并且 wait4 不那么复杂;)我一直在使用 gdb 来调试我的代码,但是在这个问题上,即使使用 gdb 我也无法弄清楚。我还在学习 gdb 和 NASM,所以我可能会遗漏一些东西。我今天将以更新鲜的头脑去做,也许会更新我的问题。印在那里只是因为我不想沉默。我越来越寂寞了。顺便感谢您的帮助。
  • @user3591723:我刚刚保存了一些我在您发布答案之前开始的编辑。
【解决方案2】:

Oooooook 经过很长时间的戳我终于想通了!问题是在 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

【讨论】:

  • 你不需要打电话给bzero:act 在 bss 中,所以它已经是全零了。如果您想避免系统调用的 libc 包装器,您不妨像 gcc 在编译您的函数时所做的那样使用 rep stosq。很高兴找到 asm 结构;我已经开始用 gdb 查看代码,但没有完成。我还在想 glibc 的 sigaction 包装器 rt_sigaction 正在向掩码添加额外的信号:P
  • 我也是这么想的!这就是为什么我最初复制它们的原因,正如您在原始评论中看到的那样。也不知道 bzero,将删除
  • 顺便说一句,是的,“应用程序通常不会搞砸”措辞适用于使用 glibc 提供的 API 的程序,而不是直接调用底层系统。这是 glibc 包装器在 POSIX 接口和 Linux 系统调用接口之间做一些额外事情的许多情况之一。它并不总是在手册页中有详细记录,但至少 glibc 是免费软件,因此您可以查看 its source code (with comments)
猜你喜欢
  • 2012-01-13
  • 2013-07-07
  • 2020-08-02
  • 2011-11-02
  • 2014-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多