• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 /*--------------------------------------------------------------------*/
3 /*--- Startup: create initial process image on Darwin              ---*/
4 /*---                                             initimg-darwin.c ---*/
5 /*--------------------------------------------------------------------*/
6 
7 /*
8    This file is part of Valgrind, a dynamic binary instrumentation
9    framework.
10 
11    Copyright (C) 2000-2017 Julian Seward
12       jseward@acm.org
13 
14    This program is free software; you can redistribute it and/or
15    modify it under the terms of the GNU General Public License as
16    published by the Free Software Foundation; either version 2 of the
17    License, or (at your option) any later version.
18 
19    This program is distributed in the hope that it will be useful, but
20    WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22    General Public License for more details.
23 
24    You should have received a copy of the GNU General Public License
25    along with this program; if not, write to the Free Software
26    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
27    02111-1307, USA.
28 
29    The GNU General Public License is contained in the file COPYING.
30 */
31 
32 #if defined(VGO_darwin)
33 
34 #include "pub_core_basics.h"
35 #include "pub_core_vki.h"
36 #include "pub_core_debuglog.h"
37 #include "pub_core_libcbase.h"
38 #include "pub_core_libcassert.h"
39 #include "pub_core_libcfile.h"
40 #include "pub_core_libcproc.h"
41 #include "pub_core_libcprint.h"
42 #include "pub_core_xarray.h"
43 #include "pub_core_clientstate.h"
44 #include "pub_core_aspacemgr.h"
45 #include "pub_core_mallocfree.h"
46 #include "pub_core_machine.h"
47 #include "pub_core_ume.h"
48 #include "pub_core_options.h"
49 #include "pub_core_tooliface.h"       /* VG_TRACK */
50 #include "pub_core_threadstate.h"     /* ThreadArchState */
51 #include "priv_initimg_pathscan.h"
52 #include "pub_core_initimg.h"         /* self */
53 
54 
55 /*====================================================================*/
56 /*=== Loading the client                                           ===*/
57 /*====================================================================*/
58 
59 /* Load the client whose name is VG_(argv_the_exename). */
60 
load_client(ExeInfo * info,Addr * client_ip)61 static void load_client ( /*OUT*/ExeInfo* info,
62                           /*OUT*/Addr*    client_ip)
63 {
64    const HChar* exe_name;
65    Int    ret;
66    SysRes res;
67 
68    vg_assert( VG_(args_the_exename) != NULL);
69    exe_name = ML_(find_executable)( VG_(args_the_exename) );
70 
71    if (!exe_name) {
72       VG_(printf)("valgrind: %s: command not found\n", VG_(args_the_exename));
73       VG_(exit)(127);      // 127 is Posix NOTFOUND
74    }
75 
76    VG_(memset)(info, 0, sizeof(*info));
77    ret = VG_(do_exec)(exe_name, info);
78 
79    // The client was successfully loaded!  Continue.
80 
81    /* Get hold of a file descriptor which refers to the client
82       executable.  This is needed for attaching to GDB. */
83    res = VG_(open)(exe_name, VKI_O_RDONLY, VKI_S_IRUSR);
84    if (!sr_isError(res))
85       VG_(cl_exec_fd) = sr_Res(res);
86 
87    /* Copy necessary bits of 'info' that were filled in */
88    *client_ip  = info->init_ip;
89 }
90 
91 
92 /*====================================================================*/
93 /*=== Setting up the client's environment                          ===*/
94 /*====================================================================*/
95 
96 /* Prepare the client's environment.  This is basically a copy of our
97    environment, except:
98 
99      DYLD_INSERT_LIBRARIES=$VALGRIND_LIB/vgpreload_core-PLATFORM.so:
100                 ($VALGRIND_LIB/vgpreload_TOOL-PLATFORM.so:)?
101                 DYLD_INSERT_LIBRARIES
102 
103    If this is missing, then it is added.
104 
105    Also, remove any binding for VALGRIND_LAUNCHER=.  The client should
106    not be able to see this.
107 
108    Also, add DYLD_SHARED_REGION=avoid, because V doesn't know how
109    to process the dyld shared cache file.
110 
111    Also, change VYLD_* (mangled by launcher) back to DYLD_*.
112 
113    If this needs to handle any more variables it should be hacked
114    into something table driven.  The copy is VG_(malloc)'d space.
115 */
setup_client_env(HChar ** origenv,const HChar * toolname)116 static HChar** setup_client_env ( HChar** origenv, const HChar* toolname)
117 {
118    const HChar* preload_core    = "vgpreload_core";
119    const HChar* ld_preload      = "DYLD_INSERT_LIBRARIES=";
120    const HChar* dyld_cache      = "DYLD_SHARED_REGION=";
121    const HChar* dyld_cache_value= "avoid";
122    const HChar* v_launcher      = VALGRIND_LAUNCHER "=";
123    Int    ld_preload_len  = VG_(strlen)( ld_preload );
124    Int    dyld_cache_len  = VG_(strlen)( dyld_cache );
125    Int    v_launcher_len  = VG_(strlen)( v_launcher );
126    Bool   ld_preload_done = False;
127    Bool   dyld_cache_done = False;
128    Int    vglib_len       = VG_(strlen)(VG_(libdir));
129 
130    HChar** cpp;
131    HChar** ret;
132    HChar*  preload_tool_path;
133    Int     envc, i;
134 
135    /* Alloc space for the vgpreload_core.so path and vgpreload_<tool>.so
136       paths.  We might not need the space for vgpreload_<tool>.so, but it
137       doesn't hurt to over-allocate briefly.  The 16s are just cautious
138       slop. */
139    Int preload_core_path_len = vglib_len + sizeof(preload_core)
140                                          + sizeof(VG_PLATFORM) + 16;
141    Int preload_tool_path_len = vglib_len + VG_(strlen)(toolname)
142                                          + sizeof(VG_PLATFORM) + 16;
143    Int preload_string_len    = preload_core_path_len + preload_tool_path_len;
144    HChar* preload_string     = VG_(malloc)("initimg-darwin.sce.1", preload_string_len);
145 
146    /* Determine if there's a vgpreload_<tool>_<platform>.so file, and setup
147       preload_string. */
148    preload_tool_path = VG_(malloc)("initimg-darwin.sce.2", preload_tool_path_len);
149    VG_(snprintf)(preload_tool_path, preload_tool_path_len,
150                  "%s/vgpreload_%s-%s.so", VG_(libdir), toolname, VG_PLATFORM);
151    if (VG_(access)(preload_tool_path, True/*r*/, False/*w*/, False/*x*/) == 0) {
152       VG_(snprintf)(preload_string, preload_string_len, "%s/%s-%s.so:%s",
153                     VG_(libdir), preload_core, VG_PLATFORM, preload_tool_path);
154    } else {
155       VG_(snprintf)(preload_string, preload_string_len, "%s/%s-%s.so",
156                     VG_(libdir), preload_core, VG_PLATFORM);
157    }
158    VG_(free)(preload_tool_path);
159 
160    VG_(debugLog)(2, "initimg", "preload_string:\n");
161    VG_(debugLog)(2, "initimg", "  \"%s\"\n", preload_string);
162 
163    /* Count the original size of the env */
164    envc = 0;
165    for (cpp = origenv; cpp && *cpp; cpp++)
166       envc++;
167 
168    /* Allocate a new space */
169    ret = VG_(malloc) ("initimg-darwin.sce.3",
170                       sizeof(HChar *) * (envc+2+1)); /* 2 new entries + NULL */
171 
172    /* copy it over */
173    for (cpp = ret; *origenv; )
174       *cpp++ = *origenv++;
175    *cpp = NULL;
176 
177    vg_assert(envc == (cpp - ret));
178 
179    /* Walk over the new environment, mashing as we go */
180    for (cpp = ret; cpp && *cpp; cpp++) {
181       if (VG_(memcmp)(*cpp, ld_preload, ld_preload_len) == 0) {
182          Int len = VG_(strlen)(*cpp) + preload_string_len;
183          HChar *cp = VG_(malloc)("initimg-darwin.sce.4", len);
184 
185          VG_(snprintf)(cp, len, "%s%s:%s",
186                        ld_preload, preload_string, (*cpp)+ld_preload_len);
187 
188          *cpp = cp;
189 
190          ld_preload_done = True;
191       }
192       if (VG_(memcmp)(*cpp, dyld_cache, dyld_cache_len) == 0) {
193          Int len = dyld_cache_len + VG_(strlen)(dyld_cache_value) + 1;
194          HChar *cp = VG_(malloc)("initimg-darwin.sce.4.2", len);
195 
196          VG_(snprintf)(cp, len, "%s%s", dyld_cache, dyld_cache_value);
197 
198          *cpp = cp;
199 
200          ld_preload_done = True;
201       }
202    }
203 
204    /* Add the missing bits */
205    if (!ld_preload_done) {
206       Int len = ld_preload_len + preload_string_len;
207       HChar *cp = VG_(malloc) ("initimg-darwin.sce.5", len);
208 
209       VG_(snprintf)(cp, len, "%s%s", ld_preload, preload_string);
210 
211       ret[envc++] = cp;
212    }
213    if (!dyld_cache_done) {
214       Int len = dyld_cache_len + VG_(strlen)(dyld_cache_value) + 1;
215       HChar *cp = VG_(malloc) ("initimg-darwin.sce.5.2", len);
216 
217       VG_(snprintf)(cp, len, "%s%s", dyld_cache, dyld_cache_value);
218 
219       ret[envc++] = cp;
220    }
221 
222 
223    /* ret[0 .. envc-1] is live now. */
224    /* Find and remove a binding for VALGRIND_LAUNCHER. */
225    for (i = 0; i < envc; i++)
226       if (0 == VG_(memcmp)(ret[i], v_launcher, v_launcher_len))
227          break;
228 
229    if (i < envc) {
230       for (; i < envc-1; i++)
231          ret[i] = ret[i+1];
232       envc--;
233    }
234 
235    /* Change VYLD_ to DYLD */
236    for (i = 0; i < envc; i++) {
237       if (0 == VG_(strncmp)(ret[i], "VYLD_", 5)) {
238          ret[i][0] = 'D';
239       }
240    }
241 
242 
243    VG_(free)(preload_string);
244    ret[envc] = NULL;
245    return ret;
246 }
247 
248 
249 /*====================================================================*/
250 /*=== Setting up the client's stack                                ===*/
251 /*====================================================================*/
252 
253 /* Add a string onto the string table, and return its address */
copy_str(HChar ** tab,const HChar * str)254 static HChar *copy_str(HChar **tab, const HChar *str)
255 {
256    HChar *cp = *tab;
257    HChar *orig = cp;
258 
259    while(*str)
260       *cp++ = *str++;
261    *cp++ = '\0';
262 
263    if (0)
264       VG_(printf)("copied %p \"%s\" len %lld\n", orig, orig, (Long)(cp-orig));
265 
266    *tab = cp;
267 
268    return orig;
269 }
270 
271 
272 /* ----------------------------------------------------------------
273 
274    This sets up the client's initial stack, containing the args,
275    environment and aux vector.
276 
277    The format of the stack on Darwin is:
278 
279    higher address +-----------------+ <- clstack_end
280                   |                 |
281                   : string table    :
282                   |                 |
283                   +-----------------+
284                   | NULL            |
285                   +-----------------+
286                   | executable_path | (first arg to execve())
287                   +-----------------+
288                   | NULL            |
289                   -                 -
290                   | envp            |
291                   +-----------------+
292                   | NULL            |
293                   -                 -
294                   | argv            |
295                   +-----------------+
296                   | argc            |
297                   +-----------------+
298                   | mach_header *   | (dynamic only)
299    lower address  +-----------------+ <- sp
300                   | undefined       |
301                   :                 :
302 
303    Allocate and create the initial client stack.  It is allocated down
304    from clstack_end, which was previously determined by the address
305    space manager.  The returned value is the SP value for the client.
306 
307    ---------------------------------------------------------------- */
308 
309 static
setup_client_stack(void * init_sp,HChar ** orig_envp,const ExeInfo * info,Addr clstack_end,SizeT clstack_max_size,const VexArchInfo * vex_archinfo)310 Addr setup_client_stack( void*  init_sp,
311                          HChar** orig_envp,
312                          const ExeInfo* info,
313                          Addr   clstack_end,
314                          SizeT  clstack_max_size,
315                          const VexArchInfo* vex_archinfo )
316 {
317    HChar **cpp;
318    HChar *strtab;		/* string table */
319    HChar *stringbase;
320    Addr *ptr;
321    unsigned stringsize;		/* total size of strings in bytes */
322    unsigned auxsize;		/* total size of auxv in bytes */
323    Int argc;			/* total argc */
324    Int envc;			/* total number of env vars */
325    unsigned stacksize;		/* total client stack size */
326    Addr client_SP;	        /* client stack base (initial SP) */
327    Addr clstack_start;
328    Int i;
329 
330    vg_assert(VG_IS_PAGE_ALIGNED(clstack_end+1));
331    vg_assert( VG_(args_for_client) );
332 
333    /* ==================== compute sizes ==================== */
334 
335    /* first of all, work out how big the client stack will be */
336    stringsize   = 0;
337    auxsize = 0;
338 
339    /* paste on the extra args if the loader needs them (ie, the #!
340       interpreter and its argument) */
341    argc = 0;
342    if (info->interp_name != NULL) {
343       argc++;
344       stringsize += VG_(strlen)(info->interp_name) + 1;
345    }
346    if (info->interp_args != NULL) {
347       argc++;
348       stringsize += VG_(strlen)(info->interp_args) + 1;
349    }
350 
351    /* now scan the args we're given... */
352    stringsize += VG_(strlen)( VG_(args_the_exename) ) + 1;
353 
354    for (i = 0; i < VG_(sizeXA)( VG_(args_for_client) ); i++) {
355       argc++;
356       stringsize += VG_(strlen)( * (HChar**)
357                                    VG_(indexXA)( VG_(args_for_client), i ))
358                     + 1;
359    }
360 
361    /* ...and the environment */
362    envc = 0;
363    for (cpp = orig_envp; cpp && *cpp; cpp++) {
364       envc++;
365       stringsize += VG_(strlen)(*cpp) + 1;
366    }
367 
368    /* Darwin executable_path + NULL */
369    auxsize += 2 * sizeof(Word);
370    if (info->executable_path) {
371        stringsize += 1 + VG_(strlen)(info->executable_path);
372    }
373 
374    /* Darwin mach_header */
375    if (info->dynamic) auxsize += sizeof(Word);
376 
377    /* OK, now we know how big the client stack is */
378    stacksize =
379       sizeof(Word) +                          /* argc */
380       sizeof(HChar **) +                      /* argc[0] == exename */
381       sizeof(HChar **)*argc +                 /* argv */
382       sizeof(HChar **) +                      /* terminal NULL */
383       sizeof(HChar **)*envc +                 /* envp */
384       sizeof(HChar **) +                      /* terminal NULL */
385       auxsize +                               /* auxv */
386       VG_ROUNDUP(stringsize, sizeof(Word));   /* strings (aligned) */
387 
388    if (0) VG_(printf)("stacksize = %d\n", stacksize);
389 
390    /* client_SP is the client's stack pointer */
391    client_SP = clstack_end + 1 - stacksize;
392    client_SP = VG_ROUNDDN(client_SP, 32); /* make stack 32 byte aligned */
393 
394    /* base of the string table (aligned) */
395    stringbase = strtab = (HChar *)clstack_end
396                          - VG_ROUNDUP(stringsize, sizeof(int));
397 
398    /* The max stack size */
399    clstack_max_size = VG_PGROUNDUP(clstack_max_size);
400 
401    /* Darwin stack is chosen by the ume loader */
402    clstack_start = clstack_end + 1 - clstack_max_size;
403 
404    /* Record stack extent -- needed for stack-change code. */
405    /* GrP fixme really? */
406    VG_(clstk_start_base) = clstack_start;
407    VG_(clstk_end)  = clstack_end;
408 
409    if (0)
410       VG_(printf)("stringsize=%d auxsize=%d stacksize=%d maxsize=0x%x\n"
411                   "clstack_start %p\n"
412                   "clstack_end   %p\n",
413 	          stringsize, auxsize, stacksize, (Int)clstack_max_size,
414                   (void*)clstack_start, (void*)clstack_end);
415 
416    /* ==================== allocate space ==================== */
417 
418    /* Stack was allocated by the ume loader. */
419 
420    /* ==================== create client stack ==================== */
421 
422    ptr = (Addr*)client_SP;
423 
424    /* --- mach_header --- */
425    if (info->dynamic) *ptr++ = info->text;
426 
427    /* --- client argc --- */
428    *ptr++ = (Addr)(argc + 1);
429 
430    /* --- client argv --- */
431    if (info->interp_name)
432       *ptr++ = (Addr)copy_str(&strtab, info->interp_name);
433    if (info->interp_args)
434       *ptr++ = (Addr)copy_str(&strtab, info->interp_args);
435 
436    *ptr++ = (Addr)copy_str(&strtab, VG_(args_the_exename));
437 
438    for (i = 0; i < VG_(sizeXA)( VG_(args_for_client) ); i++) {
439       *ptr++ = (Addr)copy_str(
440                        &strtab,
441                        * (HChar**) VG_(indexXA)( VG_(args_for_client), i )
442                      );
443    }
444    *ptr++ = 0;
445 
446    /* --- envp --- */
447    VG_(client_envp) = (HChar **)ptr;
448    for (cpp = orig_envp; cpp && *cpp; ptr++, cpp++)
449       *ptr = (Addr)copy_str(&strtab, *cpp);
450    *ptr++ = 0;
451 
452    /* --- executable_path + NULL --- */
453    if (info->executable_path)
454        *ptr++ = (Addr)copy_str(&strtab, info->executable_path);
455    else
456        *ptr++ = 0;
457    *ptr++ = 0;
458 
459    vg_assert((strtab-stringbase) == stringsize);
460 
461    /* client_SP is pointing at client's argc/argv */
462 
463    if (0) VG_(printf)("startup SP = %#lx\n", client_SP);
464    return client_SP;
465 }
466 
467 
468 /*====================================================================*/
469 /*=== Record system memory regions                                 ===*/
470 /*====================================================================*/
471 
record_system_memory(void)472 static void record_system_memory(void)
473 {
474   /* JRS 2014-Jul-08: this messes up the sync checker, because the
475      information that the kernel gives us doesn't include anything
476      about the commpage mapping.  This functionality has therefore
477      been moved to m_main.c, valgrind_main(), section "Tell the tool
478      about the initial client memory permissions".  See comments there
479      for rationale. */
480    return;
481    /*NOTREACHED*/
482 
483    /* Tell aspacem where the client's kernel commpage is */
484 #if defined(VGA_amd64)
485    /* commpage 0x7fff:ffe00000+ - not in vm_region */
486    // GrP fixme check again
487    VG_(am_notify_client_mmap)(0x7fffffe00000, 0x7ffffffff000-0x7fffffe00000,
488                               VKI_PROT_READ|VKI_PROT_EXEC, 0, -1, 0);
489 
490 #elif defined(VGA_x86)
491    /* commpage 0xfffec000+ - not in vm_region */
492    // GrP fixme check again
493    VG_(am_notify_client_mmap)(0xfffec000, 0xfffff000-0xfffec000,
494                               VKI_PROT_READ|VKI_PROT_EXEC, 0, -1, 0);
495 
496 #else
497 #  error unknown architecture
498 #endif
499 }
500 
501 
502 /*====================================================================*/
503 /*=== TOP-LEVEL: VG_(ii_create_image)                              ===*/
504 /*====================================================================*/
505 
506 /* Create the client's initial memory image. */
VG_(ii_create_image)507 IIFinaliseImageInfo VG_(ii_create_image)( IICreateImageInfo iicii,
508                                           const VexArchInfo* vex_archinfo )
509 {
510    ExeInfo info;
511    VG_(memset)( &info, 0, sizeof(info) );
512 
513    HChar** env = NULL;
514 
515    IIFinaliseImageInfo iifii;
516    VG_(memset)( &iifii, 0, sizeof(iifii) );
517 
518    //--------------------------------------------------------------
519    // Load client executable, finding in $PATH if necessary
520    //   p: get_helprequest_and_toolname()  [for 'exec', 'need_help']
521    //   p: layout_remaining_space          [so there's space]
522    //--------------------------------------------------------------
523    VG_(debugLog)(1, "initimg", "Loading client\n");
524 
525    if (VG_(args_the_exename) == NULL)
526       VG_(err_missing_prog)();
527 
528    load_client(&info, &iifii.initial_client_IP);
529 
530    //--------------------------------------------------------------
531    // Set up client's environment
532    //   p: set-libdir                   [for VG_(libdir)]
533    //   p: get_helprequest_and_toolname [for toolname]
534    //--------------------------------------------------------------
535    VG_(debugLog)(1, "initimg", "Setup client env\n");
536    env = setup_client_env(iicii.envp, iicii.toolname);
537 
538    //--------------------------------------------------------------
539    // Setup client stack, eip, and VG_(client_arg[cv])
540    //   p: load_client()     [for 'info']
541    //   p: fix_environment() [for 'env']
542    //--------------------------------------------------------------
543    iicii.clstack_end = info.stack_end;
544    iifii.clstack_max_size = info.stack_end - info.stack_start + 1;
545 
546    iifii.initial_client_SP =
547        setup_client_stack( iicii.argv - 1, env, &info,
548                            iicii.clstack_end, iifii.clstack_max_size,
549                            vex_archinfo );
550 
551    VG_(free)(env);
552 
553    VG_(debugLog)(2, "initimg",
554                  "Client info: "
555                  "initial_IP=%p initial_SP=%p stack=[%p..%p]\n",
556                  (void*)(iifii.initial_client_IP),
557                  (void*)(iifii.initial_client_SP),
558                  (void*)(info.stack_start),
559                  (void*)(info.stack_end));
560 
561 
562    // Tell aspacem about commpage, etc
563    record_system_memory();
564 
565    VG_(free)(info.interp_name); info.interp_name = NULL;
566    VG_(free)(info.interp_args); info.interp_args = NULL;
567    return iifii;
568 }
569 
570 
571 /*====================================================================*/
572 /*=== TOP-LEVEL: VG_(ii_finalise_image)                            ===*/
573 /*====================================================================*/
574 
575 /* Just before starting the client, we may need to make final
576    adjustments to its initial image.  Also we need to set up the VEX
577    guest state for thread 1 (the root thread) and copy in essential
578    starting values.  This is handed the IIFinaliseImageInfo created by
579    VG_(ii_create_image).
580 */
VG_(ii_finalise_image)581 void VG_(ii_finalise_image)( IIFinaliseImageInfo iifii )
582 {
583    ThreadArchState* arch = &VG_(threads)[1].arch;
584 
585    /* GrP fixme doesn't handle all registers from LC_THREAD or LC_UNIXTHREAD */
586 
587 #  if defined(VGP_x86_darwin)
588    vg_assert(0 == sizeof(VexGuestX86State) % 16);
589 
590    /* Zero out the initial state, and set up the simulated FPU in a
591       sane way. */
592    LibVEX_GuestX86_initialise(&arch->vex);
593 
594    /* Zero out the shadow areas. */
595    VG_(memset)(&arch->vex_shadow1, 0, sizeof(VexGuestX86State));
596    VG_(memset)(&arch->vex_shadow2, 0, sizeof(VexGuestX86State));
597 
598    /* Put essential stuff into the new state. */
599    arch->vex.guest_ESP = iifii.initial_client_SP;
600    arch->vex.guest_EIP = iifii.initial_client_IP;
601 
602 #  elif defined(VGP_amd64_darwin)
603    vg_assert(0 == sizeof(VexGuestAMD64State) % 16);
604 
605    /* Zero out the initial state, and set up the simulated FPU in a
606       sane way. */
607    LibVEX_GuestAMD64_initialise(&arch->vex);
608 
609    /* Zero out the shadow areas. */
610    VG_(memset)(&arch->vex_shadow1, 0, sizeof(VexGuestAMD64State));
611    VG_(memset)(&arch->vex_shadow2, 0, sizeof(VexGuestAMD64State));
612 
613    /* Put essential stuff into the new state. */
614    arch->vex.guest_RSP = iifii.initial_client_SP;
615    arch->vex.guest_RIP = iifii.initial_client_IP;
616 
617 #  else
618 #    error Unknown platform
619 #  endif
620 
621    /* Tell the tool that we just wrote to the registers. */
622    VG_TRACK( post_reg_write, Vg_CoreStartup, /*tid*/1, /*offset*/0,
623              sizeof(VexGuestArchState));
624 }
625 
626 #endif // defined(VGO_darwin)
627 
628 /*--------------------------------------------------------------------*/
629 /*--- end                                                          ---*/
630 /*--------------------------------------------------------------------*/
631