编辑请参阅下面的自我答案
我一直试图在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次)。
答案 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)。
实际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)
当我检查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