00001 #include "StackTrace.h"
00002 #include <stdio.h>
00003
00004 #if defined(HAVE_BFD) && HAVE_BFD!=0
00005 # include <bfd.h>
00006 # include "libiberty.h"
00007 # include "demangle.h"
00008 # include <sys/stat.h>
00009 # include <string.h>
00010 #endif
00011
00012 #ifdef ST_UNUSED
00013 #elif defined(__GNUC__) && __GNUC__>3
00014
00015 # define ST_UNUSED(x) UNUSED_##x __attribute__((unused))
00016
00017 # define ST_BODY_UNUSED(x)
00018
00019 #elif defined(__LCLINT__)
00020
00021 # define ST_UNUSED(x) x
00022
00023 # define ST_BODY_UNUSED(x)
00024
00025 #else
00026
00027 # define ST_UNUSED(x) UNUSED_##x
00028
00029 # define ST_BODY_UNUSED(x) (void)UNUSED_##x
00030 #endif
00031
00032 #ifdef __cplusplus
00033 namespace stacktrace {
00034 #endif
00035
00036 #if defined(HAVE_BFD) && HAVE_BFD!=0
00037 char * bfd_objfile=NULL;
00038 #endif
00039
00040
00041 const char * BFD_DEFAULT_TARGET="powerpc-apple-darwin8.5.0";
00042
00043
00044 #if defined(HAVE_BFD) && HAVE_BFD!=0
00045 int loadStackTraceSymbols(const char* objfile) {
00046 static int bfdInited=0;
00047 if(!bfdInited) {
00048 bfd_init();
00049 if(!bfd_set_default_target(BFD_DEFAULT_TARGET)) {
00050 bfd_error_type err=bfd_get_error();
00051 fprintf(stderr,"can't set BFD default target to `%s': %s\n",BFD_DEFAULT_TARGET,bfd_errmsg(err));
00052 fprintf(stderr,"stacktrace::loadStackTraceSymbols(%s) failed\n",objfile);
00053 return err;
00054 }
00055 bfdInited=1;
00056 }
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085 bfd_objfile=strdup(objfile);
00086 return 0;
00087 }
00088 #else
00089 int loadStackTraceSymbols(const char* ST_UNUSED(objfile)) {
00090 ST_BODY_UNUSED(objfile);
00091 return 0;
00092 }
00093 #endif
00094
00095 int unrollStackFrame(struct StackFrame* curFrame, struct StackFrame* nextFrame) {
00096 void* nsp=NULL;
00097 machineInstruction * nra=NULL;
00098
00099 #if defined(__i386__) || defined(__x86_64__) || defined(__amd64__)
00100 if(curFrame==NULL)
00101 return 0;
00102 curFrame->caller=NULL;
00103 if(nextFrame==NULL)
00104 return 0;
00105 if(curFrame->sp==NULL)
00106 return 0;
00107 if(((void**)curFrame->sp)-1==NULL)
00108 return 0;
00109 nsp=((void***)curFrame->sp)[-1];
00110 if(nsp==NULL)
00111 return 0;
00112 nsp=(void**)nsp+1;
00113 nra=*((machineInstruction**)curFrame->sp);
00114 if(nsp<=curFrame->sp) {
00115 fprintf(stderr,"stacktrace::unrollStackFrame(sp=%p,ra=%p) directed to invalid next frame: (sp=%p,ra=%p)\n",curFrame->sp,curFrame->ra,nsp,nra);
00116 return 0;
00117 }
00118 # ifdef DEBUG_STACKTRACE
00119 if(curFrame->debug)
00120 printf("( %p %p ) -> { %p %p }\n",curFrame->sp,curFrame->ra,nsp,nra);
00121 nextFrame->debug=curFrame->debug;
00122 # endif
00123 nextFrame->sp=nsp;
00124 nextFrame->ra=nra;
00125 curFrame->caller=nextFrame;
00126 return 1;
00127 #endif
00128 #ifdef __POWERPC__
00129 if(curFrame==NULL)
00130 return 0;
00131 curFrame->caller=NULL;
00132 if(nextFrame==NULL)
00133 return 0;
00134 if(curFrame->sp==NULL)
00135 return 0;
00136 if(*(void**)curFrame->sp==NULL)
00137 return 0;
00138 nsp=*(void**)curFrame->sp;
00139 nra=((machineInstruction**)nsp)[2];
00140 if(nsp<=curFrame->sp) {
00141 fprintf(stderr,"stacktrace::unrollStackFrame(sp=%p,ra=%p) directed to invalid next frame: (sp=%p,ra=%p)\n",curFrame->sp,curFrame->ra,nsp,nra);
00142 return 0;
00143 }
00144 # ifdef DEBUG_STACKTRACE
00145 if(curFrame->debug)
00146 printf("( %p %p ) -> { %p %p }\n",curFrame->sp,curFrame->ra,nsp,nra);
00147 nextFrame->debug=curFrame->debug;
00148 # endif
00149 nextFrame->sp=nsp;
00150 nextFrame->ra=nra;
00151 curFrame->caller=nextFrame;
00152 return 1;
00153 #endif
00154 #if defined(__MIPSEL__) || defined(__MIPS__)
00155
00156 machineInstruction * ins;
00157 const machineInstruction * INS_BASE=(const machineInstruction *)0x2000;
00158 if(curFrame==NULL)
00159 return 0;
00160 curFrame->caller=NULL;
00161 if(nextFrame==NULL || curFrame==NULL || curFrame->sp==NULL)
00162 return 0;
00163
00164 #ifdef __pic__
00165 ins = reinterpret_cast<machineInstruction*>(curFrame->gp-curFrame->ra);
00166 #else
00167 ins = curFrame->ra;
00168 #endif
00169
00170 for(; ins>=INS_BASE; ins--) {
00171
00172
00173
00174
00175 if ( ( *ins & 0xffff0000 ) == 0xafbf0000 )
00176 {
00177
00178 int offset = *ins & 0x000ffff;
00179
00180
00181 if (offset & 0x3)
00182 return 0;
00183
00184 nra = *reinterpret_cast<machineInstruction**>((char*)curFrame->sp + offset);
00185 break;
00186 }
00187
00188
00189
00190
00191 if ( *ins == 0x37e80000 ) {
00192 # ifdef DEBUG_STACKTRACE
00193 if(curFrame->debug)
00194 printf("( %p %p %p ) -> { kernel? }\n",curFrame->sp,curFrame->ra,curFrame->gp);
00195 # endif
00196 return 0;
00197 }
00198 }
00199
00200 for(; ins>=INS_BASE; ins--) {
00201
00202
00203
00204
00205
00206
00207
00208 if ( ( *ins & 0xffff0000 ) == 0x27bd0000 ) {
00209
00210
00211 int offset = ( *ins & 0x0000ffff ) | 0xffff0000;
00212
00213
00214 if (offset & 0x3)
00215 return 0;
00216
00217 nsp = (char*)curFrame->sp - offset;
00218 break;
00219 }
00220 }
00221
00222
00223 if(ins>=INS_BASE) {
00224 if(nsp<=curFrame->sp) {
00225 #ifdef __pic__
00226 fprintf(stderr,"stacktrace::unrollStackFrame(sp=%p,ra=%p,gp=%p) directed to invalid next frame: (sp=%p,ra=%p,gp=%p)\n",curFrame->sp,(void*)curFrame->ra,(void*)curFrame->gp,nsp,nra,(void*)(reinterpret_cast<size_t*>(nsp)[4]));
00227 #else
00228 fprintf(stderr,"stacktrace::unrollStackFrame(sp=%p,ra=%p) directed to invalid next frame: (sp=%p,ra=%p)\n",curFrame->sp,(void*)curFrame->ra,nsp,nra);
00229 #endif
00230 return 0;
00231 }
00232
00233 #ifdef __pic__
00234 # ifdef DEBUG_STACKTRACE
00235 if(curFrame->debug)
00236 printf("( %p %p %p ) -> { %p %p %p }\n",curFrame->sp,curFrame->ra,curFrame->gp,nsp,nra,reinterpret_cast<size_t*>(nsp)[4]);
00237 nextFrame->debug=curFrame->debug;
00238 # endif
00239
00240
00241
00242
00243
00244
00245
00246
00247 nextFrame->sp=nsp;
00248
00249 if(reinterpret_cast<size_t>(nra)>reinterpret_cast<size_t*>(nsp)[4]) {
00250 nextFrame->gp = curFrame->gp;
00251 } else {
00252 nextFrame->gp = reinterpret_cast<size_t*>(nsp)[4];
00253 }
00254 nextFrame->ra = nextFrame->gp-reinterpret_cast<size_t>(nra);
00255 #else
00256 # ifdef DEBUG_STACKTRACE
00257 if(curFrame->debug)
00258 printf("( %p %p ) -> { %p %p }\n",curFrame->sp,curFrame->ra,nsp,nra);
00259 nextFrame->debug=curFrame->debug;
00260 # endif
00261 nextFrame->sp=nsp;
00262 nextFrame->ra=nra;
00263 #endif
00264 curFrame->caller=nextFrame;
00265 return 1;
00266 }
00267 #ifdef __pic__
00268 # ifdef DEBUG_STACKTRACE
00269 if(curFrame->debug)
00270 printf("( %p %p %p ) -> { %p %p --- }\n",curFrame->sp,curFrame->ra,curFrame->gp,nsp,nra);
00271 # endif
00272 #else
00273 # ifdef DEBUG_STACKTRACE
00274 if(curFrame->debug)
00275 printf("( %p %p ) -> { %p %p }\n",curFrame->sp,curFrame->ra,nsp,nra);
00276 # endif
00277 #endif
00278 return 0;
00279 #endif
00280 }
00281
00282 void getCurrentStackFrame(struct StackFrame* frame) {
00283 void** csp=NULL;
00284 machineInstruction* cra=NULL;
00285
00286 #ifdef __POWERPC__
00287 __asm __volatile__ ("mr %0,r1" : "=r"(csp) );
00288 __asm __volatile__ ("mflr %0" : "=r"(cra) );
00289 #endif
00290
00291 #if defined(__MIPSEL__) || defined(__MIPS__)
00292 #ifdef __pic__
00293 size_t cgp=0;
00294 __asm __volatile__ ("move %0,$gp" : "=r"(cgp) );
00295 #endif
00296 __asm __volatile__ ("move %0,$sp" : "=r"(csp) );
00297 __asm __volatile__ ("jal readepc; nop; readepc: move %0,$ra" : "=r"(cra) );
00298 #endif
00299
00300 #if defined(__i386__) || defined(__x86_64__) || defined(__amd64__)
00301 __asm __volatile__ ("movl %%ebp,%0" : "=m"(csp) );
00302 csp++;
00303
00304 cra=*((machineInstruction**)csp);
00305 csp=((void***)csp)[-1]+1;
00306 #endif
00307
00308 frame->sp=csp;
00309 #ifdef __pic__
00310 frame->ra=cgp-reinterpret_cast<size_t>(cra);
00311 frame->gp=cgp;
00312 #else
00313 frame->ra=cra;
00314 #endif
00315
00316 #if !defined(__i386__) && !defined(__x86_64__) && !defined(__amd64__)
00317
00318
00319 unrollStackFrame(frame,frame);
00320 #endif
00321 }
00322
00323 void freeStackTrace(struct StackFrame* frame) {
00324 while(frame!=NULL) {
00325 struct StackFrame * next=frame->caller;
00326 free(frame);
00327 if(frame==next)
00328 return;
00329 frame=next;
00330 }
00331 }
00332
00333 struct StackFrame* allocateStackTrace(unsigned int size) {
00334 struct StackFrame * frame=NULL;
00335 while(size--!=0) {
00336 struct StackFrame * prev = (struct StackFrame *)malloc(sizeof(struct StackFrame));
00337 prev->caller=frame;
00338 frame=prev;
00339 }
00340 return frame;
00341 }
00342
00343
00344 struct StackFrame * recordStackTrace(unsigned int limit, unsigned int skip) {
00345 struct StackFrame cur;
00346 #ifdef DEBUG_STACKTRACE
00347 cur.debug=0;
00348 #endif
00349 if(limit==0)
00350 return NULL;
00351 getCurrentStackFrame(&cur);
00352 for(; skip!=0; skip--)
00353 if(!unrollStackFrame(&cur,&cur))
00354 return NULL;
00355 struct StackFrame * prev = (struct StackFrame *)malloc(sizeof(struct StackFrame));
00356 #ifdef DEBUG_STACKTRACE
00357 prev->debug=0;
00358 #endif
00359 unrollStackFrame(&cur,prev);
00360 for(; limit!=0; limit--) {
00361 struct StackFrame * next = (struct StackFrame *)malloc(sizeof(struct StackFrame));
00362 #ifdef DEBUG_STACKTRACE
00363 next->debug=0;
00364 #endif
00365 if(!unrollStackFrame(prev,next)) {
00366
00367 free(next);
00368 prev->caller=NULL;
00369 return cur.caller;
00370 }
00371 prev=next;
00372 }
00373
00374 prev->caller=prev;
00375 return cur.caller;
00376 }
00377
00378 struct StackFrame * recordOverStackTrace(struct StackFrame* frame, unsigned int skip) {
00379 struct StackFrame cur;
00380 #ifdef DEBUG_STACKTRACE
00381 cur.debug=0;
00382 #endif
00383 if(frame==NULL)
00384 return frame;
00385 getCurrentStackFrame(&cur);
00386 for(; skip!=0; skip--)
00387 if(!unrollStackFrame(&cur,&cur))
00388 return frame;
00389 unrollStackFrame(&cur,frame);
00390 for(; frame->caller!=NULL && frame->caller!=frame; frame=frame->caller) {
00391 cur.caller=frame->caller;
00392 if(!unrollStackFrame(frame,frame->caller))
00393 return cur.caller;
00394 }
00395
00396 frame->caller=frame;
00397 return cur.caller;
00398 }
00399
00400 #if defined(HAVE_BFD) && HAVE_BFD!=0
00401 bfd * abfd = NULL;
00402 static asymbol **syms;
00403 #endif
00404
00405
00406 void beginDisplay() {
00407 #if defined(HAVE_BFD) && HAVE_BFD!=0
00408 const char* consolation=", try addr2line or trace_lookup";
00409 if(bfd_objfile==NULL) {
00410 fprintf(stderr,"No symbols are loaded%s.\n",consolation);
00411 return;
00412 }
00413 abfd = bfd_openr(bfd_objfile, NULL);
00414 if(abfd==NULL) {
00415 bfd_error_type err=bfd_get_error();
00416 fprintf(stderr,"Can't open object file `%s': %s\n",bfd_objfile,bfd_errmsg(err));
00417 fprintf(stderr,"stacktrace::beginDisplay() failed%s.\n",consolation);
00418 return;
00419 }
00420 if(bfd_check_format (abfd, bfd_archive)) {
00421 bfd_error_type err=bfd_get_error();
00422 fprintf(stderr,"Can't get addresses from archive `%s': %s\n",bfd_objfile,bfd_errmsg(err));
00423 if(!bfd_close(abfd))
00424 fprintf(stderr,"Can't close object file `%s': %s\n",bfd_objfile,bfd_errmsg(bfd_get_error()));
00425 fprintf(stderr,"stacktrace::beginDisplay() failed%s.\n",consolation);
00426 abfd=NULL;
00427 return;
00428 }
00429 {
00430 char **matching;
00431 if(!bfd_check_format_matches(abfd, bfd_object, &matching)) {
00432 bfd_error_type err=bfd_get_error();
00433 fprintf(stderr,"Can't get archive `%s' format doesn't match bfd_object: %s\n",bfd_objfile,bfd_errmsg(err));
00434 if (err == bfd_error_file_ambiguously_recognized) {
00435 char** p=matching;
00436 fprintf(stderr,"Matching formats:");
00437 while (*p)
00438 fprintf (stderr, " %s", *p++);
00439 fputc ('\n', stderr);
00440 free (matching);
00441 }
00442 if(!bfd_close(abfd))
00443 fprintf(stderr,"Can't close object file `%s': %s\n",bfd_objfile,bfd_errmsg(bfd_get_error()));
00444 fprintf(stderr,"stacktrace::beginDisplay() failed%s.\n",consolation);
00445 abfd=NULL;
00446 }
00447 }
00448 {
00449 long symcount;
00450 unsigned int size;
00451
00452 if ((bfd_get_file_flags (abfd) & HAS_SYMS) == 0)
00453 return;
00454
00455 symcount = bfd_read_minisymbols (abfd, FALSE, (void *) &syms, &size);
00456 if (symcount == 0)
00457 symcount = bfd_read_minisymbols (abfd, TRUE , (void *) &syms, &size);
00458
00459 if (symcount < 0) {
00460 bfd_error_type err=bfd_get_error();
00461 fprintf(stderr,"Can't read minisymbols from `%s': %s\n",bfd_get_filename (abfd),bfd_errmsg(err));
00462 if(!bfd_close(abfd))
00463 fprintf(stderr,"Can't close object file `%s': %s\n",bfd_objfile,bfd_errmsg(bfd_get_error()));
00464 fprintf(stderr,"stacktrace::beginDisplay() failed%s.\n",consolation);
00465 abfd=NULL;
00466 }
00467 if(abfd!=NULL) {
00468 fprintf(stderr,"Stack trace:\n");
00469 return;
00470 }
00471 }
00472 fprintf(stderr,"Stack trace:");
00473 #else
00474 # ifdef PLATFORM_APERIOS
00475 fprintf(stderr,"Run trace_lookup:");
00476 # else
00477 const char* consolation=", try addr2line or trace_lookup";
00478 fprintf(stderr,"libBFD unavaible%s:\n",consolation);
00479 fprintf(stderr,"Stack trace:");
00480 # endif
00481 #endif
00482 }
00483
00484 #if defined(HAVE_BFD) && HAVE_BFD!=0
00485
00486
00487
00488 char * demangle (const char *name) {
00489 char *res, *alloc;
00490 const char *pre, *suf;
00491 size_t pre_len;
00492
00493 if (abfd != NULL && bfd_get_symbol_leading_char (abfd) == name[0])
00494 ++name;
00495
00496
00497
00498
00499
00500 pre = name;
00501 while (*name == '.')
00502 ++name;
00503 pre_len = name - pre;
00504
00505 alloc = NULL;
00506 suf = strchr (name, '@');
00507 if (suf != NULL)
00508 {
00509 alloc = xmalloc (suf - name + 1);
00510 memcpy (alloc, name, suf - name);
00511 alloc[suf - name] = '\0';
00512 name = alloc;
00513 }
00514
00515 res = cplus_demangle (name, DMGL_ANSI | DMGL_PARAMS);
00516 if (res != NULL)
00517 {
00518
00519 if (pre_len != 0 || suf != NULL)
00520 {
00521 size_t len;
00522 size_t suf_len;
00523 char *final;
00524
00525 if (alloc != NULL)
00526 free (alloc);
00527
00528 len = strlen (res);
00529 if (suf == NULL)
00530 suf = res + len;
00531 suf_len = strlen (suf) + 1;
00532 final = xmalloc (pre_len + len + suf_len);
00533
00534 memcpy (final, pre, pre_len);
00535 memcpy (final + pre_len, res, len);
00536 memcpy (final + pre_len + len, suf, suf_len);
00537 free (res);
00538 res = final;
00539 }
00540
00541 return res;
00542 }
00543
00544 if (alloc != NULL)
00545 free (alloc);
00546
00547 return xstrdup (pre);
00548 }
00549
00550 struct AddrLookupInfo {
00551 bfd_vma addr;
00552 const char *filename;
00553 const char *functionname;
00554 unsigned int line;
00555 bfd_boolean found;
00556 };
00557
00558
00559 static void stackTraceFindAddress (bfd *fabfd, asection *section, void *data) {
00560 struct AddrLookupInfo * info=(struct AddrLookupInfo *)data;
00561 bfd_vma vma;
00562 bfd_size_type size;
00563
00564 #ifdef bfd_get_section_size
00565 printf("searching %p: %s %x-%x\n",info->addr,section->name,bfd_get_section_vma (fabfd, section),bfd_get_section_vma (fabfd, section)+bfd_get_section_size (section));
00566 #else
00567 printf("searching %p: %s %x-%x\n",info->addr,section->name,bfd_get_section_vma (fabfd, section),bfd_get_section_vma (fabfd, section)+bfd_get_section_size_before_reloc (section));
00568 #endif
00569
00570 if (info->found)
00571 return;
00572 if ((bfd_get_section_flags (fabfd, section) & SEC_ALLOC) == 0)
00573 return;
00574
00575 vma = bfd_get_section_vma (fabfd, section);
00576 if (info->addr < vma)
00577 return;
00578 #ifdef bfd_get_section_size
00579 size = bfd_get_section_size (section);
00580 #else
00581 size = bfd_get_section_size_before_reloc (section);
00582 #endif
00583 if (info->addr >= vma + size)
00584 return;
00585
00586 info->found = bfd_find_nearest_line (fabfd, section, syms, info->addr - vma, &info->filename, &info->functionname, &info->line);
00587 printf("found %d\n",info->found);
00588 }
00589 #endif
00590
00591 #if defined(HAVE_BFD) && HAVE_BFD!=0
00592 void displayStackFrame(unsigned int depth, const struct StackFrame* frame) {
00593 if(abfd!=NULL) {
00594 struct AddrLookupInfo info={(bfd_vma)frame->ra,NULL,NULL,0,FALSE};
00595 asection *p;
00596 for (p = abfd->sections; p != NULL; p = p->next)
00597 stackTraceFindAddress(abfd, p, &info);
00598
00599 if (!info.found) {
00600 printf("%u: %p ????\n",depth,frame->ra);
00601 } else {
00602 const char *name=info.functionname;
00603 char *alloc = NULL;
00604 if (name == NULL || *name == '\0')
00605 name = "??";
00606 else {
00607 alloc = demangle(name);
00608 name = alloc;
00609 }
00610
00611 printf ("%u: %p %s", depth, frame->ra, name);
00612 if (alloc != NULL) {
00613 free (alloc);
00614 alloc=NULL;
00615 }
00616
00617 if(info.filename)
00618 printf(" (%s:%u)\n", info.filename, info.line);
00619 else
00620 printf(" (??:?)\n");
00621 }
00622 return;
00623 }
00624 fprintf(stderr," %p",frame->ra);
00625 }
00626 #else
00627 void displayStackFrame(unsigned int ST_UNUSED(depth), const struct StackFrame* frame) {
00628 ST_BODY_UNUSED(depth);
00629 fprintf(stderr," %p",(void*)frame->ra);
00630 }
00631 #endif
00632
00633
00634 void completeDisplay(int isend) {
00635 #if defined(HAVE_BFD) && HAVE_BFD!=0
00636 if (syms != NULL) {
00637 free(syms);
00638 syms = NULL;
00639 }
00640 if(abfd!=NULL) {
00641 if(!bfd_close(abfd)) {
00642 bfd_error_type err=bfd_get_error();
00643 fprintf(stderr,"Can't close object file `%s': %s\n",bfd_get_filename (abfd),bfd_errmsg(err));
00644 fprintf(stderr,"stacktrace::completeDisplay() failed");
00645 }
00646 abfd=NULL;
00647 if(!isend)
00648 fprintf(stderr," ...\n");
00649 return;
00650 }
00651 #endif
00652 if(!isend)
00653 fprintf(stderr," ...");
00654 fprintf(stderr,"\n");
00655 }
00656
00657 void displayCurrentStackTrace(unsigned int limit, unsigned int skip) {
00658 struct StackFrame cur;
00659 #ifdef DEBUG_STACKTRACE
00660 cur.debug=0;
00661 #endif
00662 unsigned int i;
00663 int more;
00664 if(limit==0)
00665 return;
00666 getCurrentStackFrame(&cur);
00667
00668 beginDisplay();
00669 for(; skip!=0; skip--) {
00670 if(!unrollStackFrame(&cur,&cur)) {
00671 completeDisplay(1);
00672 return;
00673 }
00674
00675 }
00676 for(i=0; (more=unrollStackFrame(&cur,&cur)) && i<limit; i++) {
00677
00678 displayStackFrame(i,&cur);
00679 }
00680 completeDisplay(!more);
00681 }
00682
00683 void displayStackTrace(const struct StackFrame* frame) {
00684 int i;
00685 beginDisplay();
00686 for(i=0; frame!=NULL && frame->caller!=frame; i++) {
00687 displayStackFrame(i,frame);
00688 frame=frame->caller;
00689 }
00690 if(frame!=NULL)
00691 displayStackFrame(i+1,frame);
00692 completeDisplay(frame==NULL);
00693 }
00694
00695
00696 #ifdef __cplusplus
00697 }
00698 #endif
00699
00700
00701
00702
00703
00704
00705
00706
00707
00708
00709
00710