【问题标题】:Signal handling in asm: Why am I receiving SIGSEGV when invoking the sys_pause syscall?asm 中的信号处理:为什么我在调用 sys_pause 系统调用时会收到 SIGSEGV?
【发布时间】:2017-09-28 22:53:26
【问题描述】:

我正在尝试创建一个 x86_64 汇编程序,该程序在发送SIGTERM 信号时显示“收到SIGTERM”。我的应用程序直接使用 Linux 系统调用:

%define sys_write        0x01
%define sys_rt_sigaction 0x0d
%define sys_pause        0x22
%define sys_exit         0x3c

%define SIGTERM 0x0f

%define STDOUT 0x01


; Definition of sigaction struct for sys_rt_sigaction
struc sigaction
    .sa_handler  resq 1
    .sa_flags    resq 1
    .sa_restorer resq 1
    .sa_mask     resq 1
endstruc


section .data

    ; Message shown when a syscall fails
    error_msg     db  'syscall error', 0x0a
    error_msg_len equ $ - error_msg

    ; Message shown when SIGTERM is received
    sigterm_msg     db  'SIGTERM received', 0x0a
    sigterm_msg_len equ $ - sigterm_msg


section .bss

    act resb sigaction_size
    val resd 1


section .text
global _start

_start:

    ; Initialize act
    lea rax, [handler]
    mov [act + sigaction.sa_handler], rax

    ; Set the handler
    mov rax, sys_rt_sigaction
    mov rdi, SIGTERM
    lea rsi, [act]
    mov rdx, 0x00
    mov r10, 0x08
    syscall

    ; Ensure the syscall succeeded
    cmp rax, 0
    jne error

    ; Pause until a signal is received
    mov rax, sys_pause
    syscall

    ; Upon success, jump to exit
    jmp exit

error:

    ; Display an error message
    mov rax, sys_write
    mov rdi, STDOUT
    mov rsi, error_msg
    mov rdx, error_msg_len
    syscall

    ; Set the return value to one
    mov dword [val], 0x01

exit:

    ; Terminate the application gracefully
    mov rax, sys_exit
    mov rdi, [val]
    syscall

handler:

    ; Display a message
    mov rax, sys_write
    mov rdi, STDOUT
    mov rsi, sigterm_msg
    mov rdx, sigterm_msg_len
    syscall

    ret

当我运行应用程序时,它在sys_pause 系统调用处挂起(如预期的那样),但是当我发送SIGTERM 信号时,它因分段错误而崩溃。

所以我将应用程序加载到 GDB 中以了解发生了什么:

(gdb) break _start
Breakpoint 1 at 0x4000b0
(gdb) run
Starting program: [...] 

Breakpoint 1, 0x00000000004000b0 in _start ()
(gdb) info proc
process 9639
(gdb) continue
Continuing.

GDB 会话挂起,然后我打开另一个终端并运行 kill SIGTERM 9639。这导致了以下输出:

Program received signal SIGTERM, Terminated.
0x00000000004000ec in _start ()

然后我跑了:

(gdb) disas _start
Dump of assembler code for function _start:
   0x00000000004000b0 <+0>:     lea    0x400123,%rax
   0x00000000004000b8 <+8>:     mov    %rax,0x600160
   0x00000000004000c0 <+16>:    mov    $0xd,%eax
   0x00000000004000c5 <+21>:    mov    $0xf,%edi
   0x00000000004000ca <+26>:    lea    0x600160,%rsi
   0x00000000004000d2 <+34>:    mov    $0x0,%edx
   0x00000000004000d7 <+39>:    mov    $0x8,%r10d
   0x00000000004000dd <+45>:    syscall 
   0x00000000004000df <+47>:    cmp    $0x0,%rax
   0x00000000004000e3 <+51>:    jne    0x4000ee <error>
   0x00000000004000e5 <+53>:    mov    $0x22,%eax
   0x00000000004000ea <+58>:    syscall 
=> 0x00000000004000ec <+60>:    jmp    0x400114 <exit>
End of assembler dump.

然后我继续申请:

(gdb) continue
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x00000000004000ec in _start ()

信号处理程序从未被调用,应用程序已崩溃。

我做错了什么?

【问题讨论】:

  • 我相信this 也适用于你。 PS:不要混淆来自 asm 的信号 ;)
  • 您是否尝试在strace 下运行它以查看系统调用参数和返回值?或者在gdb中,print $raxsyscall之后,看看是不是-EFAULT什么的。查看x86 tag wiki底部的调试提示。
  • @PeterCordes 不幸地不起作用。系统调用不返回错误,只有在尝试处理内核故障信号时,如果没有设置恢复器。我认为这是一个内核错误。
  • 哦,nvm,GDB 显示RIP 指向syscall 之后的指令,但SIGSEGV 先交付。这在 GDB 方面可能是虚假的(或者在 Linux 方面,用于向 GDB 显示错误的 RIP,而不是在信号处理程序或恢复器或其他方面)。当您回复@jester 时,已将其编辑到我之前的评论中一半:P
  • @PeterCordes 我设置了sa_restorer 并添加了SA_RESTORER 标志,它现在调用处理程序。但是,它仍然在之后崩溃。我会继续调试。

标签: linux assembly x86-64 nasm signal-handling


【解决方案1】:

在应用程序正常工作之前需要进行两项更正。


sa_restorer

Jester 将我指向this answer,其中提到内核需要填写sigactionsa_restorer 成员。

修复这个需要定义SA_RESTORER

%define SA_RESTORER 0x04000000

...并初始化sa_restorersa_flags 成员:

mov [act + sigaction.sa_flags], dword SA_RESTORER
lea rax, [restorer]
mov [act + sigaction.sa_restorer], rax

然后我为restorer 函数添加了一个空存根:

restorer:

    ret

此时,调用处理程序没有错误,但应用程序仍在崩溃...


sys_rt_sigreturn

显然,sa_restorer 函数需要调用 sys_rt_sigreturn 系统调用。这需要定义sys_rt_sigreturn:

%define sys_rt_sigreturn 0x0f

restorer 函数随后被修改:

restorer:

    ; return from the signal handler
    mov rax, sys_rt_sigreturn
    syscall

此时,应用程序运行并没有崩溃。

【讨论】:

  • 这也在我的回答中;)诚然,仅在示例代码中。
  • 啊,那我应该仔细看看 - 谢谢!
【解决方案2】:

这是Nathan Osman 的完整工作和更正程序。在这里回答是因为这样的例子似乎在互联网上的其他任何地方都不存在。

%define sys_write        0x01
%define sys_rt_sigaction 0x0d
%define sys_pause        0x22
%define sys_exit         0x3c
%define sys_rt_sigreturn 0x0f
%define SIGTERM 0x0f
%define SIGINT 0x02
%define STDOUT 0x01
%define SA_RESTORER 0x04000000

; Definition of sigaction struct for sys_rt_sigaction
struc sigaction
    .sa_handler  resq 1
    .sa_flags    resq 1
    .sa_restorer resq 1
    .sa_mask     resq 1
endstruc

section .data
    ; Message shown when a syscall fails
    error_msg     db  'syscall error', 0x0a
    error_msg_len equ $ - error_msg
    ; Message shown when SIGTERM is received
    sigterm_msg     db  'SIGTERM received', 0x0a
    sigterm_msg_len equ $ - sigterm_msg

section .bss
    act resb sigaction_size
    val resd 1

section .text
global _start
_start:
    ; Initialize act
    mov qword [act + sigaction.sa_handler], handler
    mov [act + sigaction.sa_flags], dword SA_RESTORER
    mov qword [act + sigaction.sa_restorer], restorer

    ; Set the handler
    mov rax, sys_rt_sigaction
    ;mov rdi, SIGINT
    mov rdi, SIGTERM
    lea rsi, [act]
    mov rdx, 0x00
    mov r10, 0x08
    syscall

    ; Ensure the syscall succeeded
    cmp rax, 0
    jne error

    ; Pause until a signal is received
    mov rax, sys_pause
    syscall

    ; Upon success, jump to exit
    jmp exit

error:
    ; Display an error message
    mov rax, sys_write
    mov rdi, STDOUT
    mov rsi, error_msg
    mov rdx, error_msg_len
    syscall

    ; Set the return value to one
    mov dword [val], 0x01

exit:
    ; Terminate the application gracefully
    mov rax, sys_exit
    mov rdi, [val]
    syscall

handler:
    ; Display a message
    mov rax, sys_write
    mov rdi, STDOUT
    mov rsi, sigterm_msg
    mov rdx, sigterm_msg_len
    syscall
    ret

restorer:
    ; return from the signal handler
    mov rax, sys_rt_sigreturn
    syscall

为了便于测试,您可以通过在此处取消注释第一个列表并注释第二个列表来将 SIGTERM 替换为 SIGINT。

    ;mov rdi, SIGINT
    mov rdi, SIGTERM

将其保存到signal.asm 并编译运行

nasm -f elf64 signal.asm -o signal.o && ld signal.o && ./a.out

在 SIGINT 版本中,按 Control-C 中断程序应打印消息“收到 SIGTERM”(也可以更改它以使其更准确)。在 SIGTERM 版本中,改为在 gdb 中运行它

$ gdb ./a.out
[GNU gdb...]
(gdb) r
Starting program: /path/a.out 

现在按下 Control-C

^C
Program received signal SIGINT, Interrupt.
0x0000000000400107 in _start ()
(gdb) signal SIGTERM
Continuing with signal SIGTERM.
SIGTERM received
[Inferior 1 (process 22742) exited normally]
(gdb)

“收到 SIGTERM”消息按预期打印。

【讨论】:

    猜你喜欢
    • 2015-12-25
    • 1970-01-01
    • 2013-09-04
    • 2022-11-07
    • 2013-05-24
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-09
    相关资源
    最近更新 更多