2016-07-18 1 views
1

Я экспериментировал с ассемблером и библиотеками GTK + 3, когда обнаружил, что мое приложение превращается в зомби, если я не связываю объектный файл с gcc против стандартной библиотеки. Вот мой код stdlib применения свободном отПочему я получаю зомби, когда я связываю код сборки без 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. Последний показывает ожидаемое поведение, то есть приложение прекращается, как только окно закрывается.

Я чувствую, что стандартная библиотека выполняет некоторые важные операции, которые я игнорирую в первом примере кода, но я не могу сказать, что это такое.

Так что мой вопрос: что отсутствует в первом примере кода?

+1

'mov rax, 60; SYS_EXIT xor rdi, rdi Syscall' замыкает цепь на нормальную процедуру выключения. Поскольку вы не показываете, как вы собираете/связываете, и это не является минимальным проверяемым примером (заголовки, которые вы используете, не являются частью вопроса), сказать сложно. Одна из возможностей заключается в том, что нужно вызвать библиотеку '' '' ''. –

+0

Даже без вызова sys_exit я все еще получаю зомби. Файлы, включенные в%, тривиальны для генерации: они просто включают объявления extern для внешних символов. Я использовал «nasm -f elf64 ...» и «ld -I/path/to/interpreter» pkg-config -libs gtk + -3.0' ... ». Я подозреваю, что мне нужно назвать что-то еще, что, вероятно, является своего рода очисткой процесса, выполненной с помощью C. Однако я не уверен, что это за чистка. Я попытался включить вызов sys_wait4 с -1 как pid, но даже с этим я все равно получаю зомби. – Phoenix87

+0

Если вы просто удалите 'mov rax, 60; SYS_EXIT xor rdi, rdi syscall', который не сработает, потому что тогда ваша программа, скорее всего, будет проходить случайную память. Функция «exit» в _C_ library, о которой я упоминал, обычно выполняет очистку, которая может потребоваться (что может отличать ваши программы отдельно), чтобы GTK нормально закрывался (на мой взгляд, также будет выполняться очистка, связанная с потоком). 'gtk.inc', а другой inc может быть тривиальным для генерации, но если вы хотите, чтобы кто-то серьезно воспринял это (или попробовал ваш код), вы можете их предоставить. Без них это не является минимальным полным проверяемым примером. –

ответ

3

Благодаря @MichaelPetch этой идеи, которая объясняет все наблюдаемые симптомы отлично:

Если gtk_main оставляет никаких потоков работает, когда она возвращается, самое важное различие между вашими двумя программами является то, что eax=60/syscall выходит только текущий поток , См. Документацию в _exit(2) man page, в которой указывается, что функция обертки glibc _exit() использовала exit_group с glibc2.3.

exit_group(2)eax=231/syscall в x86-64 ABI. Это то, что запускает/очищает код CRT, когда возвращается main().

Вы можете увидеть это, используя strace ./a.out на обеих версиях.


Это удивило меня, по крайней мере: процесс, в котором начальная нить возбужденное, но другие потоки по-прежнему работает, показан как зомби. Я попробовал это на своем рабочем столе (см. Конец этого ответа для команд сборки и деклараций extern, поэтому вам не нужно gtk.inc), и вы действительно получаете процесс, который указан как zombie, но вы можете 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 для этого.


Re: комментарии по поводу не желая связывать Libc:

Как избежать GLibC ЭЛТ запуска/очистки (путем определения _start вместо _main) это не то же самое, как избежать Libc. Ваш код не вызывает никаких функций libc напрямую, но libgtk делает. ldd /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 показывает, что libgtk зависит от libc, поэтому динамический компоновщик все равно будет отображать libc в ваш процесс. Фактически, ldd в вашей собственной программе говорит, что даже если вы не ставите -lc в командной строке компоновщика напрямую.

Таким образом, вы можете просто связать libc и позвонить exit(3) с вашего _start.

См. 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) 
+0

Благодарим за ответ, 'exit_group' решает проблему с процессами. Что касается зомби, это то, о чем сообщал системный монитор после закрытия окна. Это имело смысл для меня, так как я прекращал родительский процесс, не дожидаясь, когда вы услышите возвращаемое значение для детей. В любом случае, я могу понять, почему в этом случае можно предпочесть libc. Вопрос заключался в том, чтобы попытаться понять и узнать, где я ошибся с первым кодом, и получить более глубокое представление о внутренней механике libc в этом случае. – Phoenix87

+0

@ Phoenix87: Это хороший вопрос, меня просто раздражало ваша терминология. Но, возможно, я должен попробовать это сам, если вы скажете, что средство просмотра процесса сообщило об этом как зомби. Я просто предположил, что он не будет отображаться как зомби, если все еще будут выполняться потоки, но, возможно, это произойдет, если начальный PID завершит работу. (потоки используют одно и то же пространство нумерации, что и PID, т. е. имеют собственные PID, но getpid() возвращает PID исходного потока. Таким образом, «PID» s потоков являются фактически идентификаторами потоков. См./proc/*/task. –

+0

Также обратите внимание, что процесс не должен ждать сам. Оболочка ждет всех своих детей, поэтому процесс, который вы запускаете непосредственно из оболочки, всегда получает быстро, когда он выйдет. Вот почему это странно для создания зомби-процесса. Это не происходит для обычных однопоточных программ, когда вы запускаете их из bash. –

Смежные вопросы