edit 请参阅下面的自我答案
我一直在尝试在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 childn");
exit(0);
}
printf("message from parentn");
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次)。
信号处理程序是正常功能。使用ret
返回,而不是iret
。(从那以后,您已经编辑了您的问题来解决此问题,所以我想您还有其他问题)。
查看GCC如何编译handler()
,在Godbolt编译器资源管理器上。
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)
的内联调用。
请注意,MAN页面说wait4
已过时,新程序应使用waitpid
或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()
中做到。(正如您在Godbolt上看到的那样,它引入了BZERO)。
顺便说一句,您的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
oooook长时间戳戳后,我终于弄清楚了!问题是在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代码真正做的事情中四处挖掘这一点。我找到了我正在制作的同一syscall的位置,并将其用于Sigaction Struct的字节:
:(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程序。因此,我继续在修复器标签中复制此功能,并将其放在我的Struc和Wooooooo中的适当位置。
这是现在正在工作的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 parentn ";
const char *childmsg = "from childn ";
const char *handlemsg = "in handlen ";
const char *forkfailed = "fork failedn ";
const char *parentexit = "parent exitingn ";
const char *sleepfailed = "sleep failedn ";
const char *sleepinterrupted = "sleep interruptedn ";
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(在彼得的一些帮助下,希望它看起来和功能更好)
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