00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 #include "comma/runtime/commart.h"
00013
00014 #include <assert.h>
00015 #include <stddef.h>
00016 #include <stdio.h>
00017 #include <stdlib.h>
00018 #include <string.h>
00019
00020 #define __STDC_FORMAT_MACROS
00021 #include <inttypes.h>
00022
00023
00024
00025
00026
00027 struct _Unwind_Exception;
00028 struct _Unwind_Context;
00029
00030
00031
00032
00033 typedef enum {
00034 _URC_NO_REASON = 0,
00035 _URC_FOREIGN_EXCEPTION_CAUGHT = 1,
00036 _URC_FATAL_PHASE2_ERROR = 2,
00037 _URC_FATAL_PHASE1_ERROR = 3,
00038 _URC_NORMAL_STOP = 4,
00039 _URC_END_OF_STACK = 5,
00040 _URC_HANDLER_FOUND = 6,
00041 _URC_INSTALL_CONTEXT = 7,
00042 _URC_CONTINUE_UNWIND = 8
00043 } _Unwind_Reason_Code;
00044
00045
00046
00047
00048
00049 typedef void (*_Unwind_Exception_Cleanup_Fn) (_Unwind_Reason_Code reason,
00050 struct _Unwind_Exception *exc);
00051
00052
00053
00054
00055 extern _Unwind_Reason_Code
00056 _Unwind_RaiseException(struct _Unwind_Exception *exception_object);
00057
00058
00059
00060
00061
00062 extern void _Unwind_Resume(struct _Unwind_Exception *exception_object);
00063
00064
00065
00066
00067 extern uintptr_t
00068 _Unwind_GetLanguageSpecificData(struct _Unwind_Context *context);
00069
00070
00071
00072
00073 extern uintptr_t
00074 _Unwind_GetRegionStart(struct _Unwind_Context *context);
00075
00076
00077
00078
00079
00080 extern uintptr_t _Unwind_GetIP(struct _Unwind_Context *context);
00081
00082
00083
00084
00085 extern void _Unwind_SetGR(struct _Unwind_Context *context,
00086 int reg, uintptr_t value);
00087
00088
00089
00090
00091 extern void _Unwind_SetIP(struct _Unwind_Context *context, uintptr_t IP);
00092
00093
00094
00095
00096 struct _Unwind_Exception {
00097 uint64_t exception_class;
00098 _Unwind_Exception_Cleanup_Fn exception_cleanup;
00099 uint64_t private_1;
00100 uint64_t private_2;
00101 };
00102
00103
00104
00105
00106 typedef int _Unwind_Action;
00107 static const _Unwind_Action _UA_SEARCH_PHASE = 1;
00108 static const _Unwind_Action _UA_CLEANUP_PHASE = 2;
00109 static const _Unwind_Action _UA_HANDLER_FRAME = 4;
00110 static const _Unwind_Action _UA_FORCE_UNWIND = 8;
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127 typedef enum {
00128 DW_EH_PE_omit = 0xFF,
00129 DW_EH_PE_uleb128 = 0x01,
00130 DW_EH_PE_udata2 = 0x02,
00131 DW_EH_PE_udata4 = 0x03,
00132 DW_EH_PE_udata8 = 0x04,
00133 DW_EH_PE_sleb128 = 0x09,
00134 DW_EH_PE_sdata2 = 0x0A,
00135 DW_EH_PE_sdata4 = 0x0B,
00136 DW_EH_PE_sdata8 = 0x0C,
00137 } Dwarf_Encoding;
00138
00139 typedef enum {
00140 DW_EH_PE_absptr = 0x00,
00141 DW_EH_PE_pcrel = 0x10,
00142 DW_EH_PE_datarel = 0x30,
00143 } Dwarf_Application;
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159 static unsigned char *parse_uleb128(unsigned char *start, uint64_t *value)
00160 {
00161 uint64_t res = 0;
00162 unsigned bits = 0;
00163
00164
00165
00166
00167
00168 while (*start & 0x80) {
00169 res |= (((uint64_t)*start) & 0x7F) << bits;
00170 bits += 7;
00171 ++start;
00172 }
00173
00174
00175
00176
00177 res |= (((uint64_t)*start) & 0x7F) << bits;
00178
00179 *value = res;
00180 return ++start;
00181 }
00182
00183 static unsigned char *parse_sleb128(unsigned char *start, int64_t *value)
00184 {
00185 uint64_t res = 0;
00186 unsigned bits = 0;
00187
00188
00189
00190
00191
00192 while (*start & 0x80) {
00193 res |= (((uint64_t)*start) & 0x7F) << bits;
00194 bits += 7;
00195 ++start;
00196 }
00197
00198
00199
00200
00201 res |= (((uint64_t)*start) & 0x7F) << bits;
00202
00203 if (*start & 0x40) {
00204 bits += 7;
00205 res |= ~((uint64_t)0) << bits;
00206 }
00207
00208 *value = (int64_t)res;
00209 return ++start;
00210 }
00211
00212
00213
00214
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226 static unsigned char *
00227 parse_dwarf_value(Dwarf_Encoding ID, unsigned char *start, uint64_t *value)
00228 {
00229 switch (ID) {
00230
00231 default:
00232 assert(0 && "Invalid DWARF encoding!");
00233 return start;
00234
00235 case DW_EH_PE_omit:
00236 return start;
00237
00238 case DW_EH_PE_uleb128:
00239 return parse_uleb128(start, value);
00240
00241 case DW_EH_PE_udata2: {
00242 uint8_t dst;
00243 memcpy(&dst, start, 2);
00244 *value = (uint64_t)dst;
00245 return start + 2;
00246 }
00247
00248 case DW_EH_PE_udata4: {
00249 uint16_t dst;
00250 memcpy(&dst, start, 4);
00251 *value = (uint64_t)dst;
00252 return start + 4;
00253 }
00254
00255 case DW_EH_PE_udata8:
00256 memcpy(value, start, 8);
00257 return start + 8;
00258
00259 case DW_EH_PE_sleb128:
00260 return parse_sleb128(start, (int64_t*)value);
00261
00262 case DW_EH_PE_sdata2: {
00263 int8_t dst;
00264 memcpy(&dst, start, 2);
00265 *value = (int64_t)dst;
00266 return start + 2;
00267 }
00268
00269 case DW_EH_PE_sdata4: {
00270 int16_t dst;
00271 memcpy(&dst, start, 4);
00272 *value = (int64_t)dst;
00273 return start + 4;
00274 }
00275
00276 case DW_EH_PE_sdata8:
00277 memcpy(value, start, 8);
00278 return start + 8;
00279 }
00280 }
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324
00325
00326
00327
00328
00329
00330
00331
00332
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348
00349
00350
00351
00352
00353
00354
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377
00378
00379
00380
00381
00382
00383
00384
00385
00386
00387
00388
00389
00390
00391
00392
00393
00394
00395
00396
00397
00398
00399
00400
00401
00402
00403
00404
00405 struct LSDA_Header;
00406 struct Call_Site;
00407 struct Action_Record;
00408
00409
00410
00411
00412 struct LSDA_Header {
00413
00414
00415
00416 unsigned char *lsda_start;
00417
00418
00419
00420
00421 intptr_t lpstart;
00422
00423
00424
00425
00426 unsigned char type_table_format;
00427
00428
00429
00430
00431 unsigned char *type_table;
00432
00433
00434
00435
00436 Dwarf_Encoding call_site_format;
00437
00438
00439
00440
00441 uint64_t call_table_size;
00442
00443
00444
00445
00446 unsigned char *call_sites;
00447
00448
00449
00450
00451 unsigned char *actions;
00452 };
00453
00454
00455
00456
00457 struct Call_Site {
00458
00459
00460
00461 int64_t region_start;
00462
00463
00464
00465
00466 uint64_t region_length;
00467
00468
00469
00470
00471 int64_t landing_pad;
00472
00473
00474
00475
00476 uint64_t action_index;
00477 };
00478
00479
00480
00481
00482 struct Action_Record {
00483
00484
00485
00486 uint64_t info_index;
00487
00488
00489
00490
00491 uint64_t next_action;
00492 };
00493
00494
00495
00496
00497
00498
00499
00500
00501 static void fatal_error(const char *message)
00502 {
00503 fprintf(stderr, "EXCEPTION ERROR : %s\n", message);
00504 abort();
00505 }
00506
00507
00508
00509
00510
00511
00512 typedef enum {
00513 COMMA_DYN_MESSAGE = 1 << 0
00514 } comma_exception_flag;
00515
00516
00517
00518
00519
00520
00521
00522
00523 struct comma_exception {
00524 comma_exinfo_t id;
00525 const char *file_name;
00526 uint32_t line_number;
00527 char *message;
00528 uint32_t flags;
00529 struct _Unwind_Exception header;
00530 };
00531
00532
00533
00534
00535 struct comma_exception *
00536 to_comma_exception(struct _Unwind_Exception *header)
00537 {
00538 char *ptr = (char *)header;
00539 ptr -= offsetof(struct comma_exception, header);
00540 return (struct comma_exception *)ptr;
00541 }
00542
00543 struct _Unwind_Exception *
00544 to_Unwind_Exception(struct comma_exception *exception)
00545 {
00546 return &exception->header;
00547 }
00548
00549
00550
00551
00552
00553 static int parse_LSDA(struct _Unwind_Context *context, struct LSDA_Header *lsda)
00554 {
00555 unsigned char *ptr;
00556 uint64_t tmp;
00557
00558 lsda->lsda_start =
00559 (unsigned char *)_Unwind_GetLanguageSpecificData(context);
00560
00561 if (!lsda->lsda_start)
00562 return 1;
00563 else
00564 ptr = lsda->lsda_start;
00565
00566
00567
00568
00569
00570
00571
00572
00573 if (*ptr == DW_EH_PE_omit) {
00574 lsda->lpstart = _Unwind_GetRegionStart(context);
00575 ++ptr;
00576 }
00577 else
00578 fatal_error("Unexpected DWARF value for @LPStart!");
00579
00580
00581
00582
00583
00584
00585 lsda->type_table_format = *ptr;
00586 ++ptr;
00587 if (lsda->type_table_format != DW_EH_PE_absptr)
00588 fatal_error("Unexpected type table format!");
00589
00590
00591
00592
00593 ptr = parse_uleb128(ptr, &tmp);
00594 lsda->type_table = ptr + tmp;
00595
00596
00597
00598
00599
00600 lsda->call_site_format = *ptr;
00601 ++ptr;
00602
00603
00604
00605
00606 ptr = parse_uleb128(ptr, &lsda->call_table_size);
00607
00608
00609
00610
00611 lsda->call_sites = ptr;
00612
00613
00614
00615
00616 lsda->actions = lsda->call_sites + lsda->call_table_size;
00617
00618 return 0;
00619 }
00620
00621
00622
00623
00624
00625 static void dump_LSDA(struct LSDA_Header *lsda)
00626 {
00627 #ifdef COMMA_EH_DEBUG
00628 fputs("LSDA_Header :\n", stderr);
00629 fprintf(stderr, " lsda_start : %" PRIXPTR "\n",
00630 (uintptr_t)lsda->lsda_start);
00631 fprintf(stderr, " lpstart : %" PRIXPTR "\n",
00632 lsda->lpstart);
00633 fprintf(stderr, " tt format : %u\n",
00634 (unsigned)lsda->type_table_format);
00635 fprintf(stderr, " type_table : %" PRIXPTR "\n",
00636 (uintptr_t)lsda->type_table);
00637 fprintf(stderr, " call format : %u\n",
00638 (unsigned)lsda->call_site_format);
00639 fprintf(stderr, " call size : %" PRIu64 "\n",
00640 lsda->call_table_size);
00641 fprintf(stderr, " call sites : %" PRIXPTR "\n",
00642 (uintptr_t)lsda->call_sites);
00643 fprintf(stderr, " actions : %" PRIXPTR "\n",
00644 (uintptr_t)lsda->actions);
00645 #endif
00646 }
00647
00648
00649
00650
00651
00652
00653 static unsigned char *
00654 parse_Call_Site(struct LSDA_Header *lsda,
00655 unsigned char *ptr, struct Call_Site *dst)
00656 {
00657
00658
00659
00660
00661 if (ptr >= (lsda->call_sites + lsda->call_table_size))
00662 return 0;
00663
00664
00665
00666
00667
00668 ptr = parse_dwarf_value(lsda->call_site_format,
00669 ptr, (uint64_t*)&dst->region_start);
00670 ptr = parse_dwarf_value(lsda->call_site_format,
00671 ptr, &dst->region_length);
00672 ptr = parse_dwarf_value(lsda->call_site_format,
00673 ptr, (uint64_t*)&dst->landing_pad);
00674
00675
00676
00677
00678 ptr = parse_uleb128(ptr, &dst->action_index);
00679 return ptr;
00680 }
00681
00682
00683
00684
00685
00686 static void dump_Call_Site(struct Call_Site *site)
00687 {
00688 #ifdef COMMA_EH_DEBUG
00689 fputs("Call_Site :\n", stderr);
00690 fprintf(stderr, " region_start : %" PRIx64 "\n", site->region_start);
00691 fprintf(stderr, " region_length : %" PRIu64 "\n", site->region_length);
00692 fprintf(stderr, " landing_pad : %" PRIx64 "\n", site->landing_pad);
00693 fprintf(stderr, " action_index : %" PRIu64 "\n", site->action_index);
00694 #endif
00695 }
00696
00697
00698
00699
00700
00701 static unsigned char *
00702 parse_Action_Record(unsigned char *ptr, struct Action_Record *dst)
00703 {
00704 unsigned char *start;
00705
00706
00707
00708
00709
00710 ptr = parse_sleb128(ptr, (int64_t*)&dst->info_index);
00711
00712
00713
00714
00715
00716 start = ptr;
00717 parse_sleb128(ptr, (int64_t*)&dst->next_action);
00718
00719
00720
00721
00722
00723 if (dst->next_action)
00724 return start + dst->next_action;
00725 else
00726 return 0;
00727 }
00728
00729
00730
00731
00732
00733
00734
00735 static int
00736 find_applicable_call_site(struct LSDA_Header *lsda,
00737 struct _Unwind_Context *context,
00738 struct Call_Site *dst)
00739 {
00740
00741
00742
00743
00744 unsigned char *IP = (unsigned char *)_Unwind_GetIP(context) - 1;
00745
00746
00747
00748
00749 unsigned char *ptr = lsda->call_sites;
00750
00751
00752
00753
00754
00755 unsigned char *region =
00756 (unsigned char *)_Unwind_GetRegionStart(context);
00757
00758
00759
00760
00761 while ((ptr = parse_Call_Site(lsda, ptr, dst))) {
00762
00763
00764
00765
00766
00767 unsigned char *region_start = region + dst->region_start;
00768 if (IP < region_start)
00769 return 1;
00770
00771
00772
00773
00774
00775 if (IP < region_start + dst->region_length)
00776 return 0;
00777 }
00778
00779
00780
00781
00782
00783 fatal_error("IP out of bounds for call site table!");
00784 return 1;
00785 }
00786
00787
00788
00789
00790
00791
00792
00793 static int
00794 match_exception(struct LSDA_Header *lsda,
00795 struct _Unwind_Exception *exceptionObject,
00796 uint64_t action_index, uint64_t *dst)
00797 {
00798 struct Action_Record action;
00799 unsigned char *ptr;
00800
00801
00802
00803
00804 struct comma_exception *exception = to_comma_exception(exceptionObject);
00805
00806
00807
00808
00809
00810 if (action_index == 0) {
00811 fatal_error("Invalid action index!");
00812 return 1;
00813 }
00814 else {
00815 action_index -= 1;
00816 ptr = lsda->actions + action_index;
00817 }
00818
00819
00820
00821
00822 do {
00823 ptr = parse_Action_Record(ptr, &action);
00824
00825 if (action.info_index > 0) {
00826
00827
00828
00829
00830
00831
00832
00833
00834
00835
00836
00837
00838
00839
00840
00841 comma_exinfo_t *info;
00842 info = (comma_exinfo_t *)lsda->type_table;
00843 info -= action.info_index;
00844
00845
00846
00847
00848
00849
00850 if ((*info == 0) || (exception->id == *info)) {
00851 *dst = action.info_index;
00852 return 0;
00853 }
00854 }
00855 else {
00856
00857
00858
00859
00860 if (action.info_index < 0) {
00861 fatal_error("Filter action detected!");
00862 return 1;
00863 }
00864 }
00865 } while (ptr);
00866
00867
00868
00869
00870 return 1;
00871 }
00872
00873 static void
00874 install_handler(struct _Unwind_Context *context,
00875 struct _Unwind_Exception *exceptionObject,
00876 int64_t landing_pad, uintptr_t id)
00877 {
00878
00879
00880
00881
00882 struct comma_exception *exception = to_comma_exception(exceptionObject);
00883
00884
00885
00886
00887 _Unwind_SetGR(context, __builtin_eh_return_data_regno(0),
00888 (uintptr_t)exception);
00889
00890
00891
00892
00893
00894 _Unwind_SetGR(context, __builtin_eh_return_data_regno(1), id);
00895
00896
00897
00898
00899 _Unwind_SetIP(context, (uintptr_t)landing_pad);
00900 }
00901
00902
00903
00904
00905
00906
00907 static const uint64_t Comma_Exception_Class_ID = 0x534D570434D410Ull;
00908
00909
00910
00911
00912 static void _comma_cleanup_exception(_Unwind_Reason_Code reason,
00913 struct _Unwind_Exception *exc)
00914 {
00915 struct comma_exception *exception = to_comma_exception(exc);
00916 if (exception->flags & COMMA_DYN_MESSAGE)
00917 free(exception->message);
00918 free(exception);
00919 }
00920
00921
00922
00923
00924
00925 void _comma_raise_exception(comma_exinfo_t info,
00926 const char *file_name, uint32_t line_number,
00927 const char *message)
00928 {
00929 struct comma_exception *exception;
00930 struct _Unwind_Exception *exception_object;
00931
00932
00933
00934
00935 exception = malloc(sizeof(struct comma_exception));
00936 exception->id = info;
00937 exception->file_name = file_name;
00938 exception->line_number = line_number;
00939 exception->message = (char*)message;
00940 exception->flags = 0;
00941
00942
00943
00944
00945
00946
00947 exception->header.exception_class = Comma_Exception_Class_ID;
00948 exception->header.exception_cleanup = _comma_cleanup_exception;
00949
00950
00951
00952
00953 exception_object = to_Unwind_Exception(exception);
00954 _Unwind_RaiseException(exception_object);
00955 }
00956
00957
00958
00959
00960
00961 void _comma_raise_nexception(comma_exinfo_t info,
00962 const char *file_name, uint32_t line_number,
00963 const char *message, uint32_t length)
00964 {
00965 struct comma_exception *exception;
00966 struct _Unwind_Exception *exception_object;
00967
00968
00969
00970
00971 exception = malloc(sizeof(struct comma_exception));
00972 exception->id = info;
00973 exception->file_name = file_name;
00974 exception->line_number = line_number;
00975
00976 if (message) {
00977 exception->message = malloc(length + 1);
00978 exception->message = memcpy(exception->message, message, length);
00979 exception->message[length] = 0;
00980 exception->flags = COMMA_DYN_MESSAGE;
00981 }
00982 else {
00983 exception->message = 0;
00984 exception->flags = 0;
00985 }
00986
00987
00988
00989
00990
00991
00992 exception->header.exception_class = Comma_Exception_Class_ID;
00993 exception->header.exception_cleanup = _comma_cleanup_exception;
00994
00995
00996
00997
00998 exception_object = to_Unwind_Exception(exception);
00999 _Unwind_RaiseException(exception_object);
01000 }
01001
01002
01003
01004
01005 void _comma_reraise_exception(struct comma_exception *exception)
01006 {
01007 struct _Unwind_Exception *exception_object = to_Unwind_Exception(exception);
01008 _Unwind_RaiseException(exception_object);
01009 }
01010
01011
01012
01013
01014 void _comma_raise_system(uint32_t id,
01015 const char *file_name, uint32_t line_number,
01016 const char *message)
01017 {
01018 comma_exinfo_t info = _comma_get_exception(id);
01019 _comma_raise_exception(info, file_name, line_number, message);
01020 }
01021
01022
01023
01024
01025 _Unwind_Reason_Code
01026 _comma_eh_personality(int version,
01027 _Unwind_Action actions,
01028 uint64_t exceptionClass,
01029 struct _Unwind_Exception *exceptionObject,
01030 struct _Unwind_Context *context)
01031 {
01032 struct LSDA_Header lsda;
01033 struct Call_Site site;
01034 uint64_t id;
01035 intptr_t handler;
01036
01037
01038
01039
01040
01041 if (version != 1) {
01042 return _URC_FATAL_PHASE1_ERROR;
01043 }
01044
01045
01046
01047
01048
01049
01050 if (exceptionClass != Comma_Exception_Class_ID) {
01051 return _URC_FATAL_PHASE1_ERROR;
01052 }
01053
01054
01055
01056
01057 if (parse_LSDA(context, &lsda))
01058 return _URC_CONTINUE_UNWIND;
01059 dump_LSDA(&lsda);
01060
01061
01062
01063
01064 if (find_applicable_call_site(&lsda, context, &site))
01065 return _URC_CONTINUE_UNWIND;
01066 dump_Call_Site(&site);
01067
01068
01069
01070
01071
01072 if (!site.landing_pad)
01073 return _URC_CONTINUE_UNWIND;
01074
01075
01076
01077
01078
01079 if (!site.action_index) {
01080 fatal_error("Cleanups are not supported!");
01081 return _URC_FATAL_PHASE1_ERROR;
01082 }
01083
01084
01085
01086
01087
01088 if ((actions & _UA_CLEANUP_PHASE) && !(actions & _UA_HANDLER_FRAME))
01089 return _URC_CONTINUE_UNWIND;
01090
01091
01092
01093
01094
01095
01096 if (match_exception(&lsda, exceptionObject, site.action_index, &id))
01097 return _URC_CONTINUE_UNWIND;
01098
01099
01100
01101
01102 if (actions & _UA_SEARCH_PHASE)
01103 return _URC_HANDLER_FOUND;
01104
01105
01106
01107
01108 if (!(actions & _UA_HANDLER_FRAME))
01109 return _URC_CONTINUE_UNWIND;
01110
01111
01112
01113
01114
01115 handler = lsda.lpstart + site.landing_pad;
01116 install_handler(context, exceptionObject, handler, id);
01117 return _URC_INSTALL_CONTEXT;
01118 }
01119
01120
01121
01122
01123
01124 void _comma_unhandled_exception(struct comma_exception *exception)
01125 {
01126 const char *message = exception->message;
01127
01128 if (message)
01129 fprintf(stderr, "Unhandled exception: %s:%d: %s: %s\n",
01130 exception->file_name, exception->line_number,
01131 *exception->id, message);
01132 else
01133 fprintf(stderr, "Unhandled exception: %s:%d: %s\n",
01134 exception->file_name, exception->line_number,
01135 *exception->id);
01136
01137 abort();
01138 }
01139
01140
01141
01142
01143
01144 char *_comma_exinfo_program_error = "PROGRAM_ERROR";
01145 char *_comma_exinfo_constraint_error = "CONSTRAINT_ERROR";
01146 char *_comma_exinfo_assertion_error = "ASSERTION_ERROR";
01147
01148
01149
01150
01151 comma_exinfo_t _comma_get_exception(comma_exception_id id)
01152 {
01153 comma_exinfo_t info = 0;
01154 switch (id) {
01155 default:
01156 fatal_error("Invalid exception ID!");
01157 break;
01158
01159 case COMMA_CONSTRAINT_ERROR_E:
01160 info = &_comma_exinfo_constraint_error;
01161 break;
01162
01163 case COMMA_PROGRAM_ERROR_E:
01164 info = &_comma_exinfo_program_error;
01165 break;
01166
01167 case COMMA_ASSERTION_ERROR_E:
01168 info = &_comma_exinfo_assertion_error;
01169 break;
01170 }
01171 return info;
01172 }