Home | History | Annotate | Download | only in exp-bbv
      1 /* -*- mode: C; c-basic-offset: 3; -*- */
      2 
      3 //--------------------------------------------------------------------*/
      4 //--- BBV: a SimPoint basic block vector generator      bbv_main.c ---*/
      5 //--------------------------------------------------------------------*/
      6 
      7 /*
      8    This file is part of BBV, a Valgrind tool for generating SimPoint
      9    basic block vectors.
     10 
     11    Copyright (C) 2006-2015 Vince Weaver
     12       vince _at_ csl.cornell.edu
     13 
     14    pcfile code is Copyright (C) 2006-2015 Oriol Prat
     15       oriol.prat _at _ bsc.es
     16 
     17    This program is free software; you can redistribute it and/or
     18    modify it under the terms of the GNU General Public License as
     19    published by the Free Software Foundation; either version 2 of the
     20    License, or (at your option) any later version.
     21 
     22    This program is distributed in the hope that it will be useful, but
     23    WITHOUT ANY WARRANTY; without even the implied warranty of
     24    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     25    General Public License for more details.
     26 
     27    You should have received a copy of the GNU General Public License
     28    along with this program; if not, write to the Free Software
     29    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
     30    02111-1307, USA.
     31 
     32    The GNU General Public License is contained in the file COPYING.
     33 */
     34 
     35 
     36 #include "pub_tool_basics.h"
     37 #include "pub_tool_tooliface.h"
     38 #include "pub_tool_options.h"    /* command line options */
     39 
     40 #include "pub_tool_vki.h"        /* VKI_O_CREAT */
     41 #include "pub_tool_libcbase.h"   /* VG_(strlen) */
     42 #include "pub_tool_libcprint.h"  /* VG_(printf) */
     43 #include "pub_tool_libcassert.h" /* VG_(exit) */
     44 #include "pub_tool_mallocfree.h" /* VG_(malloc) */
     45 #include "pub_tool_machine.h"    /* VG_(fnptr_to_fnentry) */
     46 #include "pub_tool_debuginfo.h"  /* VG_(get_fnname) */
     47 
     48 #include "pub_tool_oset.h"       /* ordered set stuff */
     49 
     50    /* instruction special cases */
     51 #define REP_INSTRUCTION   0x1
     52 #define FLDCW_INSTRUCTION 0x2
     53 
     54    /* interval variables */
     55 #define DEFAULT_GRAIN_SIZE 100000000  /* 100 million by default */
     56 static Int interval_size=DEFAULT_GRAIN_SIZE;
     57 
     58    /* filenames */
     59 static const HChar *clo_bb_out_file="bb.out.%p";
     60 static const HChar *clo_pc_out_file="pc.out.%p";
     61 static HChar *pc_out_file=NULL;
     62 static HChar *bb_out_file=NULL;
     63 
     64 
     65    /* output parameters */
     66 static Bool instr_count_only=False;
     67 static Bool generate_pc_file=False;
     68 
     69    /* Global values */
     70 static OSet* instr_info_table;  /* table that holds the basic block info */
     71 static Int block_num=1;         /* global next block number */
     72 static Int current_thread=0;
     73 static Int allocated_threads=1;
     74 struct thread_info *bbv_thread=NULL;
     75 
     76    /* Per-thread variables */
     77 struct thread_info {
     78    ULong dyn_instr;         /* Current retired instruction count */
     79    ULong total_instr;       /* Total retired instruction count   */
     80    Addr last_rep_addr;      /* rep counting values */
     81    ULong rep_count;
     82    ULong global_rep_count;
     83    ULong unique_rep_count;
     84    ULong fldcw_count;       /* fldcw count */
     85    VgFile *bbtrace_fp;      /* file pointer */
     86 };
     87 
     88 struct BB_info {
     89    Addr       BB_addr;           /* used as key, must be first           */
     90    Int        n_instrs;          /* instructions in the basic block      */
     91    Int        block_num;         /* unique block identifier              */
     92    Int        *inst_counter;     /* times entered * num_instructions     */
     93    Bool       is_entry;          /* is this block a function entry point */
     94    const HChar *fn_name;         /* Function block is in                 */
     95 };
     96 
     97 
     98    /* dump the optional PC file, which contains basic block number to */
     99    /*   instruction address and function name mappings                */
    100 static void dumpPcFile(void)
    101 {
    102    struct BB_info   *bb_elem;
    103    VgFile *fp;
    104 
    105    pc_out_file =
    106           VG_(expand_file_name)("--pc-out-file", clo_pc_out_file);
    107 
    108    fp = VG_(fopen)(pc_out_file, VKI_O_CREAT|VKI_O_TRUNC|VKI_O_WRONLY,
    109                    VKI_S_IRUSR|VKI_S_IWUSR|VKI_S_IRGRP|VKI_S_IWGRP);
    110    if (fp == NULL) {
    111       VG_(umsg)("Error: cannot create pc file %s\n", pc_out_file);
    112       VG_(exit)(1);
    113    }
    114 
    115       /* Loop through the table, printing the number, address, */
    116       /*    and function name for each basic block             */
    117    VG_(OSetGen_ResetIter)(instr_info_table);
    118    while ( (bb_elem = VG_(OSetGen_Next)(instr_info_table)) ) {
    119       VG_(fprintf)( fp, "F:%d:%lx:%s\n", bb_elem->block_num,
    120                     bb_elem->BB_addr, bb_elem->fn_name);
    121    }
    122 
    123    VG_(fclose)(fp);
    124 }
    125 
    126 static VgFile *open_tracefile(Int thread_num)
    127 {
    128    VgFile *fp;
    129    // Allocate a buffer large enough for the general case "%s.%d" below
    130    HChar temp_string[VG_(strlen)(bb_out_file) + 1 + 10 + 1];
    131 
    132       /* For thread 1, don't append any thread number  */
    133       /* This lets the single-thread case not have any */
    134       /* extra values appended to the file name.       */
    135    if (thread_num==1) {
    136       VG_(strcpy)(temp_string, bb_out_file);
    137    }
    138    else {
    139       VG_(sprintf)(temp_string,"%s.%d",bb_out_file,thread_num);
    140    }
    141 
    142    fp = VG_(fopen)(temp_string, VKI_O_CREAT|VKI_O_TRUNC|VKI_O_WRONLY,
    143                    VKI_S_IRUSR|VKI_S_IWUSR|VKI_S_IRGRP|VKI_S_IWGRP);
    144 
    145    if (fp == NULL) {
    146       VG_(umsg)("Error: cannot create bb file %s\n",temp_string);
    147       VG_(exit)(1);
    148    }
    149 
    150    return fp;
    151 }
    152 
    153 static void handle_overflow(void)
    154 {
    155    struct BB_info *bb_elem;
    156 
    157    if (bbv_thread[current_thread].dyn_instr > interval_size) {
    158 
    159       if (!instr_count_only) {
    160 
    161             /* If our output file hasn't been opened, open it */
    162          if (bbv_thread[current_thread].bbtrace_fp == NULL) {
    163             bbv_thread[current_thread].bbtrace_fp=open_tracefile(current_thread);
    164          }
    165 
    166            /* put an entry to the bb.out file */
    167 
    168          VG_(fprintf)(bbv_thread[current_thread].bbtrace_fp, "T");
    169 
    170          VG_(OSetGen_ResetIter)(instr_info_table);
    171          while ( (bb_elem = VG_(OSetGen_Next)(instr_info_table)) ) {
    172             if ( bb_elem->inst_counter[current_thread] != 0 ) {
    173                VG_(fprintf)(bbv_thread[current_thread].bbtrace_fp, ":%d:%d   ",
    174                             bb_elem->block_num,
    175                             bb_elem->inst_counter[current_thread]);
    176                bb_elem->inst_counter[current_thread] = 0;
    177             }
    178          }
    179 
    180          VG_(fprintf)(bbv_thread[current_thread].bbtrace_fp, "\n");
    181       }
    182 
    183       bbv_thread[current_thread].dyn_instr -= interval_size;
    184    }
    185 }
    186 
    187 
    188 static void close_out_reps(void)
    189 {
    190    bbv_thread[current_thread].global_rep_count+=bbv_thread[current_thread].rep_count;
    191    bbv_thread[current_thread].unique_rep_count++;
    192    bbv_thread[current_thread].rep_count=0;
    193 }
    194 
    195    /* Generic function to get called each instruction */
    196 static VG_REGPARM(1) void per_instruction_BBV(struct BB_info *bbInfo)
    197 {
    198    Int n_instrs=1;
    199 
    200    tl_assert(bbInfo);
    201 
    202       /* we finished rep but didn't clear out count */
    203    if (bbv_thread[current_thread].rep_count) {
    204       n_instrs++;
    205       close_out_reps();
    206    }
    207 
    208    bbInfo->inst_counter[current_thread]+=n_instrs;
    209 
    210    bbv_thread[current_thread].total_instr+=n_instrs;
    211    bbv_thread[current_thread].dyn_instr +=n_instrs;
    212 
    213    handle_overflow();
    214 }
    215 
    216    /* Function to get called if instruction has a rep prefix */
    217 static VG_REGPARM(1) void per_instruction_BBV_rep(Addr addr)
    218 {
    219       /* handle back-to-back rep instructions */
    220    if (bbv_thread[current_thread].last_rep_addr!=addr) {
    221       if (bbv_thread[current_thread].rep_count) {
    222          close_out_reps();
    223          bbv_thread[current_thread].total_instr++;
    224          bbv_thread[current_thread].dyn_instr++;
    225       }
    226       bbv_thread[current_thread].last_rep_addr=addr;
    227    }
    228 
    229    bbv_thread[current_thread].rep_count++;
    230 
    231 }
    232 
    233    /* Function to call if our instruction has a fldcw instruction */
    234 static VG_REGPARM(1) void per_instruction_BBV_fldcw(struct BB_info *bbInfo)
    235 {
    236    Int n_instrs=1;
    237 
    238    tl_assert(bbInfo);
    239 
    240       /* we finished rep but didn't clear out count */
    241    if (bbv_thread[current_thread].rep_count) {
    242       n_instrs++;
    243       close_out_reps();
    244    }
    245 
    246       /* count fldcw instructions */
    247    bbv_thread[current_thread].fldcw_count++;
    248 
    249    bbInfo->inst_counter[current_thread]+=n_instrs;
    250 
    251    bbv_thread[current_thread].total_instr+=n_instrs;
    252    bbv_thread[current_thread].dyn_instr +=n_instrs;
    253 
    254    handle_overflow();
    255 }
    256 
    257    /* Check if the instruction pointed to is one that needs */
    258    /*   special handling.  If so, set a bit in the return   */
    259    /*   value indicating what type.                         */
    260 static Int get_inst_type(UInt len, Addr addr)
    261 {
    262    int result=0;
    263 
    264 #if defined(VGA_x86) || defined(VGA_amd64)
    265 
    266    UChar *inst_pointer;
    267    UChar  inst_byte;
    268    int i,possible_rep;
    269 
    270    /* rep prefixed instructions are counted as one instruction on */
    271    /*     x86 processors and must be handled as a special case    */
    272 
    273    /* Also, the rep prefix is re-used as part of the opcode for   */
    274    /*     SSE instructions.  So we need to specifically check for */
    275    /*     the following: movs, cmps, scas, lods, stos, ins, outs  */
    276 
    277    inst_pointer=(UChar *)addr;
    278    i=0;
    279    inst_byte=0;
    280    possible_rep=0;
    281 
    282    while (i<len) {
    283 
    284       inst_byte=*inst_pointer;
    285 
    286       if ( (inst_byte == 0x67) ||            /* size override prefix */
    287            (inst_byte == 0x66) ||            /* size override prefix */
    288            (inst_byte == 0x48) ) {           /* 64-bit prefix */
    289       } else if ( (inst_byte == 0xf2) ||     /* rep prefix    */
    290                   (inst_byte == 0xf3) ) {    /* repne prefix  */
    291          possible_rep=1;
    292       } else {
    293          break;                              /* other byte, exit */
    294       }
    295 
    296       i++;
    297       inst_pointer++;
    298    }
    299 
    300    if ( possible_rep &&
    301         ( ( (inst_byte >= 0xa4) &&     /* movs,cmps,scas */
    302             (inst_byte <= 0xaf) ) ||   /* lods,stos      */
    303           ( (inst_byte >= 0x6c) &&
    304             (inst_byte <= 0x6f) ) ) ) {  /* ins,outs       */
    305 
    306       result|=REP_INSTRUCTION;
    307    }
    308 
    309    /* fldcw instructions are double-counted by the hardware       */
    310    /*     performance counters on pentium 4 processors so it is   */
    311    /*     useful to have that count when doing validation work.   */
    312 
    313    inst_pointer=(UChar *)addr;
    314    if (len>1) {
    315          /* FLDCW detection */
    316          /* opcode is 0xd9/5, ie 1101 1001 oo10 1mmm */
    317       if ((*inst_pointer==0xd9) &&
    318           (*(inst_pointer+1)<0xb0) &&  /* need this case of fldz, etc, count */
    319           ( (*(inst_pointer+1) & 0x38) == 0x28)) {
    320          result|=FLDCW_INSTRUCTION;
    321       }
    322    }
    323 
    324 #endif
    325    return result;
    326 }
    327 
    328 
    329 
    330    /* Our instrumentation function       */
    331    /*    sbIn = super block to translate */
    332    /*    layout = guest layout           */
    333    /*    gWordTy = size of guest word    */
    334    /*    hWordTy = size of host word     */
    335 static IRSB* bbv_instrument ( VgCallbackClosure* closure,
    336                               IRSB* sbIn, const VexGuestLayout* layout,
    337                               const VexGuestExtents* vge,
    338                               const VexArchInfo* archinfo_host,
    339                               IRType gWordTy, IRType hWordTy )
    340 {
    341    Int      i,n_instrs=1;
    342    IRSB     *sbOut;
    343    IRStmt   *st;
    344    struct BB_info  *bbInfo;
    345    Addr     origAddr,ourAddr;
    346    IRDirty  *di;
    347    IRExpr   **argv, *arg1;
    348    Int      regparms,opcode_type;
    349 
    350       /* We don't handle a host/guest word size mismatch */
    351    if (gWordTy != hWordTy) {
    352       VG_(tool_panic)("host/guest word size mismatch");
    353    }
    354 
    355       /* Set up SB */
    356    sbOut = deepCopyIRSBExceptStmts(sbIn);
    357 
    358       /* Copy verbatim any IR preamble preceding the first IMark */
    359    i = 0;
    360    while ( (i < sbIn->stmts_used) && (sbIn->stmts[i]->tag!=Ist_IMark)) {
    361       addStmtToIRSB( sbOut, sbIn->stmts[i] );
    362       i++;
    363    }
    364 
    365       /* Get the first statement */
    366    tl_assert(sbIn->stmts_used > 0);
    367    st = sbIn->stmts[i];
    368 
    369       /* double check we are at a Mark statement */
    370    tl_assert(Ist_IMark == st->tag);
    371 
    372    origAddr=st->Ist.IMark.addr;
    373 
    374       /* Get the BB_info */
    375    bbInfo = VG_(OSetGen_Lookup)(instr_info_table, &origAddr);
    376 
    377    if (bbInfo==NULL) {
    378 
    379          /* BB never translated before (at this address, at least;          */
    380          /* could have been unloaded and then reloaded elsewhere in memory) */
    381 
    382          /* allocate and initialize a new basic block structure */
    383       bbInfo=VG_(OSetGen_AllocNode)(instr_info_table, sizeof(struct BB_info));
    384       bbInfo->BB_addr = origAddr;
    385       bbInfo->n_instrs = n_instrs;
    386       bbInfo->inst_counter=VG_(calloc)("bbv_instrument",
    387                                        allocated_threads,
    388                                        sizeof(Int));
    389 
    390          /* assign a unique block number */
    391       bbInfo->block_num=block_num;
    392       block_num++;
    393          /* get function name and entry point information */
    394       const HChar *fn_name;
    395       VG_(get_fnname)(origAddr, &fn_name);
    396       bbInfo->is_entry=VG_(get_fnname_if_entry)(origAddr, &fn_name);
    397       bbInfo->fn_name =VG_(strdup)("bbv_strings", fn_name);
    398          /* insert structure into table */
    399       VG_(OSetGen_Insert)( instr_info_table, bbInfo );
    400    }
    401 
    402       /* Iterate through the basic block, putting the original   */
    403       /* instructions in place, plus putting a call to updateBBV */
    404       /* for each original instruction                           */
    405 
    406       /* This is less efficient than only instrumenting the BB   */
    407       /* But it gives proper results given the fact that         */
    408       /* valgrind uses superblocks (not basic blocks) by default */
    409 
    410 
    411    while(i < sbIn->stmts_used) {
    412       st=sbIn->stmts[i];
    413 
    414       if (st->tag == Ist_IMark) {
    415 
    416          ourAddr = st->Ist.IMark.addr;
    417 
    418          opcode_type=get_inst_type(st->Ist.IMark.len,ourAddr);
    419 
    420          regparms=1;
    421          arg1= mkIRExpr_HWord( (HWord)bbInfo);
    422          argv= mkIRExprVec_1(arg1);
    423 
    424 
    425          if (opcode_type&REP_INSTRUCTION) {
    426             arg1= mkIRExpr_HWord(ourAddr);
    427             argv= mkIRExprVec_1(arg1);
    428             di= unsafeIRDirty_0_N( regparms, "per_instruction_BBV_rep",
    429                                 VG_(fnptr_to_fnentry)( &per_instruction_BBV_rep ),
    430                                 argv);
    431          }
    432          else if (opcode_type&FLDCW_INSTRUCTION) {
    433             di= unsafeIRDirty_0_N( regparms, "per_instruction_BBV_fldcw",
    434                                 VG_(fnptr_to_fnentry)( &per_instruction_BBV_fldcw ),
    435                                 argv);
    436          }
    437          else {
    438          di= unsafeIRDirty_0_N( regparms, "per_instruction_BBV",
    439                                 VG_(fnptr_to_fnentry)( &per_instruction_BBV ),
    440                                 argv);
    441          }
    442 
    443 
    444             /* Insert our call */
    445          addStmtToIRSB( sbOut,  IRStmt_Dirty(di));
    446       }
    447 
    448          /* Insert the original instruction */
    449       addStmtToIRSB( sbOut, st );
    450 
    451       i++;
    452    }
    453 
    454    return sbOut;
    455 }
    456 
    457 static struct thread_info *allocate_new_thread(struct thread_info *old,
    458                                      Int old_number, Int new_number)
    459 {
    460    struct thread_info *temp;
    461    struct BB_info   *bb_elem;
    462    Int i;
    463 
    464    temp=VG_(realloc)("bbv_main.c allocate_threads",
    465                      old,
    466                      new_number*sizeof(struct thread_info));
    467 
    468       /* init the new thread */
    469       /* We loop in case the new thread is not contiguous */
    470    for(i=old_number;i<new_number;i++) {
    471       temp[i].last_rep_addr=0;
    472       temp[i].dyn_instr=0;
    473       temp[i].total_instr=0;
    474       temp[i].global_rep_count=0;
    475       temp[i].unique_rep_count=0;
    476       temp[i].rep_count=0;
    477       temp[i].fldcw_count=0;
    478       temp[i].bbtrace_fp=NULL;
    479    }
    480       /* expand the inst_counter on all allocated basic blocks */
    481    VG_(OSetGen_ResetIter)(instr_info_table);
    482    while ( (bb_elem = VG_(OSetGen_Next)(instr_info_table)) ) {
    483       bb_elem->inst_counter =
    484                     VG_(realloc)("bbv_main.c inst_counter",
    485                                  bb_elem->inst_counter,
    486                                  new_number*sizeof(Int));
    487       for(i=old_number;i<new_number;i++) {
    488          bb_elem->inst_counter[i]=0;
    489       }
    490    }
    491 
    492    return temp;
    493 }
    494 
    495 static void bbv_thread_called ( ThreadId tid, ULong nDisp )
    496 {
    497    if (tid >= allocated_threads) {
    498       bbv_thread=allocate_new_thread(bbv_thread,allocated_threads,tid+1);
    499       allocated_threads=tid+1;
    500    }
    501    current_thread=tid;
    502 }
    503 
    504 
    505 
    506 
    507 /*--------------------------------------------------------------------*/
    508 /*--- Setup                                                        ---*/
    509 /*--------------------------------------------------------------------*/
    510 
    511 static void bbv_post_clo_init(void)
    512 {
    513    bb_out_file =
    514           VG_(expand_file_name)("--bb-out-file", clo_bb_out_file);
    515 
    516       /* Try a closer approximation of basic blocks  */
    517       /* This is the same as the command line option */
    518       /* --vex-guest-chase-thresh=0                  */
    519    VG_(clo_vex_control).guest_chase_thresh = 0;
    520 }
    521 
    522    /* Parse the command line options */
    523 static Bool bbv_process_cmd_line_option(const HChar* arg)
    524 {
    525    if VG_INT_CLO       (arg, "--interval-size",    interval_size) {}
    526    else if VG_STR_CLO  (arg, "--bb-out-file",      clo_bb_out_file) {}
    527    else if VG_STR_CLO  (arg, "--pc-out-file",      clo_pc_out_file) {
    528       generate_pc_file = True;
    529    }
    530    else if VG_BOOL_CLO (arg, "--instr-count-only", instr_count_only) {}
    531    else {
    532       return False;
    533    }
    534 
    535    return True;
    536 }
    537 
    538 static void bbv_print_usage(void)
    539 {
    540    VG_(printf)(
    541 "   --bb-out-file=<file>       filename for BBV info\n"
    542 "   --pc-out-file=<file>       filename for BB addresses and function names\n"
    543 "   --interval-size=<num>      interval size\n"
    544 "   --instr-count-only=yes|no  only print total instruction count\n"
    545    );
    546 }
    547 
    548 static void bbv_print_debug_usage(void)
    549 {
    550    VG_(printf)("    (none)\n");
    551 }
    552 
    553 static void bbv_fini(Int exitcode)
    554 {
    555    Int i;
    556 
    557    if (generate_pc_file) {
    558       dumpPcFile();
    559    }
    560 
    561    for(i=0;i<allocated_threads;i++) {
    562 
    563       if (bbv_thread[i].total_instr!=0) {
    564          HChar buf[500];  // large enough
    565          VG_(sprintf)(buf,"\n\n"
    566                           "# Thread %d\n"
    567                           "#   Total intervals: %d (Interval Size %d)\n"
    568                           "#   Total instructions: %llu\n"
    569                           "#   Total reps: %llu\n"
    570                           "#   Unique reps: %llu\n"
    571                           "#   Total fldcw instructions: %llu\n\n",
    572                 i,
    573                 (Int)(bbv_thread[i].total_instr/(ULong)interval_size),
    574                 interval_size,
    575                 bbv_thread[i].total_instr,
    576                 bbv_thread[i].global_rep_count,
    577                 bbv_thread[i].unique_rep_count,
    578                 bbv_thread[i].fldcw_count);
    579 
    580             /* Print results to display */
    581          VG_(umsg)("%s\n", buf);
    582 
    583             /* open the output file if it hasn't already */
    584          if (bbv_thread[i].bbtrace_fp == NULL) {
    585             bbv_thread[i].bbtrace_fp=open_tracefile(i);
    586          }
    587             /* Also print to results file */
    588          VG_(fprintf)(bbv_thread[i].bbtrace_fp, "%s", buf);
    589          VG_(fclose)(bbv_thread[i].bbtrace_fp);
    590       }
    591    }
    592 }
    593 
    594 static void bbv_pre_clo_init(void)
    595 {
    596    VG_(details_name)            ("exp-bbv");
    597    VG_(details_version)         (NULL);
    598    VG_(details_description)     ("a SimPoint basic block vector generator");
    599    VG_(details_copyright_author)(
    600       "Copyright (C) 2006-2015 Vince Weaver");
    601    VG_(details_bug_reports_to)  (VG_BUGS_TO);
    602 
    603    VG_(basic_tool_funcs)          (bbv_post_clo_init,
    604                                    bbv_instrument,
    605                                    bbv_fini);
    606 
    607    VG_(needs_command_line_options)(bbv_process_cmd_line_option,
    608                                    bbv_print_usage,
    609                                    bbv_print_debug_usage);
    610 
    611    VG_(track_start_client_code)( bbv_thread_called );
    612 
    613 
    614    instr_info_table = VG_(OSetGen_Create)(/*keyOff*/0,
    615                                           NULL,
    616                                           VG_(malloc), "bbv.1", VG_(free));
    617 
    618    bbv_thread=allocate_new_thread(bbv_thread,0,allocated_threads);
    619 }
    620 
    621 VG_DETERMINE_INTERFACE_VERSION(bbv_pre_clo_init)
    622 
    623 /*--------------------------------------------------------------------*/
    624 /*--- end                                                          ---*/
    625 /*--------------------------------------------------------------------*/
    626