asm 中的信号处理:为什么我在调用 sys_pause 系统调用时收到 SIGSEGV?
Signal handling in asm: Why am I receiving SIGSEGV when invoking the sys_pause syscall?
我正在尝试创建一个 x86_64 汇编程序,只要发送 SIGTERM
信号就会显示 "SIGTERM received"。我的应用程序直接使用 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 [=14=]xd,%eax
0x00000000004000c5 <+21>: mov [=14=]xf,%edi
0x00000000004000ca <+26>: lea 0x600160,%rsi
0x00000000004000d2 <+34>: mov [=14=]x0,%edx
0x00000000004000d7 <+39>: mov [=14=]x8,%r10d
0x00000000004000dd <+45>: syscall
0x00000000004000df <+47>: cmp [=14=]x0,%rax
0x00000000004000e3 <+51>: jne 0x4000ee <error>
0x00000000004000e5 <+53>: mov [=14=]x22,%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 ()
从未调用信号处理程序,应用程序已崩溃。
我做错了什么?
在应用程序正常工作之前需要进行两项更正。
sa_restorer
Jester pointed me to 其中提到内核要求填写 sigaction
的 sa_restorer
成员。
解决这个问题需要定义 SA_RESTORER
:
%define SA_RESTORER 0x04000000
...并初始化 sa_restorer
和 sa_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
此时,应用程序运行没有崩溃。
这是 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
并编译 运行 with
nasm -f elf64 signal.asm -o signal.o && ld signal.o && ./a.out
在 SIGINT 版本中,按 Control-C 中断程序应该打印消息 'SIGTERM received'(也可以更改它以使其更准确)。在 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 received' 消息按预期打印。
我正在尝试创建一个 x86_64 汇编程序,只要发送 SIGTERM
信号就会显示 "SIGTERM received"。我的应用程序直接使用 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 [=14=]xd,%eax
0x00000000004000c5 <+21>: mov [=14=]xf,%edi
0x00000000004000ca <+26>: lea 0x600160,%rsi
0x00000000004000d2 <+34>: mov [=14=]x0,%edx
0x00000000004000d7 <+39>: mov [=14=]x8,%r10d
0x00000000004000dd <+45>: syscall
0x00000000004000df <+47>: cmp [=14=]x0,%rax
0x00000000004000e3 <+51>: jne 0x4000ee <error>
0x00000000004000e5 <+53>: mov [=14=]x22,%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 ()
从未调用信号处理程序,应用程序已崩溃。
我做错了什么?
在应用程序正常工作之前需要进行两项更正。
sa_restorer
Jester pointed me to sigaction
的 sa_restorer
成员。
解决这个问题需要定义 SA_RESTORER
:
%define SA_RESTORER 0x04000000
...并初始化 sa_restorer
和 sa_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
此时,应用程序运行没有崩溃。
这是 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
并编译 运行 with
nasm -f elf64 signal.asm -o signal.o && ld signal.o && ./a.out
在 SIGINT 版本中,按 Control-C 中断程序应该打印消息 'SIGTERM received'(也可以更改它以使其更准确)。在 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 received' 消息按预期打印。