为什么当我在没有 stdlib 的情况下链接汇编代码时会出现僵尸



当我不将目标文件与标准库gcc链接时,我正在试验汇编代码和 GTK+ 3 库。这是我的无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.后者表现出预期的行为,即应用程序在窗口关闭后立即终止。

我的感觉是标准 lib 正在执行一些我在第一个代码示例中忽略的基本操作,但我无法说出它是什么。

所以我的问题是:第一个代码示例中缺少什么?

感谢@MichaelPetch这个想法,它完美地解释了所有观察到的症状:

如果gtk_main在返回时让任何线程保持运行,则两个程序之间最重要的区别是eax=60/syscall仅退出当前线程。 请参阅_exit(2)手册页中的文档,其中指出 glibc 的 _exit() 包装器函数自 glibc2.3 以来一直使用exit_group

exit_group(2)在x86-64 ABI中eax=231/syscall。 这是 CRT 启动/清理代码在返回时main()运行的内容。

您可以通过在两个版本上使用strace ./a.out来查看这一点。


这至少让我感到惊讶:初始线程已退出但其他线程仍在运行的进程显示为僵尸。 我在自己的桌面上尝试了它(请参阅此答案的末尾以获取构建命令和 extern 声明,因此您不需要gtk.inc),并且您确实得到了一个报告为僵尸的进程,但您可以按 ctrl-c 杀死 gtk 在返回时让gtk_main运行的其他线程。

./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 -

我不确定ps m -LF是否是最好的命令,但它似乎有效。 它表示关闭窗口后只有主线程退出,其他 2 个线程仍在运行。 您甚至可以直接查看/proc/$(pidof thread-exit)/task,而不是使用 ps 为您执行此操作。


回复:关于不想链接libc的评论:

避免 glibc 的 CRT 启动/清理(通过定义 _start 而不是 _main )与避免 libc 不是一回事。 你的代码不会直接调用任何 libc 函数,但libgtk函数可以。 ldd /usr/lib/x86_64-linux-gnu/libgtk-3.so.0显示 libgtk 依赖于 libc,因此动态链接器无论如何都会将 libc 映射到您的进程中。 事实上,ldd在你自己的程序上说,即使你没有直接将-lc放在链接器命令行上。

因此,您只需链接libc并从_start调用exit(3)即可。

有关构建静态与动态二进制文件的信息,请参阅此问答,这些二进制文件是否链接libc,并使用NASM或gas定义_start或main。


旁注:定义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)

最新更新