为什么在没有 stdlib 的情况下 link 汇编代码会出现僵尸?
Why do I get a zombie when I link assembly code without stdlib?
我在试验汇编代码和 GTK+ 3 库时发现,如果我不针对标准库 link 带有 gcc
的目标文件,我的应用程序就会变成僵尸。这是我的 stdlib
免费应用程序
的代码
%include "gtk.inc"
%include "glib.inc"
global _start
SECTION .data
destroy db "destroy", 0 ; const gchar*
strWindow db "Window", 0 ; const gchar*
SECTION .bss
window resq 1 ; GtkWindow *
SECTION .text
_start:
; gtk_init (&argc, &argv);
xor rdi, rdi
xor rsi, rsi
call gtk_init
; window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
xor rdi, rdi
call gtk_window_new
mov [window], rax
; gtk_window_set_title (GTK_WINDOW (window), "Window");
mov rdi, rax
mov rsi, strWindow
call gtk_window_set_title
; g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
mov rdi, [window]
mov rsi, destroy
mov rdx, gtk_main_quit
xor rcx, rcx
xor r8, r8
xor r9, r9
call g_signal_connect_data
; gtk_widget_show (window);
mov rdi, [window]
call gtk_widget_show
; gtk_main ();
call gtk_main
mov rax, 60 ; SYS_EXIT
xor rdi, rdi
syscall
这里是针对标准库link编辑的相同代码
%include "gtk.inc"
%include "glib.inc"
global main
SECTION .data
destroy db "destroy", 0 ; const gchar*
strWindow db "Window", 0 ; const gchar*
SECTION .bss
window resq 1 ; GtkWindow *
SECTION .text
main:
push rbp
mov rbp, rsp
; gtk_init (&argc, &argv);
xor rdi, rdi
xor rsi, rsi
call gtk_init
; window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
xor rdi, rdi
call gtk_window_new
mov [window], rax
; gtk_window_set_title (GTK_WINDOW (window), "Window");
mov rdi, rax
mov rsi, strWindow
call gtk_window_set_title
; g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
mov rdi, [window]
mov rsi, destroy
mov rdx, gtk_main_quit
xor rcx, rcx
xor r8, r8
xor r9, r9
call g_signal_connect_data
; gtk_widget_show (window);
mov rdi, [window]
call gtk_widget_show
; gtk_main ();
call gtk_main
pop rbp
ret
两个应用程序都创建一个 GtkWindow
。但是,当 window 关闭时,两者的行为不同。前者导致僵尸进程,我需要按 Ctrl+C
。后者表现出预期的行为,即应用程序在 window 关闭后立即终止。
我的感觉是标准库正在执行我在第一个代码示例中忽略的一些基本操作,但我无法说出它是什么。
所以我的问题是:第一个代码示例中缺少什么?
感谢@MichaelPetch 的这个想法完美地解释了所有观察到的症状:
如果 gtk_main
在 returns 时离开任何线程 运行,你的两个程序之间最重要的区别是 eax=60
/syscall
只退出当前线程。请参阅 _exit(2)
man page 中的文档,其中指出 glibc 的 _exit()
包装函数自 glibc2.3.
以来已使用 exit_group
exit_group(2)
在 x86-64 ABI 中是 eax=231
/ syscall
。这就是 CRT startup/cleanup 代码在 main()
returns.
时运行的内容
您可以在两个版本上使用 strace ./a.out
来查看。
这至少让我感到惊讶:初始线程已退出但其他线程仍在运行的进程显示为僵尸。我在自己的桌面上尝试过(请参阅构建命令和外部声明的答案的末尾,因此您不需要 gtk.inc
),您确实得到了一个报告为 zombie 的进程,但是你可以 ctrl-c 杀死 gtk 在 gtk_main
returns.
时离开 运行 的其他线程
./thread-exit & # or in the foreground, and do the following commands in another shell
[1] 20592
$ ps m -LF -p $(pidof thread-exit)
UID PID PPID LWP C NLWP SZ RSS PSR STIME TTY STAT TIME CMD
peter 20592 7749 - 0 3 109031 21920 - 06:28 pts/12 - 0:00 ./thread-exit
peter - - 20592 0 - - - 0 06:28 - Sl 0:00 -
peter - - 20593 0 - - - 0 06:28 - Sl 0:00 -
peter - - 20594 0 - - - 0 06:28 - Sl 0:00 -
然后关闭window:进程没有退出,还有2个线程运行 + 1个僵尸
$ ps m -LF -p $(pidof thread-exit)
UID PID PPID LWP C NLWP SZ RSS PSR STIME TTY STAT TIME CMD
peter 20592 7749 - 0 3 0 0 - 06:28 pts/12 - 0:00 [thread-exit] <defunct>
peter - - 20592 0 - - - 0 06:28 - Zl 0:00 -
peter - - 20593 0 - - - 0 06:28 - Sl 0:00 -
peter - - 20594 0 - - - 0 06:28 - Sl 0:00 -
我不确定 ps m -LF
是否是最好的命令,但它似乎有效。说明关闭window后只有主线程退出,还有2个线程还在运行。您甚至可以直接查看 /proc/$(pidof thread-exit)/task
,而不是使用 ps 为您完成。
回复:关于不想 link libc:
的评论
避免 glibc 的 CRT 启动/清理(通过定义 _start
而不是 _main
)与避免 libc 不同。您的代码不直接调用任何 libc 函数,但 libgtk
会调用。 ldd /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
显示 libgtk 依赖于 libc,因此动态 linker 无论如何都会将 libc 映射到您的进程中。事实上,您自己的程序中的 ldd
就是这样说的,即使您不直接将 -lc
放在 linker 命令行上也是如此。
所以您可以 link libc 并从您的 _start
.
调用 exit(3)
参见 this Q&A for info on building static vs. dynamic binaries that link libc or not and define _start or main, with NASM or gas。
旁注:定义 main
的版本不需要使用 rbp
.
创建堆栈帧
如果你省略了push rbp
/ mov rbp, rsp
,你仍然需要做一些事情来对齐call
之前的堆栈,但它可以是push rax
,或者仍然 push rbp
如果你想混淆。所以:
main:
push rax ; align the stack
...
call gtk_widget_show
pop rax ; restore stack to function-entry state
jmp gtk_main ; optimized tail-call
如果你想保留帧指针的东西,你仍然可以做尾调用,但是 pop rbp
/ jmp gtk_main
.
PS:对于那些想自己尝试的人来说,这个变化让你可以构建它而不必去寻找 gtk.inc
:
;%include "gtk.inc"
;%include "glib.inc"
extern gtk_init
extern gtk_window_new
extern g_signal_connect_data
extern gtk_window_set_title
extern gtk_widget_show
extern gtk_main
extern gtk_main_quit
构建:
yasm -felf64 -Worphan-labels -gdwarf2 thread-exit.asm &&
gcc -nostdlib -o thread-exit thread-exit.o $(pkg-config --libs gtk+-3.0)
我在试验汇编代码和 GTK+ 3 库时发现,如果我不针对标准库 link 带有 gcc
的目标文件,我的应用程序就会变成僵尸。这是我的 stdlib
免费应用程序
%include "gtk.inc"
%include "glib.inc"
global _start
SECTION .data
destroy db "destroy", 0 ; const gchar*
strWindow db "Window", 0 ; const gchar*
SECTION .bss
window resq 1 ; GtkWindow *
SECTION .text
_start:
; gtk_init (&argc, &argv);
xor rdi, rdi
xor rsi, rsi
call gtk_init
; window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
xor rdi, rdi
call gtk_window_new
mov [window], rax
; gtk_window_set_title (GTK_WINDOW (window), "Window");
mov rdi, rax
mov rsi, strWindow
call gtk_window_set_title
; g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
mov rdi, [window]
mov rsi, destroy
mov rdx, gtk_main_quit
xor rcx, rcx
xor r8, r8
xor r9, r9
call g_signal_connect_data
; gtk_widget_show (window);
mov rdi, [window]
call gtk_widget_show
; gtk_main ();
call gtk_main
mov rax, 60 ; SYS_EXIT
xor rdi, rdi
syscall
这里是针对标准库link编辑的相同代码
%include "gtk.inc"
%include "glib.inc"
global main
SECTION .data
destroy db "destroy", 0 ; const gchar*
strWindow db "Window", 0 ; const gchar*
SECTION .bss
window resq 1 ; GtkWindow *
SECTION .text
main:
push rbp
mov rbp, rsp
; gtk_init (&argc, &argv);
xor rdi, rdi
xor rsi, rsi
call gtk_init
; window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
xor rdi, rdi
call gtk_window_new
mov [window], rax
; gtk_window_set_title (GTK_WINDOW (window), "Window");
mov rdi, rax
mov rsi, strWindow
call gtk_window_set_title
; g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
mov rdi, [window]
mov rsi, destroy
mov rdx, gtk_main_quit
xor rcx, rcx
xor r8, r8
xor r9, r9
call g_signal_connect_data
; gtk_widget_show (window);
mov rdi, [window]
call gtk_widget_show
; gtk_main ();
call gtk_main
pop rbp
ret
两个应用程序都创建一个 GtkWindow
。但是,当 window 关闭时,两者的行为不同。前者导致僵尸进程,我需要按 Ctrl+C
。后者表现出预期的行为,即应用程序在 window 关闭后立即终止。
我的感觉是标准库正在执行我在第一个代码示例中忽略的一些基本操作,但我无法说出它是什么。
所以我的问题是:第一个代码示例中缺少什么?
感谢@MichaelPetch 的这个想法完美地解释了所有观察到的症状:
如果 gtk_main
在 returns 时离开任何线程 运行,你的两个程序之间最重要的区别是 eax=60
/syscall
只退出当前线程。请参阅 _exit(2)
man page 中的文档,其中指出 glibc 的 _exit()
包装函数自 glibc2.3.
exit_group
exit_group(2)
在 x86-64 ABI 中是 eax=231
/ syscall
。这就是 CRT startup/cleanup 代码在 main()
returns.
您可以在两个版本上使用 strace ./a.out
来查看。
这至少让我感到惊讶:初始线程已退出但其他线程仍在运行的进程显示为僵尸。我在自己的桌面上尝试过(请参阅构建命令和外部声明的答案的末尾,因此您不需要 gtk.inc
),您确实得到了一个报告为 zombie 的进程,但是你可以 ctrl-c 杀死 gtk 在 gtk_main
returns.
./thread-exit & # or in the foreground, and do the following commands in another shell
[1] 20592
$ ps m -LF -p $(pidof thread-exit)
UID PID PPID LWP C NLWP SZ RSS PSR STIME TTY STAT TIME CMD
peter 20592 7749 - 0 3 109031 21920 - 06:28 pts/12 - 0:00 ./thread-exit
peter - - 20592 0 - - - 0 06:28 - Sl 0:00 -
peter - - 20593 0 - - - 0 06:28 - Sl 0:00 -
peter - - 20594 0 - - - 0 06:28 - Sl 0:00 -
然后关闭window:进程没有退出,还有2个线程运行 + 1个僵尸
$ ps m -LF -p $(pidof thread-exit)
UID PID PPID LWP C NLWP SZ RSS PSR STIME TTY STAT TIME CMD
peter 20592 7749 - 0 3 0 0 - 06:28 pts/12 - 0:00 [thread-exit] <defunct>
peter - - 20592 0 - - - 0 06:28 - Zl 0:00 -
peter - - 20593 0 - - - 0 06:28 - Sl 0:00 -
peter - - 20594 0 - - - 0 06:28 - Sl 0:00 -
我不确定 ps m -LF
是否是最好的命令,但它似乎有效。说明关闭window后只有主线程退出,还有2个线程还在运行。您甚至可以直接查看 /proc/$(pidof thread-exit)/task
,而不是使用 ps 为您完成。
回复:关于不想 link libc:
的评论避免 glibc 的 CRT 启动/清理(通过定义 _start
而不是 _main
)与避免 libc 不同。您的代码不直接调用任何 libc 函数,但 libgtk
会调用。 ldd /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
显示 libgtk 依赖于 libc,因此动态 linker 无论如何都会将 libc 映射到您的进程中。事实上,您自己的程序中的 ldd
就是这样说的,即使您不直接将 -lc
放在 linker 命令行上也是如此。
所以您可以 link libc 并从您的 _start
.
exit(3)
参见 this Q&A for info on building static vs. dynamic binaries that link libc or not and define _start or main, with NASM or gas。
旁注:定义 main
的版本不需要使用 rbp
.
如果你省略了push rbp
/ mov rbp, rsp
,你仍然需要做一些事情来对齐call
之前的堆栈,但它可以是push rax
,或者仍然 push rbp
如果你想混淆。所以:
main:
push rax ; align the stack
...
call gtk_widget_show
pop rax ; restore stack to function-entry state
jmp gtk_main ; optimized tail-call
如果你想保留帧指针的东西,你仍然可以做尾调用,但是 pop rbp
/ jmp gtk_main
.
PS:对于那些想自己尝试的人来说,这个变化让你可以构建它而不必去寻找 gtk.inc
:
;%include "gtk.inc"
;%include "glib.inc"
extern gtk_init
extern gtk_window_new
extern g_signal_connect_data
extern gtk_window_set_title
extern gtk_widget_show
extern gtk_main
extern gtk_main_quit
构建:
yasm -felf64 -Worphan-labels -gdwarf2 thread-exit.asm &&
gcc -nostdlib -o thread-exit thread-exit.o $(pkg-config --libs gtk+-3.0)