File Coverage

src/panda/dwarf.cc
Criterion Covered Total %
statement 59 329 17.9
branch 31 276 11.2
condition n/a
subroutine n/a
pod n/a
total 90 605 14.8


line stmt bran cond sub pod time code
1             #include "dwarf.h"
2             #include "dl.h"
3             #include // PATH_MAX
4             #include
5             #include
6             #include
7             #include // for debug
8              
9              
10             #include
11              
12             #ifdef _WIN32
13             #include
14             #define PANDA_PATH_MAX MAX_PATH
15             #else
16             #define PANDA_PATH_MAX PATH_MAX
17             #endif
18              
19             namespace panda { namespace backtrace {
20              
21             static StackframePtr dl_produce(const void*);
22             static BacktraceProducer dl_producer(dl_produce);
23              
24 261           static DwarfInfoMap load_dwarf_info(const SharedObjectMap& so_map) {
25 261           DwarfInfoMap result;
26 8352 100         for (auto& so: so_map) {
27 16182 50         auto info = std::make_unique(so);
28 16182 50         string real_path(PANDA_PATH_MAX);
29             Dwarf_Error error;
30              
31             FILE* file;
32 8091 50         file = fopen(so.name.c_str(), "rb");
    50          
33 32364 50         DwarfInfo::file_guard_t file_guard (file, [](auto* f){ if (f) {fclose(f); }});
    50          
34 8091 50         int fd = file ? fileno(file) : 0;
35 8091 50         if (fd > 0) {
36 8091 50         auto err = dwarf_init_b(fd, DW_DLC_READ, DW_GROUPNUMBER_ANY, nullptr, &info->err_arg, &info->debug, &error);
37             //std::cout << "loading '" << so.name << "', code = " << err << "\n";
38 8091 50         if (err == DW_DLV_OK) {
39 8091           if (info->load(std::move(file_guard))) {
40             //std::cout << "dwarf info initialized for " << so.name << "\n";
41             }
42             } else if (err == DW_DLV_ERROR) {
43             //std::cout << "error initializing on " << so.name << " :: " << dwarf_errmsg(error) << "\n";
44             }
45             // use DwarfInfoMap independently whether the real file was loaded. So, if file is not found
46             // we still can produce stack frame with address/offset and .so name
47             }
48 8091 50         result.emplace(so.name, std::move(info));
49             }
50 261           return result;
51             }
52              
53              
54 261           StackframePtr dl_produce(const void* frame_ptr) {
55 522           SharedObjectMap so_map;
56 522           std::vector frames;
57 261 50         gather_info(so_map);
58 522 50         auto map = load_dwarf_info(so_map);
59 261           auto ip_addr = reinterpret_cast(frame_ptr);
60 3631 50         for(auto& it: map) {
61             //std::cout << "resolving " << it.first << "\n";
62 6479           auto frame = it.second->resolve(ip_addr);
63 3370 100         if (frame) return frame;
    100          
64             }
65 0           return StackframePtr();
66             }
67              
68 6           void install_backtracer() {
69 6           panda::Backtrace::install_producer(dl_producer);
70 6           }
71              
72              
73 16182           DwarfInfo::~DwarfInfo() {
74 8091 50         if (debug) {
75 8091           CUs.clear(); // DIEs must be released before debug
76             Dwarf_Error error;
77 8091           auto res = dwarf_finish(debug, &error);
78 8091 50         if (res != DW_DLV_OK) {
79 8091           fprintf(stderr, "dwarf_finish: %s\n", dwarf_errmsg(error));
80             }
81             }
82 8091           }
83              
84 8091           bool DwarfInfo::load(file_guard_t&& guard_) noexcept {
85 8091           guard = std::move(guard_);
86             Dwarf_Error error;
87 8091           for(int cu_number = 0;;++cu_number) {
88 8091           auto cu = std::make_unique(debug, cu_number);
89              
90 8091           auto res = dwarf_next_cu_header_d(debug, true, &cu->header_length,
91 8091           &cu->version_stamp, &cu->abbrev_offset,
92 8091           &cu->address_size, &cu->offset_size,
93 8091           &cu->extension_size, &cu->signature,
94 8091           &cu->typeoffset, nullptr,
95 16182           &cu->header_type,&error);
96 8091 50         if (res != DW_DLV_OK) { break; }
97              
98             /* The CU will have a single sibling, a cu_die. */
99 0           Dwarf_Die cu_die = nullptr;
100 0           res = dwarf_siblingof_b(debug, nullptr, true,&cu_die, &error);
101 0 0         if (res != DW_DLV_OK) { break; }
102              
103 8091 50         dwarf::DieHolder cu_die_holder(cu_die, debug, nullptr);
104 0           cu->cu_die = cu_die_holder.detach();
105 0           CUs.emplace_back(std::move(cu));
106             }
107 8091           return !CUs.empty();
108             }
109              
110              
111 3370           StackframePtr DwarfInfo::resolve(std::uint64_t ip) noexcept {
112 3370 100         if (ip < so_info.begin || ip >= so_info.end) { return StackframePtr(); }
    100          
113              
114 261           auto offset = so_info.get_offset(ip);
115             ///std::cout << "resolving " << std::hex << ip << "/" << offset << " from " << so_info.name << ", CUs: " << CUs.size() << "\n";
116              
117 261 50         for(auto it = CUs.begin(); it != CUs.end(); ++it){
118             //if (r.is_complete()) std::cout << "hit\n";
119 0           auto& cu = *it;
120 0           auto r = cu->resolve(offset);
121 0 0         if (r.is_complete()) { return r.get_frame(ip, so_info); }
    0          
122             }
123              
124             // just fall-back to .so, address & offset
125 3631           auto frame = StackframePtr(new Stackframe());
126 261           frame->library = so_info.name;
127 261           frame->address = ip;
128 261           frame->offset = offset;
129 261           return frame;
130             }
131              
132             namespace dwarf {
133              
134 0           LookupResult::LookupResult(LookupResult&& other) {
135 0           cu = std::move(other.cu);
136 0           subprogram = std::move(other.subprogram);
137 0           offset = std::move(other.offset);
138 0           }
139 0 0         bool LookupResult::is_complete() noexcept { return cu && subprogram; }
    0          
140              
141 0           StackframePtr LookupResult::get_frame(std::uint64_t ip, const SharedObjectInfo& so) noexcept {
142 0           auto frame = StackframePtr(new Stackframe());
143             //if (!is_complete()) { return frame; }
144              
145             //std::cout << "found die: " << (void*) die.die << ", h:" << addr->high << ", l: " << addr->low << ", o:" << offset << "\n";
146              
147 0           frame->address = ip;
148 0           frame->offset = offset;
149 0           frame->library = so.name;
150              
151 0 0         if (subprogram) {
152 0           auto details = subprogram->refine_fn(*this);
153 0           frame->name = details.name;
154 0           frame->line_no = details.line_no;
155             }
156              
157 0 0         if (cu) {
158 0           auto details = cu->refine_cu();
159 0           frame->file = details.name;
160             }
161              
162             ///std::cout << frame->name << " at " << frame->file << ":" << frame->line_no << ", o:" << frame->offset << "\n";
163 0           return frame;
164             }
165              
166              
167 0           DieRC::DieRC(Dwarf_Die die_, Dwarf_Debug debug_, panda::iptr parent_): die{die_}, debug{debug_}, parent{parent_} {}
168 0           DieRC::~DieRC() {
169 0           dwarf_dealloc(debug, die,DW_DLA_DIE);
170 0 0         }
171              
172 0           Dwarf_Die DieRC::resolve_ref(Dwarf_Die source, Dwarf_Half attr) noexcept {
173 0           Dwarf_Die r = nullptr;
174             Dwarf_Attribute attr_val;
175             Dwarf_Error error;
176 0           auto res = dwarf_attr(source, attr, &attr_val, &error);
177 0 0         if (res == DW_DLV_OK) {
178 0           Dwarf_Off attr_offset = 0;
179 0           res = dwarf_global_formref(attr_val,&attr_offset,&error);
180 0 0         if (res == DW_DLV_OK) {
181 0           res = dwarf_offdie_b(debug, attr_offset, true, &r, &error);
182 0 0         if (res == DW_DLV_OK) { return r; }
183             }
184             }
185 0           return nullptr;
186             }
187              
188 0           panda::iptr DieRC::discover(Dwarf_Die target) noexcept {
189 0           auto p = parent;
190 0 0         while (p->parent) { p = p->parent; }
191              
192 0           DieHolder root(p);
193             /* no need to scan CU-siblings */
194 0           Dwarf_Die child_die = nullptr;
195             Dwarf_Error error;
196 0           auto res = dwarf_child(p->die, &child_die, &error);
197              
198              
199 0 0         if(res == DW_DLV_OK) {
200 0           DieHolder child(child_die, debug, &root);
201             Dwarf_Off off;
202 0           res = dwarf_dieoffset(target, &off, &error);
203 0 0         assert(res == DW_DLV_OK);
204 0           return discover(off, child);
205             }
206              
207 0           std::abort();
208             }
209              
210 0           panda::iptr DieRC::discover(Dwarf_Off target_offset, DieHolder& node) noexcept {
211             Dwarf_Error error;
212             Dwarf_Off off;
213             int res;
214              
215 0           res = dwarf_dieoffset(node.die, &off, &error);
216 0 0         assert(res == DW_DLV_OK);
217 0 0         if (off == target_offset) { return node.detach(); }
218 0 0         if (off > target_offset) { return panda::iptr(); } /* do not lookup for fail branch */
219              
220             // in-breadth: check for siblings
221             Dwarf_Die child_die;
222 0           res = dwarf_siblingof_b(debug, node.die, true, &child_die, &error);
223 0 0         if (res == DW_DLV_OK) {
224 0           DieHolder child(child_die, debug, node.parent);
225 0 0         auto found = discover(target_offset, child);
226 0 0         if (found) { return found; }
    0          
227             }
228              
229             // in-depth: check for child
230 0           res = dwarf_child(node.die, &child_die, &error);
231 0 0         if (res == DW_DLV_OK) {
232 0           DieHolder child(child_die, debug, &node);
233 0 0         auto found = discover(target_offset, child);
234 0 0         if (found) { return found; }
    0          
235             }
236              
237 0           return panda::iptr();
238             }
239              
240              
241 0           void DieRC::refine_fn_name(Dwarf_Die it, FunctionDetails& details) noexcept {
242 0 0         if (!details.name) {
243             Dwarf_Error error;
244             Dwarf_Attribute attr_name;
245 0           auto res = dwarf_attr(it, DW_AT_name, &attr_name, &error);
246 0 0         if (res == DW_DLV_OK) {
247 0 0         iptr node = (it == die) ? iptr(this) : discover(it);
248 0           details.name = node->gather_fqn();
249             }
250             }
251 0           }
252              
253 0           string DieRC::gather_fqn() noexcept {
254             Dwarf_Error error;
255 0           char* name = nullptr;
256 0           auto res = dwarf_diename(die, &name, &error);
257 0 0         assert(res == DW_DLV_OK);
258              
259 0           string r(name);
260              
261 0           auto p = parent;
262 0 0         while(p) {
263 0           Dwarf_Half tag = 0;
264 0           res = dwarf_tag(p->die, &tag, &error);
265 0 0         assert(res == DW_DLV_OK);
266 0 0         if (tag == DW_TAG_structure_type || tag == DW_TAG_class_type || tag == DW_TAG_namespace) {
    0          
    0          
267             Dwarf_Attribute attr_name;
268 0           res = dwarf_attr(p->die, DW_AT_name, &attr_name, &error);
269 0 0         if (res == DW_DLV_OK) {
270             char* prefix;
271 0           dwarf_formstring(attr_name, &prefix, &error);
272 0 0         assert(res == DW_DLV_OK);
273 0           r = string(prefix) + "::" + r;
274             }
275             }
276 0           p = p->parent;
277             }
278              
279 0           return r;
280             }
281              
282 0           void DieRC::refine_fn_line(LookupResult& lr, FunctionDetails& details) noexcept {
283             /* currently it detects lines only in the current CU (compilation unit */
284             using LineContextHolder = std::unique_ptr>;
285 0           auto& cu = lr.cu;
286 0 0         if (!cu) { return; }
287              
288             Dwarf_Error error;
289             char* cu_name_raw;
290 0           auto res = dwarf_die_text(cu->die, DW_AT_name, &cu_name_raw, &error);
291 0 0         if (res != DW_DLV_OK) { return; }
292 0           string cu_name(cu_name_raw);
293              
294             Dwarf_Unsigned line_version;
295             Dwarf_Small table_type;
296             Dwarf_Line_Context line_context;
297 0           res = dwarf_srclines_b(cu->die, &line_version, &table_type,&line_context,&error);
298 0 0         if (res != DW_DLV_OK) { return; }
299 0 0         LineContextHolder line_context_guard(&line_context, [](auto it){ dwarf_srclines_dealloc_b(*it); });
300              
301 0           Dwarf_Signed base_index, end_index, cu_index = -1;
302             Dwarf_Signed file_count;
303 0           res = dwarf_srclines_files_indexes(line_context, &base_index,&file_count,&end_index, &error);
304 0 0         if (res != DW_DLV_OK) { return; }
305              
306             //std::cout << "looking indices for " << cu_name << ", b = " << base_index << ", e = " << end_index << "\n";
307              
308 0 0         for (Dwarf_Signed i = base_index; i < end_index; ++i) {
309             Dwarf_Unsigned modtime;
310             Dwarf_Unsigned flength;
311             Dwarf_Unsigned dirindex;
312             const char *source_name;
313              
314 0           res = dwarf_srclines_files_data(line_context, i, &source_name ,&dirindex, &modtime, &flength, &error);
315 0 0         if (res != DW_DLV_OK) { return; }
316 0 0         if (cu_name.find(source_name) != string::npos) {
317 0 0         if (dirindex) {
318             const char* dir_name;
319 0           res = dwarf_srclines_include_dir_data(line_context, static_cast(dirindex), &dir_name, &error);
320 0 0         if (res != DW_DLV_OK) { return; }
321              
322 0 0         if (cu_name.find(dir_name) != string::npos) {
323 0           cu_index = i;
324 0           break;
325             }
326             } else {
327             /* no directory / current directory */
328 0           cu_index = i;
329 0           break;
330             }
331             }
332             }
333 0 0         if (cu_index == -1) { return; }
334              
335             Dwarf_Line *linebuf;
336             Dwarf_Signed linecount;
337 0           res = dwarf_srclines_from_linecontext(line_context, &linebuf, &linecount, &error);
338 0 0         if (res != DW_DLV_OK) { return; }
339              
340              
341 0           bool found = false;
342 0           Dwarf_Unsigned prev_lineno = 0;
343 0 0         for(Dwarf_Signed i = 0; i < linecount; ++i) {
344 0           Dwarf_Unsigned lineno = 0;
345 0           Dwarf_Unsigned file_index = 0;
346 0           Dwarf_Addr lineaddr = 0;
347              
348 0           res = dwarf_lineno(linebuf[i], &lineno, &error);
349 0 0         if (res != DW_DLV_OK) { return; }
350 0           res = dwarf_lineaddr(linebuf[i], &lineaddr, &error);
351 0 0         if (res != DW_DLV_OK) { return; }
352 0           res = dwarf_line_srcfileno(linebuf[i],&file_index, &error);
353 0 0         if (res != DW_DLV_OK) { return; }
354 0 0         if (file_index != static_cast(cu_index)) { continue; }
355              
356 0 0         if (lineaddr >= lr.offset) { found = true; break; }
357 0           else { prev_lineno = lineno; }
358             }
359              
360 0 0         if (found) {
361 0 0         details.line_no = prev_lineno;
362             }
363             //std::cout << "refine_fn_line " << found << " :: " << lr.offset << " :: " << std::dec << prev_lineno << "\n";
364             }
365              
366              
367 0           void DieRC::refine_fn_line_fallback(Dwarf_Die it, FunctionDetails& details) noexcept {
368 0 0         if (!details.line_no) {
369             Dwarf_Error error;
370              
371             Dwarf_Attribute attr_line;
372 0           auto res = dwarf_attr(it, DW_AT_decl_line, &attr_line, &error);
373 0 0         if (res == DW_DLV_OK) {
374             Dwarf_Unsigned line;
375 0           res = dwarf_formudata(attr_line, &line, &error);
376 0 0         if (res == DW_DLV_OK) { details.line_no = line; }
377             }
378             }
379 0           }
380              
381 0           FunctionDetails DieRC::refine_fn(LookupResult& lr) noexcept {
382 0           FunctionDetails r;
383              
384 0           refine_fn_name(die, r);
385 0           refine_fn_line(lr, r);
386 0           refine_fn_line_fallback(die, r);
387              
388 0 0         if (!r.line_no || !r.name) {
    0          
    0          
389 0           auto die_spec = resolve_ref(die, DW_AT_specification);
390 0 0         if (die_spec) {
391 0           refine_fn_spec(die_spec, r);
392             }
393             }
394              
395 0 0         if (!r.line_no || !r.name) {
    0          
    0          
396 0           auto die_ao = resolve_ref(die, DW_AT_abstract_origin);
397 0 0         if (die_ao) {
398 0           refine_fn_ao(die_ao, r);
399 0 0         if (die_ao) { refine_fn_ao(die_ao, r); }
400             }
401             }
402              
403 0           return r;
404             }
405              
406 0           void DieRC::refine_fn_ao(Dwarf_Die abstract_origin, FunctionDetails& details) noexcept {
407 0           auto die_spec = resolve_ref(abstract_origin, DW_AT_specification);
408 0 0         if (die_spec) { refine_fn_spec(die_spec, details); }
409 0           }
410              
411              
412 0           void DieRC::refine_fn_spec(Dwarf_Die specification, FunctionDetails& details) noexcept {
413 0           refine_fn_name(specification, details);
414 0           refine_fn_line_fallback(specification, details);
415 0           }
416              
417              
418 0           CUDetails DieRC::refine_cu() noexcept {
419 0           CUDetails r;
420             Dwarf_Error error;
421 0           char* name = nullptr;
422 0           auto res = dwarf_die_text(die, DW_AT_name, &name, &error);
423 0 0         if (res == DW_DLV_OK && name) { r.name = name; }
    0          
424 0           return r;
425             }
426              
427 0           DieHolder::DieHolder(panda::iptr owner_):die{owner_->die}, debug{owner_->debug}, parent{nullptr}, owner{owner_} {
428 0 0         assert(!owner || owner->die);
    0          
429 0           }
430 0           DieHolder::DieHolder(Dwarf_Die die_, Dwarf_Debug debug_, DieHolder* parent_): die{die_}, debug{debug_}, parent{parent_}{
431 0           }
432              
433 0           DieHolder::~DieHolder() {
434 0 0         if (!owner) { dwarf_dealloc(debug, die,DW_DLA_DIE); }
435 0           }
436              
437 0           panda::iptr DieHolder::detach(){
438 0 0         if (!owner) {
439 0 0         panda::iptr parent_ptr(parent ? parent->detach() : nullptr);
    0          
440 0 0         owner = panda::iptr(new DieRC(die, debug, parent_ptr));
    0          
441             }
442 0           return owner;
443             }
444              
445 0           panda::optional DieHolder::get_addr() noexcept {
446             Dwarf_Error error;
447 0           Dwarf_Addr low = 0;
448 0           Dwarf_Addr high = 0;
449              
450 0           auto res = dwarf_lowpc(die,&low,&error);
451 0 0         if (res == DW_DLV_OK) {
452             Dwarf_Form_Class formclass;
453 0           Dwarf_Half form = 0;
454 0           res = dwarf_highpc_b(die,&high,&form,&formclass,&error);
455 0 0         if (res == DW_DLV_OK) {
456 0 0         if (formclass == DW_FORM_CLASS_CONSTANT) { high += low; }
457 0           return panda::optional{HighLow{low, high}};
458             }
459             }
460             /* Cannot check ranges yet, we don't know the ranges base offset yet. */
461 0           return panda::optional();
462             }
463              
464 0           Match DieHolder::contains(std::uint64_t offset) noexcept {
465 0           auto addr = get_addr();
466 0 0         if (addr) {
467 0 0         if ((addr->high >= offset) || (addr->low < offset)) {
    0          
    0          
468 0           return Match::no;
469             } else {
470 0           return Match::yes;
471             }
472             } else {
473             Dwarf_Error error;
474             Dwarf_Attribute attr;
475 0           auto res = dwarf_attr(die, DW_AT_ranges, &attr, &error);
476 0 0         if (res == DW_DLV_OK) {
477             Dwarf_Off ranges_offset;
478 0           res = dwarf_global_formref(attr, &ranges_offset, &error);
479 0 0         if (res == DW_DLV_OK) {
480             Dwarf_Ranges *ranges;
481             Dwarf_Signed ranges_count;
482             Dwarf_Unsigned byte_count;
483 0           res = dwarf_get_ranges_a(debug, ranges_offset, die, &ranges, &ranges_count, &byte_count, &error);
484 0 0         if (res == DW_DLV_OK) {
485 0           Dwarf_Addr baseaddr = 0;
486 0 0         for(int i = 0; i < ranges_count; ++i) {
487 0           auto r = ranges[i];
488 0           switch (r.dwr_type) {
489 0           case DW_RANGES_ADDRESS_SELECTION: baseaddr = r.dwr_addr2; break;
490             case DW_RANGES_ENTRY: {
491 0           auto low = r.dwr_addr1 + baseaddr;
492 0           auto high = r.dwr_addr2 + baseaddr;
493 0 0         auto matches = (low <= offset) && (high > offset);
    0          
494             //std::cout << "l = " << low << ", h = " << high << ", attr = " << ranges_offset << ", o = " << offset << " " << (matches ? "Y" : "N") << "\n";
495 0 0         if (matches) {return Match::yes; }
496 0           break;
497             }
498 0           default: break;
499              
500             }
501             }
502 0 0         if (ranges_count > 0) { return Match::no; }
503             }
504             }
505             }
506             }
507 0           return Match::unknown;
508             }
509              
510              
511              
512 16182           CU::CU(Dwarf_Debug debug_, int number_): debug{debug_}, number{number_} {
513 8091           memset(&signature,0, sizeof(signature));
514 8091           }
515              
516 0           LookupResult CU::resolve(std::uint64_t offset) noexcept {
517 0 0         assert(cu_die);
518 0           LookupResult lr;
519 0           DieHolder dh(cu_die);
520 0           resolve(offset, dh, lr);
521 0           return lr;
522             }
523              
524 0           bool CU::resolve(std::uint64_t offset, DieHolder& die, LookupResult& lr) noexcept {
525             //std::cout << "resolving die: " << (void*) die.die << "\n";
526              
527 0           auto er = examine(offset, die, lr);
528 0 0         if (er || lr.is_complete()) { return er; }
    0          
    0          
529              
530 0           Dwarf_Die child_die = nullptr;
531             Dwarf_Error error;
532             int res;
533              
534             // in-breadth: check for siblings
535 0           res = dwarf_siblingof_b(debug, die.die, true, &child_die, &error);
536 0 0         if (res == DW_DLV_OK) {
537 0           DieHolder child(child_die, debug, die.parent);
538 0           resolve(offset, child, lr);
539 0 0         if (lr.is_complete()) { return true; }
    0          
540 0 0         } else if (res == DW_DLV_NO_ENTRY) {
541             /* ignore */
542             } else {
543 0           return true;
544             }
545              
546             // in-depth: check for child
547 0           res = dwarf_child(die.die, &child_die, &error);
548 0 0         if(res == DW_DLV_OK) {
549 0           DieHolder child(child_die, debug, &die);
550 0           resolve(offset, child, lr);
551 0 0         if (lr.is_complete()) { return true; }
    0          
552 0 0         } else if (res == DW_DLV_NO_ENTRY) {
553             /* ignore */
554 0           return true;
555             } else {
556 0           return true;
557             }
558              
559 0           return true;
560             }
561              
562 0           bool CU::examine(std::uint64_t offset, DieHolder &die, LookupResult& lr) noexcept {
563             Dwarf_Error error;
564 0           Dwarf_Half tag = 0;
565 0 0         assert(die.die);
566 0           auto res = dwarf_tag(die.die, &tag, &error);
567 0 0         if (res != DW_DLV_OK) { return true; }
568              
569 0 0         if( tag == DW_TAG_subprogram ||
    0          
570 0           tag == DW_TAG_inlined_subroutine) {
571 0           switch (die.contains(offset)) {
572             case Match::yes: {
573 0           lr.subprogram = die.detach();
574 0           lr.offset = offset;
575 0           return lr.is_complete();
576             }
577 0           case Match::unknown: return false;
578 0           case Match::no: return false;
579             }
580             }
581 0 0         else if(tag == DW_TAG_compile_unit) {
582 0           switch (die.contains(offset)) {
583             case Match::yes: {
584 0           lr.cu = die.detach();
585 0           return lr.is_complete();
586             }
587 0           case Match::unknown: return false;
588 0           case Match::no: return true;
589             }
590             }
591              
592             /* keep scaning */
593 0           return false;
594             }
595              
596 24 50         }}}
    50