Function wrapping implementation and Symtab extensions for undefined symbols
[dyninst.git] / symtabAPI / src / Symtab-lookup.C
1 /*
2  * Copyright (c) 1996-2011 Barton P. Miller
3  * 
4  * We provide the Paradyn Parallel Performance Tools (below
5  * described as "Paradyn") on an AS IS basis, and do not warrant its
6  * validity or performance.  We reserve the right to update, modify,
7  * or discontinue this software at any time.  We shall have no
8  * obligation to supply such updates or modifications or any other
9  * form of support to you.
10  * 
11  * By your use of Paradyn, you understand and agree that we (or any
12  * other person or entity with proprietary rights in Paradyn) are
13  * under no obligation to provide either maintenance services,
14  * update services, notices of latent defects, or correction of
15  * defects for Paradyn.
16  * 
17  * This library is free software; you can redistribute it and/or
18  * modify it under the terms of the GNU Lesser General Public
19  * License as published by the Free Software Foundation; either
20  * version 2.1 of the License, or (at your option) any later version.
21  * 
22  * This library is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25  * Lesser General Public License for more details.
26  * 
27  * You should have received a copy of the GNU Lesser General Public
28  * License along with this library; if not, write to the Free Software
29  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
30  */
31
32 /* Lookup functions defined in class Symtab. Separated to reduce file size and classify. */
33
34
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <assert.h>
39 #include <string.h>
40 #include <vector>
41 #include <algorithm>
42
43 #include "common/h/Timer.h"
44 #include "common/h/debugOstream.h"
45 #include "common/h/serialize.h"
46 #include "common/h/pathName.h"
47
48 #include "Serialization.h"
49 #include "Symtab.h"
50 #include "Module.h"
51 #include "Collections.h"
52 #include "Function.h"
53 #include "Variable.h"
54 #include "annotations.h"
55
56 #include "symtabAPI/src/Object.h"
57
58 using namespace Dyninst;
59 using namespace Dyninst::SymtabAPI;
60 using namespace std;
61
62 extern SymtabError serr;
63
64 bool regexEquiv( const std::string &str,const std::string &them, bool checkCase );
65 bool pattern_match( const char *p, const char *s, bool checkCase );
66
67 static bool sort_by_sym_ptr(const Symbol *a, const Symbol *b) {
68     return a < b;
69 }
70
71 std::vector<Symbol *> *Symtab::findSymbolByOffset(Offset o)
72 {
73         //Symbol *s = NULL;
74         dyn_hash_map<Offset, std::vector<Symbol *> >::iterator iter;
75         iter = symsByOffset.find(o);
76         if (iter == symsByOffset.end()) return NULL;
77         return &(iter->second);
78 }
79
80 bool Symtab::findSymbol(std::vector<Symbol *> &ret, const std::string& name,
81                         Symbol::SymbolType sType, NameType nameType,
82                         bool isRegex, bool checkCase, bool includeUndefined)
83 {
84
85     unsigned old_size = ret.size();
86
87     std::vector<Symbol *> candidates;
88
89     if (!isRegex) {
90         // Easy case
91         if (nameType & mangledName) {
92            candidates.insert(candidates.end(), symsByMangledName[name].begin(), symsByMangledName[name].end());
93            if (includeUndefined) candidates.insert(candidates.end(), 
94                                                    undefDynSymsByMangledName[name].begin(), 
95                                                    undefDynSymsByMangledName[name].end());
96         }
97         if (nameType & prettyName) {
98            candidates.insert(candidates.end(), symsByPrettyName[name].begin(), symsByPrettyName[name].end());
99            if (includeUndefined) candidates.insert(candidates.end(), 
100                                                    undefDynSymsByPrettyName[name].begin(), 
101                                                    undefDynSymsByPrettyName[name].end());
102         }
103         if (nameType & typedName) {
104            candidates.insert(candidates.end(), symsByTypedName[name].begin(), symsByTypedName[name].end());
105            if (includeUndefined) candidates.insert(candidates.end(), 
106                                                    undefDynSymsByTypedName[name].begin(), 
107                                                    undefDynSymsByTypedName[name].end());
108         }
109     }
110     else {
111         // Build the regex list of symbols
112         // We need to iterate over every single symbol. Ugh.
113        if (includeUndefined) {
114           cerr << "Warning: regex search of undefined symbols is not supported" << endl;
115        }
116
117        for (unsigned i = 0; i < everyDefinedSymbol.size(); i++) {
118           if (nameType & mangledName) {
119              if (regexEquiv(name, everyDefinedSymbol[i]->getName(), checkCase))
120                 candidates.push_back(everyDefinedSymbol[i]);
121
122           }
123           if (nameType & prettyName) {
124              if (regexEquiv(name, everyDefinedSymbol[i]->getPrettyName(), checkCase))
125                 candidates.push_back(everyDefinedSymbol[i]);
126           }
127           if (nameType & typedName) {
128              if (regexEquiv(name, everyDefinedSymbol[i]->getTypedName(), checkCase))
129                 candidates.push_back(everyDefinedSymbol[i]);
130           }
131        }
132     }
133
134     std::set<Symbol *> matches;
135
136     for (std::vector<Symbol *>::iterator iter = candidates.begin();
137          iter != candidates.end(); ++iter) {
138        if (sType == Symbol::ST_UNKNOWN ||
139            sType == Symbol::ST_NOTYPE ||
140            sType == (*iter)->getType()) {
141           matches.insert(*iter);
142        }
143     }
144     ret.insert(ret.end(), matches.begin(), matches.end());
145
146     if (ret.size() == old_size) {
147         serr = No_Such_Symbol;
148         return false;
149     }
150     else {
151         return true;
152     }
153 }
154
155 bool Symtab::getAllSymbols(std::vector<Symbol *> &ret)
156 {
157     ret = everyDefinedSymbol;
158
159     // add undefined symbols
160     std::vector<Symbol *> temp;
161     std::vector<Symbol *>::iterator it;
162     getAllUndefinedSymbols(temp);
163     for (it = temp.begin(); it != temp.end(); it++)
164         ret.push_back(*it);
165
166     if(ret.size() > 0)
167         return true;
168     serr = No_Such_Symbol;
169     return false;
170 }
171
172 bool Symtab::getAllSymbolsByType(std::vector<Symbol *> &ret, Symbol::SymbolType sType)
173 {
174     if (sType == Symbol::ST_UNKNOWN)
175         return getAllSymbols(ret);
176
177     unsigned old_size = ret.size();
178     // Filter by the given type
179     for (unsigned i = 0; i < everyDefinedSymbol.size(); i++) {
180         if (everyDefinedSymbol[i]->getType() == sType)
181             ret.push_back(everyDefinedSymbol[i]);
182     }
183     // add undefined symbols
184     std::vector<Symbol *> temp;
185     getAllUndefinedSymbols(temp);
186     for (unsigned i = 0; i < temp.size(); i++) {
187         if (temp[i]->getType() == sType)
188             ret.push_back(temp[i]);
189     }
190
191     if (ret.size() > old_size) {
192         return true;
193     }
194     else {
195         serr = No_Such_Symbol;
196         return false;
197     }
198 }
199
200 bool Symtab::getAllDefinedSymbols(std::vector<Symbol *> &ret)
201 {
202     ret = everyDefinedSymbol;
203
204     if(ret.size() > 0)
205         return true;
206     serr = No_Such_Symbol;
207     return false;
208 }
209  
210 bool Symtab::getAllUndefinedSymbols(std::vector<Symbol *> &ret){
211     unsigned size = ret.size();
212     ret.insert(ret.end(), undefDynSyms.begin(), undefDynSyms.end());
213     if(ret.size()>size)
214         return true;
215     serr = No_Such_Symbol;
216     return false;
217 }
218
219 bool Symtab::findFuncByEntryOffset(Function *&ret, const Offset entry)
220 {
221     /* XXX
222      *
223      * When working with relocatable files, a symbol is not uniquely identified
224      * by its offset; it is uniquely identified by its Region and its offset.
225      * This discrepancy is not taken into account here.
226      */
227     if (funcsByOffset.find(entry) != funcsByOffset.end()) {
228         ret = funcsByOffset[entry];
229         return true;
230     }
231     serr = No_Such_Function;
232     return false;
233 }
234
235 bool sort_by_func_ptr(const Function *a, const Function *b) {
236     return a < b;
237 }
238
239 bool Symtab::findFunctionsByName(std::vector<Function *> &ret, const std::string name,
240                                  NameType nameType, bool isRegex, bool checkCase) {
241     std::vector<Symbol *> funcSyms;
242     if (!findSymbol(funcSyms, name, Symbol::ST_FUNCTION, nameType, isRegex, checkCase))
243         return false;
244
245     std::vector<Function *> unsortedFuncs;
246     for (unsigned i = 0; i < funcSyms.size(); i++) 
247     {
248         if (doNotAggregate(funcSyms[i])) continue;
249         if (!funcSyms[i]->getFunction())
250         {
251             fprintf(stderr, "%s[%d]:  WARNING:  internal inconsistency\n", FILE__, __LINE__);
252             fprintf(stderr, "%s[%d]:  WARNING:  %s is %s a function\n", FILE__, __LINE__, name.c_str(), funcSyms[i]->isFunction() ? "" : "not");
253             fprintf(stderr, "%s[%d]:  WARNING:  %s is %s a variable\n", FILE__, __LINE__, name.c_str(), funcSyms[i]->isVariable() ? "" : "not");
254             continue;
255         }
256         unsortedFuncs.push_back(funcSyms[i]->getFunction());
257     }
258
259     std::sort(unsortedFuncs.begin(), unsortedFuncs.end(), sort_by_func_ptr);
260     std::vector<Function *>::iterator endIter;
261     endIter = std::unique(unsortedFuncs.begin(), unsortedFuncs.end());
262     for (std::vector<Function *>::iterator iter = unsortedFuncs.begin();
263          iter != endIter;
264          iter++)
265         ret.push_back(*iter);
266
267     return true;
268 }
269
270 bool Symtab::getAllFunctions(std::vector<Function *> &ret) {
271     ret = everyFunction;
272     return (ret.size() > 0);
273 }
274
275 bool Symtab::findVariableByOffset(Variable *&ret, const Offset offset) {
276
277     /* XXX
278      *
279      * See comment in findFuncByOffset about uniqueness of symbols in
280      * relocatable files -- this discrepancy applies here as well.
281      */
282     if (varsByOffset.find(offset) != varsByOffset.end()) {
283         ret = varsByOffset[offset];
284         return true;
285     }
286     serr = No_Such_Variable;
287     return false;
288 }
289
290 static bool sort_by_var_ptr(const Variable * a, const Variable *b) {
291     return a < b;
292 }
293
294 bool Symtab::findVariablesByName(std::vector<Variable *> &ret, const std::string name,
295                                  NameType nameType, bool isRegex, bool checkCase) {
296     std::vector<Symbol *> varSyms;
297     if (!findSymbol(varSyms, name, Symbol::ST_OBJECT, nameType, isRegex, checkCase))
298         return false;
299
300     std::vector<Variable *> unsortedVars;
301     for (unsigned i = 0; i < varSyms.size(); i++) {
302         if (doNotAggregate(varSyms[i])) continue;
303         unsortedVars.push_back(varSyms[i]->getVariable());
304     }
305
306     std::sort(unsortedVars.begin(), unsortedVars.end(), sort_by_var_ptr);
307     std::vector<Variable *>::iterator endIter;
308     endIter = std::unique(unsortedVars.begin(), unsortedVars.end());
309     for (std::vector<Variable *>::iterator iter = unsortedVars.begin();
310          iter != endIter;
311          iter++)
312         ret.push_back(*iter);
313
314     return true;
315 }
316
317 bool Symtab::getAllVariables(std::vector<Variable *> &ret) 
318 {
319     ret = everyVariable;
320     return (ret.size() > 0);
321 }
322
323 bool Symtab::getAllModules(std::vector<Module *> &ret)
324 {
325     if (_mods.size() >0 )
326     {
327         ret = _mods;
328         return true;
329     }   
330
331     serr = No_Such_Module;
332     return false;
333 }
334
335 bool Symtab::findModuleByOffset(Module *&ret, Offset off)
336 {   
337    //  this should be a hash, really
338    for (unsigned int i = 0; i < _mods.size(); ++i) 
339    {
340       Module *mod = _mods[i];
341
342       if (off == mod->addr()) 
343       {
344           ret = mod;
345           return true;
346       }
347    } 
348    return false;
349 }
350
351 bool Symtab::findModuleByName(Module *&ret, const std::string name)
352 {
353    dyn_hash_map<std::string, Module *>::iterator loc;
354    loc = modsByFileName.find(name);
355
356    if (loc != modsByFileName.end()) 
357    {
358       ret = loc->second;
359       return true;
360    }
361
362    loc = modsByFullName.find(name);
363
364    if (loc != modsByFullName.end()) 
365    {
366       ret = loc->second;
367       return true;
368    }
369
370    serr = No_Such_Module;
371    ret = NULL;
372    return false;
373 }
374
375 bool Symtab::getAllRegions(std::vector<Region *>&ret)
376 {
377    if (regions_.size() > 0)
378    {
379       ret = regions_;
380       return true;
381    }
382
383    return false;
384 }
385
386 bool Symtab::getCodeRegions(std::vector<Region *>&ret)
387 {
388    if (codeRegions_.size() > 0)
389    {
390       ret = codeRegions_;
391       return true;
392    }
393
394    return false;
395 }
396
397 bool Symtab::getDataRegions(std::vector<Region *>&ret)
398 {
399    if (dataRegions_.size() > 0)
400    {
401       ret = dataRegions_;
402       return true;
403    }
404    return false;
405 }
406
407
408 bool Symtab::getAllNewRegions(std::vector<Region *>&ret)
409 {
410    std::vector<Region *> *retp = NULL;
411
412    if (!getAnnotation(retp, UserRegionsAnno))
413    {
414       fprintf(stderr, "%s[%d]:  failed to get annotations here\n", FILE__, __LINE__);
415       return false;
416    }
417
418    if (!retp)
419    {
420       fprintf(stderr, "%s[%d]:  failed to get annotations here\n", FILE__, __LINE__);
421       return false;
422    }
423
424    ret = *retp;
425
426    return true;
427 }
428
429 bool Symtab::getAllExceptions(std::vector<ExceptionBlock *> &exceptions)
430 {
431    if (excpBlocks.size()>0)
432    {
433       exceptions = excpBlocks;
434       return true;
435    }    
436
437    return false;
438 }
439
440
441 bool Symtab::findException(ExceptionBlock &excp, Offset addr)
442 {
443    for (unsigned i=0; i<excpBlocks.size(); i++)
444    {
445       if (excpBlocks[i]->contains(addr))
446       {
447          excp = *(excpBlocks[i]);
448          return true;
449       } 
450    }
451
452    return false;
453 }
454
455 /**
456  * Returns true if the Address range addr -> addr+size contains
457  * a catch block, with excp pointing to the appropriate block
458  **/
459 bool Symtab::findCatchBlock(ExceptionBlock &excp, Offset addr, unsigned size)
460 {
461     int min = 0;
462     int max = excpBlocks.size();
463     int cur = -1, last_cur;
464
465     if (max == 0)
466         return false;
467
468     //Binary search through vector for address
469     while (true)
470     {
471         last_cur = cur;
472         cur = (min + max) / 2;
473     
474         if (last_cur == cur)
475             return false;
476
477         Offset curAddr = excpBlocks[cur]->catchStart();
478         if ((curAddr <= addr && curAddr+size > addr) ||
479             (size == 0 && curAddr == addr))
480         {
481             //Found it
482             excp = *(excpBlocks[cur]);
483             return true;
484         }
485         if (addr < curAddr)
486             max = cur;
487         else if (addr > curAddr)
488             min = cur;
489     }
490 }
491  
492 bool Symtab::findRegionByEntry(Region *&ret, const Offset offset)
493 {
494     if(regionsByEntryAddr.find(offset) != regionsByEntryAddr.end())
495     {
496         ret = regionsByEntryAddr[offset];
497         return true;
498     }
499     serr = No_Such_Region;
500     return false;
501 }
502
503 /* Similar to binary search in isCode with the exception that here we
504  * search to the end of regions without regards to whether they have
505  * corresponding raw data on disk, and searches all regions.  
506  *
507  * regions_ elements that start at address 0 may overlap, ELF binaries
508  * have 0 address iff they are not loadable, but xcoff places loadable
509  * sections at address 0, including .text and .data
510  */
511 Region *Symtab::findEnclosingRegion(const Offset where)
512 {
513 #if defined (os_aix) // regions overlap so do sequential search
514     // try code regions first, then data, regions_ vector as last resort
515     for (unsigned rIdx=0; rIdx < codeRegions_.size(); rIdx++) {
516         if (where >= codeRegions_[rIdx]->getRegionAddr() &&
517             where < (codeRegions_[rIdx]->getRegionAddr() 
518                      + codeRegions_[rIdx]->getMemSize())) {
519             return codeRegions_[rIdx];
520         }
521     }
522     for (unsigned rIdx=0; rIdx < dataRegions_.size(); rIdx++) {
523         if (where >= dataRegions_[rIdx]->getRegionAddr() &&
524             where < (dataRegions_[rIdx]->getRegionAddr() 
525                      + dataRegions_[rIdx]->getMemSize())) {
526             return dataRegions_[rIdx];
527         }
528     }
529     for (unsigned rIdx=0; rIdx < regions_.size(); rIdx++) {
530         if (where >= regions_[rIdx]->getRegionAddr() &&
531             where < (regions_[rIdx]->getRegionAddr() 
532                      + regions_[rIdx]->getMemSize())) {
533             return regions_[rIdx];
534         }
535     }
536     return NULL;
537 #endif
538     int first = 0; 
539     int last = regions_.size() - 1;
540     while (last >= first) {
541         Region *curreg = regions_[(first + last) / 2];
542         if (where >= curreg->getRegionAddr()
543             && where < (curreg->getRegionAddr()
544                         + curreg->getMemSize())) {
545             return curreg;
546         }
547         else if (where < curreg->getRegionAddr()) {
548             last = ((first + last) / 2) - 1;
549         }
550         else {/* where >= (cursec->getSecAddr()
551                            + cursec->getSecSize()) */
552             first = ((first + last) / 2) + 1;
553         }
554     }
555     return NULL;
556 }
557
558 bool Symtab::findRegion(Region *&ret, const std::string secName)
559 {
560     for(unsigned index=0;index<regions_.size();index++)
561     {
562         if(regions_[index]->getRegionName() == secName)
563         {
564             ret = regions_[index];
565             return true;
566         }
567     }
568     serr = No_Such_Region;
569     return false;
570 }
571
572
573 bool Symtab::findRegion(Region *&ret, const Offset addr, const unsigned long size)
574 {
575     for(unsigned index=0;index<regions_.size();index++)
576     {
577         if(regions_[index]->getRegionAddr() == addr && regions_[index]->getRegionSize() == size)
578         {
579             ret = regions_[index];
580             return true;
581         }
582     }
583     serr = No_Such_Region;
584     return false;
585 }
586
587 ///////////////////////// REGEX //////////////////////
588
589 // Use POSIX regular expression pattern matching to check if std::string s matches
590 // the pattern in this std::string
591 bool regexEquiv( const std::string &str,const std::string &them, bool checkCase ) 
592 {
593    const char *str_ = str.c_str();
594    const char *s = them.c_str();
595    // Would this work under NT?  I don't know.
596    //#if !defined(os_windows)
597     return pattern_match(str_, s, checkCase);
598
599 }
600
601 // This function will match string s against pattern p.
602 // Asterisks match 0 or more wild characters, and a question
603 // mark matches exactly one wild character.  In other words,
604 // the asterisk is the equivalent of the regex ".*" and the
605 // question mark is the equivalent of "."
606
607 bool
608 pattern_match( const char *p, const char *s, bool checkCase ) {
609    //const char *p = ptrn;
610    //char *s = str;
611
612     while ( true ) {
613         // If at the end of the pattern, it matches if also at the end of the string
614         if( *p == '\0' )
615             return ( *s == '\0' );
616
617         // Process a '*'
618         if( *p == MULTIPLE_WILDCARD_CHARACTER ) {
619             ++p;
620
621             // If at the end of the pattern, it matches
622             if( *p == '\0' )
623                 return true;
624
625             // Try to match the remaining pattern for each remaining substring of s
626             for(; *s != '\0'; ++s )
627                 if( pattern_match( p, s, checkCase ) )
628                     return true;
629             // Failed
630             return false;
631         }
632
633         // If at the end of the string (and at this point, not of the pattern), it fails
634         if( *s == '\0' )
635             return false;
636
637         // Check if this character matches
638         bool matchChar = false;
639         if( *p == WILDCARD_CHARACTER || *p == *s )
640             matchChar = true;
641         else if( !checkCase ) {
642             if( *p >= 'A' && *p <= 'Z' && *s == ( *p + ( 'a' - 'A' ) ) )
643                 matchChar = true;
644             else if( *p >= 'a' && *p <= 'z' && *s == ( *p - ( 'a' - 'A' ) ) )
645                 matchChar = true;
646         }
647
648         if( matchChar ) {
649             ++p;
650             ++s;
651             continue;
652         }
653
654         // Did not match
655         return false;
656     }
657 }
658
659 struct Dyninst::SymtabAPI::SymbolCompareByAddr
660 {
661     bool operator()(Function *a, Function *b)
662     {
663        return (a->offset_ < b->offset_);
664     }
665 };
666
667 bool Symtab::getContainingFunction(Offset offset, Function* &func)
668 {
669    if (!isCode(offset)) {
670       return false;
671    }
672    if (everyFunction.size() && !sorted_everyFunction)
673    {
674       std::sort(everyFunction.begin(), everyFunction.end(),
675                 SymbolCompareByAddr());
676       sorted_everyFunction = true;
677    }
678    
679    unsigned low = 0;
680    unsigned high = everyFunction.size();
681    unsigned last_mid = high+1;
682    unsigned mid;
683    if (!high) return false;
684    for (;;)
685    {
686       mid = (low + high) / 2;
687       if (last_mid == mid)
688          break;
689       last_mid = mid;
690       Offset cur = everyFunction[mid]->getOffset();
691       if (cur > offset) {
692          high = mid;
693          continue;
694       }
695       if (cur < offset) {
696          low = mid;
697          continue;
698       }
699       if (cur == offset) {
700          func = everyFunction[mid];
701          return true;
702       }
703    }
704
705    if ((everyFunction[low]->getOffset() <= offset) &&
706        ((low+1 == everyFunction.size()) || 
707         (everyFunction[low+1]->getOffset() > offset)))
708    {
709          func = everyFunction[low];
710          return true;
711    }
712    return false;
713 }
714
715 Module *Symtab::getDefaultModule() {
716     Module *mod = NULL;
717     // TODO: automatically pick the module that contains this address?
718     // For now, DEFAULT_MODULE or (if we have only one) that one.
719     if (_mods.size() == 1)
720         return _mods[0];
721     else {
722         if (!findModuleByName(mod, "DEFAULT_MODULE"))
723             return NULL;
724     }
725     return mod;
726 }
727
728 unsigned Function::getSize() {
729    if (functionSize_)
730       return functionSize_;
731    for (unsigned i=0; i<symbols_.size(); i++) {
732       if (symbols_[i]->getSize()) { 
733          functionSize_ = symbols_[i]->getSize();;
734          return functionSize_;
735       }
736    }
737
738    Symtab *symtab = getFirstSymbol()->getSymtab();
739    if (symtab->everyFunction.size() && !symtab->sorted_everyFunction)
740    {
741       std::sort(symtab->everyFunction.begin(), symtab->everyFunction.end(),
742                 SymbolCompareByAddr());
743       symtab->sorted_everyFunction = true;
744    }
745
746    Offset offset = getOffset();
747    unsigned low = 0;
748    unsigned high = symtab->everyFunction.size();
749    unsigned last_mid = high+1;
750    unsigned mid;
751    for (;;)
752    {
753       mid = (low + high) / 2;
754       if (last_mid == mid)
755          return 0;
756       last_mid = mid;
757       Offset cur = symtab->everyFunction[mid]->getOffset();
758       if (cur > offset) {
759          high = mid;
760          continue;
761       }
762       if (cur < offset) {
763          low = mid;
764          continue;
765       }
766       if (cur == offset) {
767          if (mid + 1 >= symtab->everyFunction.size())
768             return 0;
769          Function *next_func = symtab->everyFunction[mid+1];
770          functionSize_ = next_func->getOffset() - getOffset();
771          return functionSize_;
772       }
773    }
774 }
775