为什么我在没有stdlib的情况下链接汇编代码会变成僵尸?
我在试验汇编代码和GTK+3库时发现,如果我不将对象文件与标准库为什么我在没有stdlib的情况下链接汇编代码会变成僵尸?,std,nasm,x86-64,ld,gtk3,Std,Nasm,X86 64,Ld,Gtk3,我在试验汇编代码和GTK+3库时发现,如果我不将对象文件与标准库gcc链接,我的应用程序就会变成僵尸。这是我的stdlib免费应用程序代码 %include "gtk.inc" %include "glib.inc" global _start SECTION .data destroy db "destroy", 0 ; const gchar* strWindow db "Window", 0 ; const gcha
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
这里是与标准库链接的相同代码
%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
。但是,当窗口关闭时,两者的行为不同。前者导致一个僵尸进程,我需要按Ctrl+C
。后者表现出预期的行为,即应用程序在窗口关闭后立即终止
我的感觉是,标准库正在执行一些我在第一个代码示例中忽略的基本操作,但我不知道它是什么
所以我的问题是:第一个代码示例中缺少了什么?感谢@MichaelPetch的这个想法,它完美地解释了所有观察到的症状: 如果
gtk_main
在返回时让任何线程运行,那么两个程序之间最重要的区别是eax=60
/syscall
仅退出当前线程。请参阅中的文档,其中指出glibc的\u exit()
包装函数自glibc2.3以来一直使用exit\u组
在x86-64 ABI中是eax=231
/syscall
。这是当main()
返回时CRT启动/清理代码运行的内容
./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 -
您可以在两个版本上使用strace./a.out
来查看这一点
这至少让我感到惊讶:一个初始线程已退出但其他线程仍在运行的进程显示为僵尸。我在自己的桌面上尝试过它(请参见本答案的结尾部分,了解构建命令和外部声明,这样您就不需要gtk.inc
),您确实得到了一个报告为a的进程,但您可以在gtk\u main
返回时按住ctrl-c键以杀死gtk运行的其他线程
./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 -
然后关闭窗口:进程没有退出,仍然有两个线程运行+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 -
如果您想保留帧指针的内容,您仍然可以执行tail调用,但是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)
mov-rax,60;SYS_EXIT xor rdi,rdi syscall
使正常关机程序短路。由于您没有展示如何组装/链接,而且这不是一个最小的可验证示例(您使用的标题不是问题的一部分),所以很难说。一种可能性是需要调用C库exit
。即使没有调用sys\u exit,我最终还是会变成一个僵尸。生成%included文件很简单:它们只包含外部符号的外部声明。我使用了“nasm-f elf64…”和“ld-I/path/to/interpreterpkg-config--libs gtk+-3.0
…”。我怀疑我需要调用其他东西,这可能是由C的退出完成的某种进程清理。然而,我不确定这次清理是什么。我试图包括一个带有-1作为pid的sys_wait4调用,但即使这样,我仍然会得到一个僵尸;SYS_EXIT xor rdi,rdi syscall
这将不起作用,因为这样您的程序可能会在随机内存中出错。我提到的C库exit
函数通常会执行必要的清理(这可能会使您的程序与众不同),以便GTK正常关闭(我记得它还会执行一些与线程相关的清理)gtk.inc
和另一个inc的生成可能微不足道,但如果您希望任何人认真对待这一点(或尝试您的代码),那么您可能希望提供它们。如果没有它们,这不是一个最小的完全可验证的例子。某些内容未初始化或未清理。如果它与线程相关,我不会感到惊讶。一个更大的问题是为什么要绕过C运行时?我不想包括C库的原因是因为我不明白为什么它们是必要的。我正在编写一个只需要从Gtk+3库调用API的Linux应用程序。感谢您的回答,exit_group
解决了进程问题。关于僵尸,这是我关闭窗口后系统监视器报告的内容。这对我来说是有意义的,因为我正在终止父进程,而不用等待子进程的返回值。无论如何,我可以理解为什么在这种情况下人们可能更喜欢libc。这个问题的精神是试图理解和了解我在第一个代码中的错误所在,并对libc的内部机制有更深入的了解。@Phoenix87:这是个好问题,我只是对你的术语感到恼火。但如果你说一个进程查看器工具将其报告为僵尸的话,也许我应该自己尝试一下。我只是假设如果仍有线程在运行,它不会显示为僵尸,但如果初始PID已退出,它可能会显示为僵尸。(线程使用与PID相同的编号空间,即有自己的PID,但是getpid()返回初始线程的PID。因此“PID”线程的个数实际上是线程ID。请参阅/proc/*/task。另外,请注意,进程不必等待自己。外壳等待其所有子进程,因此直接从外壳运行的进程在退出时总是会立即收获。这就是为什么有一个僵尸进程的原因。对于普通的单线程进程,这种情况不会发生当您从bash运行这些程序时。@Phoenix87:BTW,您可以使用我上次编辑中的建议使main()
更高效/更小。@Phoenix87:update:yes,ps
将其报告为僵尸。我使用来自的输出更新了我的答案