Fix window compilation issues
[dyninst.git] / parseAPI / src / ProbabilisticParser.C
1 /*
2  * See the dyninst/COPYRIGHT file for copyright information.
3  * 
4  * We provide the Paradyn Tools (below described as "Paradyn")
5  * on an AS IS basis, and do not warrant its validity or performance.
6  * We reserve the right to update, modify, or discontinue this
7  * software at any time.  We shall have no obligation to supply such
8  * updates or modifications or any other form of support to you.
9  * 
10  * By your use of Paradyn, you understand and agree that we (or any
11  * other person or entity with proprietary rights in Paradyn) are
12  * under no obligation to provide either maintenance services,
13  * update services, notices of latent defects, or correction of
14  * defects for Paradyn.
15  * 
16  * This library is free software; you can redistribute it and/or
17  * modify it under the terms of the GNU Lesser General Public
18  * License as published by the Free Software Foundation; either
19  * version 2.1 of the License, or (at your option) any later version.
20  * 
21  * This library is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
24  * Lesser General Public License for more details.
25  * 
26  * You should have received a copy of the GNU Lesser General Public
27  * License along with this library; if not, write to the Free Software
28  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
29  */
30
31 #if defined(cap_stripped_binaries)
32
33 #include <cstdio>
34 #include <cmath>
35 #include <algorithm>
36 #include <queue>
37 #include <iostream>
38
39 #include "entryIDs.h"
40 #include "dyn_regs.h"
41 #include "InstructionDecoder.h"
42 #include "Instruction.h"
43
44 #include "ProbabilisticParser.h"
45
46 using namespace std;
47 using namespace Dyninst;
48 using namespace Dyninst::ParseAPI;
49 using namespace Dyninst::InstructionAPI;
50 using namespace NS_x86;
51 using namespace hd; 
52
53
54 // Precision error allowed in double precision float number
55 #define ZERO 1e-8
56 static int double_cmp(double a, double b) {
57     double delta = a - b;
58     if (fabs(delta) < ZERO) return 0;
59     else if (delta > ZERO) return 1;
60     else return -1;
61 }
62
63 struct ProbAndAddr {
64     Address addr;
65     double prob;
66
67     ProbAndAddr(Address a, double p): addr(a), prob(p) {}
68     bool operator < (const ProbAndAddr &p) const {
69         if (double_cmp(prob, p.prob) == 0) return addr > p.addr;
70         return prob < p.prob;
71     }
72 };
73 static bool MatchArgs(unsigned short arg1, unsigned short arg2) {
74     return arg1 == arg2;
75 }
76
77 bool IdiomTerm::match(const IdiomTerm &it) const {    
78     if (entry_id == WILDCARD_ENTRY_ID || it.entry_id == WILDCARD_ENTRY_ID) return true; // Wildcard matches everything
79     if (entry_id == e_nop && it.entry_id == e_nop) return true; // Nops match without considering arguments
80     if (entry_id != it.entry_id) return false;
81     return MatchArgs(arg1, it.arg1) && MatchArgs(arg2, it.arg2);
82 }
83
84 bool IdiomTerm::matchOpcode(unsigned short eid) const {
85     if (entry_id == WILDCARD_ENTRY_ID || eid == WILDCARD_ENTRY_ID) return true; // Wildcard matches everything
86     return entry_id == eid;
87 }
88
89 bool IdiomTerm::operator == (const IdiomTerm &it) const {
90     return (entry_id == it.entry_id) && (arg1 == it.arg1) && (arg2 == it.arg2);
91 }
92
93 bool IdiomTerm::operator < (const IdiomTerm &it) const {
94     if (entry_id != it.entry_id) return entry_id < it.entry_id;
95     if (arg1 != it.arg1) return arg1 < it.arg1;
96     return arg2 < it.arg2;
97 }
98
99 static string HandleAnOperand(unsigned short arg, int style) {
100     if(arg != NOARG) {
101         if (arg == MEMARG) {
102             if (style) return "MEM"; else return ":A";
103         }
104         else if (arg == IMMARG) {
105             if (style) return "IMM"; else return ":B";
106         }
107         else if (arg == MULTIREG) {
108             if (style) return "MULTIREG"; else return ":C";
109         }
110         else if (arg == CALLEESAVEREG) {
111             if (style) return "Callee saved reg"; else return ":D";
112         } 
113         else if (arg == ARGUREG) {
114             if (style) return "Argu passing reg"; else return ":E";
115         }
116         else if (arg == OTHERREG) {
117             if (style) return "Other reg"; else return ":F";
118         }
119         else {
120             // the human_readble format is still broken
121             if (style) {
122                 if (arg == 0x0010) return x86_64::rip.name();
123                 switch (arg & 0xf000) {
124                     case 0x0000:
125                         //GPR
126                         return MachRegister(arg | x86_64::GPR | Arch_x86_64).name();
127                     case 0x1000:
128                         //mmx
129                         return MachRegister(arg | x86_64::MMX | Arch_x86_64).name();
130                     case 0x2000:
131                         //xxm
132                         return MachRegister(arg | x86_64::XMM | Arch_x86_64).name();
133                     case 0x4000:
134                         // flag bit
135                         return MachRegister(arg | x86_64::FLAG | Arch_x86_64).name();
136                 }
137
138               
139                 return ":" + MachRegister(arg).name();
140             }
141             char buf[64];
142             snprintf(buf, 64, "%x", arg);
143             return string(":") + string(buf);
144         }
145     }
146     return "";
147 }
148
149
150 string IdiomTerm::human_format() const {
151     string entryname;
152     if(*this == WILDCARD_TERM)
153         entryname = "*";
154     else if(entryNames_IAPI.find((entryID)(entry_id)) == entryNames_IAPI.end()) {
155         entryname = "[UNMAPED]";
156         fprintf(stderr, "Found entryID not mapped in entryNames_IAPI %d\n", entry_id);
157     }
158     else {
159         entryname = entryNames_IAPI[(entryID)(entry_id)];
160         if (arg1 != NOARG) entryname += " " + HandleAnOperand(arg1, 1);
161         if (arg2 != NOARG) entryname += "," + HandleAnOperand(arg2, 1);
162     }
163
164     return entryname;        
165
166 }
167
168 bool Idiom::operator < (const Idiom &i) const {
169     if (terms.size() != i.terms.size()) return terms.size() < i.terms.size();
170     for (size_t index = 0; index < terms.size(); ++index)
171         if (terms[index] < i.terms[index]) return true;
172         else if (i.terms[index] < terms[index]) return false;
173     return false;
174 }
175
176 static void split(const char * str, vector<uint64_t> & terms)
177 {
178     const char *s = str, *e = NULL;
179     char buf[32];
180     
181     while((e = strchr(s,'_')) != NULL) {
182         assert(e-s < 32);
183         strncpy(buf,s,e-s);
184         buf[e-s] = '\0';
185
186 #if defined (os_windows)
187 #define dyn_strtoull _strtoui64
188 #else
189 #define dyn_strtoull strtoull
190 #endif
191         terms.push_back(dyn_strtoull(buf,NULL,16));
192         
193         s = e+1;
194     }
195     // last one
196     if(strlen(s)) {
197         terms.push_back(dyn_strtoull(s,NULL,16));
198     }
199 #undef dyn_strtoull
200 }
201 Idiom::Idiom(string format, double weight, bool pre): 
202     w(weight), prefix(pre)
203 {
204     vector<uint64_t> items;
205     split(format.c_str(), items);
206
207     unsigned short entry_id, arg1, arg2;
208     for (size_t i = 0; i < items.size(); ++i) {
209         uint64_t t = items[i];
210         if(!(t & ENTRY_MASK)) {
211             t = t << ARG_SIZE;
212             t |= NOARG;
213         }
214         if(!(t & ENTRY_MASK)) {
215             t = t << ARG_SIZE;
216             t |= NOARG;
217         }
218         
219         entry_id = (t>>32) & 0xffffffff;
220         arg1 = (t>>16) & 0xffff;
221         arg2 = t & 0xffff;
222         terms.push_back(IdiomTerm(entry_id, arg1, arg2));
223
224     }
225     if (prefix) reverse(terms.begin(), terms.end());       
226 }
227
228 string Idiom::human_format() const {
229     string ret = "";
230
231     //printf("formatting %s\n",format().c_str());
232     for(unsigned i=0;i<terms.size();++i) {
233         ret += terms[i].human_format();
234         if(i<terms.size()-1)
235             ret += "|";
236     }
237     return ret;
238
239 }
240
241 IdiomPrefixTree::IdiomPrefixTree() {
242     feature = false;
243 }
244
245 void IdiomPrefixTree::addIdiom(const Idiom& idiom) {
246     addIdiom(0, idiom);
247 }
248
249 void IdiomPrefixTree::addIdiom(int cur, const Idiom& idiom) {
250     if (cur == (int)idiom.terms.size()) {
251         feature = true;
252         w = idiom.w;
253     } else {
254         auto next = children.end();
255         for (auto cit = children.begin(); cit != children.end(); ++cit) {
256             if (cit->first == idiom.terms[cur]) {
257                 next = cit;
258                 break;
259             }
260         }
261
262         if (next == children.end()) {
263             children.push_back(make_pair(idiom.terms[cur], new IdiomPrefixTree() ) );
264             next = children.end();
265             --next;
266         }
267         next->second->addIdiom(cur+1, idiom);
268     }
269 }
270
271 IdiomPrefixTree::ChildrenType IdiomPrefixTree::findChildrenWithOpcode(unsigned short entry_id) {
272     ChildrenType ret;
273     for (auto cit = children.begin(); cit != children.end(); ++cit)
274         if (cit->first.matchOpcode(entry_id))
275             ret.push_back(*cit);
276     return ret;
277 }
278
279 IdiomPrefixTree::ChildrenType IdiomPrefixTree::findChildrenWithArgs(unsigned short arg1, unsigned short arg2, ChildrenType &candidate) {
280     ChildrenType ret;
281     for (auto cit = candidate.begin(); cit != candidate.end(); ++cit) {
282         if (cit->first.match(IdiomTerm(cit->first.entry_id, arg1, arg2)))
283             ret.push_back(*cit);
284     }
285     return ret;
286 }
287
288 ProbabilityCalculator::ProbabilityCalculator(CodeRegion *reg, CodeSource *source, Parser* p, string model_spec):
289     model(model_spec), cr(reg), cs(source), parser(p) 
290 {
291 }
292
293 double ProbabilityCalculator::calcProbByMatchingIdioms(Address addr) {
294 //    if (addr != 0x8049d68) return 0;
295     double w = model.getBias();
296     bool valid = true;
297 //    fprintf(stderr, "before forward matching w = %.6lf\n", w);
298     w += calcForwardWeights(0, addr, model.getNormalIdiomTreeRoot(), valid);
299 //    fprintf(stderr, "after forward matching w = %.6lf\n", w);
300
301     if (valid) {
302         set<IdiomPrefixTree*> matched;
303         w += calcBackwardWeights(0, addr, model.getPrefixIdiomTreeRoot(), matched);
304 //      fprintf(stderr, "after backward matching w = %.6lf\n", w);
305
306         double prob = ((double)1) / (1 + exp(-w));
307         return FEPProb[addr] = reachingProb[addr] = prob;       
308     } else return FEPProb[addr] = reachingProb[addr] = 0;
309 }
310
311 void ProbabilityCalculator::calcProbByEnforcingConstraints() {
312
313     // Initialize our knowledge about non-gap functions
314     finalized.clear();
315     for (auto fit = parser->obj().funcs().begin(); fit != parser->obj().funcs().end(); ++fit) {    
316         finalized.insert(*fit);
317         for (auto bit = (*fit)->blocks().begin(); bit != (*fit)->blocks().end(); ++bit) {
318             for (Address addr = (*bit)->start(); addr != (*bit)->end(); ++addr)
319                 reachingProb[addr] = 2;
320         }
321     }
322
323     priority_queue<ProbAndAddr> q;
324     double threshold = model.getProbThreshold();
325     // Use the prob from matching idioms and push all the FEP candidates into the priority queue
326     for (auto pit = FEPProb.begin(); pit != FEPProb.end(); ++pit)
327         if (pit->second >= threshold) 
328             q.push(ProbAndAddr(pit->first, pit->second));
329     
330     while (!q.empty()) {
331         ProbAndAddr pa = q.top();
332         q.pop();
333
334         //This is an out-dated item. The address has either improved prob by
335         //applying call consistency constraints or 0 prob by applying overlapping constraints.
336         if (double_cmp(FEPProb[pa.addr], pa.prob) != 0) continue;
337
338         parser->parse_at(cr, pa.addr, true, GAP);
339
340         Function *f = parser->findFuncByEntry(cr, pa.addr);
341         assert(f != NULL);
342
343         fprintf(stderr, "Enforcing constraints at %lx with prob %.6lf\n", pa.addr, pa.prob);
344         // Enforce the overlapping constraints
345         dyn_hash_map<Address, double> newFEPProb, newReachingProb;
346         dyn_hash_set<Function *> newDiscoveredFuncs;
347         if (enforceOverlappingConstraints(f, pa.addr, pa.prob, newFEPProb, newReachingProb, newDiscoveredFuncs)) {
348             Finalize(newFEPProb, newReachingProb, newDiscoveredFuncs);
349             // Enforce the call consistency constraints
350         } else {
351             Remove(newDiscoveredFuncs);
352         }
353     }
354
355 }
356
357 double ProbabilityCalculator::getFEPProb(Address addr) {
358     if (FEPProb.find(addr) != FEPProb.end()) return FEPProb[addr];
359     return 0;
360 }
361
362 double ProbabilityCalculator::getReachingProb(Address addr) {
363     if (reachingProb.find(addr) != reachingProb.end()) return reachingProb[addr];
364     return 0;
365 }
366
367 bool ProbabilityCalculator::isFEP(Address addr) {
368     double prob = getFEPProb(addr);
369     if (prob >= model.getProbThreshold()) return true; else return false;
370 }
371
372 double ProbabilityCalculator::calcForwardWeights(int cur, Address addr, IdiomPrefixTree *tree, bool &valid) {
373     if (addr >= cr->high()) return 0;
374 //    printf("Start matching at %lx for %dth idiom term\n", addr, cur);
375
376     double w = 0;
377     if (tree->isFeature()) w = tree->getWeight();
378
379     if (tree->isLeafNode()) return w;
380     
381
382     unsigned short entry_id, len;
383     if (!getOpcode(entry_id, len, addr)) {
384         valid = false;
385         return 0;
386     }
387
388     // if the current address starts with an nop,
389     // it is definitely not a function entry point
390     if (cur == 0 && entry_id == e_nop) {
391         valid = false;
392         return 0;
393     }
394
395 //    printf("  decode entry_id %x\n", entry_id);
396     IdiomPrefixTree::ChildrenType children = tree->findChildrenWithOpcode(entry_id);
397 //    printf("  match entry_id to find %u candidates\n", children.size());
398     if (children.size() == 0) return 0;
399
400     unsigned short arg1, arg2;
401     if (!getArgs(arg1, arg2, addr)) {
402         valid = false;
403         return 0;
404     }
405     children = tree->findChildrenWithArgs(arg1, arg2, children);
406 //    printf("  decode operands %x %x\n", arg1, arg2);
407 //    printf("  match operands to find %u candidates\n", children.size());
408     if (children.size() == 0) return 0;
409
410     for (auto cit = children.begin(); cit != children.end() && valid; ++cit) {
411         w += calcForwardWeights(cur + 1, addr + len, cit->second, valid);
412         if (valid && cit->first.entry_id == WILDCARD_ENTRY_ID) {
413             Address next = addr + len;
414             if (!getOpcode(entry_id, len, next)) {
415                 valid = false;
416                 return 0;
417             }
418             w += calcForwardWeights(cur + 1, next + len, cit->second, valid);
419         }
420     }
421             
422     // the return value is not important if "valid" becomes false
423     return w;
424 }
425
426 double ProbabilityCalculator::calcBackwardWeights(int cur, Address addr, IdiomPrefixTree *tree, set<IdiomPrefixTree*> &matched) {
427     double w = 0;
428     if (tree->isFeature()) {
429         if (matched.find(tree) == matched.end()) {
430             matched.insert(tree);
431             w += tree->getWeight();
432 //          fprintf(stderr, "Backward match idiom with weight %.6lf\n", tree->getWeight());
433         }
434     }
435 //    printf("Start matching at %lx for %dth idiom term\n", addr, cur);
436
437     if (tree->isLeafNode()) return w;
438
439     for (Address prevAddr = addr - 1; prevAddr >= cr->low() && addr - prevAddr <= 15; --prevAddr) {
440         unsigned short entry_id, len;
441         if (!getOpcode(entry_id, len, prevAddr)) continue;
442         if (prevAddr + len != addr) continue;
443
444         IdiomPrefixTree::ChildrenType children = tree->findChildrenWithOpcode(entry_id);
445         if (children.size() == 0) continue;
446         
447         unsigned short arg1, arg2;
448         if (!getArgs(arg1, arg2, prevAddr)) continue;
449         children = tree->findChildrenWithArgs(arg1, arg2, children);
450         if (children.size() == 0) continue;
451
452         for (auto cit = children.begin(); cit != children.end(); ++cit)
453             w += calcBackwardWeights(cur + 1, prevAddr , cit->second, matched);
454
455     }
456     return w;
457 }
458
459 bool ProbabilityCalculator::getOpcode(unsigned short &entry_id, unsigned short &len, Address addr) {
460     if (opcodeCache.find(addr) != opcodeCache.end()) {
461         const pair<unsigned short, unsigned short> &val = opcodeCache[addr];
462         entry_id = val.first;
463         len = val.second;
464         if (len == 0) return false;
465     } else {
466         Instruction::Ptr insn;
467         unsigned char *buf = (unsigned char*)(cs->getPtrToInstruction(addr));
468         if (buf == NULL) { 
469             opcodeCache[addr] = make_pair(JUNK_OPCODE, 0);
470             return false;
471         }
472         InstructionDecoder dec( buf ,  30, cs->getArch()); 
473         insn = dec.decode();
474         if (!insn) {
475             opcodeCache[addr] = make_pair(JUNK_OPCODE, 0);
476             return false;
477         }
478         len = insn->size();
479         if (len == 0) {
480             opcodeCache[addr] = make_pair(JUNK_OPCODE, 0);
481             return false;
482         }
483         
484         const Operation & op = insn->getOperation();
485         entry_id = op.getID();
486         opcodeCache[addr] = make_pair(entry_id, len);
487     }    
488     return true;
489 }
490
491 bool ProbabilityCalculator::getArgs(unsigned short &arg1, unsigned short &arg2, Address addr) {
492     if (operandCache.find(addr) != operandCache.end()) {
493         const pair<unsigned short, unsigned short> &val = operandCache[addr];
494         arg1 = val.first;
495         arg2 = val.second;
496     } else {
497         Instruction::Ptr insn;   
498         unsigned char *buf = (unsigned char*)(cs->getPtrToInstruction(addr));
499         assert(buf != NULL);
500         
501         InstructionDecoder dec( buf ,  30, cs->getArch()); 
502         insn = dec.decode();
503         assert(insn);
504
505         vector<Operand> ops;
506         insn->getOperands(ops);
507         int args[2] = {NOARG,NOARG};
508         for(unsigned int i=0;i<2 && i<ops.size();++i) {
509             Operand & op = ops[i];
510             if (op.getValue()->size() == 0) {
511                 // This is actually an invalid instruction with valid opcode
512                 // so modify the opcode cache to make it invalid
513                 opcodeCache[addr] = make_pair(JUNK_OPCODE, 0);
514                 return false;
515             }
516
517             if(!op.readsMemory() && !op.writesMemory()) {
518                 // register or immediate
519                 set<RegisterAST::Ptr> regs;
520                 op.getReadSet(regs);
521                 op.getWriteSet(regs);  
522                 
523                 if(!regs.empty()) {
524                     if (regs.size() > 1) {
525                         args[i] = MULTIREG;
526                     } else {
527                         args[i] = (*regs.begin())->getID();
528                     }
529                 } else {
530                     // immediate
531                     args[i] = IMMARG;
532                 }
533             } else {
534                 args[i] = MEMARG; 
535             }
536         }
537         arg1 = args[0];
538         arg2 = args[1];
539         operandCache[addr] = make_pair(arg1, arg2);
540     }
541     return true;
542 }
543
544 bool ProbabilityCalculator::enforceOverlappingConstraints(Function *f, 
545                                                           Address cur_addr, 
546                                                           double cur_prob,
547                                                           dyn_hash_map<Address, double> &newFEPProb,
548                                                           dyn_hash_map<Address, double> &newReachingProb,
549                                                           dyn_hash_set<Function*> &newDiscoveredFuncs) {
550     // Only apply the overlapping constraints to un-finalized functions
551     if (finalized.find(f) != finalized.end()) return true;    
552     if (newDiscoveredFuncs.find(f) != newDiscoveredFuncs.end()) return true;
553     newDiscoveredFuncs.insert(f);
554     fprintf(stderr,"  in function at %lx\n", f->addr());
555     for (auto bit = f->blocks().begin(); bit != f->blocks().end(); ++bit) {       
556         if ((*bit)->region() != cr) continue;
557         fprintf(stderr, "  block [%lx,%lx)\n", (*bit)->start(), (*bit)->end());
558         for (Address addr = (*bit)->start(); addr < (*bit)->end(); ++addr) {
559
560             // Meet a byte that is in a function with higher probability: conflict
561             if (double_cmp(cur_prob, getReachingProb(addr)) < 0) {
562                 fprintf(stderr, "    conflict with %lx with reaching prob %.6lf\n", addr, getReachingProb(addr));
563                 return false;
564             } else if (double_cmp(cur_prob, getReachingProb(addr)) > 0) {
565                 newReachingProb[addr] = cur_prob;
566                 newFEPProb[addr] = 0;
567             } else {
568                 // TODO: meet a byte with same prob
569             }
570         }
571     }
572     
573     auto call_edges = f->callEdges();
574     for (auto eit = call_edges.begin(); eit != call_edges.end(); ++eit) {
575         if ((*eit)->type() == CALL_FT) continue;
576         Address target = (*eit)->trg()->start();
577         if (reachingProb.find(target) == reachingProb.end()) continue;
578         if (double_cmp(cur_prob, getFEPProb(target)) > 0) {
579             newFEPProb[target] = cur_prob;
580         }
581         if (double_cmp(cur_prob, getReachingProb(target)) > 0){
582             newReachingProb[target] = cur_prob;
583         }
584         Function *callee = parser->findFuncByEntry(cr, target);
585 //      fprintf(stderr, "    find call target from %lx to %lx\n", (*eit)->src()->last(), target);
586         if (callee == NULL) {
587             parser->parse_at(cr, target, true, GAP);
588             callee = parser->findFuncByEntry(cr, target);
589 //          assert(callee != NULL);  
590         }
591         if (callee == NULL) continue;
592         if (!enforceOverlappingConstraints(callee, cur_addr, cur_prob, newFEPProb, newReachingProb, newDiscoveredFuncs)) return false;
593     }
594     return true;
595 }
596
597 void ProbabilityCalculator::Finalize(dyn_hash_map<Address, double> &newFEPProb,
598                                      dyn_hash_map<Address, double> &newReachingProb,
599                                      dyn_hash_set<Function*> &newDiscoveredFuncs) {
600     for (auto nit = newFEPProb.begin(); nit != newFEPProb.end(); ++nit) {
601         FEPProb[nit->first] = nit->second;
602     }
603     for (auto nit = newReachingProb.begin(); nit != newReachingProb.end(); ++nit) {
604         reachingProb[nit->first] = nit->second;
605     }
606     finalized.insert(newDiscoveredFuncs.begin(), newDiscoveredFuncs.end());
607 }
608
609 void ProbabilityCalculator::Remove(dyn_hash_set<Function*> &newDiscoveredFuncs) {
610     for (auto fit = newDiscoveredFuncs.begin(); fit != newDiscoveredFuncs.end(); ++fit) {
611        parser->remove_func(*fit); 
612     }
613 }
614
615 void ProbabilityCalculator::prioritizedGapParsing() {
616     priority_queue<ProbAndAddr> q;
617     double threshold = model.getProbThreshold();
618
619     // Use the prob from matching idioms and push all the FEP candidates into the priority queue
620     for (auto pit = FEPProb.begin(); pit != FEPProb.end(); ++pit)
621         if (pit->second >= threshold) 
622             q.push(ProbAndAddr(pit->first, pit->second));
623     
624     while (!q.empty()) {
625         ProbAndAddr pa = q.top();
626         q.pop();
627         parser->parse_at(cr, pa.addr, true, GAP);
628
629     }
630
631 }
632 #endif
633