bash fagyik AIX-on

 ( NevemTeve | 2013. október 17., csütörtök - 10:45 )

A komponensek:
szerver: AIX 5.2, házilag fordított bash-3.2.48
kliens: Debian linux 6, házilag fordított rxvt-2.7.10

Hibajelenség reprodukálása:
1. belépés (telnet)
2. még működik (pl kiprobáljuk a stty size parancsot -- jó)
3. felnagyítjuk az ablakot
4. már nem működik (ugyanaz a parancs, mint az előbb -- nincs válasz)

Kieg:
1. ugyanez a jelenség xterm-mel
2. mindegy, milyen parancsot futtatunk, az átméretezés után mindenképp fagyás van
3. ahol én fagyást látok, ott igazából a bash végtelen ciklusban falja a CPU-t
4. ksh használatával a hiba nem jelentkezik
5. nem fagy le viszont a következő:
belépés - gdb-indítás - maximalizálás - gdb-ből kilépés (vagy gdb helyett less vagy mc)
6. nem fagy le, ha telnet helyett ssh-val megyünk!
7. nem fagy le, ha létrehozom és exportálom a LINES és COLUMNS változókat (ssh esetén ezek 'maguktól' létrejönnek!)

első ötletek:
1. bash újrafordítása -g opcióval
2. megnézni a source-ban, mit csinál SIGWINCH esetén

20131017.1840 Írtam valamit tanulságul ide (a harmadik a 'vegyes problémák' rovatban):
http://web.axelero.hu/lzsiga/terminal.html#S0013

20130818.1615: Válasz a fejlesztőtől:
Readline-6.3 has undergone fairly extensive changes to signal handling,
and does minimal work in the signal handler itself. The real work is now
deferred until it's `safe' to do it. Bash-4.3 has the same sort of changes.

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

van egy variables.c:sh_set_lines_and_columns (lines, cols) függvény, az hívódott:

#0  sh_set_lines_and_columns (lines=32, cols=90) at variables.c:875
#1  0x100cccdc in get_new_window_size (from_sig=0, rp=0x0, cp=0x0) at winsize.c:73
#2  0x1000c2b0 in shell_getc (remove_quoted_newline=1) at ./parse.y:1882
#3  0x1001275c in read_token_word (character=115) at ./parse.y:3674
#4  0x1000e71c in read_token (command=0) at ./parse.y:2708
#5  0x1000d190 in yylex () at ./parse.y:2208
#6  0x10007d80 in yyparse () at y.tab.c:1584
#7  0x100cd684 in parse_command () at eval.c:222
#8  0x100cd818 in read_command () at eval.c:266
#9  0x100cd2c4 in reader_loop () at eval.c:132
#10 0x10001354 in main (argc=1, argv=0x2ff22640, env=0x2ff22648) at shell.c:715

Debuggerel futtatva SIGSEGV van:

#0  0x1002e974 in add_or_supercede_exported_var (
    assign=0x20081628 "_=/usr/local/bin/stty", do_alloc=0) at variables.c:3191
#1  0x1002f33c in update_export_env_inplace (env_prefix=0x100d8534 <__dbsubn+7980> "_=", 
    preflen=2, value=0x200815b8 "/usr/local/bin/stty") at variables.c:3330
#2  0x1002f3b8 in put_command_name_into_env (
    command_name=0x200815b8 "/usr/local/bin/stty") at variables.c:3338
#3  0x100252b8 in execute_disk_command (words=0x20070f68, redirects=0x0, 
    command_line=0x200770a8 "stty", pipe_in=-1, pipe_out=-1, async=0, 
    fds_to_close=0x20073158, cmdflags=0) at execute_cmd.c:3660
#4  0x10023c94 in execute_simple_command (simple_command=0x20073c48, pipe_in=-1, 
    pipe_out=-1, async=0, fds_to_close=0x20073158) at execute_cmd.c:3066
#5  0x1001d1d0 in execute_command_internal (command=0x20073138, asynchronous=0, 
    pipe_in=-1, pipe_out=-1, fds_to_close=0x20073158) at execute_cmd.c:678
#6  0x1001c5ec in execute_command (command=0x20073138) at execute_cmd.c:352
#7  0x100cd3a4 in reader_loop () at eval.c:147
#8  0x10001354 in main (argc=1, argv=0x2ff22640, env=0x2ff22648) at shell.c:715

A probléma oka, hogy a 'variables.c'::export_env felülíródott valamikor:

(gdb) print 'variables.c'::export_env   
$29 = (char **) 0x20072e98
(gdb) print (char *)'variables.c'::export_env
$30 = 0x20072e98 "#1382001761"

A szerveren a "shopt" mit mond a checkwinsize valtozo allapotara, on vagy off? Ha on, es kikapcsolod, akkor is fagyik?
--
Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant.

Alapban 'off' volt, és az átállítása sem változtatott a hibán; viszont segített észrevenni egy érdekes jelenséget: ha átméretezés után belső parancsot futtatok (pl shopt, help, set), akkor nem fagy!

egy lehetséges debuggolási menet:

dir ../readline-6.2
display 'variables.c'::export_env[0]
break sh_set_lines_and_columns
handle SIGWINCH stop

Közben megkapta a szemem egy apróság: a readline (az én hibámból) -D_THREAD_SAFE nélkül volt fordítva (ezt onnen lehet tudni, hogy _Errno függvényt kellene használjon a libc-ből, nem pedig errno változót). Valószínűleg semmi köze semmihez, de akkor már ezt is javítom.

Most nézzük mi történik SIGWINCH-nél:

#0  sh_set_lines_and_columns (lines=32, cols=90) at shell.c:130
#1  0x2003c4f8 in _rl_get_screen_size (tty=0, ignore_env=1) at terminal.c:299
#2  0x2003c878 in rl_resize_terminal () at terminal.c:353
#3  0x200305fc in rl_sigwinch_handler (sig=28) at signals.c:260

(gdb) l shell.c:sh_set_lines_and_columns
121     /* Set the environment variables LINES and COLUMNS to lines and cols,
122        respectively. */
123     void
124     sh_set_lines_and_columns (lines, cols)
125          int lines, cols;
126     {
127       char *b;
128     
129     #if defined (HAVE_SETENV)
130       b = (char *)xmalloc (INT_STRLEN_BOUND (int) + 1);
131       sprintf (b, "%d", lines);
132       setenv ("LINES", b, 1);
133       xfree (b);
134     
135       b = (char *)xmalloc (INT_STRLEN_BOUND (int) + 1);
136       sprintf (b, "%d", cols);
137       setenv ("COLUMNS", b, 1);
138       xfree (b);
139     #else /* !HAVE_SETENV */
140     #  if defined (HAVE_PUTENV)
141       b = (char *)xmalloc (INT_STRLEN_BOUND (int) + sizeof ("LINES=") + 1);
142       sprintf (b, "LINES=%d", lines);
143       putenv (b);
144     
145       b = (char *)xmalloc (INT_STRLEN_BOUND (int) + sizeof ("COLUMNS=") + 1);
146       sprintf (b, "COLUMNS=%d", cols);
147       putenv (b);
148     #  endif /* HAVE_PUTENV */
149     #endif /* !HAVE_SETENV */
150     }

Úgy látom, a HAVE_SETENV ágba megy bele. (a setenv a stdlib.h-ból jön, ha _XOPEN_SOURCE >= 600)

Most pillanatnyilag úgy tűnik, hogy a 137-nél belemegy a setenv-be, és amikor kijön belőle, az export_env már felül is van írva.

Breakpoint 3, 0xd03472d0 in setenv () from /usr/lib/libc.a(shr.o)
1: 'variables.c'::export_env[0] = 0x200d29b8 "AUTHSTATE=compat"
(gdb) s
Single stepping until exit from function setenv,
which has no line number information.
sh_set_lines_and_columns (lines=32, cols=90) at shell.c:138
138       xfree (b);
1: 'variables.c'::export_env[0] = 0x200d2fb0 ""

Itt kissé meg is akadtam... az ugyanis biztos, hogy én a libc.setenv forrását nem látom...

a setenv a putenv-et hívja, akkor romlik el a változóm

=> 0xd0347424 :     bl      0xd02e454c 
1: 'variables.c'::export_env[0] = 0x200d29b8 "AUTHSTATE=compat"
(gdb) ni

0xd0347428 in setenv () from /usr/lib/libc.a(shr.o)
2: x/i $pc
=> 0xd0347428 :     oril    r0,r0,0
1: 'variables.c'::export_env[0] = 0x200d2fb0 ""

A putenv pedig realloc-ot hív

2: x/i $pc
=> 0xd02e45b0 :     l       r3,0(r27)
1: 'variables.c'::export_env[0] = 0x200d29b8 "AUTHSTATE=compat"
(gdb) ni
 
0xd02e45b4 in putenv () from /usr/lib/libc.a(shr.o)
2: x/i $pc
=> 0xd02e45b4 :     bl      0xd01ef4d0 
1: 'variables.c'::export_env[0] = 0x200d29b8 "AUTHSTATE=compat"
(gdb) print (char *)$r3
$6 = 0x200d2e98 " \r)¸ \r)Ø \r+Ø \r,( \r,\210 \r,¸ \r,è \r1x \r1¨ \r1ø \r2( \r2h \r2\210 \r2¨ \r2Ø \r3\030 \r3h \r4\030 \r4H \r4h \r4\230 \r4è \r5\b \r58 \r5x \r5\230 \r5È \r5ø \r6( \r6h \r6\230 \r7\230 \r7È \r8\b \r8( \r,H \r8H \r8h \r8¸ \016\030x \016\030¨ \r8Ø \016\030è \016\031\b \016\031\070 \016\031x \016\031¨ \016\031Ø \016\032\b \016\032\070"...
(gdb) print $r4
$7 = 220
(gdb) ni

0xd02e45b8 in putenv () from /usr/lib/libc.a(shr.o)
2: x/i $pc
=> 0xd02e45b8 :     oril    r0,r0,0
1: 'variables.c'::export_env[0] = 0x200d2fb0 ""

Csak úgy megemlítem, hogy az info libc ilyen érdekes dolgokat mond a signal-handlerekről:

   * On most systems, `malloc' and `free' are not reentrant, because
     they use a static data structure which records what memory blocks
     are free.  As a result, no library functions that allocate or free
     memory are reentrant.  This includes functions that allocate space
     to store a result.

Végül is ilyesmit alkottam, átmenetileg jó lesz (readline-6.2/shell.c/sh_set_lines_and_columns):

/* Set the environment variables LINES and COLUMNS to lines and cols,
   respectively. */
void
sh_set_lines_and_columns (lines, cols)
     int lines, cols;
{
  char *b;

  if (getenv ("LINES") && getenv ("COLUMNS")) {
#if defined (HAVE_SETENV)
  b = (char *)xmalloc (INT_STRLEN_BOUND (int) + 1);
  sprintf (b, "%d", lines);
  setenv ("LINES", b, 1);
  xfree (b);

  b = (char *)xmalloc (INT_STRLEN_BOUND (int) + 1);
  sprintf (b, "%d", cols);
  setenv ("COLUMNS", b, 1);
  xfree (b);
#else /* !HAVE_SETENV */
#  if defined (HAVE_PUTENV)
  b = (char *)xmalloc (INT_STRLEN_BOUND (int) + sizeof ("LINES=") + 1);
  sprintf (b, "LINES=%d", lines);
  putenv (b);

  b = (char *)xmalloc (INT_STRLEN_BOUND (int) + sizeof ("COLUMNS=") + 1);
  sprintf (b, "COLUMNS=%d", cols);
  putenv (b);
#  endif /* HAVE_PUTENV */
#endif /* !HAVE_SETENV */
  }
}

Megboldolva a saját kiegészítésem.

Mondjuk az lehet, hogy én vagyok hülye, de mintha a readline-6.3 is pont azt csinálná, mint a readline-6.2:

beesik a SIGWINCH, elindul a terminal.c:rl_resize_terminal
az meghívja a terminal.c:_rl_get_screen_size-t
az pedig a shell.c:sh_set_lines_and_columns-t
aki pedig hívja a setenv-et.

Amig ígért a derék fejlesztő, hogy nem fog hívni...
Namost van valami mese, hogy ha a bash-ba belegyógyított readline-t használjuk, akkor másképp működ(het)nek a dolgok... hadd ne próbáljam ki, nekem a shared lib pont azért shared lib, hogy azt használja a bash, a gdb, a php vagy aki akarja

Szerk: olyasmit lehetne haxorizálni, hogy
1. sose baktassa a kérdés változókat
2. baktassa, de csak ha már előzőleg voltak ilyen változók (hogy ne kelljen realloc-olni az environ(7)-t) és akkor is csak putenv-et szíveskedjen használni (az nem másolja le a paraméterét, hanem közvetlenül illeszti be az environment-be; szerencsére annyit megtett a derék fejlesztő, hogy 6.3-ban statikus buffereket vett fel, a 6.2-ben használt malloc helyett)

Visszaírt. Én tévedtem: nem signal-handlerből hívja. És mégis fagyizik. Bameg.

2: x/i $pc
=> 0xd1184f20 :   lil     r5,1
1: export_env[0] = 0x2003fa78 "AUTHSTATE=compat"
(gdb) 
0xd1184f24      138       setenv ("COLUMNS", setenv_buf, 1);
2: x/i $pc
=> 0xd1184f24 :   bl      0xd1185260 
1: export_env[0] = 0x2003fa78 "AUTHSTATE=compat"
(gdb) ni
0xd1184f28      138       setenv ("COLUMNS", setenv_buf, 1);
2: x/i $pc
=> 0xd1184f28 :   l       r2,20(r1)
1: export_env[0] = 0x2004f900 " \004"

Belemegy a setenv-be, és mire kijön, az export_env el van csesződve...

Az 1) nem jo megoldas, mert ezek a valtozok rettenetesen hasznosak scriptelesnel, en pl. eleg sokszor hasznalom oket, ha valami alapszintu CLI UI-t kell osszerakni.

Nem olvastam mindent vissza, konkretan miert is baj, hogy van egy setenv(3)?
--
Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant.

A gond nagyobb, mint hittem... alapvetően másképp van a shared libek lelki világa AIX-en, mint linux-on.

Ezt persze eddig is tudtam, de most nagyon megtudtam, hogy hol lakik a Unixok istene... A libreadline.so koncepciója az, hogy ha van neki egy 'setenv' nevű extern függvénye, az majd futáskor rezolválódik valahonnan, vagy a főprogramból, vagy máshonnan, majd kialakul. Sőt, ha van neki egy saját 'xmalloc' függvénye, azt is lecserélheti a hívó azonos nevű függvénye. Vagy egy másik shared lib azonos nevű függvénye. Ahogy esik, úgy puffan.

Az előbbi feature még menne, AIX-on is van dinamikus linkelés (-brtllib opció a főprogramban, -berok a shared lib-ben), na de az utóbbi...

Most abban bízom, hogy akkor menjen a bash a beledrótozott readline-nal, és legyen öröm+boldogság, de vajon a többi readline-t használó program (gdb, pl) mit csinál?
(Pótkérdés: a 'php -a' miért nem readline-ozik nekem AIX-en, linuxon meg igen? Mindkettő házi fordítású... Na jó, ezt majd legközelebb...)